dsh-loop-engine 0.1.7-rc4 → 0.1.7-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ A **published** dsh needs no extra setup. Booting the harness from its **source
22
22
 
23
23
  ## Version compatibility
24
24
 
25
- dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harness version>-rcN`. From `0.1.7-rc1` on, **one release serves two harness generations**: the 0.1.5 line (`>=0.1.5-rc.1 <0.1.6-0`, all sharing the old settings API) and the 0.1.7 line (`>=0.1.7-rc.1 <0.1.8-0`, whose `rc.1` and `rc.2` are both verified). It detects the running generation at load and takes the matching code path, so a single published package installs onto either. Every harness package it consumes declares that union range in `peerDependencies`; a harness outside it fails loudly at boot or session resume. Within a covered line a new `rc` needs no plugin release unless the API surfaces it depends on moved — `docs/compatibility.md` §1 lists the surfaces and the diff command that decides it. `0.1.5-rc3` … `0.1.5-rc5` target harness `0.1.5-rc.2`; `0.1.5-rc1`/`0.1.5-rc2` target `0.1.5-rc.1`; `1.0.0-rc8` … `1.0.0-rc15` target `0.1.2-rc.1`; `1.0.0-rc7` and earlier target `0.1.1-rc.2`.
25
+ dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harness version>-rcN`. From `0.1.7-rc1` on, **one release serves two harness generations**: the 0.1.5 line (`>=0.1.5-rc.1 <0.1.6-0`, all sharing the old settings API) and the 0.1.7 line (`>=0.1.7-rc.1 <0.1.8-0`, whose `rc.1` and `rc.2` are both verified). It detects the running generation at load and takes the matching code path, so a single published package installs onto either. Every harness package it consumes declares that union range in `peerDependencies`; a harness outside it fails loudly at boot or session resume. Within a covered line a new `rc` needs no plugin release unless the API surfaces it depends on moved — `docs/compatibility.md` §1 lists the surfaces and the diff command that decides it. Read `rcN` as **this plugin's own release counter for that harness line**, not as the harness's `rc` number (`0.1.7-rc1` … `0.1.7-rc5` all serve `0.1.7-rc.1` and `0.1.7-rc.2`). `0.1.7-rc1` … `0.1.7-rc5` target harness `0.1.7-rc.2`; `0.1.5-rc3` … `0.1.5-rc5` target harness `0.1.5-rc.2`; `0.1.5-rc1`/`0.1.5-rc2` target `0.1.5-rc.1`; `1.0.0-rc8` … `1.0.0-rc15` target `0.1.2-rc.1`; `1.0.0-rc7` and earlier target `0.1.1-rc.2`.
26
26
 
27
27
  ### Requirements
28
28
 
