@aiwg/cli 2026.10.3 → 2026.10.4
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +98 -37
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* - **Authority.** The tracker comes from `resolveTrackerAuthority` over the
|
|
6
6
|
* project config and git remote URLs. A target whose forge is neither the
|
|
7
7
|
* internal nor the customer tracker, or whose repository differs from that
|
|
8
|
-
* tracker remote, is `unknown`
|
|
9
|
-
* from the selected remote URL; no host is assumed.
|
|
8
|
+
* tracker remote, is `unknown` with a specific precondition reason. The API
|
|
9
|
+
* base is derived from the selected remote URL; no host is assumed.
|
|
10
10
|
* - **Access order.** `chooseTrackerAccess` over the probes a CLI process can
|
|
11
11
|
* offer: the tracker HTTP API (with credentials from the existing tracker
|
|
12
12
|
* token path, `resolveToken`), then the forge CLI (`gh`, GitHub only). MCP
|
|
13
|
-
* is never offered.
|
|
13
|
+
* is never offered. Blockers include a safe configuration or credential hint.
|
|
14
14
|
* - **Matching.** A comment matches exactly when it carries the
|
|
15
15
|
* `<!-- aiwg-effect: <id> -->` marker and was written by the pinned tracker
|
|
16
16
|
* actor; a merged PR matches its merge commit's `Effect-Id:` trailer.
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* older than `minAbsentAgeMs`; a younger intent is `unknown` /
|
|
21
21
|
* `consistency-lag`.
|
|
22
22
|
*
|
|
23
|
-
* Access material stays inside the transport: evidence carries IDs, SHAs
|
|
24
|
-
* digests only, and errors carry fixed messages.
|
|
23
|
+
* Access material stays inside the transport: evidence carries IDs, SHAs,
|
|
24
|
+
* digests and safe repair hints only, and errors carry fixed messages.
|
|
25
25
|
*
|
|
26
26
|
* @see docs/contracts/effect-ledger.v1.md "Tracker verifiers"
|
|
27
27
|
*/
|
|
@@ -92,7 +92,7 @@ function onPath(binary) {
|
|
|
92
92
|
}
|
|
93
93
|
}));
|
|
94
94
|
}
|
|
95
|
-
/** Resolve the tracker lane
|
|
95
|
+
/** Resolve the tracker lane, or return a classified precondition failure with a safe repair hint. */
|
|
96
96
|
function resolveLane(options, target) {
|
|
97
97
|
const parsed = TARGET.exec(target);
|
|
98
98
|
if (!parsed)
|
|
@@ -103,16 +103,16 @@ function resolveLane(options, target) {
|
|
|
103
103
|
const remotes = options.config?.remotes;
|
|
104
104
|
let lane = null;
|
|
105
105
|
if (authority.provider === provider) {
|
|
106
|
-
lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor };
|
|
106
|
+
lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor, actorConfigKey: 'remotes.tracker_actor.login' };
|
|
107
107
|
}
|
|
108
108
|
else if (authority.customerIssueTrackerRemote && authority.customerProvider === provider) {
|
|
109
|
-
lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor };
|
|
109
|
+
lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor, actorConfigKey: 'remotes.customer_tracker_actor.login' };
|
|
110
110
|
}
|
|
111
111
|
if (!lane)
|
|
112
|
-
|
|
112
|
+
return unknown('tracker-provider-unresolved', { hint: 'Set remotes.issue_provider (or remotes.customer_issue_provider for the customer tracker) to the target forge.' });
|
|
113
113
|
const endpoint = forgeEndpointFromRemote(provider, lane.url);
|
|
114
114
|
if (!endpoint || endpoint.repository.toLowerCase() !== parsed[2].toLowerCase())
|
|
115
|
-
|
|
115
|
+
return unknown('tracker-repository-mismatch', { hint: 'Match the target repository to the remote selected by remotes.issue_tracker (or remotes.customer_issue_tracker).' });
|
|
116
116
|
const laneAuthority = { ...authority, provider, issueTrackerRemote: lane.remote, issueTrackerUrl: lane.url };
|
|
117
117
|
const token = resolveToken(provider, options.env ?? process.env);
|
|
118
118
|
const cli = options.cli === false ? null : options.cli ?? {};
|
|
@@ -125,13 +125,16 @@ function resolveLane(options, target) {
|
|
|
125
125
|
const readerOptions = options.pageSize ? { pageSize: options.pageSize } : {};
|
|
126
126
|
if (decision.kind === 'http-api' && token) {
|
|
127
127
|
const transport = httpForgeTransport({ provider, apiBaseUrl: endpoint.apiBaseUrl, token, fetchImpl: options.fetchImpl, maxResponseBytes: options.maxResponseBytes });
|
|
128
|
-
return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
|
|
128
|
+
return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
|
|
129
129
|
}
|
|
130
130
|
if (decision.kind === 'cli' && cli) {
|
|
131
131
|
const transport = cliForgeTransport({ provider, hostname: endpoint.host, runner: cli.runner, binary: cli.binary, maxResponseBytes: options.maxResponseBytes });
|
|
132
|
-
return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
|
|
132
|
+
return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
// Gitea has no raw-read CLI fallback. GitHub may use gh when no token is set.
|
|
135
|
+
return provider === 'gitea'
|
|
136
|
+
? unknown('tracker-credential-missing', { hint: 'Set AIWG_GITEA_TOKEN or GITEA_TOKEN for the tracker HTTP API.' })
|
|
137
|
+
: unknown('tracker-no-access-route', { hint: 'Set AIWG_GITHUB_TOKEN or GITHUB_TOKEN, or install and authenticate gh for the tracker host.' });
|
|
135
138
|
}
|
|
136
139
|
const unknown = (reason, evidence) => ({ result: 'unknown', reason, complete: false, ...(evidence ? { evidence } : {}) });
|
|
137
140
|
/** `absent` once the intent is old enough, else `unknown` / `consistency-lag`. */
|
|
@@ -160,6 +163,8 @@ export function trackerCommentVerifier(options) {
|
|
|
160
163
|
canReportAbsent: true,
|
|
161
164
|
async verify(request) {
|
|
162
165
|
const lane = resolveLane(options, request.target);
|
|
166
|
+
if ('result' in lane)
|
|
167
|
+
return lane;
|
|
163
168
|
const expectedDigest = expectedString(request, 'digest');
|
|
164
169
|
if (expectedDigest !== undefined && !DIGEST.test(expectedDigest))
|
|
165
170
|
return unknown('malformed-response');
|
|
@@ -220,7 +225,10 @@ export function trackerCommentVerifier(options) {
|
|
|
220
225
|
const summary = { ...base, pages, scanned };
|
|
221
226
|
// A marker for this effect under another author is never ours; escalate instead of replaying.
|
|
222
227
|
if (foreignMarker)
|
|
223
|
-
return unknown('evidence-conflict', {
|
|
228
|
+
return unknown('evidence-conflict', {
|
|
229
|
+
...summary, foreignMarker: true,
|
|
230
|
+
...(!pinned ? { actorUnpinned: true, hint: `Set ${lane.actorConfigKey} to the trusted tracker author.` } : {}),
|
|
231
|
+
});
|
|
224
232
|
if (digestMatch) {
|
|
225
233
|
return { result: 'present', reason: 'digest-match', complete, evidence: { ...summary, commentId: digestMatch.id, bodyDigest: digestMatch.bodyDigest, confidence: 'exact' } };
|
|
226
234
|
}
|
|
@@ -241,6 +249,8 @@ export function trackerIssueClosedVerifier(options) {
|
|
|
241
249
|
canReportAbsent: true,
|
|
242
250
|
async verify(request) {
|
|
243
251
|
const lane = resolveLane(options, request.target);
|
|
252
|
+
if ('result' in lane)
|
|
253
|
+
return lane;
|
|
244
254
|
const issue = await read(() => lane.reader.getIssue(lane.repository, lane.number, request.signal));
|
|
245
255
|
const evidence = { ...laneEvidence(lane), state: issue.state, closedAt: issue.closedAt };
|
|
246
256
|
if (issue.state === 'closed')
|
|
@@ -263,6 +273,8 @@ export function trackerPrMergedVerifier(options) {
|
|
|
263
273
|
if (expectedSha !== undefined && !OBJECT_ID.test(expectedSha))
|
|
264
274
|
return unknown('malformed-response');
|
|
265
275
|
const lane = resolveLane(options, request.target);
|
|
276
|
+
if ('result' in lane)
|
|
277
|
+
return lane;
|
|
266
278
|
const pr = await read(() => lane.reader.getPullState(lane.repository, lane.number, request.signal));
|
|
267
279
|
const evidence = {
|
|
268
280
|
...laneEvidence(lane), state: pr.state, merged: pr.merged, mergedAt: pr.mergedAt,
|
|
@@ -200,6 +200,24 @@ export const decisionCommand = {
|
|
|
200
200
|
allowedTools: ['Read', 'Write', 'Bash'],
|
|
201
201
|
},
|
|
202
202
|
};
|
|
203
|
+
export const rulesCommand = {
|
|
204
|
+
id: 'rules',
|
|
205
|
+
type: 'command',
|
|
206
|
+
name: 'Rule Pins',
|
|
207
|
+
description: 'Pin installed rules to every request and manage reusable capability groups',
|
|
208
|
+
version: '1.0.0',
|
|
209
|
+
capabilities: ['cli', 'rules', 'configuration', 'activation'],
|
|
210
|
+
keywords: ['rules', 'pin', 'unpin', 'pinned', 'tier', 'capability', 'group'],
|
|
211
|
+
category: 'config',
|
|
212
|
+
platforms: { claude: 'full', generic: 'full' },
|
|
213
|
+
deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
|
|
214
|
+
metadata: {
|
|
215
|
+
type: 'command',
|
|
216
|
+
template: 'utility',
|
|
217
|
+
argumentHint: '<pin|unpin|pinned|group> [rules...] [--tier tiers] [--capability ids] [--group name] [--all] [--dry-run] [--json]',
|
|
218
|
+
allowedTools: ['Read', 'Write', 'Bash'],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
203
221
|
export const gatesCommand = {
|
|
204
222
|
id: 'gates',
|
|
205
223
|
type: 'command',
|
|
@@ -3752,6 +3770,7 @@ export const commandDefinitions = [
|
|
|
3752
3770
|
updateCommand,
|
|
3753
3771
|
installationCommand,
|
|
3754
3772
|
decisionCommand,
|
|
3773
|
+
rulesCommand,
|
|
3755
3774
|
gatesCommand,
|
|
3756
3775
|
refreshCommand,
|
|
3757
3776
|
regenerateCommand,
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* @design @.aiwg/architecture/design-aiwg-remove-revert.md
|
|
13
13
|
* @implements #1037
|
|
14
14
|
*/
|
|
15
|
-
import { lstat, readdir, stat, unlink, rmdir } from 'fs/promises';
|
|
15
|
+
import { lstat, readdir, readFile, stat, unlink, rmdir } from 'fs/promises';
|
|
16
16
|
import { resolve, join, relative, dirname, isAbsolute } from 'path';
|
|
17
|
+
import { deployedSkillRoots } from '../providers/deployed-skill-roots.js';
|
|
18
|
+
import { releaseSkillOwnership } from '../providers/skill-ownership.mjs';
|
|
17
19
|
import { appendProjectLocalActivity } from './project-local-activity.js';
|
|
18
20
|
import { sha256OfFileNormalized } from './managed-marker.js';
|
|
19
21
|
import { getProviderArtifactPathStrings } from '../providers/provider-definitions.js';
|
|
@@ -257,7 +259,40 @@ export async function removeProjectLocalBundle(config, projectDir, bundleId, opt
|
|
|
257
259
|
for (const provider of providers) {
|
|
258
260
|
const artifactHashes = artifactHashesForProvider(installedEntry, provider);
|
|
259
261
|
let providerHadSkip = false;
|
|
262
|
+
// Receipts cover the complete transformed skill and support tree, including
|
|
263
|
+
// retired skills no longer present in the source inventory.
|
|
264
|
+
const receiptSkills = new Set();
|
|
265
|
+
const sourceRoot = installedEntry.localPath ? resolve(projectDir, installedEntry.localPath) : undefined;
|
|
266
|
+
for (const root of deployedSkillRoots(projectDir, provider)) {
|
|
267
|
+
for (const name of await readdir(root).catch(() => [])) {
|
|
268
|
+
const skillDir = join(root, name);
|
|
269
|
+
try {
|
|
270
|
+
await readFile(join(skillDir, '.aiwg-manifest.json'), 'utf8');
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
receiptSkills.add(name);
|
|
276
|
+
const removed = releaseSkillOwnership(skillDir, {
|
|
277
|
+
frameworkSlug: bundleId,
|
|
278
|
+
skillSource: sourceRoot ? join(sourceRoot, 'skills', name) : undefined,
|
|
279
|
+
sourceRoot,
|
|
280
|
+
}, { dryRun });
|
|
281
|
+
if (removed === null) {
|
|
282
|
+
// Unrelated receipts are outside this bundle's removal inventory.
|
|
283
|
+
if (!Object.keys(artifactHashes).some(sourceRel => sourceRel.startsWith(`skills/${name}/`)))
|
|
284
|
+
continue;
|
|
285
|
+
providerHadSkip = true;
|
|
286
|
+
}
|
|
287
|
+
outcomes.push({ provider, artifactPath: `skills/${name}/SKILL.md`, deployedAbsPath: skillDir,
|
|
288
|
+
case: removed === null ? 'replaced' : 'pristine', reverted: removed !== null && !dryRun,
|
|
289
|
+
message: removed === null ? 'owned by another source — preserved' : dryRun
|
|
290
|
+
? '[dry-run] would release skill ownership' : 'released skill ownership; operator files preserved' });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
260
293
|
for (const sourceRel of Object.keys(artifactHashes)) {
|
|
294
|
+
if (sourceRel.startsWith('skills/') && receiptSkills.has(sourceRel.split('/')[1]))
|
|
295
|
+
continue;
|
|
261
296
|
const owner = resolveOwnership(config, bundleId, provider, sourceRel);
|
|
262
297
|
if (owner) {
|
|
263
298
|
providerHadSkip = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { signalChild } from '../../process/signal-child.mjs';
|
|
1
2
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
3
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
4
|
import { closeSync, lstatSync, openSync, readSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, } from 'node:fs';
|
|
@@ -53,6 +54,10 @@ export const GATE_PROVIDER_MAX_TIMEOUT_MS = 30000;
|
|
|
53
54
|
export const GATE_PROVIDER_DEFAULT_MAX_RECORDS = 50000;
|
|
54
55
|
/** Largest accepted isolated-run stdout body (single JSON message). */
|
|
55
56
|
export const GATE_PROVIDER_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
57
|
+
/** V8 old generation; RLIMIT_DATA also bounds off-heap ArrayBuffer allocations. */
|
|
58
|
+
export const GATE_PROVIDER_MAX_OLD_SPACE_MB = 128;
|
|
59
|
+
// Node reserves ~512 MiB for its code range before any provider runs.
|
|
60
|
+
export const GATE_PROVIDER_MAX_DATA_BYTES = 768 * 1024 * 1024;
|
|
56
61
|
export class GateProviderError extends Error {
|
|
57
62
|
code;
|
|
58
63
|
constructor(message, code) {
|
|
@@ -317,11 +322,16 @@ function prepareIsolatedRun(record, args) {
|
|
|
317
322
|
// OS sandbox (C1c/C1d): bubblewrap is the isolation boundary.
|
|
318
323
|
// ---------------------------------------------------------------------------
|
|
319
324
|
const BWRAP_CANDIDATES = ['/usr/bin/bwrap', '/bin/bwrap', '/usr/local/bin/bwrap'];
|
|
325
|
+
const PRLIMIT_CANDIDATES = ['/usr/bin/prlimit', '/bin/prlimit', '/usr/local/bin/prlimit'];
|
|
320
326
|
const SANDBOX_NODE_PATH = '/aiwg/bin/node';
|
|
321
327
|
const SANDBOX_CWD = '/tmp';
|
|
322
328
|
const SANDBOX_SELF_TEST_TIMEOUT_MS = 15000;
|
|
323
329
|
const SANDBOX_NAMESPACES = ['net', 'pid', 'ipc', 'uts', 'user', 'mnt'];
|
|
330
|
+
const MEMORY_NODE_ARGS = [
|
|
331
|
+
`--max-old-space-size=${GATE_PROVIDER_MAX_OLD_SPACE_MB}`, '--max-semi-space-size=16',
|
|
332
|
+
];
|
|
324
333
|
const DRIVER_NODE_ARGS = [
|
|
334
|
+
...MEMORY_NODE_ARGS,
|
|
325
335
|
'--experimental-vm-modules', '--permission', '--disallow-code-generation-from-strings',
|
|
326
336
|
'--no-warnings', '--input-type=module', '-e', ISOLATED_CHILD_SOURCE,
|
|
327
337
|
];
|
|
@@ -330,8 +340,16 @@ const RESULT_FRAME = 'AIWG-RESULT ';
|
|
|
330
340
|
const SANDBOX_SELF_TEST_SOURCE = [
|
|
331
341
|
"import fs from 'node:fs';",
|
|
332
342
|
"import net from 'node:net';",
|
|
343
|
+
"import v8 from 'node:v8';",
|
|
333
344
|
"const input = JSON.parse(fs.readFileSync(0, 'utf8'));",
|
|
334
|
-
'const out = { env: {}, ns: {}, visible: [], connect: null };',
|
|
345
|
+
'const out = { env: {}, ns: {}, visible: [], connect: null, memory: null };',
|
|
346
|
+
// Read the inherited kernel limits AND exercise the allocation path. Do
|
|
347
|
+
// not touch the allocation if the wrapper is broken: fail without committing it.
|
|
348
|
+
"const data = /^Max data size\\s+(\\S+)\\s+(\\S+)/m.exec(fs.readFileSync('/proc/self/limits', 'utf8'));",
|
|
349
|
+
'let allocationBlocked = false;',
|
|
350
|
+
'try { new Uint8Array(input.maxDataBytes); } catch (error) { allocationBlocked = error instanceof RangeError; }',
|
|
351
|
+
'out.memory = { dataSoftBytes: Number(data?.[1]) || null, dataHardBytes: Number(data?.[2]) || null,',
|
|
352
|
+
' heapLimitBytes: v8.getHeapStatistics().heap_size_limit, allocationBlocked };',
|
|
335
353
|
'for (const key of Object.keys(process.env)) out.env[key] = String(process.env[key]);',
|
|
336
354
|
"for (const name of input.namespaces) { try { out.ns[name] = fs.readlinkSync('/proc/self/ns/' + name); } catch { out.ns[name] = null; } }",
|
|
337
355
|
'for (const path of input.paths) { try { fs.lstatSync(path); out.visible.push(path); } catch { /* absent */ } }',
|
|
@@ -349,7 +367,7 @@ let sandboxOverride = null;
|
|
|
349
367
|
let sandboxVerdict;
|
|
350
368
|
let runtimeBindCache;
|
|
351
369
|
/**
|
|
352
|
-
* Test-only hook: point the runner at another bwrap path (for example a
|
|
370
|
+
* Test-only hook: point the runner at another bwrap/prlimit path (for example a
|
|
353
371
|
* missing binary, or one that fails the self-test) or reset with `null`.
|
|
354
372
|
* It can only make the runner refuse: whatever path is configured must still
|
|
355
373
|
* pass the capability self-test before any provider runs.
|
|
@@ -360,6 +378,13 @@ export function configureIsolationSandboxForTests(options) {
|
|
|
360
378
|
}
|
|
361
379
|
function resolveBwrapPath() {
|
|
362
380
|
const candidates = sandboxOverride?.bwrapPath !== undefined ? [sandboxOverride.bwrapPath] : BWRAP_CANDIDATES;
|
|
381
|
+
return resolveExecutable(candidates);
|
|
382
|
+
}
|
|
383
|
+
function resolvePrlimitPath() {
|
|
384
|
+
const candidates = sandboxOverride?.prlimitPath !== undefined ? [sandboxOverride.prlimitPath] : PRLIMIT_CANDIDATES;
|
|
385
|
+
return resolveExecutable(candidates);
|
|
386
|
+
}
|
|
387
|
+
function resolveExecutable(candidates) {
|
|
363
388
|
for (const candidate of candidates) {
|
|
364
389
|
try {
|
|
365
390
|
const status = statSync(candidate);
|
|
@@ -476,6 +501,10 @@ function sandboxCommand(nodeArgs) {
|
|
|
476
501
|
if (bwrap === null) {
|
|
477
502
|
throw new GateProviderError('bundle providers require bubblewrap (bwrap) for OS-level isolation and none was found: refusing to run', 'sandbox-unavailable');
|
|
478
503
|
}
|
|
504
|
+
const prlimit = resolvePrlimitPath();
|
|
505
|
+
if (prlimit === null) {
|
|
506
|
+
throw new GateProviderError('bundle providers require prlimit for a kernel memory cap and none was found: refusing to run', 'sandbox-unavailable');
|
|
507
|
+
}
|
|
479
508
|
const args = [
|
|
480
509
|
'--unshare-all', '--unshare-user', '--disable-userns',
|
|
481
510
|
'--die-with-parent', '--new-session', '--clearenv', '--cap-drop', 'ALL',
|
|
@@ -483,7 +512,13 @@ function sandboxCommand(nodeArgs) {
|
|
|
483
512
|
for (const [source, target] of runtimeBinds())
|
|
484
513
|
args.push('--ro-bind', source, target);
|
|
485
514
|
args.push('--tmpfs', SANDBOX_CWD, '--proc', '/proc', '--dev', '/dev', '--chdir', SANDBOX_CWD, '--', SANDBOX_NODE_PATH, ...nodeArgs);
|
|
486
|
-
return {
|
|
515
|
+
return {
|
|
516
|
+
command: prlimit,
|
|
517
|
+
// Both limits are hard as well as soft, inherited across exec into bwrap
|
|
518
|
+
// and node. Core dumps are disabled for bounded heap exhaustion failures.
|
|
519
|
+
args: [`--data=${GATE_PROVIDER_MAX_DATA_BYTES}:${GATE_PROVIDER_MAX_DATA_BYTES}`, '--core=0:0', '--', bwrap, ...args],
|
|
520
|
+
env: {},
|
|
521
|
+
};
|
|
487
522
|
}
|
|
488
523
|
/**
|
|
489
524
|
* The exact sandboxed launch used for every provider run: bubblewrap with
|
|
@@ -491,7 +526,8 @@ function sandboxCommand(nodeArgs) {
|
|
|
491
526
|
* cleared environment, new session, die-with-parent, read-only node runtime
|
|
492
527
|
* binds, a private tmpfs, and node with `--permission` (no fs, no child
|
|
493
528
|
* processes, no workers) plus `--disallow-code-generation-from-strings`.
|
|
494
|
-
*
|
|
529
|
+
* prlimit applies a hard data cap (including off-heap memory); V8 has a
|
|
530
|
+
* separate heap cap. The spawn environment itself is empty.
|
|
495
531
|
*/
|
|
496
532
|
export function isolationLaunchPlan() {
|
|
497
533
|
return sandboxCommand(DRIVER_NODE_ARGS);
|
|
@@ -515,11 +551,11 @@ function hostProbePaths() {
|
|
|
515
551
|
}
|
|
516
552
|
function selfTestInput(port) {
|
|
517
553
|
const paths = hostProbePaths();
|
|
518
|
-
return { paths, input: JSON.stringify({ namespaces: SANDBOX_NAMESPACES, paths, port }) };
|
|
554
|
+
return { paths, input: JSON.stringify({ namespaces: SANDBOX_NAMESPACES, paths, port, maxDataBytes: GATE_PROVIDER_MAX_DATA_BYTES }) };
|
|
519
555
|
}
|
|
520
556
|
function judgeSelfTest(stdout, status, paths, port) {
|
|
521
557
|
const report = {
|
|
522
|
-
ok: false, reason: '', envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
558
|
+
ok: false, reason: '', envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
523
559
|
};
|
|
524
560
|
let observed;
|
|
525
561
|
try {
|
|
@@ -550,7 +586,15 @@ function judgeSelfTest(stdout, status, paths, port) {
|
|
|
550
586
|
}
|
|
551
587
|
report.hostPathsVisible = (observed.visible ?? []).filter(path => paths.includes(path));
|
|
552
588
|
report.connect = observed.connect ?? null;
|
|
589
|
+
report.memory = observed.memory ?? null;
|
|
553
590
|
const problems = [];
|
|
591
|
+
const memory = report.memory;
|
|
592
|
+
if (memory === null
|
|
593
|
+
|| memory.dataSoftBytes !== GATE_PROVIDER_MAX_DATA_BYTES || memory.dataHardBytes !== GATE_PROVIDER_MAX_DATA_BYTES
|
|
594
|
+
|| !(memory.heapLimitBytes > 0 && memory.heapLimitBytes <= (GATE_PROVIDER_MAX_OLD_SPACE_MB + 48) * 1024 * 1024)
|
|
595
|
+
|| memory.allocationBlocked !== true) {
|
|
596
|
+
problems.push('memory cap not enforced');
|
|
597
|
+
}
|
|
554
598
|
if (report.envKeys.length)
|
|
555
599
|
problems.push(`environment leaks ${report.envKeys.join(',')}`);
|
|
556
600
|
if (report.sharedNamespaces.length)
|
|
@@ -565,7 +609,7 @@ function judgeSelfTest(stdout, status, paths, port) {
|
|
|
565
609
|
}
|
|
566
610
|
/**
|
|
567
611
|
* Runs the sandbox capability self-test asynchronously and returns the
|
|
568
|
-
* observations (empty env, private namespaces, no host paths, optional
|
|
612
|
+
* observations (enforced memory caps, empty env, private namespaces, no host paths, optional
|
|
569
613
|
* loopback connect to a parent listener on `connectPort`).
|
|
570
614
|
*/
|
|
571
615
|
export function probeIsolationSandbox(options) {
|
|
@@ -573,20 +617,20 @@ export function probeIsolationSandbox(options) {
|
|
|
573
617
|
let plan;
|
|
574
618
|
let probe;
|
|
575
619
|
try {
|
|
576
|
-
plan = sandboxCommand(['--no-warnings', '--input-type=module', '-e', SANDBOX_SELF_TEST_SOURCE]);
|
|
620
|
+
plan = sandboxCommand([...MEMORY_NODE_ARGS, '--no-warnings', '--input-type=module', '-e', SANDBOX_SELF_TEST_SOURCE]);
|
|
577
621
|
probe = selfTestInput(port);
|
|
578
622
|
}
|
|
579
623
|
catch (error) {
|
|
580
624
|
return Promise.resolve({
|
|
581
625
|
ok: false, reason: error instanceof Error ? error.message : 'sandbox unavailable',
|
|
582
|
-
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
626
|
+
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
583
627
|
});
|
|
584
628
|
}
|
|
585
629
|
return new Promise((resolvePromise) => {
|
|
586
630
|
const child = spawn(plan.command, plan.args, { env: plan.env, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
587
631
|
const chunks = [];
|
|
588
632
|
const timer = setTimeout(() => { try {
|
|
589
|
-
child
|
|
633
|
+
signalChild(child, 'SIGKILL');
|
|
590
634
|
}
|
|
591
635
|
catch { /* exited */ } }, SANDBOX_SELF_TEST_TIMEOUT_MS);
|
|
592
636
|
child.stdout?.on('data', (chunk) => { chunks.push(chunk); });
|
|
@@ -595,7 +639,7 @@ export function probeIsolationSandbox(options) {
|
|
|
595
639
|
clearTimeout(timer);
|
|
596
640
|
resolvePromise({
|
|
597
641
|
ok: false, reason: `sandbox failed to start: ${error.message}`,
|
|
598
|
-
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
642
|
+
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
599
643
|
});
|
|
600
644
|
});
|
|
601
645
|
child.on('close', (code) => {
|
|
@@ -607,15 +651,15 @@ export function probeIsolationSandbox(options) {
|
|
|
607
651
|
}
|
|
608
652
|
/**
|
|
609
653
|
* Fail-closed gate before every provider run: bwrap must exist and pass the
|
|
610
|
-
* capability self-test (cached per bwrap path for the process lifetime).
|
|
654
|
+
* capability self-test (cached per bwrap/prlimit path for the process lifetime).
|
|
611
655
|
* There is no unsandboxed fallback.
|
|
612
656
|
*/
|
|
613
657
|
function ensureIsolationSandbox() {
|
|
614
|
-
const key = resolveBwrapPath()
|
|
658
|
+
const key = JSON.stringify([resolveBwrapPath(), resolvePrlimitPath()]);
|
|
615
659
|
if (sandboxVerdict === undefined || sandboxVerdict.key !== key) {
|
|
616
660
|
let verdict;
|
|
617
661
|
try {
|
|
618
|
-
const plan = sandboxCommand(['--no-warnings', '--input-type=module', '-e', SANDBOX_SELF_TEST_SOURCE]);
|
|
662
|
+
const plan = sandboxCommand([...MEMORY_NODE_ARGS, '--no-warnings', '--input-type=module', '-e', SANDBOX_SELF_TEST_SOURCE]);
|
|
619
663
|
const probe = selfTestInput(null);
|
|
620
664
|
const completed = spawnSync(plan.command, plan.args, {
|
|
621
665
|
env: plan.env, input: probe.input, encoding: 'utf8', timeout: SANDBOX_SELF_TEST_TIMEOUT_MS,
|
|
@@ -743,14 +787,14 @@ function runIsolatedRecord(record, args, limits) {
|
|
|
743
787
|
}
|
|
744
788
|
const timer = setTimeout(() => {
|
|
745
789
|
try {
|
|
746
|
-
child
|
|
790
|
+
signalChild(child, 'SIGKILL');
|
|
747
791
|
}
|
|
748
792
|
catch { /* already exited */ }
|
|
749
793
|
settle(() => rejectPromise(new GateProviderError(`provider ${record.id} timed out`, 'timeout')));
|
|
750
794
|
}, limits.timeoutMs);
|
|
751
795
|
const onAbort = () => {
|
|
752
796
|
try {
|
|
753
|
-
child
|
|
797
|
+
signalChild(child, 'SIGKILL');
|
|
754
798
|
}
|
|
755
799
|
catch { /* already exited */ }
|
|
756
800
|
settle(() => rejectPromise(new GateProviderError(`provider ${record.id} was cancelled`, 'cancelled')));
|
|
@@ -764,7 +808,7 @@ function runIsolatedRecord(record, args, limits) {
|
|
|
764
808
|
if (bytes > limits.maxOutputBytes) {
|
|
765
809
|
capped = true;
|
|
766
810
|
try {
|
|
767
|
-
child
|
|
811
|
+
signalChild(child, 'SIGKILL');
|
|
768
812
|
}
|
|
769
813
|
catch { /* already exited */ }
|
|
770
814
|
settle(() => rejectPromise(new GateProviderError(`provider ${record.id} exceeds its output cap`, 'rejected')));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ManagerCommandOptions {
|
|
2
|
+
platform?: NodeJS.Platform;
|
|
3
|
+
env?: NodeJS.ProcessEnv;
|
|
4
|
+
execute?: (file: string, args: string[], options?: Record<string, unknown>) => unknown;
|
|
5
|
+
execOptions?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveManagerCommand(
|
|
9
|
+
file: string,
|
|
10
|
+
args: string[],
|
|
11
|
+
options?: ManagerCommandOptions,
|
|
12
|
+
): { file: string; args: string[] };
|
|
13
|
+
|
|
14
|
+
export function executeManagerCommand(
|
|
15
|
+
file: string,
|
|
16
|
+
args: string[],
|
|
17
|
+
options?: ManagerCommandOptions,
|
|
18
|
+
): unknown;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
|
|
3
|
+
const cmdMetaChars = /([()\][%!^"`<>&|;, *?])/g;
|
|
4
|
+
|
|
3
5
|
function quoteCmdArgument(value) {
|
|
4
|
-
|
|
6
|
+
// Match cross-spawn's argument transport: first escape quotes and trailing
|
|
7
|
+
// backslashes for the target's argv parser, then protect cmd metacharacters
|
|
8
|
+
// outside quotes. Doubling % only works in batch source, not a /c operand.
|
|
9
|
+
const argument = String(value)
|
|
10
|
+
.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"')
|
|
11
|
+
.replace(/(?=(\\+?)?)\1$/, '$1$1');
|
|
12
|
+
// Batch wrappers forward operands through another cmd parse (typically %*),
|
|
13
|
+
// so retain one layer of caret escaping for that second parse.
|
|
14
|
+
return `"${argument}"`.replace(cmdMetaChars, '^$1').replace(cmdMetaChars, '^$1');
|
|
5
15
|
}
|
|
6
16
|
|
|
7
17
|
/**
|
|
@@ -17,7 +27,9 @@ export function resolveManagerCommand(file, args, options = {}) {
|
|
|
17
27
|
|
|
18
28
|
const env = options.env ?? process.env;
|
|
19
29
|
const commandInterpreter = env.ComSpec || env.COMSPEC || 'cmd.exe';
|
|
20
|
-
|
|
30
|
+
// The command itself is consumed by the first parse; only its operands need
|
|
31
|
+
// the second escape layer. Escape spaces in the path as well as operators.
|
|
32
|
+
const command = `"${[String(file).replace(cmdMetaChars, '^$1'), ...args.map(quoteCmdArgument)].join(' ')}"`;
|
|
21
33
|
return {
|
|
22
34
|
file: commandInterpreter,
|
|
23
35
|
args: ['/d', '/v:off', '/s', '/c', command],
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type InstallationMethod = 'npm' | 'web' | 'source';
|
|
2
|
+
export type InstallationRunMode = 'normal' | 'development';
|
|
3
|
+
export type InstallationChannel = 'stable' | 'next' | 'nightly' | 'edge';
|
|
4
|
+
export interface InstallationIdentity {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
runMode: InstallationRunMode;
|
|
7
|
+
method: InstallationMethod;
|
|
8
|
+
root: string;
|
|
9
|
+
updateStrategy: 'npm-global' | 'signed-web' | 'source-git';
|
|
10
|
+
managerExecutable: string | null;
|
|
11
|
+
channel: InstallationChannel;
|
|
12
|
+
edgePath: string | null;
|
|
13
|
+
checkOnStartup: boolean;
|
|
14
|
+
lastUpdateCheck: number | string | null;
|
|
15
|
+
updateCheckInterval: number;
|
|
16
|
+
recordedAt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface InstallationStatus {
|
|
19
|
+
state: 'aligned' | 'mismatch' | 'stale' | 'unrecorded';
|
|
20
|
+
identity: InstallationIdentity | null;
|
|
21
|
+
canonicalRoot?: string;
|
|
22
|
+
actualRoot: string;
|
|
23
|
+
actualMethod: InstallationMethod;
|
|
24
|
+
/** Root AIWG reads its corpus from. Equals actualRoot unless an edge launcher redirect applies. */
|
|
25
|
+
frameworkRoot: string;
|
|
26
|
+
/** Edge/customize mode only: the running executable's own package root (#2505). */
|
|
27
|
+
launcher: { root: string; method: InstallationMethod } | null;
|
|
28
|
+
drift: string[];
|
|
29
|
+
managerProbe: { state: 'usable' | 'failed'; error?: string } | null;
|
|
30
|
+
}
|
|
31
|
+
export const INSTALLATION_IDENTITY_VERSION: 1;
|
|
32
|
+
export const INSTALLATION_FILE: string;
|
|
33
|
+
export function installationFile(options?: Record<string, unknown>): string;
|
|
34
|
+
export function inferInstallationMethod(root: string): InstallationMethod;
|
|
35
|
+
export function createInstallationIdentity(options: Record<string, any>): InstallationIdentity;
|
|
36
|
+
export function saveInstallationIdentity(identity: InstallationIdentity, options?: Record<string, any>): InstallationIdentity;
|
|
37
|
+
export function loadInstallationIdentity(options?: Record<string, any>): InstallationIdentity | null;
|
|
38
|
+
export function inspectInstallation(options: Record<string, any>): InstallationStatus;
|
|
39
|
+
export interface InstallationCopy {
|
|
40
|
+
root: string;
|
|
41
|
+
executables: string[];
|
|
42
|
+
onPath: boolean;
|
|
43
|
+
registered: boolean;
|
|
44
|
+
}
|
|
45
|
+
export function inspectInstallationCopies(options?: Record<string, any>): InstallationCopy[];
|
|
46
|
+
export function formatInstallationDiagnostic(status: InstallationStatus, options?: { readOnlyCommand?: string }): string;
|
|
47
|
+
export function assertCanonicalInstallation(options: Record<string, any>): InstallationStatus;
|
|
48
|
+
export function adoptInstallation(options: Record<string, any>): InstallationStatus;
|
|
49
|
+
export function switchInstallation(options: Record<string, any>): InstallationStatus;
|
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
existsSync,
|
|
6
6
|
mkdirSync,
|
|
7
7
|
readFileSync,
|
|
8
|
+
readdirSync,
|
|
8
9
|
realpathSync,
|
|
9
10
|
renameSync,
|
|
10
11
|
statSync,
|
|
11
12
|
writeFileSync,
|
|
12
13
|
} from 'node:fs';
|
|
13
14
|
import path from 'node:path';
|
|
15
|
+
import os from 'node:os';
|
|
14
16
|
import { resolveUserConfigDir } from '../config/user-config-dir.mjs';
|
|
15
17
|
import { executeManagerCommand } from './manager-command.mjs';
|
|
16
18
|
|
|
@@ -269,13 +271,86 @@ export function inspectInstallation(options = {}) {
|
|
|
269
271
|
};
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
|
|
274
|
+
/** Inspect executable copies without launching them or changing installation identity. */
|
|
275
|
+
export function inspectInstallationCopies(options = {}) {
|
|
276
|
+
const env = options.env ?? process.env;
|
|
277
|
+
const home = options.homeDir ?? os.homedir();
|
|
278
|
+
const identity = options.identity ?? loadInstallationIdentity({ ...options, createIfMissing: false });
|
|
279
|
+
const copies = new Map();
|
|
280
|
+
const names = process.platform === 'win32' ? ['aiwg', 'aiwg.cmd', 'aiwg.exe'] : ['aiwg'];
|
|
281
|
+
|
|
282
|
+
function addDirectory(directory, onPath) {
|
|
283
|
+
for (const name of names) {
|
|
284
|
+
const executable = path.resolve(directory, name);
|
|
285
|
+
if (!executableIsUsable(executable)) continue;
|
|
286
|
+
let root = path.dirname(canonicalPath(executable));
|
|
287
|
+
while (!['aiwg', '@aiwg/cli'].includes(packageName(root))) {
|
|
288
|
+
const parent = path.dirname(root);
|
|
289
|
+
if (parent === root) { root = null; break; }
|
|
290
|
+
root = parent;
|
|
291
|
+
}
|
|
292
|
+
// npm's Windows command shims are files rather than symlinks.
|
|
293
|
+
if (!root && process.platform === 'win32') {
|
|
294
|
+
const candidate = path.join(directory, 'node_modules', 'aiwg');
|
|
295
|
+
if (packageName(candidate) === 'aiwg') root = canonicalPath(candidate);
|
|
296
|
+
}
|
|
297
|
+
if (!root) continue;
|
|
298
|
+
let copy = copies.get(root);
|
|
299
|
+
if (!copy) {
|
|
300
|
+
const status = identity ? inspectInstallation({ actualRoot: root, identity, createIfMissing: false }) : null;
|
|
301
|
+
copy = {
|
|
302
|
+
root,
|
|
303
|
+
executables: [],
|
|
304
|
+
onPath: false,
|
|
305
|
+
registered: Boolean(status && status.frameworkRoot === status.canonicalRoot),
|
|
306
|
+
};
|
|
307
|
+
copies.set(root, copy);
|
|
308
|
+
}
|
|
309
|
+
if (!copy.executables.includes(executable)) copy.executables.push(executable);
|
|
310
|
+
copy.onPath ||= onPath;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const pathDirectories = (env.PATH ?? '').split(path.delimiter);
|
|
315
|
+
for (const directory of new Set(pathDirectories)) addDirectory(directory, true);
|
|
316
|
+
|
|
317
|
+
const nvmRoots = new Set([path.join(home, '.nvm'), env.NVM_DIR].filter(Boolean));
|
|
318
|
+
// A running/registered nvm copy or NVM_BIN also identifies custom nvm homes.
|
|
319
|
+
for (const location of [options.actualRoot, identity?.root, env.NVM_BIN, ...pathDirectories]) {
|
|
320
|
+
if (!location) continue;
|
|
321
|
+
let directory = path.resolve(location);
|
|
322
|
+
while (path.dirname(directory) !== directory) {
|
|
323
|
+
if (path.basename(directory) === 'node' && path.basename(path.dirname(directory)) === 'versions') {
|
|
324
|
+
nvmRoots.add(path.dirname(path.dirname(directory)));
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
directory = path.dirname(directory);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const nvmRoot of nvmRoots) {
|
|
331
|
+
const versions = path.join(nvmRoot, 'versions', 'node');
|
|
332
|
+
try {
|
|
333
|
+
for (const version of readdirSync(versions, { withFileTypes: true })) {
|
|
334
|
+
if (version.isDirectory()) addDirectory(path.join(versions, version.name, 'bin'), false);
|
|
335
|
+
}
|
|
336
|
+
} catch { /* nvm is optional and may no longer be installed. */ }
|
|
337
|
+
}
|
|
338
|
+
return [...copies.values()];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function formatInstallationDiagnostic(status, options = {}) {
|
|
273
342
|
if (status.state === 'aligned') return 'Canonical installation is aligned.';
|
|
274
343
|
return [
|
|
275
|
-
|
|
344
|
+
options.readOnlyCommand
|
|
345
|
+
? `installation identity drift detected; \`${options.readOnlyCommand}\` is read-only and continues, but update, refresh, and deployment stay blocked until the identity is repaired.`
|
|
346
|
+
: 'AIWG installation identity drift detected; update and refresh are blocked.',
|
|
347
|
+
`Running installation: ${status.actualMethod} at ${status.actualRoot}`,
|
|
348
|
+
`Registered installation: ${status.identity?.method ?? 'unrecorded'} at ${status.identity?.root ?? '(none)'}`,
|
|
276
349
|
...status.drift.map((item) => `- ${item}`),
|
|
350
|
+
'Diagnose PATH and nvm copies: aiwg doctor',
|
|
277
351
|
'Inspect: aiwg installation show',
|
|
278
|
-
|
|
352
|
+
`Register this running copy deliberately: aiwg installation adopt --method ${status.actualMethod} --yes`,
|
|
353
|
+
'To keep the registered install, put its executable first on PATH; remove unwanted npm copies using npm from their Node prefix (npm uninstall -g aiwg).',
|
|
279
354
|
'Switch deliberately: aiwg installation switch --root <path> --method <npm|web|source> [--manager <absolute-path>]',
|
|
280
355
|
].join('\n');
|
|
281
356
|
}
|
package/dist/src/issues/cli.js
CHANGED
|
@@ -310,9 +310,9 @@ async function handleClose(issues, rest, args) {
|
|
|
310
310
|
}
|
|
311
311
|
async function handleIndex(issues, rest, args) {
|
|
312
312
|
if (rest[0] !== 'rebuild')
|
|
313
|
-
throw new Error('Usage: aiwg issue index rebuild');
|
|
314
|
-
const index = await issues.rebuildIssueIndex();
|
|
315
|
-
printJsonOrText(args, index, `
|
|
313
|
+
throw new Error('Usage: aiwg issue index rebuild [--force]');
|
|
314
|
+
const index = await issues.rebuildIssueIndex({ force: args.flags.has('force') });
|
|
315
|
+
printJsonOrText(args, index, `Local issue index ready (${index.issues.length} issues)`);
|
|
316
316
|
}
|
|
317
317
|
function requiredStringFlag(args, flag) {
|
|
318
318
|
const value = stringFlag(args, flag);
|
|
@@ -406,6 +406,6 @@ function printUsage() {
|
|
|
406
406
|
aiwg issue sync map-comments <KEY> --map-file path [--json]
|
|
407
407
|
aiwg issue comment <KEY> --body "..." [--author name]
|
|
408
408
|
aiwg issue close <KEY> [--reason "..."]
|
|
409
|
-
aiwg issue index rebuild`);
|
|
409
|
+
aiwg issue index rebuild [--force]`);
|
|
410
410
|
}
|
|
411
411
|
//# sourceMappingURL=cli.js.map
|