@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
@@ -146,7 +146,7 @@ export class LogseqAdapter {
146
146
  }
147
147
  // Defensive: refuse to write `id::` block IDs ourselves. Logseq
148
148
  // auto-assigns them; if the caller smuggled one in, strip it.
149
- body = body.replace(/^\s*id::\s*[^\n]+\n/gm, '');
149
+ body = stripBlockIds(body);
150
150
  await writeFile(abs, body, 'utf-8');
151
151
  }
152
152
  async list(prefix) {
@@ -221,6 +221,34 @@ function expandHome(p) {
221
221
  }
222
222
  return p;
223
223
  }
224
+ function stripBlockIds(content) {
225
+ const chunks = [];
226
+ let start = 0;
227
+ while (start < content.length) {
228
+ let property = start;
229
+ while (property < content.length && /\s/u.test(content[property]))
230
+ property++;
231
+ if (content.startsWith('id::', property)) {
232
+ // Only the property's own line belongs to the block ID.
233
+ let end = property + 4;
234
+ while (end < content.length && !/[\n\r\u2028\u2029]/u.test(content[end]))
235
+ end++;
236
+ if (content[end] === '\r' && content[end + 1] === '\n')
237
+ end++;
238
+ start = end < content.length ? end + 1 : content.length;
239
+ continue;
240
+ }
241
+ // Keep the original line and any preceding blank lines verbatim.
242
+ let end = property;
243
+ while (end < content.length && !/[\n\r\u2028\u2029]/u.test(content[end]))
244
+ end++;
245
+ if (end < content.length)
246
+ end++;
247
+ chunks.push(content.slice(start, end));
248
+ start = end;
249
+ }
250
+ return chunks.join('');
251
+ }
224
252
  /**
225
253
  * If `content` starts with a `---` YAML block, parse the block and
226
254
  * return the parsed fields plus the body without the YAML. Otherwise
@@ -230,18 +258,51 @@ function expandHome(p) {
230
258
  * Anything more complex falls back to dropping the value as a string.
231
259
  */
232
260
  function extractYamlFrontmatter(content) {
233
- const match = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/.exec(content);
234
- if (!match)
261
+ if (!content.startsWith('---'))
262
+ return null;
263
+ let openingEnd = 3;
264
+ let firstNewline = -1;
265
+ let lastNewline = -1;
266
+ while (openingEnd < content.length && /\s/u.test(content[openingEnd])) {
267
+ if (content[openingEnd] === '\n') {
268
+ if (firstNewline < 0)
269
+ firstNewline = openingEnd;
270
+ lastNewline = openingEnd;
271
+ }
272
+ openingEnd++;
273
+ }
274
+ if (firstNewline < 0)
235
275
  return null;
236
- const [, yamlBody, body] = match;
276
+ let yamlStart = lastNewline + 1;
277
+ let closing = content.indexOf('\n---', yamlStart);
278
+ if (closing < 0) {
279
+ closing = content.indexOf('\n---', firstNewline + 1);
280
+ if (closing < 0)
281
+ return null;
282
+ yamlStart = firstNewline + 1;
283
+ for (let index = yamlStart; index < closing; index++) {
284
+ if (content[index] === '\n')
285
+ yamlStart = index + 1;
286
+ }
287
+ }
288
+ const yamlBody = content.slice(yamlStart, content[closing - 1] === '\r' ? closing - 1 : closing);
289
+ let bodyStart = closing + 4;
290
+ while (bodyStart < content.length && /\s/u.test(content[bodyStart]))
291
+ bodyStart++;
292
+ const body = content.slice(bodyStart);
237
293
  const fields = {};
238
294
  for (const line of yamlBody.split(/\r?\n/)) {
239
295
  if (line.trim().length === 0 || line.trim().startsWith('#'))
240
296
  continue;
241
- const m = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
242
- if (!m)
297
+ const separator = line.indexOf(':');
298
+ if (separator < 0)
299
+ continue;
300
+ const k = line.slice(0, separator).trimEnd();
301
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(k))
302
+ continue;
303
+ const rawV = line.slice(separator + 1).trimStart();
304
+ if (/[\r\u2028\u2029]/u.test(rawV))
243
305
  continue;
244
- const [, k, rawV] = m;
245
306
  fields[k] = parseYamlScalar(rawV.trim());
246
307
  }
