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.
Files changed (285) hide show
  1. package/README.md +236 -112
  2. package/dist/audit.d.ts +73 -15
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js +131 -61
  5. package/dist/audit.js.map +1 -1
  6. package/dist/canonical-state.d.ts +152 -40
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +382 -206
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +41 -12
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-governance.d.ts +0 -2
  14. package/dist/cognitive-governance.d.ts.map +1 -1
  15. package/dist/cognitive-governance.js +0 -4
  16. package/dist/cognitive-governance.js.map +1 -1
  17. package/dist/committed-round.d.ts +105 -141
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +249 -372
  20. package/dist/committed-round.js.map +1 -1
  21. package/dist/constraint-lifecycle.d.ts +73 -0
  22. package/dist/constraint-lifecycle.d.ts.map +1 -0
  23. package/dist/constraint-lifecycle.js +174 -0
  24. package/dist/constraint-lifecycle.js.map +1 -0
  25. package/dist/context-boundary.d.ts +83 -0
  26. package/dist/context-boundary.d.ts.map +1 -0
  27. package/dist/context-boundary.js +101 -0
  28. package/dist/context-boundary.js.map +1 -0
  29. package/dist/contract-items.d.ts +12 -8
  30. package/dist/contract-items.d.ts.map +1 -1
  31. package/dist/contract-items.js +31 -26
  32. package/dist/contract-items.js.map +1 -1
  33. package/dist/contract-requirement.d.ts +47 -0
  34. package/dist/contract-requirement.d.ts.map +1 -0
  35. package/dist/contract-requirement.js +51 -0
  36. package/dist/contract-requirement.js.map +1 -0
  37. package/dist/criterion-status.d.ts +35 -0
  38. package/dist/criterion-status.d.ts.map +1 -0
  39. package/dist/criterion-status.js +89 -0
  40. package/dist/criterion-status.js.map +1 -0
  41. package/dist/enforcement-gate.d.ts +32 -67
  42. package/dist/enforcement-gate.d.ts.map +1 -1
  43. package/dist/enforcement-gate.js +203 -439
  44. package/dist/enforcement-gate.js.map +1 -1
  45. package/dist/engine.d.ts +9 -103
  46. package/dist/engine.d.ts.map +1 -1
  47. package/dist/engine.js +28 -887
  48. package/dist/engine.js.map +1 -1
  49. package/dist/errors.d.ts +41 -0
  50. package/dist/errors.d.ts.map +1 -0
  51. package/dist/errors.js +69 -0
  52. package/dist/errors.js.map +1 -0
  53. package/dist/evidence-claims.d.ts +33 -31
  54. package/dist/evidence-claims.d.ts.map +1 -1
  55. package/dist/evidence-claims.js +58 -72
  56. package/dist/evidence-claims.js.map +1 -1
  57. package/dist/evidence-posture-view.d.ts +19 -0
  58. package/dist/evidence-posture-view.d.ts.map +1 -0
  59. package/dist/evidence-posture-view.js +116 -0
  60. package/dist/evidence-posture-view.js.map +1 -0
  61. package/dist/evidence-posture.d.ts +73 -0
  62. package/dist/evidence-posture.d.ts.map +1 -0
  63. package/dist/evidence-posture.js +281 -0
  64. package/dist/evidence-posture.js.map +1 -0
  65. package/dist/evidence-provider.d.ts +55 -37
  66. package/dist/evidence-provider.d.ts.map +1 -1
  67. package/dist/evidence-provider.js +182 -332
  68. package/dist/evidence-provider.js.map +1 -1
  69. package/dist/explain.d.ts +167 -27
  70. package/dist/explain.d.ts.map +1 -1
  71. package/dist/explain.js +373 -58
  72. package/dist/explain.js.map +1 -1
  73. package/dist/field-accounting.d.ts.map +1 -1
  74. package/dist/field-accounting.js +3 -22
  75. package/dist/field-accounting.js.map +1 -1
  76. package/dist/generate-schema.js +13 -8
  77. package/dist/generate-schema.js.map +1 -1
  78. package/dist/git-boundary.d.ts +128 -0
  79. package/dist/git-boundary.d.ts.map +1 -0
  80. package/dist/git-boundary.js +625 -0
  81. package/dist/git-boundary.js.map +1 -0
  82. package/dist/git-facts.d.ts +22 -0
  83. package/dist/git-facts.d.ts.map +1 -0
  84. package/dist/git-facts.js +29 -0
  85. package/dist/git-facts.js.map +1 -0
  86. package/dist/git-motion.d.ts +37 -0
  87. package/dist/git-motion.d.ts.map +1 -0
  88. package/dist/git-motion.js +59 -0
  89. package/dist/git-motion.js.map +1 -0
  90. package/dist/history.d.ts +44 -0
  91. package/dist/history.d.ts.map +1 -0
  92. package/dist/history.js +77 -0
  93. package/dist/history.js.map +1 -0
  94. package/dist/index.d.ts +19 -9
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +12 -4
  97. package/dist/index.js.map +1 -1
  98. package/dist/init.d.ts +16 -0
  99. package/dist/init.d.ts.map +1 -1
  100. package/dist/init.js +80 -14
  101. package/dist/init.js.map +1 -1
  102. package/dist/loop-compiler.d.ts +51 -19
  103. package/dist/loop-compiler.d.ts.map +1 -1
  104. package/dist/loop-compiler.js +423 -507
  105. package/dist/loop-compiler.js.map +1 -1
  106. package/dist/loop-store.d.ts +6 -5
  107. package/dist/loop-store.d.ts.map +1 -1
  108. package/dist/loop-store.js +153 -35
  109. package/dist/loop-store.js.map +1 -1
  110. package/dist/mcp/round-lifecycle.d.ts +285 -81
  111. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  112. package/dist/mcp/round-lifecycle.js +1629 -574
  113. package/dist/mcp/round-lifecycle.js.map +1 -1
  114. package/dist/mcp/server.d.ts +5 -0
  115. package/dist/mcp/server.d.ts.map +1 -1
  116. package/dist/mcp/server.js +61 -19
  117. package/dist/mcp/server.js.map +1 -1
  118. package/dist/mcp/session.d.ts +81 -39
  119. package/dist/mcp/session.d.ts.map +1 -1
  120. package/dist/mcp/session.js +963 -271
  121. package/dist/mcp/session.js.map +1 -1
  122. package/dist/mcp/submission-boundary.d.ts +23 -31
  123. package/dist/mcp/submission-boundary.d.ts.map +1 -1
  124. package/dist/mcp/submission-boundary.js +242 -82
  125. package/dist/mcp/submission-boundary.js.map +1 -1
  126. package/dist/mcp/submission-handle.d.ts +66 -0
  127. package/dist/mcp/submission-handle.d.ts.map +1 -0
  128. package/dist/mcp/submission-handle.js +173 -0
  129. package/dist/mcp/submission-handle.js.map +1 -0
  130. package/dist/mcp/tools.d.ts +17 -7
  131. package/dist/mcp/tools.d.ts.map +1 -1
  132. package/dist/mcp/tools.js +763 -244
  133. package/dist/mcp/tools.js.map +1 -1
  134. package/dist/open-question.d.ts +7 -0
  135. package/dist/open-question.d.ts.map +1 -0
  136. package/dist/open-question.js +14 -0
  137. package/dist/open-question.js.map +1 -0
  138. package/dist/policy-metrics.d.ts +31 -21
  139. package/dist/policy-metrics.d.ts.map +1 -1
  140. package/dist/policy-metrics.js +117 -64
  141. package/dist/policy-metrics.js.map +1 -1
  142. package/dist/policy.d.ts +15 -40
  143. package/dist/policy.d.ts.map +1 -1
  144. package/dist/policy.js +13 -13
  145. package/dist/policy.js.map +1 -1
  146. package/dist/progress.d.ts +45 -0
  147. package/dist/progress.d.ts.map +1 -0
  148. package/dist/progress.js +136 -0
  149. package/dist/progress.js.map +1 -0
  150. package/dist/prompt-assembler.d.ts +28 -9
  151. package/dist/prompt-assembler.d.ts.map +1 -1
  152. package/dist/prompt-assembler.js +592 -603
  153. package/dist/prompt-assembler.js.map +1 -1
  154. package/dist/prompt-coverage.d.ts +39 -0
  155. package/dist/prompt-coverage.d.ts.map +1 -0
  156. package/dist/prompt-coverage.js +80 -0
  157. package/dist/prompt-coverage.js.map +1 -0
  158. package/dist/prompt-freshness.d.ts +25 -0
  159. package/dist/prompt-freshness.d.ts.map +1 -0
  160. package/dist/prompt-freshness.js +88 -0
  161. package/dist/prompt-freshness.js.map +1 -0
  162. package/dist/prompt-policy.d.ts +14 -4
  163. package/dist/prompt-policy.d.ts.map +1 -1
  164. package/dist/prompt-policy.js +29 -10
  165. package/dist/prompt-policy.js.map +1 -1
  166. package/dist/protocol.d.ts +875 -419
  167. package/dist/protocol.d.ts.map +1 -1
  168. package/dist/protocol.js +73 -99
  169. package/dist/protocol.js.map +1 -1
  170. package/dist/recovery-guidance.d.ts +13 -0
  171. package/dist/recovery-guidance.d.ts.map +1 -0
  172. package/dist/recovery-guidance.js +15 -0
  173. package/dist/recovery-guidance.js.map +1 -0
  174. package/dist/recovery.d.ts +68 -0
  175. package/dist/recovery.d.ts.map +1 -0
  176. package/dist/recovery.js +234 -0
  177. package/dist/recovery.js.map +1 -0
  178. package/dist/replay.d.ts +42 -8
  179. package/dist/replay.d.ts.map +1 -1
  180. package/dist/replay.js +311 -84
  181. package/dist/replay.js.map +1 -1
  182. package/dist/result-facts.d.ts +45 -0
  183. package/dist/result-facts.d.ts.map +1 -0
  184. package/dist/result-facts.js +86 -0
  185. package/dist/result-facts.js.map +1 -0
  186. package/dist/round-contract.d.ts +38 -8
  187. package/dist/round-contract.d.ts.map +1 -1
  188. package/dist/round-contract.js +71 -31
  189. package/dist/round-contract.js.map +1 -1
  190. package/dist/round-coordinator.d.ts +45 -91
  191. package/dist/round-coordinator.d.ts.map +1 -1
  192. package/dist/round-coordinator.js +254 -271
  193. package/dist/round-coordinator.js.map +1 -1
  194. package/dist/round-driver.d.ts +56 -16
  195. package/dist/round-driver.d.ts.map +1 -1
  196. package/dist/round-driver.js +225 -39
  197. package/dist/round-driver.js.map +1 -1
  198. package/dist/round-facts.d.ts +33 -52
  199. package/dist/round-facts.d.ts.map +1 -1
  200. package/dist/round-facts.js +54 -157
  201. package/dist/round-facts.js.map +1 -1
  202. package/dist/round-guidance.d.ts +56 -0
  203. package/dist/round-guidance.d.ts.map +1 -0
  204. package/dist/round-guidance.js +161 -0
  205. package/dist/round-guidance.js.map +1 -0
  206. package/dist/round-outcome.d.ts +21 -0
  207. package/dist/round-outcome.d.ts.map +1 -0
  208. package/dist/round-outcome.js +31 -0
  209. package/dist/round-outcome.js.map +1 -0
  210. package/dist/round-transaction.d.ts +168 -21
  211. package/dist/round-transaction.d.ts.map +1 -1
  212. package/dist/round-transaction.js +856 -39
  213. package/dist/round-transaction.js.map +1 -1
  214. package/dist/self-eval.d.ts +40 -60
  215. package/dist/self-eval.d.ts.map +1 -1
  216. package/dist/self-eval.js +455 -384
  217. package/dist/self-eval.js.map +1 -1
  218. package/dist/storage.d.ts +16 -0
  219. package/dist/storage.d.ts.map +1 -1
  220. package/dist/storage.js +54 -4
  221. package/dist/storage.js.map +1 -1
  222. package/dist/subgoal-state.d.ts +2 -3
  223. package/dist/subgoal-state.d.ts.map +1 -1
  224. package/dist/subgoal-state.js +2 -3
  225. package/dist/subgoal-state.js.map +1 -1
  226. package/dist/submission-guidance.d.ts +43 -21
  227. package/dist/submission-guidance.d.ts.map +1 -1
  228. package/dist/submission-guidance.js +368 -262
  229. package/dist/submission-guidance.js.map +1 -1
  230. package/dist/submission-receipt.d.ts +16 -8
  231. package/dist/submission-receipt.d.ts.map +1 -1
  232. package/dist/submission-receipt.js +146 -51
  233. package/dist/submission-receipt.js.map +1 -1
  234. package/dist/token-utils.d.ts +47 -7
  235. package/dist/token-utils.d.ts.map +1 -1
  236. package/dist/token-utils.js +66 -15
  237. package/dist/token-utils.js.map +1 -1
  238. package/dist/transaction-identity.d.ts +9 -0
  239. package/dist/transaction-identity.d.ts.map +1 -0
  240. package/dist/transaction-identity.js +69 -0
  241. package/dist/transaction-identity.js.map +1 -0
  242. package/dist/verification-actions.d.ts +13 -0
  243. package/dist/verification-actions.d.ts.map +1 -0
  244. package/dist/verification-actions.js +44 -0
  245. package/dist/verification-actions.js.map +1 -0
  246. package/dist/verification-gate.d.ts +26 -46
  247. package/dist/verification-gate.d.ts.map +1 -1
  248. package/dist/verification-gate.js +218 -427
  249. package/dist/verification-gate.js.map +1 -1
  250. package/dist/version.d.ts +1 -1
  251. package/dist/version.js +1 -1
  252. package/dist/working-memory.d.ts +187 -0
  253. package/dist/working-memory.d.ts.map +1 -0
  254. package/dist/working-memory.js +767 -0
  255. package/dist/working-memory.js.map +1 -0
  256. package/dist/workspace-checkpoint.d.ts +88 -0
  257. package/dist/workspace-checkpoint.d.ts.map +1 -0
  258. package/dist/workspace-checkpoint.js +326 -0
  259. package/dist/workspace-checkpoint.js.map +1 -0
  260. package/dist/workspace-restore.d.ts +68 -0
  261. package/dist/workspace-restore.d.ts.map +1 -0
  262. package/dist/workspace-restore.js +604 -0
  263. package/dist/workspace-restore.js.map +1 -0
  264. package/dist/workspace.d.ts +0 -9
  265. package/dist/workspace.d.ts.map +1 -1
  266. package/dist/workspace.js +0 -17
  267. package/dist/workspace.js.map +1 -1
  268. package/loop_policy.json +5 -19
  269. package/package.json +2 -2
  270. package/skills/forge/SKILL.md +212 -0
  271. package/skills/forge/references/evidence-and-contracts.md +204 -0
  272. package/skills/forge/references/recovery-and-views.md +188 -0
  273. package/dist/cognitive-facts.d.ts +0 -30
  274. package/dist/cognitive-facts.d.ts.map +0 -1
  275. package/dist/cognitive-facts.js +0 -113
  276. package/dist/cognitive-facts.js.map +0 -1
  277. package/dist/loop-extras-parser.d.ts +0 -81
  278. package/dist/loop-extras-parser.d.ts.map +0 -1
  279. package/dist/loop-extras-parser.js +0 -213
  280. package/dist/loop-extras-parser.js.map +0 -1
  281. package/dist/loop-projection.d.ts +0 -6
  282. package/dist/loop-projection.d.ts.map +0 -1
  283. package/dist/loop-projection.js +0 -28
  284. package/dist/loop-projection.js.map +0 -1
  285. 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