@aiwg/cli 2026.10.1 → 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/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- 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 +107 -15
- 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/repair.js +9 -4
- 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 +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- 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 +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- 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 +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -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 +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -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 +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -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-health.mjs +14 -3
- 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 +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- 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 +25 -6
- 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 +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- 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/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -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/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- 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 +366 -56
- 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 +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- 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/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- 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 +127 -82
- 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-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +647 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -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 +53 -23
- 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/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- 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/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- 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 +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- 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 +30 -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 +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- 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 +84 -46
- package/dist/src/sessions/index.js +2 -0
- 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 +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- 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 +195 -14
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- 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 +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- 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/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- 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/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- 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 +360 -0
- package/tools/providers/omp-transport.mjs +194 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- 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 +439 -0
- 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 +2916 -0
|
@@ -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';
|
|
@@ -7,6 +8,7 @@ import { GateProviderEntrySchema } from '../../extensions/manifest.js';
|
|
|
7
8
|
import { artifactDigest } from '../../decision/validate.js';
|
|
8
9
|
import { providerSourceDigest } from './types.js';
|
|
9
10
|
import { ISOLATED_CHILD_SOURCE } from './isolated-child-source.js';
|
|
11
|
+
import { positiveTimeoutMs } from '../../process/bounded-child-process.mjs';
|
|
10
12
|
/**
|
|
11
13
|
* Addon/extension metric-provider loading, isolated rework (P1-P5).
|
|
12
14
|
*
|
|
@@ -52,6 +54,10 @@ export const GATE_PROVIDER_MAX_TIMEOUT_MS = 30000;
|
|
|
52
54
|
export const GATE_PROVIDER_DEFAULT_MAX_RECORDS = 50000;
|
|
53
55
|
/** Largest accepted isolated-run stdout body (single JSON message). */
|
|
54
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;
|
|
55
61
|
export class GateProviderError extends Error {
|
|
56
62
|
code;
|
|
57
63
|
constructor(message, code) {
|
|
@@ -316,11 +322,16 @@ function prepareIsolatedRun(record, args) {
|
|
|
316
322
|
// OS sandbox (C1c/C1d): bubblewrap is the isolation boundary.
|
|
317
323
|
// ---------------------------------------------------------------------------
|
|
318
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'];
|
|
319
326
|
const SANDBOX_NODE_PATH = '/aiwg/bin/node';
|
|
320
327
|
const SANDBOX_CWD = '/tmp';
|
|
321
328
|
const SANDBOX_SELF_TEST_TIMEOUT_MS = 15000;
|
|
322
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
|
+
];
|
|
323
333
|
const DRIVER_NODE_ARGS = [
|
|
334
|
+
...MEMORY_NODE_ARGS,
|
|
324
335
|
'--experimental-vm-modules', '--permission', '--disallow-code-generation-from-strings',
|
|
325
336
|
'--no-warnings', '--input-type=module', '-e', ISOLATED_CHILD_SOURCE,
|
|
326
337
|
];
|
|
@@ -329,8 +340,16 @@ const RESULT_FRAME = 'AIWG-RESULT ';
|
|
|
329
340
|
const SANDBOX_SELF_TEST_SOURCE = [
|
|
330
341
|
"import fs from 'node:fs';",
|
|
331
342
|
"import net from 'node:net';",
|
|
343
|
+
"import v8 from 'node:v8';",
|
|
332
344
|
"const input = JSON.parse(fs.readFileSync(0, 'utf8'));",
|
|
333
|
-
'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 };',
|
|
334
353
|
'for (const key of Object.keys(process.env)) out.env[key] = String(process.env[key]);',
|
|
335
354
|
"for (const name of input.namespaces) { try { out.ns[name] = fs.readlinkSync('/proc/self/ns/' + name); } catch { out.ns[name] = null; } }",
|
|
336
355
|
'for (const path of input.paths) { try { fs.lstatSync(path); out.visible.push(path); } catch { /* absent */ } }',
|
|
@@ -348,7 +367,7 @@ let sandboxOverride = null;
|
|
|
348
367
|
let sandboxVerdict;
|
|
349
368
|
let runtimeBindCache;
|
|
350
369
|
/**
|
|
351
|
-
* 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
|
|
352
371
|
* missing binary, or one that fails the self-test) or reset with `null`.
|
|
353
372
|
* It can only make the runner refuse: whatever path is configured must still
|
|
354
373
|
* pass the capability self-test before any provider runs.
|
|
@@ -359,6 +378,13 @@ export function configureIsolationSandboxForTests(options) {
|
|
|
359
378
|
}
|
|
360
379
|
function resolveBwrapPath() {
|
|
361
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) {
|
|
362
388
|
for (const candidate of candidates) {
|
|
363
389
|
try {
|
|
364
390
|
const status = statSync(candidate);
|
|
@@ -475,6 +501,10 @@ function sandboxCommand(nodeArgs) {
|
|
|
475
501
|
if (bwrap === null) {
|
|
476
502
|
throw new GateProviderError('bundle providers require bubblewrap (bwrap) for OS-level isolation and none was found: refusing to run', 'sandbox-unavailable');
|
|
477
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
|
+
}
|
|
478
508
|
const args = [
|
|
479
509
|
'--unshare-all', '--unshare-user', '--disable-userns',
|
|
480
510
|
'--die-with-parent', '--new-session', '--clearenv', '--cap-drop', 'ALL',
|
|
@@ -482,7 +512,13 @@ function sandboxCommand(nodeArgs) {
|
|
|
482
512
|
for (const [source, target] of runtimeBinds())
|
|
483
513
|
args.push('--ro-bind', source, target);
|
|
484
514
|
args.push('--tmpfs', SANDBOX_CWD, '--proc', '/proc', '--dev', '/dev', '--chdir', SANDBOX_CWD, '--', SANDBOX_NODE_PATH, ...nodeArgs);
|
|
485
|
-
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
|
+
};
|
|
486
522
|
}
|
|
487
523
|
/**
|
|
488
524
|
* The exact sandboxed launch used for every provider run: bubblewrap with
|
|
@@ -490,7 +526,8 @@ function sandboxCommand(nodeArgs) {
|
|
|
490
526
|
* cleared environment, new session, die-with-parent, read-only node runtime
|
|
491
527
|
* binds, a private tmpfs, and node with `--permission` (no fs, no child
|
|
492
528
|
* processes, no workers) plus `--disallow-code-generation-from-strings`.
|
|
493
|
-
*
|
|
529
|
+
* prlimit applies a hard data cap (including off-heap memory); V8 has a
|
|
530
|
+
* separate heap cap. The spawn environment itself is empty.
|
|
494
531
|
*/
|
|
495
532
|
export function isolationLaunchPlan() {
|
|
496
533
|
return sandboxCommand(DRIVER_NODE_ARGS);
|
|
@@ -514,11 +551,11 @@ function hostProbePaths() {
|
|
|
514
551
|
}
|
|
515
552
|
function selfTestInput(port) {
|
|
516
553
|
const paths = hostProbePaths();
|
|
517
|
-
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 }) };
|
|
518
555
|
}
|
|
519
556
|
function judgeSelfTest(stdout, status, paths, port) {
|
|
520
557
|
const report = {
|
|
521
|
-
ok: false, reason: '', envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
558
|
+
ok: false, reason: '', envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
522
559
|
};
|
|
523
560
|
let observed;
|
|
524
561
|
try {
|
|
@@ -549,7 +586,15 @@ function judgeSelfTest(stdout, status, paths, port) {
|
|
|
549
586
|
}
|
|
550
587
|
report.hostPathsVisible = (observed.visible ?? []).filter(path => paths.includes(path));
|
|
551
588
|
report.connect = observed.connect ?? null;
|
|
589
|
+
report.memory = observed.memory ?? null;
|
|
552
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
|
+
}
|
|
553
598
|
if (report.envKeys.length)
|
|
554
599
|
problems.push(`environment leaks ${report.envKeys.join(',')}`);
|
|
555
600
|
if (report.sharedNamespaces.length)
|
|
@@ -564,7 +609,7 @@ function judgeSelfTest(stdout, status, paths, port) {
|
|
|
564
609
|
}
|
|
565
610
|
/**
|
|
566
611
|
* Runs the sandbox capability self-test asynchronously and returns the
|
|
567
|
-
* observations (empty env, private namespaces, no host paths, optional
|
|
612
|
+
* observations (enforced memory caps, empty env, private namespaces, no host paths, optional
|
|
568
613
|
* loopback connect to a parent listener on `connectPort`).
|
|
569
614
|
*/
|
|
570
615
|
export function probeIsolationSandbox(options) {
|
|
@@ -572,20 +617,20 @@ export function probeIsolationSandbox(options) {
|
|
|
572
617
|
let plan;
|
|
573
618
|
let probe;
|
|
574
619
|
try {
|
|
575
|
-
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]);
|
|
576
621
|
probe = selfTestInput(port);
|
|
577
622
|
}
|
|
578
623
|
catch (error) {
|
|
579
624
|
return Promise.resolve({
|
|
580
625
|
ok: false, reason: error instanceof Error ? error.message : 'sandbox unavailable',
|
|
581
|
-
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
626
|
+
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
582
627
|
});
|
|
583
628
|
}
|
|
584
629
|
return new Promise((resolvePromise) => {
|
|
585
630
|
const child = spawn(plan.command, plan.args, { env: plan.env, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
586
631
|
const chunks = [];
|
|
587
632
|
const timer = setTimeout(() => { try {
|
|
588
|
-
child
|
|
633
|
+
signalChild(child, 'SIGKILL');
|
|
589
634
|
}
|
|
590
635
|
catch { /* exited */ } }, SANDBOX_SELF_TEST_TIMEOUT_MS);
|
|
591
636
|
child.stdout?.on('data', (chunk) => { chunks.push(chunk); });
|
|
@@ -594,7 +639,7 @@ export function probeIsolationSandbox(options) {
|
|
|
594
639
|
clearTimeout(timer);
|
|
595
640
|
resolvePromise({
|
|
596
641
|
ok: false, reason: `sandbox failed to start: ${error.message}`,
|
|
597
|
-
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null,
|
|
642
|
+
envKeys: [], sharedNamespaces: [], hostPathsVisible: [], connect: null, memory: null,
|
|
598
643
|
});
|
|
599
644
|
});
|
|
600
645
|
child.on('close', (code) => {
|
|
@@ -606,15 +651,15 @@ export function probeIsolationSandbox(options) {
|
|
|
606
651
|
}
|
|
607
652
|
/**
|
|
608
653
|
* Fail-closed gate before every provider run: bwrap must exist and pass the
|
|
609
|
-
* capability self-test (cached per bwrap path for the process lifetime).
|
|
654
|
+
* capability self-test (cached per bwrap/prlimit path for the process lifetime).
|
|
610
655
|
* There is no unsandboxed fallback.
|
|
611
656
|
*/
|
|
612
657
|
function ensureIsolationSandbox() {
|
|
613
|
-
const key = resolveBwrapPath()
|
|
658
|
+
const key = JSON.stringify([resolveBwrapPath(), resolvePrlimitPath()]);
|
|
614
659
|
if (sandboxVerdict === undefined || sandboxVerdict.key !== key) {
|
|
615
660
|
let verdict;
|
|
616
661
|
try {
|
|
617
|
-
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]);
|
|
618
663
|
const probe = selfTestInput(null);
|
|
619
664
|
const completed = spawnSync(plan.command, plan.args, {
|
|
620
665
|
env: plan.env, input: probe.input, encoding: 'utf8', timeout: SANDBOX_SELF_TEST_TIMEOUT_MS,
|
|
@@ -684,7 +729,8 @@ function runIsolatedRecordSync(record, args, limits) {
|
|
|
684
729
|
const completed = spawnSync(plan.command, plan.args, {
|
|
685
730
|
env: plan.env,
|
|
686
731
|
input,
|
|
687
|
-
|
|
732
|
+
// Validated upstream; clamped here so the bound holds at the call site.
|
|
733
|
+
timeout: positiveTimeoutMs(limits.timeoutMs, GATE_PROVIDER_DEFAULT_TIMEOUT_MS),
|
|
688
734
|
killSignal: 'SIGKILL',
|
|
689
735
|
maxBuffer: limits.maxOutputBytes,
|
|
690
736
|
encoding: 'utf8',
|
|
@@ -741,14 +787,14 @@ function runIsolatedRecord(record, args, limits) {
|
|
|
741
787
|
}
|
|
742
788
|
const timer = setTimeout(() => {
|
|
743
789
|
try {
|
|
744
|
-
child
|
|
790
|
+
signalChild(child, 'SIGKILL');
|
|
745
791
|
}
|
|
746
792
|
catch { /* already exited */ }
|
|
747
793
|
settle(() => rejectPromise(new GateProviderError(`provider ${record.id} timed out`, 'timeout')));
|
|
748
794
|
}, limits.timeoutMs);
|
|
749
795
|
const onAbort = () => {
|
|
750
796
|
try {
|
|
751
|
-
child
|
|
797
|
+
signalChild(child, 'SIGKILL');
|
|
752
798
|
}
|
|
753
799
|
catch { /* already exited */ }
|
|
754
800
|
settle(() => rejectPromise(new GateProviderError(`provider ${record.id} was cancelled`, 'cancelled')));
|
|
@@ -762,7 +808,7 @@ function runIsolatedRecord(record, args, limits) {
|
|
|
762
808
|
if (bytes > limits.maxOutputBytes) {
|
|
763
809
|
capped = true;
|
|
764
810
|
try {
|
|
765
|
-
child
|
|
811
|
+
signalChild(child, 'SIGKILL');
|
|
766
812
|
}
|
|
767
813
|
catch { /* already exited */ }
|
|
768
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,10 +27,12 @@ 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
|
-
args: ['/d', '/s', '/c', command],
|
|
35
|
+
args: ['/d', '/v:off', '/s', '/c', command],
|
|
24
36
|
// The payload above already contains cmd.exe-native quoting. Instruct
|
|
25
37
|
// Node not to apply MSVCRT escaping to those embedded quotes (#173).
|
|
26
38
|
windowsVerbatimArguments: true,
|
|
@@ -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;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boundedExecFileSync } from '../process/bounded-child-process.mjs';
|
|
2
2
|
import {
|
|
3
3
|
accessSync,
|
|
4
4
|
constants,
|
|
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
|
|
|
@@ -50,7 +52,22 @@ function resolveExecutable(name, options = {}) {
|
|
|
50
52
|
if (existsSync(besideNode)) return canonicalPath(besideNode);
|
|
51
53
|
try {
|
|
52
54
|
const finder = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
53
|
-
|
|
55
|
+
// Go/no-go review, blocker 1B: this runs unconditionally on first
|
|
56
|
+
// launch (creating the installation identity) for *every* command,
|
|
57
|
+
// including a credential-handling one — before `bin/aiwg.mjs` even
|
|
58
|
+
// knows which command that is. `which`/`where.exe` only need `PATH`
|
|
59
|
+
// (and, on Windows, `PATHEXT`/`SystemRoot` for its own resolution), so
|
|
60
|
+
// this never inherits the whole parent environment the way an absent
|
|
61
|
+
// `env` option otherwise would.
|
|
62
|
+
const lookupEnv = {
|
|
63
|
+
PATH: env.PATH ?? '',
|
|
64
|
+
...(process.platform === 'win32' ? { PATHEXT: env.PATHEXT ?? '', SystemRoot: env.SystemRoot ?? '' } : {}),
|
|
65
|
+
};
|
|
66
|
+
const found = boundedExecFileSync(finder, [name], 'quick', {
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
69
|
+
env: lookupEnv,
|
|
70
|
+
})
|
|
54
71
|
.split(/\r?\n/, 1)[0]?.trim();
|
|
55
72
|
return found ? canonicalPath(found) : null;
|
|
56
73
|
} catch {
|
|
@@ -254,13 +271,86 @@ export function inspectInstallation(options = {}) {
|
|
|
254
271
|
};
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
|
|
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 = {}) {
|
|
258
342
|
if (status.state === 'aligned') return 'Canonical installation is aligned.';
|
|
259
343
|
return [
|
|
260
|
-
|
|
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)'}`,
|
|
261
349
|
...status.drift.map((item) => `- ${item}`),
|
|
350
|
+
'Diagnose PATH and nvm copies: aiwg doctor',
|
|
262
351
|
'Inspect: aiwg installation show',
|
|
263
|
-
|
|
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).',
|
|
264
354
|
'Switch deliberately: aiwg installation switch --root <path> --method <npm|web|source> [--manager <absolute-path>]',
|
|
265
355
|
].join('\n');
|
|
266
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
|
package/dist/src/issues/local.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { constants } from 'fs';
|
|
2
|
-
import { mkdir, open, readFile, readdir, rename, unlink, writeFile } from 'fs/promises';
|
|
2
|
+
import { mkdir, open, readFile, readdir, rename, stat, unlink, writeFile } from 'fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { basename, dirname, join, relative, resolve, sep } from 'path';
|
|
4
5
|
import { hostname } from 'os';
|
|
5
6
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
@@ -328,15 +329,32 @@ export class LocalIssueProviderCore {
|
|
|
328
329
|
const idx = events.findIndex((event) => event.event_id === since);
|
|
329
330
|
return idx >= 0 ? events.slice(idx + 1) : events;
|
|
330
331
|
}
|
|
331
|
-
async rebuildIssueIndex() {
|
|
332
|
+
async rebuildIssueIndex(options = {}) {
|
|
332
333
|
await this.ensureLayout();
|
|
333
334
|
return this.lockManager.withIndexLock('rebuild issue index', async () => {
|
|
334
335
|
const itemDir = this.path('items');
|
|
335
|
-
const entries = await readdir(itemDir, { withFileTypes: true })
|
|
336
|
+
const entries = (await readdir(itemDir, { withFileTypes: true }))
|
|
337
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
338
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
339
|
+
const inputs = await Promise.all(entries.map(async (entry) => {
|
|
340
|
+
const metadata = await stat(join(itemDir, entry.name));
|
|
341
|
+
return [entry.name, metadata.mtimeMs, metadata.ctimeMs, metadata.size];
|
|
342
|
+
}));
|
|
343
|
+
const inputFingerprint = createHash('sha256').update(JSON.stringify([1, inputs])).digest('hex');
|
|
344
|
+
if (!options.force) {
|
|
345
|
+
try {
|
|
346
|
+
const cached = JSON.parse(await readFile(this.path('index/issues.index.json'), 'utf-8'));
|
|
347
|
+
if (cached?.provider === 'local' && Array.isArray(cached.issues) && cached.inputFingerprint === inputFingerprint) {
|
|
348
|
+
return cached;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
if (!(error instanceof SyntaxError) && !(isNodeError(error) && error.code === 'ENOENT'))
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
336
356
|
const issues = [];
|
|
337
357
|
for (const entry of entries) {
|
|
338
|
-
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
339
|
-
continue;
|
|
340
358
|
const record = parseIssueMarkdown(await readFile(join(itemDir, entry.name), 'utf-8'));
|
|
341
359
|
issues.push({
|
|
342
360
|
id: record.fields.id,
|
|
@@ -355,6 +373,7 @@ export class LocalIssueProviderCore {
|
|
|
355
373
|
const index = {
|
|
356
374
|
provider: 'local',
|
|
357
375
|
rebuilt_at: new Date().toISOString(),
|
|
376
|
+
inputFingerprint,
|
|
358
377
|
issues,
|
|
359
378
|
};
|
|
360
379
|
await atomicWrite(this.path('index/issues.index.json'), `${JSON.stringify(index, null, 2)}\n`);
|
package/dist/src/jobs/runner.js
CHANGED
|
@@ -184,7 +184,7 @@ async function acquireLock(stateRoot, flow) {
|
|
|
184
184
|
const lock = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
185
185
|
const tooOld = !lock.startedAt || Date.now() - Date.parse(lock.startedAt) > (flow.spec.workItem.claimTtlSeconds ?? 900) * 1000;
|
|
186
186
|
let alive = false;
|
|
187
|
-
if (
|
|
187
|
+
if (Number.isSafeInteger(lock.pid) && (lock.pid ?? 0) > 0) {
|
|
188
188
|
try {
|
|
189
189
|
process.kill(lock.pid, 0);
|
|
190
190
|
alive = true;
|
package/dist/src/lint/cli.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @issue #810
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'fs';
|
|
16
|
+
import { readAiwgConfigCandidate } from '../config/aiwg-config.js';
|
|
16
17
|
import path from 'path';
|
|
17
18
|
import { fileURLToPath } from 'url';
|
|
18
19
|
import { discoverRulesets } from './loader.js';
|
|
@@ -71,9 +72,15 @@ function parseArgs(args) {
|
|
|
71
72
|
else if (arg === '--format' && i + 1 < args.length) {
|
|
72
73
|
options.format = args[++i];
|
|
73
74
|
}
|
|
75
|
+
else if (arg === '--startup-plan' && i + 1 < args.length) {
|
|
76
|
+
options.startupPlan = args[++i];
|
|
77
|
+
}
|
|
74
78
|
else if (arg === '--ci') {
|
|
75
79
|
options.ci = true;
|
|
76
80
|
}
|
|
81
|
+
else if (arg === '--allow-empty') {
|
|
82
|
+
options.allowEmpty = true;
|
|
83
|
+
}
|
|
77
84
|
else if (arg === '--fail-on' && i + 1 < args.length) {
|
|
78
85
|
options.failOn = args[++i];
|
|
79
86
|
}
|
|
@@ -102,6 +109,14 @@ function parseArgs(args) {
|
|
|
102
109
|
export async function main(args) {
|
|
103
110
|
const options = parseArgs(args);
|
|
104
111
|
const cwd = process.cwd();
|
|
112
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
113
|
+
console.log('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--allow-empty] [--fail-on error|warn|info] [--no-gitignore]');
|
|
114
|
+
console.log(' --ci Fail on lint violations, zero files checked or zero rules applied');
|
|
115
|
+
console.log(' --allow-empty Allow zero files or zero applied rules under --ci');
|
|
116
|
+
console.log(' --startup-plan <json> Guard a complete projected startup set { provider, files: [{ path, bytes | content }] }');
|
|
117
|
+
console.log(' --list-rulesets | --list-rules <ruleset>');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
105
120
|
// Discover available rulesets. `cwd` is where the user runs the lint (used
|
|
106
121
|
// to find deployed `.aiwg/frameworks/*/lint/` overlays); `AIWG_ROOT` is the
|
|
107
122
|
// AIWG install (used to find shipped `agentic/code/frameworks/*/lint/`).
|
|
@@ -126,7 +141,7 @@ export async function main(args) {
|
|
|
126
141
|
}
|
|
127
142
|
// Require a target for actual linting
|
|
128
143
|
if (!options.target) {
|
|
129
|
-
console.error('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--fail-on error|warn|info] [--no-gitignore]');
|
|
144
|
+
console.error('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--allow-empty] [--fail-on error|warn|info] [--no-gitignore]');
|
|
130
145
|
console.error(' aiwg lint --list-rulesets');
|
|
131
146
|
console.error(' aiwg lint --list-rules <ruleset>');
|
|
132
147
|
process.exitCode = 1;
|
|
@@ -159,9 +174,20 @@ export async function main(args) {
|
|
|
159
174
|
}
|
|
160
175
|
return;
|
|
161
176
|
}
|
|
177
|
+
let startupBudget;
|
|
178
|
+
if (options.startupPlan) {
|
|
179
|
+
const plan = JSON.parse(fs.readFileSync(path.resolve(cwd, options.startupPlan), 'utf8'));
|
|
180
|
+
if (!plan || typeof plan.provider !== 'string' || !Array.isArray(plan.files)) {
|
|
181
|
+
throw new Error('Startup plan must contain provider and files (the complete assembled startup set).');
|
|
182
|
+
}
|
|
183
|
+
startupBudget = { provider: plan.provider, files: plan.files, config: await readAiwgConfigCandidate(cwd) ?? {} };
|
|
184
|
+
}
|
|
162
185
|
// Run lint
|
|
163
186
|
const result = await runLint(targetDir, selectedRulesets, {
|
|
187
|
+
startupBudget,
|
|
164
188
|
recursive: options.recursive,
|
|
189
|
+
ci: options.ci,
|
|
190
|
+
allowEmpty: options.allowEmpty,
|
|
165
191
|
failOn: options.failOn,
|
|
166
192
|
respectGitignore: options.respectGitignore ?? true,
|
|
167
193
|
});
|
package/dist/src/lint/loader.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @issue #810
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'fs';
|
|
11
|
+
import { ruleActivationRuleset } from './rule-activation.js';
|
|
11
12
|
import fsp from 'fs/promises';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import { projectAiwgPath } from '../config/project-artifacts.js';
|
|
@@ -178,7 +179,7 @@ export async function loadRuleset(lintDir, frameworkId) {
|
|
|
178
179
|
* 2. Deployed frameworks: .aiwg/frameworks/{id}/lint/
|
|
179
180
|
*/
|
|
180
181
|
export async function discoverRulesets(cwd, frameworkRoot) {
|
|
181
|
-
const rulesets = [];
|
|
182
|
+
const rulesets = [ruleActivationRuleset];
|
|
182
183
|
// Check framework source directories
|
|
183
184
|
const frameworksDir = path.join(frameworkRoot, 'agentic', 'code', 'frameworks');
|
|
184
185
|
if (fs.existsSync(frameworksDir)) {
|