@aiwg/cli 2026.10.0 → 2026.10.3

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 (168) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +270 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +3091 -24
  3. package/bin/aiwg.mjs +86 -21
  4. package/dist/src/a2a/codecs.js +9 -0
  5. package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
  6. package/dist/src/artifacts/repair.js +9 -4
  7. package/dist/src/channel/manager.mjs +59 -10
  8. package/dist/src/cli/child-process-result.js +40 -0
  9. package/dist/src/cli/env.js +38 -16
  10. package/dist/src/cli/flag-parsing.js +236 -0
  11. package/dist/src/cli/handlers/decision.js +297 -2
  12. package/dist/src/cli/handlers/diagnose.js +9 -2
  13. package/dist/src/cli/handlers/feedback.js +14 -5
  14. package/dist/src/cli/handlers/init.js +245 -30
  15. package/dist/src/cli/handlers/mc.js +23 -0
  16. package/dist/src/cli/handlers/models.js +69 -6
  17. package/dist/src/cli/handlers/ralph.js +3 -0
  18. package/dist/src/cli/handlers/refresh.js +16 -0
  19. package/dist/src/cli/handlers/regenerate.js +14 -0
  20. package/dist/src/cli/handlers/runtime-info.js +24 -9
  21. package/dist/src/cli/handlers/script-runner.js +16 -6
  22. package/dist/src/cli/handlers/session.js +396 -62
  23. package/dist/src/cli/handlers/sessions.js +146 -40
  24. package/dist/src/cli/handlers/setup-manifest.js +439 -39
  25. package/dist/src/cli/handlers/setup.js +139 -39
  26. package/dist/src/cli/handlers/subcommands.js +3 -1
  27. package/dist/src/cli/handlers/team.js +18 -8
  28. package/dist/src/cli/handlers/use.js +13 -0
  29. package/dist/src/cli/handlers/utilities.js +106 -23
  30. package/dist/src/cli/prompt-utils.js +72 -0
  31. package/dist/src/cli/provider-resolution.js +11 -0
  32. package/dist/src/config/aiwg-config.js +268 -4
  33. package/dist/src/config/cli.js +146 -19
  34. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  35. package/dist/src/config/project-artifacts-health.mjs +14 -3
  36. package/dist/src/config/user-config.js +36 -10
  37. package/dist/src/decision/adapters/jev.js +80 -28
  38. package/dist/src/decision/ask.js +352 -0
  39. package/dist/src/decision/driver.js +4 -0
  40. package/dist/src/decision/entry.js +38 -6
  41. package/dist/src/decision/heldout/collector.js +10 -1
  42. package/dist/src/decision/index.js +3 -0
  43. package/dist/src/decision/interop.js +60 -8
  44. package/dist/src/decision/jev-credentials.js +121 -0
  45. package/dist/src/decision/ledger.js +839 -0
  46. package/dist/src/decision/validate.js +6 -3
  47. package/dist/src/effects/dispatch-verifier.js +252 -0
  48. package/dist/src/effects/index.js +1 -0
  49. package/dist/src/features/installer.js +2 -2
  50. package/dist/src/flow/graph-metadata.js +5 -0
  51. package/dist/src/gates/providers/loader.js +3 -1
  52. package/dist/src/installation/manager-command.mjs +1 -1
  53. package/dist/src/installation/manager.mjs +17 -2
  54. package/dist/src/lint/runner.js +2 -2
  55. package/dist/src/mcp/adapters/codex-runtime.js +360 -56
  56. package/dist/src/mcp/cli.mjs +587 -77
  57. package/dist/src/mcp/config-file.mjs +279 -0
  58. package/dist/src/mcp/config-layers.mjs +348 -0
  59. package/dist/src/mcp/credentials.mjs +484 -0
  60. package/dist/src/mcp/grok-build-config.mjs +16 -26
  61. package/dist/src/mcp/helpers.mjs +106 -2
  62. package/dist/src/mcp/omp-config.mjs +50 -29
  63. package/dist/src/mcp/profiles.js +97 -23
  64. package/dist/src/mcp/profiles.mjs +99 -27
  65. package/dist/src/mcp/registry.js +264 -57
  66. package/dist/src/mcp/registry.mjs +198 -45
  67. package/dist/src/mcp/server.mjs +403 -20
  68. package/dist/src/mcp/toml-string.mjs +9 -0
  69. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  70. package/dist/src/mcp/tool-filters.mjs +357 -0
  71. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  72. package/dist/src/mcp/tools/decision.mjs +58 -0
  73. package/dist/src/mcp/tools/orchestration.mjs +38 -0
  74. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  75. package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
  76. package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
  77. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  78. package/dist/src/memory/project-registry.js +2 -2
  79. package/dist/src/mission-protocol/codecs.js +639 -1
  80. package/dist/src/mission-protocol/types.js +110 -0
  81. package/dist/src/mission-protocol/worker-status.js +2 -0
  82. package/dist/src/models/config-loader.js +70 -13
  83. package/dist/src/models/grok-build-models.js +93 -41
  84. package/dist/src/models/model-capabilities.v1.json +3091 -24
  85. package/dist/src/models/model-discovery.js +862 -63
  86. package/dist/src/models/provider-policy.js +625 -11
  87. package/dist/src/models/qualification-digest.mjs +17 -0
  88. package/dist/src/models/resolver.js +259 -0
  89. package/dist/src/models/worker-direct-readiness.js +529 -0
  90. package/dist/src/models/worker-dispatch-offer.js +169 -0
  91. package/dist/src/models/worker-health-readiness.js +131 -0
  92. package/dist/src/models/worker-installation.mjs +879 -0
  93. package/dist/src/models/wrapper-route.js +18 -0
  94. package/dist/src/ops/registry.js +9 -14
  95. package/dist/src/plugins/standalone-packager.js +4 -3
  96. package/dist/src/process/bounded-child-process.mjs +387 -0
  97. package/dist/src/process/cli-command.mjs +75 -0
  98. package/dist/src/providers/capability-matrix.js +8 -0
  99. package/dist/src/providers/capability-matrix.yaml +270 -0
  100. package/dist/src/providers/grok-build-headless.js +43 -21
  101. package/dist/src/providers/provider-definitions.js +9 -1
  102. package/dist/src/providers/provider-definitions.mjs +9 -2
  103. package/dist/src/serve/executor-registry.js +16 -24
  104. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  105. package/dist/src/serve/mission-conductor.js +9 -0
  106. package/dist/src/serve/shared-host-scheduler.js +2 -0
  107. package/dist/src/serve/worker-invocation.js +538 -0
  108. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  109. package/dist/src/sessions/adapter-registry.js +45 -0
  110. package/dist/src/sessions/adapters/generic.js +37 -3
  111. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  112. package/dist/src/sessions/adapters/grokbot.js +423 -24
  113. package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
  114. package/dist/src/sessions/adapters/muse.js +68 -29
  115. package/dist/src/sessions/adapters/pi.js +1 -0
  116. package/dist/src/sessions/batch-contracts.js +13 -1
  117. package/dist/src/sessions/batch-import.js +23 -42
  118. package/dist/src/sessions/candidates.js +47 -11
  119. package/dist/src/sessions/contracts.js +35 -0
  120. package/dist/src/sessions/coverage-preflight.js +134 -0
  121. package/dist/src/sessions/discovery.js +3 -0
  122. package/dist/src/sessions/grok-build-cli.js +67 -5
  123. package/dist/src/sessions/importer.js +24 -8
  124. package/dist/src/sessions/index.js +2 -0
  125. package/dist/src/sessions/repository.js +54 -13
  126. package/dist/src/sessions/workspace-discovery.js +99 -37
  127. package/dist/src/skills/adapters/clawhub.js +10 -8
  128. package/dist/src/skills/adapters/openclaw.js +10 -7
  129. package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
  130. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  131. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  132. package/dist/src/smiths/mcpsmith/example.js +1 -1
  133. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
  134. package/dist/src/storage/backends/logseq.js +68 -7
  135. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  136. package/dist/src/testing/generators/test-code-generator.js +57 -30
  137. package/dist/src/uhp/client.js +11 -1
  138. package/dist/src/uhp/mission.js +5 -0
  139. package/dist/src/update/checker.mjs +5 -5
  140. package/dist/src/update/notifier.mjs +54 -2
  141. package/dist/src/writing/fidelity.js +18 -1
  142. package/package.json +18 -1
  143. package/schemas/mission-v1.schema.json +2429 -0
  144. package/schemas/models/model-catalog.v1.schema.json +301 -0
  145. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  146. package/schemas/models/model-policy.v1.schema.json +14 -0
  147. package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
  148. package/tools/_resolve-impl.mjs +39 -0
  149. package/tools/agents/deploy-agents.mjs +15 -2
  150. package/tools/agents/providers/base.mjs +63 -43
  151. package/tools/agents/providers/grokbot.mjs +11 -12
  152. package/tools/agents/providers/hermes.mjs +6 -3
  153. package/tools/agents/providers/omp.mjs +5 -2
  154. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  155. package/tools/providers/deepseek-harness-transport.mjs +358 -0
  156. package/tools/providers/omp-transport.mjs +192 -0
  157. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  158. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  159. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  160. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  161. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  162. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  163. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  164. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  165. package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
  166. package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
  167. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  168. package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
