@aiwg/cli 2026.10.3 → 2026.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +98 -37
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -260,26 +260,49 @@ export async function assertHeldoutRequestBounds(corpus, plan, approval) {
|
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
-
|
|
263
|
+
/** Prior observations are reusable only within the same study, corpus and preregistration. */
|
|
264
|
+
export function heldoutResumeAttempts(bundle, prior) {
|
|
265
|
+
const a = bundle.approval;
|
|
266
|
+
const old = prior.filter(attempt => attempt.study === a.study && attempt.corpusDigest === a.corpusDigest);
|
|
267
|
+
if (old.some(attempt => attempt.preregistrationDigest !== a.preregistrationDigest))
|
|
268
|
+
throw new HeldoutError('changed-preregistration');
|
|
269
|
+
const latest = new Map();
|
|
270
|
+
for (const attempt of old) {
|
|
271
|
+
const key = `${attempt.rowId}/${attempt.requestId}`, previous = latest.get(key);
|
|
272
|
+
if (previous?.result?.disposition !== 'success' && (attempt.result?.disposition === 'success'
|
|
273
|
+
|| attempt.ordinal >= (previous?.ordinal ?? 0)))
|
|
274
|
+
latest.set(key, attempt);
|
|
275
|
+
}
|
|
276
|
+
return { old, latest };
|
|
277
|
+
}
|
|
278
|
+
/** The collector and dry-run reserve only unfinished requests, with their unspent retry allowance. */
|
|
279
|
+
export function heldoutPendingRequests(row, plan, latest) {
|
|
280
|
+
return row.requests.flatMap(request => {
|
|
281
|
+
const previous = latest.get(`${row.id}/${request.id}`);
|
|
282
|
+
const first = (previous?.ordinal ?? 0) + 1, attempts = 2 + plan.providerFailurePolicy.maxRetries - first;
|
|
283
|
+
return previous?.result?.disposition === 'success' || attempts <= 0 ? [] : [{ request, first, attempts }];
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
export async function planHeldoutCollection(bundle, digest, prior = []) {
|
|
264
287
|
validateHeldoutBundle(bundle, digest);
|
|
265
288
|
await assertHeldoutRequestBounds(bundle.corpus, bundle.preregistration, bundle.approval);
|
|
266
|
-
|
|
289
|
+
const { latest } = heldoutResumeAttempts(bundle, prior);
|
|
290
|
+
let maximumRequestEstimateTokens = 0, tokens = 0, usdMicros = 0, maximumAttempts = 0;
|
|
267
291
|
const rows = heldoutRowsInScope(bundle);
|
|
268
292
|
for (const row of rows)
|
|
269
|
-
for (const request of row.
|
|
293
|
+
for (const { request, attempts } of heldoutPendingRequests(row, bundle.preregistration, latest)) {
|
|
270
294
|
const planned = await heldoutRequest(bundle.corpus, bundle.preregistration, bundle.approval, row, request);
|
|
271
295
|
maximumRequestEstimateTokens = Math.max(maximumRequestEstimateTokens, planned.estimatedTokens);
|
|
272
296
|
const reservation = heldoutReservationMicros(bundle.approval, planned.estimatedTokens);
|
|
273
297
|
if (reservation > Math.floor(bundle.approval.budget.usd * 1_000_000))
|
|
274
298
|
throw new HeldoutError('approval-call-budget');
|
|
275
|
-
|
|
299
|
+
maximumAttempts += attempts;
|
|
276
300
|
tokens += attempts * heldoutReservationTokens(bundle.preregistration, planned.estimatedTokens);
|
|
277
301
|
usdMicros += attempts * reservation;
|
|
278
302
|
}
|
|
279
|
-
const attempts = rows.reduce((n, r) => n + r.requests.length, 0) * (1 + bundle.preregistration.providerFailurePolicy.maxRetries);
|
|
280
303
|
const a = bundle.approval;
|
|
281
|
-
return { calibration: structuredClone(a.calibration), rowsInScope: rows.map(row => row.id), providerCalls: 0, maximumAttempts
|
|
282
|
-
fitsBeforeStop:
|
|
304
|
+
return { calibration: structuredClone(a.calibration), rowsInScope: rows.map(row => row.id), providerCalls: 0, maximumAttempts, maximumRequestEstimateTokens, reservedTokens: tokens, reservedUsdMicros: usdMicros,
|
|
305
|
+
fitsBeforeStop: maximumAttempts <= Math.floor(a.budget.calls * 0.8) && tokens <= Math.floor(a.budget.tokens * 0.8)
|
|
283
306
|
&& usdMicros <= Math.floor(Math.min(a.budget.usd, HELDOUT_CAP_USD[a.study] - a.priorStudySpendUsd, HELDOUT_PORTFOLIO_CAP_USD - a.priorPortfolioSpendUsd) * 800_000) };
|
|
284
307
|
}
|
|
285
308
|
/** An intentionally unapproved, unpriced form. Unknown attestations stay null, never fixture approvals. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveDecisionEnablement } from '../config/decision-config.js';
|
|
1
2
|
import { constants } from 'node:fs';
|
|
2
3
|
import { access, chmod, mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
4
|
import os from 'node:os';
|
|
@@ -64,7 +65,8 @@ export async function lookupJevCredential(options = {}) {
|
|
|
64
65
|
const forced = environment.AIWG_DECISION_ENABLED;
|
|
65
66
|
const envToken = environment.JEV_API_KEY?.trim() || environment.AIWG_DECISION_JEV_API_KEY?.trim();
|
|
66
67
|
const fileRecord = envToken && (forced === '0' || forced === '1') ? null : await readCredentialFile(options);
|
|
67
|
-
const
|
|
68
|
+
const enablement = resolveDecisionEnablement(options);
|
|
69
|
+
const enabled = enablement.source === 'default' ? fileRecord?.enabled === true : enablement.enabled;
|
|
68
70
|
if (envToken)
|
|
69
71
|
return {
|
|
70
72
|
source: 'env',
|
|
@@ -78,6 +80,18 @@ export async function lookupJevCredential(options = {}) {
|
|
|
78
80
|
return null;
|
|
79
81
|
return { source: 'file', token: fileRecord.token, region: fileRecord.region, ...(fileRecord.endpoint ? { endpoint: fileRecord.endpoint } : {}), enabled };
|
|
80
82
|
}
|
|
83
|
+
/** Offline metadata-only inspection. Never probes Jev and never returns credential bytes. */
|
|
84
|
+
export async function inspectJevCredential(options = {}) {
|
|
85
|
+
try {
|
|
86
|
+
const credential = await lookupJevCredential(options);
|
|
87
|
+
if (!credential)
|
|
88
|
+
return { configured: false, enabled: false, source: 'none' };
|
|
89
|
+
return { configured: true, enabled: credential.enabled, source: credential.source };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { configured: null, enabled: null, source: 'unknown' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
81
95
|
export async function resolveJevCredentialBytes(logicalRef, options = {}) {
|
|
82
96
|
if (logicalRef !== JEV_CREDENTIAL_REF)
|
|
83
97
|
throw new JevCredentialError('configuration');
|
|
@@ -62,14 +62,17 @@ import { projectAiwgPath, projectAiwgWritePath } from '../config/project-artifac
|
|
|
62
62
|
import { redactText } from '../governance/redaction.js';
|
|
63
63
|
import { CanonicalJsonByteEstimator } from './context-plan.js';
|
|
64
64
|
import { resolveActiveProvider } from '../cli/provider-resolution.js';
|
|
65
|
-
|
|
65
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
66
|
+
import { decisionSavingsWeights, estimateDecisionSavings } from './savings.js';
|
|
67
|
+
export const DECISION_LEDGER_SCHEMA = 'aiwg-decision-ledger-record/v2';
|
|
68
|
+
export const DECISION_LEDGER_SCHEMA_V1 = 'aiwg-decision-ledger-record/v1';
|
|
66
69
|
const DECISION_LEDGER_DIR_NAME = 'decision';
|
|
67
70
|
const DECISION_LEDGER_FILE_NAME = 'ledger.jsonl';
|
|
68
71
|
const DECISION_LEDGER_KINDS = ['yes-no', 'choices', 'scale'];
|
|
69
72
|
const DECISION_LEDGER_STATUSES = ['answered', 'fallback'];
|
|
70
73
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
74
|
+
* Legacy v1 per-kind allowance, retained only for comparison and replay.
|
|
75
|
+
* Named and recorded with
|
|
73
76
|
* every estimate (not just referenced) so the figure can be recomputed
|
|
74
77
|
* later per the issue's proposal item 3.
|
|
75
78
|
*/
|
|
@@ -175,7 +178,7 @@ function answerIndexFor(mode, rawAnswer) {
|
|
|
175
178
|
function contextMessageForEstimate(input) {
|
|
176
179
|
return input.context === undefined || input.context === '' ? input.question : `${input.question}\n\nContext:\n${input.context}`;
|
|
177
180
|
}
|
|
178
|
-
function
|
|
181
|
+
function estimateSavingsV1(input, result) {
|
|
179
182
|
if (result.status === 'answered') {
|
|
180
183
|
const inputTokens = new CanonicalJsonByteEstimator().estimate(contextMessageForEstimate(input)).tokens;
|
|
181
184
|
const allowance = DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS[input.mode.kind];
|
|
@@ -185,22 +188,24 @@ function estimateSavings(input, result) {
|
|
|
185
188
|
// Avoid signed zero (`-0`): a fallback that never called Jev (e.g. not-configured) costs nothing.
|
|
186
189
|
return { estimatedLlmTokensAvoided: jevCost === 0 ? 0 : -jevCost, estimationMethod: DECISION_SAVINGS_ESTIMATION_METHOD };
|
|
187
190
|
}
|
|
188
|
-
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* code to reject through.
|
|
195
|
-
*/
|
|
196
|
-
export function resolveDecisionSessionId(explicitSessionId, env) {
|
|
191
|
+
/** Ordered harness session markers; never substitute provider/process ids. */
|
|
192
|
+
export const DECISION_SESSION_ENV_VARS = [
|
|
193
|
+
'AIWG_SESSION_ID', 'CLAUDE_CODE_SESSION_ID', 'CODEX_THREAD_ID', 'CODEX_SESSION_ID',
|
|
194
|
+
'HERMES_SESSION_ID', 'OPENCODE_SESSION_ID', 'MUSE_SESSION_ID', 'GROK_SESSION_ID', 'WARP_SESSION_ID',
|
|
195
|
+
];
|
|
196
|
+
export function resolveDecisionSession(explicitSessionId, env) {
|
|
197
197
|
const explicit = explicitSessionId?.trim();
|
|
198
198
|
if (explicit && ATTRIBUTION_FIELD_PATTERN.test(explicit))
|
|
199
|
-
return explicit;
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
return { sessionId: explicit, sessionSource: '--session' };
|
|
200
|
+
for (const key of DECISION_SESSION_ENV_VARS) {
|
|
201
|
+
const value = env[key]?.trim();
|
|
202
|
+
if (value && ATTRIBUTION_FIELD_PATTERN.test(value))
|
|
203
|
+
return { sessionId: value, sessionSource: key };
|
|
204
|
+
}
|
|
205
|
+
return { sessionId: 'unknown', sessionSource: 'unknown' };
|
|
206
|
+
}
|
|
207
|
+
export function resolveDecisionSessionId(explicitSessionId, env) {
|
|
208
|
+
return resolveDecisionSession(explicitSessionId, env).sessionId;
|
|
204
209
|
}
|
|
205
210
|
function withinRecordBudget(record) {
|
|
206
211
|
return Buffer.byteLength(JSON.stringify(record), 'utf8') <= MAX_LEDGER_RECORD_BYTES;
|
|
@@ -224,7 +229,9 @@ function enforceRecordBudget(record) {
|
|
|
224
229
|
/** Pure, synchronous and side-effect free: builds the record this call would append, fully sanitized. */
|
|
225
230
|
export function buildDecisionLedgerRecord(input, result, context) {
|
|
226
231
|
const timestamp = new Date((context.now ?? Date.now)()).toISOString();
|
|
227
|
-
const
|
|
232
|
+
const legacy = estimateSavingsV1(input, result);
|
|
233
|
+
const inputTokens = new CanonicalJsonByteEstimator().estimate(contextMessageForEstimate(input)).tokens;
|
|
234
|
+
const savings = estimateDecisionSavings(inputTokens, result.status === 'answered', (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0), context.savingsWeights ?? decisionSavingsWeights());
|
|
228
235
|
const caller = context.caller && ATTRIBUTION_FIELD_PATTERN.test(context.caller) ? context.caller : null;
|
|
229
236
|
const rawAnswer = result.answer;
|
|
230
237
|
const answer = typeof rawAnswer === 'string' ? sanitizeField(rawAnswer) : rawAnswer;
|
|
@@ -239,6 +246,7 @@ export function buildDecisionLedgerRecord(input, result, context) {
|
|
|
239
246
|
transport: context.transport,
|
|
240
247
|
provider: sanitizeField(context.provider) ?? 'unknown',
|
|
241
248
|
sessionId: sanitizeField(context.sessionId) ?? 'unknown',
|
|
249
|
+
sessionSource: sanitizeField(context.sessionSource) ?? 'unknown',
|
|
242
250
|
projectRoot: sanitizeField(context.projectRoot) ?? '',
|
|
243
251
|
decisionKind: input.mode.kind,
|
|
244
252
|
choiceIds,
|
|
@@ -250,13 +258,22 @@ export function buildDecisionLedgerRecord(input, result, context) {
|
|
|
250
258
|
status: result.status,
|
|
251
259
|
answer,
|
|
252
260
|
confidence: result.confidence,
|
|
261
|
+
...(result.answeredBy === undefined ? {} : { answeredBy: result.answeredBy }),
|
|
253
262
|
threshold: input.threshold ?? 0.8,
|
|
254
263
|
fallbackReason: result.status === 'fallback' ? result.reason : null,
|
|
255
264
|
model: typeof result.model === 'string' ? sanitizeField(result.model) : result.model,
|
|
256
265
|
usage: result.usage,
|
|
257
266
|
latencyMs: result.latencyMs,
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
...(result.aggregate ? {
|
|
268
|
+
passesRequested: result.aggregate.passesRequested, escalationPasses: result.aggregate.escalationPasses,
|
|
269
|
+
passesRun: result.aggregate.passesRun, escalated: result.aggregate.escalated,
|
|
270
|
+
earlyStop: result.aggregate.earlyStop, agreementRatio: result.aggregate.agreementRatio,
|
|
271
|
+
minAgreement: result.aggregate.minAgreement, agreementConfidenceFloor: result.aggregate.agreementConfidenceFloor,
|
|
272
|
+
passConfidenceSummary: result.aggregate.confidenceSummary,
|
|
273
|
+
} : {}),
|
|
274
|
+
estimatedLlmTokensAvoided: legacy.estimatedLlmTokensAvoided,
|
|
275
|
+
estimationMethodV1: legacy.estimationMethod,
|
|
276
|
+
...savings,
|
|
260
277
|
caller: sanitizeField(caller, MAX_METADATA_FIELD_BYTES),
|
|
261
278
|
};
|
|
262
279
|
if (context.recordText) {
|
|
@@ -480,11 +497,13 @@ async function performRecord(input, result, options, warn, flag) {
|
|
|
480
497
|
const provider = await resolveProviderSafely(options);
|
|
481
498
|
if (flag.cancelled)
|
|
482
499
|
return null;
|
|
483
|
-
const
|
|
500
|
+
const session = resolveDecisionSession(options.explicitSessionId, options.env ?? process.env);
|
|
501
|
+
const config = await readAiwgConfig(options.projectRoot);
|
|
484
502
|
const record = buildDecisionLedgerRecord(input, result, {
|
|
485
503
|
transport: options.transport,
|
|
486
504
|
provider,
|
|
487
|
-
|
|
505
|
+
...session,
|
|
506
|
+
savingsWeights: decisionSavingsWeights(config?.decision?.ask),
|
|
488
507
|
projectRoot: options.projectRoot,
|
|
489
508
|
caller: options.caller,
|
|
490
509
|
recordText: options.recordText,
|
|
@@ -588,7 +607,7 @@ function isDecisionLedgerRecord(value) {
|
|
|
588
607
|
if (!value || typeof value !== 'object')
|
|
589
608
|
return false;
|
|
590
609
|
const record = value;
|
|
591
|
-
if (record.schema !== DECISION_LEDGER_SCHEMA)
|
|
610
|
+
if (record.schema !== DECISION_LEDGER_SCHEMA && record.schema !== DECISION_LEDGER_SCHEMA_V1)
|
|
592
611
|
return false;
|
|
593
612
|
if (typeof record.id !== 'string' || record.id.length === 0)
|
|
594
613
|
return false;
|
|
@@ -622,6 +641,8 @@ function isDecisionLedgerRecord(value) {
|
|
|
622
641
|
return false;
|
|
623
642
|
if (!isUnitIntervalOrNull(record.confidence))
|
|
624
643
|
return false;
|
|
644
|
+
if (record.answeredBy !== undefined && record.answeredBy !== null && record.answeredBy !== 'confidence' && record.answeredBy !== 'agreement')
|
|
645
|
+
return false;
|
|
625
646
|
if (!isUnitIntervalOrNull(record.threshold) || record.threshold === null)
|
|
626
647
|
return false;
|
|
627
648
|
if (record.fallbackReason !== null && typeof record.fallbackReason !== 'string')
|
|
@@ -634,10 +655,54 @@ function isDecisionLedgerRecord(value) {
|
|
|
634
655
|
return false;
|
|
635
656
|
if (!isFiniteNumber(record.estimatedLlmTokensAvoided))
|
|
636
657
|
return false; // may be negative
|
|
637
|
-
if (
|
|
638
|
-
|
|
658
|
+
if (record.schema === DECISION_LEDGER_SCHEMA_V1) {
|
|
659
|
+
if (typeof record.estimationMethod !== 'string')
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const method = record.estimationMethod;
|
|
664
|
+
if (!method || typeof method !== 'object' || method.version !== 'aiwg-decision-savings/v2')
|
|
665
|
+
return false;
|
|
666
|
+
if (!['standard', 'reasoning'].includes(method.caller_model_class))
|
|
667
|
+
return false;
|
|
668
|
+
for (const key of ['input_tokens', 'reasoning_tokens', 'output_tokens', 'llm_per_mtok', 'jev_per_mtok']) {
|
|
669
|
+
if (!isFiniteNumber(method[key]) || method[key] < 0 || method[key] > 1_000_000)
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
if (typeof record.sessionSource !== 'string')
|
|
673
|
+
return false;
|
|
674
|
+
for (const key of ['llm_tokens_avoided', 'jev_tokens']) {
|
|
675
|
+
if (!isFiniteNumber(record[key]) || record[key] < 0)
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
if (!isFiniteNumber(record.cost_weighted_net_usd))
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
639
681
|
if (record.caller !== null && typeof record.caller !== 'string')
|
|
640
682
|
return false;
|
|
683
|
+
for (const key of ['passesRequested', 'escalationPasses', 'passesRun']) {
|
|
684
|
+
if (record[key] !== undefined && (!Number.isSafeInteger(record[key]) || record[key] < (key === 'passesRun' ? 0 : 1)))
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
if (record.escalated !== undefined && typeof record.escalated !== 'boolean')
|
|
688
|
+
return false;
|
|
689
|
+
if (record.agreementRatio !== undefined && (record.agreementRatio === null || !isUnitIntervalOrNull(record.agreementRatio)))
|
|
690
|
+
return false;
|
|
691
|
+
if (record.minAgreement !== undefined && !isUnitIntervalOrNull(record.minAgreement))
|
|
692
|
+
return false;
|
|
693
|
+
if (record.agreementConfidenceFloor !== undefined && (record.agreementConfidenceFloor === null || !isUnitIntervalOrNull(record.agreementConfidenceFloor)))
|
|
694
|
+
return false;
|
|
695
|
+
if (record.earlyStop !== undefined && record.earlyStop !== null && record.earlyStop !== 'certain-failure' && record.earlyStop !== 'timeout')
|
|
696
|
+
return false;
|
|
697
|
+
if (record.passConfidenceSummary !== undefined) {
|
|
698
|
+
const summary = record.passConfidenceSummary;
|
|
699
|
+
if (!summary || typeof summary !== 'object' || Array.isArray(summary))
|
|
700
|
+
return false;
|
|
701
|
+
if (![summary.count, summary.missing].every(value => Number.isSafeInteger(value) && value >= 0))
|
|
702
|
+
return false;
|
|
703
|
+
if (![summary.mean, summary.min, summary.max].every(isUnitIntervalOrNull))
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
641
706
|
return true;
|
|
642
707
|
}
|
|
643
708
|
async function* streamDecisionLedgerLines(path) {
|
|
@@ -714,20 +779,47 @@ function groupKeyFor(record, groupBy) {
|
|
|
714
779
|
return record.sessionId;
|
|
715
780
|
if (groupBy === 'kind')
|
|
716
781
|
return record.decisionKind;
|
|
782
|
+
if (groupBy === 'passes')
|
|
783
|
+
return `${record.passesRequested ?? 1}/${record.escalationPasses ?? record.passesRequested ?? 1}`;
|
|
784
|
+
if (groupBy === 'escalation')
|
|
785
|
+
return String(record.escalated ?? false);
|
|
717
786
|
return record.caller; // may be null; kept distinct from the literal string "unattributed"
|
|
718
787
|
}
|
|
719
788
|
function newAccumulator() {
|
|
720
789
|
return {
|
|
790
|
+
llmTokensAvoided: 0, costWeightedNetUsd: 0, legacyReestimatedCalls: 0,
|
|
721
791
|
totalCalls: 0, answered: 0, fallback: 0, fallbackCountByReason: {},
|
|
792
|
+
agreementCount: 0, agreementSum: 0, escalatedCalls: 0, passesRun: 0,
|
|
722
793
|
confidenceCount: 0, confidenceSum: 0, confidenceMin: null, confidenceMax: null,
|
|
723
794
|
jevInputTokens: 0, jevOutputTokens: 0, answeredJevInputTokens: 0, answeredJevOutputTokens: 0,
|
|
724
795
|
estimatedLlmTokensAvoidedTotal: 0,
|
|
725
796
|
};
|
|
726
797
|
}
|
|
727
|
-
function accumulate(acc, record) {
|
|
798
|
+
function accumulate(acc, record, weights) {
|
|
728
799
|
acc.totalCalls += 1;
|
|
800
|
+
if (record.agreementRatio !== undefined) {
|
|
801
|
+
acc.agreementCount += 1;
|
|
802
|
+
acc.agreementSum += record.agreementRatio;
|
|
803
|
+
}
|
|
804
|
+
if (record.escalated)
|
|
805
|
+
acc.escalatedCalls += 1;
|
|
806
|
+
acc.passesRun += record.passesRun ?? (record.fallbackReason === 'not-configured' ? 0 : 1);
|
|
729
807
|
const inputTokens = record.usage?.inputTokens ?? 0;
|
|
730
808
|
const outputTokens = record.usage?.outputTokens ?? 0;
|
|
809
|
+
if (record.schema === DECISION_LEDGER_SCHEMA) {
|
|
810
|
+
acc.llmTokensAvoided += record.llm_tokens_avoided;
|
|
811
|
+
acc.costWeightedNetUsd += record.cost_weighted_net_usd;
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
// v1 answered credit contains the original input estimate plus the known
|
|
815
|
+
// per-kind allowance. Digests alone cannot reconstruct caller text.
|
|
816
|
+
const recoveredInput = record.status === 'answered'
|
|
817
|
+
? Math.max(0, record.estimatedLlmTokensAvoided - DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS[record.decisionKind]) : 0;
|
|
818
|
+
const estimate = estimateDecisionSavings(recoveredInput, record.status === 'answered', inputTokens + outputTokens, weights);
|
|
819
|
+
acc.llmTokensAvoided += estimate.llm_tokens_avoided;
|
|
820
|
+
acc.costWeightedNetUsd += estimate.cost_weighted_net_usd;
|
|
821
|
+
acc.legacyReestimatedCalls += 1;
|
|
822
|
+
}
|
|
731
823
|
if (record.status === 'answered') {
|
|
732
824
|
acc.answered += 1;
|
|
733
825
|
if (record.confidence !== null) {
|
|
@@ -755,10 +847,17 @@ function finalizeGroup(key, acc) {
|
|
|
755
847
|
}
|
|
756
848
|
return {
|
|
757
849
|
key,
|
|
850
|
+
llm_tokens_avoided: acc.llmTokensAvoided,
|
|
851
|
+
jev_tokens: acc.jevInputTokens + acc.jevOutputTokens,
|
|
852
|
+
cost_weighted_net_usd: acc.costWeightedNetUsd,
|
|
853
|
+
legacyReestimatedCalls: acc.legacyReestimatedCalls,
|
|
758
854
|
totalCalls: acc.totalCalls,
|
|
759
855
|
answered: acc.answered,
|
|
760
856
|
fallback: acc.fallback,
|
|
761
857
|
fallbackRate: acc.totalCalls ? acc.fallback / acc.totalCalls : 0,
|
|
858
|
+
hitRate: acc.totalCalls ? acc.answered / acc.totalCalls : 0,
|
|
859
|
+
agreement: { count: acc.agreementCount, mean: acc.agreementCount ? acc.agreementSum / acc.agreementCount : null },
|
|
860
|
+
escalatedCalls: acc.escalatedCalls, passesRun: acc.passesRun,
|
|
762
861
|
fallbackCountByReason: acc.fallbackCountByReason,
|
|
763
862
|
fallbackRateByReason,
|
|
764
863
|
confidence: {
|
|
@@ -768,12 +867,15 @@ function finalizeGroup(key, acc) {
|
|
|
768
867
|
max: acc.confidenceMax,
|
|
769
868
|
},
|
|
770
869
|
jevUsage: { inputTokens: acc.jevInputTokens, outputTokens: acc.jevOutputTokens },
|
|
870
|
+
tokensPerAnsweredDecision: acc.answered ? (acc.jevInputTokens + acc.jevOutputTokens) / acc.answered : null,
|
|
771
871
|
estimatedLlmTokensAvoidedTotal: acc.estimatedLlmTokensAvoidedTotal,
|
|
772
872
|
netSavingsTokens: acc.estimatedLlmTokensAvoidedTotal - (acc.answeredJevInputTokens + acc.answeredJevOutputTokens),
|
|
773
873
|
};
|
|
774
874
|
}
|
|
775
875
|
/** Reads, validates, filters and aggregates the ledger offline, streaming line-by-line with incremental min/max; no network, no Jev call. Tolerates a missing ledger file (empty result). */
|
|
776
876
|
export async function decisionLedgerStats(projectRoot, filters = {}) {
|
|
877
|
+
const config = await readAiwgConfig(projectRoot);
|
|
878
|
+
const weights = decisionSavingsWeights(config?.decision?.ask);
|
|
777
879
|
const groups = new Map();
|
|
778
880
|
let skipped = 0;
|
|
779
881
|
for await (const record of streamDecisionLedgerRecords(projectRoot)) {
|
|
@@ -789,7 +891,7 @@ export async function decisionLedgerStats(projectRoot, filters = {}) {
|
|
|
789
891
|
acc = newAccumulator();
|
|
790
892
|
groups.set(key, acc);
|
|
791
893
|
}
|
|
792
|
-
accumulate(acc, record);
|
|
894
|
+
accumulate(acc, record, weights);
|
|
793
895
|
}
|
|
794
896
|
const sortedKeys = [...groups.keys()].sort((a, b) => (a ?? '').localeCompare(b ?? ''));
|
|
795
897
|
return {
|
|
@@ -798,6 +900,7 @@ export async function decisionLedgerStats(projectRoot, filters = {}) {
|
|
|
798
900
|
skipped,
|
|
799
901
|
groups: sortedKeys.map(key => finalizeGroup(key, groups.get(key))),
|
|
800
902
|
filters: { ...filters },
|
|
903
|
+
legacyEstimationWeights: weights,
|
|
801
904
|
};
|
|
802
905
|
}
|
|
803
906
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getConfigPath, readAiwgConfigCandidate } from '../config/aiwg-config.js';
|
|
4
|
+
import { inspectUserRegistry } from '../config/user-registry.js';
|
|
5
|
+
import { resolveDecisionEnablement } from '../config/decision-config.js';
|
|
6
|
+
import { projectAiwgPath } from '../config/project-artifacts.js';
|
|
7
|
+
import * as credentials from './jev-credentials.js';
|
|
8
|
+
export const DECISION_COMPACT_ANCHOR = 'Decision offload: before the first eligible bounded choice, including ordinary triage or worker routing, read `aiwg show rule decision-offload` and use the CLI or MCP ask capability. On fallback decide normally. Answers are advisory and do not authorize actions.';
|
|
9
|
+
function isInstalledEntry(value) {
|
|
10
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
+
return false;
|
|
12
|
+
const entry = value;
|
|
13
|
+
return typeof entry.version === 'string' && entry.version.trim().length > 0
|
|
14
|
+
&& typeof entry.source === 'string' && entry.source.trim().length > 0
|
|
15
|
+
// Older registry/config entries can predate installedAt, so its absence is
|
|
16
|
+
// compatible; a present non-string timestamp is corrupt evidence.
|
|
17
|
+
&& (entry.installedAt === undefined || typeof entry.installedAt === 'string')
|
|
18
|
+
&& Boolean(entry.deployedTo) && typeof entry.deployedTo === 'object' && !Array.isArray(entry.deployedTo);
|
|
19
|
+
}
|
|
20
|
+
async function inspectProjectInstall(cwd) {
|
|
21
|
+
// A positive marker is sufficient installation evidence even when an
|
|
22
|
+
// unrelated user-scope registry or config section is unreadable. Negative
|
|
23
|
+
// evidence still goes through the full candidate validator below.
|
|
24
|
+
const local = getConfigPath(cwd);
|
|
25
|
+
const candidates = [local];
|
|
26
|
+
for (const file of candidates) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(await readFile(file, 'utf8'));
|
|
29
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
30
|
+
const installed = parsed.installed;
|
|
31
|
+
if (installed && typeof installed === 'object' && !Array.isArray(installed)
|
|
32
|
+
&& Object.hasOwn(installed, 'decision-engine')) {
|
|
33
|
+
return isInstalledEntry(installed['decision-engine']) ? true : null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// A valid local config wins over compatibility/artifact copies.
|
|
37
|
+
if (file === local)
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT') {
|
|
42
|
+
if (file === local) {
|
|
43
|
+
try {
|
|
44
|
+
const artifact = projectAiwgPath(cwd, 'aiwg.config');
|
|
45
|
+
if (!candidates.includes(artifact))
|
|
46
|
+
candidates.push(artifact);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// Installation metadata is independent of worker-model defaults. Reading
|
|
59
|
+
// the validated candidate avoids turning an unrelated missing defaults
|
|
60
|
+
// bundle into unknown addon-installation evidence.
|
|
61
|
+
const config = await readAiwgConfigCandidate(cwd);
|
|
62
|
+
const installed = config?.installed ?? {};
|
|
63
|
+
return Object.hasOwn(installed, 'decision-engine')
|
|
64
|
+
? isInstalledEntry(installed['decision-engine']) ? true : null
|
|
65
|
+
: false;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function inspectUserInstall() {
|
|
72
|
+
const inspected = await inspectUserRegistry();
|
|
73
|
+
if (Object.hasOwn(inspected.registry.installed, 'decision-engine')) {
|
|
74
|
+
return isInstalledEntry(inspected.registry.installed['decision-engine']) ? true : null;
|
|
75
|
+
}
|
|
76
|
+
return inspected.status === 'unreadable' ? null : false;
|
|
77
|
+
}
|
|
78
|
+
function combineInstallEvidence(project, user) {
|
|
79
|
+
if (project === true || user === true)
|
|
80
|
+
return true;
|
|
81
|
+
if (project === null || user === null)
|
|
82
|
+
return null;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
async function inspectReadableFile(file) {
|
|
86
|
+
try {
|
|
87
|
+
await readFile(file);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return error.code === 'ENOENT' ? false : null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function inspectAnchor(file) {
|
|
95
|
+
try {
|
|
96
|
+
return (await readFile(file, 'utf8')).includes(DECISION_COMPACT_ANCHOR);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return error.code === 'ENOENT' ? false : null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Read configuration evidence only; native session loading is not observable here. */
|
|
103
|
+
export async function inspectDecisionOffloadActivation(options) {
|
|
104
|
+
const [projectInstalled, userInstalled] = await Promise.all([
|
|
105
|
+
inspectProjectInstall(options.cwd),
|
|
106
|
+
inspectUserInstall(),
|
|
107
|
+
]);
|
|
108
|
+
const installed = combineInstallEvidence(projectInstalled, userInstalled);
|
|
109
|
+
let evaluateEnabled = null;
|
|
110
|
+
try {
|
|
111
|
+
evaluateEnabled = resolveDecisionEnablement({ cwd: options.cwd, env: options.env }).enabled;
|
|
112
|
+
}
|
|
113
|
+
catch { /* invalid settings are unknown */ }
|
|
114
|
+
// The inspector is supplied by the shared decision runtime. The compatibility
|
|
115
|
+
// branch preserves older installed runtimes and discards credential material.
|
|
116
|
+
const runtime = credentials;
|
|
117
|
+
let askEnabled = installed === null ? null : false;
|
|
118
|
+
if (installed === true) {
|
|
119
|
+
try {
|
|
120
|
+
const ask = runtime.inspectJevCredential
|
|
121
|
+
? await runtime.inspectJevCredential({ cwd: options.cwd, env: options.env })
|
|
122
|
+
: await credentials.lookupJevCredential({ cwd: options.cwd, env: options.env }).then(value => ({ configured: Boolean(value), enabled: value?.enabled ?? false }));
|
|
123
|
+
askEnabled = ask.configured === null || ask.enabled === null ? null : ask.configured && ask.enabled;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
askEnabled = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const rulePath = options.frameworkRoot && path.join(options.frameworkRoot, 'agentic/code/addons/decision-engine/rules/decision-offload.md');
|
|
130
|
+
const ruleSourcePresent = rulePath ? await inspectReadableFile(rulePath) : null;
|
|
131
|
+
const startupFile = path.join(options.cwd, 'AIWG.md');
|
|
132
|
+
const startupAnchorPresent = await inspectAnchor(startupFile);
|
|
133
|
+
return { installed, askEnabled, evaluateEnabled,
|
|
134
|
+
shouldAnchor: installed === true && askEnabled === true, ruleSourcePresent, ruleDelivery: 'unknown',
|
|
135
|
+
startupAnchorPresent, nativeActivation: 'not-claimed' };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=offload-activation.js.map
|