@aiwg/cli 2026.10.5 → 2026.10.7
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.
- package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +432 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +238 -0
- package/dist/src/discovery/plan-apply.js +567 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { importImpl } from '../../_resolve-impl.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Warp Terminal Provider
|
|
3
4
|
*
|
|
@@ -48,6 +49,8 @@ import {
|
|
|
48
49
|
collectFrameworkArtifacts
|
|
49
50
|
} from './base.mjs';
|
|
50
51
|
|
|
52
|
+
import { writeContextFileSync } from './context-write.mjs';
|
|
53
|
+
|
|
51
54
|
// ============================================================================
|
|
52
55
|
// Provider Configuration
|
|
53
56
|
// ============================================================================
|
|
@@ -206,7 +209,7 @@ export async function deployWarp(targetDir, srcRoot, opts) {
|
|
|
206
209
|
// Preserve operator sections even for a forced artifact refresh.
|
|
207
210
|
const output = mergeWarpMd(existing, content, false);
|
|
208
211
|
ensureDir(targetDir, opts.dryRun);
|
|
209
|
-
if (!opts.dryRun)
|
|
212
|
+
if (!opts.dryRun) writeContextFileSync(dest, output);
|
|
210
213
|
|
|
211
214
|
}
|
|
212
215
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { importImpl } from '../../_resolve-impl.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Devin Desktop Provider (Windsurf compatibility adapter)
|
|
3
4
|
*
|
|
@@ -30,6 +31,7 @@ let fs;
|
|
|
30
31
|
try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
|
|
31
32
|
import path from 'path';
|
|
32
33
|
import {
|
|
34
|
+
withRuleDeployment,
|
|
33
35
|
ensureDir,
|
|
34
36
|
listMdFiles,
|
|
35
37
|
listMdFilesRecursive,
|
|
@@ -54,6 +56,8 @@ import {
|
|
|
54
56
|
deploySoulCompanions
|
|
55
57
|
} from './base.mjs';
|
|
56
58
|
|
|
59
|
+
import { writeContextFileSync } from './context-write.mjs';
|
|
60
|
+
|
|
57
61
|
// ============================================================================
|
|
58
62
|
// Provider Configuration
|
|
59
63
|
// ============================================================================
|
|
@@ -234,6 +238,7 @@ export function mapModel(shorthand, modelCfg, modelsConfig) {
|
|
|
234
238
|
* Generate AGENTS.md for Windsurf with all agents aggregated
|
|
235
239
|
*/
|
|
236
240
|
export function generateAgentsMd(files, destPath, opts) {
|
|
241
|
+
if (opts.contextFiles === false) return;
|
|
237
242
|
const { dryRun } = opts;
|
|
238
243
|
|
|
239
244
|
const lines = [];
|
|
@@ -283,7 +288,7 @@ export function generateAgentsMd(files, destPath, opts) {
|
|
|
283
288
|
if (dryRun) {
|
|
284
289
|
console.log(`[dry-run] Would write AGENTS.md with ${agents.length} agents`);
|
|
285
290
|
} else {
|
|
286
|
-
|
|
291
|
+
writeContextFileSync(destPath, output, 'utf8');
|
|
287
292
|
console.log(`Created AGENTS.md with ${agents.length} agents at ${path.relative(process.cwd(), destPath)}`);
|
|
288
293
|
}
|
|
289
294
|
|
|
@@ -407,7 +412,7 @@ export function generateWindsurfRules(srcRoot, target, opts) {
|
|
|
407
412
|
console.log(`[dry-run] Would write .windsurf/rules/aiwg-orchestration.md (trigger: always_on)`);
|
|
408
413
|
} else {
|
|
409
414
|
ensureDir(rulesDir);
|
|
410
|
-
|
|
415
|
+
writeContextFileSync(orchestrationPath, output, 'utf8');
|
|
411
416
|
console.log(`Created .windsurf/rules/aiwg-orchestration.md (trigger: always_on)`);
|
|
412
417
|
}
|
|
413
418
|
|
|
@@ -428,7 +433,7 @@ export function generateWindsurfRules(srcRoot, target, opts) {
|
|
|
428
433
|
if (dryRun) {
|
|
429
434
|
console.log(`[dry-run] Would write .windsurfrules (deprecated stub)`);
|
|
430
435
|
} else {
|
|
431
|
-
|
|
436
|
+
writeContextFileSync(stubPath, stubLines.join('\n'), 'utf8');
|
|
432
437
|
console.log(`Created .windsurfrules (deprecated stub → .windsurf/rules/aiwg-orchestration.md)`);
|
|
433
438
|
}
|
|
434
439
|
}
|
|
@@ -507,6 +512,7 @@ export function deploySkills(skillDirs, targetDir, opts) {
|
|
|
507
512
|
* Deploy rules as discrete files with trigger: always_on frontmatter injected
|
|
508
513
|
*/
|
|
509
514
|
export function deployRules(ruleFiles, targetDir, opts) {
|
|
515
|
+
if (opts.ruleDelivery) return [];
|
|
510
516
|
const destDir = path.join(targetDir, paths.rules);
|
|
511
517
|
ensureDir(destDir, opts.dryRun);
|
|
512
518
|
cleanupOldRuleFiles(destDir, opts);
|
|
@@ -534,6 +540,10 @@ export function getFileExtension(type) {
|
|
|
534
540
|
// ============================================================================
|
|
535
541
|
|
|
536
542
|
export async function deploy(opts) {
|
|
543
|
+
return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function deploySelected(opts) {
|
|
537
547
|
const {
|
|
538
548
|
srcRoot,
|
|
539
549
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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',
|
|
@@ -308,6 +308,9 @@ const HOST_CODES = new Set(['RESOURCE', 'NOT_FOUND', 'SANDBOX']);
|
|
|
308
308
|
const PROVIDER_CODES = new Set(['NETWORK']);
|
|
309
309
|
const INSTALLATION_CODES = new Set(['AUTH']);
|
|
310
310
|
const MODEL_CODES = new Set(['MODEL']);
|
|
311
|
+
const STATUS_REASON_CODES = new Set(['NOT_FOUND', 'SPAWN_REFUSED', 'RATE_LIMIT', 'RESOURCE', 'SANDBOX',
|
|
312
|
+
'NETWORK', 'AUTH', 'MODEL', 'UNKNOWN']);
|
|
313
|
+
export const RECOVERY_STATUS_LIMIT = 32;
|
|
311
314
|
|
|
312
315
|
export const scopeKeyFor = Object.freeze({
|
|
313
316
|
invocation: (id) => `invocation:${id ?? 'unknown'}`,
|
|
@@ -511,6 +514,7 @@ export class ScopedFailureBreaker extends EventEmitter {
|
|
|
511
514
|
this._scopes = new Map();
|
|
512
515
|
this._outstanding = new Map();
|
|
513
516
|
this._hostFailureFacts = [];
|
|
517
|
+
this._transitions = [];
|
|
514
518
|
this._canarySeq = 0;
|
|
515
519
|
this._observedInvocations = new Set();
|
|
516
520
|
this.canariesObserved = 0;
|
|
@@ -1022,6 +1026,10 @@ export class ScopedFailureBreaker extends EventEmitter {
|
|
|
1022
1026
|
const from = rec.status;
|
|
1023
1027
|
if (from === to) return;
|
|
1024
1028
|
rec.status = to;
|
|
1029
|
+
if (rec.scopeKey.length <= 256) this._transitions.push({ scopeKey: rec.scopeKey, scope: rec.scope, from, to,
|
|
1030
|
+
reasons: [...rec.evidence.keys()].filter(code => STATUS_REASON_CODES.has(code)).slice(0, 8),
|
|
1031
|
+
atMs: this.nowMs(), nextEligibleAtMs: to === 'closed' ? null : rec.nextProbeAtMs });
|
|
1032
|
+
if (this._transitions.length > RECOVERY_STATUS_LIMIT) this._transitions.shift();
|
|
1025
1033
|
this.emit('transition', { scopeKey: rec.scopeKey, from, to, action,
|
|
1026
1034
|
distinctFailures: new Set(rec.failures.map(failure => failure.invocationId)).size, reasons: [...rec.evidence.keys()],
|
|
1027
1035
|
nextEligibleAtMs: rec.nextProbeAtMs, resetAtMs: rec.resetAtMs,
|
|
@@ -1454,6 +1462,23 @@ export class ScopedFailureBreaker extends EventEmitter {
|
|
|
1454
1462
|
};
|
|
1455
1463
|
}
|
|
1456
1464
|
|
|
1465
|
+
/** Pure status projection: never sweep reservations, probe, or advance state. */
|
|
1466
|
+
recoveryStatus() {
|
|
1467
|
+
const recentKeys = [...new Set(this._transitions.map(event => event.scopeKey).reverse())];
|
|
1468
|
+
const active = [...this._scopes.values()].filter(rec => rec.status !== 'closed');
|
|
1469
|
+
const keys = [...new Set([...active.map(rec => rec.scopeKey), ...recentKeys])];
|
|
1470
|
+
const scopes = keys.filter(key => key.length <= 256).slice(0, RECOVERY_STATUS_LIMIT).map(scopeKey => {
|
|
1471
|
+
const rec = this._scopes.get(scopeKey);
|
|
1472
|
+
const last = this._transitions.findLast(event => event.scopeKey === scopeKey);
|
|
1473
|
+
return { scopeKey, scope: rec?.scope ?? last.scope, state: rec?.status ?? last.to,
|
|
1474
|
+
reasons: rec ? [...rec.evidence.keys()].filter(code => STATUS_REASON_CODES.has(code)).slice(0, 8) : [...last.reasons],
|
|
1475
|
+
nextEligibleAtMs: !rec || rec.status === 'closed' ? null : rec.nextProbeAtMs,
|
|
1476
|
+
canaryInFlight: rec?.activeCanary != null };
|
|
1477
|
+
});
|
|
1478
|
+
return { observedAtMs: this.nowMs(), scopes, omittedScopeCount: Math.max(0, keys.length - scopes.length),
|
|
1479
|
+
transitions: this._transitions.map(event => ({ ...event, reasons: [...event.reasons] })) };
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1457
1482
|
/** Correlate launcher-observed host faults across resolved inference backends.
|
|
1458
1483
|
* Replay the first fault once corroborated, preserving the three-distinct
|
|
1459
1484
|
* threshold even when the first backend alone could not establish scope.
|
|
@@ -1550,6 +1575,7 @@ export class ScopedFailureBreaker extends EventEmitter {
|
|
|
1550
1575
|
|
|
1551
1576
|
destroy() {
|
|
1552
1577
|
this._hostFailureFacts = [];
|
|
1578
|
+
this._transitions = [];
|
|
1553
1579
|
for (const rec of this._scopes.values()) rec.breaker.destroy();
|
|
1554
1580
|
this._scopes.clear();
|
|
1555
1581
|
this._outstanding.clear();
|
|
@@ -3,6 +3,7 @@ import { runRecoveryProbe } from './recovery-probe.mjs';
|
|
|
3
3
|
/** Recovery evidence observed outside worker output by the launch owner. */
|
|
4
4
|
const observations = new WeakMap();
|
|
5
5
|
const failureHints = new WeakMap();
|
|
6
|
+
const assessed = new WeakSet();
|
|
6
7
|
|
|
7
8
|
export function recordLauncherRecoveryHint(result, reasonCode) {
|
|
8
9
|
failureHints.set(result, reasonCode);
|
|
@@ -23,9 +24,10 @@ export function launcherRecoveryEvidence(result) {
|
|
|
23
24
|
*/
|
|
24
25
|
export async function observeLauncherRecoveryOutcome({ invocation, result, installation, breaker,
|
|
25
26
|
failureHint = '', beforeRequest, onDispatch, fetchImpl = globalThis.fetch, wallNowMs = Date.now, timeoutMs = 10_000 } = {}) {
|
|
26
|
-
if (!result || result.attemptOutcome !== 'failed' || result.cancelled) return;
|
|
27
|
+
if (!result || result.attemptOutcome !== 'failed' || result.cancelled || assessed.has(result)) return;
|
|
27
28
|
const backend = invocation?.selected?.inferenceProvider;
|
|
28
29
|
if (HOST_ERRORS.has(result.errorCode) || HOST_RESULTS.has(result.nativeResult)) {
|
|
30
|
+
assessed.add(result);
|
|
29
31
|
const corroboratedBackends = breaker?.observeHostFailure({ invocationId: result.invocationId,
|
|
30
32
|
inferenceProvider: backend, reasonCode: 'RESOURCE' }) ?? [];
|
|
31
33
|
if (corroboratedBackends.length >= 2) observations.set(result, {
|
|
@@ -43,6 +45,10 @@ export async function observeLauncherRecoveryOutcome({ invocation, result, insta
|
|
|
43
45
|
if (!await beforeRequest()) return;
|
|
44
46
|
const requestTimeoutMs = remaining();
|
|
45
47
|
if (!Number.isFinite(requestTimeoutMs) || requestTimeoutMs <= 0) return;
|
|
48
|
+
// One diagnostic request per attempt, including unsuccessful diagnostics.
|
|
49
|
+
// Re-reading a result cannot consume allowance or create fresh evidence.
|
|
50
|
+
if (assessed.has(result)) return;
|
|
51
|
+
assessed.add(result);
|
|
46
52
|
const observed = await runRecoveryProbe({ driver: { qualification: { installation } }, invocation: invocation ?? { transport: 'shell' },
|
|
47
53
|
timeoutMs: Math.min(10_000, requestTimeoutMs), maxOutputBytes: 65_536, onDispatch, fetchImpl, wallNowMs });
|
|
48
54
|
if (observed.reason === 'probe-rate-limited') {
|
|
@@ -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
|
-
|
|
166
|
-
|
|
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;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { RecoveryPolicy } from './consecutive-failure-circuit-breaker.mjs';
|
|
2
|
+
|
|
1
3
|
/** A recovery probe is a conductor-owned request, never an uncontained worker. */
|
|
2
4
|
export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000, maxOutputBytes = 65_536,
|
|
3
|
-
onDispatch, fetchImpl = globalThis.fetch, wallNowMs = Date.now }) {
|
|
5
|
+
onDispatch, fetchImpl = globalThis.fetch, wallNowMs = Date.now, nowMs = () => performance.now() }) {
|
|
4
6
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 10_000
|
|
5
7
|
|| !Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1 || maxOutputBytes > 65_536) {
|
|
6
8
|
throw new RangeError('Recovery probe exceeds the 10s/64KiB bounds');
|
|
@@ -19,6 +21,7 @@ export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000,
|
|
|
19
21
|
return failed('probe-endpoint-unavailable', { operatorIntervention: true });
|
|
20
22
|
}
|
|
21
23
|
const controller = new AbortController();
|
|
24
|
+
const deadlineMs = nowMs() + timeoutMs;
|
|
22
25
|
const timer = setTimeout(() => controller.abort(), Math.max(1, Math.floor(timeoutMs)));
|
|
23
26
|
let aborted;
|
|
24
27
|
const timeout = new Promise((_, reject) => {
|
|
@@ -30,14 +33,19 @@ export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000,
|
|
|
30
33
|
timeout.catch(() => {});
|
|
31
34
|
try {
|
|
32
35
|
await onDispatch?.();
|
|
36
|
+
if (nowMs() >= deadlineMs) controller.abort();
|
|
33
37
|
if (controller.signal.aborted) return failed('probe-timeout');
|
|
34
38
|
try {
|
|
35
39
|
// HEAD has no task, credentials, inference body or worker filesystem.
|
|
36
40
|
// A successful endpoint check permits only one subsequent task canary.
|
|
37
41
|
const response = await Promise.race([fetchImpl(endpoint, { method: 'HEAD', redirect: 'error', signal: controller.signal }), timeout]);
|
|
38
42
|
let headerBytes = 16 + Buffer.byteLength(response.statusText ?? '');
|
|
39
|
-
for (const [name, value] of response.headers) headerBytes += Buffer.byteLength(name) + Buffer.byteLength(value) + 4;
|
|
40
43
|
try {
|
|
44
|
+
if (nowMs() >= deadlineMs) { controller.abort(); return failed('probe-timeout'); }
|
|
45
|
+
for (const [name, value] of response.headers) {
|
|
46
|
+
headerBytes += Buffer.byteLength(name) + Buffer.byteLength(value) + 4;
|
|
47
|
+
if (headerBytes > maxOutputBytes) return failed('probe-output-limit');
|
|
48
|
+
}
|
|
41
49
|
if (headerBytes > maxOutputBytes) return failed('probe-output-limit');
|
|
42
50
|
if (response.body) return failed('probe-body-unexpected');
|
|
43
51
|
const success = response.status >= 200 && response.status <= 299;
|
|
@@ -45,7 +53,8 @@ export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000,
|
|
|
45
53
|
if (response.status === 429) {
|
|
46
54
|
const after = response.headers.get('retry-after');
|
|
47
55
|
const seconds = after != null && /^\d+(?:\.\d+)?$/.test(after.trim()) ? Number(after) : null;
|
|
48
|
-
const date = seconds === null &&
|
|
56
|
+
const date = seconds === null && /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(after ?? '')
|
|
57
|
+
? Date.parse(after) : NaN;
|
|
49
58
|
const reset = response.headers.get('x-ratelimit-reset');
|
|
50
59
|
const resetEpoch = reset != null && /^\d+(?:\.\d+)?$/.test(reset.trim()) ? Number(reset) * 1000 : NaN;
|
|
51
60
|
const resetDelay = response.headers.get('ratelimit-reset');
|
|
@@ -53,7 +62,8 @@ export async function runRecoveryProbe({ driver, invocation, timeoutMs = 10_000,
|
|
|
53
62
|
Number.isFinite(resetEpoch) ? (resetEpoch - wallNowMs()) / 1000 : null,
|
|
54
63
|
resetDelay != null && /^\d+(?:\.\d+)?$/.test(resetDelay.trim()) ? Number(resetDelay) : null]
|
|
55
64
|
.filter(value => Number.isFinite(value) && value >= 0);
|
|
56
|
-
return failed('probe-rate-limited', { retryAfterSeconds: delays.length
|
|
65
|
+
return failed('probe-rate-limited', { retryAfterSeconds: delays.length
|
|
66
|
+
? Math.min(RecoveryPolicy.maxRetryAfterSeconds, Math.max(...delays)) : null });
|
|
57
67
|
}
|
|
58
68
|
return failed('probe-failed', { status: response.status });
|
|
59
69
|
} finally {
|
|
@@ -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
|
|
5
|
-
*
|
|
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
|
-
//
|
|
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:
|
|
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:
|
|
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
|
|
665
|
-
//
|
|
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]);
|