@@ -0,0 +1,397 @@
1
+ import { resolve, join } from 'node:path';
2
+ import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
3
+ import { assertSupportedDshVersion } from '../../providers/deepseek-harness-transport.mjs';
4
+ import { classifyWorkerHealthFailure, healthEventMatchesBinding } from './worker-health.mjs';
5
+
6
+ export class DeepSeekHarnessAdapter extends ProviderAdapter {
7
+ // The headless surface supplies plain final/exit only. Cordis logs remain
8
+ // unavailable until their locator, ownership and grammar are qualified.
9
+ observeHealthEvent() { return null; }
10
+ getBinary() { return process.env.AIWG_DSH_BIN || 'dsh'; }
11
+ getName() { return 'deepseek-harness'; }
12
+ getCapabilities() {
13
+ return { streamJson: false, sessionResume: true, budgetControl: false,
14
+ systemPrompt: false, agentMode: true, mcpConfig: true, maxTurns: false,
15
+ rpcAbort: false };
16
+ }
17
+ async isAvailable() {
18
+ if (!await super.isAvailable()) return false;
19
+ try { assertSupportedDshVersion(await this.getVersion()); return true; } catch { return false; }
20
+ }
21
+ buildSessionArgs(options) {
22
+ const args = ['--profile', 'headless'];
23
+ const projectPatch = process.env.AIWG_DSH_PROJECT_PATCH || '.dsh/aiwg.cordis.patch.yml';
24
+ args.push('--patch', resolve(projectPatch));
25
+ if (process.env.AIWG_DSH_ROUTE_PATCH) args.push('--patch', resolve(process.env.AIWG_DSH_ROUTE_PATCH));
26
+ if (options.budget) this.warnUnsupported('budgetControl', 'Budget control');
27
+ if (options.maxTurns) this.warnUnsupported('maxTurns', 'Max turns');
28
+ if (options.systemPrompt) this.warnUnsupported('systemPrompt', 'Appended system prompts');
29
+ args.push(options.prompt);
30
+ return args;
31
+ }
32
+ buildAnalysisArgs(options) { return this.buildSessionArgs(options); }
33
+ mapModel(model) { return model; }
34
+ getEnvOverrides() { return { DSH_PERMISSION_MODE: 'workspace-write', DSH_TELEMETRY_DISABLED: '1', NO_COLOR: '1' }; }
35
+ parseOutput(stdout) {
36
+ const text = stdout.trim();
37
+ return text ? { events: [{ type: 'assistant_final', text }], settled: true, text } : null;
38
+ }
39
+ }
40
+
41
+ registerProvider('deepseek-harness', () => new DeepSeekHarnessAdapter());
42
+ registerProvider('dsh', () => new DeepSeekHarnessAdapter());
43
+
44
+ /**
45
+ * `dsh` (DeepSeek Harness) version-bound direct-worker profile
46
+ * (roctinam/aiwg#2909). Evidence, grades and the exact commands run are
47
+ * captured in docs/providers/deepseek-harness-direct.md. Summary:
48
+ *
49
+ * dsh --profile headless --patch <patchPath> --json - (brief on stdin, EOF)
50
+ *
51
+ * `--profile headless --help` (H, dsh 0.2.0-rc.2) lists no `--model`,
52
+ * `--effort` or `--sandbox` flag; the task positional is `-` to read stdin
53
+ * ("`-` reads stdin"). A no-model probe (no-login throwaway `DSH_HOME`,
54
+ * `echo task | dsh --profile headless --json -`) proves `-` really reads
55
+ * stdin to EOF and reaches the credential check (`MISSING_CREDENTIAL`
56
+ * before any network call) rather than failing argv parsing — confirming
57
+ * stdin arity per the LESSONS probe requirement.
58
+ *
59
+ * Model/effort/sandbox/approval are configured only through
60
+ * `--profile headless --dump-config-schema`/`--dump-default-config`
61
+ * (R-grade, read 2026-10-11): the `agent-default-model` plugin entry takes
62
+ * `{ provider, model, reasoningEffort }` (schema `config9`, `required:
63
+ * ["provider","model"]`; `reasoningEffort` enum observed on the bundled
64
+ * `deepseek-official` provider config is `off|low|high|max`); the
65
+ * `sandbox-policy` plugin entry takes `{ mode }` (`read-only|workspace-write|
66
+ * danger-full-access`); the `approval` plugin entry takes `{ policy }`
67
+ * (`ask|never`). This overlay shape comes only from the schema/default-config
68
+ * dumps, never from a run that actually exercised a tool call, so it is
69
+ * UNVERIFIED, not confirmed enforcement:
70
+ *
71
+ * - Reaching `MISSING_CREDENTIAL` on the no-model probe below proves only
72
+ * that the `--patch` file is read and PARSED before any model call. It
73
+ * proves neither that `sandbox-policy.mode` actually confines file/exec
74
+ * access, nor what `approval.policy: "never"` actually permits, because
75
+ * no probe can exercise a tool call without a real model response.
76
+ * - The stock `permission` (`@deepseek-ai/dsh-permission-presets`) plugin
77
+ * fails to mount for the `workspace-write` + `never` combination
78
+ * (`dsh: warning: 1 entry did not activate` / `permission: composed
79
+ * sandbox and approval defaults match no preset; configure
80
+ * defaultPreset explicitly`, confirmed live on the no-model probe).
81
+ * That means the stock preset-switch plugin is NOT mounted for this
82
+ * combination — it says nothing about whether `sandbox-policy`/
83
+ * `approval` themselves still enforce anything; it is evidence of an
84
+ * unmatched preset table entry, not evidence of functioning
85
+ * confinement.
86
+ * - `danger-full-access` (the only shipped preset pairing `approval:
87
+ * never`) is a full bypass and is never emitted by this profile. No
88
+ * analogy to another CLI's `-s workspace-write -c
89
+ * approval_policy="never"` semantics is drawn here: dsh's own
90
+ * behavior for this combination is simply unverified.
91
+ *
92
+ * Because of this, `model`/`effort`/`sandbox`/`approval` stay `unqualified`
93
+ * in the registry below, matching grok-build/cursor/opencode's convention
94
+ * for controls evidenced only by help, source or no-model probes.
95
+ *
96
+ * Execution-time patch delivery uses buildWorkerArtifacts, a pure declaration.
97
+ * Resolution emits an artifact placeholder; only the launcher writes the patch
98
+ * into its owned private run directory with exclusive no-follow creation and
99
+ * cleanup. No preview edits account defaults or writes a patch file.
100
+ *
101
+ * `DEEPSEEK_API_KEY` is the credential env var the no-model probe's
102
+ * `MISSING_CREDENTIAL` error names (`llm-deepseek: no API key for
103
+ * provider route "deepseek-official"; ... export DEEPSEEK_API_KEY`); it is
104
+ * the one env allowlist entry (name only, value never rendered).
105
+ *
106
+ * KNOWN GAP (isolation): `getEnvOverrides()` is called with no invocation
107
+ * context (`session-launcher.mjs` ~847: `providerAdapter.getEnvOverrides()`),
108
+ * so a per-run `DSH_HOME` cannot be synthesized here without either sharing
109
+ * one `DSH_HOME` across concurrent runs (an isolation/crossover risk per
110
+ * T-OG-17) or editing shared launcher code. This profile therefore leaves
111
+ * `DSH_HOME` unset (the operator's real home); the needed shared change is
112
+ * passing invocation context (e.g. `runDir`) into `getEnvOverrides()`.
113
+ *
114
+ * `--json` event grammar (R-grade; read from the installed
115
+ * `@deepseek-ai/dsh-headless` package's own `json-stream-*.js` and
116
+ * `index.js`, 2026-10-11; confirmed live via the no-model probe's exact
117
+ * `session`/`status`/`final` lines): exactly one `{"type":"session",...}`
118
+ * first; `{"type":"status","phase":"turn_start"|"step_start"|"step_end"|
119
+ * "turn_end","turn":N,...}` (a `turn_end` carries `"reason":{"kind":...}`;
120
+ * any other `phase` is rejected as unknown); `{"type":"text"|"thinking",
121
+ * "text":...}` for assistant content; `{"type":"tool_call",...}`/
122
+ * `{"type":"tool_result","status":"completed"|"error",...}`; exactly one
123
+ * terminal `{"type":"final","text":...}` last, and nothing after it.
124
+ * `index.js`'s own `run()` sets the process exit code with
125
+ * `io.exit(outcome.reason?.kind === "completed" ? 0 : 1)` — only
126
+ * `"completed"` is success; `final.text` is the last non-empty assistant
127
+ * text seen since this task's `turn/start`, independent of success (an
128
+ * error path can still carry partial text), so this profile never treats
129
+ * exit code or non-empty `final.text` alone as success. A `turn_start`
130
+ * RESETS the completion flag (a later, unfinished turn after an earlier
131
+ * completed one must not inherit that earlier success), so only the LAST
132
+ * turn's `turn_end.reason.kind === "completed"` counts, with no top-level
133
+ * `{"type":"error",...}` line and no `tool_result` with `status:"error"`
134
+ * anywhere.
135
+ */
136
+ export const DEEPSEEK_HARNESS_SUPPORTED_VERSIONS = Object.freeze(['0.2.0-rc.2']);
137
+ export const DEEPSEEK_HARNESS_DIRECT_EFFORT_VALUES = Object.freeze(['off', 'low', 'high', 'max']);
138
+ export const DEEPSEEK_HARNESS_DIRECT_SANDBOX_MODES = Object.freeze(['read-only', 'workspace-write']);
139
+ /** Non-prompting execution only: `ask` (the only alternative the CLI's own
140
+ * `APPROVAL_POLICIES` enum offers) can block on a prompt headless has no
141
+ * channel to answer. */
142
+ export const DEEPSEEK_HARNESS_DIRECT_APPROVAL_POLICIES = Object.freeze(['never']);
143
+ /** Brief transport for the direct profile: piped stdin (`-`), never argv. */
144
+ export const DEEPSEEK_HARNESS_DIRECT_STDIN = 'brief-text';
145
+ const AUTO_SELECTION_VALUES = Object.freeze(['auto', 'default']);
146
+ /** Conservative identifier charset for YAML-embedded values: letters,
147
+ * digits, `.`, `_`, `-`, `/`, `:`, first character alphanumeric. Rejects
148
+ * YAML-significant and quoting characters (quotes, anchors/tags, newlines,
149
+ * whitespace) outright, in addition to the `JSON.stringify` quoting
150
+ * applied when the value is written into the patch. */
151
+ const DEEPSEEK_HARNESS_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._\-/:]*$/;
152
+
153
+ function requireOptionSafeString(value, label) {
154
+ if (typeof value !== 'string' || !value || value.includes('\0')) throw new Error(`DeepSeek Harness worker requires ${label}`);
155
+ if (value.startsWith('-') || /[\s\u0000-\u001f]/.test(value)) {
156
+ throw new Error(`DeepSeek Harness worker ${label} cannot look like an option: ${JSON.stringify(value)}`);
157
+ }
158
+ return value;
159
+ }
160
+
161
+ function normalizeDeepSeekHarnessModel(model) {
162
+ const value = requireOptionSafeString(model, 'an explicit model');
163
+ if (AUTO_SELECTION_VALUES.includes(value.toLowerCase())) {
164
+ throw new Error(`DeepSeek Harness worker requires an exact model, not an auto-selected value: ${JSON.stringify(value)}`);
165
+ }
166
+ if (!DEEPSEEK_HARNESS_IDENTIFIER.test(value)) {
167
+ throw new Error(`DeepSeek Harness worker model must use a conservative identifier charset: ${JSON.stringify(value)}`);
168
+ }
169
+ return value;
170
+ }
171
+
172
+ function normalizeDeepSeekHarnessEffort(effort) {
173
+ if (typeof effort !== 'string' || !DEEPSEEK_HARNESS_DIRECT_EFFORT_VALUES.includes(effort)) {
174
+ throw new Error('DeepSeek Harness worker requires an explicit qualified reasoning effort (never a CLI default)');
175
+ }
176
+ return effort;
177
+ }
178
+
179
+ function normalizeDeepSeekHarnessSandbox(sandbox) {
180
+ if (!DEEPSEEK_HARNESS_DIRECT_SANDBOX_MODES.includes(sandbox)) {
181
+ throw new Error(`DeepSeek Harness worker sandbox is not a qualified direct mode: ${String(sandbox)}`);
182
+ }
183
+ return sandbox;
184
+ }
185
+
186
+ function normalizeDeepSeekHarnessApproval(approval) {
187
+ if (!DEEPSEEK_HARNESS_DIRECT_APPROVAL_POLICIES.includes(approval)) {
188
+ throw new Error(`DeepSeek Harness worker approval is not a qualified direct policy: ${String(approval)}`);
189
+ }
190
+ return approval;
191
+ }
192
+
193
+ /** Pure: the `--patch` overlay YAML text for one invocation's model,
194
+ * effort, sandbox and approval. Performs no I/O — see the module-level
195
+ * execution-time artifact contract. Exported so tests and the docs' manual smoke
196
+ * command can write this text themselves. Every value is validated against
197
+ * a conservative charset/enum AND `JSON.stringify`-quoted in the emitted
198
+ * YAML (defense in depth): neither alone is trusted. The patch never
199
+ * carries the brief or a credential.
200
+ * @param {{ model: unknown, effort: unknown, sandbox: unknown, approval: unknown }} selection
201
+ * @returns {string}
202
+ */
203
+ export function buildDeepSeekHarnessPatch({ model, effort, sandbox, approval }) {
204
+ const safeModel = normalizeDeepSeekHarnessModel(model);
205
+ const safeEffort = normalizeDeepSeekHarnessEffort(effort);
206
+ const safeSandbox = normalizeDeepSeekHarnessSandbox(sandbox);
207
+ const safeApproval = normalizeDeepSeekHarnessApproval(approval);
208
+ const provider = 'deepseek-official';
209
+ return [
210
+ '- id: agent-default-model',
211
+ ' config:',
212
+ ` provider: ${JSON.stringify(provider)}`,
213
+ ` model: ${JSON.stringify(safeModel)}`,
214
+ ` reasoningEffort: ${JSON.stringify(safeEffort)}`,
215
+ '- id: sandbox-policy',
216
+ ' config:',
217
+ ` mode: ${JSON.stringify(safeSandbox)}`,
218
+ '- id: approval',
219
+ ' config:',
220
+ ` policy: ${JSON.stringify(safeApproval)}`,
221
+ '',
222
+ ].join('\n');
223
+ }
224
+
225
+ /** Pure placeholder: the launcher substitutes its private, per-run path. */
226
+ export function deepSeekHarnessPatchPath(_invocation) {
227
+ return '${artifact:dsh-patch}';
228
+ }
229
+
230
+ /** Strict scan of one `--json` stdout capture against the grammar documented
231
+ * above. Throws on any malformed line, a `session` event that is not
232
+ * first/is duplicated, an unknown `status` phase, an unknown event type, a
233
+ * top-level `error` event, a `tool_result` with `status:"error"`, a
234
+ * non-object/non-string `final.text`, or `final` not being the last line.
235
+ * A `turn_start` resets the completion flag, so an unfinished later turn
236
+ * after an earlier completed one is rejected. Returns `{ text, completed }`
237
+ * where `completed` reflects only the LAST observed `turn_end`.
238
+ */
239
+ export function parseDeepSeekHarnessTerminal(output, maxOutputBytes = 10 * 1024 * 1024) {
240
+ if (typeof output !== 'string' && !(output instanceof Uint8Array)) throw new Error('DeepSeek Harness output must be bytes or text');
241
+ const bytes = typeof output === 'string' ? Buffer.from(output) : output;
242
+ if (bytes.byteLength > maxOutputBytes) throw new Error('DeepSeek Harness stream exceeds byte bound');
243
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
244
+ const KNOWN_PHASES = ['turn_start', 'step_start', 'step_end', 'turn_end'];
245
+ let sawSession = false, completed = false, finalText = null, finalSeen = false;
246
+ const lines = text.split('\n').filter(line => line.trim());
247
+ for (let i = 0; i < lines.length; i += 1) {
248
+ if (finalSeen) throw new Error('DeepSeek Harness event after final terminal');
249
+ let event;
250
+ try { event = JSON.parse(lines[i]); } catch { throw new Error('Malformed DeepSeek Harness event'); }
251
+ if (!event || typeof event !== 'object' || Array.isArray(event) || typeof event.type !== 'string') {
252
+ throw new Error('Malformed DeepSeek Harness event');
253
+ }
254
+ if (event.type === 'error') throw new Error(`DeepSeek Harness error event: ${String(event.message)}`);
255
+ if (event.type === 'session') {
256
+ if (i !== 0 || sawSession) throw new Error('DeepSeek Harness session event must be exactly one, first');
257
+ sawSession = true;
258
+ continue;
259
+ }
260
+ if (!sawSession) throw new Error('DeepSeek Harness session event must be exactly one, first');
261
+ if (event.type === 'status') {
262
+ if (!KNOWN_PHASES.includes(event.phase)) throw new Error(`Unknown DeepSeek Harness status phase: ${String(event.phase)}`);
263
+ if (event.phase === 'turn_start') completed = false;
264
+ if (event.phase === 'turn_end') completed = event.reason?.kind === 'completed';
265
+ continue;
266
+ }
267
+ if (event.type === 'tool_result') {
268
+ if (event.status === 'error') throw new Error('DeepSeek Harness tool_result error');
269
+ continue;
270
+ }
271
+ if (['text', 'thinking', 'tool_call'].includes(event.type)) continue;
272
+ if (event.type === 'final') {
273
+ if (typeof event.text !== 'string') throw new Error('Malformed DeepSeek Harness final');
274
+ finalText = event.text;
275
+ finalSeen = true;
276
+ continue;
277
+ }
278
+ throw new Error(`Unknown DeepSeek Harness event type: ${event.type}`);
279
+ }
280
+ if (!sawSession) throw new Error('Missing DeepSeek Harness session event');
281
+ if (!finalSeen) throw new Error('Missing DeepSeek Harness final event');
282
+ if (!completed) throw new Error('DeepSeek Harness run did not complete');
283
+ if (typeof finalText !== 'string' || !finalText.trim()) throw new Error('Missing DeepSeek Harness final text');
284
+ return { text: finalText, completed };
285
+ }
286
+
287
+ /** Direct worker profile: strict terminal validation layered over the
288
+ * legacy `DeepSeekHarnessAdapter`'s lenient `parseOutput`/`buildSessionArgs`,
289
+ * which stay untouched for the legacy Ralph loop (mirrors
290
+ * `claude-adapter.mjs`'s `ClaudeDirectAdapter` precedent).
291
+ */
292
+ export class DeepSeekHarnessDirectAdapter extends DeepSeekHarnessAdapter {
293
+ // #2909 fix round 1 (item 2, mirrors #2995's FactoryDirectAdapter): production
294
+ // resolves the installed adapter by `adapter.id` (`deepseek-harness-direct`)
295
+ // and `src/models/worker-installation.mjs`'s `inspectWorkerInstallation`
296
+ // requires `adapter.id === instance.getName()`. `getBinary()` (the real
297
+ // `dsh` executable) and the `provider: 'deepseek-harness'` literals inside
298
+ // `observeHealthEvent` are untouched -- only this identity label changes.
299
+ getName() { return 'deepseek-harness-direct'; }
300
+
301
+ observeHealthEvent(event, source, binding) {
302
+ if (source.kind !== 'stdout' || !event || typeof event !== 'object') return null;
303
+ if (!healthEventMatchesBinding({ sessionId: event.sessionId }, binding)) return null;
304
+ if (event.type === 'session' && typeof event.sessionId === 'string') return { kind: 'setup', sessionId: event.sessionId };
305
+ if (event.type === 'status') {
306
+ if (event.phase === 'turn_start') return { kind: 'accepted', key: event.turn };
307
+ if (event.phase === 'step_start' || event.phase === 'step_end') return { kind: 'progress', key: `${event.turn}:${event.step}` };
308
+ if (event.phase === 'turn_end') {
309
+ if (event.reason?.kind === 'completed') return { kind: 'terminal', success: true, key: `turn_end:${event.turn}` };
310
+ return { ...classifyWorkerHealthFailure({ type: 'error', message: event.reason?.error?.message ?? event.reason?.kind }, { source, provider: 'deepseek-harness' }), fatal: true };
311
+ }
312
+ return null;
313
+ }
314
+ if (event.type === 'text' || event.type === 'thinking') return { kind: 'activity' };
315
+ if (event.type === 'tool_call') return { kind: 'progress', key: event.callId };
316
+ if (event.type === 'tool_result') {
317
+ if (event.status === 'error') return { kind: 'error', reasonCode: 'tool-failed' };
318
+ return { kind: 'progress', key: event.callId };
319
+ }
320
+ if (event.type === 'error') {
321
+ return { ...classifyWorkerHealthFailure({ type: 'error', message: event.message }, { source, provider: 'deepseek-harness' }), fatal: true };
322
+ }
323
+ return null;
324
+ }
325
+
326
+ /** Driver-owned argv. PURE: performs no I/O (#2909 fix round 1, item 1 —
327
+ * `resolveWorkerInvocation` calls this during resolution/preview as well
328
+ * as dispatch, and installed preparation/revalidation calls it again, so
329
+ * a write here would fire on every preview and let concurrent finals
330
+ * sharing a directory overwrite each other's controls). No brief text on
331
+ * argv (stdin only, via `-`); no invented `--model`/`--effort`/`--sandbox`
332
+ * flags (none exist). Model/effort/sandbox/approval are validated here
333
+ * (via {@link buildDeepSeekHarnessPatch}, result discarded) so argv is
334
+ * never rendered for a control this profile cannot actually enforce, and
335
+ * the launcher writes the declared patch at execution time. */
336
+ buildWorkerArgs(invocation, _profile, permissions = {}) {
337
+ const model = invocation?.selected?.model;
338
+ const effort = invocation?.selected?.effort?.value;
339
+ const sandbox = permissions.sandbox ?? 'workspace-write';
340
+ const approval = permissions.approval ?? 'never';
341
+ buildDeepSeekHarnessPatch({ model, effort, sandbox, approval });
342
+ const cwd = invocation?.cwd;
343
+ if (typeof cwd !== 'string' || !cwd.startsWith('/') || cwd.includes('\0')) throw new Error('DeepSeek Harness worker requires an absolute workspace');
344
+ const brief = invocation?.brief;
345
+ // Resolution supplies brief text; installed rechecks carry only its path.
346
+ // Neither form contributes prompt bytes to argv.
347
+ if (brief !== undefined ? typeof brief !== 'string' || !brief || brief.includes('\0')
348
+ : typeof invocation?.briefPath !== 'string' || !invocation.briefPath.startsWith('/') || invocation.briefPath.includes('\0')) {
349
+ throw new Error('DeepSeek Harness worker requires a non-empty brief or an absolute brief path');
350
+ }
351
+ const patchPath = deepSeekHarnessPatchPath(invocation);
352
+ return ['--profile', 'headless', '--patch', patchPath, '--json', '-'];
353
+ }
354
+
355
+ /** Pure execution-time artifact declaration; workspace-relative locations
356
+ * are intentionally unsupported by the shared launcher. */
357
+ buildWorkerArtifacts(invocation) {
358
+ const permissions = invocation.permissions ?? {};
359
+ return [{ name: 'dsh-patch', location: 'run-directory', content: buildDeepSeekHarnessPatch({
360
+ model: invocation.selected.model, effort: invocation.selected.effort.value,
361
+ sandbox: permissions.sandbox ?? 'workspace-write', approval: permissions.approval ?? 'never',
362
+ }) }];
363
+ }
364
+
365
+ /** No evidenced stdin abort channel; stdin carries the brief to EOF. */
366
+ getAbortInput() { return null; }
367
+
368
+ /** Candidate extractor consumed by the installed driver's extraction
369
+ * hook: `null` for anything not an affirmative, completed success. */
370
+ parseOutput(stdout) {
371
+ try { return { text: parseDeepSeekHarnessTerminal(stdout).text }; } catch { return null; }
372
+ }
373
+
374
+ /** Authoritative final-claim gate (session-launcher.mjs's `finalValid`/
375
+ * `extractFinal`). Re-validates strictly, then hands only the model's own
376
+ * final text to the shared strict `workerClaims` parser, mirroring
377
+ * `ClaudeDirectAdapter`/`OpenClawAdapter`. */
378
+ async parseWorkerFinal(output, options = {}) {
379
+ if (options.format === 'mcp') return super.parseWorkerFinal(output, options);
380
+ const text = typeof output === 'string' ? output : Buffer.from(output).toString('utf8');
381
+ const terminal = parseDeepSeekHarnessTerminal(text);
382
+ return super.parseWorkerFinal(terminal.text, { ...options, format: 'json' });
383
+ }
384
+ }
385
+
386
+ /** Installed-module interface version (mirrors codex-adapter.mjs/
387
+ * openclaw-adapter.mjs). */
388
+ export const interfaceVersion = 1;
389
+
390
+ /** Installed-module registration: the direct class loads under its own id,
391
+ * never the legacy `deepseek-harness`/`dsh` ids (loading it must not
392
+ * overwrite the legacy Ralph-loop registration in-process). */
393
+ export function register(factory) {
394
+ factory('deepseek-harness-direct', () => new DeepSeekHarnessDirectAdapter());
395
+ }
396
+
397
+ export default DeepSeekHarnessAdapter;