@aiwg/cli 2026.10.9 → 2026.10.10

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 (91) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  8. package/dist/src/cli/handlers/use.js +191 -230
  9. package/dist/src/cli/ui.js +1 -0
  10. package/dist/src/config/aiwg-config.js +38 -184
  11. package/dist/src/config/cli-help.js +54 -0
  12. package/dist/src/config/cli.js +5 -69
  13. package/dist/src/config/config-normalization.js +10 -0
  14. package/dist/src/config/worker-activation-gate.js +26 -0
  15. package/dist/src/config/worker-adoption-cli.js +102 -0
  16. package/dist/src/config/worker-adoption.js +368 -0
  17. package/dist/src/config/worker-config-digest.js +14 -0
  18. package/dist/src/config/worker-group-templates.js +150 -0
  19. package/dist/src/config/worker-guidance-warning.js +28 -0
  20. package/dist/src/config/worker-owner-lock.js +152 -0
  21. package/dist/src/config/worker-parallelism.js +154 -0
  22. package/dist/src/config/worker-write.js +75 -0
  23. package/dist/src/decision/telemetry/exporter.js +2 -1
  24. package/dist/src/decision/telemetry/index.js +1 -0
  25. package/dist/src/decision/telemetry/mapping.js +43 -0
  26. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  27. package/dist/src/decision/telemetry/redaction.js +6 -1
  28. package/dist/src/decision/telemetry/types.js +3 -0
  29. package/dist/src/decision/telemetry/worker.js +69 -0
  30. package/dist/src/lint/rule-activation.js +15 -7
  31. package/dist/src/lint/runner.js +2 -2
  32. package/dist/src/mcp/server.mjs +2 -11
  33. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  34. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  35. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  36. package/dist/src/mission-protocol/codecs.js +8 -38
  37. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  38. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  39. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  40. package/dist/src/models/conductor-data-policy.mjs +190 -0
  41. package/dist/src/models/config-loader.js +19 -3
  42. package/dist/src/models/config-writer.js +27 -0
  43. package/dist/src/models/model-capabilities.v1.json +197 -3
  44. package/dist/src/models/provider-policy.js +7 -155
  45. package/dist/src/models/resolver.js +2 -293
  46. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  47. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  48. package/dist/src/models/worker-direct-readiness.js +9 -218
  49. package/dist/src/models/worker-health-readiness.js +105 -26
  50. package/dist/src/models/worker-installation.mjs +12 -14
  51. package/dist/src/models/worker-member-references.js +64 -0
  52. package/dist/src/models/worker-plan.js +317 -0
  53. package/dist/src/models/worker-selection-policy.js +217 -0
  54. package/dist/src/models/wrapper-deployment.js +1 -1
  55. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  56. package/dist/src/serve/shared-host-scheduler.js +25 -107
  57. package/dist/src/serve/shared-host-starvation.js +73 -0
  58. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  59. package/dist/src/serve/worker-artifacts.mjs +56 -0
  60. package/dist/src/serve/worker-command-request.js +68 -0
  61. package/dist/src/serve/worker-invocation-paths.js +144 -0
  62. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  63. package/dist/src/serve/worker-invocation.js +24 -291
  64. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  65. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  66. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  67. package/package.json +9 -1
  68. package/schemas/mission-v1.schema.json +20 -19
  69. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  70. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  71. package/tools/agents/providers/claude.mjs +3 -30
  72. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  73. package/tools/agents/providers/codex.mjs +2 -8
  74. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  75. package/tools/agents/providers/rule-delivery.mjs +3 -16
  76. package/tools/agents/providers/rule-presentation.mjs +38 -0
  77. package/tools/plugin/package-plugins.mjs +1 -1
  78. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  79. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  80. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  81. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  82. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  83. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  84. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  85. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  86. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  87. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  88. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  89. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  90. package/tools/ralph-external/session-launcher.mjs +73 -217
  91. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -0,0 +1,35 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { initializeFrameworkWorkspace, ensureDir } from './base.mjs';
