@aiwg/cli 2026.10.5 → 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.
- 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 +3 -1
- 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/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-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/cli/handlers/decision.js +19 -1
- 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/local-executor.js +5 -3
- 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 +428 -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 +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -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/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -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 +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- 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 +233 -0
- package/dist/src/discovery/plan-apply.js +571 -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/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-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/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/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- 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 +17 -4
- 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/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/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 +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- 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 +114 -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 +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- 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 +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- 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 +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- 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 +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- 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/muse-adapter.mjs +12 -2
- 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 +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- 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
|
-
|
|
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',
|
|
@@ -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;
|
|
@@ -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]);
|
|
@@ -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
|
-
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
}
|