dsh-loop-engine 0.1.5-rc2 → 0.1.5-rc4

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 -177
  2. package/README.zh.md +48 -100
  3. package/lib/client.js +887 -266
  4. package/lib/index.js +2212 -914
  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 +116 -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 +44 -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 +35 -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,66 @@
1
+ /**
2
+ * The React binding over the plugin's per-session engine cache.
3
+ *
4
+ * Separated from `./session-engine.ts` on purpose: the cache, the contribution,
5
+ * and the switcher are plain logic with no React import, so they are exercised
6
+ * in node (`tests/session-engine-cache.spec.ts`) — this file is the only part of
7
+ * the read path that needs a component to render.
8
+ *
9
+ * It is also the ONE driver of the chat turn-status row
10
+ * (`./turn-status.ts`): the row is painted through a document-level attribute,
11
+ * and a component is the only place that knows whether the session it renders is
12
+ * the one on screen. The cache cannot know that — it answers for every session a
13
+ * surface has watched — so a reflection made from there could be (and was) taken
14
+ * over by a background session. Here the chip and the composer of the session on
15
+ * screen declare that session as the row's subject while they are mounted, with
16
+ * the focus guard inside `reflectTurnStatusEngine`, and the reflection follows
17
+ * the cache's answer so a switch repaints the row.
18
+ *
19
+ * @module dsh-loop-engine/client/use-session-engine
20
+ */
21
+ import { type SessionEngineReport } from '../agent-preset-ids.ts';
22
+ import type { SessionEngineCache } from './session-engine.ts';
23
+ /**
24
+ * Follow one session's engine report in a component, and — because a component
25
+ * is what knows whether this session is the one on screen — drive the chat
26
+ * turn-status row from it.
27
+ *
28
+ * The value is read from the cache DURING render, so a session switch can never
29
+ * paint the previous session's engine: only the re-render is deferred, and only
30
+ * until the host answers. The reflection runs in an effect rather than during
31
+ * render (it writes to the document), and re-runs when the ANSWER changes — not
32
+ * just when the session id does — so the row follows a switch: the picker's
33
+ * `invalidate` drops the cached answer, the cache re-asks, the hook's watcher
34
+ * bumps, the engine changes, and the attribute lands on the engine the session
35
+ * now runs.
36
+ *
37
+ * Only the ACTUAL engine reaches the row. A recorded engine that differs from it
38
+ * is one whose switch never took over (its release did not complete), so the row
39
+ * keeps painting what is really running — the same rule the chip and the composer
40
+ * follow when they name an engine; they merely also carry the marker
41
+ * ({@link SessionEngineReport}).
42
+ *
43
+ * The value is read from the cache DURING render, so a session switch can never
44
+ * paint the previous session's engine: only the re-render is deferred, and only
45
+ * until the host answers. This effect is also the trigger that re-reads the
46
+ * session: it runs when the session on screen CHANGES (a mount, a switch, or the
47
+ * same session opened again), and the subscription it installs re-reads the
48
+ * session on its first watch (`SessionEngineCache.watch` → `refresh`), so a
49
+ * session the user comes back to cannot keep rendering a report this page took
50
+ * before — coming back is not what moves the session (a switch is performed on
51
+ * the host, and the one that has to release the session reloads this page), but
52
+ * it is long enough for the answer to be old.
53
+ * Re-renders of the same session do not re-run this effect, so nothing here asks
54
+ * again while the session stays on screen.
55
+ *
56
+ * One effect does the whole thing, so the three steps cannot come apart: declare
57
+ * this session as the row's subject (`focusTurnStatusSession`), reflect its
58
+ * engine by that declaration (`reflectTurnStatusEngine`), and release the
59
+ * declaration when this surface goes away (`blurTurnStatusSession`, which
60
+ * withdraws it only while it is still this session's).
61
+ * @param cache - the cache the plugin mounted.
62
+ * @param sessionId - the session to follow, or undefined off a session scope.
63
+ * @returns the engine report, or undefined while it is unknown.
64
+ */
65
+ export declare function useEngineOfSession(cache: SessionEngineCache, sessionId: string | undefined): SessionEngineReport | undefined;
66
+ //# sourceMappingURL=use-session-engine.d.ts.map
@@ -17,6 +17,14 @@
17
17
  * are cwd-dependent, and a global dsh registration would collide across
18
18
  * projects.
19
19
  *
