loopforge 3.9.2 → 4.0.7
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 +236 -112
- package/dist/audit.d.ts +73 -15
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +131 -61
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +152 -40
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +382 -206
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -12
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +0 -2
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +0 -4
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +105 -141
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +249 -372
- package/dist/committed-round.js.map +1 -1
- package/dist/constraint-lifecycle.d.ts +73 -0
- package/dist/constraint-lifecycle.d.ts.map +1 -0
- package/dist/constraint-lifecycle.js +174 -0
- package/dist/constraint-lifecycle.js.map +1 -0
- package/dist/context-boundary.d.ts +83 -0
- package/dist/context-boundary.d.ts.map +1 -0
- package/dist/context-boundary.js +101 -0
- package/dist/context-boundary.js.map +1 -0
- package/dist/contract-items.d.ts +12 -8
- package/dist/contract-items.d.ts.map +1 -1
- package/dist/contract-items.js +31 -26
- package/dist/contract-items.js.map +1 -1
- package/dist/contract-requirement.d.ts +47 -0
- package/dist/contract-requirement.d.ts.map +1 -0
- package/dist/contract-requirement.js +51 -0
- package/dist/contract-requirement.js.map +1 -0
- package/dist/criterion-status.d.ts +35 -0
- package/dist/criterion-status.d.ts.map +1 -0
- package/dist/criterion-status.js +89 -0
- package/dist/criterion-status.js.map +1 -0
- package/dist/enforcement-gate.d.ts +32 -67
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +203 -439
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +9 -103
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +28 -887
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-claims.d.ts +33 -31
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +58 -72
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-posture-view.d.ts +19 -0
- package/dist/evidence-posture-view.d.ts.map +1 -0
- package/dist/evidence-posture-view.js +116 -0
- package/dist/evidence-posture-view.js.map +1 -0
- package/dist/evidence-posture.d.ts +73 -0
- package/dist/evidence-posture.d.ts.map +1 -0
- package/dist/evidence-posture.js +281 -0
- package/dist/evidence-posture.js.map +1 -0
- package/dist/evidence-provider.d.ts +55 -37
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +182 -332
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +167 -27
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +373 -58
- package/dist/explain.js.map +1 -1
- package/dist/field-accounting.d.ts.map +1 -1
- package/dist/field-accounting.js +3 -22
- package/dist/field-accounting.js.map +1 -1
- package/dist/generate-schema.js +13 -8
- package/dist/generate-schema.js.map +1 -1
- package/dist/git-boundary.d.ts +128 -0
- package/dist/git-boundary.d.ts.map +1 -0
- package/dist/git-boundary.js +625 -0
- package/dist/git-boundary.js.map +1 -0
- package/dist/git-facts.d.ts +22 -0
- package/dist/git-facts.d.ts.map +1 -0
- package/dist/git-facts.js +29 -0
- package/dist/git-facts.js.map +1 -0
- package/dist/git-motion.d.ts +37 -0
- package/dist/git-motion.d.ts.map +1 -0
- package/dist/git-motion.js +59 -0
- package/dist/git-motion.js.map +1 -0
- package/dist/history.d.ts +44 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +16 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +80 -14
- package/dist/init.js.map +1 -1
- package/dist/loop-compiler.d.ts +51 -19
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +423 -507
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +6 -5
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +153 -35
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +285 -81
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +1629 -574
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +61 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +81 -39
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +963 -271
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +23 -31
- package/dist/mcp/submission-boundary.d.ts.map +1 -1
- package/dist/mcp/submission-boundary.js +242 -82
- package/dist/mcp/submission-boundary.js.map +1 -1
- package/dist/mcp/submission-handle.d.ts +66 -0
- package/dist/mcp/submission-handle.d.ts.map +1 -0
- package/dist/mcp/submission-handle.js +173 -0
- package/dist/mcp/submission-handle.js.map +1 -0
- package/dist/mcp/tools.d.ts +17 -7
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +763 -244
- package/dist/mcp/tools.js.map +1 -1
- package/dist/open-question.d.ts +7 -0
- package/dist/open-question.d.ts.map +1 -0
- package/dist/open-question.js +14 -0
- package/dist/open-question.js.map +1 -0
- package/dist/policy-metrics.d.ts +31 -21
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +117 -64
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +15 -40
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +13 -13
- package/dist/policy.js.map +1 -1
- package/dist/progress.d.ts +45 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +136 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-assembler.d.ts +28 -9
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +592 -603
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +39 -0
- package/dist/prompt-coverage.d.ts.map +1 -0
- package/dist/prompt-coverage.js +80 -0
- package/dist/prompt-coverage.js.map +1 -0
- package/dist/prompt-freshness.d.ts +25 -0
- package/dist/prompt-freshness.d.ts.map +1 -0
- package/dist/prompt-freshness.js +88 -0
- package/dist/prompt-freshness.js.map +1 -0
- package/dist/prompt-policy.d.ts +14 -4
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +29 -10
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +875 -419
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +73 -99
- package/dist/protocol.js.map +1 -1
- package/dist/recovery-guidance.d.ts +13 -0
- package/dist/recovery-guidance.d.ts.map +1 -0
- package/dist/recovery-guidance.js +15 -0
- package/dist/recovery-guidance.js.map +1 -0
- package/dist/recovery.d.ts +68 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +234 -0
- package/dist/recovery.js.map +1 -0
- package/dist/replay.d.ts +42 -8
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +311 -84
- package/dist/replay.js.map +1 -1
- package/dist/result-facts.d.ts +45 -0
- package/dist/result-facts.d.ts.map +1 -0
- package/dist/result-facts.js +86 -0
- package/dist/result-facts.js.map +1 -0
- package/dist/round-contract.d.ts +38 -8
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +71 -31
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +45 -91
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +254 -271
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +56 -16
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +225 -39
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +33 -52
- package/dist/round-facts.d.ts.map +1 -1
- package/dist/round-facts.js +54 -157
- package/dist/round-facts.js.map +1 -1
- package/dist/round-guidance.d.ts +56 -0
- package/dist/round-guidance.d.ts.map +1 -0
- package/dist/round-guidance.js +161 -0
- package/dist/round-guidance.js.map +1 -0
- package/dist/round-outcome.d.ts +21 -0
- package/dist/round-outcome.d.ts.map +1 -0
- package/dist/round-outcome.js +31 -0
- package/dist/round-outcome.js.map +1 -0
- package/dist/round-transaction.d.ts +168 -21
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +856 -39
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +40 -60
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +455 -384
- package/dist/self-eval.js.map +1 -1
- package/dist/storage.d.ts +16 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +54 -4
- package/dist/storage.js.map +1 -1
- package/dist/subgoal-state.d.ts +2 -3
- package/dist/subgoal-state.d.ts.map +1 -1
- package/dist/subgoal-state.js +2 -3
- package/dist/subgoal-state.js.map +1 -1
- package/dist/submission-guidance.d.ts +43 -21
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +368 -262
- package/dist/submission-guidance.js.map +1 -1
- package/dist/submission-receipt.d.ts +16 -8
- package/dist/submission-receipt.d.ts.map +1 -1
- package/dist/submission-receipt.js +146 -51
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +47 -7
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +66 -15
- package/dist/token-utils.js.map +1 -1
- package/dist/transaction-identity.d.ts +9 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +69 -0
- package/dist/transaction-identity.js.map +1 -0
- package/dist/verification-actions.d.ts +13 -0
- package/dist/verification-actions.d.ts.map +1 -0
- package/dist/verification-actions.js +44 -0
- package/dist/verification-actions.js.map +1 -0
- package/dist/verification-gate.d.ts +26 -46
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +218 -427
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +187 -0
- package/dist/working-memory.d.ts.map +1 -0
- package/dist/working-memory.js +767 -0
- package/dist/working-memory.js.map +1 -0
- package/dist/workspace-checkpoint.d.ts +88 -0
- package/dist/workspace-checkpoint.d.ts.map +1 -0
- package/dist/workspace-checkpoint.js +326 -0
- package/dist/workspace-checkpoint.js.map +1 -0
- package/dist/workspace-restore.d.ts +68 -0
- package/dist/workspace-restore.d.ts.map +1 -0
- package/dist/workspace-restore.js +604 -0
- package/dist/workspace-restore.js.map +1 -0
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +0 -17
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +5 -19
- package/package.json +2 -2
- package/skills/forge/SKILL.md +212 -0
- package/skills/forge/references/evidence-and-contracts.md +204 -0
- package/skills/forge/references/recovery-and-views.md +188 -0
- package/dist/cognitive-facts.d.ts +0 -30
- package/dist/cognitive-facts.d.ts.map +0 -1
- package/dist/cognitive-facts.js +0 -113
- package/dist/cognitive-facts.js.map +0 -1
- package/dist/loop-extras-parser.d.ts +0 -81
- package/dist/loop-extras-parser.d.ts.map +0 -1
- package/dist/loop-extras-parser.js +0 -213
- package/dist/loop-extras-parser.js.map +0 -1
- package/dist/loop-projection.d.ts +0 -6
- package/dist/loop-projection.d.ts.map +0 -1
- package/dist/loop-projection.js +0 -28
- package/dist/loop-projection.js.map +0 -1
- package/skills/perception/SKILL.md +0 -370
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
/** v4.0.2/v4.0.3: bounded Working Memory — the ONE cognitive extension of
|
|
2
|
+
* `CanonicalLoopState`.
|
|
3
|
+
*
|
|
4
|
+
* It is a PURE derivation over the current branch's committed rounds and
|
|
5
|
+
* committed rollback directives. It performs no I/O, is never persisted as
|
|
6
|
+
* its own store, and is never an independent factual source. Prompt and
|
|
7
|
+
* state-file renderers share the formatters below and differ only by profile,
|
|
8
|
+
* so the two projections cannot drift apart.
|
|
9
|
+
*
|
|
10
|
+
* v4.0.3 adds auditable truncation: `sourceCounts` is computed BEFORE any
|
|
11
|
+
* cap/dedupe, `renderedCounts`/`omittedCounts` describe what actually entered
|
|
12
|
+
* the body, and every omission produces a deterministic notice. Agent text is
|
|
13
|
+
* always blockquoted and labeled a report.
|
|
14
|
+
*
|
|
15
|
+
* Hard exclusions (guaranteed by the inputs this module accepts):
|
|
16
|
+
* rejected/terminated/invalid attempts, uncommitted held snapshots,
|
|
17
|
+
* abandoned rounds excluded by the branch walker, and the pending rollback
|
|
18
|
+
* directive (already covered by the current Recovery Brief).
|
|
19
|
+
*/
|
|
20
|
+
import { historyWindow } from "./history.js";
|
|
21
|
+
import { normalizeText } from "./token-utils.js";
|
|
22
|
+
import { deriveOpenQuestionId } from "./open-question.js";
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
// Deterministic windows and caps (code constants, never policy)
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
26
|
+
/** Committed rounds that feed reports / assumptions / questions / redirects. */
|
|
27
|
+
export const WORKING_MEMORY_WINDOW_ROUNDS = 5;
|
|
28
|
+
export const WORKING_MEMORY_REPORT_LIMIT = 5;
|
|
29
|
+
export const WORKING_MEMORY_ASSUMPTION_LIMIT = 8;
|
|
30
|
+
export const WORKING_MEMORY_QUESTION_LIMIT = 6;
|
|
31
|
+
export const WORKING_MEMORY_RECOVERY_LIMIT = 3;
|
|
32
|
+
export const WORKING_MEMORY_REDIRECT_LIMIT = 3;
|
|
33
|
+
/** Per-recovery display cap for the abandoned-path detail lists. */
|
|
34
|
+
export const WORKING_MEMORY_RECOVERY_ITEM_LIMIT = 3;
|
|
35
|
+
/** Failed-round ids rendered for one recovery; replay/explain keep the full
|
|
36
|
+
* committed list. */
|
|
37
|
+
export const WORKING_MEMORY_FAILED_ROUNDS_DISPLAY = 10;
|
|
38
|
+
/** Per-text projection caps. Committed originals are never truncated. */
|
|
39
|
+
export const WORKING_MEMORY_TEXT_LIMITS = {
|
|
40
|
+
latestReport: 1200,
|
|
41
|
+
olderReport: 400,
|
|
42
|
+
assumption: 500,
|
|
43
|
+
openQuestion: 300,
|
|
44
|
+
recoveryReason: 500,
|
|
45
|
+
recoveryApproach: 200,
|
|
46
|
+
recoveryAssumption: 500,
|
|
47
|
+
redirectReason: 500,
|
|
48
|
+
};
|
|
49
|
+
/** Prompt density by level. Levels change only the size of this bounded
|
|
50
|
+
* projection; they never change governance facts or coverage. */
|
|
51
|
+
export const PROMPT_WORKING_MEMORY_DENSITY = {
|
|
52
|
+
l0: {
|
|
53
|
+
reports: 1,
|
|
54
|
+
assumptions: 2,
|
|
55
|
+
openQuestions: 2,
|
|
56
|
+
recoveries: 1,
|
|
57
|
+
redirects: 1,
|
|
58
|
+
totalChars: 1600,
|
|
59
|
+
},
|
|
60
|
+
l1: {
|
|
61
|
+
reports: WORKING_MEMORY_REPORT_LIMIT,
|
|
62
|
+
assumptions: WORKING_MEMORY_ASSUMPTION_LIMIT,
|
|
63
|
+
openQuestions: WORKING_MEMORY_QUESTION_LIMIT,
|
|
64
|
+
recoveries: WORKING_MEMORY_RECOVERY_LIMIT,
|
|
65
|
+
redirects: WORKING_MEMORY_REDIRECT_LIMIT,
|
|
66
|
+
totalChars: Number.POSITIVE_INFINITY,
|
|
67
|
+
},
|
|
68
|
+
l2: {
|
|
69
|
+
reports: WORKING_MEMORY_REPORT_LIMIT,
|
|
70
|
+
assumptions: WORKING_MEMORY_ASSUMPTION_LIMIT,
|
|
71
|
+
openQuestions: WORKING_MEMORY_QUESTION_LIMIT,
|
|
72
|
+
recoveries: WORKING_MEMORY_RECOVERY_LIMIT,
|
|
73
|
+
redirects: WORKING_MEMORY_REDIRECT_LIMIT,
|
|
74
|
+
totalChars: Number.POSITIVE_INFINITY,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
/** The state file keeps the full fixed window; it has no prompt budget. */
|
|
78
|
+
export const STATE_WORKING_MEMORY_DENSITY = {
|
|
79
|
+
reports: WORKING_MEMORY_REPORT_LIMIT,
|
|
80
|
+
assumptions: WORKING_MEMORY_ASSUMPTION_LIMIT,
|
|
81
|
+
openQuestions: WORKING_MEMORY_QUESTION_LIMIT,
|
|
82
|
+
recoveries: WORKING_MEMORY_RECOVERY_LIMIT,
|
|
83
|
+
redirects: WORKING_MEMORY_REDIRECT_LIMIT,
|
|
84
|
+
totalChars: Number.POSITIVE_INFINITY,
|
|
85
|
+
};
|
|
86
|
+
/** v4.0.3: the ONE deterministic truncation notice for the prompt. It counts
|
|
87
|
+
* toward the body budget, so it can never push the section over its ceiling. */
|
|
88
|
+
export const WORKING_MEMORY_ABBREVIATED_NOTICE = "> Working Memory is abbreviated; omitted committed memory is " +
|
|
89
|
+
"available through replay/explain (and the state file when enabled).";
|
|
90
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
91
|
+
// Text normalization
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
/** Canonical text for a Working Memory item:
|
|
94
|
+
* - CRLF / CR → LF;
|
|
95
|
+
* - tab → two spaces;
|
|
96
|
+
* - C0 control characters removed;
|
|
97
|
+
* - 3+ consecutive blank lines collapsed;
|
|
98
|
+
* - surrounding whitespace trimmed.
|
|
99
|
+
*/
|
|
100
|
+
export function normalizeWorkingMemoryText(value) {
|
|
101
|
+
return value
|
|
102
|
+
.replace(/\r\n?/g, "\n")
|
|
103
|
+
.replace(/\t/g, " ")
|
|
104
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "")
|
|
105
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
106
|
+
.trim();
|
|
107
|
+
}
|
|
108
|
+
/** Truncate at a line boundary and leave an explicit marker. */
|
|
109
|
+
export function truncateWorkingMemoryText(text, maxChars) {
|
|
110
|
+
if (text.length <= maxChars)
|
|
111
|
+
return { text, truncated: false };
|
|
112
|
+
const marker = "\n… [truncated]";
|
|
113
|
+
const room = Math.max(0, maxChars - marker.length);
|
|
114
|
+
let clipped = text.slice(0, room);
|
|
115
|
+
const lastBreak = clipped.lastIndexOf("\n");
|
|
116
|
+
if (lastBreak > 0)
|
|
117
|
+
clipped = clipped.slice(0, lastBreak);
|
|
118
|
+
return { text: `${clipped.trimEnd()}${marker}`, truncated: true };
|
|
119
|
+
}
|
|
120
|
+
/** Every line of Agent text becomes a blockquote line so `##`, headings or
|
|
121
|
+
* list markers inside the report cannot masquerade as state-file sections. */
|
|
122
|
+
function quote(text, indent = "") {
|
|
123
|
+
if (!text)
|
|
124
|
+
return [];
|
|
125
|
+
return text.split("\n").map((line) => line === "" ? `${indent}>` : `${indent}> ${line}`);
|
|
126
|
+
}
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
128
|
+
// Derivation
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
+
function reportMemory(view) {
|
|
131
|
+
const raw = view.evaluation?.output_summary ?? "";
|
|
132
|
+
const noChangeReason = view.evaluation?.no_change_reason?.trim();
|
|
133
|
+
const blocker = view.evaluation?.blocker?.trim();
|
|
134
|
+
return {
|
|
135
|
+
roundId: view.roundId,
|
|
136
|
+
round: view.round,
|
|
137
|
+
attempt: view.attempt,
|
|
138
|
+
agentSuccessClaim: view.agentSuccessClaim,
|
|
139
|
+
agentOutcome: view.agentOutcome,
|
|
140
|
+
outputSummary: normalizeWorkingMemoryText(raw),
|
|
141
|
+
sourceChars: raw.length,
|
|
142
|
+
...(noChangeReason
|
|
143
|
+
? { noChangeReason: normalizeWorkingMemoryText(noChangeReason) }
|
|
144
|
+
: {}),
|
|
145
|
+
...(blocker ? { blocker: normalizeWorkingMemoryText(blocker) } : {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function dedupeAssumptions(entries) {
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
const out = [];
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (!entry.text)
|
|
153
|
+
continue;
|
|
154
|
+
const key = normalizeText(entry.text);
|
|
155
|
+
if (!key || seen.has(key))
|
|
156
|
+
continue;
|
|
157
|
+
seen.add(key);
|
|
158
|
+
out.push(entry);
|
|
159
|
+
if (out.length >= WORKING_MEMORY_ASSUMPTION_LIMIT)
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function dedupeQuestions(entries) {
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const out = [];
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (!entry.text)
|
|
169
|
+
continue;
|
|
170
|
+
const key = entry.id;
|
|
171
|
+
if (!key || seen.has(key))
|
|
172
|
+
continue;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
out.push(entry);
|
|
175
|
+
if (out.length >= WORKING_MEMORY_QUESTION_LIMIT)
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
function boundedRecoveryItems(values) {
|
|
181
|
+
return {
|
|
182
|
+
items: values
|
|
183
|
+
.map((value) => normalizeWorkingMemoryText(value))
|
|
184
|
+
.filter(Boolean)
|
|
185
|
+
.slice(0, WORKING_MEMORY_RECOVERY_ITEM_LIMIT),
|
|
186
|
+
sourceCount: values.length,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** Derive the bounded Working Memory from committed history only.
|
|
190
|
+
*
|
|
191
|
+
* `currentRound` selects the compile window: only rounds strictly below it
|
|
192
|
+
* (the SAME window the compiler uses) feed reports, assumptions and
|
|
193
|
+
* redirects. Open questions remain active until explicitly resolved. A
|
|
194
|
+
* recovery enters memory only after its redo round is in the
|
|
195
|
+
* past; the pending directive is also excluded.
|
|
196
|
+
*
|
|
197
|
+
* v4.0.3: `sourceCounts` is measured before any cap/dedupe so every omission
|
|
198
|
+
* can be stated. */
|
|
199
|
+
export function deriveWorkingMemory(history, currentRound = Number.POSITIVE_INFINITY) {
|
|
200
|
+
const window = historyWindow(history, currentRound);
|
|
201
|
+
// The window is ascending; the bounded derivation keeps the most recent
|
|
202
|
+
// rounds, then exposes them newest-first.
|
|
203
|
+
const recent = window.slice(-WORKING_MEMORY_WINDOW_ROUNDS);
|
|
204
|
+
const reportSource = [...recent]
|
|
205
|
+
.reverse()
|
|
206
|
+
.map(reportMemory)
|
|
207
|
+
.slice(0, WORKING_MEMORY_REPORT_LIMIT);
|
|
208
|
+
const reports = reportSource;
|
|
209
|
+
const assumptionSource = [];
|
|
210
|
+
for (const view of [...recent].reverse()) {
|
|
211
|
+
for (const value of view.evaluation?.wrong_assumptions ?? []) {
|
|
212
|
+
const text = normalizeWorkingMemoryText(value);
|
|
213
|
+
if (text) {
|
|
214
|
+
assumptionSource.push({ round: view.round, kind: "wrong_assumption", text });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const value of view.retractedConstraints ?? []) {
|
|
218
|
+
const text = normalizeWorkingMemoryText(value);
|
|
219
|
+
if (text) {
|
|
220
|
+
assumptionSource.push({ round: view.round, kind: "retracted_constraint", text });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const assumptions = dedupeAssumptions(assumptionSource);
|
|
225
|
+
const activeQuestions = new Map();
|
|
226
|
+
// An unresolved question remains active until its stable id is closed;
|
|
227
|
+
// unlike reports, it must not expire merely because its declaration left
|
|
228
|
+
// the recent-report window.
|
|
229
|
+
for (const view of window) {
|
|
230
|
+
for (const value of view.openQuestions ?? []) {
|
|
231
|
+
const text = normalizeWorkingMemoryText(value);
|
|
232
|
+
if (text) {
|
|
233
|
+
const id = deriveOpenQuestionId(text);
|
|
234
|
+
activeQuestions.set(id, { id, round: view.round, text });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const id of view.resolvedOpenQuestionIds ?? []) {
|
|
238
|
+
activeQuestions.delete(id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const questionSource = [...activeQuestions.values()]
|
|
242
|
+
.sort((left, right) => right.round - left.round || left.id.localeCompare(right.id));
|
|
243
|
+
const openQuestions = dedupeQuestions(questionSource);
|
|
244
|
+
const pendingId = history.pendingDirective?.transactionId ||
|
|
245
|
+
history.pendingDirective?.roundId ||
|
|
246
|
+
null;
|
|
247
|
+
const recoverySource = [...history.directives]
|
|
248
|
+
.filter((view) => view.recovery !== null && view.recovery !== undefined)
|
|
249
|
+
.filter((view) => view.recovery.target.redo_round < currentRound)
|
|
250
|
+
.filter((view) => (view.transactionId ?? view.roundId) !== pendingId);
|
|
251
|
+
const recoveries = recoverySource
|
|
252
|
+
.slice(-WORKING_MEMORY_RECOVERY_LIMIT)
|
|
253
|
+
.reverse()
|
|
254
|
+
.map((view) => {
|
|
255
|
+
const recovery = view.recovery;
|
|
256
|
+
const observedFiles = boundedRecoveryItems(recovery.observed_skipped_files ?? recovery.skipped_files ?? []);
|
|
257
|
+
const reportedFiles = boundedRecoveryItems(recovery.reported_skipped_files ?? []);
|
|
258
|
+
const approaches = boundedRecoveryItems(recovery.approaches);
|
|
259
|
+
const assumptions = boundedRecoveryItems(recovery.wrong_assumptions);
|
|
260
|
+
const discoveries = boundedRecoveryItems(recovery.skipped_discoveries);
|
|
261
|
+
return {
|
|
262
|
+
round: view.round,
|
|
263
|
+
redoRound: recovery.target.redo_round,
|
|
264
|
+
origin: recovery.origin,
|
|
265
|
+
reason: normalizeWorkingMemoryText(recovery.reason),
|
|
266
|
+
abandonedRounds: [...(recovery.abandoned_rounds ?? [])],
|
|
267
|
+
failedRounds: [...recovery.failed_rounds],
|
|
268
|
+
observedSkippedFiles: observedFiles.items,
|
|
269
|
+
reportedSkippedFiles: reportedFiles.items,
|
|
270
|
+
approaches: approaches.items,
|
|
271
|
+
wrongAssumptions: assumptions.items,
|
|
272
|
+
preservedDiscoveries: discoveries.items,
|
|
273
|
+
detailSourceCounts: {
|
|
274
|
+
observedSkippedFiles: observedFiles.sourceCount,
|
|
275
|
+
reportedSkippedFiles: reportedFiles.sourceCount,
|
|
276
|
+
approaches: approaches.sourceCount,
|
|
277
|
+
wrongAssumptions: assumptions.sourceCount,
|
|
278
|
+
preservedDiscoveries: discoveries.sourceCount,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
const redirectSource = [...recent]
|
|
283
|
+
.filter((view) => view.contractRedirect !== null && view.contractRedirect !== undefined);
|
|
284
|
+
const redirects = redirectSource
|
|
285
|
+
.slice(-WORKING_MEMORY_REDIRECT_LIMIT)
|
|
286
|
+
.reverse()
|
|
287
|
+
.map((view) => ({
|
|
288
|
+
round: view.round,
|
|
289
|
+
contractId: view.contractRedirect.contract_id,
|
|
290
|
+
reason: normalizeWorkingMemoryText(view.contractRedirect.reason ?? ""),
|
|
291
|
+
}));
|
|
292
|
+
return {
|
|
293
|
+
reports,
|
|
294
|
+
assumptions,
|
|
295
|
+
openQuestions,
|
|
296
|
+
recoveries,
|
|
297
|
+
redirects,
|
|
298
|
+
sourceCounts: {
|
|
299
|
+
windowRounds: recent.length,
|
|
300
|
+
reports: reportSource.length,
|
|
301
|
+
assumptions: assumptionSource.length,
|
|
302
|
+
openQuestions: activeQuestions.size,
|
|
303
|
+
recoveries: recoverySource.length,
|
|
304
|
+
redirects: redirectSource.length,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
export function hasWorkingMemoryContent(memory) {
|
|
309
|
+
if (!memory)
|
|
310
|
+
return false;
|
|
311
|
+
return memory.reports.length > 0 ||
|
|
312
|
+
memory.assumptions.length > 0 ||
|
|
313
|
+
memory.openQuestions.length > 0 ||
|
|
314
|
+
memory.recoveries.length > 0 ||
|
|
315
|
+
memory.redirects.length > 0;
|
|
316
|
+
}
|
|
317
|
+
function emptySourceCounts() {
|
|
318
|
+
return {
|
|
319
|
+
windowRounds: 0,
|
|
320
|
+
reports: 0,
|
|
321
|
+
assumptions: 0,
|
|
322
|
+
openQuestions: 0,
|
|
323
|
+
recoveries: 0,
|
|
324
|
+
redirects: 0,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function reportBlockText(report, maxChars) {
|
|
328
|
+
if (!report.outputSummary)
|
|
329
|
+
return { text: "", truncated: false };
|
|
330
|
+
return truncateWorkingMemoryText(report.outputSummary, maxChars);
|
|
331
|
+
}
|
|
332
|
+
function reportHeader(report, newest) {
|
|
333
|
+
const outcome = report.agentOutcome ? `, agent_outcome=${report.agentOutcome}` : "";
|
|
334
|
+
if (newest) {
|
|
335
|
+
// v4.0.4: the newest report in the window is the latest COMMITTED report.
|
|
336
|
+
// After a redo it is not necessarily R(N-1), so the label does not claim a
|
|
337
|
+
// previous-round identity.
|
|
338
|
+
return `**Latest committed report (Agent report) — R${report.round}** ` +
|
|
339
|
+
`(source=agent_report; ref=${report.roundId}; ` +
|
|
340
|
+
`agent_success_claim=${report.agentSuccessClaim}${outcome}, attempt ${report.attempt})`;
|
|
341
|
+
}
|
|
342
|
+
return report.outputSummary
|
|
343
|
+
? `- R${report.round} (agent_success_claim=${report.agentSuccessClaim}${outcome}; ` +
|
|
344
|
+
`source=agent_report; ref=${report.roundId}):`
|
|
345
|
+
: `- R${report.round} (agent_success_claim=${report.agentSuccessClaim}${outcome}; ` +
|
|
346
|
+
`source=agent_report; ref=${report.roundId}): ` +
|
|
347
|
+
"(no output_summary recorded)";
|
|
348
|
+
}
|
|
349
|
+
function renderReportBlock(report, newest) {
|
|
350
|
+
const header = reportHeader(report, newest);
|
|
351
|
+
const details = [];
|
|
352
|
+
if (report.noChangeReason) {
|
|
353
|
+
details.push(...quote(`no_change_reason (Agent report): ${report.noChangeReason}`));
|
|
354
|
+
}
|
|
355
|
+
if (report.blocker) {
|
|
356
|
+
details.push(...quote(`blocker (Agent report): ${report.blocker}`));
|
|
357
|
+
}
|
|
358
|
+
if (newest) {
|
|
359
|
+
if (!report.outputSummary)
|
|
360
|
+
return [header, ...details, "(no output_summary recorded)"];
|
|
361
|
+
return [header, ...quote(report.outputSummary), ...details];
|
|
362
|
+
}
|
|
363
|
+
if (!report.outputSummary)
|
|
364
|
+
return [header, ...details];
|
|
365
|
+
return [header, ...quote(report.outputSummary, " "), ...details];
|
|
366
|
+
}
|
|
367
|
+
function renderQuestionItem(item) {
|
|
368
|
+
return [`- [${item.id}] R${item.round}:`, ...quote(item.text, " ")];
|
|
369
|
+
}
|
|
370
|
+
function renderAssumptionItem(item) {
|
|
371
|
+
const label = item.kind === "wrong_assumption"
|
|
372
|
+
? "assumption"
|
|
373
|
+
: "retracted constraint";
|
|
374
|
+
return [`- R${item.round} ${label}:`, ...quote(item.text, " ")];
|
|
375
|
+
}
|
|
376
|
+
function renderDecisionItem(item) {
|
|
377
|
+
return item.lines;
|
|
378
|
+
}
|
|
379
|
+
function recoveryDetailOmissions(recovery) {
|
|
380
|
+
const labels = [
|
|
381
|
+
["observedSkippedFiles", "observed files"],
|
|
382
|
+
["reportedSkippedFiles", "reported files"],
|
|
383
|
+
["approaches", "approaches"],
|
|
384
|
+
["wrongAssumptions", "wrong assumptions"],
|
|
385
|
+
["preservedDiscoveries", "preserved discoveries"],
|
|
386
|
+
];
|
|
387
|
+
return labels.flatMap(([key, label]) => {
|
|
388
|
+
const source = recovery.detailSourceCounts[key];
|
|
389
|
+
const rendered = recovery[key].length;
|
|
390
|
+
return source > rendered
|
|
391
|
+
? [`${label} ${rendered}/${source} (omitted ${source - rendered})`]
|
|
392
|
+
: [];
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function renderRecoveryDecision(recovery) {
|
|
396
|
+
const rounds = recovery.origin === "agent"
|
|
397
|
+
? recovery.abandonedRounds
|
|
398
|
+
: recovery.failedRounds;
|
|
399
|
+
const shownRounds = rounds.slice(0, WORKING_MEMORY_FAILED_ROUNDS_DISPLAY);
|
|
400
|
+
const moreRounds = rounds.length - shownRounds.length;
|
|
401
|
+
const roundsLabel = recovery.origin === "agent" ? "abandoned" : "failed";
|
|
402
|
+
const roundsText = shownRounds.length > 0
|
|
403
|
+
? `; ${roundsLabel} R${shownRounds.join(", R")}` +
|
|
404
|
+
(moreRounds > 0 ? ` and ${moreRounds} more` : "")
|
|
405
|
+
: "";
|
|
406
|
+
const lines = [
|
|
407
|
+
`- R${recovery.round} recovery → redo R${recovery.redoRound} ` +
|
|
408
|
+
`(${recovery.origin}${roundsText})`,
|
|
409
|
+
];
|
|
410
|
+
const omissions = recoveryDetailOmissions(recovery);
|
|
411
|
+
if (omissions.length > 0) {
|
|
412
|
+
lines.push(` Recovery details abbreviated: ${omissions.join(", ")}`);
|
|
413
|
+
}
|
|
414
|
+
if (recovery.reason)
|
|
415
|
+
lines.push(...quote(`reason: ${recovery.reason}`, " "));
|
|
416
|
+
if (recovery.observedSkippedFiles.length > 0) {
|
|
417
|
+
lines.push(...quote(`observed changed files (machine): ${recovery.observedSkippedFiles.join(", ")}`, " "));
|
|
418
|
+
}
|
|
419
|
+
if (recovery.reportedSkippedFiles.length > 0) {
|
|
420
|
+
lines.push(...quote(`Agent-reported changed files: ${recovery.reportedSkippedFiles.join(", ")}`, " "));
|
|
421
|
+
}
|
|
422
|
+
for (const approach of recovery.approaches) {
|
|
423
|
+
lines.push(...quote(`abandoned approach (Agent report): ${approach}`, " "));
|
|
424
|
+
}
|
|
425
|
+
for (const assumption of recovery.wrongAssumptions) {
|
|
426
|
+
lines.push(...quote(`assumption (Agent report): ${assumption}`, " "));
|
|
427
|
+
}
|
|
428
|
+
for (const discovery of recovery.preservedDiscoveries) {
|
|
429
|
+
lines.push(...quote(`preserved discovery (Agent report): ${discovery}`, " "));
|
|
430
|
+
}
|
|
431
|
+
return { kind: "recovery", round: recovery.round, lines };
|
|
432
|
+
}
|
|
433
|
+
function renderRedirectDecision(redirect) {
|
|
434
|
+
const lines = [
|
|
435
|
+
`- R${redirect.round} contract redirect (${redirect.contractId})`,
|
|
436
|
+
];
|
|
437
|
+
if (redirect.reason)
|
|
438
|
+
lines.push(...quote(`reason (Agent report): ${redirect.reason}`, " "));
|
|
439
|
+
return { kind: "redirect", round: redirect.round, lines };
|
|
440
|
+
}
|
|
441
|
+
function buildText(previous, earlier, questions, decisions, assumptions) {
|
|
442
|
+
const blocks = [];
|
|
443
|
+
if (previous)
|
|
444
|
+
blocks.push(renderReportBlock(previous.report, true));
|
|
445
|
+
if (questions.length > 0) {
|
|
446
|
+
blocks.push([
|
|
447
|
+
"**Open Questions (Agent reports)**",
|
|
448
|
+
...questions.flatMap(renderQuestionItem),
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
451
|
+
if (decisions.length > 0) {
|
|
452
|
+
blocks.push([
|
|
453
|
+
"**Recent Decisions (committed events)**",
|
|
454
|
+
...decisions.flatMap(renderDecisionItem),
|
|
455
|
+
]);
|
|
456
|
+
}
|
|
457
|
+
if (assumptions.length > 0) {
|
|
458
|
+
blocks.push([
|
|
459
|
+
"**Falsified Assumptions and Retractions (Agent reports)**",
|
|
460
|
+
...assumptions.flatMap(renderAssumptionItem),
|
|
461
|
+
]);
|
|
462
|
+
}
|
|
463
|
+
if (earlier.length > 0) {
|
|
464
|
+
blocks.push([
|
|
465
|
+
"**Earlier Committed Reports (Agent reports)**",
|
|
466
|
+
...earlier.flatMap((block) => renderReportBlock(block.report, false)),
|
|
467
|
+
]);
|
|
468
|
+
}
|
|
469
|
+
return blocks.map((block) => block.join("\n")).join("\n\n");
|
|
470
|
+
}
|
|
471
|
+
function applyReportCap(report, cap) {
|
|
472
|
+
const clipped = reportBlockText(report, cap);
|
|
473
|
+
return {
|
|
474
|
+
report: {
|
|
475
|
+
...report,
|
|
476
|
+
outputSummary: clipped.text,
|
|
477
|
+
},
|
|
478
|
+
text: clipped.text,
|
|
479
|
+
truncated: clipped.truncated,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function zeroCounts() {
|
|
483
|
+
return {
|
|
484
|
+
reports: 0,
|
|
485
|
+
assumptions: 0,
|
|
486
|
+
openQuestions: 0,
|
|
487
|
+
recoveries: 0,
|
|
488
|
+
redirects: 0,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/** Format Working Memory with a profile. Deterministic: the same memory and
|
|
492
|
+
* profile always produce the same string and metadata.
|
|
493
|
+
*
|
|
494
|
+
* `surface` selects the truncation notice: the prompt uses the fixed notice
|
|
495
|
+
* (which counts toward `totalChars`), the state file appends a structured
|
|
496
|
+
* `> Abbreviated: ...` line (it has no character budget). */
|
|
497
|
+
function formatWorkingMemory(memory, profile, surface) {
|
|
498
|
+
if (!hasWorkingMemoryContent(memory))
|
|
499
|
+
return null;
|
|
500
|
+
let anyTruncated = false;
|
|
501
|
+
const source = memory.sourceCounts ?? emptySourceCounts();
|
|
502
|
+
// The derivation applies fixed source caps before this renderer sees the
|
|
503
|
+
// arrays. Preserve that omission in the prompt projection even when the
|
|
504
|
+
// current profile can render the complete bounded arrays.
|
|
505
|
+
anyTruncated =
|
|
506
|
+
source.reports > memory.reports.length ||
|
|
507
|
+
source.assumptions > memory.assumptions.length ||
|
|
508
|
+
source.openQuestions > memory.openQuestions.length ||
|
|
509
|
+
source.recoveries > memory.recoveries.length ||
|
|
510
|
+
source.redirects > memory.redirects.length;
|
|
511
|
+
const reportLimit = Math.max(0, profile.reports);
|
|
512
|
+
const latest = memory.reports[0] ?? null;
|
|
513
|
+
let previous = null;
|
|
514
|
+
if (latest && reportLimit > 0) {
|
|
515
|
+
previous = applyReportCap(latest, WORKING_MEMORY_TEXT_LIMITS.latestReport);
|
|
516
|
+
anyTruncated ||= previous.truncated;
|
|
517
|
+
}
|
|
518
|
+
const earlier = memory.reports
|
|
519
|
+
.slice(1, reportLimit)
|
|
520
|
+
.map((report) => applyReportCap(report, WORKING_MEMORY_TEXT_LIMITS.olderReport));
|
|
521
|
+
anyTruncated ||= earlier.some((block) => block.truncated);
|
|
522
|
+
// Density omission is a projection truncation, not a committed loss.
|
|
523
|
+
if (latest && memory.reports.length > reportLimit)
|
|
524
|
+
anyTruncated = true;
|
|
525
|
+
const questionLimit = Math.max(0, profile.openQuestions);
|
|
526
|
+
if (memory.openQuestions.length > questionLimit)
|
|
527
|
+
anyTruncated = true;
|
|
528
|
+
const questions = memory.openQuestions
|
|
529
|
+
.slice(0, questionLimit)
|
|
530
|
+
.map((entry) => {
|
|
531
|
+
const clipped = truncateWorkingMemoryText(entry.text, WORKING_MEMORY_TEXT_LIMITS.openQuestion);
|
|
532
|
+
anyTruncated ||= clipped.truncated;
|
|
533
|
+
return {
|
|
534
|
+
id: entry.id,
|
|
535
|
+
round: entry.round,
|
|
536
|
+
text: clipped.text,
|
|
537
|
+
truncated: clipped.truncated,
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
const assumptionLimit = Math.max(0, profile.assumptions);
|
|
541
|
+
if (memory.assumptions.length > assumptionLimit)
|
|
542
|
+
anyTruncated = true;
|
|
543
|
+
const assumptions = memory.assumptions
|
|
544
|
+
.slice(0, assumptionLimit)
|
|
545
|
+
.map((entry) => {
|
|
546
|
+
const clipped = truncateWorkingMemoryText(entry.text, WORKING_MEMORY_TEXT_LIMITS.assumption);
|
|
547
|
+
anyTruncated ||= clipped.truncated;
|
|
548
|
+
return {
|
|
549
|
+
round: entry.round,
|
|
550
|
+
kind: entry.kind,
|
|
551
|
+
text: clipped.text,
|
|
552
|
+
truncated: clipped.truncated,
|
|
553
|
+
};
|
|
554
|
+
});
|
|
555
|
+
const recoveryLimit = Math.max(0, profile.recoveries);
|
|
556
|
+
const redirectLimit = Math.max(0, profile.redirects);
|
|
557
|
+
if (memory.recoveries.length > recoveryLimit ||
|
|
558
|
+
memory.redirects.length > redirectLimit) {
|
|
559
|
+
anyTruncated = true;
|
|
560
|
+
}
|
|
561
|
+
const decisions = [];
|
|
562
|
+
for (const recovery of memory.recoveries.slice(0, recoveryLimit)) {
|
|
563
|
+
const reason = truncateWorkingMemoryText(recovery.reason, WORKING_MEMORY_TEXT_LIMITS.recoveryReason);
|
|
564
|
+
const approaches = recovery.approaches.map((value) => truncateWorkingMemoryText(value, WORKING_MEMORY_TEXT_LIMITS.recoveryApproach));
|
|
565
|
+
const wrongAssumptions = recovery.wrongAssumptions.map((value) => truncateWorkingMemoryText(value, WORKING_MEMORY_TEXT_LIMITS.recoveryAssumption));
|
|
566
|
+
const rounds = recovery.origin === "agent"
|
|
567
|
+
? recovery.abandonedRounds : recovery.failedRounds;
|
|
568
|
+
anyTruncated ||= recoveryDetailOmissions(recovery).length > 0 ||
|
|
569
|
+
rounds.length > WORKING_MEMORY_FAILED_ROUNDS_DISPLAY || reason.truncated ||
|
|
570
|
+
approaches.some((item) => item.truncated) ||
|
|
571
|
+
wrongAssumptions.some((item) => item.truncated);
|
|
572
|
+
decisions.push(renderRecoveryDecision({
|
|
573
|
+
...recovery,
|
|
574
|
+
reason: reason.text,
|
|
575
|
+
approaches: approaches.map((item) => item.text),
|
|
576
|
+
wrongAssumptions: wrongAssumptions.map((item) => item.text),
|
|
577
|
+
}));
|
|
578
|
+
}
|
|
579
|
+
for (const redirect of memory.redirects.slice(0, redirectLimit)) {
|
|
580
|
+
const reason = truncateWorkingMemoryText(redirect.reason, WORKING_MEMORY_TEXT_LIMITS.redirectReason);
|
|
581
|
+
anyTruncated ||= reason.truncated;
|
|
582
|
+
decisions.push(renderRedirectDecision({ ...redirect, reason: reason.text }));
|
|
583
|
+
}
|
|
584
|
+
let text = buildText(previous, earlier, questions, decisions, assumptions);
|
|
585
|
+
const totalChars = profile.totalChars;
|
|
586
|
+
const noticeSuffix = surface === "prompt"
|
|
587
|
+
? `\n\n${WORKING_MEMORY_ABBREVIATED_NOTICE}`
|
|
588
|
+
: "";
|
|
589
|
+
const size = () => text.length + (anyTruncated ? noticeSuffix.length : 0);
|
|
590
|
+
if (Number.isFinite(totalChars)) {
|
|
591
|
+
// Drop lower-priority details first. The previous report is the anchor and
|
|
592
|
+
// is truncated only after every other block is gone. The truncation notice
|
|
593
|
+
// counts toward the ceiling.
|
|
594
|
+
while (size() > totalChars) {
|
|
595
|
+
if (earlier.length > 0) {
|
|
596
|
+
earlier.pop();
|
|
597
|
+
}
|
|
598
|
+
else if (assumptions.length > 0) {
|
|
599
|
+
assumptions.pop();
|
|
600
|
+
}
|
|
601
|
+
else if (decisions.some((item) => item.kind === "redirect")) {
|
|
602
|
+
const index = decisions.map((item) => item.kind).lastIndexOf("redirect");
|
|
603
|
+
decisions.splice(index, 1);
|
|
604
|
+
}
|
|
605
|
+
else if (decisions.length > 0) {
|
|
606
|
+
decisions.pop();
|
|
607
|
+
}
|
|
608
|
+
else if (questions.length > 0) {
|
|
609
|
+
questions.pop();
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
anyTruncated = true;
|
|
615
|
+
text = buildText(previous, earlier, questions, decisions, assumptions);
|
|
616
|
+
}
|
|
617
|
+
if (size() > totalChars && previous) {
|
|
618
|
+
const withoutReport = buildText({ ...previous, report: { ...previous.report, outputSummary: "" }, text: "" }, earlier, questions, decisions, assumptions);
|
|
619
|
+
const overhead = (withoutReport.length > 0 ? withoutReport.length + 1 : 0) +
|
|
620
|
+
(anyTruncated ? noticeSuffix.length : 0);
|
|
621
|
+
const room = Math.max(0, totalChars - overhead);
|
|
622
|
+
const clipped = truncateWorkingMemoryText(previous.report.outputSummary, room);
|
|
623
|
+
const wasTruncated = previous.truncated;
|
|
624
|
+
previous = {
|
|
625
|
+
...previous,
|
|
626
|
+
report: { ...previous.report, outputSummary: clipped.text },
|
|
627
|
+
text: clipped.text,
|
|
628
|
+
truncated: wasTruncated || clipped.truncated,
|
|
629
|
+
};
|
|
630
|
+
anyTruncated ||= clipped.truncated;
|
|
631
|
+
text = buildText(previous, earlier, questions, decisions, assumptions);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const reportRounds = [
|
|
635
|
+
...(previous ? [previous.report.round] : []),
|
|
636
|
+
...earlier.map((block) => block.report.round),
|
|
637
|
+
];
|
|
638
|
+
const renderedCounts = {
|
|
639
|
+
reports: reportRounds.length,
|
|
640
|
+
assumptions: assumptions.length,
|
|
641
|
+
openQuestions: questions.length,
|
|
642
|
+
recoveries: decisions.filter((item) => item.kind === "recovery").length,
|
|
643
|
+
redirects: decisions.filter((item) => item.kind === "redirect").length,
|
|
644
|
+
};
|
|
645
|
+
if (surface === "prompt" && anyTruncated) {
|
|
646
|
+
text = text ? `${text}\n\n${WORKING_MEMORY_ABBREVIATED_NOTICE}` : WORKING_MEMORY_ABBREVIATED_NOTICE;
|
|
647
|
+
}
|
|
648
|
+
if (surface === "state" && anyTruncated) {
|
|
649
|
+
const parts = [];
|
|
650
|
+
const labels = [
|
|
651
|
+
["reports", "reports"],
|
|
652
|
+
["openQuestions", "questions"],
|
|
653
|
+
["assumptions", "assumptions"],
|
|
654
|
+
["recoveries", "recoveries"],
|
|
655
|
+
["redirects", "redirects"],
|
|
656
|
+
];
|
|
657
|
+
for (const [key, label] of labels) {
|
|
658
|
+
if (source[key] > renderedCounts[key] || !source[key]) {
|
|
659
|
+
if (source[key] > renderedCounts[key]) {
|
|
660
|
+
parts.push(`${label} ${renderedCounts[key]}/${source[key]}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
text = `${text}\n\n> Abbreviated${parts.length > 0 ? `: ${parts.join(", ")}` : " (item details truncated)"}`;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
text,
|
|
668
|
+
reportRounds,
|
|
669
|
+
renderedCounts,
|
|
670
|
+
questionIds: questions.map((item) => item.id),
|
|
671
|
+
previousReport: previous
|
|
672
|
+
? {
|
|
673
|
+
round: previous.report.round,
|
|
674
|
+
sourceChars: memory.reports[0]?.sourceChars ?? 0,
|
|
675
|
+
renderedChars: previous.report.outputSummary.length,
|
|
676
|
+
truncated: previous.truncated,
|
|
677
|
+
}
|
|
678
|
+
: null,
|
|
679
|
+
truncated: anyTruncated,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function projectionFrom(memory, formatted, rendered) {
|
|
683
|
+
const source = memory.sourceCounts ?? emptySourceCounts();
|
|
684
|
+
const counts = rendered ? formatted.renderedCounts : zeroCounts();
|
|
685
|
+
return {
|
|
686
|
+
rendered,
|
|
687
|
+
reportRounds: rendered ? formatted.reportRounds : [],
|
|
688
|
+
counts,
|
|
689
|
+
sourceCounts: { ...source },
|
|
690
|
+
omittedCounts: {
|
|
691
|
+
reports: Math.max(0, source.reports - counts.reports),
|
|
692
|
+
assumptions: Math.max(0, source.assumptions - counts.assumptions),
|
|
693
|
+
openQuestions: Math.max(0, source.openQuestions - counts.openQuestions),
|
|
694
|
+
recoveries: Math.max(0, source.recoveries - counts.recoveries),
|
|
695
|
+
redirects: Math.max(0, source.redirects - counts.redirects),
|
|
696
|
+
},
|
|
697
|
+
questionIds: rendered ? formatted.questionIds : [],
|
|
698
|
+
previousReport: rendered ? formatted.previousReport : null,
|
|
699
|
+
renderedChars: rendered ? formatted.text.length : 0,
|
|
700
|
+
truncated: rendered ? formatted.truncated : true,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/** Prompt projection for one level. Returns null when there is nothing to
|
|
704
|
+
* render, so the prompt renders no empty section. */
|
|
705
|
+
export function formatWorkingMemoryPrompt(memory, level) {
|
|
706
|
+
if (!memory)
|
|
707
|
+
return null;
|
|
708
|
+
const formatted = formatWorkingMemory(memory, PROMPT_WORKING_MEMORY_DENSITY[level], "prompt");
|
|
709
|
+
if (!formatted)
|
|
710
|
+
return null;
|
|
711
|
+
return {
|
|
712
|
+
body: formatted.text,
|
|
713
|
+
projection: projectionFrom(memory, formatted, true),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/** v4.0.3: the projection of a Working Memory section that the prompt budget
|
|
717
|
+
* dropped WHOLE (`allowPartial: false`). `sourceCounts` is preserved so the
|
|
718
|
+
* omission is auditable; every rendered count is zero. */
|
|
719
|
+
export function unrenderedWorkingMemoryProjection(memory, level) {
|
|
720
|
+
if (!memory || !hasWorkingMemoryContent(memory))
|
|
721
|
+
return null;
|
|
722
|
+
const formatted = formatWorkingMemory(memory, PROMPT_WORKING_MEMORY_DENSITY[level], "prompt");
|
|
723
|
+
if (!formatted)
|
|
724
|
+
return null;
|
|
725
|
+
return projectionFrom(memory, formatted, false);
|
|
726
|
+
}
|
|
727
|
+
/** State-file projection: the full fixed window, no prompt budget. */
|
|
728
|
+
export function formatWorkingMemoryState(memory) {
|
|
729
|
+
if (!memory) {
|
|
730
|
+
return {
|
|
731
|
+
lines: [],
|
|
732
|
+
truncated: false,
|
|
733
|
+
sourceCounts: emptySourceCounts(),
|
|
734
|
+
renderedCounts: zeroCounts(),
|
|
735
|
+
omittedCounts: zeroCounts(),
|
|
736
|
+
questionIds: [],
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const formatted = formatWorkingMemory(memory, STATE_WORKING_MEMORY_DENSITY, "state");
|
|
740
|
+
if (!formatted) {
|
|
741
|
+
return {
|
|
742
|
+
lines: [],
|
|
743
|
+
truncated: false,
|
|
744
|
+
sourceCounts: { ...(memory.sourceCounts ?? emptySourceCounts()) },
|
|
745
|
+
renderedCounts: zeroCounts(),
|
|
746
|
+
omittedCounts: zeroCounts(),
|
|
747
|
+
questionIds: [],
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const source = memory.sourceCounts ?? emptySourceCounts();
|
|
751
|
+
const counts = formatted.renderedCounts;
|
|
752
|
+
return {
|
|
753
|
+
lines: formatted.text.split("\n"),
|
|
754
|
+
truncated: formatted.truncated,
|
|
755
|
+
sourceCounts: { ...source },
|
|
756
|
+
renderedCounts: counts,
|
|
757
|
+
omittedCounts: {
|
|
758
|
+
reports: Math.max(0, source.reports - counts.reports),
|
|
759
|
+
assumptions: Math.max(0, source.assumptions - counts.assumptions),
|
|
760
|
+
openQuestions: Math.max(0, source.openQuestions - counts.openQuestions),
|
|
761
|
+
recoveries: Math.max(0, source.recoveries - counts.recoveries),
|
|
762
|
+
redirects: Math.max(0, source.redirects - counts.redirects),
|
|
763
|
+
},
|
|
764
|
+
questionIds: formatted.questionIds,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
//# sourceMappingURL=working-memory.js.map
|