247
308
  return { fields, body };
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { link, mkdir, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
- import { execFileSync } from "node:child_process";
4
+ import { boundedExecFileSync } from "../process/bounded-child-process.mjs";
5
5
  export const FORTEMI_QUALIFICATION_RECEIPT = "aiwg.fortemi-live-qualification-receipt/v1";
6
6
  const DIGEST = /^sha256:[0-9a-f]{64}$/;
7
7
  const COMMIT = /^[0-9a-f]{40}$/;
@@ -23,7 +23,7 @@ function hasCompleteOperationInventory(operations) {
23
23
  names.every((name) => REQUIRED_OPERATIONS.has(name)));
24
24
  }
25
25
  export function resolveFortemiQualificationSource(env = process.env, cwd = process.cwd()) {
26
- const git = (...args) => execFileSync("git", args, {
26
+ const git = (...args) => boundedExecFileSync("git", args, 'quick', {
27
27
  cwd,
28
28
  encoding: "utf8",
29
29
  stdio: ["ignore", "pipe", "ignore"],
@@ -31,6 +31,7 @@ export class TestCodeGenerator {
31
31
  const warnings = [];
32
32
  const files = [];
33
33
  try {
34
+ this.validateInputs(suite);
34
35
  // Group test cases by level
35
36
  const unitTests = suite.testCases.filter(tc => tc.level === 'unit');
36
37
  const integrationTests = suite.testCases.filter(tc => tc.level === 'integration');
@@ -80,6 +81,33 @@ export class TestCodeGenerator {
80
81
  };
81
82
  }
82
83
  }
84
+ /** Validate declared string inputs before rendering any files. */
85
+ validateInputs(suite) {
86
+ const requireString = (value, field) => {
87
+ if (typeof value !== 'string')
88
+ throw new Error(`${field} must be a string`);
89
+ };
90
+ requireString(suite.name, 'suite.name');
91
+ for (const [index, testCase] of suite.testCases.entries()) {
92
+ const field = `testCases[${index}]`;
93
+ requireString(testCase.name, `${field}.name`);
94
+ if (!Array.isArray(testCase.testData))
95
+ throw new Error(`${field}.testData must be an array`);
96
+ for (const [dataIndex, data] of testCase.testData.entries()) {
97
+ const dataField = `${field}.testData[${dataIndex}]`;
98
+ requireString(data.name, `${dataField}.name`);
99
+ if (!Array.isArray(data.examples))
100
+ throw new Error(`${dataField}.examples must be an array`);
101
+ for (const [exampleIndex, example] of data.examples.entries()) {
102
+ requireString(example, `${dataField}.examples[${exampleIndex}]`);
103
+ }
104
+ }
105
+ for (const [stepIndex, step] of testCase.steps.entries()) {
106
+ if (step.testData !== undefined)
107
+ requireString(step.testData, `${field}.steps[${stepIndex}].testData`);
108
+ }
109
+ }
110
+ }
83
111
  /**
84
112
  * Generate a test file for unit or integration tests
85
113
  */
@@ -95,7 +123,7 @@ export class TestCodeGenerator {
95
123
  lines.push(this.generateImports(level));
96
124
  lines.push('');
97
125
  // Test suite
98
- lines.push(`describe('${suite.name} - ${level.charAt(0).toUpperCase() + level.slice(1)} Tests', () => {`);
126
+ lines.push(`describe(${JSON.stringify(`${suite.name} - ${level.charAt(0).toUpperCase() + level.slice(1)} Tests`)}, () => {`);
99
127
  // Setup/teardown
100
128
  if (this.options.includeSetup) {
101
129
  lines.push(this.generateSetup(indent, suite));
@@ -137,7 +165,7 @@ export class TestCodeGenerator {
137
165
  }
138
166
  lines.push('');
139
167
  // Test suite
140
- lines.push(`test.describe('${suite.name} - E2E Tests', () => {`);
168
+ lines.push(`test.describe(${JSON.stringify(`${suite.name} - E2E Tests`)}, () => {`);
141
169
  // Setup
142
170
  if (this.options.includeSetup) {
143
171
  lines.push(`${indent}test.beforeEach(async ({ page }) => {`);
@@ -167,12 +195,12 @@ export class TestCodeGenerator {
167
195
  if (!this.options.includeComments)
168
196
  return '';
169
197
  return `/**
170
- * ${_level.toUpperCase()} Tests: ${suite.name}
198
+ * ${_level.toUpperCase()} Tests: ${this.escapeComment(suite.name)}
171
199
  *
172
- * Auto-generated from ${suite.useCaseId}
173
- * Generated at: ${suite.generatedAt}
200
+ * Auto-generated from ${this.escapeComment(suite.useCaseId)}
201
+ * Generated at: ${this.escapeComment(suite.generatedAt)}
174
202
  *
175
- * @module test/${_level}/${suite.useCaseId.toLowerCase()}
203
+ * @module test/${_level}/${this.escapeComment(suite.useCaseId.toLowerCase())}
176
204
  */`;
177
205
  }
178
206
  /**
@@ -246,19 +274,19 @@ export class TestCodeGenerator {
246
274
  // Test description comment
247
275
  if (this.options.includeComments) {
248
276
  lines.push(`${indent}/**`);
249
- lines.push(`${indent} * ${testCase.id}: ${testCase.name}`);
250
- lines.push(`${indent} * ${testCase.description}`);
251
- lines.push(`${indent} * Type: ${testCase.type}`);
252
- lines.push(`${indent} * Priority: ${testCase.priority}`);
277
+ lines.push(`${indent} * ${this.escapeComment(testCase.id)}: ${this.escapeComment(testCase.name)}`);
278
+ lines.push(`${indent} * ${this.escapeComment(testCase.description)}`);
279
+ lines.push(`${indent} * Type: ${this.escapeComment(testCase.type)}`);
280
+ lines.push(`${indent} * Priority: ${this.escapeComment(testCase.priority)}`);
253
281
  lines.push(`${indent} */`);
254
282
  }
255
283
  // Test function
256
- lines.push(`${indent}it('${this.escapeString(testCase.name)}', async () => {`);
284
+ lines.push(`${indent}it(${JSON.stringify(testCase.name)}, async () => {`);
257
285
  // Preconditions
258
286
  if (testCase.preconditions.length > 0) {
259
287
  lines.push(`${indent2}// Preconditions:`);
260
288
  for (const pre of testCase.preconditions) {
261
- lines.push(`${indent2}// - ${pre}`);
289
+ lines.push(`${indent2}// - ${this.escapeComment(pre)}`);
262
290
  }
263
291
  lines.push('');
264
292
  }
@@ -267,7 +295,7 @@ export class TestCodeGenerator {
267
295
  if (testCase.testData.length > 0) {
268
296
  lines.push(`${indent2}const testData = {`);
269
297
  for (const data of testCase.testData) {
270
- lines.push(`${indent3}${data.name}: ${this.getExampleValue(data.examples)}, // ${data.type}: ${data.constraints.join(', ')}`);
298
+ lines.push(`${indent3}${JSON.stringify(data.name)}: ${this.getExampleValue(data.examples)}, // ${this.escapeComment(data.type)}: ${this.escapeComment(data.constraints.join(', '))}`);
271
299
  }
272
300
  lines.push(`${indent2}};`);
273
301
  }
@@ -282,7 +310,7 @@ export class TestCodeGenerator {
282
310
  step.action.toLowerCase().includes('check')) {
283
311
  continue; // Skip verification steps for Act section
284
312
  }
285
- lines.push(`${indent2}// Step ${step.number}: ${step.action}`);
313
+ lines.push(`${indent2}// Step ${this.escapeComment(step.number)}: ${this.escapeComment(step.action)}`);
286
314
  lines.push(`${indent2}// TODO: Implement action`);
287
315
  }
288
316
  lines.push('');
@@ -293,7 +321,7 @@ export class TestCodeGenerator {
293
321
  !step.action.toLowerCase().includes('check')) {
294
322
  continue; // Skip action steps for Assert section
295
323
  }
296
- lines.push(`${indent2}// ${step.expectedResult}`);
324
+ lines.push(`${indent2}// ${this.escapeComment(step.expectedResult)}`);
297
325
  lines.push(`${indent2}// expect(result).toEqual(expected);`);
298
326
  }
299
327
  // Default assertion if no verify steps
@@ -308,7 +336,7 @@ export class TestCodeGenerator {
308
336
  lines.push('');
309
337
  lines.push(`${indent2}// Postconditions:`);
310
338
  for (const post of testCase.postconditions) {
311
- lines.push(`${indent2}// - ${post}`);
339
+ lines.push(`${indent2}// - ${this.escapeComment(post)}`);
312
340
  }
313
341
  }
314
342
  lines.push(`${indent}});`);
@@ -323,15 +351,15 @@ export class TestCodeGenerator {
323
351
  // Test description comment
324
352
  if (this.options.includeComments) {
325
353
  lines.push(`${indent}/**`);
326
- lines.push(`${indent} * ${testCase.id}: ${testCase.name}`);
327
- lines.push(`${indent} * ${testCase.description}`);
354
+ lines.push(`${indent} * ${this.escapeComment(testCase.id)}: ${this.escapeComment(testCase.name)}`);
355
+ lines.push(`${indent} * ${this.escapeComment(testCase.description)}`);
328
356
  lines.push(`${indent} */`);
329
357
  }
330
358
  // Test function
331
- lines.push(`${indent}test('${this.escapeString(testCase.name)}', async ({ page }) => {`);
359
+ lines.push(`${indent}test(${JSON.stringify(testCase.name)}, async ({ page }) => {`);
332
360
  // Steps
333
361
  for (const step of testCase.steps) {
334
- lines.push(`${indent2}// Step ${step.number}: ${step.action}`);
362
+ lines.push(`${indent2}// Step ${this.escapeComment(step.number)}: ${this.escapeComment(step.action)}`);
335
363
  lines.push(this.generateE2EStepCode(step, indent2));
336
364
  lines.push('');
337
365
  }
@@ -350,7 +378,7 @@ export class TestCodeGenerator {
350
378
  return `${indent}await page.click('selector'); // TODO: Update selector`;
351
379
  }
352
380
  if (action.includes('enter') || action.includes('type') || action.includes('input')) {
353
- return `${indent}await page.fill('selector', '${step.testData || 'test-data'}'); // TODO: Update`;
381
+ return `${indent}await page.fill('selector', ${JSON.stringify(step.testData || 'test-data')}); // TODO: Update`;
354
382
  }
355
383
  if (action.includes('navigate') || action.includes('go to')) {
356
384
  return `${indent}await page.goto('/path'); // TODO: Update path`;
@@ -359,15 +387,14 @@ export class TestCodeGenerator {
359
387
  return `${indent}await page.waitForSelector('selector'); // TODO: Update selector`;
360
388
  }
361
389
  if (action.includes('verify') || action.includes('check') || action.includes('see')) {
362
- return `${indent}await expect(page.locator('selector')).toBeVisible(); // ${step.expectedResult}`;
390
+ return `${indent}await expect(page.locator('selector')).toBeVisible(); // ${this.escapeComment(step.expectedResult)}`;
363
391
  }
364
- return `${indent}// TODO: Implement step - ${step.action}`;
392
+ return `${indent}// TODO: Implement step - ${this.escapeComment(step.action)}`;
365
393
  }
366
- /**
367
- * Escape string for use in test names
368
- */
369
- escapeString(str) {
370
- return str.replace(/'/g, "\\'").replace(/"/g, '\\"');
394
+ /** Keep metadata within a single line or block comment. */
395
+ escapeComment(value) {
396
+ return String(value).replace(/\*\//g, '* /')
397
+ .replace(/[\r\n\u2028\u2029]/g, (char) => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
371
398
  }
372
399
  /**
373
400
  * Get example value from test data
@@ -376,9 +403,9 @@ export class TestCodeGenerator {
376
403
  if (examples.length === 0)
377
404
  return "'test-value'";
378
405
  const example = examples[0];
379
- if (example.match(/^\d+$/))
406
+ if (/^\d+$/.test(example) && String(Number(example)) === example)
380
407
  return example;
381
- return `'${example}'`;
408
+ return JSON.stringify(example);
382
409
  }
383
410
  }
384
411
  //# sourceMappingURL=test-code-generator.js.map
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { DEFAULT_UHP_LIMITS, isUhpLoopback, isUhpPrivateAddress, resolveUhpLimits, validateUhpEndpoint } from './config.js';
6
6
  import { parseUhpError, redactUhpText, UhpError } from './errors.js';
7
7
  import { parseUhpEventStream } from './sse.js';
8
+ import { assertMissionWorkerHandoff } from '../mission-protocol/codecs.js';
8
9
  import { UHP_VERSION, } from './types.js';
9
10
  const ID_PATTERNS = {
10
11
  harness: /^chrn_[A-Za-z0-9_-]+$/,
@@ -100,6 +101,7 @@ function ensureDiscovery(value) {
100
101
  return discovery;
101
102
  }
102
103
  function ensureResponse(value) {
104
+ assertMissionWorkerHandoff(value, 'uhp');
103
105
  if (!value || typeof value !== 'object')
104
106
  throw new UhpError('invalid_response', 'UHP response must be an object', { remoteState: 'unknown' });
105
107
  const response = value;
@@ -238,6 +240,7 @@ export class UhpClient {
238
240
  return this.json(harnessId ? `v1/harnesses/${encodeURIComponent(assertId('harness', harnessId))}/models` : 'v1/models');
239
241
  }
240
242
  async createResponse(request, options = {}) {
243
+ assertMissionWorkerHandoff(request, 'uhp');
241
244
  const normalized = { ...request, stream: false, metadata: { ...request.metadata, ...(request.metadata?.harness_id || !this.profile.defaultHarness ? {} : { harness_id: this.profile.defaultHarness }) }, ...(request.model || !this.profile.defaultModel ? {} : { model: this.profile.defaultModel }) };
242
245
  if ((normalized.timeout_seconds ?? this.limits.maxTaskSeconds) > this.limits.maxTaskSeconds)
243
246
  throw new UhpError('task_budget_exceeded', 'Requested UHP task duration exceeds the profile limit', { remoteState: 'not-started' });
@@ -267,6 +270,7 @@ export class UhpClient {
267
270
  }
268
271
  }
269
272
  async *streamResponse(request, options = {}) {
273
+ assertMissionWorkerHandoff(request, 'uhp');
270
274
  const normalized = { ...request, stream: true, metadata: { ...request.metadata, ...(request.metadata?.harness_id || !this.profile.defaultHarness ? {} : { harness_id: this.profile.defaultHarness }) } };
271
275
  const digest = canonicalUhpRequestDigest(normalized);
272
276
  const key = options.idempotencyKey ?? `aiwg-${digest}`;
@@ -285,7 +289,12 @@ export class UhpClient {
285
289
  throw parseUhpError(response.status, parseJson(await readBounded(response)), secret ? [secret] : []);
286
290
  if (!response.headers.get('content-type')?.toLowerCase().startsWith('text/event-stream') || !response.body)
287
291
  throw new UhpError('invalid_stream', 'UHP streaming response must be text/event-stream', { remoteState: 'unknown' });
288
- yield* parseUhpEventStream(response.body, { inactivityTimeoutMs: this.limits.inactivityTimeoutMs, secrets: secret ? [secret] : [], signal: options.signal });
292
+ for await (const event of parseUhpEventStream(response.body, { inactivityTimeoutMs: this.limits.inactivityTimeoutMs, secrets: secret ? [secret] : [], signal: options.signal })) {
293
+ assertMissionWorkerHandoff(event, 'uhp');
294
+ if (event.response)
295
+ assertMissionWorkerHandoff(event.response, 'uhp');
296
+ yield event;
297
+ }
289
298
  }
290
299
  async readResponse(responseId) {
291
300
  return ensureResponse(await this.json(`v1/responses/${encodeURIComponent(assertId('response', responseId))}`));
@@ -295,6 +304,7 @@ export class UhpClient {
295
304
  return this.readResponse(responseId);
296
305
  }
297
306
  async continueResponse(previousResponseId, request, options = {}) {
307
+ assertMissionWorkerHandoff(request, 'uhp');
298
308
  const previous = await this.readResponse(previousResponseId);
299
309
  const harness = previous.metadata.harness_id;
300
310
  if (request.metadata?.harness_id && harness && request.metadata.harness_id !== harness)
@@ -1,5 +1,6 @@
1
1
  import { UHP_VERSION } from './types.js';
2
2
  import { decodeMission } from '../mission-protocol/index.js';
3
+ import { assertMissionWorkerHandoff } from '../mission-protocol/codecs.js';
3
4
  function collectArtifacts(response) {
4
5
  const artifacts = new Map();
5
6
  const visit = (value) => {
@@ -48,6 +49,10 @@ function collectInputFiles(request) {
48
49
  return files;
49
50
  }
50
51
  export function projectUhpResponseToMission(profile, response, request = { input: '' }, event) {
52
+ assertMissionWorkerHandoff(response, 'uhp');
53
+ assertMissionWorkerHandoff(request, 'uhp');
54
+ if (event)
55
+ assertMissionWorkerHandoff(event, 'uhp');
51
56
  const known = ['in_progress', 'completed', 'failed', 'incomplete', 'cancelled'].includes(response.status);
52
57
  const state = response.status === 'in_progress' ? 'running' : known ? response.status : 'unknown';
53
58
  const metadata = response.metadata ?? {};
@@ -12,7 +12,7 @@
12
12
  import fs from 'fs/promises';
13
13
  import path from 'path';
14
14
  import https from 'https';
15
- import { execSync } from 'child_process';
15
+ import { boundedExecFileSync } from '../process/bounded-child-process.mjs';
16
16
  import { createInterface } from 'readline';
17
17
  import { loadConfig, saveConfig, getChannel, getPackageRoot } from '../channel/manager.mjs';
18
18
  import { updateInstallation } from './service.mjs';
@@ -68,15 +68,15 @@ async function fetchLatestNpmVersion() {
68
68
  async function checkGitUpdates(edgePath) {
69
69
  try {
70
70
  // Fetch latest without merging
71
- execSync('git fetch --quiet', { cwd: edgePath, timeout: 10000 });
71
+ boundedExecFileSync('git', ['fetch', '--quiet'], 'network', { cwd: edgePath, timeout: 10000 });
72
72
 
73
73
  // Check if local is behind remote
74
- const localHash = execSync('git rev-parse HEAD', {
74
+ const localHash = boundedExecFileSync('git', ['rev-parse', 'HEAD'], 'quick', {
75
75
  cwd: edgePath,
76
76
  encoding: 'utf8',
77
77
  }).trim();
78
78
 
79
- const remoteHash = execSync('git rev-parse origin/main', {
79
+ const remoteHash = boundedExecFileSync('git', ['rev-parse', 'origin/main'], 'quick', {
80
80
  cwd: edgePath,
81
81
  encoding: 'utf8',
82
82
  }).trim();
@@ -322,7 +322,7 @@ async function checkEdgeUpdates(config) {
322
322
  console.log('');
323
323
  console.log('Updating edge installation...');
324
324
  try {
325
- execSync('git pull --ff-only', { cwd: config.edgePath, stdio: 'inherit' });
325
+ boundedExecFileSync('git', ['pull', '--ff-only'], 'network', { cwd: config.edgePath, stdio: 'inherit' });
326
326
  console.log('Update complete!');
327
327
  } catch (error) {
328
328
  console.error('Update failed. Run manually:');
@@ -8,7 +8,11 @@
8
8
  * `bin/aiwg.mjs`. It spawns a **detached, unref()'d** child Node
9
9
  * process that runs this file with `--check` and writes the result
10
10
  * to `~/.aiwg/update-notifier.json`. The child exits immediately
11
- * after writing; the parent never waits on it.
11
+ * after writing; the parent never waits on it. The child's
12
+ * environment is an explicit allow-list (`notifierChildEnv()`), never
13
+ * the whole parent environment — see that function's doc comment.
14
+ * `bin/aiwg.mjs` also never calls this at all for a command that
15
+ * itself handles a credential (`isCredentialHandlingCommand()`).
12
16
  *
13
17
  * 2. On the *next* invocation, `maybePrintNotice()` reads the cache
14
18
  * and prints a single-line "update available" message to stderr.
@@ -217,6 +221,54 @@ async function runCheck(packageRoot) {
217
221
  });
218
222
  }
219
223
 
224
+ /**
225
+ * Environment variable names copied from the parent into the background
226
+ * check child (go/no-go review, blocker 1B) — never the whole environment,
227
+ * which is what let a secret-bearing variable set for an entirely
228
+ * unrelated command (or read by one of this CLI's own credential-handling
229
+ * commands) reach this child too, visible to `NODE_DEBUG=child_process`
230
+ * and to anything else that can observe a child's own environment while it
231
+ * runs. Chosen by reading what this file's own `--check` path (module load
232
+ * + `runCheck()`) actually consults:
233
+ *
234
+ * PATH general process hygiene
235
+ * HOME, USERPROFILE, HOMEDRIVE, HOMEPATH `os.homedir()` (POSIX / Windows),
236
+ * consulted by `resolveUserConfigDir()`
237
+ * AIWG_CONFIG `resolveUserConfigDir()`'s own override
238
+ * NODE_EXTRA_CA_CERTS Node's own TLS trust-store
239
+ * augmentation — needed for the
240
+ * registry HTTPS GET to succeed
241
+ * behind a custom-CA proxy
242
+ *
243
+ * `fetchLatestVersion()` is a plain `https.get()` with no proxy-aware
244
+ * agent, so `HTTP(S)_PROXY`/`NO_PROXY` have no effect on it today and are
245
+ * deliberately left out — add them here (and give `fetchLatestVersion()` a
246
+ * proxy-aware agent) in the same change if proxy support is ever added.
247
+ * `loadInstallationIdentity()` is imported but never called by `--check`
248
+ * (only by the parent, before deciding whether to spawn at all), so its own
249
+ * env reads do not apply here either.
250
+ */
251
+ const NOTIFIER_CHILD_ENV_ALLOWLIST = Object.freeze([
252
+ 'PATH', 'HOME', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH', 'AIWG_CONFIG', 'NODE_EXTRA_CA_CERTS',
253
+ ]);
254
+
255
+ /**
256
+ * Build the background check child's environment: only the allow-listed
257
+ * names, copied from `parentEnv` when present, plus the child-identity
258
+ * marker the child's own entry point does not otherwise need but existing
259
+ * tooling may key off of. Exported for the unit test that pins "only
260
+ * allow-listed names cross over, even when the parent env holds an extra
261
+ * secret-shaped variable" (go/no-go review, blocker 1 tests).
262
+ */
263
+ export function notifierChildEnv(parentEnv = process.env) {
264
+ const env = {};
265
+ for (const name of NOTIFIER_CHILD_ENV_ALLOWLIST) {
266
+ if (parentEnv[name] !== undefined) env[name] = parentEnv[name];
267
+ }
268
+ env.AIWG_UPDATE_NOTIFIER_CHILD = '1';
269
+ return env;
270
+ }
271
+
220
272
  /**
221
273
  * Schedule a background update check if one hasn't run in the last
222
274
  * CHECK_INTERVAL_MS. Non-blocking: spawns a detached child process and
@@ -248,7 +300,7 @@ export function scheduleBackgroundCheck(packageRoot) {
248
300
  {
249
301
  detached: true,
250
302
  stdio: 'ignore',
251
- env: { ...process.env, AIWG_UPDATE_NOTIFIER_CHILD: '1' },
303
+ env: notifierChildEnv(),
252
304
  },
253
305
  );
254
306
  // Unref so the parent can exit without waiting for the child. This is
@@ -2,6 +2,23 @@ import { parseWritingBrief, writingBriefHash, applyProofreadCorrections } from '
2
2
  function matches(text, pattern) {
3
3
  return [...text.matchAll(pattern)].map(m => ({ text: m[0], start: m.index, end: m.index + m[0].length }));
4
4
  }
5
+ function sentences(text) {
6
+ const result = [];
7
+ let start = 0;
8
+ for (let index = 0; index < text.length; index++) {
9
+ const character = text[index];
10
+ if (character === '\n' || character === '.' || character === '!' || character === '?') {
11
+ if (index > start) {
12
+ const end = character === '\n' ? index : index + 1;
13
+ result.push({ text: text.slice(start, end), start, end });
14
+ }
15
+ start = index + 1;
16
+ }
17
+ }
18
+ if (start < text.length)
19
+ result.push({ text: text.slice(start), start, end: text.length });
20
+ return result;
21
+ }
5
22
  /** Literal invariants fail closed; lexical signals locate changes requiring semantic review. */
6
23
  export function assessWritingFidelity(original, candidate, value) {
7
24
  const brief = value ? parseWritingBrief(value) : undefined;
@@ -40,7 +57,7 @@ export function assessWritingFidelity(original, candidate, value) {
40
57
  }
41
58
  const firstPerson = /\b(?:I|we|my|our|me|us)\b/giu;
42
59
  const allowedPersonal = brief?.authorClaims.map(c => c.text) ?? [];
43
- for (const claim of matches(candidate, /[^.!?\n]+(?:[.!?]|$)/gu)) {
60
+ for (const claim of sentences(candidate)) {
44
61
  if (matches(claim.text, firstPerson).length && !original.includes(claim.text.trim()) && !allowedPersonal.some(text => text === claim.text.trim())) {
45
62
  changes.push({ kind: 'first-person', side: 'candidate', start: claim.start, end: claim.end, reason: 'New first-person wording lacks exact supplied author grounding; review is required.' });
46
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.10.0",
3
+ "version": "2026.10.3",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,6 +30,9 @@
30
30
  "schemas/security/",
31
31
  "schemas/decision/",
32
32
  "schemas/dataset/",
33
+ "schemas/mission-v1.schema.json",
34
+ "schemas/models/",
35
+ "vscode-extension/schemas/aiwg.config.v1.json",
33
36
  "dist/src/",
34
37
  "!dist/**/*.d.ts",
35
38
  "!dist/**/*.map",
@@ -38,6 +41,19 @@
38
41
  "dist/src/resources/web-release.d.ts",
39
42
  "dist/src/dataset/**/*.d.ts",
40
43
  "tools/_resolve-impl.mjs",
44
+ "tools/ralph-external/lib/provider-adapter.mjs",
45
+ "tools/ralph-external/lib/worker-health.mjs",
46
+ "tools/ralph-external/lib/claude-adapter.mjs",
47
+ "tools/ralph-external/lib/codex-adapter.mjs",
48
+ "tools/ralph-external/lib/opencode-adapter.mjs",
49
+ "tools/ralph-external/lib/factory-adapter.mjs",
50
+ "tools/ralph-external/lib/pi-adapter.mjs",
51
+ "tools/ralph-external/lib/omp-adapter.mjs",
52
+ "tools/ralph-external/lib/deepseek-harness-adapter.mjs",
53
+ "tools/ralph-external/lib/muse-adapter.mjs",
54
+ "tools/ralph-external/lib/openhuman-adapter.mjs",
55
+ "tools/providers/omp-transport.mjs",
56
+ "tools/providers/deepseek-harness-transport.mjs",
41
57
  "tools/agents/deploy-agents.mjs",
42
58
  "tools/agents/providers/",
43
59
  "tools/providers/antigravity-transport.mjs",
@@ -71,6 +87,7 @@
71
87
  "dependencies": {
72
88
  "@fortemi/core": "2026.7.15",
73
89
  "@modelcontextprotocol/sdk": "^1.30.0",
90
+ "acorn": "^8.16.0",
74
91
  "ajv": "^8.20.0",
75
92
  "ajv-formats": "^3.0.1",
76
93
  "chalk": "^4.1.2",