5
+ import { deployHookFile } from './claude.mjs';
6
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
7
+
8
+ export async function postDeploy(targetDir, opts) {
9
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
10
+ await deployConductorDataGuard(targetDir, 'claude', opts);
11
+
12
+ // Claude-specific post-deployment (settings.json, etc.)
13
+ const claudeDir = path.join(targetDir, '.claude');
14
+ const settingsPath = path.join(claudeDir, 'settings.json');
15
+
16
+ // If settings.json doesn't exist, create a minimal one
17
+ if (!fs.existsSync(settingsPath) && !opts.dryRun) {
18
+ ensureDir(claudeDir);
19
+ const settings = {
20
+ version: '1.0',
21
+ created: new Date().toISOString(),
22
+ aiwg: {
23
+ enabled: true,
24
+ mode: opts.mode || 'all'
25
+ }
26
+ };
27
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
28
+ console.log('Created .claude/settings.json');
29
+ }
30
+
31
+ // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
32
+ if (opts.srcRoot && opts.contextFiles !== false) {
33
+ deployHookFile(targetDir, opts);
34
+ }
35
+ }
@@ -1,3 +1,5 @@
1
+ import { postDeploy } from './claude-post-deploy.mjs';
2
+ export { postDeploy };
1
3
  import { importImpl } from '../../_resolve-impl.mjs';