20
+ * The built-in list is limited to commands the CLI actually recognizes in SDK
21
+ * mode (verified against Claude Code 2.1.220 by driving real SDK queries):
22
+ * unknown ones are not forwarded to the model at all — the CLI answers
23
+ * `Unknown command: /name` — but registering one would still advertise a
24
+ * command that does not exist. Note that some recognized commands report
25
+ * `isn't available in this environment` outside the interactive TUI; that
26
+ * report is the engine's own answer and travels back as an assistant message.
27
+ *
20
28
  * @module dsh-loop-engine/commands
21
29
  */
22
30
  import type { UserMessage } from '@deepseek-ai/dsh-session';
@@ -60,9 +68,9 @@ export declare const CLAUDE_CODE_COMMANDS: readonly CommandDefinition[];
60
68
  /**
61
69
  * Discover the user-level custom slash commands from `~/.claude/commands/*.md`
62
70
  * and build forwarding definitions for them. The scan is synchronous so the
63
- * mount path can register the commands before the engine-selection commit
64
- * returns; files without a usable name or description, and names already taken
65
- * by the built-ins, are skipped.
71
+ * agent-creation path can register the commands before the agent is published;
72
+ * files without a usable name or description, and names already taken by the
73
+ * built-ins, are skipped.
66
74
  * @returns forwarding definitions, sorted by file name.
67
75
  */