@@ -48,6 +48,7 @@ dsh-loop-engine is versioned **in lockstep with the harness it targets**: `<harn
48
48
 
49
49
  - Its preset is a copy of `standard` minus the dsh-native rows an external engine replaces — dsh's `/plan`, `/compact` (and auto-compaction), the model-facing goal tool, the human `/goal` command, and the dsh skill rows (one stripped preset per engine). The preset is a harness-generation-dependent artifact: a directory under `$DSH_HOME/.agent-presets/loop-engine-<engine>/` on the 0.1.5 line, and an `@deepseek-ai/dsh-agent-preset` row inserted into the profile patch on the 0.1.7 line, which no longer reads that directory. See [docs/architecture.md](docs/architecture.md) §3.5.
50
50
  - The engine's own slash commands and skill catalog are registered into **that agent's own scope**, so two sessions on different engines never see each other's menus, and the whole surface is released with the agent.
51
+ - **Kimi Code and Codex keep ONE child process per session** (`kimi acp` / `codex app-server`), reused across steps — that is what saves a spawn per step, and it also means a session that finished a turn and is left alone keeps paying for that process (the harness does not release an agent at turn end). Claude Code and Pi spawn per step and hold nothing. Set `childIdleMs` (milliseconds) in the composition entry — the plugin's row in the profile's `cordis.patch.yml` — to shut an idle session's child down, respawning it on the next step; `0` or omitted keeps the child for the session's whole life, which is the default. It closes the child only: the agent, the session, and the page are untouched, so nothing reloads and `session/disposed` is never emitted. See [docs/driver-core.md](docs/driver-core.md) §7.8.
51
52
  - Engine-agnostic dsh commands (`/export`, `/feedback`, `/permission`) keep working and stay.
52
53
 
53
54
  ## Known limitations
package/README.zh.md CHANGED
@@ -22,7 +22,7 @@ dsh plugin --profile web add dsh-loop-engine
22
22
 
23
23
  ## 版本兼容
24
24
 
25
- `dsh-loop-engine` 与它针对的 harness **同版本对齐**:`<harness version>-rcN`。自 `0.1.7-rc1` 起,**一个发布版本同时服务两代 harness**:0.1.5 线(`>=0.1.5-rc.1 <0.1.6-0`,三段 `rc` 共用同一套旧 settings API)与 0.1.7 线(`>=0.1.7-rc.1 <0.1.8-0`,其中 `rc.1` 与 `rc.2` 都已核对)。它在加载期探测当前是哪一代、走对应分支,所以同一份已发布产物在两代上都能安装运行。它消费的每个 harness 包都在 `peerDependencies` 里声明这个并集范围;超出范围会在启动或会话恢复时响亮地失败。在被覆盖的那条线里,新的 `rc` **不需要**插件跟版——除非它依赖的 API 面动了;`docs/compatibility.md` §1 列出了这些面与"要不要改代码"的那条判定命令。`0.1.5-rc3` … `0.1.5-rc5` 针对 harness `0.1.5-rc.2`;`0.1.5-rc1`/`0.1.5-rc2` 针对 `0.1.5-rc.1`;`1.0.0-rc8` … `1.0.0-rc15` 针对 `0.1.2-rc.1`;`1.0.0-rc7` 及更早针对 `0.1.1-rc.2`。
25
+ `dsh-loop-engine` 与它针对的 harness **同版本对齐**:`<harness version>-rcN`。自 `0.1.7-rc1` 起,**一个发布版本同时服务两代 harness**:0.1.5 线(`>=0.1.5-rc.1 <0.1.6-0`,三段 `rc` 共用同一套旧 settings API)与 0.1.7 线(`>=0.1.7-rc.1 <0.1.8-0`,其中 `rc.1` 与 `rc.2` 都已核对)。它在加载期探测当前是哪一代、走对应分支,所以同一份已发布产物在两代上都能安装运行。它消费的每个 harness 包都在 `peerDependencies` 里声明这个并集范围;超出范围会在启动或会话恢复时响亮地失败。在被覆盖的那条线里,新的 `rc` **不需要**插件跟版——除非它依赖的 API 面动了;`docs/compatibility.md` §1 列出了这些面与"要不要改代码"的那条判定命令。`rcN` 读作**本插件对那条 harness 线的第 N 次发布号**,不是 harness 自己的 `rc` 号(`0.1.7-rc1` … `0.1.7-rc5` 都服务 `0.1.7-rc.1` 与 `0.1.7-rc.2`)。`0.1.7-rc1` … `0.1.7-rc5` 针对 harness `0.1.7-rc.2`;`0.1.5-rc3` … `0.1.5-rc5` 针对 harness `0.1.5-rc.2`;`0.1.5-rc1`/`0.1.5-rc2` 针对 `0.1.5-rc.1`;`1.0.0-rc8` … `1.0.0-rc15` 针对 `0.1.2-rc.1`;`1.0.0-rc7` 及更早针对 `0.1.1-rc.2`。
26
26
 
27
27
  ### 环境要求
28
28
 
package/lib/index.js CHANGED
@@ -2389,6 +2389,34 @@ import { LlmError as LlmError3, createAssistantMessage as createAssistantMessage
2389
2389
  import { createScope as createScope2 } from "@deepseek-ai/dsh-scope";
2390
2390
  import { canonicalHeader as canonicalHeader2 } from "@deepseek-ai/dsh-session";
2391
2391
 
2392
+ // src/driver-core/idle-child.ts
2393
+ function createIdleChildCloser(options) {
2394
+ const { idleMs, close, warn } = options;
2395
+ let timer;
2396
+ const cancel = () => {
2397
+ if (timer === void 0) return;
2398
+ clearTimeout(timer);
2399
+ timer = void 0;
2400
+ };
2401
+ return {
2402
+ arm: () => {
2403
+ if (!(idleMs > 0)) return;
2404
+ cancel();
2405
+ timer = setTimeout(() => {
2406
+ timer = void 0;
2407
+ try {
2408
+ close();
2409
+ } catch (error) {
2410
+ warn(`loop-engine: idle shutdown failed to close a child process: ${String(error)}`);
2411
+ }
2412
+ }, idleMs);
2413
+ timer.unref?.();
2414
+ },
2415
+ cancel,
2416
+ dispose: cancel
2417
+ };
2418
+ }
2419
+
2392
2420
  // src/engine-codex/model-handover.ts
2393
2421
  var CODEX_DSH_PROVIDER = "dsh";
2394
2422
  var CODEX_DSH_API_KEY_ENV = "DSH_LOOP_ENGINE_API_KEY";
@@ -2928,9 +2956,18 @@ var CodexAgent = class {
2928
2956
  this.phase = { kind: "idle", lastTurn };
2929
2957
  this.scope = createScope2(loopCtx, this);
2930
2958
  this.ctx = this.scope.ctx.extend({ agent: this });
2959
+ this.idleChild = createIdleChildCloser({
2960
+ idleMs: this.config.childIdleMs,
2961
+ close: () => {
2962
+ this.closeChild();
2963
+ },
2964
+ warn: (message) => {
2965
+ loopCtx.logger.warn(message);
2966
+ }
2967
+ });
2931
2968
  this.scope.ctx.effect(() => () => {
2932
- this.appServer?.dispose();
2933
- this.appServer = void 0;
2969
+ this.idleChild.dispose();
2970
+ this.closeChild();
2934
2971
  }, "codex.appServerClient()");
2935
2972
  }
2936
2973
  loopCtx;
@@ -2982,6 +3019,17 @@ var CodexAgent = class {
2982
3019
  * must respawn the child rather than reuse one configured for another endpoint.
2983
3020
  */
2984
3021
  appServerConfig;
3022
+ /**
3023
+ * Countdown that shuts the child down once this agent has been idle past the
3024
+ * configured window, so an idle session stops paying for a process nothing
3025
+ * is driving. Armed and cancelled by {@link setPhase}.
3026
+ */
3027
+ idleChild;
3028
+ /** Shut the cached child down; the next step's {@link appServerClient} spawns a fresh one. */
3029
+ closeChild() {
3030
+ this.appServer?.dispose();
3031
+ this.appServer = void 0;
3032
+ }
2985
3033
  /**
2986
3034
  * Return the cached app-server client, spawning one on first use, after a dead
2987
3035
  * process, or when the resolved dsh endpoint changed since the last spawn.
@@ -3075,6 +3123,8 @@ var CodexAgent = class {
3075
3123
  setPhase(next) {
3076
3124
  const previousStatus = this.status;
3077
3125
  this.phase = next;
3126
+ if (next.kind === "idle") this.idleChild.arm();
3127
+ else this.idleChild.cancel();
3078
3128
  const status = this.status;
3079
3129
  if (status !== previousStatus) {
3080
3130
  this.dispatch.emit("agent/status", { status });
@@ -3610,14 +3660,16 @@ var Config2 = z2.object({
3610
3660
  sandboxMode: z2.union([...CODEX_SANDBOX_MODES]),
3611
3661
  approvalPolicy: z2.union([...CODEX_APPROVAL_POLICIES]),
3612
3662
  env: z2.dict(z2.string()).default({}),
3613
- model: z2.string()
3663
+ model: z2.string(),
3664
+ childIdleMs: z2.number()
3614
3665
  });
3615
3666
  function resolveConfig2(config) {
3616
3667
  return {
3617
3668
  sandboxMode: config.sandboxMode,
3618
3669
  approvalPolicy: config.approvalPolicy,
3619
3670
  env: config.env ?? {},
3620
- model: config.model
3671
+ model: config.model,
3672
+ childIdleMs: config.childIdleMs ?? 0
3621
3673
  };
3622
3674
  }
3623
3675
  var CODEX_ENGINE_LABEL = "agentLoopCodex";
@@ -5122,9 +5174,18 @@ var KimiAgent = class {
5122
5174
  this.phase = { kind: "idle", lastTurn };
5123
5175
  this.scope = createScope4(loopCtx, this);
5124
5176
  this.ctx = this.scope.ctx.extend({ agent: this });
5177
+ this.idleChild = createIdleChildCloser({
5178
+ idleMs: this.config.childIdleMs,
5179
+ close: () => {
5180
+ this.closeChild();
5181
+ },
5182
+ warn: (message) => {
5183
+ loopCtx.logger.warn(message);
5184
+ }
5185
+ });
5125
5186
  this.scope.ctx.effect(() => () => {
5126
- this.acp?.dispose();
5127
- this.acp = void 0;
5187
+ this.idleChild.dispose();
5188
+ this.closeChild();
5128
5189
  }, "kimi.acpClient()");
5129
5190
  }
5130
5191
  loopCtx;
@@ -5157,6 +5218,17 @@ var KimiAgent = class {
5157
5218
  * every step.
5158
5219
  */
5159
5220
  handoverEnvCache;
5221
+ /**
5222
+ * Countdown that shuts the child down once this agent has been idle past the
5223
+ * configured window, so an idle session stops paying for a process nothing
5224
+ * is driving. Armed and cancelled by {@link setPhase}.
5225
+ */
5226
+ idleChild;
5227
+ /** Shut the cached child down; the next step's {@link acpClient} spawns a fresh one. */
5228
+ closeChild() {
5229
+ this.acp?.dispose();
5230
+ this.acp = void 0;
5231
+ }
5160
5232
  get status() {
5161
5233
  return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
5162
5234
  }
@@ -5164,6 +5236,8 @@ var KimiAgent = class {
5164
5236
  setPhase(next) {
5165
5237
  const previousStatus = this.status;
5166
5238
  this.phase = next;
5239
+ if (next.kind === "idle") this.idleChild.arm();
5240
+ else this.idleChild.cancel();
5167
5241
  const status = this.status;
5168
5242
  if (status !== previousStatus) {
5169
5243
  this.dispatch.emit("agent/status", { status });
@@ -5732,13 +5806,15 @@ var KIMI_DISPOSE_GRACE_MS = 3e3;
5732
5806
  var Config4 = z4.object({
5733
5807
  model: z4.string(),
5734
5808
  env: z4.dict(z4.string()).default({}),
5735
- bin: z4.string()
5809
+ bin: z4.string(),
5810
+ childIdleMs: z4.number()
5736
5811
  });
5737
5812
  function resolveConfig4(config) {
5738
5813
  return {
5739
5814
  model: config.model,
5740
5815
  env: config.env ?? {},
5741
- bin: kimiBinResolver(config.bin)
5816
+ bin: kimiBinResolver(config.bin),
5817
+ childIdleMs: config.childIdleMs ?? 0
5742
5818
  };
5743
5819
  }
5744
5820
  var KIMI_ENGINE_LABEL = "agentLoopKimi";
@@ -7498,6 +7574,7 @@ var Config5 = z6.object({
7498
7574
  piProvider: z6.string(),
7499
7575
  piThinking: z6.string(),
7500
7576
  kimiBin: z6.string(),
7577
+ childIdleMs: z6.number(),
7501
7578
  // The live fields exist only on the 0.1.7 line; the 0.1.5 line carries the
7502
7579
  // selection in a settings section instead. The empty arm never runs when the
7503
7580
  // coverage job is on 0.1.7; the 0.1.5 dep set is exercised by
@@ -7568,7 +7645,8 @@ function codexConfig(config) {
7568
7645
  ...config.sandboxMode === void 0 ? {} : { sandboxMode: config.sandboxMode },
7569
7646
  ...config.approvalPolicy === void 0 ? {} : { approvalPolicy: config.approvalPolicy },
7570
7647
  ...config.env === void 0 ? {} : { env: config.env },
7571
- ...config.model === void 0 ? {} : { model: config.model }
7648
+ ...config.model === void 0 ? {} : { model: config.model },
7649
+ ...config.childIdleMs === void 0 ? {} : { childIdleMs: config.childIdleMs }
7572
7650
  };
7573
7651
  }
7574
7652
  function piConfig(config) {
@@ -7584,7 +7662,8 @@ function kimiConfig(config) {
7584
7662
  return {
7585
7663
  ...config.model === void 0 ? {} : { model: config.model },
7586
7664
  ...config.env === void 0 ? {} : { env: config.env },
7587
- ...config.kimiBin === void 0 ? {} : { bin: config.kimiBin }
7665
+ ...config.kimiBin === void 0 ? {} : { bin: config.kimiBin },
7666
+ ...config.childIdleMs === void 0 ? {} : { childIdleMs: config.childIdleMs }
7588
7667
  };
7589
7668
  }
7590
7669
  function apply(ctx, config) {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Idle shutdown for a driver's persistent child process.
3
+ *
4
+ * Two engines keep one child alive per session so a step never pays a spawn:
5
+ * Kimi's `kimi acp` and Codex's app-server. Nothing in the harness releases an
6
+ * agent when a turn ends — an agent lives as long as its session's scope — so
7
+ * that child outlives the work that needed it and an idle session keeps
8
+ * costing what its child costs (tens to hundreds of MB for a Node CLI). The
9
+ * closer arms a countdown whenever its owner goes idle and runs the owner's
10
+ * close when the countdown expires, so the next step's own lazy accessor
11
+ * spawns a fresh child.
12
+ *
13
+ * The closer is inert unless `idleMs` is positive: a deployment that does not
14
+ * configure idle shutdown keeps every child for the process's lifetime, which
15
+ * is the behaviour that predates this reasoner.
16
+ *
17
+ * This is deliberately NOT an agent release: disposing the agent emits
18
+ * `session/disposed`, which the browser half reads as "this session is gone"
19
+ * with no way back in that page's lifetime (`router-loop.ts` `move`). Closing
20
+ * the child leaves the agent, the session, and the page exactly where they are.
21
+ *
22
+ * @module dsh-loop-engine/driver-core/idle-child
23
+ */
24
+ /** An armed-on-idle countdown that closes one child process. */
25
+ export interface IdleChildCloser {
26
+ /**
27
+ * (Re)start the countdown. Called when the owner settles into an idle phase;
28
+ * a no-op while idle shutdown is disabled.
29
+ */
30
+ arm(): void;
31
+ /**
32
+ * Cancel a pending countdown, for the moment the child is needed again.
33
+ * Cancelling a countdown that never armed is a no-op.
34
+ */
35
+ cancel(): void;
36
+ /**
37
+ * Cancel the countdown and drop the timer. Called from the owner's teardown,
38
+ * so a disposed agent never closes a child through a stale callback.
39
+ */
40
+ dispose(): void;
41
+ }
42
+ /** Input of {@link createIdleChildCloser}. */
43
+ export interface IdleChildCloserOptions {
44
+ /** Idle window in milliseconds; zero or less leaves the child alone forever. */
45
+ readonly idleMs: number;
46
+ /** Close the child; the owner's next accessor call spawns a fresh one. */
47
+ readonly close: () => void;
48
+ /** Report a failing close without letting it escape the countdown. */
49
+ readonly warn: (message: string) => void;
50
+ }
51
+ /**
52
+ * Build one owner's idle countdown.
53
+ * @param options - the idle window, the close to run, and a diagnostic sink.
54
+ * @returns the closer, inert while the idle window is not positive.
55
+ */
56
+ export declare function createIdleChildCloser(options: IdleChildCloserOptions): IdleChildCloser;
57
+ //# sourceMappingURL=idle-child.d.ts.map
@@ -70,7 +70,15 @@ export declare class CodexAgent implements Agent {
70
70
  * must respawn the child rather than reuse one configured for another endpoint.
71
71
  */
72
72
  private appServerConfig;
73
+ /**
74
+ * Countdown that shuts the child down once this agent has been idle past the
75
+ * configured window, so an idle session stops paying for a process nothing
76
+ * is driving. Armed and cancelled by {@link setPhase}.
77
+ */
78
+ private readonly idleChild;
73
79
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig);
80
+ /** Shut the cached child down; the next step's {@link appServerClient} spawns a fresh one. */
81
+ private closeChild;
74
82
  /**
75
83
  * Return the cached app-server client, spawning one on first use, after a dead
76
84
  * process, or when the resolved dsh endpoint changed since the last spawn.
@@ -40,6 +40,12 @@ export interface Config {
40
40
  env?: Record<string, string>;
41
41
  /** Fallback model for the app-server thread, used when the session selects none; Codex native settings own the model when omitted. */
42
42
  model?: string;
43
+ /**
44
+ * Idle window in milliseconds before a session's `codex app-server` child is
45
+ * shut down, spawned again by the next step. Omitted or non-positive keeps
46
+ * the child for the session's whole life.
47
+ */
48
+ childIdleMs?: number;
43
49
  }
44
50
  /** Schema of the Codex loop plugin configuration. */
45
51
  export declare const Config: z<Config>;
@@ -15,5 +15,12 @@ export interface ResolvedConfig {
15
15
  readonly approvalPolicy: CodexApprovalPolicy | undefined;
16
16
  readonly env: Record<string, string>;
17
17
  readonly model: string | undefined;
18
+ /**
19
+ * How long an idle session keeps its `codex app-server` child, in
20
+ * milliseconds; zero or less keeps it for the session's whole life. See
21
+ * `driver-core/idle-child.ts` for why the child, and not the agent, is what
22
+ * gets released.
23
+ */
24
+ readonly childIdleMs: number;
18
25
  }
19
26
  //# sourceMappingURL=types.d.ts.map
@@ -56,7 +56,15 @@ export declare class KimiAgent implements Agent {
56
56
  * every step.
57
57
  */
58
58
  private handoverEnvCache;
59
+ /**
60
+ * Countdown that shuts the child down once this agent has been idle past the
61
+ * configured window, so an idle session stops paying for a process nothing
62
+ * is driving. Armed and cancelled by {@link setPhase}.
63
+ */
64
+ private readonly idleChild;
59
65
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: KimiSpawnCapability, bin: string);
66
+ /** Shut the cached child down; the next step's {@link acpClient} spawns a fresh one. */
67
+ private closeChild;
60
68
  get status(): AgentStatus;
61
69
  /** Commit a phase and publish its externally visible status transition. */
62
70
  private setPhase;
@@ -32,6 +32,12 @@ export interface Config {
32
32
  env?: Record<string, string>;
33
33
  /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
34
34
  bin?: string;
35
+ /**
36
+ * Idle window in milliseconds before a session's `kimi acp` child is shut
37
+ * down, spawned again by the next step. Omitted or non-positive keeps the
38
+ * child for the session's whole life.
39
+ */
40
+ childIdleMs?: number;
35
41
  }
36
42
  /** Schema of the Kimi loop plugin configuration. */
37
43
  export declare const Config: z<Config>;
@@ -19,5 +19,12 @@ export interface ResolvedConfig {
19
19
  readonly env: Record<string, string>;
20
20
  /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
21
21
  readonly bin: string;
22
+ /**
23
+ * How long an idle session keeps its `kimi acp` child, in milliseconds; zero
24
+ * or less keeps it for the session's whole life. See
25
+ * `driver-core/idle-child.ts` for why the child, and not the agent, is what
26
+ * gets released.
27
+ */
28
+ readonly childIdleMs: number;
22
29
  }
23
30
  //# sourceMappingURL=types.d.ts.map
@@ -74,6 +74,14 @@ export interface Config extends ClaudeCodeConfig {
74
74
  piThinking?: string;
75
75
  /** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
76
76
  kimiBin?: string;
77
+ /**
78
+ * Idle window in milliseconds after which a hosted session's PERSISTENT child
79
+ * process (Kimi's `kimi acp`, Codex's app-server) is shut down and spawned
80
+ * again by the next step. Zero or omitted keeps the child for the session's
81
+ * whole life. Only these two engines hold a child between steps: the Pi and
82
+ * Claude Code drivers spawn per step, so the knob does not reach them.
83
+ */
84
+ childIdleMs?: number;
77
85
  /**
78
86
  * Default engine NEW sessions are created on. A live, profile-backed field:
79
87
  * the settings shell edits it and the running plugin reads it through this
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-loop-engine",
3
3
  "description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin (Claude Code / Codex / Pi / Kimi Code drivers) maintained by @kuun993, zero main-repo changes",
4
- "version": "0.1.7-rc4",
4
+ "version": "0.1.7-rc5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },