@aiwg/cli 2026.10.4 → 2026.10.6

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 (220) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +72 -9
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +146 -16
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-preparation.js +91 -26
  16. package/dist/src/artifacts/index-reader.js +44 -0
  17. package/dist/src/artifacts/index-refresh.js +130 -56
  18. package/dist/src/artifacts/index-status.js +20 -5
  19. package/dist/src/artifacts/index-worker.mjs +9 -7
  20. package/dist/src/artifacts/memory-maintenance.js +503 -0
  21. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  22. package/dist/src/artifacts/query-engine.js +263 -108
  23. package/dist/src/artifacts/relations-builder.js +2 -2
  24. package/dist/src/artifacts/source-graph.js +2 -2
  25. package/dist/src/artifacts/types.js +9 -1
  26. package/dist/src/cli/handlers/decision.js +19 -1
  27. package/dist/src/cli/handlers/guided-setup.js +470 -0
  28. package/dist/src/cli/handlers/help.js +2 -2
  29. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +41 -6
  32. package/dist/src/cli/handlers/local-executor.js +5 -3
  33. package/dist/src/cli/handlers/mc.js +509 -53
  34. package/dist/src/cli/handlers/models.js +32 -2
  35. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  36. package/dist/src/cli/handlers/rules.js +1 -1
  37. package/dist/src/cli/handlers/script-runner.js +73 -2
  38. package/dist/src/cli/handlers/serve.js +5 -3
  39. package/dist/src/cli/handlers/sessions.js +40 -6
  40. package/dist/src/cli/handlers/setup.js +428 -1
  41. package/dist/src/cli/handlers/skill-lint.js +1 -1
  42. package/dist/src/cli/handlers/subcommands.js +118 -8
  43. package/dist/src/cli/handlers/use.js +279 -93
  44. package/dist/src/cli/handlers/workspace.js +3 -1
  45. package/dist/src/cli/prompt-utils.js +8 -0
  46. package/dist/src/cli/router.js +16 -1
  47. package/dist/src/config/aiwg-config.js +42 -0
  48. package/dist/src/config/cli.js +7 -3
  49. package/dist/src/config/decision-config.js +14 -1
  50. package/dist/src/config/native-sandbox-warnings.js +25 -0
  51. package/dist/src/contributors/validation.js +6 -5
  52. package/dist/src/decision/admission.js +7 -3
  53. package/dist/src/decision/benchmark.js +469 -0
  54. package/dist/src/decision/ledger.js +22 -3
  55. package/dist/src/decision/pricing.js +147 -0
  56. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  57. package/dist/src/decision/qualification/load-staged.js +539 -0
  58. package/dist/src/decision/qualification/load.js +109 -33
  59. package/dist/src/decision/qualification/runner.js +5 -2
  60. package/dist/src/decision/review/service.js +6 -4
  61. package/dist/src/decision/review/validate.js +23 -2
  62. package/dist/src/decision/savings.js +8 -3
  63. package/dist/src/discovery/index.js +492 -0
  64. package/dist/src/discovery/interview.js +233 -0
  65. package/dist/src/discovery/plan-apply.js +571 -0
  66. package/dist/src/discovery/plan-store.js +171 -0
  67. package/dist/src/discovery/plan-types.js +2 -0
  68. package/dist/src/discovery/plan-validation.js +139 -0
  69. package/dist/src/discovery/types.js +2 -0
  70. package/dist/src/extensions/commands/definitions.js +49 -9
  71. package/dist/src/extensions/manifest.js +71 -44
  72. package/dist/src/extensions/project-local-discovery.js +1 -1
  73. package/dist/src/extensions/project-quickref.js +18 -17
  74. package/dist/src/extensions/validation.js +32 -29
  75. package/dist/src/housekeeper/daemon-config.js +148 -0
  76. package/dist/src/housekeeper/ipc.js +84 -0
  77. package/dist/src/housekeeper/lifecycle.js +148 -0
  78. package/dist/src/housekeeper/platform.js +35 -0
  79. package/dist/src/housekeeper/status.js +70 -0
  80. package/dist/src/housekeeper/units.js +114 -0
  81. package/dist/src/jobs/flow.js +22 -21
  82. package/dist/src/lint/rule-activation.js +20 -5
  83. package/dist/src/lint/runner.js +1 -1
  84. package/dist/src/mcp/cli.mjs +5 -6
  85. package/dist/src/mcp/config-file.mjs +6 -2
  86. package/dist/src/mcp/config-layers.mjs +23 -7
  87. package/dist/src/mcp/profiles.js +18 -31
  88. package/dist/src/mcp/profiles.mjs +18 -32
  89. package/dist/src/mcp/registry.js +7 -27
  90. package/dist/src/mcp/registry.mjs +7 -28
  91. package/dist/src/mcp/server.mjs +34 -1
  92. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  93. package/dist/src/mcp/tools/command-run.mjs +3 -2
  94. package/dist/src/mcp/tools/decision.mjs +49 -48
  95. package/dist/src/mcp/tools/discovery.mjs +46 -21
  96. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  97. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  98. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  99. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  100. package/dist/src/memory/audit.js +719 -0
  101. package/dist/src/memory/canonical-context.js +392 -13
  102. package/dist/src/memory/canonical-maintenance.js +95 -0
  103. package/dist/src/memory/cli-result.js +22 -0
  104. package/dist/src/memory/cli.js +176 -0
  105. package/dist/src/memory/context-pack.js +57 -6
  106. package/dist/src/memory/cost.js +512 -0
  107. package/dist/src/memory/firewall-classification.d.mts +7 -0
  108. package/dist/src/memory/firewall-classification.mjs +32 -0
  109. package/dist/src/memory/index.js +8 -0
  110. package/dist/src/memory/line-maintenance.js +68 -0
  111. package/dist/src/memory/maintenance-cli.js +286 -0
  112. package/dist/src/memory/maintenance-contract.js +136 -0
  113. package/dist/src/memory/maintenance-index.js +18 -0
  114. package/dist/src/memory/maintenance-owner.js +2 -0
  115. package/dist/src/memory/maintenance-record.js +138 -0
  116. package/dist/src/memory/maintenance.js +254 -0
  117. package/dist/src/memory/provider-memory-adapter.js +154 -0
  118. package/dist/src/memory/recommend-cli.js +83 -0
  119. package/dist/src/memory/recommend.js +269 -0
  120. package/dist/src/memory/scheduled.js +81 -0
  121. package/dist/src/memory/taxonomy.js +44 -0
  122. package/dist/src/mission-protocol/codecs.js +19 -9
  123. package/dist/src/models/config-loader.js +11 -1
  124. package/dist/src/models/model-capabilities.v1.json +74 -50
  125. package/dist/src/models/provider-policy.js +125 -62
  126. package/dist/src/models/resolver.js +45 -8
  127. package/dist/src/models/worker-installation.d.mts +7 -0
  128. package/dist/src/models/worker-installation.mjs +169 -37
  129. package/dist/src/models/wrapper-route.js +1 -1
  130. package/dist/src/output-modes/registry.js +8 -7
  131. package/dist/src/plugin/metadata-validator.js +1 -1
  132. package/dist/src/providers/deploy-version.d.mts +1 -0
  133. package/dist/src/providers/deploy-version.mjs +17 -0
  134. package/dist/src/providers/provider-definitions.js +19 -16
  135. package/dist/src/providers/skill-ownership.d.mts +11 -2
  136. package/dist/src/providers/skill-ownership.mjs +35 -17
  137. package/dist/src/rules/activation.d.mts +11 -2
  138. package/dist/src/rules/activation.mjs +82 -6
  139. package/dist/src/rules/inventory.js +1 -1
  140. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  141. package/dist/src/schema/zod-validation.d.mts +6 -0
  142. package/dist/src/schema/zod-validation.mjs +50 -0
  143. package/dist/src/serve/executor-registry.js +297 -20
  144. package/dist/src/serve/executor-token.d.mts +1 -0
  145. package/dist/src/serve/executor-token.mjs +8 -0
  146. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  147. package/dist/src/serve/worker-invocation.js +114 -15
  148. package/dist/src/sessions/adapters/claude.js +19 -18
  149. package/dist/src/sessions/adapters/codex.js +33 -19
  150. package/dist/src/sessions/adapters/copilot.js +14 -13
  151. package/dist/src/sessions/adapters/cursor.js +19 -18
  152. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  153. package/dist/src/sessions/adapters/factory.js +11 -10
  154. package/dist/src/sessions/adapters/generic.js +32 -23
  155. package/dist/src/sessions/adapters/grokbot.js +35 -35
  156. package/dist/src/sessions/adapters/hermes.js +20 -19
  157. package/dist/src/sessions/adapters/muse.js +50 -49
  158. package/dist/src/sessions/adapters/omp.js +5 -4
  159. package/dist/src/sessions/adapters/openclaw.js +30 -29
  160. package/dist/src/sessions/adapters/opencode.js +26 -26
  161. package/dist/src/sessions/adapters/openhuman.js +24 -23
  162. package/dist/src/sessions/adapters/pi.js +5 -4
  163. package/dist/src/sessions/adapters/windsurf.js +8 -8
  164. package/dist/src/sessions/analytics.js +10 -9
  165. package/dist/src/sessions/batch-contracts.js +1 -1
  166. package/dist/src/sessions/batch-import.js +202 -2
  167. package/dist/src/sessions/candidates.js +7 -6
  168. package/dist/src/sessions/contracts.js +73 -54
  169. package/dist/src/sessions/coverage-preflight.js +26 -1
  170. package/dist/src/sessions/output-registration.js +11 -10
  171. package/dist/src/sessions/policy.js +18 -7
  172. package/dist/src/sessions/refresh.js +2 -3
  173. package/dist/src/sessions/repository.js +4 -2
  174. package/dist/src/sessions/workspace-discovery.js +92 -26
  175. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  176. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  177. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  178. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  179. package/dist/src/update/checker.d.mts +4 -0
  180. package/dist/src/update/checker.mjs +14 -0
  181. package/dist/src/writing/exemplar-selection.js +6 -6
  182. package/dist/src/writing/voice-evaluation.js +24 -23
  183. package/dist/src/writing/writer-migration.js +14 -13
  184. package/dist/src/writing/writer-profile.js +19 -18
  185. package/dist/src/writing/writing-brief.js +18 -17
  186. package/dist/src/writing/writing-receipt.js +47 -46
  187. package/package.json +5 -2
  188. package/schemas/decision/DecisionReview.schema.json +78 -1
  189. package/schemas/mission-v1.schema.json +3 -0
  190. package/tools/agents/deploy-agents.mjs +67 -39
  191. package/tools/agents/providers/antigravity.mjs +6 -1
  192. package/tools/agents/providers/base.mjs +19 -13
  193. package/tools/agents/providers/claude.mjs +1 -1
  194. package/tools/agents/providers/codex.mjs +50 -56
  195. package/tools/agents/providers/copilot.mjs +8 -2
  196. package/tools/agents/providers/cursor.mjs +1 -1
  197. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  198. package/tools/agents/providers/factory.mjs +7 -1
  199. package/tools/agents/providers/grok-build.mjs +18 -2
  200. package/tools/agents/providers/grokbot.mjs +1 -1
  201. package/tools/agents/providers/hermes.mjs +7 -13
  202. package/tools/agents/providers/muse-hooks.mjs +21 -14
  203. package/tools/agents/providers/muse.mjs +7 -2
  204. package/tools/agents/providers/omp.mjs +28 -4
  205. package/tools/agents/providers/opencode.mjs +14 -5
  206. package/tools/agents/providers/openhuman.mjs +1 -1
  207. package/tools/agents/providers/pi.mjs +1 -1
  208. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  209. package/tools/agents/providers/rule-delivery.mjs +263 -71
  210. package/tools/agents/providers/windsurf.mjs +7 -0
  211. package/tools/cli/workspace-status.mjs +5 -0
  212. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  213. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  214. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  215. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  216. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  217. package/tools/ralph-external/process-monitor.mjs +7 -4
  218. package/tools/ralph-external/session-launcher.mjs +280 -32
  219. package/tools/skills/deploy-skills-codex.mjs +11 -7
  220. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -30,6 +30,7 @@ let fs;
