dsh-loop-engine-allinone 0.0.0-stage → 0.4.0

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 (143) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/CONTRIBUTING.md +78 -0
  3. package/LICENSE +23 -0
  4. package/NOTICE +45 -0
  5. package/README.md +221 -2
  6. package/README.zh.md +89 -0
  7. package/SECURITY.md +39 -0
  8. package/cordis.patch.yml +3 -0
  9. package/lib/client.js +2166 -0
  10. package/lib/index.js +11621 -0
  11. package/lib/invariant.js +453 -0
  12. package/lib/types/agent-preset-ids.d.ts +276 -0
  13. package/lib/types/client/LoopEngineBadge.d.ts +71 -0
  14. package/lib/types/client/LoopEngineComposerSelect.d.ts +126 -0
  15. package/lib/types/client/LoopEngineSection.d.ts +35 -0
  16. package/lib/types/client/engine-marks.d.ts +87 -0
  17. package/lib/types/client/index.d.ts +39 -0
  18. package/lib/types/client/locales.d.ts +182 -0
  19. package/lib/types/client/reload.d.ts +135 -0
  20. package/lib/types/client/session-engine.d.ts +508 -0
  21. package/lib/types/client/store.d.ts +103 -0
  22. package/lib/types/client/turn-status.d.ts +234 -0
  23. package/lib/types/client/use-session-engine.d.ts +74 -0
  24. package/lib/types/commands.d.ts +77 -0
  25. package/lib/types/compat.d.ts +25 -0
  26. package/lib/types/driver-acp/agent.d.ts +289 -0
  27. package/lib/types/driver-acp/approval.d.ts +27 -0
  28. package/lib/types/driver-acp/capabilities.d.ts +200 -0
  29. package/lib/types/driver-acp/client.d.ts +297 -0
  30. package/lib/types/driver-acp/executable.d.ts +53 -0
  31. package/lib/types/driver-acp/mapping.d.ts +76 -0
  32. package/lib/types/driver-acp/profile.d.ts +126 -0
  33. package/lib/types/driver-acp/runtime.d.ts +45 -0
  34. package/lib/types/driver-acp/subprocess.d.ts +19 -0
  35. package/lib/types/driver-acp/terminal.d.ts +98 -0
  36. package/lib/types/driver-acp/transport.d.ts +43 -0
  37. package/lib/types/driver-acp/types.d.ts +152 -0
  38. package/lib/types/driver-cli/capability.d.ts +42 -0
  39. package/lib/types/driver-cli/cli-agent.d.ts +258 -0
  40. package/lib/types/driver-cli/cli-loop.d.ts +46 -0
  41. package/lib/types/driver-cli/flatten.d.ts +35 -0
  42. package/lib/types/driver-cli/frames.d.ts +128 -0
  43. package/lib/types/driver-cli/ndjson.d.ts +106 -0
  44. package/lib/types/driver-cli/session-sink.d.ts +333 -0
  45. package/lib/types/driver-cli/stream-json.d.ts +61 -0
  46. package/lib/types/driver-cli/text.d.ts +66 -0
  47. package/lib/types/driver-cli/transport.d.ts +84 -0
  48. package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
  49. package/lib/types/driver-core/assistant-stream.d.ts +68 -0
  50. package/lib/types/driver-core/context-files.d.ts +62 -0
  51. package/lib/types/driver-core/host-servers.d.ts +119 -0
  52. package/lib/types/driver-core/hosted-engine-runtime.d.ts +190 -0
  53. package/lib/types/driver-core/hosted-tool-vocabulary.d.ts +78 -0
  54. package/lib/types/driver-core/idle-child.d.ts +57 -0
  55. package/lib/types/driver-core/image-access.d.ts +25 -0
  56. package/lib/types/driver-core/inbox.d.ts +104 -0
  57. package/lib/types/driver-core/model-handover.d.ts +134 -0
  58. package/lib/types/driver-core/ownership.d.ts +41 -0
  59. package/lib/types/driver-core/permission-knobs.d.ts +26 -0
  60. package/lib/types/driver-core/prompt.d.ts +64 -0
  61. package/lib/types/driver-core/session-lifetime.d.ts +62 -0
  62. package/lib/types/driver-core/session-model.d.ts +128 -0
  63. package/lib/types/driver-core/skill-inject.d.ts +59 -0
  64. package/lib/types/driver-core/step-close.d.ts +49 -0
  65. package/lib/types/driver-core/step-images.d.ts +62 -0
  66. package/lib/types/driver-core/step-tools.d.ts +43 -0
  67. package/lib/types/driver-core/system-head.d.ts +43 -0
  68. package/lib/types/engine-agy/agent.d.ts +67 -0
  69. package/lib/types/engine-agy/frames.d.ts +90 -0
  70. package/lib/types/engine-agy/loop.d.ts +64 -0
  71. package/lib/types/engine-agy/process.d.ts +173 -0
  72. package/lib/types/engine-claude/agent.d.ts +186 -0
  73. package/lib/types/engine-claude/loop.d.ts +73 -0
  74. package/lib/types/engine-claude/mapping.d.ts +108 -0
  75. package/lib/types/engine-claude/permission.d.ts +41 -0
  76. package/lib/types/engine-claude/process.d.ts +59 -0
  77. package/lib/types/engine-claude/sdk.d.ts +67 -0
  78. package/lib/types/engine-claude/types.d.ts +28 -0
  79. package/lib/types/engine-codex/agent.d.ts +216 -0
  80. package/lib/types/engine-codex/appserver/client.d.ts +80 -0
  81. package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
  82. package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
  83. package/lib/types/engine-codex/appserver/types.d.ts +225 -0
  84. package/lib/types/engine-codex/loop.d.ts +75 -0
  85. package/lib/types/engine-codex/model-handover.d.ts +56 -0
  86. package/lib/types/engine-codex/permission.d.ts +126 -0
  87. package/lib/types/engine-codex/skills.d.ts +28 -0
  88. package/lib/types/engine-codex/types.d.ts +36 -0
  89. package/lib/types/engine-drivers.d.ts +132 -0
  90. package/lib/types/engine-grok/agent.d.ts +24 -0
  91. package/lib/types/engine-grok/loop.d.ts +61 -0
  92. package/lib/types/engine-grok/process.d.ts +46 -0
  93. package/lib/types/engine-grok/profile.d.ts +82 -0
  94. package/lib/types/engine-hermes/agent.d.ts +47 -0
  95. package/lib/types/engine-hermes/loop.d.ts +56 -0
  96. package/lib/types/engine-hermes/process.d.ts +92 -0
  97. package/lib/types/engine-kimi/acp/client.d.ts +123 -0
  98. package/lib/types/engine-kimi/acp/mapping.d.ts +73 -0
  99. package/lib/types/engine-kimi/acp/types.d.ts +143 -0
  100. package/lib/types/engine-kimi/agent.d.ts +24 -0
  101. package/lib/types/engine-kimi/commands.d.ts +44 -0
  102. package/lib/types/engine-kimi/loop.d.ts +62 -0
  103. package/lib/types/engine-kimi/model-handover.d.ts +32 -0
  104. package/lib/types/engine-kimi/permission.d.ts +28 -0
  105. package/lib/types/engine-kimi/process.d.ts +47 -0
  106. package/lib/types/engine-kimi/profile.d.ts +19 -0
  107. package/lib/types/engine-kimi/skills.d.ts +52 -0
  108. package/lib/types/engine-kimi/types.d.ts +25 -0
  109. package/lib/types/engine-minimaxcode/agent.d.ts +24 -0
  110. package/lib/types/engine-minimaxcode/loop.d.ts +83 -0
  111. package/lib/types/engine-minimaxcode/process.d.ts +53 -0
  112. package/lib/types/engine-minimaxcode/profile.d.ts +72 -0
  113. package/lib/types/engine-of-session.d.ts +97 -0
  114. package/lib/types/engine-pi/agent.d.ts +230 -0
  115. package/lib/types/engine-pi/loop.d.ts +72 -0
  116. package/lib/types/engine-pi/model-handover.d.ts +84 -0
  117. package/lib/types/engine-pi/permission.d.ts +47 -0
  118. package/lib/types/engine-pi/rpc/client.d.ts +107 -0
  119. package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
  120. package/lib/types/engine-pi/rpc/types.d.ts +249 -0
  121. package/lib/types/engine-pi/skills.d.ts +47 -0
  122. package/lib/types/engine-pi/types.d.ts +36 -0
  123. package/lib/types/engine-qoderclicn/agent.d.ts +59 -0
  124. package/lib/types/engine-qoderclicn/loop.d.ts +51 -0
  125. package/lib/types/engine-qoderclicn/process.d.ts +113 -0
  126. package/lib/types/engine-registry.d.ts +584 -0
  127. package/lib/types/engine-remote.d.ts +192 -0
  128. package/lib/types/engine-surface.d.ts +36 -0
  129. package/lib/types/engine-zcode/agent.d.ts +50 -0
  130. package/lib/types/engine-zcode/loop.d.ts +47 -0
  131. package/lib/types/engine-zcode/process.d.ts +111 -0
  132. package/lib/types/index.d.ts +205 -0
  133. package/lib/types/invariant.d.ts +26 -0
  134. package/lib/types/model-selection-reset.d.ts +271 -0
  135. package/lib/types/namespace.d.ts +25 -0
  136. package/lib/types/patch-manager.d.ts +104 -0
  137. package/lib/types/preset.d.ts +254 -0
  138. package/lib/types/provider-route.d.ts +245 -0
  139. package/lib/types/router-loop.d.ts +458 -0
  140. package/lib/types/session-engine-store.d.ts +138 -0
  141. package/lib/types/settings.d.ts +60 -0
  142. package/lib/types/skills.d.ts +85 -0
  143. package/package.json +150 -4
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The terminal bridge: an ACP agent's `terminal/*` calls, executed through the
3
+ * harness's own shell service.
4
+ *
5
+ * This is the piece that makes a tool-using ACP engine actually work. Grok Build
6
+ * runs every shell tool by asking the CLIENT to execute the command
7
+ * (`terminal/create`); a client that answers `methodNotFound` gives the model a
8
+ * broken tool, which is what this plugin shipped before. The bridge fixes that
9
+ * without touching the engine and without inventing a second execution path:
10
+ *
11
+ * - the command goes through `ctx.shell` (`ShellExecutor.resolve` +
12
+ * `execute`), which is the SAME service dsh's own `bash` tool uses — so the
13
+ * sandbox mode, the timeout policy and the output caps are dsh's, not a
14
+ * plugin-local imitation of them;
15
+ * - when the composition has no shell service the bridge REFUSES rather than
16
+ * falling back to a bare `child_process.spawn`: an unconfined execution
17
+ * channel is exactly the thing a sandbox exists to prevent, and silently
18
+ * becoming it would be worse than the tool failing;
19
+ * - the reverse-RPC approval gate still runs first, so a session whose approval
20
+ * policy says `ask` denies the command before it is ever spawned.
21
+ *
22
+ * ## Terminal lifecycle
23
+ *
24
+ * ACP models a terminal as a long-lived object: `create` returns an id, the agent
25
+ * polls `terminal/output`, awaits `terminal/wait_for_exit`, and finally calls
26
+ * `terminal/release`. The harness's shell handle already has that shape
27
+ * (`readOutput` is incremental, `done` settles at close, `kill` stops it), so the
28
+ * bridge is a thin adapter over one execution.
29
+ *
30
+ * @module dsh-loop-engine-allinone/driver-acp/terminal
31
+ */
32
+ import type { AcpTerminal, AcpTerminalSpawnOptions } from './capabilities.ts';
33
+ /**
34
+ * The part of the harness shell service this bridge uses, declared structurally.
35
+ *
36
+ * The real service is `@deepseek-ai/dsh-shell`'s `ShellExecutor`. It is declared
37
+ * here for the same reason every other host service in this plugin is: the plugin
38
+ * depends on the CONTRACT it consumes, and a minimal composition may not carry the
39
+ * package at all.
40
+ */
41
+ export interface ShellExecutorLike {
42
+ /**
43
+ * Fill in and cap an execution request.
44
+ * @param request - the caller's request.
45
+ * @returns the resolved spec to hand to {@link ShellExecutorLike.execute}.
46
+ */
47
+ resolve(request: ShellExecRequestLike): ShellExecSpecLike;
48
+ /**
49
+ * Prepare and spawn one command.
50
+ * @param spec - a resolved spec.
51
+ * @returns the prepared handle.
52
+ */
53
+ execute(spec: ShellExecSpecLike): Promise<ShellExecutionLike>;
54
+ }
55
+ /** The subset of the shell request shape this bridge fills in. */
56
+ export interface ShellExecRequestLike {
57
+ command: string;
58
+ workdir?: string;
59
+ timeoutMs?: number;
60
+ onExpiry?: 'kill' | 'none';
61
+ env?: Record<string, string>;
62
+ }
63
+ /** The resolved spec, echoed so the handle's result can be read. */
64
+ export interface ShellExecSpecLike extends ShellExecRequestLike {
65
+ workdir: string;
66
+ timeoutMs: number;
67
+ onExpiry: 'kill' | 'none';
68
+ }
69
+ /** The part of one shell execution this bridge reads. */
70
+ export interface ShellExecutionLike {
71
+ readonly done: Promise<void>;
72
+ readonly exitCode: number | null;
73
+ readOutput(): {
74
+ readonly stdout: string;
75
+ readonly stderr: string;
76
+ };
77
+ kill(): boolean;
78
+ result(): Promise<{
79
+ readonly exitCode: number | null;
80
+ readonly stdout: {
81
+ readonly text: string;
82
+ } | string;
83
+ readonly stderr: {
84
+ readonly text: string;
85
+ } | string;
86
+ }>;
87
+ }
88
+ /**
89
+ * Create the bridge an ACP client uses to answer `terminal/*`.
90
+ *
91
+ * @param shell - the harness shell service, or undefined when the composition has
92
+ * none (the bridge then refuses every command, see the module note).
93
+ * @param defaultCwd - the session's working directory, used when the agent names
94
+ * none of its own.
95
+ * @returns a spawn function for {@link AcpReverseHandlers.spawnTerminal}.
96
+ */
97
+ export declare function createTerminalBridge(shell: ShellExecutorLike | undefined, defaultCwd: string): (command: string, options: AcpTerminalSpawnOptions) => Promise<AcpTerminal>;
98
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The process transport an ACP driver speaks over, engine-neutral.
3
+ *
4
+ * The driver hands the client a handle carrying the child's stdin/stdout/stderr
5
+ * (projected from the dsh subprocess seam). The three engines differ in HOW the
6
+ * child is launched — `kimi acp`, `grok agent stdio`, `mcode acp` — and not at
7
+ * all in what the driver does with it, so the transport is declared once here and
8
+ * each engine's `process.ts` owns its own argv and executable resolution.
9
+ *
10
+ * The names are deliberately this generic: an engine that wants its own spelling
11
+ * for the published surface (`KimiProcess`) aliases these rather than restating
12
+ * them, so a change to the seam cannot reach one engine and miss another.
13
+ *
14
+ * @module dsh-loop-engine-allinone/driver-acp/transport
15
+ */
16
+ import type { Readable, Writable } from 'node:stream';
17
+ /** The exact argv/cwd/env the driver requests for one ACP child. */
18
+ export interface AcpSpawnSpec {
19
+ /** The program plus its flags; `argv[0]` is the engine's executable. */
20
+ readonly argv: readonly string[];
21
+ /** The session's working directory, also the ACP session's `cwd`. */
22
+ readonly cwd: string;
23
+ /** The child's environment, layered over the credential-scrubbed parent. */
24
+ readonly env: Record<string, string>;
25
+ /** Cancellation: the seam escalates process-tree termination when it fires. */
26
+ readonly signal?: AbortSignal;
27
+ }
28
+ /** A spawned ACP agent process, as the client's transport needs it. */
29
+ export interface AcpProcess {
30
+ /** Child stdin (JSON-RPC request frames). */
31
+ readonly stdin: Writable;
32
+ /** Child stdout (responses and `session/update` notifications). */
33
+ readonly stdout: Readable;
34
+ /** Child stderr (diagnostics; drained and dropped). */
35
+ readonly stderr: Readable;
36
+ /** Resolves when the child closes. */
37
+ readonly done: Promise<unknown>;
38
+ /** Request process-tree termination. */
39
+ terminate(): void;
40
+ }
41
+ /** Spawns one ACP child over the given spec (the driver's spawn capability). */
42
+ export type AcpSpawnCapability = (spec: AcpSpawnSpec) => AcpProcess;
43
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Agent Client Protocol (ACP) wire types shared by every ACP engine driver.
3
+ *
4
+ * Three engines speak ACP over stdio (JSON-RPC 2.0): Kimi Code (`kimi acp`),
5
+ * Grok Build (`grok agent stdio`) and MiniMax Code (`mcode acp`). The driver
6
+ * speaks the minimal, stable subset a normal agent flow needs: initialize,
7
+ * session/new, session/prompt, session/cancel, plus the `session/update`
8
+ * notifications carrying incremental assistant text (`agent_message_chunk`),
9
+ * thinking (`agent_thought_chunk`), and tool calls (`tool_call` /
10
+ * `tool_call_update`). Tool-approval arrives as the reverse-RPC
11
+ * `session/request_permission`, which the client must answer.
12
+ *
13
+ * The shapes below were READ from live frames, not from the specification, and
14
+ * each engine's own deltas are recorded where they matter
15
+ * (`docs/engine-registry.md`): kimi nests tool content
16
+ * as `{ type: 'content', content: { type: 'text' } }`, all three carry text
17
+ * chunks as `content: { type: 'text', text }`, and a model is selected through
18
+ * a DIFFERENT method per engine (`session/set_model` on kimi,
19
+ * `session/set_config_option` on grok and MiniMax Code). Content blocks tolerate
20
+ * block types this build does not know by ignoring them. The image block is
21
+ * modelled for the OUTGOING side — the bytes a dsh step delivers as an ACP
22
+ * image block.
23
+ *
24
+ * @module dsh-loop-engine-allinone/driver-acp/types
25
+ */
26
+ /** A text content block in an ACP update (the observed kimi shape). */
27
+ export interface AcpTextContent {
28
+ readonly type: 'text';
29
+ readonly text: string;
30
+ }
31
+ /** One tool-call content block: the observed kimi `{ type: 'content', content }` nesting. */
32
+ export interface AcpToolContentBlock {
33
+ readonly type: 'content';
34
+ readonly content: AcpTextContent;
35
+ }
36
+ /** An image content block, as ACP/MCP spells it (base64 bytes + media type). */
37
+ export interface AcpImageContent {
38
+ readonly type: 'image';
39
+ /** Base64-encoded image bytes. */
40
+ readonly data: string;
41
+ /** The image's own media type. */
42
+ readonly mimeType: string;
43
+ }
44
+ /** A content block in a chunk/thought update; text is handled, others are ignored. */
45
+ export type AcpContentBlock = AcpTextContent | AcpImageContent | {
46
+ readonly type: 'resource_link' | 'audio' | string;
47
+ readonly [key: string]: unknown;
48
+ };
49
+ /** One `session/update` notification (partial; the discriminator is `sessionUpdate`). */
50
+ export interface AcpUpdate {
51
+ readonly sessionUpdate: string;
52
+ readonly [key: string]: unknown;
53
+ }
54
+ /**
55
+ * A tool-call announcement (`sessionUpdate: 'tool_call'`).
56
+ *
57
+ * Measured against kimi 0.28.x: an announcement carries the call's identity
58
+ * (`toolCallId`/`title`/`kind`) and an empty content card, but **never** its
59
+ * input — `rawInput` only ever arrives on a later `tool_call_update` (the
60
+ * execution-start frame).
61
+ */
62
+ export interface AcpToolCallExt extends AcpUpdate {
63
+ readonly sessionUpdate: 'tool_call';
64
+ readonly toolCallId: string;
65
+ readonly title: string;
66
+ readonly kind: string;
67
+ readonly status: string;
68
+ readonly content?: readonly AcpToolContentBlock[];
69
+ /** The call's real input; absent on the announcement (see above). */
70
+ readonly rawInput?: unknown;
71
+ }
72
+ /** A tool-call progress/result stream (`sessionUpdate: 'tool_call_update'`). */
73
+ export interface AcpToolCallStreamExt extends AcpUpdate {
74
+ readonly sessionUpdate: 'tool_call_update';
75
+ readonly toolCallId: string;
76
+ readonly status: string;
77
+ readonly content?: readonly AcpToolContentBlock[];
78
+ /** The call's real input, first present on the execution-start frame. */
79
+ readonly rawInput?: unknown;
80
+ /** The tool's raw structured output, present once the call settles. */
81
+ readonly rawOutput?: unknown;
82
+ }
83
+ /** One option the agent offers for a `session/request_permission`. */
84
+ export interface AcpPermissionOption {
85
+ /** Opaque id the client sends back in `outcome.optionId` (the only field the agent reads). */
86
+ readonly optionId: string;
87
+ /** Human-readable label for the option. */
88
+ readonly name?: string;
89
+ /** Option class: `allow_once` / `allow_always` / `reject_once` / `reject_always`. */
90
+ readonly kind?: string;
91
+ }
92
+ /** The `session/request_permission` reverse-RPC params. */
93
+ export interface AcpPermissionRequest {
94
+ readonly sessionId?: string;
95
+ /** Every option the agent will accept an answer for; empty means no answerable choice. */
96
+ readonly options?: readonly AcpPermissionOption[];
97
+ /** The tool call the prompt belongs to (presentation/correlation only). */
98
+ readonly toolCall?: unknown;
99
+ }
100
+ /**
101
+ * Terminal outcome of a permission request: the option the client picked, or
102
+ * `cancelled` when it had no answer to give.
103
+ */
104
+ export type AcpPermissionOutcome = {
105
+ readonly outcome: 'selected';
106
+ readonly optionId: string;
107
+ } | {
108
+ readonly outcome: 'cancelled';
109
+ };
110
+ /** The `session/request_permission` result the agent expects (ACP `RequestPermissionResponse`). */
111
+ export interface AcpPermissionResponse {
112
+ readonly outcome: AcpPermissionOutcome;
113
+ }
114
+ /** Result of a completed `session/prompt` request (opaque; the turn ended). */
115
+ export interface AcpPromptResult {
116
+ readonly [key: string]: unknown;
117
+ }
118
+ /** The id-scoped response to a request (result or error). */
119
+ export interface AcpResponse {
120
+ readonly id: number;
121
+ readonly result?: unknown;
122
+ readonly error?: {
123
+ readonly code: number;
124
+ readonly message: string;
125
+ };
126
+ }
127
+ /** A JSON-RPC request/notification frame on the wire. */
128
+ export interface AcpFrame {
129
+ readonly jsonrpc: '2.0';
130
+ readonly id?: number;
131
+ readonly method?: string;
132
+ readonly params?: unknown;
133
+ readonly result?: unknown;
134
+ readonly error?: {
135
+ readonly code: number;
136
+ readonly message: string;
137
+ };
138
+ }
139
+ /** Guards toolkit: is the frame a `session/update` notification carrying an update. */
140
+ export declare function isUpdateFrame(frame: AcpFrame): frame is AcpFrame & {
141
+ readonly method: 'session/update';
142
+ readonly params: {
143
+ readonly sessionId: string;
144
+ readonly update: AcpUpdate;
145
+ };
146
+ };
147
+ /** Guards toolkit: is the frame a reverse-RPC request needing an approval answer. */
148
+ export declare function isPermissionRequestFrame(frame: AcpFrame): frame is AcpFrame & {
149
+ readonly id: number;
150
+ readonly method: 'session/request_permission';
151
+ };
152
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Capability guards: what a driver does when its engine cannot do what the step
3
+ * asked for.
4
+ *
5
+ * The plugin's rule is that an engine which cannot honour something says so,
6
+ * rather than accepting it and quietly doing something else ("能力不足的引擎如实
7
+ * 降级,不假装支持"). The registry declares each engine's capabilities
8
+ * (`EngineCapabilities`), and this module turns a declaration into the check a
9
+ * driver runs before it spends a child process on a request the engine will
10
+ * mistranslate.
11
+ *
12
+ * The distinction that matters: a capability the engine LACKS is a refusal with
13
+ * the user's next action named, not a silent drop. Dropping an image and sending
14
+ * the text alone would look to the user like the engine read the picture.
15
+ *
16
+ * @module dsh-loop-engine-allinone/driver-cli/capability
17
+ */
18
+ import type { UserMessage } from '@deepseek-ai/dsh-session';
19
+ import type { EngineDescriptor } from '../engine-registry.ts';
20
+ /**
21
+ * Count the image blocks this step delivers.
22
+ *
23
+ * Only THIS step's own messages, not the whole transcript: an older image in the
24
+ * history is already a text placeholder in the serialized prompt (that is what
25
+ * `serializeHistory` does), so only a fresh attachment can be lost by an engine
26
+ * with no image channel.
27
+ * @param messages - the step's message batch.
28
+ * @returns how many image blocks they carry.
29
+ */
30
+ export declare function stepImageCount(messages: readonly UserMessage[]): number;
31
+ /**
32
+ * The refusal to report when a step delivers images to an engine that declared
33
+ * it cannot take them, or undefined when the step is fine.
34
+ *
35
+ * The message names the engine, the count, and what the user can do — which is
36
+ * the difference between a refusal and a puzzle.
37
+ * @param messages - the step's message batch.
38
+ * @param descriptor - the engine's registry row.
39
+ * @returns the refusal message, or undefined.
40
+ */
41
+ export declare function unsupportedImagesRefusal(messages: readonly UserMessage[], descriptor: EngineDescriptor): string | undefined;
42
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1,258 @@
1
+ /**
2
+ * The shared CLI agent: the turn/step lifecycle every external-CLI engine runs,
3
+ * in one place.
4
+ *
5
+ * The four original drivers each carry their own copy of this machinery — about
6
+ * 450 near-identical lines apiece (the `jscpd:ignore` markers in
7
+ * `src/engine-<id>/agent.ts` mark exactly that duplication). Keeping those copies
8
+ * was the right call at the time: they were written and hardened one engine at a
9
+ * time, against protocols whose failure modes differed. It is the wrong call for
10
+ * engines added after this refactor, because the machinery is where the subtle
11
+ * bugs live (a missing `agent/error`, a step left open, an inbox claim that
12
+ * never wakes the driver) and a new engine should not have to rediscover them.
13
+ *
14
+ * So a NEW engine implements {@link CliHostedAgent} instead. It supplies:
15
+ *
16
+ * - {@link CliHostedAgent.runStep} — the protocol round trip, which is the only
17
+ * part that is actually engine-specific; and
18
+ * - whatever transport, argv and frame mapping that round trip needs.
19
+ *
20
+ * Everything else — the phase machine, the durable inbox, cancellation, the
21
+ * pre-step waterfall, skill injection, maintenance, the status transitions —
22
+ * is inherited and therefore identical across engines by construction.
23
+ *
24
+ * The durable-log half of a step is {@link SessionSink}, which this base owns
25
+ * and hands to the subclass. The split is deliberate: the base owns *when*
26
+ * things happen (turn opened, step started, step closed), the sink owns *what
27
+ * lands in the log* (messages, tool calls, results, streams), and the subclass
28
+ * owns *what the engine said*.
29
+ *
30
+ * @module dsh-loop-engine-allinone/driver-cli/cli-agent
31
+ */
32
+ import type { Agent, AgentCancelCause, AgentOptions, AgentStatus, CancelOptions, InboxTarget } from '@deepseek-ai/dsh-agent';
33
+ import type { ImageAttachmentAccessResolver, Message } from '@deepseek-ai/dsh-llm';
34
+ import type { Scope } from '@deepseek-ai/dsh-scope';
35
+ import type { Session, SessionId, TurnEndReason, UserMessage } from '@deepseek-ai/dsh-session';
36
+ import type { Context } from '@deepseek-ai/cordis';
37
+ import type { HostedEngineId } from '../engine-registry.ts';
38
+ import { DriverInbox } from '../driver-core/inbox.ts';
39
+ import { SessionSink, type SinkPosition } from './session-sink.ts';
40
+ /**
41
+ * Provider route label every hosted engine logs into `request/header` and into
42
+ * message provenance — the ONE route they share
43
+ * ({@link HOSTED_ROUTE_LABEL}), so the model menu carries a single `external`
44
+ * group instead of one per engine.
45
+ */
46
+ export declare const CLI_PROVIDER = "external";
47
+ /** The ending a step reports, before the turn loop folds it. */
48
+ type StepEndReason = Extract<TurnEndReason, {
49
+ kind: 'completed' | 'max-tokens';
50
+ }>;
51
+ /**
52
+ * What one subclass needs to tell the base about itself.
53
+ *
54
+ * A small object rather than a long constructor parameter list, because every
55
+ * engine adds fields to it over time and a positional list makes each addition a
56
+ * breaking change for every subclass.
57
+ */
58
+ export interface CliAgentHost {
59
+ /** Which engine this agent runs, for tool-name projection and diagnostics. */
60
+ readonly engine: HostedEngineId;
61
+ /**
62
+ * The model label logged into `request/header` and message provenance.
63
+ *
64
+ * The engine's own word for "whatever it decides" when the deployment pinned
65
+ * none — it is what makes the session's `(provider, model)` resolve to this
66
+ * plugin's placeholder route entry instead of a composite string naming a
67
+ * model no adapter serves.
68
+ */
69
+ readonly model: string | undefined;
70
+ /**
71
+ * The environment entries the child is spawned with, layered over the
72
+ * credential-scrubbed parent environment.
73
+ */
74
+ readonly env: Record<string, string>;
75
+ }
76
+ /**
77
+ * The shared base for every external-CLI engine.
78
+ *
79
+ * Subclasses implement {@link runStep} only. Everything else here is the
80
+ * lifecycle the harness's `Agent` contract requires, written once.
81
+ */
82
+ export declare abstract class CliHostedAgent implements Agent {
83
+ /**
84
+ * The context the agent was built on.
85
+ *
86
+ * Protected rather than private: a subclass's protocol round trip needs the
87
+ * same host reads the four original drivers make from this context (the
88
+ * session's model selection, the attachment service), and re-deriving them
89
+ * from the agent-scoped context would be a second, subtly different read.
90
+ */
91
+ protected readonly loopCtx: Context;
92
+ /** This agent's session id. */
93
+ readonly id: SessionId;
94
+ /** The loop options the session was built with. */
95
+ readonly options: AgentOptions;
96
+ /** The durable session this agent serves. */
97
+ readonly session: Session;
98
+ /** Engine facts the base cannot answer for itself. */
99
+ protected readonly hostFacts: CliAgentHost;
100
+ /** The durable inbox, driven from the session's own log. */
101
+ readonly inbox: DriverInbox;
102
+ /** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
103
+ readonly scope: Scope;
104
+ /** The agent's own context, carrying the agent in its store. */
105
+ readonly ctx: Context;
106
+ /** The shared durable-log writer for this agent's lifecycle. */
107
+ protected readonly sink: SessionSink;
108
+ /** Resolves the read-only path behind each image block of the prompt. */
109
+ protected readonly imageAccess: ImageAttachmentAccessResolver;
110
+ private phase;
111
+ private activityDone;
112
+ private readonly dispatch;
113
+ private requestHeaderLogged;
114
+ /**
115
+ * Whether a wake was requested while the activity could not accept it.
116
+ *
117
+ * Held on the phase rather than here so it is reset with the phase, which is
118
+ * what makes "the last wake wins" true across a turn boundary.
119
+ */
120
+ constructor(
121
+ /**
122
+ * The context the agent was built on.
123
+ *
124
+ * Protected rather than private: a subclass's protocol round trip needs the
125
+ * same host reads the four original drivers make from this context (the
126
+ * session's model selection, the attachment service), and re-deriving them
127
+ * from the agent-scoped context would be a second, subtly different read.
128
+ */
129
+ loopCtx: Context,
130
+ /** This agent's session id. */
131
+ id: SessionId,
132
+ /** The loop options the session was built with. */
133
+ options: AgentOptions,
134
+ /** The durable session this agent serves. */
135
+ session: Session,
136
+ /** Engine facts the base cannot answer for itself. */
137
+ hostFacts: CliAgentHost);
138
+ /** The label the session's requests and messages are recorded under. */
139
+ protected modelLabel(): string;
140
+ /** The engine this agent runs. */
141
+ protected get engine(): HostedEngineId;
142
+ /** The environment entries the child is spawned with. */
143
+ protected get env(): Record<string, string>;
144
+ /**
145
+ * Run one step's protocol round trip.
146
+ *
147
+ * The ONLY method a subclass must implement, and the only place an engine is
148
+ * actually different. It is handed the position its frames belong to and must
149
+ * feed them to {@link CliHostedAgent.sink}; returning means the engine
150
+ * settled.
151
+ *
152
+ * A protocol failure (a malformed frame, a child that died mid-run) should
153
+ * throw — the base reports it as the turn's `error` ending. An engine that
154
+ * merely answered badly is NOT a protocol failure: report it through the sink
155
+ * (`{ kind: 'error' }`) and the base turns it into a failed turn with the
156
+ * engine's own words.
157
+ * The return value is the step's own verdict on the turn: `{ kind: 'completed' }`
158
+ * for an ordinary answer, `{ kind: 'max-tokens' }` when the engine hit its
159
+ * output ceiling, or `null` to say the sink's own verdict stands. The turn loop
160
+ * takes the FIRST verdict a step reports and keeps it for the turn.
161
+ * @param messages - the messages this step delivers.
162
+ * @param position - the turn/step the frames belong to.
163
+ * @param signal - cancellation.
164
+ * @returns the step's ending.
165
+ */
166
+ protected abstract runStep(messages: readonly UserMessage[], position: SinkPosition, signal: AbortSignal): Promise<StepEndReason | null>;
167
+ /**
168
+ * Called after the sink rotates to a new step, so a subclass can reset any
169
+ * per-step state it holds.
170
+ * @param step - the step number now open.
171
+ */
172
+ protected onStepRotated(step: number): void;
173
+ /**
174
+ * Release whatever the subclass opened for the step that just ended.
175
+ *
176
+ * Called from the step's `finally`, so it runs on success, failure and
177
+ * cancellation alike. A driver that spawns per step disposes its child here; a
178
+ * driver holding a persistent child leaves it.
179
+ */
180
+ protected releaseStep(): void;
181
+ /**
182
+ * The transport's own teardown, when the agent's scope is unwound.
183
+ *
184
+ * Where a persistent child is finally closed. A per-step transport has
185
+ * nothing to do here, because {@link releaseStep} already released it.
186
+ */
187
+ protected disposeTransport(): void;
188
+ get status(): AgentStatus;
189
+ /** Commit a phase and publish its externally visible status transition. */
190
+ private setPhase;
191
+ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;
192
+ /**
193
+ * Queue a message for the next turn and wake the driver.
194
+ * @param input - the user message to deliver.
195
+ */
196
+ followup(input: UserMessage): void;
197
+ /**
198
+ * Queue a message for the running step and wake the driver.
199
+ * @param input - the user message to deliver.
200
+ */
201
+ steer(input: UserMessage): void;
202
+ /**
203
+ * Queue a message for the running step without waking the driver.
204
+ * @param input - the user message to deliver.
205
+ */
206
+ inject(input: UserMessage): void;
207
+ cancel(cause: AgentCancelCause, options?: CancelOptions): void;
208
+ /**
209
+ * Run a maintenance job while the agent is idle.
210
+ * @param job - the maintenance operation, receiving the phase abort signal.
211
+ * @returns the maintenance result.
212
+ */
213
+ runMaintenance<T>(job: (signal: AbortSignal) => Promise<T>): Promise<T>;
214
+ /**
215
+ * Start one driver, or latch its wake behind maintenance or an aborted
216
+ * activity.
217
+ * @param wakeAfterAbort - the {@link send} classification, captured before the
218
+ * inbox insertion so a reentrant cancel cannot reclassify it.
219
+ */
220
+ private wakeDriver;
221
+ whenIdle(): Promise<void>;
222
+ /** Report one failure at its live boundary, then preserve it for driver containment. */
223
+ private throwError;
224
+ private kick;
225
+ /** Open one turn and run its steps until the inbox stops feeding it. */
226
+ private turn;
227
+ /**
228
+ * Claim this step's messages and run them past the pre-step waterfall.
229
+ * @param target - which inbox queue to claim from.
230
+ * @param position - the turn/step being proposed.
231
+ * @returns the step's decision.
232
+ */
233
+ private preStep;
234
+ /**
235
+ * Scan the step's user messages for `/name` skill gestures, load each matching
236
+ * skill, and inject the rendered content into the batch.
237
+ * @param messages - the step's message batch.
238
+ * @param signal - cancellation (aborted loads are silently dropped).
239
+ * @returns the original batch, or an extended one.
240
+ */
241
+ private injectSkills;
242
+ /**
243
+ * Append the session's request-header snapshot, once per loop instance.
244
+ *
245
+ * Written by the driver rather than the sink because it describes the request
246
+ * the ENGINE was asked to make, and only the driver knows when the first one
247
+ * is about to go out.
248
+ */
249
+ protected assertRequestHeader(): void;
250
+ /** Append a `tool/call` event for one call this step announced. */
251
+ protected appendToolCall(callId: string, name: string, args: string): void;
252
+ /** The engine-scope teardown hook, installed in the constructor. */
253
+ protected installTransportTeardown(): void;
254
+ /** The transcript this step should send, as the sink's caller sees it. */
255
+ protected history(): Message[];
256
+ }
257
+ export {};
258
+ //# sourceMappingURL=cli-agent.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The shared runtime base for every external-CLI engine.
3
+ *
4
+ * Each engine's own `loop.ts` used to carry the same four lines of ceremony:
5
+ * resolve the subprocess service, throw a loud engine-specific error if the
6
+ * profile has none, and build a spawn capability that remembers the termination
7
+ * grace period and whether the prompt travels on stdin. That is identical for
8
+ * every CLI engine, so it lives here, and an engine's runtime is left with the
9
+ * two things that are actually its own: how to read its composition-entry knobs,
10
+ * and how to construct its agent.
11
+ *
12
+ * The subprocess service is still resolved LAZILY (at construction, not at
13
+ * plugin apply), which is the original drivers' deliberate choice and is
14
+ * preserved here: an engine is built only when a session actually selects it, so
15
+ * a profile without the subprocess service fails THAT session loud instead of
16
+ * stalling the whole plugin at boot.
17
+ *
18
+ * @module dsh-loop-engine-allinone/driver-cli/cli-loop
19
+ */
20
+ import type { Context } from '@deepseek-ai/cordis';
21
+ import { HostedEngineRuntime } from '../driver-core/hosted-engine-runtime.ts';
22
+ import type { HostedAgent } from '../driver-core/hosted-engine-runtime.ts';
23
+ import type { CliSpawnCapability } from './transport.ts';
24
+ /**
25
+ * One CLI engine's creation/resume machinery.
26
+ *
27
+ * `TConfig` is the engine's own resolved configuration and `TAgent` its agent;
28
+ * the spawn capability is owned here so a subclass never has to thread it.
29
+ */
30
+ export declare abstract class CliEngineRuntime<TConfig, TAgent extends HostedAgent> extends HostedEngineRuntime<TConfig, TAgent> {
31
+ /**
32
+ * The engine's spawn capability: the dsh subprocess seam with this engine's
33
+ * grace period and stdin decision already applied.
34
+ */
35
+ protected readonly spawn: CliSpawnCapability;
36
+ /**
37
+ * @param ctx - the owning context.
38
+ * @param label - effect-label prefix, e.g. `agentLoopQoderclicn`.
39
+ * @param config - already-resolved engine configuration.
40
+ * @param engine - the engine id, for the "needs the subprocess service" diagnostic.
41
+ * @param graceMs - grace period for process-tree termination.
42
+ * @param pipeStdin - whether this engine reads its prompt from the child's stdin.
43
+ */
44
+ constructor(ctx: Context, label: string, config: TConfig, engine: string, graceMs: number, pipeStdin: boolean);
45
+ }
46
+ //# sourceMappingURL=cli-loop.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Flatten one engine-reported tool result to the text a durable message stores.
3
+ *
4
+ * Its own module rather than a helper inside the sink, for one structural
5
+ * reason: the protocol adapters need it (`stream-json.ts` calls it for every
6
+ * `tool_result` block), and the sink imports the HOST plane
7
+ * (`@deepseek-ai/dsh-llm`, `@deepseek-ai/dsh-session`). Keeping the flattener
8
+ * here means an adapter can be exercised against a real CLI — as
9
+ * `scripts/probe/smoke-driver.ts` does — without loading a Cordis context.
10
+ *
11
+ * The rules:
12
+ *
13
+ * - a string is the answer;
14
+ * - `null`/`undefined` is empty, which the caller turns into a visible
15
+ * placeholder rather than an unrenderable empty block;
16
+ * - a list of blocks is joined, reading each block's own `text`;
17
+ * - a block that carries no text is JSON-encoded rather than dropped, so a
18
+ * structured result stays inspectable in the transcript;
19
+ * - an object with a `content` member is unwrapped, because that is the
20
+ * wrapper several CLIs put around their real payload.
21
+ *
22
+ * @module dsh-loop-engine-allinone/driver-cli/flatten
23
+ */
24
+ /**
25
+ * Flatten one engine tool result to the text dsh stores.
26
+ *
27
+ * Engines report a result as a string, as a list of content blocks, or as an
28
+ * arbitrary JSON value; dsh's durable shape is text. Anything that is not text
29
+ * is JSON-encoded rather than dropped, so a structured result stays inspectable
30
+ * in the transcript instead of vanishing.
31
+ * @param result - the engine's result payload.
32
+ * @returns the text to store.
33
+ */
34
+ export declare function flattenToolResult(result: unknown): string;
35
+ //# sourceMappingURL=flatten.d.ts.map