2
4
  /**
3
5
  * Claude Code Provider
@@ -43,7 +45,6 @@ import {
43
45
  pruneStaleAiwgSkills,
44
46
  computeAllKernelNames,
45
47
  parseFrontmatter,
46
- initializeFrameworkWorkspace,
47
48
  filterAgentFiles,
48
49
  getAddonAgentFiles,
49
50
  getAddonCommandFiles,
@@ -395,34 +396,6 @@ export function createAgentsMd(target, srcRoot, dryRun) {
395
396
  // Post-Deployment
396
397
  // ============================================================================
397
398
 
398
- export async function postDeploy(targetDir, opts) {
399
- // Initialize framework workspace structure
400
- initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
401
-
402
- // Claude-specific post-deployment (settings.json, etc.)
403
- const claudeDir = path.join(targetDir, '.claude');
404
- const settingsPath = path.join(claudeDir, 'settings.json');
405
-
406
- // If settings.json doesn't exist, create a minimal one
407
- if (!fs.existsSync(settingsPath) && !opts.dryRun) {
408
- ensureDir(claudeDir);
409
- const settings = {
410
- version: '1.0',
411
- created: new Date().toISOString(),
412
- aiwg: {
413
- enabled: true,
414
- mode: opts.mode || 'all'
415
- }
416
- };
417
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
418
- console.log('Created .claude/settings.json');
419
- }
420
-
421
- // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
422
- if (opts.srcRoot && opts.contextFiles !== false) {
423
- deployHookFile(targetDir, opts);
424
- }
425
- }
426
399
 
427
400
  /**
428
401
  * Deploy the AIWG.md hook file and add @AIWG.md directive to CLAUDE.md.
@@ -444,7 +417,7 @@ function interpolateHookTokens(content, counts, targetDir) {
444
417
  });
445
418
  }
446
419
 
447
- function deployHookFile(targetDir, opts) {
420
+ export function deployHookFile(targetDir, opts) {
448
421
  const { srcRoot, dryRun, counts } = opts;
449
422
  const templatePath = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'templates', 'project', 'AIWG.md');
450
423
  const hookDest = path.join(targetDir, 'AIWG.md');
@@ -0,0 +1,13 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import { initializeFrameworkWorkspace } from './base.mjs';
3
+ import { createAgentsMd } from './codex.mjs';
4
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
5
+
6
+ export async function postDeploy(targetDir, opts) {
7
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
8
+ await deployConductorDataGuard(targetDir, 'codex', opts);
9
+
10
+ if (opts.createAgentsMd) {
11
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
12
+ }
13
+ }
@@ -1,3 +1,5 @@
1
+ import { postDeploy } from './codex-post-deploy.mjs';
2
+ export { postDeploy };
1
3
  import { importImpl } from '../../_resolve-impl.mjs';
2
4
  /**
3
5
  * OpenAI Codex Provider
@@ -47,7 +49,6 @@ import {
47
49
  writeFile,
48
50
  deployFiles,
49
51
  createAgentsMdFromTemplate,
50
- initializeFrameworkWorkspace,
51
52
  getAddonAgentFiles,
52
53
  getAddonCommandFiles,
53
54
  getAddonSkillDirs,
@@ -511,13 +512,6 @@ export function generatePluginBundle(targetDir, opts = {}) {
511
512
  // Post-Deployment
512
513
  // ============================================================================
513
514
 
514
- export async function postDeploy(targetDir, opts) {
515
- initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
516
-
517
- if (opts.createAgentsMd) {
518
- if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
519
- }
520
- }
521
515
 
522
516
  // ============================================================================
523
517
  // File Extension
@@ -0,0 +1,61 @@
1
+ // Compile the existing project data policy into project-local provider refusals.
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { importImpl, resolveImpl } from '../../_resolve-impl.mjs';
6
+
7
+ const quote = value => `'${value.replace(/'/g, `'"'"'`)}'`;
8
+
9
+ // Local-only paths are declared only in project/user models.json or project
10
+ // parallelism (the built-in defaults declare none). Skip the full model-config
11
+ // load when none of them mention the field, so ordinary deploys never depend
12
+ // on the framework default config (absent from the slim CLI package).
13
+ function declaresLocalOnlyPaths(targetDir) {
14
+ const userDir = process.env.AIWG_CONFIG || path.join(os.homedir(), '.config/aiwg');
15
+ return [path.join(targetDir, 'models.json'), path.join(userDir, 'models.json'), path.join(targetDir, '.aiwg/aiwg.config')]
16
+ .some(file => { try { return fs.readFileSync(file, 'utf8').includes('localOnlyPaths'); } catch { return false; } });
17
+ }
18
+
19
+ export async function deployConductorDataGuard(targetDir, provider, opts = {}) {
20
+ if (!declaresLocalOnlyPaths(targetDir)) return [];
21
+ // Always resolve policy through the existing loader, including inherited restrictions.
22
+ const { ConfigLoader } = await importImpl(import.meta.url, 'models/config-loader.js');
23
+ const { readAiwgConfig } = await importImpl(import.meta.url, 'config/aiwg-config.js');
24
+ const guard = await importImpl(import.meta.url, 'models/conductor-data-policy.mjs');
25
+ const config = await readAiwgConfig(targetDir);
26
+ const models = await new ConfigLoader(targetDir).load(config?.parallelism);
27
+ const paths = guard.collectLocalOnlyPaths(models.defaults.workerSelection, config?.parallelism);
28
+ if (!paths.length || opts.dryRun) return paths;
29
+ const root = path.resolve(targetDir);
30
+ if (provider === 'claude') {
31
+ const settingsPath = path.join(root, '.claude/settings.json');
32
+ const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, 'utf8')) : {};
33
+ const hookPath = path.join(root, '.claude/hooks/aiwg-local-only.mjs');
34
+ const hookSource = fs.readFileSync(resolveImpl(import.meta.url, 'models/conductor-data-policy.mjs'), 'utf8');
35
+ fs.mkdirSync(path.dirname(hookPath), { recursive: true });
36
+ fs.writeFileSync(hookPath, `${hookSource}\nrunConductorDataHook(${JSON.stringify(root)}, ${JSON.stringify(paths)});\n`);
37
+ settings.permissions ??= {};
38
+ settings.permissions.deny = [...new Set([...(settings.permissions.deny ?? []), ...paths.flatMap(file => [
39
+ `Read(//${path.join(root, file).slice(1)})`, `Read(//${path.join(root, file).slice(1)}/**)`,
40
+ ])])];
41
+ settings.hooks ??= {};
42
+ const command = `node ${quote(hookPath)}`;
43
+ const entries = settings.hooks.PreToolUse ?? [];
44
+ settings.hooks.PreToolUse = [...entries.filter(entry => !entry.hooks?.some(hook => hook.command === command)),
45
+ { matcher: '.*', hooks: [{ type: 'command', command }] }];
46
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
47
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
48
+ } else if (provider === 'codex') {
49
+ const rulesPath = path.join(root, '.codex/rules/aiwg-local-only.rules');
50
+ const lines = ['# AIWG local-only inputs: explicit command prefixes only; not filesystem isolation.'];
51
+ for (const file of paths) for (const spelling of [file, `./${file}`, path.join(root, file)]) {
52
+ for (const command of ['cat', 'head', 'tail', 'less', 'more', 'sed', 'awk', 'grep', 'rg']) {
53
+ lines.push(`prefix_rule(pattern = ${JSON.stringify([command, spelling])}, decision = "forbidden")`);
54
+ }
55
+ }
56
+ fs.mkdirSync(path.dirname(rulesPath), { recursive: true });
57
+ fs.writeFileSync(rulesPath, lines.join('\n') + '\n');
58
+ }
59
+ console.log(`Local-only conductor guard (${provider}): ${paths.join(', ')}; shell coverage is limited.`);
60
+ return paths;
61
+ }
@@ -20,6 +20,8 @@ const { guardRulesInlineBudget } = await importImpl(import.meta.url, 'rules/inli
20
20
  const { userConfigFile } = await importImpl(import.meta.url, 'config/user-config-dir.mjs');
21
21
  const { resolveProjectAiwgDir } = await importImpl(import.meta.url, 'config/project-artifacts-runtime.mjs');
22
22
  import { writeContextFileSync, CONTEXT_STAGE_ENV } from './context-write.mjs';
23
+ import { projectOnDemandReferences, renderRuleKernelIndex } from './rule-presentation.mjs';
24
+ export { renderRuleKernelIndex } from './rule-presentation.mjs';
23
25
  const PREPARATION_HOOK = Symbol.for('aiwg.provider-preparation-hook.v1');
24
26
 
25
27
  export function installRuleDeploymentPreparationHook(hook) {
@@ -298,21 +300,6 @@ export async function prepareRuleDelivery(opts) {
298
300
  return { rules, bindings, targets, pins: pinned.rules, pinWarnings: pinned.warnings };
299
301
  }
300
302
 
301
- export function renderRuleKernelIndex(rules, nativePaths = false) {
302
- const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
303
- const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
304
- const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
305
- if (!summary.length && !paths.length && !kernels.length) return '';
306
- const lines = ['# Rule kernel index', ''];
307
- for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
308
- for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
309
- if (paths.length) {
310
- lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
311
- nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
312
- for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
313
- }
314
- return lines.join('\n');
315
- }
316
303
 
317
304
  function managed(content, opts) {
318
305
  return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
@@ -334,7 +321,7 @@ function putStartup(file, body) {
334
321
 
335
322
  /** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
336
323
  function projectRules(opts, provider) {
337
- const { rules } = opts.ruleDelivery;
324
+ const rules = projectOnDemandReferences(opts.ruleDelivery.rules, ['claude', 'cursor'].includes(provider.name));
338
325
  if (opts.contextFiles !== false && provider.name === 'hermes') {
339
326
  // The old context generator carries hardcoded corpus directives. Only the
340
327
  // selected contracts below may activate policy; preserve operator prose.
@@ -0,0 +1,38 @@
1
+ /** Render deployed rule references without links to catalog-only siblings. */
2
+ import path from 'node:path';
3
+
4
+ export function projectOnDemandReferences(rules, nativePaths = false) {
5
+ const byFile = new Map(rules.map(rule => [path.resolve(rule.file), rule]));
6
+ return rules.map(rule => {
7
+ const rewrite = content => content?.replace(
8
+ /\[[^\]\n]+\]\((?:\.\/)?([\w-]+)\.md\)[ \t]*\(`aiwg show rule \1(?: --body)?`, on-demand\)/g,
9
+ (reference, name) => {
10
+ const sibling = byFile.get(path.resolve(path.dirname(rule.file), `${name}.md`));
11
+ // Only known catalog rules can replace a relative file reference. Keep
12
+ // unknown/broken links and links to files selected for native delivery.
13
+ if (!sibling?.tagged || sibling.name !== name || sibling.activation === 'kernel' ||
14
+ (nativePaths && sibling.activation === 'paths')) return reference;
15
+ return `\`aiwg show rule ${name} --body\` (on-demand)`;
16
+ },
17
+ );
18
+ return { ...rule, content: rewrite(rule.content), body: rewrite(rule.body),
19
+ ...(rule.coreBody === undefined ? {} : { coreBody: rewrite(rule.coreBody) }) };
20
+ });
21
+ }
22
+
23
+ export function renderRuleKernelIndex(rules, nativePaths = false) {
24
+ const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
25
+ const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
26
+ const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
27
+ if (!summary.length && !paths.length && !kernels.length) return '';
28
+ const lines = ['# Rule kernel index', ''];
29
+ for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
30
+ for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
31
+ if (paths.length) {
32
+ lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
33
+ nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
34
+ for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
35
+ }
36
+ return lines.join('\n');
37
+ }
38
+
@@ -41,7 +41,7 @@ const PLUGIN_CONFIGS = {
41
41
  skills: 'agentic/code/frameworks/sdlc-complete/skills'
42
42
  },
