@aiwg/cli 2026.10.10 → 2026.10.12
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/providers/model-capabilities.v1.json +78 -14
- package/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +287 -31
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +18 -28
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-capabilities.v1.json +78 -14
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -539
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +30 -1
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +44 -6
- package/dist/src/sessions/contracts.js +20 -1
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +9 -232
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +88 -9
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +7 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
- package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +51 -96
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -29,7 +29,7 @@ import { loadCliCommandsContribution, registerCliCommands, registerHooks, } from
|
|
|
29
29
|
import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
|
|
30
30
|
import * as ui from '../ui.js';
|
|
31
31
|
import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir, getConfigPath } from '../../config/aiwg-config.js';
|
|
32
|
-
import { appendGitignore } from '../../config/gitignore.js';
|
|
32
|
+
import { AIWG_RUNTIME_PATTERNS, appendGitignore } from '../../config/gitignore.js';
|
|
33
33
|
import { getLogger } from '../log.js';
|
|
34
34
|
import { installCockpit } from './cockpit.js';
|
|
35
35
|
import { initHandler } from './init.js';
|
|
@@ -64,6 +64,7 @@ const PREPARABLE_BUILTIN_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'herm
|
|
|
64
64
|
// Distinct from agentsmith (which creates subagent personas).
|
|
65
65
|
import { generate as generateContextFiles, discoverDeployedArtifacts, } from '../../smiths/context-pipeline/index.js';
|
|
66
66
|
import { verifyModelWrapperDeployment } from '../../models/wrapper-deployment.js';
|
|
67
|
+
import { countDeployedBundleFiles, countDeployedBundleRules } from './use-bundle-counts.js';
|
|
67
68
|
import { aggregateUseDeploymentResult, buildDryRunUseResult, renderUseDeploymentResult, verifyProviderDeployment, } from '../services/deployment-verification.js';
|
|
68
69
|
import { finalizeProviderTransformationReceipt, providerReceiptHasLocalSources, sourceVerificationsFromSignedWebRelease, } from '../../providers/transformation-receipt-integration.js';
|
|
69
70
|
import { loadResourceTrustRootFile, resolveWebRelease, } from '../../resources/web-release.js';
|
|
@@ -501,11 +502,19 @@ function getProviderKernelSkillsPath(provider) {
|
|
|
501
502
|
const PROVIDER_GENERATED_GITIGNORE_PATTERNS = {
|
|
502
503
|
codex: ['.codex/', '.agents/'],
|
|
503
504
|
};
|
|
504
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Patterns `aiwg use` ensures in the project's .gitignore on every deploy:
|
|
507
|
+
* the AIWG runtime subdirs doctor expects, plus any provider-generated dirs.
|
|
508
|
+
* Append-only via appendGitignore — operator content is never rewritten (#3635).
|
|
509
|
+
*/
|
|
510
|
+
export function gitignorePatternsForDeploy(provider) {
|
|
511
|
+
return [...AIWG_RUNTIME_PATTERNS, ...(PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider] ?? [])];
|
|
512
|
+
}
|
|
513
|
+
export async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
|
|
505
514
|
if (opts.dryRun)
|
|
506
515
|
return;
|
|
507
|
-
const patterns =
|
|
508
|
-
if (
|
|
516
|
+
const patterns = gitignorePatternsForDeploy(provider);
|
|
517
|
+
if (patterns.length === 0)
|
|
509
518
|
return;
|
|
510
519
|
try {
|
|
511
520
|
const result = await appendGitignore(projectRoot, patterns);
|
|
@@ -1020,24 +1029,6 @@ async function listBundleMdStems(bundlePath, subdir) {
|
|
|
1020
1029
|
return [];
|
|
1021
1030
|
}
|
|
1022
1031
|
}
|
|
1023
|
-
async function countDeployedBundleFiles(bundlePath, subdir, target, deployPath, extensions) {
|
|
1024
|
-
if (!deployPath)
|
|
1025
|
-
return 0;
|
|
1026
|
-
const stems = await listBundleMdStems(bundlePath, subdir);
|
|
1027
|
-
if (stems.length === 0)
|
|
1028
|
-
return 0;
|
|
1029
|
-
const destDir = resolveDeployPath(target, deployPath);
|
|
1030
|
-
let count = 0;
|
|
1031
|
-
for (const stem of stems) {
|
|
1032
|
-
for (const ext of extensions) {
|
|
1033
|
-
if (await fileExists(path.join(destDir, `${stem}${ext}`))) {
|
|
1034
|
-
count++;
|
|
1035
|
-
break;
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return count;
|
|
1040
|
-
}
|
|
1041
1032
|
async function listBundleSkillNameCandidates(bundlePath) {
|
|
1042
1033
|
const skillsRoot = path.join(bundlePath, 'skills');
|
|
1043
1034
|
try {
|
|
@@ -1099,7 +1090,7 @@ async function countBundleDeployedArtifacts(bundlePath, target, provider) {
|
|
|
1099
1090
|
agents: await countDeployedBundleFiles(bundlePath, 'agents', target, paths.agents, ['.md', '.toml']),
|
|
1100
1091
|
commands: await countDeployedBundleFiles(bundlePath, 'commands', target, paths.commands, ['.md']),
|
|
1101
1092
|
skills: await countDeployedBundleSkills(bundlePath, target, provider),
|
|
1102
|
-
rules: await
|
|
1093
|
+
rules: await countDeployedBundleRules(bundlePath, target, paths.rules),
|
|
1103
1094
|
};
|
|
1104
1095
|
}
|
|
1105
1096
|
const SKILL_SUPPORT_REFERENCE = /(?:^|[\s`('"\[])((?:templates|references|scripts|assets)\/[A-Za-z0-9._@/+\-]+)(?=$|[\s`)'"\],:;])/gm;
|
|
@@ -3513,11 +3504,10 @@ export class UseHandler {
|
|
|
3513
3504
|
try {
|
|
3514
3505
|
const manifestPath = path.join(addonSource, 'manifest.json');
|
|
3515
3506
|
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
3516
|
-
//
|
|
3517
|
-
//
|
|
3518
|
-
//
|
|
3519
|
-
|
|
3520
|
-
const counts = await countBundleSourceArtifacts(addonSource);
|
|
3507
|
+
// Record only artifacts actually written for this provider. Bridge-only
|
|
3508
|
+
// providers (e.g. project-scope grokbot) write zero native files,
|
|
3509
|
+
// so source-manifest counts would leave phantom registry entries.
|
|
3510
|
+
const counts = await countBundleDeployedArtifacts(addonSource, target, provider);
|
|
3521
3511
|
const updated = updateInstalled(config, framework, provider, counts, {
|
|
3522
3512
|
version: manifest.version ?? (await getVersionInfo()).version,
|
|
3523
3513
|
source: 'bundled',
|
|
@@ -13,6 +13,7 @@ import fs from 'fs';
|
|
|
13
13
|
import fsp from 'fs/promises';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { createScriptRunner } from './script-runner.js';
|
|
16
|
+
import { DOCTOR_HELP } from './doctor-help.js';
|
|
16
17
|
import { getFrameworkRoot } from '../../channel/manager.mjs';
|
|
17
18
|
import { updateInstallation } from '../../update/service.mjs';
|
|
18
19
|
import { useHandler as useFrameworkHandler } from './use.js';
|
|
@@ -494,30 +495,6 @@ async function runUserScopeDoctor(verbose) {
|
|
|
494
495
|
message: lines.join('\n') + '\n',
|
|
495
496
|
};
|
|
496
497
|
}
|
|
497
|
-
const DOCTOR_HELP = `aiwg doctor — check AIWG installation health
|
|
498
|
-
|
|
499
|
-
Usage:
|
|
500
|
-
aiwg doctor [options]
|
|
501
|
-
aiwg -doctor [options]
|
|
502
|
-
aiwg --doctor [options]
|
|
503
|
-
|
|
504
|
-
Options:
|
|
505
|
-
--deployment Verify configured provider deployments
|
|
506
|
-
--provider <name> Limit deployment verification to one provider
|
|
507
|
-
--bundle <name> Limit deployment verification to one bundle
|
|
508
|
-
--scope <scope> Verification scope: project or user
|
|
509
|
-
--user Alias for --scope user
|
|
510
|
-
--qualify With --probe-workers, record installation and configured model/role approval
|
|
511
|
-
--yes Confirm worker qualification without a terminal
|
|
512
|
-
--dry-run With --probe-workers, preview qualification without writes
|
|
513
|
-
--cli NAME Limit worker probes/qualification to one configured CLI
|
|
514
|
-
--decision-offload Show offline advisory decision readiness and adoption report
|
|
515
|
-
--probe-workers Run bounded worker CLI version probes (default: config-only, no subprocess)
|
|
516
|
-
--project-local Include project-local artifact diagnostics
|
|
517
|
-
--json Emit machine-readable output (with --probe-workers: one worker-probe record per CLI; with --deployment: deployment verification; with --decision-offload: offline snapshot)
|
|
518
|
-
--verbose, -v Include verbose diagnostics
|
|
519
|
-
--help, -h Show this help
|
|
520
|
-
`;
|
|
521
498
|
/**
|
|
522
499
|
* Handler for doctor command
|
|
523
500
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateHitlPolicy } from '../policy/hitl-validation.js';
|
|
1
2
|
import { normalizeAiwgConfigCandidate } from './config-normalization.js';
|
|
2
3
|
import { atomicConfigWrite, withWorkerWriteLock } from './worker-write.js';
|
|
3
4
|
import { workerConfigDigest } from './worker-config-digest.js';
|
|
@@ -979,6 +980,9 @@ export async function readAiwgConfigCandidate(projectDir) {
|
|
|
979
980
|
if (workspaceErrors.length > 0) {
|
|
980
981
|
throw new Error(`Invalid .aiwg/aiwg.config:\n${workspaceErrors.join('\n')}`);
|
|
981
982
|
}
|
|
983
|
+
const hitlErrors = validateHitlPolicy(parsed.authorization?.hitl);
|
|
984
|
+
if (hitlErrors.length)
|
|
985
|
+
throw new Error(hitlErrors.join("\n"));
|
|
982
986
|
const authorizationErrors = parsed.authorization
|
|
983
987
|
? validateAuthorization(parsed.authorization).filter(item => item.severity === 'error')
|
|
984
988
|
: [];
|
|
@@ -1050,6 +1054,9 @@ async function writeAiwgConfigUnlocked(projectDir, config) {
|
|
|
1050
1054
|
throw error;
|
|
1051
1055
|
}
|
|
1052
1056
|
}
|
|
1057
|
+
const hitlErrors = validateHitlPolicy(config.authorization?.hitl);
|
|
1058
|
+
if (hitlErrors.length)
|
|
1059
|
+
throw new Error(hitlErrors.join("\n"));
|
|
1053
1060
|
const pinConfigs = loadRulePinConfigs(projectDir, config);
|
|
1054
1061
|
const currentCapabilities = new Set(installedRuleCapabilities(pinConfigs));
|
|
1055
1062
|
const removed = previous ? installedRuleCapabilities({ ...pinConfigs, project: previous }).filter(id => !currentCapabilities.has(id)) : [];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Exposes HITL inspection, validation, preview, enable/revoke and signed receipts under config/steward.
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { AiwgError, EXIT_CODES } from '../cli/errors.js';
|
|
5
|
+
import { getProjectDir, readAiwgConfig, writeAiwgConfig } from './aiwg-config.js';
|
|
6
|
+
import { ensureAuthorization, loadHitlLayers } from '../policy/hitl-config.js';
|
|
7
|
+
import { resolveHitlAuthorization } from '../policy/hitl.js';
|
|
8
|
+
import { validateHitlPolicy } from '../policy/hitl-validation.js';
|
|
9
|
+
export const authorizationUsage = `aiwg config authorization (also aiwg steward authorization)
|
|
10
|
+
inspect | validate [--target DIRECTORY]
|
|
11
|
+
preview --action-file FILE [--target DIRECTORY]
|
|
12
|
+
enable --policy-file FILE [--target DIRECTORY]
|
|
13
|
+
revoke [--profile NAME] [--target DIRECTORY]
|
|
14
|
+
receipt --action-file FILE [--target DIRECTORY]
|
|
15
|
+
Preview is read-only. Receipt writes the decision to the existing signed effect ledger;
|
|
16
|
+
it does not perform the proposed action. Action files cannot supply session authorization.`;
|
|
17
|
+
const usageError = (message) => new AiwgError({ code: 'ERR_AUTHORIZATION_USAGE', message, exitCode: EXIT_CODES.USAGE });
|
|
18
|
+
async function jsonFile(file) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw usageError('Authorization JSON input could not be read or parsed');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function authorizationCli(args, cwd = process.cwd()) {
|
|
27
|
+
if (!args.length || args.includes('--help') || args[0] === 'help') {
|
|
28
|
+
console.log(authorizationUsage);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const [operation, ...rest] = args;
|
|
32
|
+
const flags = {};
|
|
33
|
+
const allowed = ['--target', ...(operation === 'enable' ? ['--policy-file'] : []),
|
|
34
|
+
...(['preview', 'receipt'].includes(operation) ? ['--action-file'] : []), ...(operation === 'revoke' ? ['--profile'] : [])];
|
|
35
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
36
|
+
if (!allowed.includes(rest[i]) || !rest[i + 1] || rest[i + 1].startsWith('--') || flags[rest[i]])
|
|
37
|
+
throw usageError('Invalid authorization option');
|
|
38
|
+
flags[rest[i]] = rest[i + 1];
|
|
39
|
+
}
|
|
40
|
+
const target = resolve(getProjectDir({ cwd }, rest));
|
|
41
|
+
if (operation === 'inspect' || operation === 'validate') {
|
|
42
|
+
const loaded = await loadHitlLayers(target);
|
|
43
|
+
console.log(JSON.stringify(operation === 'inspect' ? loaded : { valid: true, layers: loaded.layers.length }, null, 2));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (operation === 'preview' || operation === 'receipt') {
|
|
47
|
+
if (!flags['--action-file'])
|
|
48
|
+
throw usageError('--action-file is required');
|
|
49
|
+
const action = await jsonFile(resolve(cwd, flags['--action-file']));
|
|
50
|
+
const loaded = await loadHitlLayers(target);
|
|
51
|
+
const context = { now: new Date().toISOString(), layers: loaded.layers };
|
|
52
|
+
if (operation === 'preview')
|
|
53
|
+
console.log(JSON.stringify(resolveHitlAuthorization(action, context), null, 2));
|
|
54
|
+
else {
|
|
55
|
+
const { effectSession, openLedger } = await import('../effects/host.js');
|
|
56
|
+
const { recordHitlAuthorization } = await import('../policy/hitl-receipt.js');
|
|
57
|
+
const session = await effectSession(loaded.projectDir, 'custom');
|
|
58
|
+
console.log(JSON.stringify(await recordHitlAuthorization(openLedger(session), action, context), null, 2));
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (operation !== 'enable' && operation !== 'revoke')
|
|
63
|
+
throw usageError('Unknown authorization operation');
|
|
64
|
+
const config = await readAiwgConfig(target);
|
|
65
|
+
if (!config)
|
|
66
|
+
throw usageError('No project config found; initialize project config first');
|
|
67
|
+
const authorization = ensureAuthorization(config);
|
|
68
|
+
if (operation === 'enable') {
|
|
69
|
+
if (!flags['--policy-file'])
|
|
70
|
+
throw usageError('--policy-file is required');
|
|
71
|
+
const policy = await jsonFile(resolve(cwd, flags['--policy-file']));
|
|
72
|
+
const errors = validateHitlPolicy(policy);
|
|
73
|
+
if (policy === undefined || errors.length)
|
|
74
|
+
throw usageError('Invalid authorization.hitl policy');
|
|
75
|
+
authorization.hitl = { ...policy, enabled: true };
|
|
76
|
+
}
|
|
77
|
+
else if (authorization.hitl) {
|
|
78
|
+
if (flags['--profile']) {
|
|
79
|
+
if (!Object.hasOwn(authorization.hitl.profiles, flags['--profile']))
|
|
80
|
+
throw usageError('Unknown authorization profile');
|
|
81
|
+
delete authorization.hitl.profiles[flags['--profile']];
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
authorization.hitl.enabled = false;
|
|
85
|
+
}
|
|
86
|
+
await writeAiwgConfig(target, config);
|
|
87
|
+
console.log(JSON.stringify({ operation, enabled: authorization.hitl?.enabled ?? false }));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=authorization-cli.js.map
|
|
@@ -17,6 +17,7 @@ Subcommands:
|
|
|
17
17
|
path Print config directory path
|
|
18
18
|
edit Open config in $EDITOR/$VISUAL (requires a terminal)
|
|
19
19
|
edit --print-path Print the config file path instead of opening an editor
|
|
20
|
+
authorization Inspect/validate/preview/enable/revoke bounded HITL policy
|
|
20
21
|
gitignore Show/check/fix .gitignore AIWG entries
|
|
21
22
|
|
|
22
23
|
Reviewed worker policy pair (project scope, no prompts):
|
package/dist/src/config/cli.js
CHANGED
|
@@ -33,6 +33,11 @@ const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
33
33
|
* Main CLI entry point for `aiwg config <subcommand> [args]`
|
|
34
34
|
*/
|
|
35
35
|
export async function main(args) {
|
|
36
|
+
if (args[0] === 'authorization') {
|
|
37
|
+
const { authorizationCli } = await import('./authorization-cli.js');
|
|
38
|
+
await authorizationCli(args.slice(1));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
if (await handleWorkerAdoptionArgs(args, process.cwd(), 'config'))
|
|
37
42
|
return;
|
|
38
43
|
// Extract --config-dir flag before routing
|
|
@@ -291,7 +296,7 @@ async function projectConfigSet(key, raw, args, quiet = false) {
|
|
|
291
296
|
}
|
|
292
297
|
// Coerce booleans for known boolean fields
|
|
293
298
|
let value = raw;
|
|
294
|
-
if (key === 'rules' || key.startsWith('rules.') || key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
|
|
299
|
+
if (key === 'authorization' || key.startsWith('authorization.') || key === 'rules' || key.startsWith('rules.') || key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
|
|
295
300
|
try {
|
|
296
301
|
value = JSON.parse(raw);
|
|
297
302
|
}
|
|
@@ -397,6 +402,10 @@ async function projectConfigSet(key, raw, args, quiet = false) {
|
|
|
397
402
|
? (existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : emptyConfig())
|
|
398
403
|
: (await readAiwgConfig(projectDir)) ?? emptyConfig();
|
|
399
404
|
const before = structuredClone(getDottedPath(cfg, key));
|
|
405
|
+
if (key.startsWith('authorization.hitl')) {
|
|
406
|
+
const { ensureAuthorization } = await import('../policy/hitl-config.js');
|
|
407
|
+
ensureAuthorization(cfg);
|
|
408
|
+
}
|
|
400
409
|
setDottedPath(cfg, key, value);
|
|
401
410
|
const externalLinkErrors = validateExternalLinks(cfg.externalLinks);
|
|
402
411
|
if (externalLinkErrors.length > 0) {
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker AIWG context providers and launch-time bootstrap ensure (#3685).
|
|
3
|
+
*
|
|
4
|
+
* Orchestration must automatically `aiwg use` the providers configured for
|
|
5
|
+
* it, so any new worker process starts with the AIWG bootstrap in place.
|
|
6
|
+
* This module owns the config-side half: resolving WHICH providers
|
|
7
|
+
* (config `providers` plus every CLI configured under
|
|
8
|
+
* `parallelism.clis` / `parallelism.groups.*.members`, mapped through the
|
|
9
|
+
* provider registry) and checking/ensuring that bootstrap inside exactly
|
|
10
|
+
* one worker workdir (never outside it).
|
|
11
|
+
*
|
|
12
|
+
* The tools/ half (`tools/ralph-external/lib/worker-context.mjs`) loads the
|
|
13
|
+
* config through `readAiwgConfig` and injects it here; direct callers may
|
|
14
|
+
* pass an already-loaded config object instead.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { getProviderArtifactPathStrings, getProviderDefinition, normalizeProviderDefinitionId, } from '../providers/provider-definitions.js';
|
|
20
|
+
import { emptyConfig, readAiwgConfig, writeAiwgConfig, } from './aiwg-config.js';
|
|
21
|
+
/**
|
|
22
|
+
* Typed refusal when a worker's AIWG bootstrap cannot be ensured.
|
|
23
|
+
* `remedy` names the operator action that unblocks the launch.
|
|
24
|
+
*/
|
|
25
|
+
export class WorkerContextBootstrapError extends Error {
|
|
26
|
+
code = 'OG_WORKER_CONTEXT_UNAVAILABLE';
|
|
27
|
+
remedy;
|
|
28
|
+
provider;
|
|
29
|
+
workdir;
|
|
30
|
+
constructor(message, remedy, opts) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'WorkerContextBootstrapError';
|
|
33
|
+
this.remedy = remedy;
|
|
34
|
+
if (opts?.provider !== undefined)
|
|
35
|
+
this.provider = opts.provider;
|
|
36
|
+
if (opts?.workdir !== undefined)
|
|
37
|
+
this.workdir = opts.workdir;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Explicit provider lists must be non-empty arrays of deployable ids. */
|
|
41
|
+
export function validateExplicitWorkerContextProviders(providers) {
|
|
42
|
+
if (!Array.isArray(providers) || providers.length === 0
|
|
43
|
+
|| providers.some(p => typeof p !== 'string' || p.length === 0 || p.includes('\0') || p.includes(','))) {
|
|
44
|
+
throw new WorkerContextBootstrapError('worker context providers must be a non-empty array of provider ids', 'pass an explicit non-empty provider list, or configure providers plus orchestration CLIs in .aiwg/aiwg.config');
|
|
45
|
+
}
|
|
46
|
+
return [...new Set(providers)];
|
|
47
|
+
}
|
|
48
|
+
const ADAPTER_REFERENCE = /^(providers|workerAdapters):([a-z][a-z0-9-]{0,62})$/;
|
|
49
|
+
/**
|
|
50
|
+
* Map one orchestration CLI id to its AIWG provider id.
|
|
51
|
+
* A CLI without an explicit `parallelism.clis` entry defaults to
|
|
52
|
+
* `providers:<cli>` (the same default `resolveWorkerGroupInstallation`
|
|
53
|
+
* applies). Non-provider adapters (e.g. `workerAdapters:local-http`) and
|
|
54
|
+
* ids the registry does not know are reported as skipped, never guessed.
|
|
55
|
+
*/
|
|
56
|
+
export function mapCliToProviderId(cli, parallelism) {
|
|
57
|
+
if (typeof cli !== 'string' || cli.length === 0) {
|
|
58
|
+
return { skipped: `empty CLI reference` };
|
|
59
|
+
}
|
|
60
|
+
const raw = typeof parallelism?.clis?.[cli]?.adapter === 'string'
|
|
61
|
+
&& parallelism.clis[cli].adapter.length > 0
|
|
62
|
+
? parallelism.clis[cli].adapter
|
|
63
|
+
: `providers:${cli}`;
|
|
64
|
+
const match = ADAPTER_REFERENCE.exec(raw);
|
|
65
|
+
if (!match || match[1] !== 'providers') {
|
|
66
|
+
return { skipped: `clis/${cli}: ${raw}` };
|
|
67
|
+
}
|
|
68
|
+
const provider = normalizeProviderDefinitionId(match[2]);
|
|
69
|
+
if (!provider)
|
|
70
|
+
return { skipped: `clis/${cli}: ${raw} (unknown provider)` };
|
|
71
|
+
return { provider };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the full worker-context provider set: the union of the config's
|
|
75
|
+
* own `providers` and every CLI configured for orchestration
|
|
76
|
+
* (`parallelism.clis` keys/adapters and the `cli` of every
|
|
77
|
+
* `parallelism.groups.*.members[]`), mapped to AIWG provider ids through
|
|
78
|
+
* the provider registry. Non-provider adapters are skipped and reported.
|
|
79
|
+
* Fails closed when the union is empty.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveWorkerContextProvidersFromConfig(config) {
|
|
82
|
+
const providers = [];
|
|
83
|
+
const skipped = [];
|
|
84
|
+
const add = (id) => {
|
|
85
|
+
if (!providers.includes(id))
|
|
86
|
+
providers.push(id);
|
|
87
|
+
};
|
|
88
|
+
const skip = (entry) => {
|
|
89
|
+
if (!skipped.includes(entry))
|
|
90
|
+
skipped.push(entry);
|
|
91
|
+
};
|
|
92
|
+
const configured = config?.providers;
|
|
93
|
+
if (Array.isArray(configured)) {
|
|
94
|
+
for (const entry of configured) {
|
|
95
|
+
if (typeof entry !== 'string' || entry.length === 0)
|
|
96
|
+
continue;
|
|
97
|
+
// Configured providers deploy verbatim when the registry does not
|
|
98
|
+
// know them: project-local provider adapters resolve at deploy time
|
|
99
|
+
// (see the `use` handler's project-local resolution), so an unknown
|
|
100
|
+
// id here is the deployer's decision, not this resolver's.
|
|
101
|
+
add(normalizeProviderDefinitionId(entry) ?? entry);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const parallelism = config?.parallelism;
|
|
105
|
+
for (const cli of Object.keys(parallelism?.clis ?? {})) {
|
|
106
|
+
const mapped = mapCliToProviderId(cli, parallelism);
|
|
107
|
+
if ('provider' in mapped)
|
|
108
|
+
add(mapped.provider);
|
|
109
|
+
else
|
|
110
|
+
skip(mapped.skipped);
|
|
111
|
+
}
|
|
112
|
+
for (const [groupId, group] of Object.entries(parallelism?.groups ?? {})) {
|
|
113
|
+
for (const member of group?.members ?? []) {
|
|
114
|
+
const cli = member?.cli;
|
|
115
|
+
const mapped = mapCliToProviderId(cli, parallelism);
|
|
116
|
+
if ('provider' in mapped)
|
|
117
|
+
add(mapped.provider);
|
|
118
|
+
else
|
|
119
|
+
skip(typeof cli === 'string' && cli ? `groups/${groupId}/members cli '${cli}': ${mapped.skipped}` : `groups/${groupId}/members: ${mapped.skipped}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (providers.length === 0) {
|
|
123
|
+
throw new WorkerContextBootstrapError('worker context deployment needs configured providers: the union of config providers and orchestration CLIs is empty'
|
|
124
|
+
+ (skipped.length > 0 ? ` (skipped: ${skipped.join('; ')})` : ''), 'configure providers and/or parallelism clis/groups in .aiwg/aiwg.config, or pass an explicit provider list');
|
|
125
|
+
}
|
|
126
|
+
return { providers, skipped };
|
|
127
|
+
}
|
|
128
|
+
/** AIWG-managed rule-section markers written into a deployed AGENTS.md. */
|
|
129
|
+
const WORKER_CONTEXT_RULE_MARKER = /<!--\s*AIWG:[A-Za-z0-9_-]+:(start|end)\s*-->/;
|
|
130
|
+
/**
|
|
131
|
+
* Check that a worker workdir already carries one provider's bootstrap:
|
|
132
|
+
* the kernel artifact dirs (`use all` writes kernel skills, rules,
|
|
133
|
+
* behaviors — never the full agent/command surface) and, for providers
|
|
134
|
+
* whose contract targets AGENTS.md, the AIWG rule sections.
|
|
135
|
+
*/
|
|
136
|
+
export function workerContextBootstrapStatus({ workdir, provider }) {
|
|
137
|
+
const missing = [];
|
|
138
|
+
const definition = getProviderDefinition(provider);
|
|
139
|
+
if (!definition) {
|
|
140
|
+
return { present: false, missing: [`unknown provider '${provider}'`] };
|
|
141
|
+
}
|
|
142
|
+
const paths = getProviderArtifactPathStrings(provider);
|
|
143
|
+
const kernelDirs = [
|
|
144
|
+
definition.paths.kernelSkills,
|
|
145
|
+
paths?.rules ?? null,
|
|
146
|
+
paths?.behaviors ?? null,
|
|
147
|
+
].filter((dir) => typeof dir === 'string' && dir.length > 0);
|
|
148
|
+
const requiredDirs = kernelDirs.length > 0
|
|
149
|
+
? kernelDirs
|
|
150
|
+
: Object.values(paths ?? {}).filter((dir) => typeof dir === 'string' && dir.length > 0);
|
|
151
|
+
for (const dir of [...new Set(requiredDirs)]) {
|
|
152
|
+
const absolute = path.isAbsolute(dir) ? dir : path.join(workdir, dir);
|
|
153
|
+
let entries = null;
|
|
154
|
+
try {
|
|
155
|
+
entries = readdirSync(absolute);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
entries = null;
|
|
159
|
+
}
|
|
160
|
+
if (!entries || entries.length === 0)
|
|
161
|
+
missing.push(`provider dir '${dir}'`);
|
|
162
|
+
}
|
|
163
|
+
if ((definition.context?.bootstrapTargets ?? []).includes('AGENTS.md')) {
|
|
164
|
+
let content = null;
|
|
165
|
+
try {
|
|
166
|
+
content = readFileSync(path.join(workdir, 'AGENTS.md'), 'utf8');
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
content = null;
|
|
170
|
+
}
|
|
171
|
+
if (!content || !WORKER_CONTEXT_RULE_MARKER.test(content)) {
|
|
172
|
+
missing.push('AGENTS.md rule sections');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { present: missing.length === 0, missing };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Materialize the union provider set into the worker workdir's own config
|
|
179
|
+
* through the existing loader/writer (never ad-hoc JSON). Existing entries
|
|
180
|
+
* keep their order; union members absent from the worktree base are
|
|
181
|
+
* appended. A missing config is created minimal. An already-current config
|
|
182
|
+
* is left untouched so the deploy below stays the only writer.
|
|
183
|
+
*/
|
|
184
|
+
export async function syncWorkerContextProviders(options) {
|
|
185
|
+
const { workdir } = options;
|
|
186
|
+
if (typeof workdir !== 'string' || !path.isAbsolute(workdir)) {
|
|
187
|
+
throw new WorkerContextBootstrapError(`worker context sync needs an absolute workdir, got ${JSON.stringify(workdir)}`, 'sync worker context providers only into the worker workdir', { workdir: typeof workdir === 'string' ? workdir : undefined });
|
|
188
|
+
}
|
|
189
|
+
const providers = validateExplicitWorkerContextProviders(options.providers);
|
|
190
|
+
const read = options.readConfig ?? ((dir) => readAiwgConfig(dir));
|
|
191
|
+
const existing = await read(workdir);
|
|
192
|
+
const current = Array.isArray(existing?.providers)
|
|
193
|
+
? existing.providers.filter((entry) => typeof entry === 'string')
|
|
194
|
+
: [];
|
|
195
|
+
const merged = [...current];
|
|
196
|
+
for (const provider of providers) {
|
|
197
|
+
if (!merged.includes(provider))
|
|
198
|
+
merged.push(provider);
|
|
199
|
+
}
|
|
200
|
+
const unchanged = merged.length === current.length && merged.every((entry, index) => entry === current[index]);
|
|
201
|
+
if (unchanged)
|
|
202
|
+
return { providers: merged, updated: false };
|
|
203
|
+
const base = existing !== null && typeof existing === 'object' && 'version' in existing
|
|
204
|
+
? existing
|
|
205
|
+
: {};
|
|
206
|
+
const next = { ...emptyConfig(merged), ...base, providers: merged };
|
|
207
|
+
const write = options.writeConfig ?? ((dir, config) => writeAiwgConfig(dir, config));
|
|
208
|
+
await write(workdir, next);
|
|
209
|
+
return { providers: merged, updated: true };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Canonical one-pass deploy argv: a single `aiwg use all` invocation
|
|
213
|
+
* covering every listed provider. Never one deploy per provider:
|
|
214
|
+
* per-provider invocations rewrite AGENTS.md piecemeal and clobber each
|
|
215
|
+
* other's rule sections, while one pass keeps them all. No `--providers`
|
|
216
|
+
* flag: it feeds only the global bootstrap and the orchestrated
|
|
217
|
+
* verification report — `executeCore` resolves project-scope providers
|
|
218
|
+
* from config (`src/cli/handlers/use.ts`), ignoring both the flag and any
|
|
219
|
+
* explicitly resolved list. The supported project-scope mechanism is the
|
|
220
|
+
* configured-provider path: `syncWorkerContextProviders` first, then this
|
|
221
|
+
* single pass reads the union back from the worktree config.
|
|
222
|
+
*/
|
|
223
|
+
export function buildWorkerContextDeployArgs({ providers, target }) {
|
|
224
|
+
// Fail fast on a hostile list; the synced worktree config carries the set.
|
|
225
|
+
validateExplicitWorkerContextProviders(providers);
|
|
226
|
+
return ['use', 'all', '--target', target, '--yes'];
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Ensure a worker workdir carries its CLI's AIWG bootstrap before launch.
|
|
230
|
+
* Present: no-op. Missing: sync the union set into the workdir config,
|
|
231
|
+
* then exactly one `aiwg use all` pass into the workdir (never anywhere
|
|
232
|
+
* else), then re-verify. Impossible (no binary, failed deploy, still
|
|
233
|
+
* missing afterwards): throw `WorkerContextBootstrapError` naming the
|
|
234
|
+
* remedy — the caller refuses the launch rather than starting a
|
|
235
|
+
* context-free worker.
|
|
236
|
+
*/
|
|
237
|
+
export async function ensureWorkerContextBootstrap(options) {
|
|
238
|
+
const { workdir, provider, aiwgBin = process.env.AIWG_BIN ?? 'aiwg', spawn = spawnSync, timeoutMs = 300000, statusFn = workerContextBootstrapStatus, } = options;
|
|
239
|
+
if (typeof workdir !== 'string' || !path.isAbsolute(workdir) || !existsSync(workdir)) {
|
|
240
|
+
throw new WorkerContextBootstrapError(`worker context bootstrap needs an existing absolute workdir, got ${JSON.stringify(workdir)}`, 'launch the worker with its resolved absolute workdir', { provider, workdir: typeof workdir === 'string' ? workdir : undefined });
|
|
241
|
+
}
|
|
242
|
+
const status = statusFn({ workdir, provider });
|
|
243
|
+
if (status.present)
|
|
244
|
+
return { ensured: false, providers: [provider], skippedBridges: [] };
|
|
245
|
+
const providers = options.providers && options.providers.length > 0
|
|
246
|
+
? validateExplicitWorkerContextProviders(options.providers)
|
|
247
|
+
: [provider];
|
|
248
|
+
// The configured-provider path: the single pass below resolves its set
|
|
249
|
+
// from the workdir config, so the union must be materialized there
|
|
250
|
+
// first — `--providers` never reaches project-scope deploys.
|
|
251
|
+
const sync = options.syncFn ?? syncWorkerContextProviders;
|
|
252
|
+
let synced;
|
|
253
|
+
try {
|
|
254
|
+
synced = await sync({ workdir, providers });
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error instanceof WorkerContextBootstrapError)
|
|
258
|
+
throw error;
|
|
259
|
+
throw new WorkerContextBootstrapError(`worker context sync failed: ${error instanceof Error ? error.message : String(error)}`, `write the union providers into ${path.join(workdir, '.aiwg', 'aiwg.config')} (or run 'aiwg use all --target ${workdir} --yes' manually) before launch`, { provider, workdir });
|
|
260
|
+
}
|
|
261
|
+
const argv = buildWorkerContextDeployArgs({ providers: synced.providers, target: workdir });
|
|
262
|
+
const deploy = options.deployFn ?? (({ worktreePath, providers: deployProviders, aiwgBin: bin }) => {
|
|
263
|
+
// worktreePath IS the worker workdir by construction: the only
|
|
264
|
+
// `--target` ever passed is `workdir` above, and the spawn cwd is the
|
|
265
|
+
// same directory, so no deploy write can land outside it.
|
|
266
|
+
if (path.resolve(worktreePath) !== path.resolve(workdir)) {
|
|
267
|
+
throw new WorkerContextBootstrapError(`refusing worker context deploy outside the worker workdir (${worktreePath} !== ${workdir})`, 'deploy worker context only into the worker workdir', { provider, workdir });
|
|
268
|
+
}
|
|
269
|
+
const result = spawn(bin, argv, { cwd: workdir, encoding: 'utf8', timeout: timeoutMs });
|
|
270
|
+
if (result.error) {
|
|
271
|
+
throw new WorkerContextBootstrapError(`worker context deploy failed to run (${result.error?.message});`
|
|
272
|
+
+ ` missing in ${workdir}: ${status.missing.join('; ')}`, `install the aiwg CLI on PATH or set AIWG_BIN to its location (tried '${bin}'),`
|
|
273
|
+
+ ` or run 'aiwg ${argv.join(' ')}' manually before launch`, { provider, workdir });
|
|
274
|
+
}
|
|
275
|
+
if (result.status !== 0) {
|
|
276
|
+
throw new WorkerContextBootstrapError(`worker context deploy exited ${result.status}: ${String(result.stderr ?? '').trim().slice(0, 2000)};`
|
|
277
|
+
+ ` missing in ${workdir}: ${status.missing.join('; ')}`, `run 'aiwg ${argv.join(' ')}' manually and resolve the reported failure before launch`, { provider, workdir });
|
|
278
|
+
}
|
|
279
|
+
return { providers: [...deployProviders] };
|
|
280
|
+
});
|
|
281
|
+
let deployed;
|
|
282
|
+
try {
|
|
283
|
+
deployed = await deploy({ worktreePath: workdir, providers: synced.providers, aiwgBin });
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (error instanceof WorkerContextBootstrapError)
|
|
287
|
+
throw error;
|
|
288
|
+
throw new WorkerContextBootstrapError(`worker context deploy failed: ${error instanceof Error ? error.message : String(error)};`
|
|
289
|
+
+ ` missing in ${workdir}: ${status.missing.join('; ')}`, `run 'aiwg ${argv.join(' ')}' manually and resolve the reported failure before launch`, { provider, workdir });
|
|
290
|
+
}
|
|
291
|
+
const after = statusFn({ workdir, provider });
|
|
292
|
+
if (!after.present) {
|
|
293
|
+
throw new WorkerContextBootstrapError(`worker context deploy did not produce the bootstrap: still missing in ${workdir}: ${after.missing.join('; ')}`, `run 'aiwg ${argv.join(' ')}' manually and verify the provider bootstrap before launch`, { provider, workdir });
|
|
294
|
+
}
|
|
295
|
+
// Keep deploy-touched tracked bridges out of the worker diff. A workdir
|
|
296
|
+
// outside any git repository has no tracked files; skip silently there.
|
|
297
|
+
let skippedBridges = [];
|
|
298
|
+
if (options.skipWorktreeFn) {
|
|
299
|
+
skippedBridges = options.skipWorktreeFn({ workdir });
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
try {
|
|
303
|
+
skippedBridges = flagDeployDirtiedSkipWorktree({ workdir, spawn, timeoutMs });
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
skippedBridges = [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { ensured: true, providers: [...deployed.providers], skippedBridges };
|
|
310
|
+
}
|
|
311
|
+
/** Tracked files the deploy modified, hidden from the worker diff. */
|
|
312
|
+
export function flagDeployDirtiedSkipWorktree({ workdir, spawn = spawnSync, timeoutMs = 30000 }) {
|
|
313
|
+
const status = spawn('git', ['status', '--porcelain=v1', '--untracked-files=no'], { cwd: workdir, encoding: 'utf8', timeout: timeoutMs });
|
|
314
|
+
if (status.error)
|
|
315
|
+
throw new Error(`git status failed: ${status.error?.message}`);
|
|
316
|
+
if (status.status !== 0)
|
|
317
|
+
throw new Error(`git status exited ${status.status}`);
|
|
318
|
+
const files = [];
|
|
319
|
+
for (const line of String(status.stdout ?? '').split('\n')) {
|
|
320
|
+
if (!line.trim())
|
|
321
|
+
continue;
|
|
322
|
+
const raw = line.slice(3);
|
|
323
|
+
const arrow = raw.indexOf(' -> ');
|
|
324
|
+
const rel = arrow >= 0 ? raw.slice(arrow + 4) : raw;
|
|
325
|
+
if (!rel || rel.startsWith('"'))
|
|
326
|
+
throw new Error(`worker context deploy dirtied an unflaggeable path: ${line}`);
|
|
327
|
+
files.push(rel);
|
|
328
|
+
}
|
|
329
|
+
if (files.length > 0) {
|
|
330
|
+
const flag = spawn('git', ['update-index', '--skip-worktree', '--', ...files], { cwd: workdir, encoding: 'utf8', timeout: timeoutMs });
|
|
331
|
+
if (flag.error || flag.status !== 0) {
|
|
332
|
+
throw new Error(`git update-index --skip-worktree failed: ${flag.error?.message ?? String(flag.stderr ?? '').trim()}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return files;
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=worker-context.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../gates/lattice.js';
|
|
1
2
|
import { ContextBudgetManager } from '../metrics/context-budget.js';
|
|
2
3
|
import { sha256 } from './compile-cache/identity.js';
|
|
3
4
|
import { admitEntry } from './entry.js';
|
|
@@ -436,9 +437,9 @@ export function buildContextPruningEvaluationReport(input) {
|
|
|
436
437
|
findings.add(`missing-input:${name}`);
|
|
437
438
|
// Automatic rollback triggers from the D26 rollout plan: protected miss, demonstrated quality harm, negative
|
|
438
439
|
// economics. An inconclusive non-inferiority result (no evidence of harm) holds instead.
|
|
439
|
-
const rollback =
|
|
440
|
+
const rollback = findings.has('protected-retention-breach')
|
|
440
441
|
|| findings.has('quality-harm') || findings.has('negative-net-economics');
|
|
441
|
-
const decision = rollback ? 'ROLLBACK' :
|
|
442
|
+
const decision = ceilingDecision(upstreamDecision, rollback ? 'ROLLBACK' : findings.size > 0 ? 'HOLD' : 'PROMOTE');
|
|
442
443
|
const advisory = [...findings].some(item => item.startsWith('insufficient-') || item.startsWith('quality-metric-missing:')
|
|
443
444
|
|| item.startsWith('missing-input:') || item.startsWith('quality-outcomes-incomplete:')
|
|
444
445
|
|| item.startsWith('quality-non-inferiority-inconclusive:')
|