@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Shared runtime resolver for config consumers and direct preflight tools. */
|
|
2
|
+
export const DEFAULT_DISPATCH_HOST_THRESHOLDS = Object.freeze({
|
|
3
|
+
min_avail_mem_gb: 8,
|
|
4
|
+
max_load_ratio: 1.0,
|
|
5
|
+
max_mem_psi_avg10: 30,
|
|
6
|
+
max_mem_psi_avg60: 10,
|
|
7
|
+
// 08 brief/evidence retention: stop new admissions when less than 2 GiB free remains.
|
|
8
|
+
min_free_disk_gb: 2,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configured layers can only tighten one another and the conservative defaults;
|
|
13
|
+
* minimums take MAX and maximums take MIN across build, admission and task.
|
|
14
|
+
* Validate each layer before merging so an invalid value cannot be hidden.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveDispatchHostThresholds(buildDispatch, admissionHostResources, overlay) {
|
|
17
|
+
const configured = { ...DEFAULT_DISPATCH_HOST_THRESHOLDS };
|
|
18
|
+
const keys = [...Object.keys(DEFAULT_DISPATCH_HOST_THRESHOLDS), 'max_workers'];
|
|
19
|
+
for (const layer of [buildDispatch, admissionHostResources, overlay]) {
|
|
20
|
+
if (layer === undefined) continue;
|
|
21
|
+
if (layer === null || typeof layer !== 'object' || Array.isArray(layer)) {
|
|
22
|
+
throw new Error('OG_CONFIG_REFERENCE dispatch thresholds: must be an object');
|
|
23
|
+
}
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
const value = layer[key];
|
|
26
|
+
if (value === undefined) continue;
|
|
27
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
28
|
+
throw new Error(`OG_CONFIG_REFERENCE dispatch threshold ${key}: must be a non-negative number`);
|
|
29
|
+
}
|
|
30
|
+
if (key === 'max_workers' && (!Number.isInteger(value) || value < 1)) {
|
|
31
|
+
throw new Error('OG_CONFIG_REFERENCE dispatch threshold max_workers: must be an integer >= 1 when set');
|
|
32
|
+
}
|
|
33
|
+
configured[key] = configured[key] === undefined ? value
|
|
34
|
+
: key.startsWith('min_') ? Math.max(configured[key], value) : Math.min(configured[key], value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { ...DEFAULT_DISPATCH_HOST_THRESHOLDS, ...configured };
|
|
38
|
+
}
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* @module src/config/gitignore
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from 'fs/promises';
|
|
11
|
+
import { existsSync, realpathSync } from 'fs';
|
|
11
12
|
import * as path from 'path';
|
|
12
13
|
import { execFile } from 'child_process';
|
|
13
14
|
import { promisify } from 'util';
|
|
15
|
+
import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
|
|
14
16
|
const execFileAsync = promisify(execFile);
|
|
15
17
|
const GITIGNORE_PROBE_BASENAME = '.aiwg-ignore-probe';
|
|
16
18
|
// ===========================
|
|
@@ -205,4 +207,52 @@ export async function appendGitignore(projectRoot, patterns) {
|
|
|
205
207
|
created: !fileExists && toAdd.length > 0,
|
|
206
208
|
};
|
|
207
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Drop files git ignores (#2555).
|
|
212
|
+
*
|
|
213
|
+
* Shared by lint and artifact discovery. `git check-ignore` is asked once for
|
|
214
|
+
* the whole set so the semantics are git's own (negations, nested
|
|
215
|
+
* `.gitignore`, `core.excludesFile`) rather than a re-implementation. Outside a
|
|
216
|
+
* repository, or without git, every file is kept. NUL delimiters preserve
|
|
217
|
+
* whitespace and quoting in filenames.
|
|
218
|
+
*/
|
|
219
|
+
export function dropGitignored(targetDir, files) {
|
|
220
|
+
if (files.length === 0)
|
|
221
|
+
return files;
|
|
222
|
+
try {
|
|
223
|
+
// Avoid starting Git for synthetic/source trees outside a repository. Resolve
|
|
224
|
+
// symlinked working directories just as Git does, and probe on every call so
|
|
225
|
+
// initializing a repository between scans takes effect immediately. Explicit
|
|
226
|
+
// Git environment and bare repositories still use Git's own discovery.
|
|
227
|
+
if (!process.env.GIT_DIR && !process.env.GIT_WORK_TREE && !process.env.GIT_COMMON_DIR) {
|
|
228
|
+
let directory = realpathSync(targetDir);
|
|
229
|
+
for (;;) {
|
|
230
|
+
if (existsSync(path.join(directory, '.git')) ||
|
|
231
|
+
(existsSync(path.join(directory, 'HEAD')) &&
|
|
232
|
+
(existsSync(path.join(directory, 'objects')) || existsSync(path.join(directory, 'commondir')))))
|
|
233
|
+
break;
|
|
234
|
+
const parent = path.dirname(directory);
|
|
235
|
+
if (parent === directory)
|
|
236
|
+
return files;
|
|
237
|
+
directory = parent;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const result = boundedSpawnSync('git', ['check-ignore', '-z', '--stdin'], 'quick', {
|
|
241
|
+
cwd: targetDir,
|
|
242
|
+
input: `${files.join('\0')}\0`,
|
|
243
|
+
encoding: 'utf8',
|
|
244
|
+
});
|
|
245
|
+
// 0 = some paths ignored, 1 = none ignored, anything else = not a repo or
|
|
246
|
+
// git unavailable, in which case nothing is filtered.
|
|
247
|
+
if (result.error || (result.status !== 0 && result.status !== 1))
|
|
248
|
+
return files;
|
|
249
|
+
const ignored = new Set((result.stdout ?? '').split('\0').filter(Boolean));
|
|
250
|
+
if (ignored.size === 0)
|
|
251
|
+
return files;
|
|
252
|
+
return files.filter((file) => !ignored.has(file));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return files;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
208
258
|
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const OVERRIDABLE_INDEX_GRAPHS = ['project', 'codebase'];
|
|
2
|
+
/** Shared by config validation and graph loading, including non-CLI callers. */
|
|
3
|
+
export function validateIndexGraphOverrides(overrides) {
|
|
4
|
+
if (overrides === undefined)
|
|
5
|
+
return [];
|
|
6
|
+
if (typeof overrides !== 'object' || overrides === null || Array.isArray(overrides)) {
|
|
7
|
+
return ['index.graphOverrides: must be an object mapping supported built-in graph names to overrides'];
|
|
8
|
+
}
|
|
9
|
+
const errors = [];
|
|
10
|
+
for (const [name, raw] of Object.entries(overrides)) {
|
|
11
|
+
const where = `index.graphOverrides.${name}`;
|
|
12
|
+
if (!OVERRIDABLE_INDEX_GRAPHS.includes(name)) {
|
|
13
|
+
errors.push(`${where}: unsupported built-in graph override (supported: ${OVERRIDABLE_INDEX_GRAPHS.join(', ')})`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
17
|
+
errors.push(`${where}: must be an object`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const override = raw;
|
|
21
|
+
for (const field of Object.keys(override)) {
|
|
22
|
+
if (field !== 'scanDirs' && field !== 'extensions') {
|
|
23
|
+
errors.push(`${where}.${field}: unknown field (supported: scanDirs, extensions)`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const field of ['scanDirs', 'extensions']) {
|
|
27
|
+
const value = override[field];
|
|
28
|
+
if (value !== undefined && (!Array.isArray(value) || value.length === 0 ||
|
|
29
|
+
!value.every(item => typeof item === 'string' && item.trim().length > 0))) {
|
|
30
|
+
errors.push(`${where}.${field}: must be a non-empty array of strings`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index-graph-overrides.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ProjectArtifactHealthClassification =
|
|
2
|
+
| 'local'
|
|
3
|
+
| 'healthy-split-root'
|
|
4
|
+
| 'degraded-offline'
|
|
5
|
+
| 'legacy-missing-control-plane'
|
|
6
|
+
| 'duplicated-identical'
|
|
7
|
+
| 'duplicated-divergent';
|
|
8
|
+
|
|
9
|
+
export interface ProjectControlFileHealth {
|
|
10
|
+
file: string;
|
|
11
|
+
local: boolean;
|
|
12
|
+
external: boolean;
|
|
13
|
+
identical: boolean | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ProjectArtifactHealth {
|
|
17
|
+
classification: ProjectArtifactHealthClassification;
|
|
18
|
+
severity: 'ok' | 'warning' | 'error';
|
|
19
|
+
external_configured: boolean;
|
|
20
|
+
source: { kind: string; key?: string; path?: string };
|
|
21
|
+
local_control_root: string;
|
|
22
|
+
artifact_root: string;
|
|
23
|
+
external_reachable: boolean;
|
|
24
|
+
control_files: ProjectControlFileHealth[];
|
|
25
|
+
missing_local_control_files: string[];
|
|
26
|
+
divergent_control_files: string[];
|
|
27
|
+
duplicated_local_corpus_files: string[];
|
|
28
|
+
divergent_local_corpus_files: string[];
|
|
29
|
+
repairable: boolean;
|
|
30
|
+
action: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PROJECT_CONTROL_PLANE_FILES: readonly string[];
|
|
34
|
+
export function auditProjectArtifactHealth(
|
|
35
|
+
projectDir: string,
|
|
36
|
+
env?: Record<string, string | undefined>,
|
|
37
|
+
): ProjectArtifactHealth;
|
|
@@ -10,6 +10,7 @@ import { join, relative, resolve, sep } from 'node:path';
|
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_PROJECT_AIWG_DIR,
|
|
12
12
|
PROJECT_AIWG_LOCATION_FILE,
|
|
13
|
+
isProjectArtifactRootExternal,
|
|
13
14
|
readProjectArtifactLocation,
|
|
14
15
|
resolveProjectAiwgDir,
|
|
15
16
|
} from './project-artifacts-runtime.mjs';
|
|
@@ -57,8 +58,16 @@ export function auditProjectArtifactHealth(projectDir, env = process.env) {
|
|
|
57
58
|
const localRoot = join(root, DEFAULT_PROJECT_AIWG_DIR);
|
|
58
59
|
const artifactRoot = resolveProjectAiwgDir(root, env);
|
|
59
60
|
const source = configuredSource(root, env);
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// Cheap, read-only check for whether an external root is configured at all
|
|
62
|
+
// — reused so repair's early refusal (src/artifacts/repair.ts) and this
|
|
63
|
+
// audit agree on the same definition of "local mode" (#274).
|
|
64
|
+
const externalConfigured = isProjectArtifactRootExternal(root, env);
|
|
65
|
+
// In local mode (no external root configured) artifactRoot === localRoot:
|
|
66
|
+
// there is nothing external to reach or compare against. Skip the
|
|
67
|
+
// reachability stat and the full-corpus listing/byte-compare entirely —
|
|
68
|
+
// previously this read and byte-compared every local payload file with
|
|
69
|
+
// itself on every `aiwg status`/`doctor`/`repair` call (#274).
|
|
70
|
+
const externalReachable = externalConfigured && existsSync(artifactRoot) && statSync(artifactRoot).isDirectory();
|
|
62
71
|
|
|
63
72
|
const controls = PROJECT_CONTROL_PLANE_FILES.map((file) => {
|
|
64
73
|
const localPath = join(localRoot, file);
|
|
@@ -69,7 +78,9 @@ export function auditProjectArtifactHealth(projectDir, env = process.env) {
|
|
|
69
78
|
});
|
|
70
79
|
const missingLocal = controls.filter((item) => !item.local).map((item) => item.file);
|
|
71
80
|
const divergentControl = controls.filter((item) => item.identical === false).map((item) => item.file);
|
|
72
|
-
const localPayload =
|
|
81
|
+
const localPayload = externalConfigured
|
|
82
|
+
? listFiles(localRoot).filter((file) => !PROJECT_CONTROL_PLANE_FILES.includes(file))
|
|
83
|
+
: [];
|
|
73
84
|
const divergentPayload = externalReachable
|
|
74
85
|
? localPayload.filter((file) => !sameFile(join(localRoot, file), join(artifactRoot, file)))
|
|
75
86
|
: [];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DEFAULT_PROJECT_AIWG_DIR: '.aiwg';
|
|
2
|
+
export const AIWG_ARTIFACTS_PATH_ENV: 'AIWG_ARTIFACTS_PATH';
|
|
3
|
+
export const PROJECT_AIWG_LOCATION_FILE: '.aiwg-location';
|
|
4
|
+
|
|
5
|
+
export type ProjectArtifactEnv = Record<string, string | undefined>;
|
|
6
|
+
|
|
7
|
+
export function expandProjectArtifactPath(pathValue: string, projectDir: string): string;
|
|
8
|
+
export function parseProjectArtifactLocation(contents: string): string | null;
|
|
9
|
+
export function readProjectArtifactLocation(projectDir: string): string | null;
|
|
10
|
+
export function resolveProjectAiwgDir(projectDir: string, env?: ProjectArtifactEnv): string;
|
|
11
|
+
export function isProjectArtifactRootExternal(projectDir: string, env?: ProjectArtifactEnv): boolean;
|
|
12
|
+
export function resolveProjectAiwgDirForWrite(projectDir: string, env?: ProjectArtifactEnv): string;
|
|
13
|
+
export function resolveProjectControlDir(projectDir: string): string;
|
|
14
|
+
export function projectAiwgPath(projectDir: string, ...segments: string[]): string;
|
|
15
|
+
export function projectAiwgWritePath(projectDir: string, ...segments: string[]): string;
|
|
16
|
+
export function projectControlPath(projectDir: string, ...segments: string[]): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface UserConfigDirOptions {
|
|
2
|
+
configDir?: string;
|
|
3
|
+
env?: Record<string, string | undefined>;
|
|
4
|
+
homeDir?: string;
|
|
5
|
+
exists?: (path: string) => boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveUserConfigDir(options?: UserConfigDirOptions): string;
|
|
9
|
+
export function userConfigFile(name: string, options?: UserConfigDirOptions): string;
|
|
@@ -24,6 +24,7 @@ import { resolveUserConfigDir } from './user-config-dir.mjs';
|
|
|
24
24
|
*/
|
|
25
25
|
export const KNOWN_CONFIG_FILES = [
|
|
26
26
|
{ filename: 'config.yaml', description: 'Global AIWG user preferences' },
|
|
27
|
+
{ filename: 'aiwg.config', description: 'Operator rule pins and capability groups' },
|
|
27
28
|
{ filename: 'models.json', description: 'Model tier and provider overrides' },
|
|
28
29
|
{ filename: 'ops.json', description: 'Ops workspace registry' },
|
|
29
30
|
{ filename: 'mcp-servers.json', description: 'MCP server registry (single source of truth)' },
|
|
@@ -122,7 +123,7 @@ export class UserConfig {
|
|
|
122
123
|
try {
|
|
123
124
|
await access(filePath);
|
|
124
125
|
const content = await readFile(filePath, 'utf-8');
|
|
125
|
-
if (spec.filename.endsWith('.json')) {
|
|
126
|
+
if (spec.filename.endsWith('.json') || spec.filename === 'aiwg.config') {
|
|
126
127
|
result[spec.filename] = JSON.parse(content);
|
|
127
128
|
}
|
|
128
129
|
else {
|
|
@@ -245,10 +246,8 @@ export class UserConfig {
|
|
|
245
246
|
if (this.configCache) {
|
|
246
247
|
return this.configCache;
|
|
247
248
|
}
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
const content = await readFile(configPath, 'utf-8');
|
|
251
|
-
const parsed = parseYamlSimple(content);
|
|
249
|
+
const parsed = await this.readConfig();
|
|
250
|
+
if (parsed) {
|
|
252
251
|
this.configCache = {
|
|
253
252
|
...DEFAULT_USER_CONFIG,
|
|
254
253
|
...parsed,
|
|
@@ -259,7 +258,7 @@ export class UserConfig {
|
|
|
259
258
|
};
|
|
260
259
|
return this.configCache;
|
|
261
260
|
}
|
|
262
|
-
|
|
261
|
+
else {
|
|
263
262
|
// File doesn't exist — return defaults (don't create file yet)
|
|
264
263
|
this.configCache = {
|
|
265
264
|
...DEFAULT_USER_CONFIG,
|
|
@@ -271,10 +270,24 @@ export class UserConfig {
|
|
|
271
270
|
return this.configCache;
|
|
272
271
|
}
|
|
273
272
|
}
|
|
273
|
+
async readConfig() {
|
|
274
|
+
const configPath = resolve(this.configDir, 'config.yaml');
|
|
275
|
+
try {
|
|
276
|
+
const content = await readFile(configPath, 'utf-8');
|
|
277
|
+
return parseYamlSimple(content);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (err.code === 'ENOENT')
|
|
281
|
+
return null;
|
|
282
|
+
throw new Error(`Failed to load ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
274
285
|
/**
|
|
275
286
|
* Save config.yaml
|
|
276
287
|
*/
|
|
277
288
|
async saveConfig(config) {
|
|
289
|
+
// Recheck the existing file even when defaults or cached values are being saved.
|
|
290
|
+
await this.readConfig();
|
|
278
291
|
await this.ensureDir();
|
|
279
292
|
const configPath = resolve(this.configDir, 'config.yaml');
|
|
280
293
|
const yaml = serializeYamlSimple(config);
|
|
@@ -304,11 +317,15 @@ export function parseYamlSimple(content) {
|
|
|
304
317
|
continue;
|
|
305
318
|
const key = match[1].trim();
|
|
306
319
|
const rawValue = match[2].trim();
|
|
320
|
+
// Config keys have no legitimate use for prototype-related names at any level.
|
|
321
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
322
|
+
throw new Error('Unsafe config key: prototype-related names are not allowed');
|
|
323
|
+
}
|
|
307
324
|
if (indent === 0) {
|
|
308
325
|
if (rawValue === '' || rawValue === undefined) {
|
|
309
326
|
// Section header
|
|
310
327
|
currentSection = key;
|
|
311
|
-
if (!(key
|
|
328
|
+
if (!Object.hasOwn(result, key)) {
|
|
312
329
|
result[key] = {};
|
|
313
330
|
}
|
|
314
331
|
}
|
|
@@ -375,7 +392,7 @@ function getNestedValue(obj, path) {
|
|
|
375
392
|
const parts = path.split('.');
|
|
376
393
|
let current = obj;
|
|
377
394
|
for (const part of parts) {
|
|
378
|
-
if (current === null || current === undefined || typeof current !== 'object') {
|
|
395
|
+
if (current === null || current === undefined || typeof current !== 'object' || !Object.hasOwn(current, part)) {
|
|
379
396
|
return undefined;
|
|
380
397
|
}
|
|
381
398
|
current = current[part];
|
|
@@ -384,15 +401,25 @@ function getNestedValue(obj, path) {
|
|
|
384
401
|
}
|
|
385
402
|
function setNestedValue(obj, path, value) {
|
|
386
403
|
const parts = path.split('.');
|
|
404
|
+
// Reject the entire path before mutating config; key uses below also guard each segment.
|
|
405
|
+
if (parts.some(part => part === '__proto__' || part === 'constructor' || part === 'prototype')) {
|
|
406
|
+
throw new Error('Unsafe config key path: prototype-related segments are not allowed');
|
|
407
|
+
}
|
|
387
408
|
let current = obj;
|
|
388
|
-
for (let i = 0; i < parts.length
|
|
409
|
+
for (let i = 0; i < parts.length; i++) {
|
|
389
410
|
const part = parts[i];
|
|
390
|
-
if (
|
|
411
|
+
if (part === '__proto__' || part === 'constructor' || part === 'prototype') {
|
|
412
|
+
throw new Error('Unsafe config key path: prototype-related segments are not allowed');
|
|
413
|
+
}
|
|
414
|
+
if (i === parts.length - 1) {
|
|
415
|
+
current[part] = value;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (!Object.hasOwn(current, part) || typeof current[part] !== 'object' || current[part] === null) {
|
|
391
419
|
current[part] = {};
|
|
392
420
|
}
|
|
393
421
|
current = current[part];
|
|
394
422
|
}
|
|
395
|
-
current[parts[parts.length - 1]] = value;
|
|
396
423
|
}
|
|
397
424
|
function parseValue(value) {
|
|
398
425
|
if (value === 'true')
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* --scope user` works from any cwd.
|
|
13
13
|
*/
|
|
14
14
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
15
|
import { dirname } from 'node:path';
|
|
17
16
|
import { userScopeConfigPath } from '../cli/scope-resolver.js';
|
|
18
17
|
/**
|
|
@@ -46,28 +45,37 @@ export function emptyUserRegistry() {
|
|
|
46
45
|
return { version: '1', installed: {} };
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
|
-
* Read the
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* warning (the broken file is preserved so the operator can inspect it).
|
|
48
|
+
* Read the user registry while preserving whether empty means absent or
|
|
49
|
+
* unreadable. Callers making adoption/readiness claims must not collapse a
|
|
50
|
+
* corrupt or inaccessible registry into proof that an addon is missing.
|
|
53
51
|
*/
|
|
54
|
-
export async function
|
|
55
|
-
const
|
|
56
|
-
if (!existsSync(path))
|
|
57
|
-
return emptyUserRegistry();
|
|
52
|
+
export async function inspectUserRegistry() {
|
|
53
|
+
const registryFile = userRegistryPath();
|
|
58
54
|
try {
|
|
59
|
-
const raw = await readFile(
|
|
55
|
+
const raw = await readFile(registryFile, 'utf-8');
|
|
60
56
|
const parsed = JSON.parse(raw);
|
|
61
|
-
if (!parsed.installed || typeof parsed.installed !== 'object') {
|
|
62
|
-
|
|
57
|
+
if (!parsed.installed || typeof parsed.installed !== 'object' || Array.isArray(parsed.installed)) {
|
|
58
|
+
throw new Error('installed must be an object');
|
|
63
59
|
}
|
|
64
|
-
return { version: '1', installed: parsed.installed };
|
|
60
|
+
return { registry: { version: '1', installed: parsed.installed }, status: 'available' };
|
|
65
61
|
}
|
|
66
62
|
catch (err) {
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
if (err.code === 'ENOENT') {
|
|
64
|
+
return { registry: emptyUserRegistry(), status: 'absent' };
|
|
65
|
+
}
|
|
66
|
+
process.stderr.write(`[user-registry] failed to parse ${registryFile}: ${err instanceof Error ? err.message : String(err)} — using empty registry\n`);
|
|
67
|
+
return { registry: emptyUserRegistry(), status: 'unreadable' };
|
|
69
68
|
}
|
|
70
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the per-user registry. Returns an empty registry when the file doesn't
|
|
72
|
+
* exist — that's the normal case before the operator's first `aiwg use --scope
|
|
73
|
+
* user`. On parse failure returns the empty registry and writes a stderr
|
|
74
|
+
* warning (the broken file is preserved so the operator can inspect it).
|
|
75
|
+
*/
|
|
76
|
+
export async function readUserRegistry() {
|
|
77
|
+
return (await inspectUserRegistry()).registry;
|
|
78
|
+
}
|
|
71
79
|
/**
|
|
72
80
|
* Persist the user registry. Creates the parent directory if missing.
|
|
73
81
|
*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Host-local AIWG state, shared by CLI telemetry and maintenance leases. */
|
|
4
|
+
export function getUserStateDir(env = process.env) {
|
|
5
|
+
return path.join(env.XDG_STATE_HOME || path.join(env.HOME || os.homedir(), '.local', 'state'), 'aiwg');
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=user-state.js.map
|
|
@@ -2,7 +2,6 @@ import { DecisionValidationError, validateDecisionValue, validateDistribution }
|
|
|
2
2
|
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
3
3
|
import { partitionProjectedState } from '../projection.js';
|
|
4
4
|
import { isTraceparent } from '../telemetry/context.js';
|
|
5
|
-
import { runInNewContext } from 'node:vm';
|
|
6
5
|
import { lookup } from 'node:dns/promises';
|
|
7
6
|
import { isIP } from 'node:net';
|
|
8
7
|
import { request as httpsRequest } from 'node:https';
|
|
@@ -12,7 +11,7 @@ export const JEV_ENDPOINT = 'https://api.typesafe.ai/v1/systemone';
|
|
|
12
11
|
const MAX_HEADER_BYTES = 32 * 1024;
|
|
13
12
|
const MAX_BODY_BYTES = 1024 * 1024;
|
|
14
13
|
const MAX_REQUEST_ID_LENGTH = 128;
|
|
15
|
-
const
|
|
14
|
+
const MAX_PARSE_DEPTH = 64;
|
|
16
15
|
/** Safe resolver taxonomy; messages are intentionally discarded at this boundary. */
|
|
17
16
|
export class JevCredentialError extends Error {
|
|
18
17
|
category;
|
|
@@ -45,6 +44,7 @@ export class JevDecisionAdapter {
|
|
|
45
44
|
this.region = options.region?.trim() ? options.region : null;
|
|
46
45
|
this.observeResponseBody = options.observeResponseBody;
|
|
47
46
|
}
|
|
47
|
+
/** Used only for non-2xx error bodies, where there is no separate successful parse to stay consistent with. */
|
|
48
48
|
observeResponse(body) {
|
|
49
49
|
if (!this.observeResponseBody)
|
|
50
50
|
return;
|
|
@@ -61,6 +61,30 @@ export class JevDecisionAdapter {
|
|
|
61
61
|
this.observeResponseBody(body, { actualModel: typeof parsed.model === 'string' && parsed.model ? parsed.model : null,
|
|
62
62
|
usage });
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Derives the observer's model/usage from a value `parseBoundedJson` has already produced (or `undefined`
|
|
66
|
+
* on a failed parse) -- never from a second, independent parse of the same bytes. `parseBoundedJson` runs
|
|
67
|
+
* inside a bounded-time VM context (`MAX_PARSE_MS`); calling it twice on identical bytes let one call
|
|
68
|
+
* succeed while the other spuriously hit that wall-clock bound under host contention, so the reported
|
|
69
|
+
* metadata could disagree with the actual parsed answer even though both described one response.
|
|
70
|
+
*/
|
|
71
|
+
observationMetadataOf(parsed) {
|
|
72
|
+
let record = {};
|
|
73
|
+
try {
|
|
74
|
+
record = asRecord(parsed);
|
|
75
|
+
}
|
|
76
|
+
catch { /* A non-object (or absent) top-level value has no model; usage stays unknown. */ }
|
|
77
|
+
let usage = emptyUsage();
|
|
78
|
+
try {
|
|
79
|
+
usage = normalizeUsage(record.usage);
|
|
80
|
+
}
|
|
81
|
+
catch { /* Missing usage is unknown, including on auth errors. */ }
|
|
82
|
+
return { actualModel: typeof record.model === 'string' && record.model ? record.model : null, usage };
|
|
83
|
+
}
|
|
84
|
+
/** Observes exactly once, from the single parse attempt `parsed` already represents (successful or not). */
|
|
85
|
+
observeParsed(body, parsed) {
|
|
86
|
+
this.observeResponseBody?.(body, this.observationMetadataOf(parsed));
|
|
87
|
+
}
|
|
64
88
|
async capabilities() {
|
|
65
89
|
return {
|
|
66
90
|
answerKinds: ['choice', 'ordinal-score', 'truth-probability'],
|
|
@@ -191,9 +215,7 @@ export class JevDecisionAdapter {
|
|
|
191
215
|
try {
|
|
192
216
|
parsedError = parseBoundedJson(errorBody);
|
|
193
217
|
}
|
|
194
|
-
catch
|
|
195
|
-
if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT')
|
|
196
|
-
throw error;
|
|
218
|
+
catch {
|
|
197
219
|
parsedError = null;
|
|
198
220
|
}
|
|
199
221
|
if (parsedError && typeof parsedError === 'object' && !Array.isArray(parsedError)) {
|
|
@@ -217,8 +239,15 @@ export class JevDecisionAdapter {
|
|
|
217
239
|
let parsed;
|
|
218
240
|
try {
|
|
219
241
|
const encoded = await readBoundedBody(response, signal);
|
|
220
|
-
|
|
221
|
-
|
|
242
|
+
// One parse attempt; the observer reads its outcome (success or failure) before answer validation,
|
|
243
|
+
// so an invalid answer still gets model/usage checked, and a parse failure is reported as unknown
|
|
244
|
+
// to both the observer and the main path -- never divergently.
|
|
245
|
+
try {
|
|
246
|
+
parsed = parseBoundedJson(encoded);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
this.observeParsed(encoded, parsed);
|
|
250
|
+
}
|
|
222
251
|
if (request.signal.aborted)
|
|
223
252
|
return externalInterruption(request, 'terminal-response', metadata);
|
|
224
253
|
if (deadline.aborted)
|
|
@@ -334,8 +363,14 @@ export class JevDecisionAdapter {
|
|
|
334
363
|
}
|
|
335
364
|
try {
|
|
336
365
|
const encoded = await readBoundedBody(response, signal);
|
|
337
|
-
|
|
338
|
-
|
|
366
|
+
// One parse attempt; see the single-request `evaluate()` for why observation never re-parses.
|
|
367
|
+
let parsed;
|
|
368
|
+
try {
|
|
369
|
+
parsed = parseBoundedJson(encoded);
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
this.observeParsed(encoded, parsed);
|
|
373
|
+
}
|
|
339
374
|
const normalized = normalizeBatchResponse(requests, parsed).map(answer => ({
|
|
340
375
|
questionId: answer.questionId,
|
|
341
376
|
observation: { ...answer.observation, ...metadata },
|
|
@@ -574,19 +609,21 @@ function safeRequestId(value) {
|
|
|
574
609
|
return typeof value === 'string' && value.length > 0 && value.length <= MAX_REQUEST_ID_LENGTH
|
|
575
610
|
&& /^[\x21-\x7e]+$/.test(value) && !value.includes(',') ? value : null;
|
|
576
611
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
612
|
+
/** @internal Exported from this module for direct admission and CPU-cost evidence, not the public barrel. */
|
|
613
|
+
export function parseBoundedJson(text) {
|
|
614
|
+
// Bound work, not elapsed time: a VM timeout includes host scheduling delay.
|
|
615
|
+
// The stream reader also enforces this bound, before UTF-8 decoding.
|
|
616
|
+
if (text.length > MAX_BODY_BYTES || Buffer.byteLength(text, 'utf8') > MAX_BODY_BYTES) {
|
|
617
|
+
throw new DecisionValidationError('Jev response too large');
|
|
618
|
+
}
|
|
619
|
+
// Scan iteratively before allocation by JSON.parse. Total key text/count is
|
|
620
|
+
// bounded by body bytes; no recursive walk or suffix copying is needed.
|
|
621
|
+
// String/key spans are disjoint; skip the whitespace consumed by lookahead.
|
|
622
|
+
// Each code unit is visited once. Key slicing/decoding/hashing sum to
|
|
623
|
+
// O(body bytes), as does native parsing.
|
|
624
|
+
const containers = [];
|
|
625
|
+
let index = 0;
|
|
626
|
+
while (index < text.length) {
|
|
590
627
|
const char = text[index];
|
|
591
628
|
if (char === '"') {
|
|
592
629
|
const start = index++;
|
|
@@ -600,20 +637,31 @@ function rejectDuplicateAnswerKeys(text) {
|
|
|
600
637
|
else if (next === '"')
|
|
601
638
|
break;
|
|
602
639
|
}
|
|
603
|
-
|
|
640
|
+
let next = index;
|
|
641
|
+
while (next < text.length && /[\x20\t\r\n]/.test(text[next]))
|
|
642
|
+
next += 1;
|
|
643
|
+
const keys = containers.at(-1);
|
|
644
|
+
if (keys && text[next] === ':') {
|
|
604
645
|
const key = JSON.parse(text.slice(start, index));
|
|
605
646
|
if (keys.has(key))
|
|
606
|
-
throw new DecisionValidationError('duplicate Jev
|
|
647
|
+
throw new DecisionValidationError('duplicate Jev response key');
|
|
607
648
|
keys.add(key);
|
|
608
649
|
}
|
|
650
|
+
index = next;
|
|
609
651
|
continue;
|
|
610
652
|
}
|
|
611
|
-
if (char === '{' || char === '[')
|
|
612
|
-
|
|
653
|
+
if (char === '{' || char === '[') {
|
|
654
|
+
if (containers.length >= MAX_PARSE_DEPTH)
|
|
655
|
+
throw new DecisionValidationError('Jev response nesting too deep');
|
|
656
|
+
containers.push(char === '{' ? new Set() : null);
|
|
657
|
+
}
|
|
613
658
|
else if (char === '}' || char === ']')
|
|
614
|
-
|
|
659
|
+
containers.pop();
|
|
615
660
|
index += 1;
|
|
616
661
|
}
|
|
662
|
+
// Native parsing checks the complete JSON grammar, with no reviver or code
|
|
663
|
+
// execution. Input bytes and nesting bound its work and allocated graph.
|
|
664
|
+
return JSON.parse(text);
|
|
617
665
|
}
|
|
618
666
|
function headersWithinLimit(headers) {
|
|
619
667
|
let bytes = 0;
|