dsh-loop-engine 0.1.5-rc3 → 0.1.5-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.
Files changed (54) hide show
  1. package/README.md +44 -187
  2. package/README.zh.md +45 -103
  3. package/lib/client.js +887 -266
  4. package/lib/index.js +2216 -915
  5. package/lib/invariant.js +43 -45
  6. package/lib/types/agent-preset-ids.d.ts +303 -0
  7. package/lib/types/client/LoopEngineBadge.d.ts +44 -17
  8. package/lib/types/client/LoopEngineComposerSelect.d.ts +79 -13
  9. package/lib/types/client/LoopEngineSection.d.ts +5 -4
  10. package/lib/types/client/locales.d.ts +133 -7
  11. package/lib/types/client/reload.d.ts +135 -0
  12. package/lib/types/client/session-engine.d.ts +474 -0
  13. package/lib/types/client/store.d.ts +1 -1
  14. package/lib/types/client/turn-status.d.ts +112 -10
  15. package/lib/types/client/use-session-engine.d.ts +66 -0
  16. package/lib/types/commands.d.ts +11 -3
  17. package/lib/types/driver-core/host-servers.d.ts +106 -0
  18. package/lib/types/driver-core/hosted-engine-runtime.d.ts +190 -0
  19. package/lib/types/driver-core/hosted-tool-vocabulary.d.ts +72 -0
  20. package/lib/types/driver-core/model-handover.d.ts +134 -0
  21. package/lib/types/driver-core/ownership.d.ts +6 -5
  22. package/lib/types/driver-core/prompt.d.ts +32 -0
  23. package/lib/types/driver-core/session-lifetime.d.ts +62 -0
  24. package/lib/types/driver-core/session-model.d.ts +82 -0
  25. package/lib/types/engine-claude/agent.d.ts +23 -3
  26. package/lib/types/engine-claude/loop.d.ts +16 -15
  27. package/lib/types/engine-codex/agent.d.ts +22 -3
  28. package/lib/types/engine-codex/appserver/client.d.ts +15 -2
  29. package/lib/types/engine-codex/loop.d.ts +13 -15
  30. package/lib/types/engine-codex/model-handover.d.ts +56 -0
  31. package/lib/types/engine-kimi/acp/client.d.ts +10 -0
  32. package/lib/types/engine-kimi/agent.d.ts +19 -2
  33. package/lib/types/engine-kimi/commands.d.ts +18 -14
  34. package/lib/types/engine-kimi/loop.d.ts +14 -16
  35. package/lib/types/engine-kimi/model-handover.d.ts +32 -0
  36. package/lib/types/engine-kimi/process.d.ts +2 -2
  37. package/lib/types/engine-kimi/types.d.ts +1 -1
  38. package/lib/types/engine-of-session.d.ts +97 -0
  39. package/lib/types/engine-pi/agent.d.ts +25 -23
  40. package/lib/types/engine-pi/loop.d.ts +13 -23
  41. package/lib/types/engine-pi/model-handover.d.ts +39 -0
  42. package/lib/types/engine-pi/types.d.ts +2 -2
  43. package/lib/types/engine-remote.d.ts +192 -0
  44. package/lib/types/engine-surface.d.ts +36 -0
  45. package/lib/types/index.d.ts +51 -50
  46. package/lib/types/invariant.d.ts +8 -5
  47. package/lib/types/model-selection-reset.d.ts +271 -0
  48. package/lib/types/patch-manager.d.ts +57 -39
  49. package/lib/types/preset.d.ts +39 -26
  50. package/lib/types/provider-route.d.ts +83 -36
  51. package/lib/types/router-loop.d.ts +406 -0
  52. package/lib/types/session-engine-store.d.ts +138 -0
  53. package/lib/types/settings.d.ts +12 -11
  54. package/package.json +109 -104
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The session-scoped half of one published hosted transaction: the session's
3
+ * entry in the store, and the write handle that stores its events.
4
+ *
5
+ * Ownership is ONE object rather than two closure variables because an engine
6
+ * swap MOVES it. A hot swap replaces a session's agent while the session itself
7
+ * stays live: the retiring machine hands its lifetime to the successor instead
8
+ * of releasing it, and the successor releases it later exactly as the agent that
9
+ * entered the session would have. Releasing it at the handover point would emit
10
+ * `session/disposed`, which the browser half reads as the session being gone —
11
+ * its session list drops the row, its composer renders "session unavailable",
12
+ * and the conversation view falls back to the workspace picker — so a live
13
+ * session would be torn out from under the page attached to it.
14
+ *
15
+ * The two release steps are separate operations because their ORDER around the
16
+ * agent registry's own release is load-bearing: `agent/disposed` is published
17
+ * after driver quiescence and BEFORE the session is detached (the event's
18
+ * contract in `@deepseek-ai/dsh-agent`), and the write handle closes before the
19
+ * store attachment is released so its buffered closing events are drained
20
+ * durably. Users of this type therefore close the handle, detach the agent, then
21
+ * leave the store.
22
+ *
23
+ * Neither step needs a memo of its own: `SessionHandle.close()` is idempotent by
24
+ * contract, and the detacher `SessionsService.enter` returns is single-shot.
25
+ *
26
+ * @module dsh-loop-engine/driver-core/session-lifetime
27
+ */
28
+ import type { Session } from '@deepseek-ai/dsh-session';
29
+ import type { SessionHandle } from '@deepseek-ai/dsh-session-persistence';
30
+ /**
31
+ * One live session's lifetime resources, owned by whichever agent drives it.
32
+ */
33
+ export declare class SessionLifetime {
34
+ /** The live session these resources belong to. */
35
+ readonly session: Session;
36
+ /** The write handle this lifetime owns, or `undefined` when no persistence backend is mounted. */
37
+ private readonly handle;
38
+ /** The store's detacher; `undefined` until the entry is bound by the transaction that entered the session. */
39
+ private detach;
40
+ /**
41
+ * @param session - the live session whose entry and write handle these are.
42
+ * @param handle - the write handle the transaction acquired, if a backend is mounted.
43
+ */
44
+ constructor(session: Session, handle: SessionHandle | undefined);
45
+ /**
46
+ * Whether the session's store entry is already held: true for a lifetime a
47
+ * retiring machine handed over, false for one whose transaction still has to
48
+ * enter the session it prepared.
49
+ */
50
+ get entered(): boolean;
51
+ /**
52
+ * Bind the store entry this lifetime owns. Called once, by the transaction
53
+ * that enters the session; a joined session's entry is already bound.
54
+ * @param detach - the detacher the session's own `enter` returned.
55
+ */
56
+ bind(detach: () => void): void;
57
+ /** Drain and close the write handle — the durability barrier that precedes leaving the store. */
58
+ closeHandle(): Promise<void>;
59
+ /** Remove the session from the store, publishing its paired disposal exactly once. */
60
+ leaveStore(): void;
61
+ }
62
+ //# sourceMappingURL=session-lifetime.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The dsh model selection a hosted engine is handed, and the ONE judgement that
3
+ * decides it.
4
+ *
5
+ * A session's model seat is the harness's own per-session fact: the host writes
6
+ * a `model/selection` event when the user picks a model, and derives the
7
+ * session's selection from the newest pending selection, else the logged
8
+ * `request/header` (`packages/api/session-controller/src/agent.ts`,
9
+ * `selectionFor`). A hosted engine owns its model natively, but it can still be
10
+ * handed a real dsh model when the session selected one — that is what this
11
+ * module answers.
12
+ *
13
+ * Three cases, decided once here and consumed by all four drivers:
14
+ *
15
+ * - NO selection, or one whose provider is a hosted engine route label
16
+ * ({@link isHostedProviderRoute}: today's shared `external`, or a label an
17
+ * earlier build logged) → `undefined`. The engine keeps its own native
18
+ * default or the model the deployment pinned in its composition; nothing is
19
+ * sent, so the engine is not asked a question it never had an opinion on.
20
+ * - a REAL dsh model (any other provider) → `{ provider, model }`. Each engine
21
+ * renders that into its own interface: Pi's `--model <provider>/<model>`,
22
+ * Claude Code's `Options.model`, Codex's `thread/start` `model`, and Kimi's
23
+ * ACP `session/set_model`. Whether the engine can actually serve the model is
24
+ * the engine's own business: a refusal is reported, never swallowed.
25
+ *
26
+ * The read is the host's own ({@link currentSelection}), so the value an engine
27
+ * gets is the value the host would install for the session — and it is taken
28
+ * fresh on every step, because a session's model can change mid-conversation.
29
+ *
30
+ * @module dsh-loop-engine/driver-core/session-model
31
+ */
32
+ import type { Context } from '@deepseek-ai/cordis';
33
+ import type { Session } from '@deepseek-ai/dsh-session';
34
+ import type { SessionModelSelection, SessionProjectionsService } from './host-servers.ts';
35
+ /** The model a session's own selection asks a hosted engine to run. */
36
+ export interface SessionModelOverride {
37
+ /** Provider route the selection names, in the engine's own provider vocabulary. */
38
+ readonly provider: string;
39
+ /** Provider-owned model id the selection names. */
40
+ readonly model: string;
41
+ }
42
+ /**
43
+ * The selection the host will use for this session, by the host's own read
44
+ * (`ApiSessionAgentController.selectionFor`): a pending selection — one
45
+ * appended, not yet consumed by a matching recorded request — outranks the
46
+ * logged header, and the header answers when nothing is pending.
47
+ *
48
+ * A deployment with no session-projection registry is not an error: the read
49
+ * simply skips the pending half and answers from the logged header, exactly as
50
+ * a session whose log carries only a header would.
51
+ * @param session - the session whose selection is being read.
52
+ * @param projections - the host session-projection registry, when composed.
53
+ * @returns the current selection, or undefined when the log records none.
54
+ */
55
+ export declare function currentSelection(session: Session, projections: SessionProjectionsService | undefined): SessionModelSelection | undefined;
56
+ /**
57
+ * The model one hosted engine should be handed for this selection, or
58
+ * `undefined` to leave the engine to its own default (or the deployment's pin).
59
+ *
60
+ * The provider is the whole test: a hosted engine route label — the shared
61
+ * `external` this plugin serves as a placeholder, or a per-engine label an
62
+ * earlier build logged — means "the engine decides", not a model to send. Any
63
+ * other provider is a real dsh model, and the plugin passes it through rather
64
+ * than second-guessing whether the engine can serve it: a rejection is the
65
+ * engine's to report, and this plugin surfaces it.
66
+ * @param selection - the session's current selection, or undefined when none.
67
+ * @returns the model to hand over, or undefined to send nothing.
68
+ */
69
+ export declare function sessionModelOverride(selection: SessionModelSelection | undefined): SessionModelOverride | undefined;
70
+ /**
71
+ * The model override for one session, read live from its log and judged by
72
+ * {@link sessionModelOverride}.
73
+ *
74
+ * The four drivers' single entry: each calls this at the top of every step, so a
75
+ * model picked mid-conversation lands on the engine's next request rather than
76
+ * being frozen when the agent was built.
77
+ * @param ctx - the driver's context, carrying the session-projection registry.
78
+ * @param session - the session whose selection is being resolved.
79
+ * @returns the model to hand the engine, or undefined to send nothing.
80
+ */
81
+ export declare function sessionModelOverrideOf(ctx: Context, session: Session): SessionModelOverride | undefined;
82
+ //# sourceMappingURL=session-model.d.ts.map
@@ -12,8 +12,14 @@ import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
12
12
  import type { Context } from '@deepseek-ai/cordis';
