@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
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision-offload checkpoint execution and offline adoption reporting.
|
|
3
|
+
*
|
|
4
|
+
* Opportunity records describe only checkpoints that called this contract.
|
|
5
|
+
* Generic activity-log prose is deliberately not interpreted as a precise
|
|
6
|
+
* opportunity denominator. Completed-call records remain in ledger.ts; the
|
|
7
|
+
* shared correlation id joins the two append-only journals.
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { constants as fsConstants, createReadStream } from 'node:fs';
|
|
11
|
+
import { lstat, mkdir, open } from 'node:fs/promises';
|
|
12
|
+
import { createInterface } from 'node:readline';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { projectAiwgPath, projectAiwgWritePath } from '../config/project-artifacts.js';
|
|
15
|
+
import { resolveDecisionEnablement } from '../config/decision-config.js';
|
|
16
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
17
|
+
import { resolveActiveProvider } from '../cli/provider-resolution.js';
|
|
18
|
+
import { askDecision, DecisionAskUsageError } from './ask.js';
|
|
19
|
+
import { inspectJevCredential } from './jev-credentials.js';
|
|
20
|
+
import { inspectDecisionOffloadActivation } from './offload-activation.js';
|
|
21
|
+
import { ATTRIBUTION_FIELD_PATTERN, decisionLedgerHistory, DECISION_LEDGER_RECORD_TIMEOUT_MS, recordDecisionAsk, resolveDecisionSession, } from './ledger.js';
|
|
22
|
+
export const DECISION_OPPORTUNITY_SCHEMA = 'aiwg-decision-opportunity/v1';
|
|
23
|
+
export const DECISION_OFFLOAD_SNAPSHOT_SCHEMA = 'aiwg-decision-offload-snapshot/v1';
|
|
24
|
+
const OPPORTUNITY_FILE = 'opportunities.jsonl';
|
|
25
|
+
const DIR_MODE = 0o700;
|
|
26
|
+
const FILE_MODE = 0o600;
|
|
27
|
+
const MAX_OPPORTUNITY_RECORD_BYTES = 4000;
|
|
28
|
+
export const DECISION_OPPORTUNITY_RECORD_TIMEOUT_MS = DECISION_LEDGER_RECORD_TIMEOUT_MS;
|
|
29
|
+
const TIMED_OUT = Symbol('decision-offload-timeout');
|
|
30
|
+
export const DECISION_OFFLOAD_CHECKPOINT_COVERAGE = [
|
|
31
|
+
'address-issues:worker-pick', 'address-issues:failure-triage', 'address-issues:review-verdict',
|
|
32
|
+
'address-issues:minor-triage', 'aiwg-mission:worker-pick', 'aiwg-mission:failure-triage',
|
|
33
|
+
'subagent-scoping:worker-pick', 'subagent-scoping:failure-triage',
|
|
34
|
+
].map(caller => ({ caller, coverage: 'instruction-only' }));
|
|
35
|
+
export const DECISION_OFFLOAD_EXECUTABLE_COVERAGE = [
|
|
36
|
+
{ entrypoint: 'aiwg decision ask', transport: 'cli', coverage: 'shared-contract-generic' },
|
|
37
|
+
{ entrypoint: 'decision-ask', transport: 'mcp', coverage: 'shared-contract-generic' },
|
|
38
|
+
];
|
|
39
|
+
function safeCaller(caller) {
|
|
40
|
+
if (!ATTRIBUTION_FIELD_PATTERN.test(caller))
|
|
41
|
+
throw new DecisionAskUsageError('invalid-caller', 'checkpoint caller must be a valid attribution tag');
|
|
42
|
+
return caller;
|
|
43
|
+
}
|
|
44
|
+
export function decisionOffloadCategory(caller) {
|
|
45
|
+
if (!caller)
|
|
46
|
+
return 'unattributed';
|
|
47
|
+
const prefix = caller.split(':', 1)[0].toLowerCase();
|
|
48
|
+
if (['diagnosis', 'diagnostic', 'control'].includes(prefix))
|
|
49
|
+
return 'diagnostic';
|
|
50
|
+
if (['setup', 'verify'].includes(prefix))
|
|
51
|
+
return 'setup';
|
|
52
|
+
if (['benchmark', 'bench', 'qualification', 'eval'].includes(prefix))
|
|
53
|
+
return 'benchmark';
|
|
54
|
+
return 'organic';
|
|
55
|
+
}
|
|
56
|
+
async function providerFor(options) {
|
|
57
|
+
const work = (async () => {
|
|
58
|
+
const resolve = options.resolveProvider ?? resolveActiveProvider;
|
|
59
|
+
const resolution = await resolve({ cwd: options.projectRoot, explicitProvider: options.explicitProvider, env: options.env ?? process.env });
|
|
60
|
+
return resolution.provider ?? 'unknown';
|
|
61
|
+
})().catch(() => 'unknown');
|
|
62
|
+
const outcome = await bounded(work, options.providerTimeoutMs ?? DECISION_OPPORTUNITY_RECORD_TIMEOUT_MS);
|
|
63
|
+
return outcome === TIMED_OUT ? 'unknown' : outcome;
|
|
64
|
+
}
|
|
65
|
+
async function bounded(work, timeoutMs) {
|
|
66
|
+
let timer;
|
|
67
|
+
const timeout = new Promise(resolve => {
|
|
68
|
+
timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
|
|
69
|
+
// Keep this timer referenced: it is the only liveness guarantee when an
|
|
70
|
+
// injected resolver or filesystem promise never settles in a standalone
|
|
71
|
+
// CLI process. Unref would let Node exit before the ask can run.
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
const outcome = await Promise.race([work, timeout]);
|
|
75
|
+
if (outcome === TIMED_OUT)
|
|
76
|
+
work.catch(() => undefined);
|
|
77
|
+
return outcome;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (timer)
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function boundedAppend(append, record, projectRoot, timeoutMs, warn) {
|
|
85
|
+
let work;
|
|
86
|
+
try {
|
|
87
|
+
work = Promise.resolve(append(record, projectRoot, warn));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
warn(`warning: failed to record decision opportunity: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const outcome = await bounded(work.catch(error => {
|
|
94
|
+
warn(`warning: failed to record decision opportunity: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
return false;
|
|
96
|
+
}), timeoutMs);
|
|
97
|
+
if (outcome === TIMED_OUT) {
|
|
98
|
+
warn(`warning: decision opportunity recording exceeded ${timeoutMs}ms; completion unknown`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return outcome;
|
|
102
|
+
}
|
|
103
|
+
function recordFor(options, fields, correlationId, provider) {
|
|
104
|
+
const session = resolveDecisionSession(options.explicitSessionId, options.env ?? process.env);
|
|
105
|
+
const caller = safeCaller(options.caller);
|
|
106
|
+
return {
|
|
107
|
+
schema: DECISION_OPPORTUNITY_SCHEMA,
|
|
108
|
+
correlationId,
|
|
109
|
+
timestamp: new Date(options.now?.() ?? Date.now()).toISOString(),
|
|
110
|
+
provider,
|
|
111
|
+
...session,
|
|
112
|
+
transport: options.transport,
|
|
113
|
+
caller,
|
|
114
|
+
category: options.category ?? decisionOffloadCategory(caller),
|
|
115
|
+
...fields,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const RECORD_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d{3}Z$/;
|
|
119
|
+
function isStrictTimestamp(value) {
|
|
120
|
+
if (typeof value !== 'string' || !RECORD_TIMESTAMP_PATTERN.test(value))
|
|
121
|
+
return false;
|
|
122
|
+
const parsed = new Date(value);
|
|
123
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
124
|
+
}
|
|
125
|
+
function validOpportunity(value) {
|
|
126
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
127
|
+
return false;
|
|
128
|
+
const v = value;
|
|
129
|
+
return v.schema === DECISION_OPPORTUNITY_SCHEMA
|
|
130
|
+
&& typeof v.correlationId === 'string' && v.correlationId.length > 0
|
|
131
|
+
&& isStrictTimestamp(v.timestamp)
|
|
132
|
+
&& ['started', 'completed', 'skipped'].includes(String(v.phase))
|
|
133
|
+
&& typeof v.provider === 'string'
|
|
134
|
+
&& typeof v.sessionId === 'string' && typeof v.sessionSource === 'string'
|
|
135
|
+
&& ['cli', 'mcp', 'internal'].includes(String(v.transport))
|
|
136
|
+
&& typeof v.caller === 'string' && ATTRIBUTION_FIELD_PATTERN.test(v.caller)
|
|
137
|
+
&& ['organic', 'diagnostic', 'setup', 'benchmark', 'unattributed'].includes(String(v.category))
|
|
138
|
+
&& typeof v.eligible === 'boolean' && typeof v.attempted === 'boolean'
|
|
139
|
+
&& ['answered', 'fallback', 'skipped', 'unknown'].includes(String(v.outcome))
|
|
140
|
+
&& typeof v.reason === 'string'
|
|
141
|
+
&& (typeof v.ledgerRecorded === 'boolean' || v.ledgerRecorded === null);
|
|
142
|
+
}
|
|
143
|
+
async function writeFully(handle, buffer) {
|
|
144
|
+
let offset = 0;
|
|
145
|
+
while (offset < buffer.length) {
|
|
146
|
+
const { bytesWritten } = await handle.write(buffer.subarray(offset));
|
|
147
|
+
if (!Number.isFinite(bytesWritten) || bytesWritten <= 0)
|
|
148
|
+
throw new Error('decision opportunity: write made no progress');
|
|
149
|
+
offset += bytesWritten;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function appendDecisionOpportunity(record, projectRoot, warn = message => process.stderr.write(`${message}\n`)) {
|
|
153
|
+
if (!validOpportunity(record))
|
|
154
|
+
return false;
|
|
155
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
156
|
+
if (Buffer.byteLength(line, 'utf8') > MAX_OPPORTUNITY_RECORD_BYTES) {
|
|
157
|
+
warn(`warning: decision opportunity exceeds the ${MAX_OPPORTUNITY_RECORD_BYTES}-byte size cap and was not written`);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
let handle;
|
|
161
|
+
try {
|
|
162
|
+
const directory = projectAiwgWritePath(projectRoot, 'decision');
|
|
163
|
+
const file = path.join(directory, OPPORTUNITY_FILE);
|
|
164
|
+
await mkdir(directory, { recursive: true, mode: DIR_MODE });
|
|
165
|
+
const dirStat = await lstat(directory);
|
|
166
|
+
if (!dirStat.isDirectory() || dirStat.isSymbolicLink() || (dirStat.mode & 0o077) !== 0)
|
|
167
|
+
throw new Error('unsafe opportunity directory');
|
|
168
|
+
try {
|
|
169
|
+
const fileStat = await lstat(file);
|
|
170
|
+
if (!fileStat.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || (fileStat.mode & 0o077) !== 0)
|
|
171
|
+
throw new Error('unsafe opportunity file');
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.code !== 'ENOENT')
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
handle = await open(file, fsConstants.O_APPEND | fsConstants.O_CREAT | fsConstants.O_WRONLY | (fsConstants.O_NOFOLLOW ?? 0), FILE_MODE);
|
|
178
|
+
const opened = await handle.stat();
|
|
179
|
+
if (!opened.isFile() || opened.nlink !== 1 || (opened.mode & 0o077) !== 0)
|
|
180
|
+
throw new Error('unsafe opened opportunity file');
|
|
181
|
+
await writeFully(handle, Buffer.from(line, 'utf8'));
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
warn(`warning: failed to record decision opportunity: ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await handle?.close().catch(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export async function runDecisionOffloadCheckpoint(input, options) {
|
|
193
|
+
const correlationId = (options.id ?? randomUUID)();
|
|
194
|
+
const provider = await providerFor(options);
|
|
195
|
+
const append = options.appendOpportunity ?? appendDecisionOpportunity;
|
|
196
|
+
let warned = false;
|
|
197
|
+
let pendingOpportunityWarning;
|
|
198
|
+
const warn = (message) => {
|
|
199
|
+
if (warned)
|
|
200
|
+
return;
|
|
201
|
+
warned = true;
|
|
202
|
+
(options.warn ?? (value => process.stderr.write(`${value}\n`)))(message);
|
|
203
|
+
};
|
|
204
|
+
const queueOpportunityWarning = (message) => { pendingOpportunityWarning ??= message; };
|
|
205
|
+
const flushOpportunityWarning = () => {
|
|
206
|
+
if (pendingOpportunityWarning)
|
|
207
|
+
warn(pendingOpportunityWarning);
|
|
208
|
+
};
|
|
209
|
+
const eligibility = options.eligibility ?? { eligible: true };
|
|
210
|
+
const appendBounded = (record, appendWarn) => boundedAppend(append, record, options.projectRoot, options.opportunityTimeoutMs ?? DECISION_OPPORTUNITY_RECORD_TIMEOUT_MS, appendWarn);
|
|
211
|
+
if (!eligibility.eligible) {
|
|
212
|
+
const opportunity = recordFor(options, { phase: 'skipped', eligible: false, attempted: false, outcome: 'skipped', reason: eligibility.reason ?? 'other-exclusion', ledgerRecorded: null }, correlationId, provider);
|
|
213
|
+
return { outcome: 'skipped', result: null, opportunity, opportunityRecorded: await appendBounded(opportunity, warn) };
|
|
214
|
+
}
|
|
215
|
+
// Persist the eligible, attempted checkpoint before invoking the backend.
|
|
216
|
+
// If the process dies in the call, the offline snapshot retains an unknown
|
|
217
|
+
// attempt rather than incorrectly reporting that no opportunity existed.
|
|
218
|
+
const started = recordFor(options, {
|
|
219
|
+
phase: 'started', eligible: true, attempted: true, outcome: 'unknown',
|
|
220
|
+
reason: 'attempt-started', ledgerRecorded: null,
|
|
221
|
+
}, correlationId, provider);
|
|
222
|
+
const attemptRecorded = await appendBounded(started, queueOpportunityWarning);
|
|
223
|
+
let result;
|
|
224
|
+
try {
|
|
225
|
+
const ask = options.ask ?? askDecision;
|
|
226
|
+
result = await ask(input, options.askRuntime ?? { cwd: options.projectRoot, env: options.env ?? process.env });
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
const usage = error instanceof DecisionAskUsageError;
|
|
230
|
+
const opportunity = recordFor(options, {
|
|
231
|
+
phase: 'completed',
|
|
232
|
+
eligible: true, attempted: !usage, outcome: usage ? 'skipped' : 'unknown',
|
|
233
|
+
reason: usage ? 'usage-rejected' : 'execution-unknown', ledgerRecorded: false,
|
|
234
|
+
}, correlationId, provider);
|
|
235
|
+
const opportunityRecorded = await appendBounded(opportunity, queueOpportunityWarning);
|
|
236
|
+
flushOpportunityWarning();
|
|
237
|
+
if (usage)
|
|
238
|
+
throw error;
|
|
239
|
+
return { outcome: 'unknown', result: null, opportunity, attemptRecorded, opportunityRecorded, error };
|
|
240
|
+
}
|
|
241
|
+
let ledger = null;
|
|
242
|
+
try {
|
|
243
|
+
ledger = await (options.recordAsk ?? recordDecisionAsk)(input, result, {
|
|
244
|
+
...options,
|
|
245
|
+
warn,
|
|
246
|
+
caller: options.ledgerCaller === undefined ? safeCaller(options.caller) : options.ledgerCaller,
|
|
247
|
+
id: () => correlationId,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
warn(`warning: failed to record decision ledger: ${error instanceof Error ? error.message : String(error)}`);
|
|
252
|
+
}
|
|
253
|
+
const opportunity = recordFor(options, {
|
|
254
|
+
phase: 'completed',
|
|
255
|
+
eligible: true, attempted: result.reason !== 'not-configured', outcome: result.status,
|
|
256
|
+
reason: result.reason, ledgerRecorded: ledger ? true : null,
|
|
257
|
+
}, correlationId, provider);
|
|
258
|
+
const opportunityRecorded = await appendBounded(opportunity, queueOpportunityWarning);
|
|
259
|
+
flushOpportunityWarning();
|
|
260
|
+
return { outcome: result.status, result, opportunity, attemptRecorded, opportunityRecorded };
|
|
261
|
+
}
|
|
262
|
+
/** Collapse append-only started/completed events to one current state per checkpoint. */
|
|
263
|
+
function collapseOpportunities(records) {
|
|
264
|
+
const byId = new Map();
|
|
265
|
+
for (const record of records)
|
|
266
|
+
byId.set(record.correlationId, record);
|
|
267
|
+
return [...byId.values()];
|
|
268
|
+
}
|
|
269
|
+
async function readOpportunities(projectRoot) {
|
|
270
|
+
const file = projectAiwgPath(projectRoot, 'decision', OPPORTUNITY_FILE);
|
|
271
|
+
const records = [];
|
|
272
|
+
let invalid = 0;
|
|
273
|
+
try {
|
|
274
|
+
const input = createReadStream(file, { encoding: 'utf8' });
|
|
275
|
+
for await (const line of createInterface({ input, crlfDelay: Infinity })) {
|
|
276
|
+
if (!line.trim())
|
|
277
|
+
continue;
|
|
278
|
+
try {
|
|
279
|
+
const value = JSON.parse(line);
|
|
280
|
+
if (validOpportunity(value))
|
|
281
|
+
records.push(value);
|
|
282
|
+
else
|
|
283
|
+
invalid += 1;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
invalid += 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { records, invalid, status: 'available' };
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (error.code === 'ENOENT')
|
|
293
|
+
return { records, invalid, status: 'absent' };
|
|
294
|
+
return { records, invalid: invalid + 1, status: 'unreadable' };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const ACTIVITY_LINE_PATTERN = /^##\s+\[(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})\]\s+[a-z]+\s+\|\s+(.+?)\s*$/;
|
|
298
|
+
const ORCHESTRATION_ACTIVITY_PATTERN = /\b(?:worker[- ]dispatch|dispatch(?:ed|ing)?\s+(?:a\s+)?worker|address-issues(?:\s+cycle)?|orchestration\s+worker)\b/i;
|
|
299
|
+
async function readOrchestrationActivity(projectRoot, since) {
|
|
300
|
+
const root = projectAiwgPath(projectRoot);
|
|
301
|
+
const candidates = [path.join(root, 'activity.log'), path.join(root, 'activity-log', 'activity.log')];
|
|
302
|
+
for (const candidate of candidates) {
|
|
303
|
+
try {
|
|
304
|
+
let recognizable = 0;
|
|
305
|
+
let valid = 0;
|
|
306
|
+
const input = createReadStream(candidate, { encoding: 'utf8' });
|
|
307
|
+
for await (const line of createInterface({ input, crlfDelay: Infinity })) {
|
|
308
|
+
const match = ACTIVITY_LINE_PATTERN.exec(line);
|
|
309
|
+
if (!match)
|
|
310
|
+
continue;
|
|
311
|
+
const timestamp = Date.parse(`${match[1]}T${match[2]}:00.000Z`);
|
|
312
|
+
if (!Number.isFinite(timestamp) || timestamp < since.getTime())
|
|
313
|
+
continue;
|
|
314
|
+
valid += 1;
|
|
315
|
+
if (ORCHESTRATION_ACTIVITY_PATTERN.test(match[3]))
|
|
316
|
+
recognizable += 1;
|
|
317
|
+
}
|
|
318
|
+
return { status: 'available', source: path.relative(projectRoot, candidate), valid, recognizable };
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (error.code === 'ENOENT')
|
|
322
|
+
continue;
|
|
323
|
+
return { status: 'unreadable', source: path.relative(projectRoot, candidate), valid: 0, recognizable: 0 };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { status: 'absent', source: null, valid: 0, recognizable: 0 };
|
|
327
|
+
}
|
|
328
|
+
function filterScope(records, sessionId, since) {
|
|
329
|
+
return records.filter(record => sessionId ? record.sessionId === sessionId : Date.parse(record.timestamp) >= since.getTime());
|
|
330
|
+
}
|
|
331
|
+
function completedCalls(records) {
|
|
332
|
+
const fallbackReasons = {};
|
|
333
|
+
let answered = 0;
|
|
334
|
+
let fallback = 0;
|
|
335
|
+
let llmTokensAvoided = 0;
|
|
336
|
+
let jevTokens = 0;
|
|
337
|
+
for (const record of records) {
|
|
338
|
+
if (record.status === 'answered')
|
|
339
|
+
answered += 1;
|
|
340
|
+
else {
|
|
341
|
+
fallback += 1;
|
|
342
|
+
const reason = record.fallbackReason ?? 'unknown';
|
|
343
|
+
fallbackReasons[reason] = (fallbackReasons[reason] ?? 0) + 1;
|
|
344
|
+
}
|
|
345
|
+
llmTokensAvoided += record.llm_tokens_avoided ?? record.estimatedLlmTokensAvoided;
|
|
346
|
+
jevTokens += record.jev_tokens ?? (record.usage?.inputTokens ?? 0) + (record.usage?.outputTokens ?? 0);
|
|
347
|
+
}
|
|
348
|
+
return { completed: records.length, answered, fallback, fallbackReasons, llmTokensAvoided, jevTokens };
|
|
349
|
+
}
|
|
350
|
+
function opportunityCounts(records) {
|
|
351
|
+
const reasons = {};
|
|
352
|
+
for (const record of records)
|
|
353
|
+
reasons[record.reason] = (reasons[record.reason] ?? 0) + 1;
|
|
354
|
+
return {
|
|
355
|
+
observed: records.length,
|
|
356
|
+
eligible: records.filter(record => record.eligible).length,
|
|
357
|
+
attempted: records.filter(record => record.attempted).length,
|
|
358
|
+
answered: records.filter(record => record.outcome === 'answered').length,
|
|
359
|
+
fallback: records.filter(record => record.outcome === 'fallback').length,
|
|
360
|
+
skipped: records.filter(record => record.outcome === 'skipped').length,
|
|
361
|
+
unknown: records.filter(record => record.outcome === 'unknown').length,
|
|
362
|
+
usageRejected: records.filter(record => record.reason === 'usage-rejected').length,
|
|
363
|
+
ledgerRecordingUnknown: records.filter(record => record.ledgerRecorded === null && record.outcome !== 'skipped').length,
|
|
364
|
+
reasons,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function activationEvidence(input) {
|
|
368
|
+
return { ...(input?.ruleSourceAvailable !== undefined ? { ruleSourceAvailable: input.ruleSourceAvailable } : {}), addon: input?.addon ?? 'unknown', ruleDelivery: input?.ruleDelivery ?? 'unknown',
|
|
369
|
+
startupAnchor: input?.startupAnchor ?? 'unknown', nativeActivation: input?.nativeActivation ?? 'not-claimed',
|
|
370
|
+
...(input?.source ? { source: input.source } : {}) };
|
|
371
|
+
}
|
|
372
|
+
function askReadinessState(configured, latest) {
|
|
373
|
+
if (configured === null)
|
|
374
|
+
return 'unknown';
|
|
375
|
+
if (!configured)
|
|
376
|
+
return 'disabled';
|
|
377
|
+
if (latest?.outcome === 'answered')
|
|
378
|
+
return 'reachable';
|
|
379
|
+
if (latest?.outcome === 'fallback' && latest.attempted)
|
|
380
|
+
return 'fallback';
|
|
381
|
+
if (latest?.outcome === 'unknown')
|
|
382
|
+
return 'unknown';
|
|
383
|
+
return 'configured-unprobed';
|
|
384
|
+
}
|
|
385
|
+
export async function buildDecisionOffloadSnapshot(options) {
|
|
386
|
+
const projectRoot = path.resolve(options.projectRoot);
|
|
387
|
+
const env = options.env ?? process.env;
|
|
388
|
+
const now = options.now ?? new Date();
|
|
389
|
+
let projectConfig = null;
|
|
390
|
+
let configReadable = true;
|
|
391
|
+
try {
|
|
392
|
+
projectConfig = await readAiwgConfig(projectRoot);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
configReadable = false;
|
|
396
|
+
}
|
|
397
|
+
const configuredThresholds = projectConfig?.decision?.offload_report;
|
|
398
|
+
const windowHours = options.windowHours ?? configuredThresholds?.window_hours ?? 24;
|
|
399
|
+
const since = new Date(now.getTime() - windowHours * 60 * 60 * 1000);
|
|
400
|
+
const detectedSession = resolveDecisionSession(options.sessionId, env);
|
|
401
|
+
const sessionId = options.sessionId ?? (detectedSession.sessionId === 'unknown' ? undefined : detectedSession.sessionId);
|
|
402
|
+
const thresholds = {
|
|
403
|
+
minimumEligible: configuredThresholds?.minimum_eligible ?? 1,
|
|
404
|
+
minimumAttemptRate: configuredThresholds?.minimum_attempt_rate ?? 0.5,
|
|
405
|
+
repeatedFallbacks: configuredThresholds?.repeated_fallbacks ?? 3,
|
|
406
|
+
...options.thresholds,
|
|
407
|
+
};
|
|
408
|
+
let enablement = null;
|
|
409
|
+
try {
|
|
410
|
+
enablement = resolveDecisionEnablement({ cwd: projectRoot, env });
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
configReadable = false;
|
|
414
|
+
}
|
|
415
|
+
let credential;
|
|
416
|
+
try {
|
|
417
|
+
credential = await inspectJevCredential({ cwd: projectRoot, env });
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
credential = { configured: null, enabled: null, source: 'unknown' };
|
|
421
|
+
}
|
|
422
|
+
const [opportunitiesRead, ledger, activity] = await Promise.all([
|
|
423
|
+
readOpportunities(projectRoot),
|
|
424
|
+
decisionLedgerHistory(projectRoot),
|
|
425
|
+
readOrchestrationActivity(projectRoot, since),
|
|
426
|
+
]);
|
|
427
|
+
const opportunityEvents = filterScope(opportunitiesRead.records, sessionId, since);
|
|
428
|
+
const opportunities = collapseOpportunities(opportunityEvents);
|
|
429
|
+
const calls = filterScope(ledger.records, sessionId, since);
|
|
430
|
+
const organicOpportunities = opportunities.filter(record => record.category === 'organic');
|
|
431
|
+
const diagnosticOpportunities = opportunities.filter(record => record.category !== 'organic');
|
|
432
|
+
const organic = opportunityCounts(organicOpportunities);
|
|
433
|
+
const diagnostics = opportunityCounts(diagnosticOpportunities);
|
|
434
|
+
const opportunityIds = new Set(opportunities.filter(record => record.attempted).map(record => record.correlationId));
|
|
435
|
+
const callIds = new Set(calls.map(record => record.id));
|
|
436
|
+
const organicOpportunityIds = new Set(organicOpportunities.filter(record => record.attempted).map(record => record.correlationId));
|
|
437
|
+
const organicCallIds = new Set(calls.filter(record => decisionOffloadCategory(record.caller) === 'organic').map(record => record.id));
|
|
438
|
+
const latestObserved = [...opportunities].sort((a, b) => a.timestamp.localeCompare(b.timestamp)).at(-1);
|
|
439
|
+
const latestTransportEvidence = [...opportunities].filter(record => record.attempted)
|
|
440
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp)).at(-1);
|
|
441
|
+
const askConfigured = credential.configured === null || credential.enabled === null
|
|
442
|
+
? null : credential.configured && credential.enabled;
|
|
443
|
+
const evaluateState = configReadable && enablement
|
|
444
|
+
? (enablement.enabled ? 'configured-unprobed' : 'disabled') : 'unknown';
|
|
445
|
+
let inspectedActivation;
|
|
446
|
+
if (!options.activationEvidence) {
|
|
447
|
+
try {
|
|
448
|
+
const inspected = await inspectDecisionOffloadActivation({ cwd: projectRoot, frameworkRoot: options.frameworkRoot, env });
|
|
449
|
+
inspectedActivation = {
|
|
450
|
+
addon: inspected.installed === true ? 'installed' : inspected.installed === false ? 'missing' : 'unknown',
|
|
451
|
+
ruleSourceAvailable: inspected.ruleSourcePresent,
|
|
452
|
+
ruleDelivery: inspected.ruleDelivery,
|
|
453
|
+
startupAnchor: inspected.startupAnchorPresent === true ? 'present'
|
|
454
|
+
: inspected.startupAnchorPresent === false ? 'missing' : 'unknown',
|
|
455
|
+
nativeActivation: inspected.nativeActivation,
|
|
456
|
+
source: 'local-configuration-and-startup-pointer; source availability is not delivery',
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
catch { /* unavailable inspection remains unknown, never a loading claim */ }
|
|
460
|
+
}
|
|
461
|
+
const activation = activationEvidence(options.activationEvidence ?? inspectedActivation);
|
|
462
|
+
const organicCalls = calls.filter(record => decisionOffloadCategory(record.caller) === 'organic');
|
|
463
|
+
const activityCandidate = activity.recognizable > 0 && organicCalls.length === 0;
|
|
464
|
+
const activitySuppressionReason = !activityCandidate ? null
|
|
465
|
+
: activation.addon === 'unknown' ? 'activation-unknown'
|
|
466
|
+
: activation.addon === 'missing' ? 'addon-missing'
|
|
467
|
+
: askConfigured === null ? 'ask-readiness-unknown'
|
|
468
|
+
: askConfigured === false ? 'ask-disabled-or-unconfigured'
|
|
469
|
+
: null;
|
|
470
|
+
const activityGap = activityCandidate && activitySuppressionReason === null;
|
|
471
|
+
const driftReasons = [];
|
|
472
|
+
const attemptedWithoutLedger = [...opportunityIds].filter(id => !callIds.has(id)).length;
|
|
473
|
+
const organicAttemptedWithoutLedger = [...organicOpportunityIds].filter(id => !organicCallIds.has(id)).length;
|
|
474
|
+
if (!configReadable || opportunitiesRead.status === 'unreadable' || opportunitiesRead.invalid > 0 || organic.unknown > 0)
|
|
475
|
+
driftReasons.push('observation-unknown');
|
|
476
|
+
if (organic.ledgerRecordingUnknown > 0 || ledger.skipped > 0)
|
|
477
|
+
driftReasons.push('recording-failure-or-unknown');
|
|
478
|
+
if (organicAttemptedWithoutLedger > 0)
|
|
479
|
+
driftReasons.push('attempted-without-completed-ledger');
|
|
480
|
+
if (organic.usageRejected > 0)
|
|
481
|
+
driftReasons.push('usage-rejection');
|
|
482
|
+
if (activation.addon === 'missing')
|
|
483
|
+
driftReasons.push('missing-addon');
|
|
484
|
+
else if (activation.addon === 'unknown')
|
|
485
|
+
driftReasons.push('activation-evidence-unknown');
|
|
486
|
+
if (activation.ruleDelivery === 'missing')
|
|
487
|
+
driftReasons.push('missing-rule-delivery');
|
|
488
|
+
if (activation.startupAnchor === 'missing')
|
|
489
|
+
driftReasons.push('missing-activation-anchor');
|
|
490
|
+
if (askConfigured === false)
|
|
491
|
+
driftReasons.push('disabled-or-unconfigured');
|
|
492
|
+
else if (askConfigured === null)
|
|
493
|
+
driftReasons.push('ask-readiness-unknown');
|
|
494
|
+
if (organic.eligible > 0 && organic.eligible >= thresholds.minimumEligible) {
|
|
495
|
+
const rate = organic.eligible ? organic.attempted / organic.eligible : 0;
|
|
496
|
+
if (organic.attempted === 0)
|
|
497
|
+
driftReasons.push('observed-eligible-zero-attempts');
|
|
498
|
+
else if (rate < thresholds.minimumAttemptRate)
|
|
499
|
+
driftReasons.push('observed-eligible-low-attempt-rate');
|
|
500
|
+
}
|
|
501
|
+
if (organic.fallback >= thresholds.repeatedFallbacks)
|
|
502
|
+
driftReasons.push('repeated-fallback');
|
|
503
|
+
const unknown = !configReadable || askConfigured === null || activation.addon === 'unknown' || activation.startupAnchor === 'unknown'
|
|
504
|
+
|| opportunitiesRead.status === 'unreadable' || opportunitiesRead.invalid > 0
|
|
505
|
+
|| organic.unknown > 0 || organic.ledgerRecordingUnknown > 0 || ledger.skipped > 0 || organicAttemptedWithoutLedger > 0;
|
|
506
|
+
const driftStatus = unknown ? 'unknown' : organic.eligible === 0 ? 'not-applicable' : driftReasons.length ? 'advisory' : 'ok';
|
|
507
|
+
return {
|
|
508
|
+
schema: DECISION_OFFLOAD_SNAPSHOT_SCHEMA,
|
|
509
|
+
scope: { projectRoot, mode: sessionId ? 'session' : 'window', sessionId: sessionId ?? null,
|
|
510
|
+
since: sessionId ? null : since.toISOString(), windowHours: sessionId ? null : windowHours, generatedAt: now.toISOString() },
|
|
511
|
+
readiness: {
|
|
512
|
+
ask: { state: askReadinessState(askConfigured, latestTransportEvidence), configured: credential.configured,
|
|
513
|
+
enabled: credential.enabled, source: credential.source, probed: false },
|
|
514
|
+
evaluate: { state: evaluateState, enabled: enablement?.enabled ?? null,
|
|
515
|
+
source: configReadable && enablement ? enablement.source : 'unknown', probed: false },
|
|
516
|
+
observed: latestObserved ? {
|
|
517
|
+
timestamp: latestObserved.timestamp, transport: latestObserved.transport,
|
|
518
|
+
category: latestObserved.category, outcome: latestObserved.outcome,
|
|
519
|
+
reason: latestObserved.reason,
|
|
520
|
+
} : null,
|
|
521
|
+
},
|
|
522
|
+
activation,
|
|
523
|
+
coverage: { executable: DECISION_OFFLOAD_EXECUTABLE_COVERAGE,
|
|
524
|
+
instructionOnly: DECISION_OFFLOAD_CHECKPOINT_COVERAGE, unsupportedObservation: 'unknown' },
|
|
525
|
+
opportunities: { organic, excluded: diagnostics, journalEvents: opportunityEvents.length, invalidRecords: opportunitiesRead.invalid,
|
|
526
|
+
journal: opportunitiesRead.status },
|
|
527
|
+
calls: { organic: completedCalls(organicCalls),
|
|
528
|
+
excluded: completedCalls(calls.filter(record => decisionOffloadCategory(record.caller) !== 'organic')), invalidRecords: ledger.skipped },
|
|
529
|
+
activityHint: {
|
|
530
|
+
status: activityGap ? 'advisory' : 'none',
|
|
531
|
+
reason: activityGap ? 'orchestration-without-organic-completed-calls' : null,
|
|
532
|
+
suppressionReason: activitySuppressionReason,
|
|
533
|
+
recognizableOrchestrationEvents: activity.recognizable,
|
|
534
|
+
eligibleCount: 'unknown',
|
|
535
|
+
confidence: 'low',
|
|
536
|
+
scope: 'recent-window',
|
|
537
|
+
source: activity.source,
|
|
538
|
+
journal: activity.status,
|
|
539
|
+
note: 'Generic activity is a low-confidence signal only; it is not a measured eligible-opportunity denominator.',
|
|
540
|
+
},
|
|
541
|
+
correlation: { matched: [...opportunityIds].filter(id => callIds.has(id)).length,
|
|
542
|
+
attemptedWithoutLedger,
|
|
543
|
+
organicAttemptedWithoutLedger,
|
|
544
|
+
ledgerWithoutOpportunity: [...callIds].filter(id => !opportunityIds.has(id)).length },
|
|
545
|
+
drift: { status: driftStatus, advisory: driftStatus === 'advisory', reasons: [...new Set(driftReasons)], thresholds,
|
|
546
|
+
thresholdSource: options.thresholds ? 'options' : configuredThresholds ? 'project' : 'default',
|
|
547
|
+
note: organic.eligible === 0 ? 'No observed eligible checkpoints; this is not evidence of healthy adoption.' : null },
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=offload.js.map
|
|
@@ -110,9 +110,7 @@ export async function runDecisionLoadHarness(options) {
|
|
|
110
110
|
const { manifest, clock, driver, sample } = options;
|
|
111
111
|
const arrivals = decisionLoadArrivals(manifest, options.windowSeconds);
|
|
112
112
|
const start = clock.now();
|
|
113
|
-
const
|
|
114
|
-
const cpuStart = threadCpu?.();
|
|
115
|
-
const wallStart = process.hrtime.bigint();
|
|
113
|
+
const cpuStart = process.threadCpuUsage();
|
|
116
114
|
let rss = process.memoryUsage().rss;
|
|
117
115
|
let heap = process.memoryUsage().heapUsed;
|
|
118
116
|
const sampleMemory = () => {
|
|
@@ -170,8 +168,8 @@ export async function runDecisionLoadHarness(options) {
|
|
|
170
168
|
await clock.advance(drainStep);
|
|
171
169
|
const results = await all;
|
|
172
170
|
sampleMemory();
|
|
173
|
-
const threadUsage =
|
|
174
|
-
const cpuMicros = threadUsage
|
|
171
|
+
const threadUsage = process.threadCpuUsage(cpuStart);
|
|
172
|
+
const cpuMicros = threadUsage.user + threadUsage.system;
|
|
175
173
|
const virtualDurationMs = Math.max(1, clock.now() - start);
|
|
176
174
|
const shedReasons = {};
|
|
177
175
|
let attempts = 0;
|
|
@@ -219,7 +217,7 @@ export async function runDecisionLoadHarness(options) {
|
|
|
219
217
|
heapUsedMiB: Number((heap / 1_048_576).toFixed(1)),
|
|
220
218
|
residentMemoryMiB: Number((rss / 1_048_576).toFixed(1)),
|
|
221
219
|
cpuPercentOneCore: Number(((cpuMicros / 1000) / virtualDurationMs * 100).toFixed(3)),
|
|
222
|
-
cpuSource:
|
|
220
|
+
cpuSource: 'thread',
|
|
223
221
|
},
|
|
224
222
|
};
|
|
225
223
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Planning assumptions, not a vendor price quote or measured caller usage. */
|
|
2
|
+
export const DEFAULT_DECISION_SAVINGS_WEIGHTS = {
|
|
3
|
+
caller_model_class: 'reasoning',
|
|
4
|
+
reasoning_tokens: 1024,
|
|
5
|
+
output_tokens: 128,
|
|
6
|
+
llm_per_mtok: 15,
|
|
7
|
+
jev_per_mtok: 0.2,
|
|
8
|
+
};
|
|
9
|
+
export function decisionSavingsWeights(ask) {
|
|
10
|
+
const modelClass = ask?.savings?.caller_model_class ?? 'reasoning';
|
|
11
|
+
return {
|
|
12
|
+
...DEFAULT_DECISION_SAVINGS_WEIGHTS,
|
|
13
|
+
...ask?.cost,
|
|
14
|
+
caller_model_class: modelClass,
|
|
15
|
+
reasoning_tokens: ask?.savings?.reasoning_tokens ?? (modelClass === 'reasoning' ? 1024 : 256),
|
|
16
|
+
output_tokens: ask?.savings?.output_tokens ?? 128,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function estimateDecisionSavings(inputTokens, answered, jevTokens, weights) {
|
|
20
|
+
const llmTokens = answered ? inputTokens + weights.reasoning_tokens + weights.output_tokens : 0;
|
|
21
|
+
return {
|
|
22
|
+
llm_tokens_avoided: llmTokens,
|
|
23
|
+
jev_tokens: jevTokens,
|
|
24
|
+
cost_weighted_net_usd: (llmTokens * weights.llm_per_mtok - jevTokens * weights.jev_per_mtok) / 1_000_000,
|
|
25
|
+
estimationMethod: { version: 'aiwg-decision-savings/v2', input_tokens: inputTokens, ...weights },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=savings.js.map
|
|
@@ -41,7 +41,11 @@ export const UNKNOWN_REASONS = [
|
|
|
41
41
|
'container-unreadable',
|
|
42
42
|
'paging-incomplete',
|
|
43
43
|
'consistency-lag',
|
|
44
|
-
'tracker-blocked',
|
|
44
|
+
'tracker-blocked', // Retained for existing ledger records.
|
|
45
|
+
'tracker-provider-unresolved',
|
|
46
|
+
'tracker-repository-mismatch',
|
|
47
|
+
'tracker-credential-missing',
|
|
48
|
+
'tracker-no-access-route',
|
|
45
49
|
'malformed-response',
|
|
46
50
|
'evidence-conflict',
|
|
47
51
|
];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @see docs/contracts/effect-ledger.v1.md "Built-in verifiers"
|
|
15
15
|
*/
|
|
16
|
+
import { signalChild } from '../../process/signal-child.mjs';
|
|
16
17
|
import { spawn } from 'node:child_process';
|
|
17
18
|
import { EffectVerifierError } from './types.js';
|
|
18
19
|
export const GIT_VERIFIER_VERSION = '1.0.0';
|
|
@@ -31,7 +32,7 @@ function spawnRunner(repoDir, binary) {
|
|
|
31
32
|
const collect = (sink) => (chunk) => {
|
|
32
33
|
size += chunk.length;
|
|
33
34
|
if (size > MAX_OUTPUT)
|
|
34
|
-
child
|
|
35
|
+
signalChild(child);
|
|
35
36
|
else
|
|
36
37
|
sink.push(chunk);
|
|
37
38
|
};
|