kimi-code/packages/agent-core/test/agent/background
Kai 37eda4e59a
feat(config): add env overrides for loop control and background task limits (#1993)
* feat(config): add env overrides for loop control and background task limits

Add three operational environment overrides, resolved as env > config.toml
> default in both engines (agent-core and agent-core-v2), matching the
existing KIMI_IMAGE_MAX_EDGE_PX / KIMI_SUBAGENT_TIMEOUT_MS pattern:

- KIMI_LOOP_MAX_STEPS_PER_TURN overrides loop_control.max_steps_per_turn
- KIMI_LOOP_MAX_RETRIES_PER_STEP overrides loop_control.max_retries_per_step
- KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS overrides background.max_running_tasks

Invalid values are ignored and fall back to the config value. The v2 engine
resolves them through config section env bindings (effective-only, never
persisted); the v1 engine resolves them at the consumption point.

* fix(config): strip env-bound fields before persisting config writes

Environment overrides resolved into the effective config could be echoed
back through IConfigService.set/replace (e.g. GET then POST
/api/v1/config) and persisted into config.toml, outliving the env var.
This affected the pre-existing image / subagent / keep-alive bindings as
well as the new loop control and max-running-tasks bindings.

Extend ConfigStripEnv with a getEnv parameter and add a shared
stripEnvBoundFields helper: while a field's env var is set, writes
restore the field's raw on-disk value (or drop it) instead of persisting
an echoed env value; when unset, normal writes persist. Register it for
the loopControl, task/background, image, and subagent sections.

Also fix ConfigService.stripEnv looking up rawSnake by the camelCase
domain key; on-disk sections are keyed snake_case.

* fix(config): honor binding parsers when stripping env fields on persist

An invalid env value (e.g. KIMI_LOOP_MAX_STEPS_PER_TURN=abc) is ignored
on the read path but still marked the field env-owned on the write path,
so a config write for that field was silently dropped.
stripEnvBoundFields now derives the guard from the section's envBindings
and skips fields whose env value fails the binding's parse, so invalid
env values are ignored on both paths — and the duplicated field/env
descriptor list is gone.

Also drop function-level comments added beside helpers; agent-core-v2
keeps comments solely in the top-of-file block, so the strip semantics
now live in the config.ts / configService.ts headers.

* fix(config): re-apply env overlays from the env-free base on every read

Two follow-ups from review:

- ConfigService.get()/getAll() re-applied section env bindings on the
  already-overlaid effective cache (and get() mutated it in place), so a
  valid override degraded to invalid or unset kept serving the stale
  value until the next reload — and an echoed stale value could then be
  persisted by a config write. Reads now recompute from a cached
  env-free validated base, so degraded or removed env values fall back
  to the file immediately.
- stripEnvBoundFields restored env-owned fields from the raw snake
  sub-object, missing values persisted under legacy keys (e.g.
  max_steps_per_run). Section stripEnv now receives the env-free,
  fromToml-normalized raw base, so legacy aliases are honored.

* test(kap-server): retry temp-home cleanup in auth tests

auth.test.ts removed its temp home with a plain recursive rm, which races
late async writers in the server shutdown path and flakes with ENOTEMPTY
(seen on main CI and locally on main). Harden the cleanup with the same
maxRetries/retryDelay options sessions.test.ts already uses.

* chore(changeset): consolidate the env-override changesets into two entries

One feature entry (loop/background env overrides, both engines plus the
CLI) and one fix entry (env values persisting on writes and sticking
after degrade/unset, agent-core-v2 plus the CLI).

* fix(config): clear fully-stripped sections and refresh overlay domains on get()

Two follow-ups from review:

- stripEnvBoundFields returned an empty object when every written field
  was env-owned, so a section with a non-empty default (e.g. subagent)
  stored raw = {} and the default stopped applying until the next
  reload. A fully stripped result now clears the raw section instead.
- get(domain) only recomputed env overlays for sections with env
  bindings, so domains written solely by a ConfigEffectiveOverlay
  (models / defaultModel from KIMI_MODEL_NAME) kept serving stale cached
  values after the env changed. get() now derives every non-memory
  domain from the fresh env-free base, matching getAll().

* test(config): drive the get() overlay freshness test with an inline overlay

The previous version imported the model env overlay to activate it, which
the CI runners failed to resolve from this test file (both tsgo and
vitest, while the same specifier resolves elsewhere — not reproducible
locally). An inline ConfigEffectiveOverlay double exercises the same
ConfigService contract with a tighter seam and no module dependency.

* fix(config): preserve unknown fields on full strip and clone nested env targets

Two follow-ups from review:

- stripEnvBoundFields cleared the raw section whenever the stripped
  result was empty, so an env echo write could drop unknown
  forward-compatible fields from the TOML table. An emptied section now
  keeps its raw table while the env-free base still holds other fields,
  and is cleared only when nothing remains (defaults keep applying).
- applyEnvBindings reused nested child objects in place, so a nested
  env binding on a persistable key would mutate the env-free validated
  base and serve stale values after the env var is removed. Children
  are now cloned before descending.

* fix(config): keep the env-free base when a strip leaves nothing to persist

Returning {} for a fully stripped write stored the empty object into the
raw layer while the TOML table survived via rawSnake, so the bases
diverged; a second echo write then saw an empty base, cleared the
section, and deleted the table — dropping unknown forward-compatible
fields. When nothing persistable remains, the write is now a no-op for
the section (the env-free base is kept as-is), and the section is
cleared only when the base is empty.

* fix(config): revalidate stripped results so restored raw values never persist

A strip may restore on-disk values from the unvalidated raw base (e.g.
an env-masked invalid field), smuggling them past the merge-time
validation into the stored config: the section would then fail the next
buildValidated pass, dropping accompanying valid edits from the runtime
while the invalid value stayed persisted. set() now revalidates the
stripped result — discarding the parse output so unknown fields survive
— and rejects the write instead, matching replace().
2026-07-21 19:49:00 +08:00
..
agent-timeout.test.ts feat: detach foreground tasks to background (#821) 2026-06-20 21:24:22 +08:00
foreground-persistence.test.ts feat: detach foreground tasks to background (#821) 2026-06-20 21:24:22 +08:00
heartbeat-stale.test.ts refactor: background task manager persistence (#285) 2026-06-02 12:48:31 +08:00
helpers.ts feat: detach foreground tasks to background (#821) 2026-06-20 21:24:22 +08:00
ids.test.ts feat: detach foreground tasks to background (#821) 2026-06-20 21:24:22 +08:00
manager.test.ts feat(config): add env overrides for loop control and background task limits (#1993) 2026-07-21 19:49:00 +08:00
output-access.test.ts fix: dispose process stdio after managed commands (#822) 2026-06-16 22:09:11 +08:00
output-limit.test.ts fix(agent-core): cap background shell output to match foreground (#1372) 2026-07-04 18:48:04 +08:00
persist.test.ts ci: run unit tests on windows (#1037) 2026-06-26 11:56:41 +08:00
persistence-compat.test.ts refactor: background task manager persistence (#285) 2026-06-02 12:48:31 +08:00
reconcile.test.ts refactor: background task manager persistence (#285) 2026-06-02 12:48:31 +08:00
rpc-events.test.ts refactor: standardize telemetry property names (#1184) 2026-06-29 12:34:38 +08:00