13
13
  import type { ResolvedConfig } from './types.ts';
14
14
  import { DriverInbox } from '../driver-core/inbox.ts';
15
- /** Provider route label used for logged header snapshots and message provenance. */
16
- export declare const PROVIDER = "claude-code";
15
+ import { type SpawnCapability } from './sdk.ts';
16
+ /**
17
+ * Provider route label this driver logs into request/header snapshots and
18
+ * message provenance — the ONE route every hosted engine shares
19
+ * ({@link HOSTED_ROUTE_LABEL}), so all four engines select `external/default`
20
+ * and the model menu carries a single group instead of one per engine.
21
+ */
22
+ export declare const PROVIDER = "external";
17
23
  /** Drives one session through turn and step boundaries on Claude Code. */
18
24
  export declare class ClaudeCodeAgent implements Agent {
19
25
  private loopCtx;
@@ -21,6 +27,13 @@ export declare class ClaudeCodeAgent implements Agent {
21
27
  readonly options: AgentOptions;
22
28
  readonly session: Session;
23
29
  private readonly config;
30
+ /**
31
+ * Process-spawn capability handed down from the engine, which resolves it
32
+ * from the host's subprocess service. Held as a capability rather than read
33
+ * off the context because a service PROPERTY read requires the reading
34
+ * fiber to have injected it, and this agent's context is the engine's.
35
+ */
36
+ private readonly spawn;
24
37
  readonly inbox: DriverInbox;
25
38
  private phase;
26
39
  private activityDone;
@@ -44,7 +57,14 @@ export declare class ClaudeCodeAgent implements Agent {
44
57
  * usage record is only meaningful while one step holds the whole query.
45
58
  */
46
59
  private rotated;
47
- constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig);
60
+ constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig,
61
+ /**
62
+ * Process-spawn capability handed down from the engine, which resolves it
63
+ * from the host's subprocess service. Held as a capability rather than read
64
+ * off the context because a service PROPERTY read requires the reading
65
+ * fiber to have injected it, and this agent's context is the engine's.
66
+ */
67
+ spawn: SpawnCapability);
48
68
  get status(): AgentStatus;
49
69
  /** Commit a phase and publish its externally visible status transition. */
50
70
  private setPhase;
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Claude Code loop engine module: hosts the AgentFactory that drives every
3
- * session through the official Claude Agent SDK, one stateless query per dsh
4
- * step, with the durable session log as the sole source of model context.
5
- * dsh-loop-engine constructs this factory when the Claude Code engine is
6
- * selected; this module is a library, not a Cordis plugin entry.
2
+ * Claude Code loop engine module: drives every session it is handed through
3
+ * the official Claude Agent SDK, one stateless query per dsh step, with the
4
+ * durable session log as the sole source of model context. The router routes a
5
+ * session here on the plugin's own engine record, else its recorded agent
6
+ * preset; this module is a library, not a Cordis plugin entry.
7
7
  *
8
8
  * @module dsh-loop-engine/engine-claude
9
9
  */
@@ -12,8 +12,9 @@ import z from '@deepseek-ai/schemastery';
12
12
  import type { AgentOptions } from '@deepseek-ai/dsh-agent';
13
13
  import type { Session, SessionId } from '@deepseek-ai/dsh-session';
14
14
  import { ClaudeCodeAgent } from './agent.ts';
15
+ import { type SpawnCapability } from './sdk.ts';
15
16
  import type { ClaudeCodePermissionMode, ResolvedConfig } from './types.ts';
16
- import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
17
+ import { HostedEngineRuntime } from '../driver-core/hosted-engine-runtime.ts';
17
18
  /** Deployment-selectable non-interactive Claude Code permission modes. */
18
19
  export declare const CLAUDE_CODE_PERMISSION_MODES: readonly ClaudeCodePermissionMode[];
19
20
  /** Deployment-owned configuration for the Claude Code loop plugin. */
@@ -31,7 +32,7 @@ export interface Config {
31
32
  permissionMode?: ClaudeCodePermissionMode;
32
33
  /** Explicit environment entries layered over the credential-scrubbed parent environment. */
33
34
  env?: Record<string, string>;
34
- /** Model label for the logged request header; Claude Code native settings own the actual model. */
35
+ /** Model passed to each query (`Options.model`, overridden by the session's own pick); also the request-header label. Claude Code native settings own the model when omitted. */
35
36
  model?: string;
36
37
  /** Grace in milliseconds for Claude Code process-tree termination. */
37
38
  disposeGraceMs?: number;
@@ -40,21 +41,21 @@ export interface Config {
40
41
  }
41
42
  /** Schema of the Claude Code loop plugin configuration. */
42
43
  export declare const Config: z<Config>;
43
- /** Host-face ctx key for the Claude Code loop service. */
44
+ /** Host-face ctx key this engine's runtime is registered under. */
45
+ export declare const CLAUDE_CODE_ENGINE_LABEL = "agentLoopClaudeCode";
44
46
  declare module '@deepseek-ai/cordis' {
45
47
  interface Context {
46
48
  agentLoopClaudeCode: ClaudeCodeLoop;
47
49
  }
48
50
  }
49
51
  /**
50
- * Concrete AgentFactory and driver service of the Claude Code loop. Creation
51
- * and resume follow the registry factory contract and the shared publication
52
- * transaction: prepare, run setup, then publish through both registries,
53
- * announce, and emit `agent/session-start`.
52
+ * Creation/resume machinery for the Claude Code engine. The process-wide
53
+ * AgentFactory slot belongs to the router, which delegates each session to the
54
+ * engine its preset names; this class is that engine's driver, not a plugin.
54
55
  */
55
- export declare class ClaudeCodeLoop extends HostedLoopFactory<ResolvedConfig, ClaudeCodeAgent> {
56
- /** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
57
- static inject: string[];
56
+ export declare class ClaudeCodeLoop extends HostedEngineRuntime<ResolvedConfig, ClaudeCodeAgent> {
57
+ /** Process-spawn capability handed to every agent, sandboxed by the subprocess seam. */
58
+ readonly spawn: SpawnCapability;
58
59
  constructor(ctx: Context, config: Config);
59
60
  /** Construct the Claude Code driver for one prepared session. */
60
61
  protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): ClaudeCodeAgent;
@@ -19,8 +19,13 @@ import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
19
19
  import type { Context } from '@deepseek-ai/cordis';
20
20
  import type { ResolvedConfig } from './types.ts';
21
21
  import { DriverInbox } from '../driver-core/inbox.ts';
22
- /** Provider route label used for logged header snapshots and message provenance. */
23
- export declare const PROVIDER = "codex";
22
+ /**
23
+ * Provider route label this driver logs into request/header snapshots and
24
+ * message provenance — the ONE route every hosted engine shares
25
+ * ({@link HOSTED_ROUTE_LABEL}), so all four engines select `external/default`
26
+ * and the model menu carries a single group instead of one per engine.
27
+ */
28
+ export declare const PROVIDER = "external";
24
29
  /** Drives one session through turn and step boundaries on Codex. */
25
30
  export declare class CodexAgent implements Agent {
26
31
  private loopCtx;
@@ -59,8 +64,22 @@ export declare class CodexAgent implements Agent {
59
64
  private beginSegment;
60
65
  /** Lazily created app-server client, reused across steps and released on scope teardown. */
61
66
  private appServer;
67
+ /**
68
+ * The spawn configuration (`argv` + `env`) the cached client was built from.
69
+ * Codex's endpoint lives in the child's own command line, so a handover change
70
+ * must respawn the child rather than reuse one configured for another endpoint.
71
+ */
72
+ private appServerConfig;
62
73
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig);
63
- /** Return the cached app-server client, spawning one on first use or after a dead process. */
74
+ /**
75
+ * Return the cached app-server client, spawning one on first use, after a dead
76
+ * process, or when the resolved dsh endpoint changed since the last spawn.
77
+ *
78
+ * A handover is codex's own `-c` configuration for a dsh provider, so a
79
+ * different endpoint (different provider, base URL, protocol, or credential)
80
+ * needs a different child — the config is fixed when the process starts.
81
+ * @param handover - the session's resolved dsh endpoint, or undefined to leave codex to its own configuration.
82
+ */
64
83
  private appServerClient;
65
84
  /**
66
85
  * Answer one server-initiated interaction. Approvals go through the dsh
@@ -35,8 +35,21 @@ export declare class AppServerClient {
35
35
  get closed(): boolean;
36
36
  /** Create a client by spawning `codex app-server`. */
37
37
  private constructor();
38
- /** Spawn the pinned app-server dependency and initialize the client. */
39
- static create(): Promise<AppServerClient>;
38
+ /**
39
+ * Spawn the pinned app-server dependency and initialize the client.
40
+ *
41
+ * `argv` appends the driver's own overrides to the bare `app-server`
42
+ * subcommand (codex's `-c key=value` configuration), and `env` layers the
43
+ * driver's explicit entries over the ambient environment — the child still
44
+ * needs `PATH` and, under a user's own codex setup, the ambient auth facts its
45
+ * configuration reads, so this is an overlay, not a replacement. Both default
46
+ * to nothing, which is the bare `codex app-server` the driver used before a
47
+ * dsh endpoint was ever handed over.
48
+ * @param argv - extra arguments after the `app-server` subcommand.
49
+ * @param env - explicit environment entries layered over the ambient one.
50
+ * @returns the initialized client.
51
+ */
52
+ static create(argv?: readonly string[], env?: NodeJS.ProcessEnv): Promise<AppServerClient>;
40
53
  /** Set the notification handler for streaming events. */
41
54
  onNotification(handler: NotificationHandler): void;
42
55
  /** Set the handler for server-initiated requests (e.g. approvals). */
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Codex loop engine module: hosts the AgentFactory that drives every session
3
- * through the OpenAI Codex SDK, one stateless thread per dsh step, with the
4
- * durable session log as the sole source of model context. dsh-loop-engine
5
- * constructs this factory when the Codex engine is selected; this module is a
6
- * library, not a Cordis plugin entry. The Codex SDK spawns its own CLI binary
2
+ * Codex loop engine module: drives every session it is handed through the
3
+ * OpenAI Codex SDK, one stateless thread per dsh step, with the durable
4
+ * session log as the sole source of model context. The router routes a session
5
+ * here on the plugin's own engine record, else its agent preset; this module is
6
+ * a library, not a Cordis plugin entry. The Codex SDK spawns its own CLI binary
7
7
  * (no spawn injection seam), so this loop deliberately does not inject the dsh
8
8
  * subprocess service.
9
9
  *
@@ -15,7 +15,7 @@ import type { AgentOptions } from '@deepseek-ai/dsh-agent';
15
15
  import type { Session, SessionId } from '@deepseek-ai/dsh-session';
16
16
  import { CodexAgent } from './agent.ts';
17
17
  import type { CodexApprovalPolicy, CodexSandboxMode, ResolvedConfig } from './types.ts';
18
- import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
18
+ import { HostedEngineRuntime } from '../driver-core/hosted-engine-runtime.ts';
19
19
  /** Codex CLI sandbox modes a deployment may pin. */
20
20
  export declare const CODEX_SANDBOX_MODES: readonly CodexSandboxMode[];
21
21
  /** Codex CLI approval policies a deployment may pin. */
@@ -38,26 +38,24 @@ export interface Config {
38
38
  approvalPolicy?: CodexApprovalPolicy;
39
39
  /** Explicit environment entries layered over the credential-scrubbed parent environment. */
40
40
  env?: Record<string, string>;
41
- /** Model override for the SDK; Codex native settings own the model when omitted. */
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
43
  }
44
44
  /** Schema of the Codex loop plugin configuration. */
45
45
  export declare const Config: z<Config>;
46
- /** Host-face ctx key for the Codex loop service. */
46
+ /** Host-face ctx key this engine's runtime is registered under. */
47
+ export declare const CODEX_ENGINE_LABEL = "agentLoopCodex";
47
48
  declare module '@deepseek-ai/cordis' {
48
49
  interface Context {
49
50
  agentLoopCodex: CodexLoop;
50
51
  }
51
52
  }
52
53
  /**
53
- * Concrete AgentFactory and driver service of the Codex loop. Creation and
54
- * resume follow the registry factory contract and the shared publication
55
- * transaction: prepare, run setup, then publish through both registries,
56
- * announce, and emit `agent/session-start`.
54
+ * Creation/resume machinery for the Codex engine. The process-wide
55
+ * AgentFactory slot belongs to the router, which delegates each session to the
56
+ * engine its preset names; this class is that engine's driver, not a plugin.
57
57
  */
58
- export declare class CodexLoop extends HostedLoopFactory<ResolvedConfig, CodexAgent> {
59
- /** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
60
- static inject: string[];
58
+ export declare class CodexLoop extends HostedEngineRuntime<ResolvedConfig, CodexAgent> {
61
59
  constructor(ctx: Context, config: Config);
62
60
  /** Construct the Codex driver for one prepared session. */
63
61
  protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): CodexAgent;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Codex's dialect for a dsh endpoint: the `codex app-server` configuration
3
+ * overrides and environment that point it at dsh's model.
4
+ *
5
+ * Codex resolves a custom endpoint through `model_providers.<id>` in its own
6
+ * `config.toml` (`name`, `base_url`, `wire_api`, and an `env_key` naming the
7
+ * variable the credential is read from), and `codex app-server` accepts those as
8
+ * `-c key=value` overrides — so the handover needs no edit of `~/.codex`. The
9
+ * credential travels in the child's environment under {@link CODEX_DSH_API_KEY_ENV},
10
+ * which the `env_key` names.
11
+ *
12
+ * `name` is not optional in practice even though the CLI's own docs describe it
13
+ * as display metadata: codex 0.149.1 (the version this plugin pins) refuses to
14
+ * load a provider whose `name` is empty — "provider name must not be empty in
15
+ * `model_providers`" — and that config error kills `app-server` on startup, which
16
+ * the driver can only report as "process exited unexpectedly". Omitting it looked
17
+ * correct and failed before any request was ever sent, so every override this
18
+ * module builds carries it (verified against the pinned binary).
19
+ *
20
+ * Codex speaks only OpenAI's `responses` wire as of the version this plugin pins
21
+ * (0.149.1 dropped `chat`). A dsh protocol with no codex equivalent (Anthropic
22
+ * Messages, and now OpenAI Chat Completions) is NOT guessed: `wire_api` is
23
+ * omitted, so codex falls back to its own default wire and fails loud on the
24
+ * request — the honest outcome, since the plugin cannot make codex speak a
25
+ * protocol it does not implement. That default is `responses` as of 0.149.1: the
26
+ * request lands on `<baseURL>/responses`, which an Anthropic Messages or Chat
27
+ * Completions endpoint does not serve.
28
+ *
29
+ * @module dsh-loop-engine/engine-codex/model-handover
30
+ */
31
+ import type { DshModelHandover } from '../driver-core/model-handover.ts';
32
+ /** Provider id codex is configured with when dsh's endpoint is handed over. */
33
+ export declare const CODEX_DSH_PROVIDER = "dsh";
34
+ /** Environment variable codex reads the handed-over credential from. */
35
+ export declare const CODEX_DSH_API_KEY_ENV = "DSH_LOOP_ENGINE_API_KEY";
36
+ /** The `codex app-server` argv/env that hand one dsh endpoint over. */
37
+ export interface CodexModelConfig {
38
+ /** `-c` overrides to append to the `app-server` subcommand. */
39
+ readonly argv: string[];
40
+ /** Environment the child needs to read the endpoint's credential. */
41
+ readonly env: Record<string, string>;
42
+ }
43
+ /**
44
+ * Translate one dsh endpoint handover into codex's `-c` overrides and the
45
+ * environment carrying the credential.
46
+ *
47
+ * The provider id ({@link CODEX_DSH_PROVIDER}) is a plugin-owned name: it exists
48
+ * only in this child's command line, so it cannot collide with a provider the
49
+ * user configured, and the model itself is still handed over as the thread's own
50
+ * `model` param. The provider's `name` repeats that id because codex rejects an
51
+ * empty one at config load, before any request is made.
52
+ * @param handover - the resolved dsh endpoint for the session's selection.
53
+ * @returns the argv entries and environment to spawn the app-server with.
54
+ */
55
+ export declare function codexModelConfig(handover: DshModelHandover): CodexModelConfig;
56
+ //# sourceMappingURL=model-handover.d.ts.map
@@ -83,6 +83,16 @@ export declare class AcpClient {
83
83
  initialize(): Promise<unknown>;
84
84
  /** Start a fresh ACP session and resolve to its session id. */
85
85
  newSession(cwd: string): Promise<string>;
86
+ /**
87
+ * Select the model for one ACP session. The agent answers with an error frame
88
+ * when it cannot serve the id, and {@link request} rejects on that frame — so
89
+ * an engine that refuses the model fails the step loud rather than silently
90
+ * running its own default.
91
+ * @param sessionId - the ACP session the model applies to.
92
+ * @param modelId - the model id to select.
93
+ * @returns the agent's response to `session/set_model`.
94
+ */
95
+ setModel(sessionId: string, modelId: string): Promise<unknown>;
86
96
  /** Prompt the agent in a session and resolve when the turn completes. */
87
97
  prompt(sessionId: string, text: string): Promise<unknown>;
88
98
  /** Cancel the active turn in a session (fire-and-forget). */
@@ -17,8 +17,13 @@ import type { Context } from '@deepseek-ai/cordis';
17
17
  import type { ResolvedConfig } from './types.ts';
18
18
  import { DriverInbox } from '../driver-core/inbox.ts';
19
19
  import type { KimiSpawnCapability } from './process.ts';
20
- /** Provider route label used for logged header snapshots and message provenance. */
21
- export declare const PROVIDER = "kimi";
20
+ /**
21
+ * Provider route label this driver logs into request/header snapshots and
22
+ * message provenance — the ONE route every hosted engine shares
23
+ * ({@link HOSTED_ROUTE_LABEL}), so all four engines select `external/default`
24
+ * and the model menu carries a single group instead of one per engine.
25
+ */
26
+ export declare const PROVIDER = "external";
22
27
  /** Drives one session through turn and step boundaries on Kimi Code. */
23
28
  export declare class KimiAgent implements Agent {
24
29
  private loopCtx;
@@ -44,6 +49,13 @@ export declare class KimiAgent implements Agent {
44
49
  private acp;
45
50
  /** The spawn spec the cached client was built from; a change forces a respawn. */
46
51
  private lastSpec;
52
+ /**
53
+ * The environment the last handover produced, memoized by its own content so
54
+ * an unchanged endpoint reuses ONE object: `specsEqual` compares environments
55
+ * by reference, and a fresh object per step would otherwise respawn the child
56
+ * every step.
57
+ */
58
+ private handoverEnvCache;
47
59
  constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: KimiSpawnCapability, bin: string);
48
60
  get status(): AgentStatus;
49
61
  /** Commit a phase and publish its externally visible status transition. */
@@ -107,6 +119,11 @@ export declare class KimiAgent implements Agent {
107
119
  private acpClient;
108
120
  /** Build the `kimi acp` argv/cwd/env for the persistent child. */
109
121
  private spawnSpec;
122
+ /**
123
+ * The child environment for one handover, memoized by content so an unchanged
124
+ * endpoint yields the SAME object across steps (see {@link handoverEnvCache}).
125
+ */
126
+ private handoverEnv;
110
127
  /** Run one `kimi acp` step for the current session history and map the streamed updates. */
111
128
  private step;
112
129
  /** Per-step accumulation state for streamed assistant blocks and tool calls. */
@@ -4,24 +4,28 @@
4
4
  * The dsh `commands` runtime executes a registered command locally — the line is
5
5
  * consumed and never reaches the model — so a command whose real processing
6
6
  * lives inside the Kimi engine must forward the raw line back to the agent, which
7
- * Kimi then expands (as far as the ACP prompt surface supports). Registering the
8
- * built-ins keeps them visible in the dsh web slash menu; unregistered `/lines`
9
- * pass through as user text, but the menu would hide the engine's command
10
- * surface.
7
+ * Kimi then expands natively. Registering the built-ins keeps them visible in
8
+ * the dsh web slash menu; unregistered `/lines` pass through as user text, but
9
+ * the menu would hide the engine's command surface.
11
10
  *
12
- * Kimi's slash commands are chiefly TUI controls (`/login`, `/provider`,
13
- * `/settings`, `/sessions`, `/tasks`, …) that the ACP prompt surface does not
14
- * expand the way an interactive TUI does; this bridge therefore registers the
15
- * subset that are meaningful to forward to the engine (session/mode/status and
16
- * the goal form). `skill:` commands are already carried by the dsh skill
17
- * injection seam and Kimi's own shorthand, so they are not duplicated here.
11
+ * The list below is exactly what the `kimi acp` command surface implements
12
+ * (verified against kimi 0.28.1, both by driving `session/prompt` directly and
13
+ * by reading the `available_commands_update` the child publishes): `compact`,
14
+ * `status`, `usage`, `mcp`, `tasks`, `help`. Everything else Kimi's TUI offers
15
+ * (`/login`, `/provider`, `/settings`, `/sessions`, `/clear`, `/plan`, …) is a
16
+ * TUI control the ACP surface does not implement — forwarding one answers
17
+ * `Unknown ACP command: /name.` — so none of them is registered. `skill:`
18
+ * commands are already carried by the dsh skill injection seam and Kimi's own
19
+ * shorthand, so they are not duplicated here.
18
20
  *
19
21
  * `model` is deliberately absent even though the CLI has it: the dsh web
20
22
  * client owns a `/model` contribution, and a same-named host command makes
21
23
  * `ui-commands` throw the whole command menu source away, leaving only the
22
- * skill group. `/goal` stays: the managed block disables dsh's `command-goal`
23
- * row for hosted engines, so the slot is free and the CLI's own goal mode
24
- * takes it over.
24
+ * skill group. `/goal` is absent too: the ACP surface implements no `/goal`, so
25
+ * registering one would only answer with an unknown-command error. Hosted
26
+ * sessions have no dsh `/goal` either — the engine preset they join strips the
27
+ * `command-goal` row (`src/preset.ts`, `STRIPPED_ROWS`) — so the command is
28
+ * simply not part of their menu.
25
29
  *
26
30
  * @module dsh-loop-engine/engine-kimi/commands
27
31
  */
@@ -35,6 +39,6 @@ import type { CommandDefinition, CommandInvocation, CommandResult } from '../com
35
39
  * @returns the command handler.
36
40
  */
37
41
  export declare function forwardKimiCommand(name: string): (invocation: CommandInvocation) => CommandResult;
38
- /** Kimi Code's built-in slash commands that make sense to forward to the engine. */
42
+ /** Kimi Code's built-in slash commands that the ACP surface implements. */
39
43
  export declare const KIMI_COMMANDS: readonly CommandDefinition[];
40
44
  //# sourceMappingURL=commands.d.ts.map
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Kimi Code loop engine module: hosts the AgentFactory that drives every
3
- * session through a persistent `kimi acp` child (Agent Client Protocol over
4
- * stdio), speaking one stateless `session/new` + `session/prompt` per dsh step,
5
- * with the durable session log as the sole source of model context.
6
- * dsh-loop-engine constructs this factory when the Kimi engine is selected; this
7
- * module is a library, not a Cordis plugin entry. Kimi has no host approval
2
+ * Kimi Code loop engine module: drives every session it is handed through a
3
+ * persistent `kimi acp` child (Agent Client Protocol over stdio), speaking one
4
+ * stateless `session/new` + `session/prompt` per dsh step, with the durable
5
+ * session log as the sole source of model context. The router routes a session
6
+ * here on the plugin's own engine record, else its agent preset; this module is
7
+ * a library, not a Cordis plugin entry. Kimi has no host approval
8
8
  * callback, so tool approvals surfaced by ACP (`session/request_permission`) are
9
9
  * answered from the session's dsh approval knobs (an `ask` policy degrades to
10
10
  * denial); the whole child is spawned through the dsh subprocess seam — the only
@@ -21,12 +21,12 @@ import type { Session, SessionId } from '@deepseek-ai/dsh-session';
21
21
  import { KimiAgent } from './agent.ts';
22
22
  import type { KimiSpawnCapability } from './process.ts';
23
23
  import type { ResolvedConfig } from './types.ts';
24
- import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
24
+ import { HostedEngineRuntime } from '../driver-core/hosted-engine-runtime.ts';
25
25
  /** Grace in milliseconds for Kimi process-tree termination. */
26
26
  export declare const KIMI_DISPOSE_GRACE_MS = 3000;
27
27
  /** Deployment-owned configuration for the Kimi loop plugin. */
28
28
  export interface Config {
29
- /** Model alias for the `kimi` child (`-m`); Kimi native config owns the model when omitted. */
29
+ /** Model sent per ACP session (`session/set_model`) when the session selects none; Kimi native config owns the model when omitted. */
30
30
  model?: string;
31
31
  /** Explicit environment entries passed to the `kimi` child. */
32
32
  env?: Record<string, string>;
@@ -35,21 +35,19 @@ export interface Config {
35
35
  }
36
36
  /** Schema of the Kimi loop plugin configuration. */
37
37
  export declare const Config: z<Config>;
38
- /** Host-face ctx key for the Kimi loop service. */
38
+ /** Host-face ctx key this engine's runtime is registered under. */
39
+ export declare const KIMI_ENGINE_LABEL = "agentLoopKimi";
39
40
  declare module '@deepseek-ai/cordis' {
40
41
  interface Context {
41
42
  agentLoopKimi: KimiLoop;
42
43
  }
43
44
  }
44
45
  /**
45
- * Concrete AgentFactory and driver service of the Kimi loop. Creation and
46
- * resume follow the registry factory contract and the shared publication
47
- * transaction: prepare, run setup, then publish through both registries,
48
- * announce, and emit `agent/session-start`.
46
+ * Creation/resume machinery for the Kimi Code engine. The process-wide
47
+ * AgentFactory slot belongs to the router, which delegates each session to the
48
+ * engine its preset names; this class is that engine's driver, not a plugin.
49
49
  */
50
- export declare class KimiLoop extends HostedLoopFactory<ResolvedConfig, KimiAgent> {
51
- /** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
52
- static inject: string[];
50
+ export declare class KimiLoop extends HostedEngineRuntime<ResolvedConfig, KimiAgent> {
53
51
  /** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
54
52
  readonly spawn: KimiSpawnCapability;
55
53
  constructor(ctx: Context, config: Config);