30
30
  try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
31
31
  import path from 'path';
32
32
  import {
33
+ withRuleDeployment,
33
34
  ensureDir,
34
35
  listMdFiles,
35
36
  listMdFilesRecursive,
@@ -234,6 +235,7 @@ export function mapModel(shorthand, modelCfg, modelsConfig) {
234
235
  * Generate AGENTS.md for Windsurf with all agents aggregated
235
236
  */
236
237
  export function generateAgentsMd(files, destPath, opts) {
238
+ if (opts.contextFiles === false) return;
237
239
  const { dryRun } = opts;
238
240
 
239
241
  const lines = [];
@@ -507,6 +509,7 @@ export function deploySkills(skillDirs, targetDir, opts) {
507
509
  * Deploy rules as discrete files with trigger: always_on frontmatter injected
508
510
  */
509
511
  export function deployRules(ruleFiles, targetDir, opts) {
512
+ if (opts.ruleDelivery) return [];
510
513
  const destDir = path.join(targetDir, paths.rules);
511
514
  ensureDir(destDir, opts.dryRun);
512
515
  cleanupOldRuleFiles(destDir, opts);
@@ -534,6 +537,10 @@ export function getFileExtension(type) {
534
537
  // ============================================================================
535
538
 
536
539
  export async function deploy(opts) {
540
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
541
+ }
542
+
543
+ async function deploySelected(opts) {
537
544
  const {
538
545
  srcRoot,
539
546
  target,
@@ -32,6 +32,7 @@ const __filename = fileURLToPath(import.meta.url);
32
32
  const __dirname = path.dirname(__filename);
33
33
  const packageRoot = path.resolve(__dirname, '../..');
34
34
  const runtimeRoot = existsSync(path.join(packageRoot, 'src')) ? 'src' : 'dist/src';
35
+ const { readNativeSandboxWarnings } = await importImpl(import.meta.url, 'config/native-sandbox-warnings.js');
35
36
  const [{ resolveProjectAiwgDir }, { auditProjectArtifactHealth }] = await Promise.all([
36
37
  import(pathToFileURL(path.join(packageRoot, runtimeRoot, 'config/project-artifacts-runtime.mjs')).href),
37
38
  import(pathToFileURL(path.join(packageRoot, runtimeRoot, 'config/project-artifacts-health.mjs')).href),
@@ -356,6 +357,7 @@ async function buildWorkspaceStatus(projectRoot) {
356
357
  const { rulePinState } = await importImpl(import.meta.url, 'rules/pin-state.js');
357
358
  const pins = rulePinState(projectRoot, packageRoot);
358
359
  const result = {
360
+ nativeSandboxWarnings: await readNativeSandboxWarnings(projectRoot).catch(() => []),
359
361
  rule_pins: { rules: pins.rules, warnings: pins.warnings },
360
362
  decisionOffload: await collectDecisionOffloadReport(projectRoot),
361
363
  workspace: {
@@ -545,6 +547,7 @@ async function buildVerificationProbe(projectRoot) {
545
547
 
546
548
  return {
547
549
  schema: 'aiwg.status.probe.v1',
550
+ nativeSandboxWarnings: workspace.nativeSandboxWarnings,
548
551
  rule_pins: workspace.rule_pins,
549
552
  generated_at: new Date().toISOString(),
550
553
  project_root: path.resolve(projectRoot),
@@ -725,6 +728,7 @@ async function workspaceStatus(args) {
725
728
  console.log('='.repeat(60));
726
729
  console.log('');
727
730
  console.log('No .aiwg/ workspace found in this project.');
731
+ for (const warning of result.nativeSandboxWarnings) console.log(`Warning: ${warning.message}`);
728
732
  console.log('');
729
733
  console.log('To create a workspace:');
730
734
  console.log(' aiwg use sdlc # Install SDLC framework');
@@ -741,6 +745,7 @@ async function workspaceStatus(args) {
741
745
  }
742
746
 
743
747
  // Text output
748
+ for (const warning of result.nativeSandboxWarnings) console.log(`Warning: ${warning.message}`);
744
749
  console.log('\nAIWG - Workspace Status');
745
750
  console.log('='.repeat(60));
746
751
  console.log('');
@@ -27,16 +27,14 @@ const _require = createRequire(import.meta.url);
27
27
  let fs;
28
28
  try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
29
29
  import path from 'path';
30
+ import { fileURLToPath } from 'node:url';
30
31
  import os from 'os';
31
32
  import { getFrameworksForMode, normalizeDeploymentMode, getAddonSkillDirs, listSkillDirs, collectFrameworkArtifacts } from '../agents/providers/base.mjs';
32
33
 
33
- const CODEX_PROMPTS_DIR = path.join(os.homedir(), '.codex', 'prompts');
34
-
35
- function parseArgs() {
36
- const args = process.argv.slice(2);
34
+ function parseArgs(args, homeDir) {
37
35
  const cfg = {
38
36
  source: null,
39
- target: CODEX_PROMPTS_DIR,
37
+ target: path.join(homeDir, '.codex', 'prompts'),
40
38
  mode: 'all',
41
39
  dryRun: false,
42
40
  force: false,
@@ -265,8 +263,8 @@ function getCommandDirectories(srcRoot, mode) {
265
263
  return dirs;
266
264
  }
267
265
 
268
- (async function main() {
269
- const cfg = parseArgs();
266
+ export async function main(args = process.argv.slice(2), internal = {}) {
267
+ const cfg = parseArgs(args, internal.homeDir || os.homedir());
270
268
  const { source, target, mode, dryRun, force, prefix } = cfg;
271
269
 
272
270
  // Resolve source directory
@@ -333,4 +331,9 @@ function getCommandDirectories(srcRoot, mode) {
333
331
  console.log(`\nRestart Codex to load new prompts.`);
334
332
  console.log(`Use prompts with: /prompts:${prefix}-<command-name>`);
335
333
  }
336
- })();
334
+ }
335
+
336
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
337
+ try { await main(); }
338
+ catch (error) { console.error(error.message); process.exitCode = 1; }
339
+ }
@@ -75,7 +75,7 @@ const MODEL_MAP = {
75
75
  */
76
76
  export const CODEX_SUPPORTED_VERSIONS = Object.freeze(['0.161.0']);
77
77
  export const CODEX_DIRECT_EFFORT_VALUES = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh']);
78
- export const CODEX_DIRECT_SANDBOX_MODES = Object.freeze(['read-only', 'workspace-write']);
78
+ export const CODEX_DIRECT_SANDBOX_MODES = Object.freeze(['read-only', 'workspace-write', 'external']);
79
79
  /** Non-prompting execution only: a requested `on-request` is refused. */
80
80
  export const CODEX_DIRECT_APPROVAL_POLICIES = Object.freeze(['never']);
81
81
  /** Brief transport for the direct profile: piped stdin, never argv. */
@@ -195,6 +195,10 @@ export class CodexAdapter extends ProviderAdapter {
195
195
  };
196
196
  }
197
197
 
198
+ // Worker envNames come from the recorded installation environment, filtered
199
+ // through both registry allowlists. PATH must survive env -i so Codex can
200
+ // find the host's AppArmor-approved bwrap and system shell tools.
201
+
198
202
  /**
199
203
  * Driver-owned argv after the binary for the version-bound direct-shell
200
204
  * profile. Every control is explicit: `--ignore-user-config` (user
@@ -252,7 +256,7 @@ export class CodexAdapter extends ProviderAdapter {
252
256
  '--ignore-user-config',
253
257
  '-m', model,
254
258
  '-c', `model_reasoning_effort=${JSON.stringify(effort)}`,
255
- '-s', sandbox,
259
+ '-s', sandbox === 'external' ? 'danger-full-access' : sandbox,
256
260
  '-C', cwd,
257
261
  '-c', `approval_policy=${JSON.stringify(approval)}`,
258
262
  '--skip-git-repo-check',
@@ -162,12 +162,15 @@ export class MuseAdapter extends ProviderAdapter {
162
162
  }
163
163
  let schema;
164
164
  try { schema = JSON.parse(profile.controls.structuredOutput.mapping); } catch { /* unavailable mapping blocks */ }
165
- if (!Array.isArray(schema) || schema.length !== 2 || schema[0] !== '--output-schema'
166
- || typeof schema[1] !== 'string' || !path.isAbsolute(schema[1]) || schema[1].includes('\0') || schema[1].split(path.sep).includes('..')) {
165
+ const directText = profile.controls.structuredOutput.mapping === '[]'
166
+ && profile.controls.structuredOutput.support === 'unqualified';
167
+ if (!directText && (!Array.isArray(schema) || schema.length !== 2 || schema[0] !== '--output-schema'
168
+ || typeof schema[1] !== 'string' || !path.isAbsolute(schema[1]) || schema[1].includes('\0') || schema[1].split(path.sep).includes('..'))) {
167
169
  throw new Error('Muse requires a host-bound output-schema control mapping');
168
170
  }
169
171
  if (!permissions || !['read-only', 'workspace-write', 'external'].includes(permissions.sandbox)
170
172
  || !Number.isSafeInteger(health?.maxSteps) || health.maxSteps < 1) throw new Error('Muse requires bounded steps and sandbox authority');
173
+ if ((permissions.approval ?? 'never') !== 'never') throw new Error('Muse direct worker approval requires never');
171
174
  // Trust is a host-owned, qualification-bound control, like the schema.
172
175
  // Brief contents and CLI defaults cannot opt into workspace instructions.
173
176
  let trust;
@@ -179,6 +182,7 @@ export class MuseAdapter extends ProviderAdapter {
179
182
  '--workspace', cwd, ...trust, '--approval-mode', 'never', '--approval-judge', 'off', '--max-model-steps', String(health.maxSteps),
180
183
  ...schema, '--prompt-file', briefPath];
181
184
  if (permissions.sandbox === 'read-only') args.push('--disable-write', '--disable-shell');
185
+ if (permissions.sandbox === 'external') args.push('--disable-sandbox');
182
186
  return args;
183
187
  }
184
188
 
@@ -361,4 +365,10 @@ export class MuseAdapter extends ProviderAdapter {
361
365
 
362
366
  registerProvider('muse', () => new MuseAdapter());
363
367
 
368
+ /** Installed worker module contract; legacy registration above remains supported. */
369
+ export const interfaceVersion = 1;
370
+ export function register(factory) {
371
+ factory('muse', () => new MuseAdapter());
372
+ }
373
+
364
374
  export default MuseAdapter;
@@ -123,7 +123,7 @@ export function createWorkerHealthObserver(options = {}) {
123
123
  const replayedKeys = new Set();
124
124
  const events = [];
125
125
  const gaps = [];
126
- let sequence = 0, generation = 0, steps = null, total = 0, lastCheck = started, maxGap = 0;
126
+ let sequence = 0, generation = options.observerGeneration ?? 0, steps = null, total = 0, lastCheck = started, maxGap = 0;
127
127
  let observed = null, activity = null, progress = null, heartbeat = null, handshakeAt = null, stalledAt = null;
128
128
  let state = 'launching', handshake = 'unobserved', confidence = 'unobserved', reasonCode = 'AWAITING_HANDSHAKE';
129
129
  let termination = 'running', transitionCount = 0, live = null, disconnected = false, finalized = false;
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Worker process isolation for Tier 0 direct dispatch (FR-OG-108, #2954).
3
3
  *
4
- * Direct workers must never share the conductor's cgroup: on the titan host
5
- * an oomd kill of one shared cgroup took the conductor session with it.
4
+ * Direct workers default to a separate cgroup: on the titan host an oomd
5
+ * kill of one shared cgroup took the conductor session with it. Explicit
6
+ * native-sandbox policy instead uses an owned process group and the CLI's
7
+ * sandbox, without a cgroup memory limit or descendant kill scope.
6
8
  * This module builds `systemd-run --user` launch arguments (own unit or
7
9
  * scope with `MemoryMax` and `MemorySwapMax=0`, PATH/HOME passthrough,
8
10
  * stdin `/dev/null`) and, where no systemd user manager exists, returns an
@@ -40,6 +42,50 @@ import { buildSystemdClientEnv, resolveSystemdClient, resolveSystemdRun, CONNECT
40
42
 
41
43
  const UNIT_SAFE = /[^A-Za-z0-9:_.@-]/g;
42
44
  const transportBootstrapVariables = ['NODE_OPTIONS', 'NODE_PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'LD_AUDIT', 'LD_DEBUG', 'LD_DEBUG_OUTPUT'];
45
+ const nativeSandboxProbes = new Map();
46
+ let nativeSandboxProbeSequence = 0;
47
+ export const NATIVE_SANDBOX_UNAVAILABLE = 'OG_NATIVE_SANDBOX_UNAVAILABLE';
48
+
49
+ export function workerNeedsNativeNamespaces(adapter, permissions = {}, platform = process.platform) {
50
+ return platform === 'linux' && adapter?.provider === 'codex'
51
+ && (permissions.transport ?? 'shell') === 'shell'
52
+ && ['read-only', 'workspace-write'].includes(permissions.sandbox ?? 'workspace-write');
53
+ }
54
+
55
+ export function nativeSandboxFailure(text) {
56
+ return /loopback: Failed RTM_NEWADDR: Operation not permitted|uid_map[^\n]*Operation not permitted/i.test(text);
57
+ }
58
+
59
+ export function nativeSandboxProbeMessage(result) {
60
+ return `${NATIVE_SANDBOX_UNAVAILABLE}: Native sandbox unavailable: restricted unprivileged user namespaces inside systemd user units (host AppArmor policy may restrict userns). Probe: ${String(result.detail ?? 'failed').replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512)}. Select parallelism.groups.<group>.execution.launchMode="native-sandbox" with a verified enforcing CLI sandbox, or configure a host AppArmor allowance for the systemd worker launch path.`;
61
+ }
62
+
63
+ /** No inference: exercise user/network namespaces through the worker's unit and client environment path. */
64
+ export function probeWorkerNativeSandbox({ spawn = spawnSync, env = process.env, envNames = ['PATH', 'HOME'], cwd,
65
+ cache = spawn === spawnSync ? nativeSandboxProbes : undefined } = {}) {
66
+ const key = JSON.stringify([cwd, [...envNames].sort()]);
67
+ if (cache?.has(key)) return cache.get(key);
68
+ let result;
69
+ try {
70
+ const managerEnvNames = resolveManagerEnvNames({ spawn });
71
+ const command = process.execPath;
72
+ const args = [fileURLToPath(import.meta.url), '--worker-environment', JSON.stringify(unitEnvironmentNames(envNames)),
73
+ '--', '/usr/bin/unshare', '--user', '--map-root-user', '--net', '/usr/bin/true'];
74
+ const argv = buildWorkerIsolationArgs({ unit: proposeWorkerUnitName(`sandbox-probe-${process.pid}-${++nativeSandboxProbeSequence}`),
75
+ command, args, cwd, env, envNames, managerEnvNames, memoryMaxGb: 0.25, attached: true,
76
+ extraProperties: ['RuntimeMaxSec=5s', 'TimeoutStopSec=1s'] });
77
+ const observed = spawn(resolveSystemdRun(), ['--collect', ...argv.slice(1)], {
78
+ encoding: 'utf8', timeout: 8000, maxBuffer: 4096, env: buildSystemdClientEnv({ env, envNames }) });
79
+ result = observed.status === 0 && !observed.error ? { ok: true, mode: 'systemd-unit' }
80
+ : { ok: false, mode: 'systemd-unit', code: NATIVE_SANDBOX_UNAVAILABLE,
81
+ detail: String(observed.error?.message ?? (String(observed.stderr ?? '').trim() || `exit ${observed.status}`)).replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512) };
82
+ } catch (error) {
83
+ result = { ok: false, mode: 'systemd-unit', code: NATIVE_SANDBOX_UNAVAILABLE,
84
+ detail: String(error.message).replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 512) };
85
+ }
86
+ cache?.set(key, result);
87
+ return result;
88
+ }
43
89
 
44
90
  /** Derive a unit-safe `aiwg-worker-<name>-<epoch>` unit name. */
45
91
  export function proposeWorkerUnitName(name, nowSeconds = Math.floor(Date.now() / 1000)) {
@@ -340,6 +386,7 @@ export function launchIsolatedWorker({
340
386
  config,
341
387
  allowDegraded = false,
342
388
  isolation,
389
+ workerGroup,
343
390
  kind = 'unit',
344
391
  attached = false,
345
392
  spawn = spawnSync,
@@ -368,6 +415,15 @@ export function launchIsolatedWorker({
368
415
  return { launched: false, blocked: true, failures: resolvedGate.failures ?? [], gate: resolvedGate };
369
416
  }
370
417
  const detected = isolation ?? detectWorkerIsolation({ spawn });
418
+ const nativeSandbox = detected.mode === 'native-sandbox';
419
+ if (nativeSandbox) {
420
+ const execution = config?.parallelism?.groups?.[workerGroup]?.execution;
421
+ if (execution?.launchMode !== 'native-sandbox' || !['read-only', 'workspace-write'].includes(execution.sandbox)
422
+ || (execution.transport ?? 'shell') !== 'shell' || !attached || !ownedGroup) {
423
+ throw Object.assign(new Error('native-sandbox requires policy opt-in and an owned attached process group with an enforcing native sandbox'),
424
+ { code: 'OG_POLICY_RELAXATION' });
425
+ }
426
+ }
371
427
  if (detected.mode === 'degraded' && !allowDegraded) {
372
428
  return {
373
429
  launched: false,
@@ -376,7 +432,7 @@ export function launchIsolatedWorker({
376
432
  gate: resolvedGate,
377
433
  };
378
434
  }
379
- if (detected.mode === 'degraded') {
435
+ if (detected.mode === 'degraded' || nativeSandbox) {
380
436
  assertAbsolute('worker binary', command);
381
437
  if (cwd !== undefined) assertAbsolute('working directory', cwd);
382
438
  if (stdinPath !== undefined) assertAbsolute('stdin brief', stdinPath);
@@ -385,7 +441,7 @@ export function launchIsolatedWorker({
385
441
  if (!Array.isArray(args) || args.some(token => typeof token !== 'string' || token.includes('\0'))) {
386
442
  throw new Error('Worker isolation argv must be NUL-free strings');
387
443
  }
388
- // Degraded mode has no systemd unit to confine the environment, so this
444
+ // Process-group modes have no systemd unit to confine the environment, so this
389
445
  // module builds the allowlisted environment itself and passes it as the
390
446
  // child's *entire* environment (the `env` option replaces, never merges
391
447
  // with, the current process environment) — the functional equivalent of
@@ -441,12 +497,12 @@ export function launchIsolatedWorker({
441
497
  }
442
498
  const result = _runDispatchCommit(onDispatchCommit, () => {
443
499
  const child = spawnAsync(launchCommand, launchArgs, launchOptions);
444
- onSpawnAttempt?.({ mode: 'degraded', child, pid: child.pid });
500
+ onSpawnAttempt?.({ mode: detected.mode, child, pid: child.pid });
445
501
  const outcome = {
446
502
  launched: Number.isInteger(child.pid) && child.pid > 0,
447
- mode: 'degraded', child, pid: child.pid,
503
+ mode: detected.mode, child, pid: child.pid,
448
504
  ...(attached ? { transportSupervisor: true } : {}),
449
- diagnostic: `launched without resource confinement: ${detected.reason}`,
505
+ diagnostic: nativeSandbox ? 'Native CLI sandbox boundary; no cgroup resource confinement' : `launched without resource confinement: ${detected.reason}`,
450
506
  gate: resolvedGate,
451
507
  };
452
508
  // Preserve launch errors even when callers attach listeners later.
@@ -661,8 +717,8 @@ export function runWorkerTransport({ command, args, environment, record,
661
717
  }
662
718
 
663
719
  // Private attached transport entry, never a public CLI or legacy Ralph route.
664
- // The systemd unit owns this supervisor and its worker; cgroup exit evidence
665
- // includes descendants. Only the actual worker receives the allowed values.
720
+ // The unit or launcher-owned process group owns this supervisor and worker.
721
+ // Only unit cgroup exit evidence covers descendants. The worker receives only allowed values.
666
722
  if (process.argv[1] === fileURLToPath(import.meta.url) && process.argv[2] === '--worker-environment') {
667
723
  process.umask(0o077);
668
724
  const names = JSON.parse(process.argv[3]);
@@ -843,7 +843,10 @@ async function captureSizes(directory) {
843
843
  /** Readers never create a monitor, walk a worktree or execute a subprocess. */
844
844
  export async function sampleWorkerStatus(options, memory = new Map()) {
845
845
  const now = options.now?.() ?? Date.now(), sampledAt = new Date(now).toISOString();
846
- const began = monotonicMs();
846
+ // Use one monotonic source for worker age and the sampling budget. Injected
847
+ // lifecycle traces must not expire because the host descheduled their I/O.
848
+ const monotonicNow = options.monotonicNow ?? monotonicMs;
849
+ const began = monotonicNow();
847
850
  const report = { version: 1, sampledAt, scope: 'direct-conductor', observerCoverage: 'unobserved',
848
851
  workers: [], breakers: [], nextCheckAt: null, complete: true, findings: [], exitCode: 0 };
849
852
  let root;
@@ -853,10 +856,10 @@ export async function sampleWorkerStatus(options, memory = new Map()) {
853
856
  : await boundedEntries(root, STATUS_LIMITS.directories);
854
857
  report.complete = listing.complete;
855
858
  if (!listing.complete) { report.findings.push('SELECTION_LIMIT: narrow --runs-root'); report.exitCode = 2; }
856
- const sampleTime = options.monotonicNow?.() ?? monotonicMs();
859
+ const sampleTime = monotonicNow();
857
860
  const bootId = options.bootId ?? (() => { try { return readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim(); } catch { return null; } })();
858
861
  for (const entry of listing.entries) {
859
- if (monotonicMs() - began >= STATUS_LIMITS.sampleMs) {
862
+ if (monotonicNow() - began >= STATUS_LIMITS.sampleMs) {
860
863
  report.complete = false; report.findings.push('SAMPLE_BUDGET: narrow selection'); if (report.exitCode !== 1) report.exitCode = 2; break;
861
864
  }
862
865
  let directory;
@@ -894,7 +897,7 @@ export async function sampleWorkerStatus(options, memory = new Map()) {
894
897
  row.findings.push(code); report.findings.push(`${row.invocationId}: ${code}`);
895
898
  }
896
899
  }
897
- if (monotonicMs() - began >= STATUS_LIMITS.sampleMs) {
900
+ if (monotonicNow() - began >= STATUS_LIMITS.sampleMs) {
898
901
  report.complete = false; report.findings.push('SAMPLE_OVERRUN: observation unavailable; narrow selection');
899
902
  row.health.coverage = 'degraded'; row.health.unavailableSignals.push('observer-continuity'); row.nextAction = 'RESTORE_OBSERVATION';
900
903
  }