68
76
  export declare function discoverUserSlashCommands(): CommandDefinition[];
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Minimal structural shapes of the optional host registries the plugin
3
+ * extends.
4
+ *
5
+ * Declared locally rather than imported from their packages: the plugin
6
+ * depends on the SERVICE CONTRACTS it consumes only where the harness exposes
7
+ * one (`dsh-agent`, `dsh-session`, …), and the command/skill registries are
8
+ * read through `ctx.get` and may legitimately be absent in a minimal profile.
9
+ * Keeping the shapes here means one definition shared by every consumer and no
10
+ * peer dependency that a headless composition does not need.
11
+ *
12
+ * @module dsh-loop-engine/driver-core/host-servers
13
+ */
14
+ import type { CommandDefinition } from '../commands.ts';
15
+ import type { SkillProvider, SkillProviderControl } from '../skills.ts';
16
+ import type { SettingsNamespace, SettingsPathOp } from '@deepseek-ai/dsh-settings';
17
+ import type { Session } from '@deepseek-ai/dsh-session';
18
+ import type { HostedEngineRouteAdapter } from '../provider-route.ts';
19
+ /** The host command registry (`ctx.commands`), as this plugin uses it. */
20
+ export interface CommandsService {
21
+ /** Register one definition in the calling context's scope layer. */
22
+ register(def: CommandDefinition): () => void;
23
+ }
24
+ /** The host skill registry (`ctx.skills`), as this plugin uses it. */
25
+ export interface SkillsService {
26
+ /** Register a provider in the calling context's scope layer. */
27
+ registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void;
28
+ }
29
+ /** The host preset roster (`ctx.agentPresets`), as this plugin uses it. */
30
+ export interface AgentPresetsService {
31
+ /** The effective default preset id. */
32
+ readonly defaultId: string;
33
+ /** Read one preset's composition text. */
34
+ read(id: string): Promise<string>;
35
+ }
36
+ /** The host settings service's mutation seam, as this plugin uses it. */
37
+ export interface SettingsMutator {
38
+ /** Apply ops to one namespace. */
39
+ mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[]): Promise<void>;
40
+ /** Registered namespaces, when the provider can enumerate them. */
41
+ describe?(): SettingsDescriptorLike[];
42
+ }
43
+ /**
44
+ * One registered settings namespace, as the host describes it
45
+ * (`@deepseek-ai/dsh-settings` `SettingsDescriptor`). Only the two fields this
46
+ * plugin reads are declared.
47
+ */
48
+ export interface SettingsDescriptorLike {
49
+ /** Namespace name. */
50
+ readonly ns: string;
51
+ /**
52
+ * The composition's own value for this namespace, before the user layer. It is
53
+ * what a saved user-layer value overrode, which is why `model-selection-reset.ts`
54
+ * reads it: the deployment's own configured default model is a real model it
55
+ * can name when the saved default does not.
56
+ */
57
+ readonly base?: unknown;
58
+ }
59
+ /** The host llm registry (`ctx.llm`), as this plugin uses it. */
60
+ export interface LlmRegistry {
61
+ /** Register a placeholder adapter for one or more provider labels. */
62
+ registerAdapter(providers: string[], adapter: HostedEngineRouteAdapter): () => void;
63
+ }
64
+ /** The host session-projection registry (`ctx.sessionProjections`), as this plugin uses it. */
65
+ export interface SessionProjectionsService {
66
+ /** Folded projection state of one session, or undefined when it never advanced. */
67
+ stateOf(session: Session, key: 'turnBoundary'): TurnBoundaryFacts | undefined;
68
+ /** The same fold for the durable model selection (`model-selection-reset.ts`). */
69
+ stateOf(session: Session, key: 'modelSelection'): ModelSelectionFacts | undefined;
70
+ }
71
+ /**
72
+ * One complete model selection as the host's `model/selection` event and its
73
+ * projection carry it (`packages/api/session-controller/src/types.ts`,
74
+ * `ModelSelection`).
75
+ */
76
+ export interface SessionModelSelection {
77
+ /** Registered provider route. */
78
+ readonly provider: string;
79
+ /** Provider-owned model id. */
80
+ readonly model: string;
81
+ /** Adapter-owned reasoning effort, or provider/default behavior when absent. */
82
+ readonly reasoningEffort?: string;
83
+ }
84
+ /**
85
+ * The slice of the host's `modelSelection` projection state this plugin reads.
86
+ * `lastUsed` is deliberately not declared: it is that fold's view of the newest
87
+ * `request/header`, which the plugin reads from the session itself
88
+ * (`Session.requestHeader()`) with the same result.
89
+ */
90
+ export interface ModelSelectionFacts {
91
+ /** Later selection not yet consumed by a matching recorded request, or null. */
92
+ readonly pending: SessionModelSelection | null;
93
+ }
94
+ /**
95
+ * The turn-boundary facts this plugin reads. Mirrors `TurnBoundaryProjection`
96
+ * in `@deepseek-ai/dsh-agent` without depending on that module's projection
97
+ * registry augmentation, which a non-plugin consumer has no other reason to
98
+ * pull into its program.
99
+ */
100
+ export interface TurnBoundaryFacts {
101
+ /** Seq of the open turn's `turn/start`, or null between turns. */
102
+ readonly openTurnStartSeq: number | null;
103
+ /** Turn number of the latest `turn/start`; 0 before the first turn. */
104
+ readonly lastTurn: number;
105
+ }
106
+ //# sourceMappingURL=host-servers.d.ts.map
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Shared AgentFactory transaction machinery for the hosted loop engines.
3
+ *
4
+ * All four engines (Claude Code, Codex, Pi, Kimi Code) implement the harness's
5
+ * AgentFactory contract the same way: prepare a driver, scope, and one memoized
6
+ * reverse teardown for a session; run the caller's setup under a fused abort
7
+ * signal; publish through both registries and announce; and on resume, own a
8
+ * session's write handle across the cold read, crash repair, and
9
+ * re-publication. None of that touches an engine protocol — the whole
10
+ * engine-specific surface is one call, {@link HostedEngineRuntime.buildAgent}.
11
+ *
12
+ * One move has no counterpart in the harness's own factory contract and is what
13
+ * makes an in-place engine swap possible: a LIVE session changes drivers without
14
+ * being released. {@link HostedAgentHandle.retire} stops the outgoing machine
15
+ * while leaving the session entered, and {@link HostedEngineRuntime.swap} builds
16
+ * the incoming engine's machine onto that same Session. The session's store
17
+ * entry and write handle travel across the handover in a
18
+ * {@link SessionLifetime} — the one object that owns them.
19
+ *
20
+ * This body mirrors the default in-process `agent-loop` factory. It is a plain
21
+ * class, not a Cordis plugin: the process-wide AgentFactory slot is owned by
22
+ * the router (`router-loop.ts`, which subclasses the harness `AgentLoop`), and
23
+ * every hosted engine is one runtime instance the router delegates to. That is
24
+ * what lets several engines serve different sessions concurrently — the
25
+ * harness admits exactly one factory, so the factory itself must dispatch.
26
+ *
27
+ * Subclasses supply:
28
+ * - the effect label prefix (`<label>.transactions()`,
29
+ * `<label>.lifecycle(id)`, `<label>.resume-load(id)`) — the lifecycle label
30
+ * is asserted by tests, so it must stay `<label>.lifecycle(...)`;
31
+ * - their own configuration resolution;
32
+ * - {@link HostedEngineRuntime.buildAgent}.
33
+ *
34
+ * @module dsh-loop-engine/driver-core/hosted-engine-runtime
35
+ */
36
+ import type { Context } from '@deepseek-ai/cordis';
37
+ import type { Agent, AgentFactory, AgentHandle, AgentOptions, AgentSetup, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
38
+ import { SessionId } from '@deepseek-ai/dsh-session';
39
+ import type { Session } from '@deepseek-ai/dsh-session';
40
+ import type { Scope } from '@deepseek-ai/dsh-scope';
41
+ import { SessionLifetime } from './session-lifetime.ts';
42
+ /**
43
+ * What the transaction machinery needs of a driver beyond the harness `Agent`
44
+ * contract: a teardown entry point that also unwinds the driver's own scope.
45
+ * The concrete engines declare these; the base only spells them out so the
46
+ * shared body can call them generically.
47
+ */
48
+ export interface HostedAgent extends Agent {
49
+ /** Stop the machine with the given cause, without waiting for it to settle. */
50
+ cancel(cause: Parameters<Agent['cancel']>[0]): void;
51
+ /** Resolves when the machine has no work in flight. */
52
+ whenIdle(): Promise<void>;
53
+ /** The driver's own resource scope, unwound after the machine settles. */
54
+ readonly scope: Scope;
55
+ }
56
+ /**
57
+ * The plugin's own handle for one published hosted agent: the harness contract
58
+ * plus the two facts an in-place engine swap needs.
59
+ *
60
+ * A swap is two moves by two different runtimes: the outgoing engine retires its
61
+ * machine and hands the session over, and the incoming engine builds onto that
62
+ * session. The router is the only caller and it tracks the handle already, so
63
+ * the harness's own `AgentHandle` — which carries neither fact — is widened
64
+ * here rather than reaching back into a transaction that has ended.
65
+ */
66
+ export interface HostedAgentHandle extends AgentHandle {
67
+ /** The live session's lifetime resources, owned by this agent until it is disposed. */
68
+ readonly lifetime: SessionLifetime;
69
+ /**
70
+ * Retire this machine for good while LEAVING the session alive: stop it,
71
+ * unwind its scope, and leave the agent registry — the session's entry and
72
+ * write handle pass to {@link SessionLifetime}'s next owner instead of being
73
+ * released here. Only valid while this agent still owns the session: the
74
+ * router calls it for the session's live, idle agent only.
75
+ */
76
+ retire(): Promise<void>;
77
+ }
78
+ /** Options for {@link HostedEngineRuntime.swap}. */
79
+ export interface SwapAgentOptions {
80
+ /** The live session's lifetime resources, handed over by the outgoing engine. */
81
+ readonly lifetime: SessionLifetime;
82
+ /** Loop options for the successor; the router replays the outgoing agent's own. */
83
+ readonly agentOptions?: AgentOptions | undefined;
84
+ /** The composition callback the session was built with, replayed onto the successor. */
85
+ readonly setup?: AgentSetup | undefined;
86
+ /** The outgoing agent's runtime owner, for a child session's inherited ownership. */
87
+ readonly parentAgent?: Agent | undefined;
88
+ }
89
+ /**
90
+ * Concrete creation/resume machinery for one hosted engine.
91
+ *
92
+ * Creation and resume follow the registry factory contract and the shared
93
+ * publication transaction: prepare, run setup, then publish through both
94
+ * registries, announce, and emit `agent/session-start`. {@link swap} follows the
95
+ * same transaction onto a session another agent entered, which is the only
96
+ * difference between taking a session over and owning it from birth.
97
+ */
98
+ export declare abstract class HostedEngineRuntime<TConfig, TAgent extends HostedAgent> implements AgentFactory {
99
+ /** Validated configuration owned by this engine instance. */
100
+ readonly config: TConfig;
101
+ /** Effect-label prefix; also identifies the engine in diagnostics. */
102
+ readonly label: string;
103
+ /** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
104
+ protected readonly runtime: {
105
+ ctx: Context;
106
+ };
107
+ private readonly ownership;
108
+ /**
109
+ * @param ctx - the owning context; its fiber's unload tears every live agent down.
110
+ * @param label - effect-label prefix, e.g. `agentLoopKimi`.
111
+ * @param config - already-resolved engine configuration.
112
+ */
113
+ constructor(ctx: Context, label: string, config: TConfig);
114
+ /**
115
+ * Construct this engine's driver for one prepared session. Called once per
116
+ * create, resume, or swap, after the session exists and before setup runs; the
117
+ * hook is the engine's entire protocol surface.
118
+ */
119
+ protected abstract buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): TAgent;
120
+ /**
121
+ * Construct the driver, scope, and one memoized reverse teardown for a new
122
+ * agent. The teardown is registered with the factory and the owner fiber
123
+ * BEFORE publication, so a mid-setup unload rolls everything back; `signal`
124
+ * fuses caller cancellation with lifecycle teardown for setup awaits.
125
+ *
126
+ * `lifetime` carries the session's store entry and write handle rather than
127
+ * this body owning them: a fresh transaction binds them when it publishes,
128
+ * while a swap publishes a session whose entry another machine already bound
129
+ * and whose write handle another machine already opened.
130
+ */
131
+ private prepare;
132
+ /** Prepare one Agent around an acquired Session, run setup, and publish it. */
133
+ private setupAndPublish;
134
+ /**
135
+ * Create an agent and session under one caller-supplied identity, owned by
136
+ * the accessing fiber. When a persistence backend is mounted, the session's
137
+ * durable identity is stored before publication.
138
+ * @param ownerCtx - caller context that structurally owns the lifecycle.
139
+ * @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
140
+ * @returns the published handle.
141
+ */
142
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<HostedAgentHandle>;
143
+ /**
144
+ * Take a fresh session's write ownership when persistence is mounted.
145
+ * Nothing is appended here: the constructor seed (which never re-emits
146
+ * through `session/event`) is stored by {@link appendUnstoredSuffix} at the
147
+ * publication commit point, so a failed or cancelled setup closes an
148
+ * unmaterialized handle and leaves no stored residue — the same id can be
149
+ * created again.
150
+ * @param session - the unpublished session to store.
151
+ * @param signal - optional cancellation forwarded to the backend create.
152
+ * @returns the owned handle and stored cursor, or `undefined` without a backend.
153
+ */
154
+ private createStoredSession;
155
+ /**
156
+ * Durably store the session events appended since the last stored cursor.
157
+ * Pre-publication appends (constructor seed markers, setup-window events)
158
+ * never re-emit through `session/event`, so publication must flush them
159
+ * through the handle before live events start routing into it.
160
+ * @param stored - the session's owned handle and stored cursor, if any.
161
+ * @param session - the unpublished session whose suffix is stored.
162
+ */
163
+ private appendUnstoredSuffix;
164
+ /**
165
+ * Resume an owned agent from the configured persistence service.
166
+ * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
167
+ * @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
168
+ * @returns the published handle.
169
+ */
170
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<HostedAgentHandle>;
171
+ /**
172
+ * Build this engine's agent onto a LIVE session another engine's agent left,
173
+ * taking the session's lifetime over.
174
+ *
175
+ * The counterpart of {@link HostedAgentHandle.retire}: together they are an
176
+ * in-place engine swap, and the harness's own factory contract has neither.
177
+ * Nothing is read from persistence and nothing is created in the store — the
178
+ * successor drives the Session object that is already live and already
179
+ * entered, so a browser half attached to that session sees no lifecycle edge
180
+ * at all. The lifetime carries the still-open write handle, so the session
181
+ * keeps being stored through the same channel it was already using.
182
+ * @param ownerCtx - caller context that structurally owns the lifecycle.
183
+ * @param options - the live session's lifetime, loop options, setup, and parent.
184
+ * @returns the published handle, which owns the session from here on.
185
+ */
186
+ swap(ownerCtx: Context, options: SwapAgentOptions): Promise<HostedAgentHandle>;
187
+ /** Resume through an explicit persistence service. */
188
+ private resumeWith;
189
+ }
190
+ //# sourceMappingURL=hosted-engine-runtime.d.ts.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Projection of a hosted engine's tool vocabulary onto dsh's, plus the plan
3
+ * snapshot a hosted plan tool carries.
4
+ *
5
+ * The durable `tool/call` event is what the Web client's tool rows
6
+ * (`@deepseek-ai/dsh-client-ui-chat`'s tool Definition), the produced-files row
7
+ * and inline file links (`@deepseek-ai/dsh-client-ui-deliverables`'
8
+ * `mutationPath`), and the trajectory view read. Those consumers recognize only
9
+ * dsh's own tool names and argument shapes, while a hosted engine names its
10
+ * tools differently: Claude's `Write`/`Edit`/`Read`/`Bash`, Codex's
11
+ * `apply_patch`/`command_execution`, Kimi's title-cased `Bash`, Pi's
12
+ * `path`-keyed arguments. This module normalizes the event a driver appends
13
+ * while leaving the durable assistant message — and therefore the next step's
14
+ * serialized prompt — in the engine's own vocabulary. Session pairing is by
15
+ * callId, so the two spellings coexist.
16
+ *
17
+ * Only lossless projections are applied. A call whose arguments do not carry
18
+ * the dsh fields is passed through unchanged and simply renders as a generic
19
+ * row, so a new engine-side tool degrades instead of mis-rendering.
20
+ *
21
+ * Plan extraction is separate: dsh's todo panel is driven by the `todo/write`
22
+ * session event, not by the tool row, so a driver appends that event for the
23
+ * plan tool it recognizes.
24
+ *
25
+ * @module dsh-loop-engine/driver-core/hosted-tool-vocabulary
26
+ */
27
+ /** A hosted loop engine id — the settings engine union without `in-process`. */
28
+ export type HostedEngineId = 'claude-code' | 'codex' | 'pi' | 'kimi';
29
+ /** One tool call projected into dsh's vocabulary for the durable `tool/call` event. */
30
+ export interface NormalizedHostedToolCall {
31
+ /** dsh tool name when the projection recognizes the call, else the engine's own spelling. */
32
+ readonly name: string;
33
+ /** dsh-shaped arguments JSON when the projection rewrites them, else the engine's own string. */
34
+ readonly arguments: string;
35
+ }
36
+ /** One plan entry in the shape the `todo/write` event and the todo panel consume. */
37
+ export interface HostedPlanTodo {
38
+ /** The task line, verbatim. */
39
+ readonly content: string;
40
+ /** Lifecycle state, restricted to dsh's three-state union. */
41
+ readonly status: 'pending' | 'in_progress' | 'completed';
42
+ }
43
+ declare module '@deepseek-ai/dsh-session/types' {
44
+ interface SessionEventMap {
45
+ 'todo/write': {
46
+ todos: {
47
+ content: string;
48
+ status: 'pending' | 'in_progress' | 'completed';
49
+ }[];
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Project one hosted tool call onto dsh's `tool/call` vocabulary. The engine's
55
+ * own assistant-message block is untouched; only the event this returns is
56
+ * normalized, so the next step's serialized prompt keeps the engine's spelling.
57
+ * @param engine - the hosted engine the call came from.
58
+ * @param name - the engine's own tool name.
59
+ * @param argumentsJson - the engine's arguments JSON, verbatim.
60
+ * @returns the dsh name and (when reshaped) dsh-shaped arguments.
61
+ */
62
+ export declare function normalizeHostedToolCall(engine: HostedEngineId, name: string, argumentsJson: string): NormalizedHostedToolCall;
63
+ /**
64
+ * Read the whole todo list a hosted plan tool wrote, in the shape the
65
+ * `todo/write` session event carries.
66
+ * @param engine - the hosted engine the call came from.
67
+ * @param name - the engine's own tool name.
68
+ * @param argumentsJson - the engine's arguments JSON, verbatim.
69
+ * @returns the plan entries (possibly empty) when the call is a plan write, else `undefined`.
70
+ */
71
+ export declare function planTodosOfHostedTool(engine: HostedEngineId, name: string, argumentsJson: string): readonly HostedPlanTodo[] | undefined;
72
+ //# sourceMappingURL=hosted-tool-vocabulary.d.ts.map
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The dsh endpoint, protocol, and credential a hosted engine is handed when a
3
+ * session selects a REAL dsh model — the second half of the model handover
4
+ * whose first half (`session-model.ts`) only carries the model name.
5
+ *
6
+ * A hosted engine owns its model natively, and today it also owns the endpoint
7
+ * and the credential: each CLI keeps its own provider table. That is why
8
+ * "handing over a dsh model" used to mean "the engine can find that model name
9
+ * in its own configuration". This module answers the other question — what
10
+ * `baseURL`, wire protocol, and credential does the session's selection name in
11
+ * dsh's own settings — so each driver can point its engine at dsh's endpoint.
12
+ *
13
+ * The two reads are the host's own seams, deliberately:
14
+ *
15
+ * - the provider → settings address mapping comes from the llm registry's
16
+ * configurable-provider directory (`LlmRuntime.listConfigurableProviders`,
17
+ * `packages/llm/llm/src/types.ts` `LlmConfigurableProvider`). That directory
18
+ * is the authoritative answer to "which namespace, at which path, configures
19
+ * provider X": `llm-pi-ai` maps a route to `['providers', route]` under
20
+ * `llm-pi-ai`, `llm-deepseek` maps its one route to the whole `llm-deepseek`
21
+ * section. Reading the directory rather than hardcoding namespaces is what
22
+ * keeps this plugin from inventing a mapping the deployment did not make.
23
+ * - the credential is resolved through the credentials seam, because the
24
+ * profile stores an `apiKeyEnv` NAME and its value lives in the harness's
25
+ * credential store, never in the process environment
26
+ * (`packages/bundle/base/cordis.patch.yml`).
27
+ *
28
+ * Both reads are reads of ANOTHER plugin's private namespace, and no contract in
29
+ * the harness guarantees their shape. Every step is therefore defensive: a shape
30
+ * this module does not recognize resolves to `undefined` (the engine keeps its
31
+ * own configuration, exactly today's behavior) and one warning, never a throw
32
+ * and never a guess. The warning names the provider and the model, never a
33
+ * credential.
34
+ *
35
+ * @module dsh-loop-engine/driver-core/model-handover
36
+ */
37
+ import type { Context } from '@deepseek-ai/cordis';
38
+ import type { SessionModelOverride } from './session-model.ts';
39
+ /**
40
+ * The endpoint facts one hosted engine needs to speak to the dsh model a session
41
+ * selected: the model id, the base URL, dsh's wire protocol name for it, and the
42
+ * resolved credential value.
43
+ *
44
+ * The protocol is carried verbatim (`llm-pi-ai` values such as
45
+ * `anthropic-messages`, `openai-completions`, `openai-responses`), because each
46
+ * engine translates it into its own vocabulary — and an engine whose protocol
47
+ * the endpoint does not speak is left to fail loud on its own request rather
48
+ * than being silently given a different endpoint.
49
+ */
50
+ export interface DshModelHandover {
51
+ /** Provider route the selection named (the engine's provider key). */
52
+ readonly provider: string;
53
+ /** Provider-owned model id the selection named. */
54
+ readonly model: string;
55
+ /** Endpoint base as dsh configured it. */
56
+ readonly baseURL: string;
57
+ /** dsh's wire protocol name for this endpoint. */
58
+ readonly api: string;
59
+ /** The resolved credential value; never logged, never evented. */
60
+ readonly apiKey: string;
61
+ }
62
+ /**
63
+ * One entry of the host llm registry's configurable-provider directory
64
+ * (`LlmConfigurableProvider`), narrowed to the address fields this module reads.
65
+ */
66
+ export interface ConfigurableProviderAddress {
67
+ /** Provider route key the entry activates when configured. */
68
+ readonly provider: string;
69
+ /** User-settings namespace whose section configures this provider. */
70
+ readonly settingsNs: string;
71
+ /** Path from that section root to this provider's profile; empty when the section IS the profile. */
72
+ readonly settingsPath: readonly string[];
73
+ }
74
+ /**
75
+ * The host llm registry (`ctx.llm`), as this module uses it.
76
+ *
77
+ * `listConfigurableProviders` is optional: a minimal profile may serve the
78
+ * registry without the directory, and an older harness may predate it. A
79
+ * missing directory is "cannot map", not an error — the engine keeps its own
80
+ * configuration.
81
+ */
82
+ export interface LlmDirectoryService {
83
+ /** Every provider route an adapter can activate through configuration. */
84
+ listConfigurableProviders?(): readonly ConfigurableProviderAddress[];
85
+ }
86
+ /** The host settings service (`ctx.settings`), as this module uses it. */
87
+ export interface SettingsReader {
88
+ /** Read one registered namespace's resolved value. */
89
+ get?(ns: string): unknown;
90
+ }
91
+ /** The host credentials seam (`ctx.credentials`), as this module uses it. */
92
+ export interface CredentialsService {
93
+ /** Resolve one reference (an environment-variable NAME) to its current value. */
94
+ resolve(ref: string): Promise<{
95
+ readonly value: string;
96
+ } | undefined>;
97
+ }
98
+ /**
99
+ * Resolve the session's own model selection into the endpoint triple a hosted
100
+ * engine can dial, or `undefined` to leave the engine to its own configuration.
101
+ *
102
+ * `undefined` is the answer in exactly two situations: the selection names no
103
+ * real dsh model ({@link SessionModelOverride} is already `undefined` for the
104
+ * hosted seat), or a real model whose endpoint/credential dsh does not disclose
105
+ * to this read. The second case warns once — it is a deployment-shape gap the
106
+ * operator can see and fix, not a silent downgrade.
107
+ *
108
+ * Called fresh on every step by each driver, so a model or provider picked
109
+ * mid-conversation reaches the engine's next request rather than being frozen
110
+ * when the agent was built.
111
+ * @param ctx - the driver's context, carrying the llm/settings/credentials seams.
112
+ * @param override - the session's dsh model selection, already judged by `sessionModelOverrideOf`.
113
+ * @returns the resolvable endpoint triple, or undefined to inject nothing.
114
+ */
115
+ export declare function resolveModelHandover(ctx: Context, override: SessionModelOverride | undefined): Promise<DshModelHandover | undefined>;
116
+ //# sourceMappingURL=model-handover.d.ts.map
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Shared factory ownership and abort-race machinery for the hosted engines.
2
+ * Shared engine ownership and abort-race machinery for the hosted engines.
3
3
  * All four loop drivers (Claude Code, Codex, Pi, Kimi Code) run the same
4
- * lifecycle: exactly one factory owns the AgentFactory slot, every live
5
- * agent's teardown is tracked until it settles, and setup awaits are raced
6
- * against a fused abort signal. These helpers are engine-free — they only
4
+ * lifecycle: one runtime per engine owns that engine's live agents, every
5
+ * live agent's teardown is tracked until it settles, and setup awaits are
6
+ * raced against a fused abort signal. The process-wide AgentFactory slot is
7
+ * the router's, not any engine's; these helpers are engine-free — they only
7
8
  * touch the fiber state, the session id type, and an AbortController — so the
8
- * loop modules share them verbatim.
9
+ * engine modules share them verbatim.
9
10
  *
10
11
  * @module dsh-loop-engine/driver-core/ownership
11
12
  */
@@ -5,11 +5,43 @@
5
5
  * exact projection, so a later replay of the same log derives the identical
6
6
  * prompt (Model-visible ⟺ logged bridge).
7
7
  *
8
+ * A step whose live request is an engine slash command is the one exception to
9
+ * the transcript framing: {@link engineSlashPrompt} sends that command line
10
+ * verbatim, because the engines only recognize their own commands at the head
11
+ * of the prompt. It is still derived from the log alone, so the guarantee
12
+ * holds.
13
+ *
8
14
  * @module dsh-loop-engine/driver-core/prompt
9
15
  */
10
16
  import type { Message } from '@deepseek-ai/dsh-llm';
11
17
  /** Model-facing stand-in for an image block that the hosted engines cannot consume as bytes. */
12
18
  export declare const OMITTED_IMAGE_TEXT = "[image omitted: the driver does not transcribe images; read the file when a path is available]";
19
+ /**
20
+ * The engine's own slash-command line to send as this step's entire prompt, or
21
+ * `undefined` when the step is an ordinary conversational step.
22
+ *
23
+ * Every hosted engine expands a slash command only when the text handed to it
24
+ * *starts* with `/` — Kimi's ACP adapter parses the first prompt block, Claude
25
+ * Code's local-command dispatch and Pi's input expansion both test the leading
26
+ * character of the message string. The serialized transcript never satisfies
27
+ * that (it opens with `<user>`), so a forwarded `/status` reaches the model as
28
+ * prose instead of the engine's own command surface. This helper lets a driver
29
+ * recognize the case and send the command line verbatim, with no transcript
30
+ * framing and no replay history: a slash command is a control line for the
31
+ * engine, not conversation for the model.
32
+ *
33
+ * The live request is the last derived message, so a step whose trailing
34
+ * message is a bare command line is a command step. A trailing skill-injection
35
+ * message (the `/name` skill gesture the drivers materialize as its own user
36
+ * message) displaces it and keeps the step on the transcript path.
37
+ *
38
+ * The returned line is a pure function of the log prefix, so the step stays
39
+ * replayable: the same log derives the same prompt.
40
+ * @param messages - derived history, oldest first, as returned by
41
+ * `Session.deriveMessages()` at step time.
42
+ * @returns the raw command line, or `undefined` for an ordinary step.
43
+ */
44
+ export declare function engineSlashPrompt(messages: readonly Message[]): string | undefined;
13
45
  /**
14
46
  * Serialize a derived conversation history into the prompt text of one hosted
15
47
  * query. The last message is the live user request that triggered the step;