43
43
  extraCopy: [
44
- { from: 'tools/security/threat-assessment.mjs', to: 'tools/security/threat-assessment.mjs' },
44
+ ...['threat-assessment.mjs', 'threat-assessment'].map(name => ({ from: `tools/security/${name}`, to: `tools/security/${name}` })),
45
45
  { from: 'agentic/code/frameworks/sdlc-complete/flows/flow-architecture-evolution.playbook.yaml', to: 'flows/flow-architecture-evolution.playbook.yaml' },
46
46
  { from: 'agentic/code/frameworks/sdlc-complete/flows/flow-release.playbook.yaml', to: 'flows/flow-release.playbook.yaml' }
47
47
  ],
@@ -212,8 +212,8 @@ export class ClaudeAdapter extends ProviderAdapter {
212
212
  ];
213
213
 
214
214
  // Session tracking
215
- if (options.sessionId) {
216
- args.push('--session-id', options.sessionId);
215
+ if (options.resumeSession || options.sessionId) {
216
+ args.push(options.resumeSession ? '--resume' : '--session-id', options.resumeSession || options.sessionId);
217
217
  }
218
218
 
219
219
  // Model selection (Claude passes through directly)
@@ -44,7 +44,7 @@ const MODEL_MAP = {
44
44
  *
45
45
  * codex --no-daemon exec --ignore-user-config -m M \
46
46
  * -c model_reasoning_effort="E" -s S -C W -c approval_policy="never" \
47
- * --skip-git-repo-check -o F --json - < briefPath
47
+ * --skip-git-repo-check -o F.last-message --json - < briefPath
48
48
  *
49
49
  * `--no-daemon` is a root-level flag and `--ignore-user-config` is an
50
50
  * `exec`-level flag (root `--help` lists no `--ignore-user-config`;
@@ -206,7 +206,7 @@ export class CodexAdapter extends ProviderAdapter {
206
206
  * model, explicit effort as one `-c model_reasoning_effort="E"` token
207
207
  * (TOML `"E"` quoting via `JSON.stringify`; there is no shell in argv),
208
208
  * explicit sandbox/approval (`never` only), the conductor workdir as
209
- * `-C`, the final file as `-o`, the JSONL event stream as `--json` and
209
+ * `-C`, a separate last-message capture as `-o`, the JSONL stream as `--json` and
210
210
  * `-` as the prompt argument so the brief arrives on stdin
211
211
  * (`stdin: 'brief-text'`). Permission flags from the legacy Ralph loop
212
212
  * (`--dangerously-bypass-approvals-and-sandbox`), guessed step caps and
@@ -260,7 +260,7 @@ export class CodexAdapter extends ProviderAdapter {
260
260
  '-C', cwd,
261
261
  '-c', `approval_policy=${JSON.stringify(approval)}`,
262
262
  '--skip-git-repo-check',
263
- '-o', finalPath,
263
+ '-o', `${finalPath}.last-message`,
264
264
  '--json',
265
265
  '-',
266
266
  ];
@@ -0,0 +1,86 @@
1
+ // Validate the parent reservation and disclose one-shot attempt bindings.
2
+ import { importImpl } from '../../_resolve-impl.mjs';
3
+ import { sampleWorkerIdentity } from '../process-monitor.mjs';
4
+ import { changedBindings } from './session-attempt.mjs';
5
+ const { qualificationDigest } = await importImpl(import.meta.url, 'models/qualification-digest.mjs');
6
+
7
+ export function managedAttemptBindings(plan) {
8
+ const invocation = plan.invocation;
9
+ const digests = plan.digests ?? {};
10
+ const selected = invocation?.selected ?? plan.selected;
11
+ return { policyDigest: invocation?.policyDigest ?? (digests.configDigest && digests.resolvedModelsDigest
12
+ ? qualificationDigest([digests.configDigest, digests.resolvedModelsDigest]) : null),
13
+ catalogDigest: invocation?.catalogDigest ?? digests.catalogDigest ?? null,
14
+ profileDigest: invocation?.profileDigest ?? digests.profileDigest ?? null,
15
+ selected: selected ? { group: selected.group, member: selected.member, cli: selected.cli,
16
+ inferenceProvider: selected.inferenceProvider, model: selected.model,
17
+ effort: typeof selected.effort === 'object' ? selected.effort?.value ?? null : selected.effort ?? null,
18
+ profileRef: selected.profileRef ?? selected.profile } : null, sessionId: null };
19
+ }
20
+
21
+ export async function validateManagedAttempt(options, saved, inputs, now, validate, publish) {
22
+ const attempt = await managedWorkerAttempt(options, saved, inputs, now);
23
+ await publish(attempt);
24
+ try {
25
+ const invocation = await validate(saved, inputs.snapshot, inputs.drivers, now);
26
+ return { invocation, attempt };
27
+ } catch (error) {
28
+ attempt.status = 'blocked'; attempt.errorCode = error.code ?? 'PLAN_REVALIDATION_FAILED';
29
+ await publish(attempt);
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ export async function managedWorkerAttempt(options, saved, inputs, now) {
35
+ const { resolveWorkerInvocation } = await importImpl(import.meta.url, 'serve/worker-invocation.js');
36
+ const current = await resolveWorkerInvocation(inputs.snapshot, saved.request, inputs.drivers, now);
37
+ const original = options.attempt?.originalBindings ?? managedAttemptBindings(saved);
38
+ const bindings = managedAttemptBindings(current);
39
+ return { ...options.attempt, kind: 'fresh', reason: options.attempt?.reason
40
+ ?? (options.resumeSession || options.sessionId ? 'one-shot-continuation-unsupported' : 'one-shot-profile'),
41
+ requestedSessionId: options.attempt?.requestedSessionId ?? options.resumeSession ?? options.sessionId ?? null,
42
+ requestId: options.managedAdmission.requestId, originalBindings: original,
43
+ bindings, changes: changedBindings(original, bindings),
44
+ status: current.status === 'eligible' ? 'prepared' : 'blocked',
45
+ diagnostics: current.diagnostics };
46
+ }
47
+
48
+ /** Reuse the MC authority and its registered one-shot identity at every spawn boundary. */
49
+ export async function assertManagedWorkerLaunch(options, invocation, inputs, projectRoot = process.cwd()) {
50
+ if (!options.managedAdmission || invocation.mode !== 'managed'
51
+ || inputs.snapshot.config.parallelism?.worker_mode !== 'managed') throw new Error('Managed worker admission is required');
52
+ const { createMcAdmissionScheduler } = await importImpl(import.meta.url, 'cli/handlers/mc.js');
53
+ const { loadLauncherRegistry } = await importImpl(import.meta.url, 'cli/handlers/ralph-launcher.js');
54
+ const { workerModelKey } = await importImpl(import.meta.url, 'models/provider-policy.js');
55
+ const scheduler = await createMcAdmissionScheduler(projectRoot, inputs.snapshot.config);
56
+ const { requestId, generation } = options.managedAdmission;
57
+ const snapshot = scheduler.snapshot();
58
+ const record = snapshot.records[requestId];
59
+ const deadlineAtMs = Math.min(options.cycleDeadlineAtMs ?? Infinity, ...[record?.accounting?.runKey, record?.accounting?.taskKey]
60
+ .map(key => snapshot.allowances?.[key]?.deadlineAt).filter(Boolean).map(Date.parse));
61
+ const registered = loadLauncherRegistry(projectRoot).loops[options.loopId ?? process.env.RALPH_LOOP_ID];
62
+ const worker = record?.managed;
63
+ const reservation = record?.accounting?.reservation;
64
+ if (!record || record.state !== 'admitted' || record.leaseGeneration !== generation
65
+ || !record.launchClaimedAt || !record.metadata?.invocationId || Date.parse(record.leaseExpiresAt ?? '') <= Date.now()
66
+ || deadlineAtMs <= Date.now()
67
+ || worker?.kind !== 'external' || worker.group !== invocation.selected.group
68
+ || worker.member !== invocation.selected.member || worker.cli !== invocation.selected.cli
69
+ || worker.model !== workerModelKey(invocation.selected.inferenceProvider, invocation.selected.model)
70
+ || reservation?.tokens != null && !(invocation.limits.tokens <= reservation.tokens)
71
+ || reservation?.costUsd != null && !(invocation.limits.costUsd !== null && invocation.limits.costUsd <= reservation.costUsd)
72
+ || registered?.managedAdmission?.requestId !== requestId || registered.managedAdmission.generation !== generation
73
+ || registered.managedWorker?.invocationPath !== options.invocationPath
74
+ || registered.managedWorker?.runDir !== options.runDir) {
75
+ throw new Error('Managed worker launch differs from the current claimed admission');
76
+ }
77
+ if (record.execution) {
78
+ const identity = sampleWorkerIdentity(process.pid);
79
+ if (record.execution.pid !== process.pid || !identity?.bootId || !identity.startTicks
80
+ || record.execution.startedAt !== `${identity.bootId}:${identity.startTicks}:${identity.uid}`) {
81
+ throw new Error('Managed worker supervisor identity differs');
82
+ }
83
+ }
84
+ return { ...record, deadlineAtMs };
85
+ }
86
+