@orion-agents/orion-code 0.1.8 → 0.1.9
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/CHANGELOG.md +445 -0
- package/README.md +15 -6
- package/README.zh-CN.md +26 -7
- package/dist/agents/coordinator.d.ts.map +1 -1
- package/dist/agents/coordinator.js +49 -9
- package/dist/agents/coordinator.js.map +1 -1
- package/dist/agents/fork.d.ts +5 -1
- package/dist/agents/fork.d.ts.map +1 -1
- package/dist/agents/fork.js +9 -10
- package/dist/agents/fork.js.map +1 -1
- package/dist/agents/worker-pool.d.ts.map +1 -1
- package/dist/agents/worker-pool.js +8 -3
- package/dist/agents/worker-pool.js.map +1 -1
- package/dist/cli.js +6 -6
- package/dist/cli.js.map +1 -1
- package/dist/commands/context-tool-command-handlers.d.ts.map +1 -1
- package/dist/commands/context-tool-command-handlers.js +42 -0
- package/dist/commands/context-tool-command-handlers.js.map +1 -1
- package/dist/commands/model-command-handlers.d.ts +1 -1
- package/dist/commands/model-command-handlers.d.ts.map +1 -1
- package/dist/commands/model-command-handlers.js +166 -1
- package/dist/commands/model-command-handlers.js.map +1 -1
- package/dist/commands/session-command-handlers.d.ts.map +1 -1
- package/dist/commands/session-command-handlers.js +97 -5
- package/dist/commands/session-command-handlers.js.map +1 -1
- package/dist/commands/session-commands.js +2 -2
- package/dist/commands/session-commands.js.map +1 -1
- package/dist/commands/types.d.ts +6 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/framework/index.d.ts +5 -3
- package/dist/framework/index.d.ts.map +1 -1
- package/dist/framework/index.js +9 -1
- package/dist/framework/index.js.map +1 -1
- package/dist/framework/query.d.ts +26 -4
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +319 -62
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/stop-decision.d.ts +56 -0
- package/dist/framework/stop-decision.d.ts.map +1 -0
- package/dist/framework/stop-decision.js +36 -0
- package/dist/framework/stop-decision.js.map +1 -0
- package/dist/framework/tool-scheduler.d.ts +5 -0
- package/dist/framework/tool-scheduler.d.ts.map +1 -1
- package/dist/framework/tool-scheduler.js +24 -9
- package/dist/framework/tool-scheduler.js.map +1 -1
- package/dist/harness/assembler.d.ts.map +1 -1
- package/dist/harness/assembler.js +77 -19
- package/dist/harness/assembler.js.map +1 -1
- package/dist/harness/capability-profile.d.ts +3 -0
- package/dist/harness/capability-profile.d.ts.map +1 -0
- package/dist/harness/capability-profile.js +44 -0
- package/dist/harness/capability-profile.js.map +1 -0
- package/dist/harness/capsule.d.ts.map +1 -1
- package/dist/harness/capsule.js +22 -3
- package/dist/harness/capsule.js.map +1 -1
- package/dist/harness/compact-benchmark-corpus.d.ts +10 -0
- package/dist/harness/compact-benchmark-corpus.d.ts.map +1 -0
- package/dist/harness/compact-benchmark-corpus.js +56 -0
- package/dist/harness/compact-benchmark-corpus.js.map +1 -0
- package/dist/harness/context-harness.d.ts +23 -3
- package/dist/harness/context-harness.d.ts.map +1 -1
- package/dist/harness/context-harness.js +183 -11
- package/dist/harness/context-harness.js.map +1 -1
- package/dist/harness/contract.d.ts.map +1 -1
- package/dist/harness/contract.js +106 -11
- package/dist/harness/contract.js.map +1 -1
- package/dist/harness/drift-guard.d.ts +2 -1
- package/dist/harness/drift-guard.d.ts.map +1 -1
- package/dist/harness/drift-guard.js +315 -20
- package/dist/harness/drift-guard.js.map +1 -1
- package/dist/harness/evidence.d.ts.map +1 -1
- package/dist/harness/evidence.js +25 -12
- package/dist/harness/evidence.js.map +1 -1
- package/dist/harness/index.d.ts +7 -2
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +19 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +44 -14
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/progress-controller.d.ts +14 -0
- package/dist/harness/progress-controller.d.ts.map +1 -0
- package/dist/harness/progress-controller.js +110 -0
- package/dist/harness/progress-controller.js.map +1 -0
- package/dist/harness/state.d.ts.map +1 -1
- package/dist/harness/state.js +5 -2
- package/dist/harness/state.js.map +1 -1
- package/dist/harness/stop-controller.d.ts +15 -0
- package/dist/harness/stop-controller.d.ts.map +1 -0
- package/dist/harness/stop-controller.js +87 -0
- package/dist/harness/stop-controller.js.map +1 -0
- package/dist/harness/types.d.ts +137 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/verification.d.ts +7 -0
- package/dist/harness/verification.d.ts.map +1 -0
- package/dist/harness/verification.js +122 -0
- package/dist/harness/verification.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts +3 -0
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +30 -5
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/prompt-context.d.ts +42 -0
- package/dist/memory/prompt-context.d.ts.map +1 -0
- package/dist/memory/prompt-context.js +129 -0
- package/dist/memory/prompt-context.js.map +1 -0
- package/dist/runtime/chat-compact.d.ts +26 -0
- package/dist/runtime/chat-compact.d.ts.map +1 -0
- package/dist/runtime/chat-compact.js +132 -0
- package/dist/runtime/chat-compact.js.map +1 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +64 -74
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/goals/coordinator.d.ts +3 -0
- package/dist/runtime/goals/coordinator.d.ts.map +1 -1
- package/dist/runtime/goals/coordinator.js +5 -15
- package/dist/runtime/goals/coordinator.js.map +1 -1
- package/dist/runtime/goals/prompt.js +1 -1
- package/dist/runtime/goals/prompt.js.map +1 -1
- package/dist/runtime/goals/stop-policy.d.ts +9 -0
- package/dist/runtime/goals/stop-policy.d.ts.map +1 -0
- package/dist/runtime/goals/stop-policy.js +83 -0
- package/dist/runtime/goals/stop-policy.js.map +1 -0
- package/dist/runtime/goals/types.d.ts +0 -1
- package/dist/runtime/goals/types.d.ts.map +1 -1
- package/dist/runtime/goals/types.js +0 -1
- package/dist/runtime/goals/types.js.map +1 -1
- package/dist/runtime/model-coordinator.d.ts +44 -1
- package/dist/runtime/model-coordinator.d.ts.map +1 -1
- package/dist/runtime/model-coordinator.js +161 -19
- package/dist/runtime/model-coordinator.js.map +1 -1
- package/dist/runtime/subagents/index.d.ts +1 -1
- package/dist/runtime/subagents/index.d.ts.map +1 -1
- package/dist/runtime/subagents/index.js.map +1 -1
- package/dist/runtime/subagents/production.d.ts +6 -2
- package/dist/runtime/subagents/production.d.ts.map +1 -1
- package/dist/runtime/subagents/production.js +25 -6
- package/dist/runtime/subagents/production.js.map +1 -1
- package/dist/runtime/subagents/runner.d.ts +12 -1
- package/dist/runtime/subagents/runner.d.ts.map +1 -1
- package/dist/runtime/subagents/runner.js +62 -1
- package/dist/runtime/subagents/runner.js.map +1 -1
- package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
- package/dist/runtime/subagents/runtime-integration.js +2 -1
- package/dist/runtime/subagents/runtime-integration.js.map +1 -1
- package/dist/runtime/subagents/supervisor.d.ts.map +1 -1
- package/dist/runtime/subagents/supervisor.js +6 -0
- package/dist/runtime/subagents/supervisor.js.map +1 -1
- package/dist/runtime/subagents/types.d.ts +4 -1
- package/dist/runtime/subagents/types.d.ts.map +1 -1
- package/dist/runtime/subagents/types.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +2 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/services/compact/auto-compact.d.ts +82 -2
- package/dist/services/compact/auto-compact.d.ts.map +1 -1
- package/dist/services/compact/auto-compact.js +226 -44
- package/dist/services/compact/auto-compact.js.map +1 -1
- package/dist/services/compact/compact.d.ts +54 -37
- package/dist/services/compact/compact.d.ts.map +1 -1
- package/dist/services/compact/compact.js +512 -127
- package/dist/services/compact/compact.js.map +1 -1
- package/dist/services/compact/coordinator.d.ts +5 -1
- package/dist/services/compact/coordinator.d.ts.map +1 -1
- package/dist/services/compact/coordinator.js +10 -1
- package/dist/services/compact/coordinator.js.map +1 -1
- package/dist/services/compact/fingerprint.d.ts +27 -0
- package/dist/services/compact/fingerprint.d.ts.map +1 -0
- package/dist/services/compact/fingerprint.js +69 -0
- package/dist/services/compact/fingerprint.js.map +1 -0
- package/dist/services/compact/index.d.ts +7 -5
- package/dist/services/compact/index.d.ts.map +1 -1
- package/dist/services/compact/index.js +19 -6
- package/dist/services/compact/index.js.map +1 -1
- package/dist/services/compact/planner.d.ts +41 -0
- package/dist/services/compact/planner.d.ts.map +1 -0
- package/dist/services/compact/planner.js +102 -0
- package/dist/services/compact/planner.js.map +1 -0
- package/dist/services/compact/semantic-summary.d.ts +93 -0
- package/dist/services/compact/semantic-summary.d.ts.map +1 -0
- package/dist/services/compact/semantic-summary.js +158 -0
- package/dist/services/compact/semantic-summary.js.map +1 -0
- package/dist/services/compact/summary-generator.d.ts +23 -0
- package/dist/services/compact/summary-generator.d.ts.map +1 -1
- package/dist/services/compact/summary-generator.js +173 -39
- package/dist/services/compact/summary-generator.js.map +1 -1
- package/dist/services/file-context.d.ts.map +1 -1
- package/dist/services/file-context.js +29 -33
- package/dist/services/file-context.js.map +1 -1
- package/dist/services/global-config.d.ts +6 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js +8 -0
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/llm.d.ts +3 -0
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +7 -0
- package/dist/services/llm.js.map +1 -1
- package/dist/services/project-instructions.d.ts.map +1 -1
- package/dist/services/project-instructions.js +10 -17
- package/dist/services/project-instructions.js.map +1 -1
- package/dist/services/safe-project-reader.d.ts +30 -0
- package/dist/services/safe-project-reader.d.ts.map +1 -0
- package/dist/services/safe-project-reader.js +141 -0
- package/dist/services/safe-project-reader.js.map +1 -0
- package/dist/services/session-storage.d.ts +134 -5
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +727 -57
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/usage-state.d.ts +7 -0
- package/dist/services/usage-state.d.ts.map +1 -1
- package/dist/services/usage-state.js +117 -16
- package/dist/services/usage-state.js.map +1 -1
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +34 -35
- package/dist/skills/runtime.js.map +1 -1
- package/docs/migration/v0.1.8-to-v0.1.9.md +60 -0
- package/docs/plan/v0.1.9-release-checklist.md +64 -0
- package/docs/readme.md +27 -0
- package/npm-shrinkwrap.json +14 -38
- package/package.json +5 -1
- package/dist/services/compact/micro-compact.d.ts +0 -21
- package/dist/services/compact/micro-compact.d.ts.map +0 -1
- package/dist/services/compact/micro-compact.js +0 -99
- package/dist/services/compact/micro-compact.js.map +0 -1
|
@@ -10,6 +10,8 @@ exports.SESSION_SIDECAR_SUFFIXES = exports.redactTraceText = void 0;
|
|
|
10
10
|
exports.resolveProjectPath = resolveProjectPath;
|
|
11
11
|
exports.getProjectKey = getProjectKey;
|
|
12
12
|
exports.isSessionMetaFile = isSessionMetaFile;
|
|
13
|
+
exports.prepareSessionCompactSourceReceipt = prepareSessionCompactSourceReceipt;
|
|
14
|
+
exports.advanceSessionCompactSourceReceipt = advanceSessionCompactSourceReceipt;
|
|
13
15
|
exports.createSession = createSession;
|
|
14
16
|
exports.saveSessionMeta = saveSessionMeta;
|
|
15
17
|
exports.updateSessionEffort = updateSessionEffort;
|
|
@@ -60,11 +62,55 @@ const tool_call_groups_1 = require("./compact/tool-call-groups");
|
|
|
60
62
|
const redaction_1 = require("./redaction");
|
|
61
63
|
const debug_log_1 = require("../utils/debug-log");
|
|
62
64
|
const goal_storage_1 = require("./goal-storage");
|
|
65
|
+
const fingerprint_1 = require("./compact/fingerprint");
|
|
66
|
+
const token_estimate_1 = require("../utils/token-estimate");
|
|
63
67
|
const harness_1 = require("../harness");
|
|
64
68
|
var redaction_2 = require("./redaction");
|
|
65
69
|
Object.defineProperty(exports, "redactTraceText", { enumerable: true, get: function () { return redaction_2.redactTraceText; } });
|
|
66
70
|
function sanitizeTraceEvent(event) {
|
|
67
71
|
const sanitized = { ...event };
|
|
72
|
+
if (sanitized.stopDecision) {
|
|
73
|
+
sanitized.stopDecision = {
|
|
74
|
+
...sanitized.stopDecision,
|
|
75
|
+
reason: {
|
|
76
|
+
...sanitized.stopDecision.reason,
|
|
77
|
+
message: (0, redaction_1.redactTraceText)(sanitized.stopDecision.reason.message),
|
|
78
|
+
},
|
|
79
|
+
evidence: sanitized.stopDecision.evidence.map(item => ({
|
|
80
|
+
...item,
|
|
81
|
+
source: (0, redaction_1.redactTraceText)(item.source),
|
|
82
|
+
detail: (0, redaction_1.redactTraceText)(item.detail),
|
|
83
|
+
})),
|
|
84
|
+
nextActions: sanitized.stopDecision.nextActions.map(item => ({
|
|
85
|
+
...item,
|
|
86
|
+
label: (0, redaction_1.redactTraceText)(item.label),
|
|
87
|
+
command: item.command ? (0, redaction_1.redactTraceText)(item.command) : undefined,
|
|
88
|
+
})),
|
|
89
|
+
resources: Object.fromEntries(Object.entries(sanitized.stopDecision.resources).map(([key, value]) => [
|
|
90
|
+
key,
|
|
91
|
+
value ? { ...value } : value,
|
|
92
|
+
])),
|
|
93
|
+
criterionStates: sanitized.stopDecision.criterionStates?.map(item => ({
|
|
94
|
+
...item,
|
|
95
|
+
id: (0, redaction_1.redactTraceText)(item.id),
|
|
96
|
+
})),
|
|
97
|
+
evidenceRefs: sanitized.stopDecision.evidenceRefs?.map(redaction_1.redactTraceText),
|
|
98
|
+
progressDelta: sanitized.stopDecision.progressDelta
|
|
99
|
+
? {
|
|
100
|
+
...sanitized.stopDecision.progressDelta,
|
|
101
|
+
criterionChanges: sanitized.stopDecision.progressDelta.criterionChanges.map(item => ({
|
|
102
|
+
...item,
|
|
103
|
+
id: (0, redaction_1.redactTraceText)(item.id),
|
|
104
|
+
})),
|
|
105
|
+
newEvidenceRefs: sanitized.stopDecision.progressDelta.newEvidenceRefs.map(redaction_1.redactTraceText),
|
|
106
|
+
newChangedFiles: sanitized.stopDecision.progressDelta.newChangedFiles.map(redaction_1.redactTraceText),
|
|
107
|
+
newDecisions: sanitized.stopDecision.progressDelta.newDecisions.map(redaction_1.redactTraceText),
|
|
108
|
+
newBlockers: sanitized.stopDecision.progressDelta.newBlockers.map(redaction_1.redactTraceText),
|
|
109
|
+
newDiagnostics: sanitized.stopDecision.progressDelta.newDiagnostics.map(redaction_1.redactTraceText),
|
|
110
|
+
}
|
|
111
|
+
: undefined,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
68
114
|
for (const key of [
|
|
69
115
|
'argsSummary',
|
|
70
116
|
'error',
|
|
@@ -94,6 +140,14 @@ function sanitizeTraceEvent(event) {
|
|
|
94
140
|
if (sanitized.promptOmittedEvidence) {
|
|
95
141
|
sanitized.promptOmittedEvidence = sanitized.promptOmittedEvidence.map(redaction_1.redactTraceText);
|
|
96
142
|
}
|
|
143
|
+
if (sanitized.promptSectionManifest) {
|
|
144
|
+
sanitized.promptSectionManifest = sanitized.promptSectionManifest.map(item => ({
|
|
145
|
+
...item,
|
|
146
|
+
name: (0, redaction_1.redactTraceText)(item.name),
|
|
147
|
+
source: (0, redaction_1.redactTraceText)(item.source),
|
|
148
|
+
reason: item.reason ? (0, redaction_1.redactTraceText)(item.reason) : undefined,
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
97
151
|
if (sanitized.workspaceChangedByTurn) {
|
|
98
152
|
sanitized.workspaceChangedByTurn = sanitized.workspaceChangedByTurn.map(redaction_1.redactTraceText);
|
|
99
153
|
}
|
|
@@ -419,10 +473,277 @@ function parseHarnessSidecarFile(path) {
|
|
|
419
473
|
return null;
|
|
420
474
|
}
|
|
421
475
|
}
|
|
476
|
+
function canonicalizeForHash(value) {
|
|
477
|
+
if (Array.isArray(value))
|
|
478
|
+
return value.map(canonicalizeForHash);
|
|
479
|
+
if (!value || typeof value !== 'object')
|
|
480
|
+
return value;
|
|
481
|
+
return Object.fromEntries(Object.entries(value)
|
|
482
|
+
.filter(([, entry]) => entry !== undefined)
|
|
483
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
484
|
+
.map(([key, entry]) => [key, canonicalizeForHash(entry)]));
|
|
485
|
+
}
|
|
486
|
+
function canonicalContentHash(value) {
|
|
487
|
+
return (0, crypto_1.createHash)('sha256')
|
|
488
|
+
.update(JSON.stringify(canonicalizeForHash(value)), 'utf-8')
|
|
489
|
+
.digest('hex');
|
|
490
|
+
}
|
|
491
|
+
function compactBindingHash(parts) {
|
|
492
|
+
return canonicalContentHash(parts);
|
|
493
|
+
}
|
|
494
|
+
function isSha256(value) {
|
|
495
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
496
|
+
}
|
|
497
|
+
function isRecord(value) {
|
|
498
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
499
|
+
}
|
|
500
|
+
function isStringArray(value) {
|
|
501
|
+
return Array.isArray(value) && value.every(item => typeof item === 'string' && item.length > 0);
|
|
502
|
+
}
|
|
503
|
+
function sameStrings(left, right) {
|
|
504
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
505
|
+
}
|
|
506
|
+
function validatePrepareSourceReceipt(receipt, sessionId) {
|
|
507
|
+
if (receipt.schemaVersion !== 1 ||
|
|
508
|
+
receipt.sessionId !== sessionId ||
|
|
509
|
+
!Number.isInteger(receipt.sourceMessageCount) ||
|
|
510
|
+
receipt.sourceMessageCount < 0 ||
|
|
511
|
+
!isSha256(receipt.sourcePrefixHash) ||
|
|
512
|
+
(receipt.activeCheckpointId !== null &&
|
|
513
|
+
(typeof receipt.activeCheckpointId !== 'string' || !receipt.activeCheckpointId))) {
|
|
514
|
+
throw new Error('Invalid compact prepare-source receipt');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function assertPrepareSourceMatches(receipt, session, rawMessages) {
|
|
518
|
+
validatePrepareSourceReceipt(receipt, session.id);
|
|
519
|
+
const activeCheckpointId = session.activeCompactCheckpointId ?? null;
|
|
520
|
+
if (activeCheckpointId !== receipt.activeCheckpointId) {
|
|
521
|
+
throw new Error(`Stale compact candidate: active checkpoint changed from ${receipt.activeCheckpointId ?? 'none'} to ${activeCheckpointId ?? 'none'}`);
|
|
522
|
+
}
|
|
523
|
+
if (rawMessages.length !== receipt.sourceMessageCount) {
|
|
524
|
+
throw new Error(`Stale compact candidate: source message count changed from ${receipt.sourceMessageCount} to ${rawMessages.length}`);
|
|
525
|
+
}
|
|
526
|
+
const prefixHash = canonicalContentHash(rawMessages);
|
|
527
|
+
if (prefixHash !== receipt.sourcePrefixHash) {
|
|
528
|
+
throw new Error('Stale compact candidate: source transcript hash changed during preparation');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/** Capture the append-only transcript identity before preparing a compact candidate. */
|
|
532
|
+
function prepareSessionCompactSourceReceipt(sessionId) {
|
|
533
|
+
const receipt = withLockedSession(sessionId, session => {
|
|
534
|
+
const rawMessages = readSessionMessagesForSession(session);
|
|
535
|
+
return {
|
|
536
|
+
schemaVersion: 1,
|
|
537
|
+
sessionId,
|
|
538
|
+
sourceMessageCount: rawMessages.length,
|
|
539
|
+
sourcePrefixHash: canonicalContentHash(rawMessages),
|
|
540
|
+
activeCheckpointId: session.activeCompactCheckpointId ?? null,
|
|
541
|
+
};
|
|
542
|
+
});
|
|
543
|
+
if (!receipt)
|
|
544
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
545
|
+
return receipt;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Advance a prepare receipt only across the exact messages written by the
|
|
549
|
+
* preparing turn. Any interleaved writer fails closed instead of being claimed
|
|
550
|
+
* by a candidate that never observed its messages.
|
|
551
|
+
*/
|
|
552
|
+
function advanceSessionCompactSourceReceipt(receipt, appendedMessages) {
|
|
553
|
+
const advanced = withLockedSession(receipt.sessionId, session => {
|
|
554
|
+
validatePrepareSourceReceipt(receipt, session.id);
|
|
555
|
+
if ((session.activeCompactCheckpointId ?? null) !== receipt.activeCheckpointId) {
|
|
556
|
+
throw new Error('Stale compact candidate: active checkpoint changed during preparation');
|
|
557
|
+
}
|
|
558
|
+
const rawMessages = readSessionMessagesForSession(session);
|
|
559
|
+
if (rawMessages.length !== receipt.sourceMessageCount + appendedMessages.length ||
|
|
560
|
+
canonicalContentHash(rawMessages.slice(0, receipt.sourceMessageCount)) !==
|
|
561
|
+
receipt.sourcePrefixHash ||
|
|
562
|
+
canonicalContentHash(rawMessages.slice(receipt.sourceMessageCount)) !==
|
|
563
|
+
canonicalContentHash(appendedMessages)) {
|
|
564
|
+
throw new Error('Stale compact candidate: transcript tail contains concurrent or unexpected messages');
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
...receipt,
|
|
568
|
+
sourceMessageCount: rawMessages.length,
|
|
569
|
+
sourcePrefixHash: canonicalContentHash(rawMessages),
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
if (!advanced)
|
|
573
|
+
throw new Error(`Session not found: ${receipt.sessionId}`);
|
|
574
|
+
return advanced;
|
|
575
|
+
}
|
|
576
|
+
function inspectSemanticSummary(value) {
|
|
577
|
+
if (!isRecord(value) || value.version !== 1) {
|
|
578
|
+
throw new Error('Compact semantic summary schema is invalid');
|
|
579
|
+
}
|
|
580
|
+
if (!Number.isInteger(value.taskEpoch) || value.taskEpoch < 1) {
|
|
581
|
+
throw new Error('Compact semantic summary taskEpoch is invalid');
|
|
582
|
+
}
|
|
583
|
+
for (const field of [
|
|
584
|
+
'constraints',
|
|
585
|
+
'decisions',
|
|
586
|
+
'completed',
|
|
587
|
+
'pending',
|
|
588
|
+
'blockers',
|
|
589
|
+
'files',
|
|
590
|
+
'evidenceRefs',
|
|
591
|
+
]) {
|
|
592
|
+
if (!isStringArray(value[field])) {
|
|
593
|
+
throw new Error(`Compact semantic summary ${field} must be a string array`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (!isRecord(value.coverage) || !isStringArray(value.coverage.groupIds)) {
|
|
597
|
+
throw new Error('Compact semantic summary coverage is invalid');
|
|
598
|
+
}
|
|
599
|
+
const coverageGroupIds = value.coverage.groupIds;
|
|
600
|
+
if (value.coverage.groupCount !== coverageGroupIds.length ||
|
|
601
|
+
!Number.isInteger(value.coverage.messageCount) ||
|
|
602
|
+
value.coverage.messageCount < 0) {
|
|
603
|
+
throw new Error('Compact semantic summary coverage counts are inconsistent');
|
|
604
|
+
}
|
|
605
|
+
if (!Array.isArray(value.items)) {
|
|
606
|
+
throw new Error('Compact semantic summary items must be an array');
|
|
607
|
+
}
|
|
608
|
+
const taskEpochs = [];
|
|
609
|
+
for (const item of value.items) {
|
|
610
|
+
if (!isRecord(item) ||
|
|
611
|
+
typeof item.id !== 'string' ||
|
|
612
|
+
!item.id ||
|
|
613
|
+
typeof item.groupId !== 'string' ||
|
|
614
|
+
!item.groupId ||
|
|
615
|
+
!isStringArray(item.sourceRefs) ||
|
|
616
|
+
!Number.isInteger(item.tokenEstimate) ||
|
|
617
|
+
item.tokenEstimate < 0 ||
|
|
618
|
+
!Number.isInteger(item.taskEpoch) ||
|
|
619
|
+
item.taskEpoch < 1 ||
|
|
620
|
+
typeof item.text !== 'string') {
|
|
621
|
+
throw new Error('Compact semantic summary contains an invalid ContextItem');
|
|
622
|
+
}
|
|
623
|
+
taskEpochs.push(item.taskEpoch);
|
|
624
|
+
}
|
|
625
|
+
const criterionStatesValue = value.criterionStates;
|
|
626
|
+
if (criterionStatesValue !== undefined && !Array.isArray(criterionStatesValue)) {
|
|
627
|
+
throw new Error('Compact semantic criterionStates must be an array');
|
|
628
|
+
}
|
|
629
|
+
const criterionStates = (criterionStatesValue ?? []).map(item => {
|
|
630
|
+
if (!isRecord(item) ||
|
|
631
|
+
typeof item.id !== 'string' ||
|
|
632
|
+
!item.id ||
|
|
633
|
+
typeof item.statement !== 'string' ||
|
|
634
|
+
!['pending', 'passed', 'failed', 'waived'].includes(String(item.status)) ||
|
|
635
|
+
!isStringArray(item.evidenceRefs)) {
|
|
636
|
+
throw new Error('Compact semantic summary contains an invalid criterion state');
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
id: item.id,
|
|
640
|
+
status: String(item.status),
|
|
641
|
+
evidenceRefs: item.evidenceRefs,
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
if (new Set(criterionStates.map(item => item.id)).size !== criterionStates.length) {
|
|
645
|
+
throw new Error('Compact semantic summary contains duplicate criterion IDs');
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
coverageGroupIds,
|
|
649
|
+
criterionStates,
|
|
650
|
+
taskEpoch: value.taskEpoch,
|
|
651
|
+
taskEpochs,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
function semanticValidationReceipt(semanticSummary, candidate, harnessState) {
|
|
655
|
+
const view = inspectSemanticSummary(semanticSummary);
|
|
656
|
+
const expectedCoverage = candidate.plan.evictedGroups.map(group => group.id);
|
|
657
|
+
if (!sameStrings(view.coverageGroupIds, expectedCoverage)) {
|
|
658
|
+
throw new Error('Compact semantic summary does not cover the prepared evicted groups');
|
|
659
|
+
}
|
|
660
|
+
const expectedTaskEpoch = Math.max(1, Math.floor(harnessState?.taskEpoch ?? harnessState?.contract?.taskEpoch ?? view.taskEpochs[0] ?? 1));
|
|
661
|
+
if (view.taskEpoch !== expectedTaskEpoch ||
|
|
662
|
+
view.taskEpochs.some(taskEpoch => taskEpoch !== expectedTaskEpoch)) {
|
|
663
|
+
throw new Error('Compact semantic summary taskEpoch does not match the active Harness epoch');
|
|
664
|
+
}
|
|
665
|
+
const contractCriteria = harnessState?.contract?.criteria ?? [];
|
|
666
|
+
const expectedCriteria = contractCriteria.map(criterion => ({
|
|
667
|
+
id: criterion.id,
|
|
668
|
+
status: criterion.status ?? 'pending',
|
|
669
|
+
evidenceRefs: [...criterion.evidenceRefs],
|
|
670
|
+
}));
|
|
671
|
+
if (expectedCriteria.length > 0) {
|
|
672
|
+
if (view.criterionStates.length !== expectedCriteria.length) {
|
|
673
|
+
throw new Error('Compact semantic summary omitted active task criteria');
|
|
674
|
+
}
|
|
675
|
+
for (const [index, expected] of expectedCriteria.entries()) {
|
|
676
|
+
const actual = view.criterionStates[index];
|
|
677
|
+
if (actual.id !== expected.id ||
|
|
678
|
+
actual.status !== expected.status ||
|
|
679
|
+
!sameStrings(actual.evidenceRefs, expected.evidenceRefs)) {
|
|
680
|
+
throw new Error(`Compact semantic criterion mismatch: ${expected.id}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const knownEvidenceRefs = new Set([
|
|
685
|
+
...(harnessState?.ledger.flatMap(item => [item.id, `ledger:${item.id}`]) ?? []),
|
|
686
|
+
...(harnessState?.evidenceIndex?.flatMap(item => [item.id, `evidence:${item.id}`]) ?? []),
|
|
687
|
+
...(harnessState?.capsule?.keyFacts.flatMap(item => [item.id, `fact:${item.id}`]) ?? []),
|
|
688
|
+
]);
|
|
689
|
+
const validatedEvidenceRefs = [
|
|
690
|
+
...new Set(view.criterionStates.flatMap(item => item.evidenceRefs)),
|
|
691
|
+
];
|
|
692
|
+
if (validatedEvidenceRefs.length > 0 && !harnessState) {
|
|
693
|
+
throw new Error('Compact semantic evidence references require an authoritative Harness state');
|
|
694
|
+
}
|
|
695
|
+
for (const criterion of view.criterionStates) {
|
|
696
|
+
if (criterion.status === 'passed' && criterion.evidenceRefs.length === 0) {
|
|
697
|
+
throw new Error(`Passed compact criterion lacks evidence: ${criterion.id}`);
|
|
698
|
+
}
|
|
699
|
+
for (const evidenceRef of criterion.evidenceRefs) {
|
|
700
|
+
if (!knownEvidenceRefs.has(evidenceRef)) {
|
|
701
|
+
throw new Error(`Dangling compact criterion evidence reference: ${evidenceRef}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
schemaValid: true,
|
|
707
|
+
coverageValid: true,
|
|
708
|
+
taskEpoch: expectedTaskEpoch,
|
|
709
|
+
taskEpochVerified: true,
|
|
710
|
+
criterionEvidenceRefsValid: true,
|
|
711
|
+
preservedCriterionIds: view.criterionStates.map(item => item.id),
|
|
712
|
+
validatedEvidenceRefs,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function validatePersistedSemanticReceipt(semanticSummary, receipt) {
|
|
716
|
+
const view = inspectSemanticSummary(semanticSummary);
|
|
717
|
+
if (receipt.schemaValid !== true ||
|
|
718
|
+
receipt.coverageValid !== true ||
|
|
719
|
+
receipt.taskEpochVerified !== true ||
|
|
720
|
+
receipt.criterionEvidenceRefsValid !== true ||
|
|
721
|
+
!Number.isInteger(receipt.taskEpoch) ||
|
|
722
|
+
receipt.taskEpoch < 1 ||
|
|
723
|
+
!isStringArray(receipt.preservedCriterionIds) ||
|
|
724
|
+
!Array.isArray(receipt.validatedEvidenceRefs) ||
|
|
725
|
+
!receipt.validatedEvidenceRefs.every(ref => typeof ref === 'string' && ref.length > 0) ||
|
|
726
|
+
view.taskEpoch !== receipt.taskEpoch ||
|
|
727
|
+
view.taskEpochs.some(taskEpoch => taskEpoch !== receipt.taskEpoch) ||
|
|
728
|
+
!sameStrings(view.criterionStates.map(item => item.id), receipt.preservedCriterionIds) ||
|
|
729
|
+
!sameStrings([...new Set(view.criterionStates.flatMap(item => item.evidenceRefs))], receipt.validatedEvidenceRefs)) {
|
|
730
|
+
throw new Error('Compact semantic validation receipt does not match its summary');
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function reportInvalidCompactCheckpoint(path, reason) {
|
|
734
|
+
(0, debug_log_1.debugError)('session-storage.validateCompactCheckpoint', new Error(`Invalid compact checkpoint: ${reason}`), path);
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
function compactCandidatePath(path) {
|
|
738
|
+
return `${path}.candidate`;
|
|
739
|
+
}
|
|
740
|
+
function compactPreviousPath(path) {
|
|
741
|
+
return `${path}.previous`;
|
|
742
|
+
}
|
|
422
743
|
function parseCompactCheckpointFile(path) {
|
|
423
744
|
try {
|
|
424
745
|
const parsed = JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
|
|
425
|
-
if (parsed?.version !== 1 ||
|
|
746
|
+
if ((parsed?.version !== 1 && parsed?.version !== 2) ||
|
|
426
747
|
!parsed.checkpointId ||
|
|
427
748
|
!parsed.sessionId ||
|
|
428
749
|
!Array.isArray(parsed.modelHistory) ||
|
|
@@ -430,6 +751,117 @@ function parseCompactCheckpointFile(path) {
|
|
|
430
751
|
parsed.sourceMessageCount < 0 ||
|
|
431
752
|
!parsed.summary ||
|
|
432
753
|
typeof parsed.summary.text !== 'string') {
|
|
754
|
+
return reportInvalidCompactCheckpoint(path, 'base schema mismatch');
|
|
755
|
+
}
|
|
756
|
+
if (parsed.version === 1)
|
|
757
|
+
return parsed;
|
|
758
|
+
if (parsed.sourceBoundary?.startMessageIndex !== 0 ||
|
|
759
|
+
parsed.sourceBoundary.endMessageIndexExclusive !== parsed.sourceMessageCount ||
|
|
760
|
+
!isSha256(parsed.sourcePrefixHash) ||
|
|
761
|
+
!isSha256(parsed.modelHistoryHash) ||
|
|
762
|
+
parsed.summary.schemaVersion !== 2 ||
|
|
763
|
+
typeof parsed.summary.strategy !== 'string' ||
|
|
764
|
+
!parsed.summary.strategy ||
|
|
765
|
+
!Number.isInteger(parsed.contractVersion) ||
|
|
766
|
+
parsed.contractVersion < 1 ||
|
|
767
|
+
!Number.isInteger(parsed.harnessStateVersion) ||
|
|
768
|
+
parsed.harnessStateVersion < 1 ||
|
|
769
|
+
parsed.validation?.schemaValid !== true ||
|
|
770
|
+
parsed.validation.toolCallGroupsValid !== true ||
|
|
771
|
+
parsed.validation.sourcePrefixVerified !== true ||
|
|
772
|
+
parsed.validation.targetMet !== true ||
|
|
773
|
+
!isSha256(parsed.validation.bindingHash) ||
|
|
774
|
+
!Number.isFinite(parsed.validation.targetHeadroomRatio) ||
|
|
775
|
+
!Number.isFinite(parsed.validation.achievedUsageRatio) ||
|
|
776
|
+
!parsed.candidateReceipt ||
|
|
777
|
+
!['semantic_candidate', 'compatibility_adapter'].includes(parsed.candidateReceipt.source) ||
|
|
778
|
+
!isSha256(parsed.candidateReceipt.candidateFingerprint) ||
|
|
779
|
+
!isSha256(parsed.candidateReceipt.persistedProjectionFingerprint) ||
|
|
780
|
+
!Number.isInteger(parsed.candidateReceipt.beforeTokens) ||
|
|
781
|
+
parsed.candidateReceipt.beforeTokens < 0 ||
|
|
782
|
+
!Number.isInteger(parsed.candidateReceipt.afterTokens) ||
|
|
783
|
+
parsed.candidateReceipt.afterTokens < 0 ||
|
|
784
|
+
(parsed.candidateReceipt.targetTokens !== undefined &&
|
|
785
|
+
(!Number.isInteger(parsed.candidateReceipt.targetTokens) ||
|
|
786
|
+
parsed.candidateReceipt.targetTokens < 0)) ||
|
|
787
|
+
!Number.isFinite(parsed.candidateReceipt.targetRatio) ||
|
|
788
|
+
!Array.isArray(parsed.candidateReceipt.diagnostics) ||
|
|
789
|
+
(parsed.harnessBinding !== undefined &&
|
|
790
|
+
(!Number.isInteger(parsed.harnessBinding.schemaVersion) ||
|
|
791
|
+
parsed.harnessBinding.schemaVersion < 1 ||
|
|
792
|
+
!isSha256(parsed.harnessBinding.stateHash))) ||
|
|
793
|
+
(parsed.goalBinding !== undefined &&
|
|
794
|
+
(!parsed.goalBinding.goalId ||
|
|
795
|
+
!Number.isInteger(parsed.goalBinding.revision) ||
|
|
796
|
+
parsed.goalBinding.revision < 0 ||
|
|
797
|
+
!isSha256(parsed.goalBinding.stateHash)))) {
|
|
798
|
+
return reportInvalidCompactCheckpoint(path, 'V2 receipt mismatch');
|
|
799
|
+
}
|
|
800
|
+
if (canonicalContentHash(parsed.modelHistory) !== parsed.modelHistoryHash) {
|
|
801
|
+
return reportInvalidCompactCheckpoint(path, 'replacement history hash mismatch');
|
|
802
|
+
}
|
|
803
|
+
if ((0, fingerprint_1.canonicalMessagesFingerprint)(parsed.modelHistory) !==
|
|
804
|
+
parsed.candidateReceipt.persistedProjectionFingerprint) {
|
|
805
|
+
return reportInvalidCompactCheckpoint(path, 'persisted projection fingerprint mismatch');
|
|
806
|
+
}
|
|
807
|
+
const semanticSummaryHash = parsed.candidateReceipt.semanticSummary
|
|
808
|
+
? canonicalContentHash(parsed.candidateReceipt.semanticSummary)
|
|
809
|
+
: undefined;
|
|
810
|
+
if (semanticSummaryHash !== parsed.candidateReceipt.semanticSummaryHash) {
|
|
811
|
+
return reportInvalidCompactCheckpoint(path, 'semantic summary hash mismatch');
|
|
812
|
+
}
|
|
813
|
+
const coverageHash = parsed.candidateReceipt.semanticSummary
|
|
814
|
+
? canonicalContentHash(parsed.candidateReceipt.semanticSummary.coverage)
|
|
815
|
+
: undefined;
|
|
816
|
+
if (coverageHash !== parsed.candidateReceipt.coverageHash) {
|
|
817
|
+
return reportInvalidCompactCheckpoint(path, 'semantic coverage hash mismatch');
|
|
818
|
+
}
|
|
819
|
+
if (parsed.candidateReceipt.source === 'semantic_candidate') {
|
|
820
|
+
if (!parsed.candidateReceipt.prepareSource ||
|
|
821
|
+
!parsed.candidateReceipt.semanticSummary ||
|
|
822
|
+
!parsed.candidateReceipt.semanticValidation ||
|
|
823
|
+
parsed.validation.prepareSourceVerified !== true ||
|
|
824
|
+
parsed.validation.candidateTokensVerified !== true ||
|
|
825
|
+
parsed.validation.semanticReceiptVerified !== true) {
|
|
826
|
+
return reportInvalidCompactCheckpoint(path, 'semantic candidate validation is incomplete');
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
validatePrepareSourceReceipt(parsed.candidateReceipt.prepareSource, parsed.sessionId);
|
|
830
|
+
if (parsed.candidateReceipt.prepareSource.sourceMessageCount !== parsed.sourceMessageCount ||
|
|
831
|
+
parsed.candidateReceipt.prepareSource.sourcePrefixHash !== parsed.sourcePrefixHash) {
|
|
832
|
+
return reportInvalidCompactCheckpoint(path, 'prepare-source boundary mismatch');
|
|
833
|
+
}
|
|
834
|
+
validatePersistedSemanticReceipt(parsed.candidateReceipt.semanticSummary, parsed.candidateReceipt.semanticValidation);
|
|
835
|
+
}
|
|
836
|
+
catch (error) {
|
|
837
|
+
return reportInvalidCompactCheckpoint(path, error instanceof Error ? error.message : String(error));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
const prepareSourceHash = parsed.candidateReceipt.prepareSource
|
|
841
|
+
? canonicalContentHash(parsed.candidateReceipt.prepareSource)
|
|
842
|
+
: undefined;
|
|
843
|
+
const semanticValidationHash = parsed.candidateReceipt.semanticValidation
|
|
844
|
+
? canonicalContentHash(parsed.candidateReceipt.semanticValidation)
|
|
845
|
+
: undefined;
|
|
846
|
+
const expectedBindingHash = compactBindingHash({
|
|
847
|
+
sourcePrefixHash: parsed.sourcePrefixHash,
|
|
848
|
+
modelHistoryHash: parsed.modelHistoryHash,
|
|
849
|
+
candidateFingerprint: parsed.candidateReceipt.candidateFingerprint,
|
|
850
|
+
persistedProjectionFingerprint: parsed.candidateReceipt.persistedProjectionFingerprint,
|
|
851
|
+
semanticSummaryHash,
|
|
852
|
+
prepareSourceHash,
|
|
853
|
+
semanticValidationHash,
|
|
854
|
+
harnessStateHash: parsed.harnessBinding?.stateHash,
|
|
855
|
+
goalStateHash: parsed.goalBinding?.stateHash,
|
|
856
|
+
});
|
|
857
|
+
if (expectedBindingHash !== parsed.validation.bindingHash) {
|
|
858
|
+
return reportInvalidCompactCheckpoint(path, 'cross-state binding hash mismatch');
|
|
859
|
+
}
|
|
860
|
+
try {
|
|
861
|
+
(0, tool_call_groups_1.assertToolCallGroups)(parsed.modelHistory);
|
|
862
|
+
}
|
|
863
|
+
catch (error) {
|
|
864
|
+
(0, debug_log_1.debugError)('session-storage.validateCompactCheckpoint.toolGroups', error, path);
|
|
433
865
|
return null;
|
|
434
866
|
}
|
|
435
867
|
return parsed;
|
|
@@ -733,30 +1165,158 @@ function persistSessionCompactHistory(sessionId, messages, timestamp = Date.now(
|
|
|
733
1165
|
appendSessionMessages(sessionId, compactMessages);
|
|
734
1166
|
return markSessionTranscriptDisplayStart(sessionId, timestamp);
|
|
735
1167
|
}
|
|
736
|
-
function
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1168
|
+
function loadCompactCheckpointForSession(session, messages) {
|
|
1169
|
+
const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, session.id);
|
|
1170
|
+
const candidatePaths = [compactPath, compactPreviousPath(compactPath)];
|
|
1171
|
+
for (const path of candidatePaths) {
|
|
1172
|
+
if (!(0, fs_1.existsSync)(path))
|
|
1173
|
+
continue;
|
|
1174
|
+
const checkpoint = parseCompactCheckpointFile(path);
|
|
1175
|
+
if (!checkpoint || checkpoint.sessionId !== session.id)
|
|
1176
|
+
continue;
|
|
1177
|
+
if (session.activeCompactCheckpointId !== checkpoint.checkpointId)
|
|
1178
|
+
continue;
|
|
1179
|
+
if (checkpoint.sourceMessageCount > messages.length)
|
|
1180
|
+
continue;
|
|
1181
|
+
if (checkpoint.version === 2 &&
|
|
1182
|
+
canonicalContentHash(messages.slice(0, checkpoint.sourceMessageCount)) !==
|
|
1183
|
+
checkpoint.sourcePrefixHash) {
|
|
1184
|
+
reportInvalidCompactCheckpoint(path, 'covered transcript prefix hash mismatch');
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
return checkpoint;
|
|
745
1188
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
return
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
function loadSessionCompactCheckpoint(sessionId) {
|
|
1192
|
+
return (withLockedSession(sessionId, session => loadCompactCheckpointForSession(session, readSessionMessagesForSession(session))) ?? null);
|
|
750
1193
|
}
|
|
751
1194
|
function commitSessionCompactCheckpoint(input) {
|
|
752
1195
|
const checkpoint = withLockedSession(input.sessionId, session => {
|
|
753
|
-
const
|
|
1196
|
+
const rawMessages = readSessionMessagesForSession(session);
|
|
1197
|
+
const rawCount = rawMessages.length;
|
|
1198
|
+
if (input.candidate && !input.prepareSource) {
|
|
1199
|
+
throw new Error('Semantic compact candidate requires a prepare-source receipt');
|
|
1200
|
+
}
|
|
1201
|
+
if (input.prepareSource) {
|
|
1202
|
+
if (input.prepareSource.sourceMessageCount !== input.sourceMessageCount) {
|
|
1203
|
+
throw new Error('Compact source boundary differs from its prepare-source receipt');
|
|
1204
|
+
}
|
|
1205
|
+
assertPrepareSourceMatches(input.prepareSource, session, rawMessages);
|
|
1206
|
+
}
|
|
754
1207
|
if (input.sourceMessageCount < 0 || input.sourceMessageCount > rawCount) {
|
|
755
1208
|
throw new Error(`Invalid compact source boundary ${input.sourceMessageCount}; transcript has ${rawCount} messages`);
|
|
756
1209
|
}
|
|
757
1210
|
const createdAt = input.createdAt ?? Date.now();
|
|
1211
|
+
const candidateModelHistory = input.modelHistory.map(message => JSON.parse(JSON.stringify(message)));
|
|
1212
|
+
(0, tool_call_groups_1.assertToolCallGroups)(candidateModelHistory);
|
|
1213
|
+
const modelHistory = input.modelHistory
|
|
1214
|
+
.filter(message => message.role !== 'system')
|
|
1215
|
+
.map(message => JSON.parse(JSON.stringify(message)));
|
|
1216
|
+
(0, tool_call_groups_1.assertToolCallGroups)(modelHistory);
|
|
1217
|
+
const targetHeadroomRatio = 0.65;
|
|
1218
|
+
const safeInputBudget = Math.max(1, input.afterUsage.safeInputBudget ?? input.afterUsage.contextWindow);
|
|
1219
|
+
const achievedUsageRatio = input.afterUsage.usedTokens / safeInputBudget;
|
|
1220
|
+
if (!Number.isFinite(achievedUsageRatio) || achievedUsageRatio > targetHeadroomRatio) {
|
|
1221
|
+
throw new Error(`Compact candidate did not reach the required ${Math.round(targetHeadroomRatio * 100)}% safe-input headroom target (${Math.round(achievedUsageRatio * 100)}% used)`);
|
|
1222
|
+
}
|
|
1223
|
+
const sourcePrefixHash = canonicalContentHash(rawMessages.slice(0, input.sourceMessageCount));
|
|
1224
|
+
const modelHistoryHash = canonicalContentHash(modelHistory);
|
|
1225
|
+
const persistedProjectionFingerprint = (0, fingerprint_1.canonicalMessagesFingerprint)(modelHistory);
|
|
1226
|
+
const candidateBeforeTokens = input.candidate?.beforeTokens ?? input.beforeUsage.usedTokens;
|
|
1227
|
+
const candidateAfterTokens = input.candidate?.afterTokens ?? input.afterUsage.usedTokens;
|
|
1228
|
+
const candidateTargetRatio = input.candidate?.plan.targetRatio ?? targetHeadroomRatio;
|
|
1229
|
+
const candidateFingerprint = input.candidate?.fingerprint ?? persistedProjectionFingerprint;
|
|
1230
|
+
for (const [name, value] of [
|
|
1231
|
+
['beforeTokens', candidateBeforeTokens],
|
|
1232
|
+
['afterTokens', candidateAfterTokens],
|
|
1233
|
+
]) {
|
|
1234
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1235
|
+
throw new Error(`Compact candidate ${name} must be a non-negative integer`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (!isSha256(candidateFingerprint)) {
|
|
1239
|
+
throw new Error('Compact candidate fingerprint must be a SHA-256 digest');
|
|
1240
|
+
}
|
|
1241
|
+
if (input.candidate) {
|
|
1242
|
+
const actualCandidateTokens = (0, token_estimate_1.estimateMessagesTokens)(candidateModelHistory);
|
|
1243
|
+
if (candidateAfterTokens !== actualCandidateTokens) {
|
|
1244
|
+
throw new Error(`Compact candidate afterTokens ${candidateAfterTokens} does not match actual history ${actualCandidateTokens}`);
|
|
1245
|
+
}
|
|
1246
|
+
if (input.candidate.plan.targetTokens !== undefined &&
|
|
1247
|
+
candidateAfterTokens > input.candidate.plan.targetTokens) {
|
|
1248
|
+
throw new Error('Compact candidate exceeds its prepared target token budget');
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (!Number.isFinite(candidateTargetRatio) ||
|
|
1252
|
+
candidateTargetRatio <= 0 ||
|
|
1253
|
+
candidateTargetRatio > targetHeadroomRatio) {
|
|
1254
|
+
throw new Error(`Compact candidate target ratio must be within (0, ${targetHeadroomRatio}]`);
|
|
1255
|
+
}
|
|
1256
|
+
const semanticSummary = input.candidate
|
|
1257
|
+
? JSON.parse(JSON.stringify(input.candidate.semanticSummary))
|
|
1258
|
+
: undefined;
|
|
1259
|
+
const semanticSummaryHash = semanticSummary ? canonicalContentHash(semanticSummary) : undefined;
|
|
1260
|
+
const coverageHash = semanticSummary
|
|
1261
|
+
? canonicalContentHash(semanticSummary.coverage)
|
|
1262
|
+
: undefined;
|
|
1263
|
+
const semanticValidation = input.candidate && semanticSummary
|
|
1264
|
+
? semanticValidationReceipt(semanticSummary, input.candidate, input.semanticHarnessState ?? input.harnessState)
|
|
1265
|
+
: undefined;
|
|
1266
|
+
const prepareSource = input.prepareSource
|
|
1267
|
+
? { ...input.prepareSource }
|
|
1268
|
+
: undefined;
|
|
1269
|
+
const harnessBinding = input.harnessState
|
|
1270
|
+
? {
|
|
1271
|
+
schemaVersion: input.harnessState.version ?? input.harnessStateVersion ?? 2,
|
|
1272
|
+
stateHash: canonicalContentHash(input.harnessState),
|
|
1273
|
+
}
|
|
1274
|
+
: undefined;
|
|
1275
|
+
const goalBinding = input.goalBinding
|
|
1276
|
+
? {
|
|
1277
|
+
goalId: input.goalBinding.goalId,
|
|
1278
|
+
revision: input.goalBinding.revision,
|
|
1279
|
+
stateHash: canonicalContentHash(input.goalBinding.state ?? {
|
|
1280
|
+
goalId: input.goalBinding.goalId,
|
|
1281
|
+
revision: input.goalBinding.revision,
|
|
1282
|
+
}),
|
|
1283
|
+
}
|
|
1284
|
+
: undefined;
|
|
1285
|
+
const candidateReceipt = {
|
|
1286
|
+
source: input.candidate ? 'semantic_candidate' : 'compatibility_adapter',
|
|
1287
|
+
candidateFingerprint,
|
|
1288
|
+
persistedProjectionFingerprint,
|
|
1289
|
+
beforeTokens: candidateBeforeTokens,
|
|
1290
|
+
afterTokens: candidateAfterTokens,
|
|
1291
|
+
targetTokens: input.candidate?.plan.targetTokens,
|
|
1292
|
+
targetRatio: candidateTargetRatio,
|
|
1293
|
+
semanticSummary,
|
|
1294
|
+
semanticSummaryHash,
|
|
1295
|
+
diagnostics: input.candidate?.diagnostics.map(diagnostic => ({
|
|
1296
|
+
...diagnostic,
|
|
1297
|
+
message: (0, redaction_1.redactTraceText)(diagnostic.message),
|
|
1298
|
+
})) ?? [],
|
|
1299
|
+
coverageHash,
|
|
1300
|
+
prepareSource,
|
|
1301
|
+
semanticValidation,
|
|
1302
|
+
};
|
|
1303
|
+
const prepareSourceHash = prepareSource ? canonicalContentHash(prepareSource) : undefined;
|
|
1304
|
+
const semanticValidationHash = semanticValidation
|
|
1305
|
+
? canonicalContentHash(semanticValidation)
|
|
1306
|
+
: undefined;
|
|
1307
|
+
const bindingHash = compactBindingHash({
|
|
1308
|
+
sourcePrefixHash,
|
|
1309
|
+
modelHistoryHash,
|
|
1310
|
+
candidateFingerprint,
|
|
1311
|
+
persistedProjectionFingerprint,
|
|
1312
|
+
semanticSummaryHash,
|
|
1313
|
+
prepareSourceHash,
|
|
1314
|
+
semanticValidationHash,
|
|
1315
|
+
harnessStateHash: harnessBinding?.stateHash,
|
|
1316
|
+
goalStateHash: goalBinding?.stateHash,
|
|
1317
|
+
});
|
|
758
1318
|
const nextCheckpoint = {
|
|
759
|
-
version:
|
|
1319
|
+
version: 2,
|
|
760
1320
|
checkpointId: (0, crypto_1.randomUUID)(),
|
|
761
1321
|
sessionId: input.sessionId,
|
|
762
1322
|
createdAt,
|
|
@@ -764,34 +1324,128 @@ function commitSessionCompactCheckpoint(input) {
|
|
|
764
1324
|
modelId: input.modelId,
|
|
765
1325
|
sourceMessageCount: input.sourceMessageCount,
|
|
766
1326
|
transcriptStartMessageIndex: Math.max(0, Math.min(input.sourceMessageCount, input.transcriptStartMessageIndex)),
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
.
|
|
1327
|
+
sourceBoundary: {
|
|
1328
|
+
startMessageIndex: 0,
|
|
1329
|
+
endMessageIndexExclusive: input.sourceMessageCount,
|
|
1330
|
+
},
|
|
1331
|
+
sourcePrefixHash,
|
|
1332
|
+
modelHistory,
|
|
1333
|
+
modelHistoryHash,
|
|
770
1334
|
summary: {
|
|
771
1335
|
...input.summary,
|
|
772
1336
|
sourceMessageCount: input.sourceMessageCount,
|
|
1337
|
+
schemaVersion: 2,
|
|
1338
|
+
strategy: input.strategy ??
|
|
1339
|
+
(input.summary.source === 'llm' ? 'semantic-llm-v2' : 'deterministic-fallback-v2'),
|
|
773
1340
|
},
|
|
774
1341
|
beforeUsage: { ...input.beforeUsage },
|
|
775
1342
|
afterUsage: { ...input.afterUsage },
|
|
1343
|
+
contractVersion: (() => {
|
|
1344
|
+
const derivedContractVersion = input.harnessState?.contract?.version ?? 1;
|
|
1345
|
+
if (input.contractVersion !== undefined &&
|
|
1346
|
+
input.harnessState?.contract?.version !== undefined &&
|
|
1347
|
+
input.contractVersion !== derivedContractVersion) {
|
|
1348
|
+
throw new Error(`Compact contractVersion ${input.contractVersion} does not match Harness contract ${derivedContractVersion}`);
|
|
1349
|
+
}
|
|
1350
|
+
return input.contractVersion ?? derivedContractVersion;
|
|
1351
|
+
})(),
|
|
1352
|
+
harnessStateVersion: harnessBinding?.schemaVersion ?? input.harnessStateVersion ?? 2,
|
|
1353
|
+
harnessBinding,
|
|
1354
|
+
goalBinding,
|
|
1355
|
+
candidateReceipt,
|
|
1356
|
+
validation: {
|
|
1357
|
+
schemaValid: true,
|
|
1358
|
+
toolCallGroupsValid: true,
|
|
1359
|
+
sourcePrefixVerified: true,
|
|
1360
|
+
targetHeadroomRatio,
|
|
1361
|
+
achievedUsageRatio,
|
|
1362
|
+
targetMet: true,
|
|
1363
|
+
prepareSourceVerified: prepareSource ? true : undefined,
|
|
1364
|
+
candidateTokensVerified: input.candidate ? true : undefined,
|
|
1365
|
+
semanticReceiptVerified: semanticValidation ? true : undefined,
|
|
1366
|
+
bindingHash,
|
|
1367
|
+
validatedAt: createdAt,
|
|
1368
|
+
},
|
|
776
1369
|
};
|
|
777
1370
|
const previousId = session.activeCompactCheckpointId;
|
|
778
1371
|
const previousTime = session.lastCompactAt;
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
session.
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
//
|
|
785
|
-
|
|
1372
|
+
const previousUpdatedAt = session.updatedAt;
|
|
1373
|
+
const previousUpdatedAtIso = session.updatedAtIso;
|
|
1374
|
+
const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, input.sessionId);
|
|
1375
|
+
const candidatePath = compactCandidatePath(compactPath);
|
|
1376
|
+
const previousPath = compactPreviousPath(compactPath);
|
|
1377
|
+
// Recover a process that crashed after installing a new sidecar but before
|
|
1378
|
+
// committing its pointer. The previous file is keyed by the durable pointer.
|
|
1379
|
+
if ((0, fs_1.existsSync)(previousPath)) {
|
|
1380
|
+
const installed = (0, fs_1.existsSync)(compactPath) ? parseCompactCheckpointFile(compactPath) : null;
|
|
1381
|
+
const previous = parseCompactCheckpointFile(previousPath);
|
|
1382
|
+
if (previousId &&
|
|
1383
|
+
installed?.checkpointId !== previousId &&
|
|
1384
|
+
previous?.checkpointId === previousId) {
|
|
1385
|
+
if ((0, fs_1.existsSync)(compactPath))
|
|
1386
|
+
(0, fs_1.unlinkSync)(compactPath);
|
|
1387
|
+
(0, fs_1.renameSync)(previousPath, compactPath);
|
|
1388
|
+
}
|
|
1389
|
+
else if (installed?.checkpointId === previousId) {
|
|
1390
|
+
(0, fs_1.unlinkSync)(previousPath);
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
throw new Error('Cannot reconcile interrupted compact checkpoint transaction');
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
(0, atomic_write_1.atomicWriteFileSync)(candidatePath, JSON.stringify(nextCheckpoint, null, 2), { mode: 0o600 });
|
|
1397
|
+
const prepared = parseCompactCheckpointFile(candidatePath);
|
|
1398
|
+
if (prepared?.version !== 2 || prepared.checkpointId !== nextCheckpoint.checkpointId) {
|
|
1399
|
+
if ((0, fs_1.existsSync)(candidatePath))
|
|
1400
|
+
(0, fs_1.unlinkSync)(candidatePath);
|
|
1401
|
+
throw new Error('Compact candidate failed durable reload validation');
|
|
1402
|
+
}
|
|
1403
|
+
let previousInstalled = false;
|
|
1404
|
+
let candidateInstalled = false;
|
|
786
1405
|
try {
|
|
787
|
-
|
|
1406
|
+
if ((0, fs_1.existsSync)(compactPath)) {
|
|
1407
|
+
(0, fs_1.renameSync)(compactPath, previousPath);
|
|
1408
|
+
previousInstalled = true;
|
|
1409
|
+
}
|
|
1410
|
+
(0, fs_1.renameSync)(candidatePath, compactPath);
|
|
1411
|
+
candidateInstalled = true;
|
|
1412
|
+
session.activeCompactCheckpointId = nextCheckpoint.checkpointId;
|
|
1413
|
+
session.lastCompactAt = createdAt;
|
|
1414
|
+
session.updatedAt = createdAt;
|
|
1415
|
+
session.updatedAtIso = new Date(createdAt).toISOString();
|
|
1416
|
+
writeSessionMetaUnlocked(session);
|
|
1417
|
+
const persistedSession = readSessionMetaAtPath((0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, session.id));
|
|
1418
|
+
const persistedCheckpoint = parseCompactCheckpointFile(compactPath);
|
|
1419
|
+
if (persistedSession?.activeCompactCheckpointId !== nextCheckpoint.checkpointId ||
|
|
1420
|
+
persistedCheckpoint?.checkpointId !== nextCheckpoint.checkpointId) {
|
|
1421
|
+
throw new Error('Compact checkpoint commit could not be read back atomically');
|
|
1422
|
+
}
|
|
788
1423
|
}
|
|
789
1424
|
catch (error) {
|
|
790
1425
|
session.activeCompactCheckpointId = previousId;
|
|
791
1426
|
session.lastCompactAt = previousTime;
|
|
792
|
-
|
|
1427
|
+
session.updatedAt = previousUpdatedAt;
|
|
1428
|
+
session.updatedAtIso = previousUpdatedAtIso;
|
|
1429
|
+
if (candidateInstalled && (0, fs_1.existsSync)(compactPath))
|
|
1430
|
+
(0, fs_1.unlinkSync)(compactPath);
|
|
1431
|
+
if (previousInstalled && (0, fs_1.existsSync)(previousPath))
|
|
1432
|
+
(0, fs_1.renameSync)(previousPath, compactPath);
|
|
1433
|
+
if ((0, fs_1.existsSync)(candidatePath))
|
|
1434
|
+
(0, fs_1.unlinkSync)(candidatePath);
|
|
1435
|
+
const persistedSession = readSessionMetaAtPath((0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, session.id));
|
|
1436
|
+
if (persistedSession?.activeCompactCheckpointId === nextCheckpoint.checkpointId) {
|
|
1437
|
+
writeSessionMetaUnlocked(session);
|
|
1438
|
+
}
|
|
793
1439
|
throw error;
|
|
794
1440
|
}
|
|
1441
|
+
if ((0, fs_1.existsSync)(previousPath)) {
|
|
1442
|
+
try {
|
|
1443
|
+
(0, fs_1.unlinkSync)(previousPath);
|
|
1444
|
+
}
|
|
1445
|
+
catch (error) {
|
|
1446
|
+
(0, debug_log_1.debugError)('session-storage.compactPreviousCleanup', error, previousPath);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
795
1449
|
return nextCheckpoint;
|
|
796
1450
|
});
|
|
797
1451
|
if (!checkpoint)
|
|
@@ -799,16 +1453,16 @@ function commitSessionCompactCheckpoint(input) {
|
|
|
799
1453
|
return checkpoint;
|
|
800
1454
|
}
|
|
801
1455
|
function loadSessionTranscriptMessages(sessionId) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
1456
|
+
return (withLockedSession(sessionId, session => {
|
|
1457
|
+
const messages = readSessionMessagesForSession(session);
|
|
1458
|
+
const checkpoint = loadCompactCheckpointForSession(session, messages);
|
|
1459
|
+
if (checkpoint) {
|
|
1460
|
+
return messages.slice(checkpoint.transcriptStartMessageIndex);
|
|
1461
|
+
}
|
|
1462
|
+
return typeof session.transcriptDisplayStartTime === 'number'
|
|
1463
|
+
? messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0))
|
|
1464
|
+
: messages;
|
|
1465
|
+
}) ?? []);
|
|
812
1466
|
}
|
|
813
1467
|
function hasPersistedCompactContext(messages) {
|
|
814
1468
|
return messages.some(message => message.content.includes('[Orion Code Context State v2]') ||
|
|
@@ -1192,24 +1846,37 @@ function readSessionTraceEvents(sessionId) {
|
|
|
1192
1846
|
* 包含完整的 tool_calls 信息,确保 LLM 能理解之前的工具调用
|
|
1193
1847
|
*/
|
|
1194
1848
|
function loadSessionHistory(sessionId) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1849
|
+
return (withLockedSession(sessionId, session => {
|
|
1850
|
+
const messages = readSessionMessagesForSession(session);
|
|
1851
|
+
const checkpoint = loadCompactCheckpointForSession(session, messages);
|
|
1852
|
+
if (checkpoint) {
|
|
1853
|
+
const restored = [
|
|
1854
|
+
...checkpoint.modelHistory.map(message => ({ ...message })),
|
|
1855
|
+
...messages.slice(checkpoint.sourceMessageCount).map(sessionMessageToModelMessage),
|
|
1856
|
+
];
|
|
1857
|
+
if (checkpoint.version === 2) {
|
|
1858
|
+
try {
|
|
1859
|
+
(0, tool_call_groups_1.assertToolCallGroups)(restored);
|
|
1860
|
+
return restored;
|
|
1861
|
+
}
|
|
1862
|
+
catch (error) {
|
|
1863
|
+
// Never mutate or synthesize data inside a validated V2
|
|
1864
|
+
// replacement. Fall back to the append-only transcript instead.
|
|
1865
|
+
(0, debug_log_1.debugError)('session-storage.restoreCompactCheckpoint.toolGroups', error, sessionId);
|
|
1866
|
+
return (0, tool_call_groups_1.sealToolCallGroups)(messages.map(sessionMessageToModelMessage));
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return (0, tool_call_groups_1.sealToolCallGroups)(restored);
|
|
1870
|
+
}
|
|
1871
|
+
let modelVisibleMessages = messages;
|
|
1872
|
+
if (typeof session.transcriptDisplayStartTime === 'number') {
|
|
1873
|
+
const afterDisplayStart = messages.filter(message => (message.timestamp ?? 0) >= (session.transcriptDisplayStartTime ?? 0));
|
|
1874
|
+
modelVisibleMessages = hasPersistedCompactContext(afterDisplayStart)
|
|
1875
|
+
? afterDisplayStart
|
|
1876
|
+
: messages;
|
|
1877
|
+
}
|
|
1878
|
+
return (0, tool_call_groups_1.sealToolCallGroups)(modelVisibleMessages.map(sessionMessageToModelMessage));
|
|
1879
|
+
}) ?? []);
|
|
1213
1880
|
}
|
|
1214
1881
|
function sessionMessageToModelMessage(message) {
|
|
1215
1882
|
const result = {
|
|
@@ -1292,11 +1959,14 @@ function renameSession(sessionId, name) {
|
|
|
1292
1959
|
function deleteSession(sessionId) {
|
|
1293
1960
|
return (withLockedSession(sessionId, session => {
|
|
1294
1961
|
let deleted = false;
|
|
1962
|
+
const compactPath = (0, config_dir_1.getProjectSessionCompactPath)(session.projectPath, sessionId);
|
|
1295
1963
|
const paths = [
|
|
1296
1964
|
(0, config_dir_1.getProjectSessionMetaPath)(session.projectPath, sessionId),
|
|
1297
1965
|
(0, config_dir_1.getProjectSessionMessagesPath)(session.projectPath, sessionId),
|
|
1298
1966
|
(0, config_dir_1.getProjectSessionHarnessPath)(session.projectPath, sessionId),
|
|
1299
|
-
|
|
1967
|
+
compactPath,
|
|
1968
|
+
compactCandidatePath(compactPath),
|
|
1969
|
+
compactPreviousPath(compactPath),
|
|
1300
1970
|
(0, config_dir_1.getProjectSessionTracePath)(session.projectPath, sessionId),
|
|
1301
1971
|
];
|
|
1302
1972
|
// Preserve a durable deletion fence so a stale writer in another process
|