@caupulican/pi-agent-core 0.81.7 → 0.81.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,14 @@ const REVERSAL_PATTERN = /\b(undo|revert|roll back|never mind|scrap that|forget
7
7
  const PROHIBITION_SOURCE_MAX_CHARS = 1_500;
8
8
  /** Upper bound on gate-demanded rules; most recent win (same bounding rationale as Done carry-over). */
9
9
  const MAX_PROHIBITIONS = 8;
10
+ /** Upper bound on gate-demanded actions; mirrors the prompt's "15 most recent Done items" rule. */
11
+ const MAX_ACTIONS = 15;
12
+ const MAX_WORKING_SET_FILES = 8;
13
+ const MAX_ERROR_FACTS = 5;
14
+ const ERROR_LINE_MAX_CHARS = 160;
15
+ const COMMAND_PREFIX_MAX_CHARS = 80;
16
+ /** Shared clamp for the active-task text because verification can only demand what the prompt receives. */
17
+ export const ACTIVE_TASK_SOURCE_MAX_CHARS = 4_000;
10
18
  const FILE_KIND_PRIORITY = {
11
19
  read: 1,
12
20
  created: 2,
@@ -65,21 +73,6 @@ function assistantToolCallTarget(name, rawArgs) {
65
73
  function clampText(text, maxLen) {
66
74
  return text.length > maxLen ? text.slice(0, maxLen) : text;
67
75
  }
68
- function firstLine(text) {
69
- for (const line of text.split("\n")) {
70
- const trimmed = line.trim();
71
- if (trimmed) {
72
- return trimmed;
73
- }
74
- }
75
- return "";
76
- }
77
- function firstOutcome(message) {
78
- if (message.role !== "toolResult") {
79
- return "";
80
- }
81
- return firstLine(messageToText(message));
82
- }
83
76
  function appearsCreated(message) {
84
77
  if (message.role !== "toolResult") {
85
78
  return false;
@@ -153,11 +146,53 @@ function isToolCallBlock(block) {
153
146
  function toolCallVerb(name) {
154
147
  return TOOL_VERB_BY_NAME[name] ?? name.toUpperCase();
155
148
  }
149
+ function isHarnessPlumbingTarget(target) {
150
+ if (!target)
151
+ return false;
152
+ return (target.includes("/.pi/agent/context-gc/") ||
153
+ target.includes("~/.pi/agent/context-gc/") ||
154
+ /\/tmp\/pi-bash-[^\s]+\.log\b/.test(target));
155
+ }
156
+ function normalizeOperationTarget(toolName, path) {
157
+ if (!path)
158
+ return "(unknown)";
159
+ if (toolName !== "bash")
160
+ return path;
161
+ return clampText(path.replace(/\s+/g, " ").trim(), COMMAND_PREFIX_MAX_CHARS);
162
+ }
163
+ function operationKey(toolName, path) {
164
+ return `${toolName}:${normalizeOperationTarget(toolName, path)}`;
165
+ }
166
+ function operationLabel(toolName, path) {
167
+ return `${toolCallVerb(toolName)} ${normalizeOperationTarget(toolName, path)}`;
168
+ }
169
+ function firstErrorLine(text) {
170
+ const line = text
171
+ .split(/\r?\n/)
172
+ .map((part) => part.trim())
173
+ .find(Boolean);
174
+ return clampText(line ?? "failed", ERROR_LINE_MAX_CHARS);
175
+ }
176
+ function isFailureToolResult(message, text) {
177
+ if (message.role !== "toolResult")
178
+ return false;
179
+ if (message.isError === true)
180
+ return true;
181
+ return /\b(exit code|failed|failure|error|exception|traceback)\b/i.test(text);
182
+ }
156
183
  export function extractCompactionFacts(entries, start, end) {
157
184
  const rangeStart = Math.max(0, start);
158
185
  const rangeEnd = Math.min(entries.length, Math.max(rangeStart, end));
159
186
  if (rangeStart >= rangeEnd) {
160
- return { files: [], actions: [], prohibitions: [], cancelledText: "", activeTaskSource: "" };
187
+ return {
188
+ files: [],
189
+ workingSet: [],
190
+ actions: [],
191
+ errorFacts: [],
192
+ prohibitions: [],
193
+ cancelledText: "",
194
+ activeTaskSource: "",
195
+ };
161
196
  }
162
197
  const filesByPath = new Map();
163
198
  const filesNotes = new Map();
@@ -166,6 +201,7 @@ export function extractCompactionFacts(entries, start, end) {
166
201
  const pendingById = new Map();
167
202
  const pendingByOrder = [];
168
203
  const actions = [];
204
+ const openErrors = new Map();
169
205
  const prohibitions = [];
170
206
  let activeTaskSource = "";
171
207
  const cancelledParts = [];
@@ -182,7 +218,7 @@ export function extractCompactionFacts(entries, start, end) {
182
218
  }
183
219
  sinceLastUser.length = 0;
184
220
  if (userText) {
185
- activeTaskSource = userText;
221
+ activeTaskSource = clampText(userText, ACTIVE_TASK_SOURCE_MAX_CHARS);
186
222
  // Mandatory Rules exist for SPOKEN durable prohibitions ("do not touch X"), not for
187
223
  // documents. A long pasted text (plan, instruction file) can contain dozens of
188
224
  // "never/do not" lines; harvesting them makes the verification gate demand the
@@ -210,6 +246,17 @@ export function extractCompactionFacts(entries, start, end) {
210
246
  sinceLastUser.push(messageText);
211
247
  }
212
248
  }
249
+ if (message.role === "assistant" && message.stopReason === "error") {
250
+ const errorMessage = message.errorMessage;
251
+ openErrors.set("assistant:error", {
252
+ operation: "ASSISTANT response",
253
+ error: firstErrorLine(typeof errorMessage === "string" ? errorMessage : messageToText(message)),
254
+ lastTouch: i,
255
+ });
256
+ }
257
+ else if (message.role === "assistant" && message.stopReason === "stop") {
258
+ openErrors.delete("assistant:error");
259
+ }
213
260
  if (message.role === "assistant" && Array.isArray(message.content)) {
214
261
  for (const block of message.content) {
215
262
  if (!isToolCallBlock(block)) {
@@ -226,7 +273,6 @@ export function extractCompactionFacts(entries, start, end) {
226
273
  baseKind,
227
274
  finalKind: baseKind,
228
275
  verb,
229
- outcome: "",
230
276
  resolved: false,
231
277
  };
232
278
  actionFacts.push(fact);
@@ -263,25 +309,37 @@ export function extractCompactionFacts(entries, start, end) {
263
309
  }
264
310
  if (matchedIndex !== undefined) {
265
311
  const fact = actionFacts[matchedIndex];
266
- const outcome = firstOutcome(message);
267
- fact.outcome = outcome;
268
312
  fact.resolved = true;
269
313
  if (fact.baseKind === "modified" && appearsCreated(message)) {
270
314
  fact.finalKind = "created";
271
315
  }
272
- const finalOutcome = clampText(fact.outcome, 80);
273
- fact.outcome = finalOutcome;
274
- if (fact.path && fact.finalKind) {
316
+ if (fact.path && fact.finalKind && !isHarnessPlumbingTarget(fact.path)) {
275
317
  const existing = filesByPath.get(fact.path);
276
318
  const nextKind = fact.finalKind;
277
319
  const note = fact.verb;
278
320
  if (!existing || FILE_KIND_PRIORITY[nextKind] > FILE_KIND_PRIORITY[existing.kind]) {
279
- filesByPath.set(fact.path, { path: fact.path, kind: nextKind, note });
321
+ filesByPath.set(fact.path, { path: fact.path, kind: nextKind, note, lastTouch: i });
280
322
  }
281
323
  else if (existing.kind === nextKind) {
282
- existing.note = `${note}: ${finalOutcome}`;
324
+ existing.note = note;
325
+ existing.lastTouch = i;
326
+ }
327
+ else {
328
+ existing.lastTouch = i;
283
329
  }
284
330
  }
331
+ const resultText = messageToText(message);
332
+ const key = operationKey(fact.name, fact.path);
333
+ if (isFailureToolResult(message, resultText)) {
334
+ openErrors.set(key, {
335
+ operation: operationLabel(fact.name, fact.path),
336
+ error: firstErrorLine(resultText),
337
+ lastTouch: i,
338
+ });
339
+ }
340
+ else {
341
+ openErrors.delete(key);
342
+ }
285
343
  const pendingPos = pendingByOrder.indexOf(matchedIndex);
286
344
  if (pendingPos >= 0) {
287
345
  pendingByOrder.splice(pendingPos, 1);
@@ -302,11 +360,18 @@ export function extractCompactionFacts(entries, start, end) {
302
360
  }
303
361
  }
304
362
  for (const action of actionFacts) {
363
+ if (isHarnessPlumbingTarget(action.path)) {
364
+ continue;
365
+ }
305
366
  if (action.finalKind && action.path && !filesByPath.has(action.path)) {
306
- const note = action.outcome ? `${action.verb}: ${action.outcome}` : action.verb;
307
- filesByPath.set(action.path, { path: action.path, kind: action.finalKind, note });
367
+ filesByPath.set(action.path, {
368
+ path: action.path,
369
+ kind: action.finalKind,
370
+ note: action.verb,
371
+ lastTouch: actionFacts.indexOf(action),
372
+ });
308
373
  }
309
- actions.push(`${action.verb} ${action.path ?? "(unknown)"} — ${action.outcome}`);
374
+ actions.push(`${action.verb} ${action.path ?? "(unknown)"}`);
310
375
  }
311
376
  for (const file of filesByPath.values()) {
312
377
  if (!file.note && filesNotes.has(file.path)) {
@@ -325,28 +390,54 @@ export function extractCompactionFacts(entries, start, end) {
325
390
  file.note = suffix;
326
391
  }
327
392
  }
393
+ const files = Array.from(filesByPath.values())
394
+ .sort((a, b) => b.lastTouch - a.lastTouch ||
395
+ FILE_KIND_PRIORITY[b.kind] - FILE_KIND_PRIORITY[a.kind] ||
396
+ a.path.localeCompare(b.path))
397
+ .map(({ lastTouch: _lastTouch, ...file }) => file);
328
398
  return {
329
- files: Array.from(filesByPath.values()).sort((a, b) => {
330
- if (a.path === b.path) {
331
- return FILE_KIND_PRIORITY[a.kind] - FILE_KIND_PRIORITY[b.kind];
332
- }
333
- return a.path.localeCompare(b.path);
334
- }),
335
- actions,
399
+ files,
400
+ workingSet: files.slice(0, MAX_WORKING_SET_FILES),
401
+ actions: dedupeMostRecent(actions).slice(-MAX_ACTIONS),
402
+ errorFacts: Array.from(openErrors.values())
403
+ .sort((a, b) => a.lastTouch - b.lastTouch)
404
+ .slice(-MAX_ERROR_FACTS)
405
+ .map(({ lastTouch: _lastTouch, ...error }) => error),
336
406
  prohibitions: prohibitions.slice(-MAX_PROHIBITIONS),
337
407
  cancelledText: cancelledParts.join("\n"),
338
408
  activeTaskSource,
339
409
  };
340
410
  }
411
+ function dedupeMostRecent(values) {
412
+ const seen = new Set();
413
+ const kept = [];
414
+ for (let i = values.length - 1; i >= 0; i--) {
415
+ const value = values[i];
416
+ if (seen.has(value)) {
417
+ continue;
418
+ }
419
+ seen.add(value);
420
+ kept.push(value);
421
+ }
422
+ return kept.reverse();
423
+ }
341
424
  export function renderFactsBlock(facts) {
342
425
  const lines = ["files:"];
343
426
  for (const file of facts.files) {
344
427
  lines.push(`${file.kind}: ${file.path} — ${file.note}`);
345
428
  }
429
+ lines.push("working set:");
430
+ for (const file of facts.workingSet) {
431
+ lines.push(`${file.path} — ${file.note || file.kind}`);
432
+ }
346
433
  lines.push("actions:");
347
434
  for (const action of facts.actions) {
348
435
  lines.push(action);
349
436
  }
437
+ lines.push("open errors:");
438
+ for (const error of facts.errorFacts) {
439
+ lines.push(`${error.operation}: ${error.error}`);
440
+ }
350
441
  lines.push("prohibitions:");
351
442
  for (const prohibition of facts.prohibitions) {
352
443
  lines.push(prohibition);
@@ -356,7 +447,7 @@ export function renderFactsBlock(facts) {
356
447
  // guaranteed to reach the prompt, so the gated text must ride in it (bounded).
357
448
  lines.push("active task:");
358
449
  if (facts.activeTaskSource) {
359
- lines.push(clampText(facts.activeTaskSource, 4000));
450
+ lines.push(clampText(facts.activeTaskSource, ACTIVE_TASK_SOURCE_MAX_CHARS));
360
451
  }
361
452
  return lines.join("\n");
362
453
  }
@@ -1 +1 @@
1
- {"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/compaction/extraction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { AgentMessage } from \"../types.ts\";\n\nexport interface CompactionFacts {\n\tfiles: Array<{ path: string; kind: \"modified\" | \"created\" | \"read\"; note: string }>;\n\tactions: string[];\n\tprohibitions: string[];\n\tcancelledText: string;\n\tactiveTaskSource: string;\n}\n\ninterface ToolCallFact {\n\tid: string | undefined;\n\tname: string;\n\tpath: string | undefined;\n\tbaseKind: \"modified\" | \"created\" | \"read\" | null;\n\tfinalKind: \"modified\" | \"created\" | \"read\" | null;\n\tverb: string;\n\toutcome: string;\n\tresolved: boolean;\n}\n\nconst PROHIBITION_PATTERN = /\\b(do not|don't|never|stop (?:doing|using|changing)|no more)\\b/i;\n// Deliberately narrow: a bare \"stop\" (\"stop the server and rerun\") is everyday phrasing, not a\n// reversal of the prior work — matching it marked whole turns cancelled and made the\n// cancelled-work gate fight the recall gates (2026-07-06 incident).\nconst REVERSAL_PATTERN =\n\t/\\b(undo|revert|roll back|never mind|scrap that|forget (?:it|that)|stop (?:that|this|it|everything|working on))\\b/i;\n\n/** User messages longer than this are documents/pastes, not spoken prohibitions. */\nconst PROHIBITION_SOURCE_MAX_CHARS = 1_500;\n/** Upper bound on gate-demanded rules; most recent win (same bounding rationale as Done carry-over). */\nconst MAX_PROHIBITIONS = 8;\n\nconst FILE_KIND_PRIORITY: Record<NonNullable<ToolCallFact[\"finalKind\"]>, number> = {\n\tread: 1,\n\tcreated: 2,\n\tmodified: 3,\n};\n\nconst TOOL_KIND_BY_NAME: Record<string, \"modified\" | \"created\" | \"read\" | null> = {\n\tread: \"read\",\n\tgrep: \"read\",\n\tfind: \"read\",\n\twrite: \"modified\",\n\tedit: \"modified\",\n\tbash: null,\n};\n\nconst TOOL_VERB_BY_NAME: Record<string, string> = {\n\twrite: \"WRITE\",\n\tedit: \"EDIT\",\n\tbash: \"RUN\",\n\tread: \"READ\",\n\tgrep: \"READ\",\n\tfind: \"READ\",\n};\n\nfunction messageToText(message: AgentMessage): string {\n\tconst rawContent = (message as { content?: unknown }).content;\n\tif (typeof rawContent === \"string\") {\n\t\treturn rawContent;\n\t}\n\tif (!Array.isArray(rawContent)) {\n\t\treturn \"\";\n\t}\n\n\tconst parts: string[] = [];\n\tfor (const block of rawContent) {\n\t\tif (!block || typeof block !== \"object\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((block as { type?: unknown }).type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t}\n\t}\n\treturn parts.join(\" \").trim();\n}\n\nfunction assistantToolCallTarget(name: string, rawArgs: unknown): string | undefined {\n\tif (!rawArgs || typeof rawArgs !== \"object\") {\n\t\treturn undefined;\n\t}\n\tconst args = rawArgs as Record<string, unknown>;\n\n\tif (\n\t\t(name === \"read\" || name === \"grep\" || name === \"find\" || name === \"write\" || name === \"edit\") &&\n\t\ttypeof args.path === \"string\" &&\n\t\targs.path.length > 0\n\t) {\n\t\treturn args.path;\n\t}\n\n\tif (name === \"bash\" && typeof args.command === \"string\" && args.command.length > 0) {\n\t\treturn args.command;\n\t}\n\n\treturn undefined;\n}\n\nfunction clampText(text: string, maxLen: number): string {\n\treturn text.length > maxLen ? text.slice(0, maxLen) : text;\n}\n\nfunction firstLine(text: string): string {\n\tfor (const line of text.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (trimmed) {\n\t\t\treturn trimmed;\n\t\t}\n\t}\n\treturn \"\";\n}\n\nfunction firstOutcome(message: AgentMessage): string {\n\tif (message.role !== \"toolResult\") {\n\t\treturn \"\";\n\t}\n\treturn firstLine(messageToText(message));\n}\n\nfunction appearsCreated(message: AgentMessage): boolean {\n\tif (message.role !== \"toolResult\") {\n\t\treturn false;\n\t}\n\n\tconst text = messageToText(message).toLowerCase();\n\tif (/(\\bnew file\\b|\\bcreated\\b|\\bcreated file\\b)/.test(text)) {\n\t\treturn true;\n\t}\n\n\tconst details = (message as { details?: unknown }).details;\n\tif (!details || typeof details !== \"object\") {\n\t\treturn false;\n\t}\n\n\tif (typeof (details as { created?: unknown }).created === \"boolean\") {\n\t\treturn (details as { created: boolean }).created;\n\t}\n\tif (typeof (details as { isCreated?: unknown }).isCreated === \"boolean\") {\n\t\treturn (details as { isCreated: boolean }).isCreated;\n\t}\n\tif (typeof (details as { isNewFile?: unknown }).isNewFile === \"boolean\") {\n\t\treturn (details as { isNewFile: boolean }).isNewFile;\n\t}\n\n\treturn false;\n}\n\nfunction splitSentenceLines(text: string): string[] {\n\treturn text\n\t\t.split(/(?<=[.!?])\\s+|\\n+/)\n\t\t.map((line) => line.trim().replace(/[.!?]+$/, \"\"))\n\t\t.filter(Boolean);\n}\n\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\treturn entry.message;\n\t\tcase \"custom_message\":\n\t\t\treturn {\n\t\t\t\trole: \"custom\",\n\t\t\t\tcustomType: entry.customType,\n\t\t\t\tcontent: entry.content,\n\t\t\t\tdetails: entry.details,\n\t\t\t\tdisplay: entry.display,\n\t\t\t\ttimestamp: new Date(entry.timestamp).getTime(),\n\t\t\t};\n\t\tcase \"branch_summary\":\n\t\t\treturn {\n\t\t\t\trole: \"branchSummary\",\n\t\t\t\tsummary: entry.summary,\n\t\t\t\tfromId: entry.fromId,\n\t\t\t\ttimestamp: new Date(entry.timestamp).getTime(),\n\t\t\t};\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction isToolCallBlock(block: unknown): block is { type: \"toolCall\"; id?: string; name: string; arguments: unknown } {\n\tif (!block || typeof block !== \"object\") {\n\t\treturn false;\n\t}\n\tif ((block as { type?: unknown }).type !== \"toolCall\") {\n\t\treturn false;\n\t}\n\tif (typeof (block as { name?: unknown }).name !== \"string\") {\n\t\treturn false;\n\t}\n\tif (!(block as { arguments?: unknown }).arguments) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction toolCallVerb(name: string): string {\n\treturn TOOL_VERB_BY_NAME[name] ?? name.toUpperCase();\n}\n\nexport function extractCompactionFacts(entries: SessionEntry[], start: number, end: number): CompactionFacts {\n\tconst rangeStart = Math.max(0, start);\n\tconst rangeEnd = Math.min(entries.length, Math.max(rangeStart, end));\n\n\tif (rangeStart >= rangeEnd) {\n\t\treturn { files: [], actions: [], prohibitions: [], cancelledText: \"\", activeTaskSource: \"\" };\n\t}\n\n\tconst filesByPath = new Map<string, { path: string; kind: \"modified\" | \"created\" | \"read\"; note: string }>();\n\tconst filesNotes = new Map<string, string>();\n\tconst seenProhibitions = new Set<string>();\n\tconst actionFacts: ToolCallFact[] = [];\n\tconst pendingById = new Map<string, number[]>();\n\tconst pendingByOrder: number[] = [];\n\n\tconst actions: string[] = [];\n\tconst prohibitions: string[] = [];\n\tlet activeTaskSource = \"\";\n\tconst cancelledParts: string[] = [];\n\tconst sinceLastUser: string[] = [];\n\n\tfor (let i = rangeStart; i < rangeEnd; i++) {\n\t\tconst message = getMessageFromEntry(entries[i]);\n\t\tif (!message) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message.role === \"user\") {\n\t\t\tconst userText = messageToText(message);\n\t\t\tif (REVERSAL_PATTERN.test(userText)) {\n\t\t\t\tcancelledParts.push(...sinceLastUser);\n\t\t\t}\n\t\t\tsinceLastUser.length = 0;\n\n\t\t\tif (userText) {\n\t\t\t\tactiveTaskSource = userText;\n\t\t\t\t// Mandatory Rules exist for SPOKEN durable prohibitions (\"do not touch X\"), not for\n\t\t\t\t// documents. A long pasted text (plan, instruction file) can contain dozens of\n\t\t\t\t// \"never/do not\" lines; harvesting them makes the verification gate demand the\n\t\t\t\t// checkpoint reproduce the document (2026-07-06 field incident: 13 fragment rules\n\t\t\t\t// extracted from one pasted instruction). Documents live on disk; skip them here.\n\t\t\t\tif (userText.length <= PROHIBITION_SOURCE_MAX_CHARS) {\n\t\t\t\t\tfor (const sentence of splitSentenceLines(userText)) {\n\t\t\t\t\t\tif (!PROHIBITION_PATTERN.test(sentence)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst normalized = clampText(sentence, 160);\n\t\t\t\t\t\tconst dedupeKey = normalized.toLowerCase();\n\t\t\t\t\t\tif (!seenProhibitions.has(dedupeKey)) {\n\t\t\t\t\t\t\tseenProhibitions.add(dedupeKey);\n\t\t\t\t\t\t\tprohibitions.push(normalized);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message.role === \"assistant\" || message.role === \"toolResult\") {\n\t\t\tconst messageText = messageToText(message);\n\t\t\tif (messageText) {\n\t\t\t\tsinceLastUser.push(messageText);\n\t\t\t}\n\t\t}\n\n\t\tif (message.role === \"assistant\" && Array.isArray(message.content)) {\n\t\t\tfor (const block of message.content) {\n\t\t\t\tif (!isToolCallBlock(block)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst name = block.name;\n\t\t\t\tconst verb = toolCallVerb(name);\n\t\t\t\tconst path = assistantToolCallTarget(name, block.arguments);\n\t\t\t\tconst baseKind = TOOL_KIND_BY_NAME[name] ?? null;\n\t\t\t\tconst fact: ToolCallFact = {\n\t\t\t\t\tid: block.id,\n\t\t\t\t\tname,\n\t\t\t\t\tpath,\n\t\t\t\t\tbaseKind,\n\t\t\t\t\tfinalKind: baseKind,\n\t\t\t\t\tverb,\n\t\t\t\t\toutcome: \"\",\n\t\t\t\t\tresolved: false,\n\t\t\t\t};\n\t\t\t\tactionFacts.push(fact);\n\t\t\t\tconst index = actionFacts.length - 1;\n\t\t\t\tpendingByOrder.push(index);\n\t\t\t\tif (fact.id) {\n\t\t\t\t\tconst bucket = pendingById.get(fact.id) ?? [];\n\t\t\t\t\tbucket.push(index);\n\t\t\t\t\tpendingById.set(fact.id, bucket);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (message.role === \"toolResult\") {\n\t\t\tconst toolCallId = (message as { toolCallId?: string }).toolCallId;\n\t\t\tconst toolName = (message as { toolName?: string }).toolName;\n\t\t\tlet matchedIndex: number | undefined;\n\n\t\t\tif (toolCallId) {\n\t\t\t\tconst bucket = pendingById.get(toolCallId);\n\t\t\t\tif (bucket && bucket.length > 0) {\n\t\t\t\t\tmatchedIndex = bucket.shift();\n\t\t\t\t\tif (bucket.length === 0) {\n\t\t\t\t\t\tpendingById.delete(toolCallId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matchedIndex === undefined && toolName) {\n\t\t\t\tfor (const index of pendingByOrder) {\n\t\t\t\t\tconst candidate = actionFacts[index];\n\t\t\t\t\tif (!candidate.resolved && candidate.name === toolName) {\n\t\t\t\t\t\tmatchedIndex = index;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matchedIndex !== undefined) {\n\t\t\t\tconst fact = actionFacts[matchedIndex];\n\t\t\t\tconst outcome = firstOutcome(message);\n\t\t\t\tfact.outcome = outcome;\n\t\t\t\tfact.resolved = true;\n\t\t\t\tif (fact.baseKind === \"modified\" && appearsCreated(message)) {\n\t\t\t\t\tfact.finalKind = \"created\";\n\t\t\t\t}\n\n\t\t\t\tconst finalOutcome = clampText(fact.outcome, 80);\n\t\t\t\tfact.outcome = finalOutcome;\n\t\t\t\tif (fact.path && fact.finalKind) {\n\t\t\t\t\tconst existing = filesByPath.get(fact.path);\n\t\t\t\t\tconst nextKind = fact.finalKind;\n\t\t\t\t\tconst note = fact.verb;\n\t\t\t\t\tif (!existing || FILE_KIND_PRIORITY[nextKind] > FILE_KIND_PRIORITY[existing.kind]) {\n\t\t\t\t\t\tfilesByPath.set(fact.path, { path: fact.path, kind: nextKind, note });\n\t\t\t\t\t} else if (existing.kind === nextKind) {\n\t\t\t\t\t\texisting.note = `${note}: ${finalOutcome}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst pendingPos = pendingByOrder.indexOf(matchedIndex);\n\t\t\t\tif (pendingPos >= 0) {\n\t\t\t\t\tpendingByOrder.splice(pendingPos, 1);\n\t\t\t\t}\n\t\t\t\tif (toolCallId) {\n\t\t\t\t\tconst bucket = pendingById.get(toolCallId);\n\t\t\t\t\tif (bucket) {\n\t\t\t\t\t\tconst pos = bucket.indexOf(matchedIndex);\n\t\t\t\t\t\tif (pos >= 0) {\n\t\t\t\t\t\t\tbucket.splice(pos, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (bucket.length === 0) {\n\t\t\t\t\t\t\tpendingById.delete(toolCallId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const action of actionFacts) {\n\t\tif (action.finalKind && action.path && !filesByPath.has(action.path)) {\n\t\t\tconst note = action.outcome ? `${action.verb}: ${action.outcome}` : action.verb;\n\t\t\tfilesByPath.set(action.path, { path: action.path, kind: action.finalKind, note });\n\t\t}\n\n\t\tactions.push(`${action.verb} ${action.path ?? \"(unknown)\"} — ${action.outcome}`);\n\t}\n\n\tfor (const file of filesByPath.values()) {\n\t\tif (!file.note && filesNotes.has(file.path)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (file.note && filesNotes.get(file.path) === file.note) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (file.note) {\n\t\t\tfilesNotes.set(file.path, file.note);\n\t\t}\n\t}\n\n\tfor (const file of filesByPath.values()) {\n\t\tconst suffix = filesNotes.get(file.path);\n\t\tif (suffix) {\n\t\t\tfile.note = suffix;\n\t\t}\n\t}\n\n\treturn {\n\t\tfiles: Array.from(filesByPath.values()).sort((a, b) => {\n\t\t\tif (a.path === b.path) {\n\t\t\t\treturn FILE_KIND_PRIORITY[a.kind] - FILE_KIND_PRIORITY[b.kind];\n\t\t\t}\n\t\t\treturn a.path.localeCompare(b.path);\n\t\t}),\n\t\tactions,\n\t\tprohibitions: prohibitions.slice(-MAX_PROHIBITIONS),\n\t\tcancelledText: cancelledParts.join(\"\\n\"),\n\t\tactiveTaskSource,\n\t};\n}\n\nexport function renderFactsBlock(facts: CompactionFacts): string {\n\tconst lines: string[] = [\"files:\"];\n\tfor (const file of facts.files) {\n\t\tlines.push(`${file.kind}: ${file.path} — ${file.note}`);\n\t}\n\tlines.push(\"actions:\");\n\tfor (const action of facts.actions) {\n\t\tlines.push(action);\n\t}\n\tlines.push(\"prohibitions:\");\n\tfor (const prohibition of facts.prohibitions) {\n\t\tlines.push(prohibition);\n\t}\n\t// The active-task gate demands near-verbatim recall of this text, but the conversation the\n\t// summarizer sees may be pre-digested or truncated — the facts block is the one channel\n\t// guaranteed to reach the prompt, so the gated text must ride in it (bounded).\n\tlines.push(\"active task:\");\n\tif (facts.activeTaskSource) {\n\t\tlines.push(clampText(facts.activeTaskSource, 4000));\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/compaction/extraction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { AgentMessage } from \"../types.ts\";\n\nexport interface CompactionFileFact {\n\tpath: string;\n\tkind: \"modified\" | \"created\" | \"read\";\n\tnote: string;\n}\n\nexport interface CompactionErrorFact {\n\toperation: string;\n\terror: string;\n}\n\nexport interface CompactionFacts {\n\tfiles: CompactionFileFact[];\n\tworkingSet: CompactionFileFact[];\n\tactions: string[];\n\terrorFacts: CompactionErrorFact[];\n\tprohibitions: string[];\n\tcancelledText: string;\n\tactiveTaskSource: string;\n}\n\ninterface ToolCallFact {\n\tid: string | undefined;\n\tname: string;\n\tpath: string | undefined;\n\tbaseKind: \"modified\" | \"created\" | \"read\" | null;\n\tfinalKind: \"modified\" | \"created\" | \"read\" | null;\n\tverb: string;\n\tresolved: boolean;\n}\n\nconst PROHIBITION_PATTERN = /\\b(do not|don't|never|stop (?:doing|using|changing)|no more)\\b/i;\n// Deliberately narrow: a bare \"stop\" (\"stop the server and rerun\") is everyday phrasing, not a\n// reversal of the prior work — matching it marked whole turns cancelled and made the\n// cancelled-work gate fight the recall gates (2026-07-06 incident).\nconst REVERSAL_PATTERN =\n\t/\\b(undo|revert|roll back|never mind|scrap that|forget (?:it|that)|stop (?:that|this|it|everything|working on))\\b/i;\n\n/** User messages longer than this are documents/pastes, not spoken prohibitions. */\nconst PROHIBITION_SOURCE_MAX_CHARS = 1_500;\n/** Upper bound on gate-demanded rules; most recent win (same bounding rationale as Done carry-over). */\nconst MAX_PROHIBITIONS = 8;\n/** Upper bound on gate-demanded actions; mirrors the prompt's \"15 most recent Done items\" rule. */\nconst MAX_ACTIONS = 15;\nconst MAX_WORKING_SET_FILES = 8;\nconst MAX_ERROR_FACTS = 5;\nconst ERROR_LINE_MAX_CHARS = 160;\nconst COMMAND_PREFIX_MAX_CHARS = 80;\n/** Shared clamp for the active-task text because verification can only demand what the prompt receives. */\nexport const ACTIVE_TASK_SOURCE_MAX_CHARS = 4_000;\n\nconst FILE_KIND_PRIORITY: Record<NonNullable<ToolCallFact[\"finalKind\"]>, number> = {\n\tread: 1,\n\tcreated: 2,\n\tmodified: 3,\n};\n\nconst TOOL_KIND_BY_NAME: Record<string, \"modified\" | \"created\" | \"read\" | null> = {\n\tread: \"read\",\n\tgrep: \"read\",\n\tfind: \"read\",\n\twrite: \"modified\",\n\tedit: \"modified\",\n\tbash: null,\n};\n\nconst TOOL_VERB_BY_NAME: Record<string, string> = {\n\twrite: \"WRITE\",\n\tedit: \"EDIT\",\n\tbash: \"RUN\",\n\tread: \"READ\",\n\tgrep: \"READ\",\n\tfind: \"READ\",\n};\n\nfunction messageToText(message: AgentMessage): string {\n\tconst rawContent = (message as { content?: unknown }).content;\n\tif (typeof rawContent === \"string\") {\n\t\treturn rawContent;\n\t}\n\tif (!Array.isArray(rawContent)) {\n\t\treturn \"\";\n\t}\n\n\tconst parts: string[] = [];\n\tfor (const block of rawContent) {\n\t\tif (!block || typeof block !== \"object\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((block as { type?: unknown }).type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t}\n\t}\n\treturn parts.join(\" \").trim();\n}\n\nfunction assistantToolCallTarget(name: string, rawArgs: unknown): string | undefined {\n\tif (!rawArgs || typeof rawArgs !== \"object\") {\n\t\treturn undefined;\n\t}\n\tconst args = rawArgs as Record<string, unknown>;\n\n\tif (\n\t\t(name === \"read\" || name === \"grep\" || name === \"find\" || name === \"write\" || name === \"edit\") &&\n\t\ttypeof args.path === \"string\" &&\n\t\targs.path.length > 0\n\t) {\n\t\treturn args.path;\n\t}\n\n\tif (name === \"bash\" && typeof args.command === \"string\" && args.command.length > 0) {\n\t\treturn args.command;\n\t}\n\n\treturn undefined;\n}\n\nfunction clampText(text: string, maxLen: number): string {\n\treturn text.length > maxLen ? text.slice(0, maxLen) : text;\n}\n\nfunction appearsCreated(message: AgentMessage): boolean {\n\tif (message.role !== \"toolResult\") {\n\t\treturn false;\n\t}\n\n\tconst text = messageToText(message).toLowerCase();\n\tif (/(\\bnew file\\b|\\bcreated\\b|\\bcreated file\\b)/.test(text)) {\n\t\treturn true;\n\t}\n\n\tconst details = (message as { details?: unknown }).details;\n\tif (!details || typeof details !== \"object\") {\n\t\treturn false;\n\t}\n\n\tif (typeof (details as { created?: unknown }).created === \"boolean\") {\n\t\treturn (details as { created: boolean }).created;\n\t}\n\tif (typeof (details as { isCreated?: unknown }).isCreated === \"boolean\") {\n\t\treturn (details as { isCreated: boolean }).isCreated;\n\t}\n\tif (typeof (details as { isNewFile?: unknown }).isNewFile === \"boolean\") {\n\t\treturn (details as { isNewFile: boolean }).isNewFile;\n\t}\n\n\treturn false;\n}\n\nfunction splitSentenceLines(text: string): string[] {\n\treturn text\n\t\t.split(/(?<=[.!?])\\s+|\\n+/)\n\t\t.map((line) => line.trim().replace(/[.!?]+$/, \"\"))\n\t\t.filter(Boolean);\n}\n\nfunction getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {\n\tswitch (entry.type) {\n\t\tcase \"message\":\n\t\t\treturn entry.message;\n\t\tcase \"custom_message\":\n\t\t\treturn {\n\t\t\t\trole: \"custom\",\n\t\t\t\tcustomType: entry.customType,\n\t\t\t\tcontent: entry.content,\n\t\t\t\tdetails: entry.details,\n\t\t\t\tdisplay: entry.display,\n\t\t\t\ttimestamp: new Date(entry.timestamp).getTime(),\n\t\t\t};\n\t\tcase \"branch_summary\":\n\t\t\treturn {\n\t\t\t\trole: \"branchSummary\",\n\t\t\t\tsummary: entry.summary,\n\t\t\t\tfromId: entry.fromId,\n\t\t\t\ttimestamp: new Date(entry.timestamp).getTime(),\n\t\t\t};\n\t\tcase \"compaction\":\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction isToolCallBlock(block: unknown): block is { type: \"toolCall\"; id?: string; name: string; arguments: unknown } {\n\tif (!block || typeof block !== \"object\") {\n\t\treturn false;\n\t}\n\tif ((block as { type?: unknown }).type !== \"toolCall\") {\n\t\treturn false;\n\t}\n\tif (typeof (block as { name?: unknown }).name !== \"string\") {\n\t\treturn false;\n\t}\n\tif (!(block as { arguments?: unknown }).arguments) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nfunction toolCallVerb(name: string): string {\n\treturn TOOL_VERB_BY_NAME[name] ?? name.toUpperCase();\n}\n\nfunction isHarnessPlumbingTarget(target: string | undefined): boolean {\n\tif (!target) return false;\n\treturn (\n\t\ttarget.includes(\"/.pi/agent/context-gc/\") ||\n\t\ttarget.includes(\"~/.pi/agent/context-gc/\") ||\n\t\t/\\/tmp\\/pi-bash-[^\\s]+\\.log\\b/.test(target)\n\t);\n}\n\nfunction normalizeOperationTarget(toolName: string, path: string | undefined): string {\n\tif (!path) return \"(unknown)\";\n\tif (toolName !== \"bash\") return path;\n\treturn clampText(path.replace(/\\s+/g, \" \").trim(), COMMAND_PREFIX_MAX_CHARS);\n}\n\nfunction operationKey(toolName: string, path: string | undefined): string {\n\treturn `${toolName}:${normalizeOperationTarget(toolName, path)}`;\n}\n\nfunction operationLabel(toolName: string, path: string | undefined): string {\n\treturn `${toolCallVerb(toolName)} ${normalizeOperationTarget(toolName, path)}`;\n}\n\nfunction firstErrorLine(text: string): string {\n\tconst line = text\n\t\t.split(/\\r?\\n/)\n\t\t.map((part) => part.trim())\n\t\t.find(Boolean);\n\treturn clampText(line ?? \"failed\", ERROR_LINE_MAX_CHARS);\n}\n\nfunction isFailureToolResult(message: AgentMessage, text: string): boolean {\n\tif (message.role !== \"toolResult\") return false;\n\tif ((message as { isError?: unknown }).isError === true) return true;\n\treturn /\\b(exit code|failed|failure|error|exception|traceback)\\b/i.test(text);\n}\n\nexport function extractCompactionFacts(entries: SessionEntry[], start: number, end: number): CompactionFacts {\n\tconst rangeStart = Math.max(0, start);\n\tconst rangeEnd = Math.min(entries.length, Math.max(rangeStart, end));\n\n\tif (rangeStart >= rangeEnd) {\n\t\treturn {\n\t\t\tfiles: [],\n\t\t\tworkingSet: [],\n\t\t\tactions: [],\n\t\t\terrorFacts: [],\n\t\t\tprohibitions: [],\n\t\t\tcancelledText: \"\",\n\t\t\tactiveTaskSource: \"\",\n\t\t};\n\t}\n\n\tconst filesByPath = new Map<string, CompactionFileFact & { lastTouch: number }>();\n\tconst filesNotes = new Map<string, string>();\n\tconst seenProhibitions = new Set<string>();\n\tconst actionFacts: ToolCallFact[] = [];\n\tconst pendingById = new Map<string, number[]>();\n\tconst pendingByOrder: number[] = [];\n\n\tconst actions: string[] = [];\n\tconst openErrors = new Map<string, CompactionErrorFact & { lastTouch: number }>();\n\tconst prohibitions: string[] = [];\n\tlet activeTaskSource = \"\";\n\tconst cancelledParts: string[] = [];\n\tconst sinceLastUser: string[] = [];\n\n\tfor (let i = rangeStart; i < rangeEnd; i++) {\n\t\tconst message = getMessageFromEntry(entries[i]);\n\t\tif (!message) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message.role === \"user\") {\n\t\t\tconst userText = messageToText(message);\n\t\t\tif (REVERSAL_PATTERN.test(userText)) {\n\t\t\t\tcancelledParts.push(...sinceLastUser);\n\t\t\t}\n\t\t\tsinceLastUser.length = 0;\n\n\t\t\tif (userText) {\n\t\t\t\tactiveTaskSource = clampText(userText, ACTIVE_TASK_SOURCE_MAX_CHARS);\n\t\t\t\t// Mandatory Rules exist for SPOKEN durable prohibitions (\"do not touch X\"), not for\n\t\t\t\t// documents. A long pasted text (plan, instruction file) can contain dozens of\n\t\t\t\t// \"never/do not\" lines; harvesting them makes the verification gate demand the\n\t\t\t\t// checkpoint reproduce the document (2026-07-06 field incident: 13 fragment rules\n\t\t\t\t// extracted from one pasted instruction). Documents live on disk; skip them here.\n\t\t\t\tif (userText.length <= PROHIBITION_SOURCE_MAX_CHARS) {\n\t\t\t\t\tfor (const sentence of splitSentenceLines(userText)) {\n\t\t\t\t\t\tif (!PROHIBITION_PATTERN.test(sentence)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst normalized = clampText(sentence, 160);\n\t\t\t\t\t\tconst dedupeKey = normalized.toLowerCase();\n\t\t\t\t\t\tif (!seenProhibitions.has(dedupeKey)) {\n\t\t\t\t\t\t\tseenProhibitions.add(dedupeKey);\n\t\t\t\t\t\t\tprohibitions.push(normalized);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (message.role === \"assistant\" || message.role === \"toolResult\") {\n\t\t\tconst messageText = messageToText(message);\n\t\t\tif (messageText) {\n\t\t\t\tsinceLastUser.push(messageText);\n\t\t\t}\n\t\t}\n\n\t\tif (message.role === \"assistant\" && (message as { stopReason?: unknown }).stopReason === \"error\") {\n\t\t\tconst errorMessage = (message as { errorMessage?: unknown }).errorMessage;\n\t\t\topenErrors.set(\"assistant:error\", {\n\t\t\t\toperation: \"ASSISTANT response\",\n\t\t\t\terror: firstErrorLine(typeof errorMessage === \"string\" ? errorMessage : messageToText(message)),\n\t\t\t\tlastTouch: i,\n\t\t\t});\n\t\t} else if (message.role === \"assistant\" && (message as { stopReason?: unknown }).stopReason === \"stop\") {\n\t\t\topenErrors.delete(\"assistant:error\");\n\t\t}\n\n\t\tif (message.role === \"assistant\" && Array.isArray(message.content)) {\n\t\t\tfor (const block of message.content) {\n\t\t\t\tif (!isToolCallBlock(block)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst name = block.name;\n\t\t\t\tconst verb = toolCallVerb(name);\n\t\t\t\tconst path = assistantToolCallTarget(name, block.arguments);\n\t\t\t\tconst baseKind = TOOL_KIND_BY_NAME[name] ?? null;\n\t\t\t\tconst fact: ToolCallFact = {\n\t\t\t\t\tid: block.id,\n\t\t\t\t\tname,\n\t\t\t\t\tpath,\n\t\t\t\t\tbaseKind,\n\t\t\t\t\tfinalKind: baseKind,\n\t\t\t\t\tverb,\n\t\t\t\t\tresolved: false,\n\t\t\t\t};\n\t\t\t\tactionFacts.push(fact);\n\t\t\t\tconst index = actionFacts.length - 1;\n\t\t\t\tpendingByOrder.push(index);\n\t\t\t\tif (fact.id) {\n\t\t\t\t\tconst bucket = pendingById.get(fact.id) ?? [];\n\t\t\t\t\tbucket.push(index);\n\t\t\t\t\tpendingById.set(fact.id, bucket);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (message.role === \"toolResult\") {\n\t\t\tconst toolCallId = (message as { toolCallId?: string }).toolCallId;\n\t\t\tconst toolName = (message as { toolName?: string }).toolName;\n\t\t\tlet matchedIndex: number | undefined;\n\n\t\t\tif (toolCallId) {\n\t\t\t\tconst bucket = pendingById.get(toolCallId);\n\t\t\t\tif (bucket && bucket.length > 0) {\n\t\t\t\t\tmatchedIndex = bucket.shift();\n\t\t\t\t\tif (bucket.length === 0) {\n\t\t\t\t\t\tpendingById.delete(toolCallId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matchedIndex === undefined && toolName) {\n\t\t\t\tfor (const index of pendingByOrder) {\n\t\t\t\t\tconst candidate = actionFacts[index];\n\t\t\t\t\tif (!candidate.resolved && candidate.name === toolName) {\n\t\t\t\t\t\tmatchedIndex = index;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matchedIndex !== undefined) {\n\t\t\t\tconst fact = actionFacts[matchedIndex];\n\t\t\t\tfact.resolved = true;\n\t\t\t\tif (fact.baseKind === \"modified\" && appearsCreated(message)) {\n\t\t\t\t\tfact.finalKind = \"created\";\n\t\t\t\t}\n\n\t\t\t\tif (fact.path && fact.finalKind && !isHarnessPlumbingTarget(fact.path)) {\n\t\t\t\t\tconst existing = filesByPath.get(fact.path);\n\t\t\t\t\tconst nextKind = fact.finalKind;\n\t\t\t\t\tconst note = fact.verb;\n\t\t\t\t\tif (!existing || FILE_KIND_PRIORITY[nextKind] > FILE_KIND_PRIORITY[existing.kind]) {\n\t\t\t\t\t\tfilesByPath.set(fact.path, { path: fact.path, kind: nextKind, note, lastTouch: i });\n\t\t\t\t\t} else if (existing.kind === nextKind) {\n\t\t\t\t\t\texisting.note = note;\n\t\t\t\t\t\texisting.lastTouch = i;\n\t\t\t\t\t} else {\n\t\t\t\t\t\texisting.lastTouch = i;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst resultText = messageToText(message);\n\t\t\t\tconst key = operationKey(fact.name, fact.path);\n\t\t\t\tif (isFailureToolResult(message, resultText)) {\n\t\t\t\t\topenErrors.set(key, {\n\t\t\t\t\t\toperation: operationLabel(fact.name, fact.path),\n\t\t\t\t\t\terror: firstErrorLine(resultText),\n\t\t\t\t\t\tlastTouch: i,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\topenErrors.delete(key);\n\t\t\t\t}\n\n\t\t\t\tconst pendingPos = pendingByOrder.indexOf(matchedIndex);\n\t\t\t\tif (pendingPos >= 0) {\n\t\t\t\t\tpendingByOrder.splice(pendingPos, 1);\n\t\t\t\t}\n\t\t\t\tif (toolCallId) {\n\t\t\t\t\tconst bucket = pendingById.get(toolCallId);\n\t\t\t\t\tif (bucket) {\n\t\t\t\t\t\tconst pos = bucket.indexOf(matchedIndex);\n\t\t\t\t\t\tif (pos >= 0) {\n\t\t\t\t\t\t\tbucket.splice(pos, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (bucket.length === 0) {\n\t\t\t\t\t\t\tpendingById.delete(toolCallId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const action of actionFacts) {\n\t\tif (isHarnessPlumbingTarget(action.path)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (action.finalKind && action.path && !filesByPath.has(action.path)) {\n\t\t\tfilesByPath.set(action.path, {\n\t\t\t\tpath: action.path,\n\t\t\t\tkind: action.finalKind,\n\t\t\t\tnote: action.verb,\n\t\t\t\tlastTouch: actionFacts.indexOf(action),\n\t\t\t});\n\t\t}\n\n\t\tactions.push(`${action.verb} ${action.path ?? \"(unknown)\"}`);\n\t}\n\n\tfor (const file of filesByPath.values()) {\n\t\tif (!file.note && filesNotes.has(file.path)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (file.note && filesNotes.get(file.path) === file.note) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (file.note) {\n\t\t\tfilesNotes.set(file.path, file.note);\n\t\t}\n\t}\n\n\tfor (const file of filesByPath.values()) {\n\t\tconst suffix = filesNotes.get(file.path);\n\t\tif (suffix) {\n\t\t\tfile.note = suffix;\n\t\t}\n\t}\n\n\tconst files = Array.from(filesByPath.values())\n\t\t.sort(\n\t\t\t(a, b) =>\n\t\t\t\tb.lastTouch - a.lastTouch ||\n\t\t\t\tFILE_KIND_PRIORITY[b.kind] - FILE_KIND_PRIORITY[a.kind] ||\n\t\t\t\ta.path.localeCompare(b.path),\n\t\t)\n\t\t.map(({ lastTouch: _lastTouch, ...file }) => file);\n\n\treturn {\n\t\tfiles,\n\t\tworkingSet: files.slice(0, MAX_WORKING_SET_FILES),\n\t\tactions: dedupeMostRecent(actions).slice(-MAX_ACTIONS),\n\t\terrorFacts: Array.from(openErrors.values())\n\t\t\t.sort((a, b) => a.lastTouch - b.lastTouch)\n\t\t\t.slice(-MAX_ERROR_FACTS)\n\t\t\t.map(({ lastTouch: _lastTouch, ...error }) => error),\n\t\tprohibitions: prohibitions.slice(-MAX_PROHIBITIONS),\n\t\tcancelledText: cancelledParts.join(\"\\n\"),\n\t\tactiveTaskSource,\n\t};\n}\n\nfunction dedupeMostRecent(values: string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst kept: string[] = [];\n\tfor (let i = values.length - 1; i >= 0; i--) {\n\t\tconst value = values[i];\n\t\tif (seen.has(value)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(value);\n\t\tkept.push(value);\n\t}\n\treturn kept.reverse();\n}\n\nexport function renderFactsBlock(facts: CompactionFacts): string {\n\tconst lines: string[] = [\"files:\"];\n\tfor (const file of facts.files) {\n\t\tlines.push(`${file.kind}: ${file.path} — ${file.note}`);\n\t}\n\tlines.push(\"working set:\");\n\tfor (const file of facts.workingSet) {\n\t\tlines.push(`${file.path} — ${file.note || file.kind}`);\n\t}\n\tlines.push(\"actions:\");\n\tfor (const action of facts.actions) {\n\t\tlines.push(action);\n\t}\n\tlines.push(\"open errors:\");\n\tfor (const error of facts.errorFacts) {\n\t\tlines.push(`${error.operation}: ${error.error}`);\n\t}\n\tlines.push(\"prohibitions:\");\n\tfor (const prohibition of facts.prohibitions) {\n\t\tlines.push(prohibition);\n\t}\n\t// The active-task gate demands near-verbatim recall of this text, but the conversation the\n\t// summarizer sees may be pre-digested or truncated — the facts block is the one channel\n\t// guaranteed to reach the prompt, so the gated text must ride in it (bounded).\n\tlines.push(\"active task:\");\n\tif (facts.activeTaskSource) {\n\t\tlines.push(clampText(facts.activeTaskSource, ACTIVE_TASK_SOURCE_MAX_CHARS));\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -15,8 +15,8 @@ export interface ModelAndAuth {
15
15
  }
16
16
  export interface CompactionLoopDeps {
17
17
  measureLiveTokens(): number;
18
- getTriggerThreshold(): number;
19
- getMargin(): number;
18
+ shouldCompact(tokens: number): boolean;
19
+ getPostApplyMargin(): number;
20
20
  getBranch(): SessionEntry[];
21
21
  resolveModelAndAuth(modelTier: CompactionCycleParams["modelTier"]): Promise<ModelAndAuth>;
22
22
  summarizeAndVerify(params: CompactionCycleParams, model: Model<any>, apiKey: string | undefined, headers: Record<string, string> | undefined, branch: SessionEntry[]): Promise<{
@@ -32,6 +32,7 @@ export interface CompactionLoopDeps {
32
32
  cycle: number;
33
33
  from: string;
34
34
  cause: string;
35
+ detail?: string;
35
36
  }): void;
36
37
  getBaseKeepRecentTokens?(): number;
37
38
  verifyPostApplyEffect?(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/compaction/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,mBAAmB,IAAI,MAAM,CAAC;IAC9B,SAAS,IAAI,MAAM,CAAC;IACpB,SAAS,IAAI,YAAY,EAAE,CAAC;IAC5B,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1F,kBAAkB,CACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,MAAM,EAAE,YAAY,EAAE,GACpB,OAAO,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAAC;IACzC,4BAA4B,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC,GAAG;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACrG,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtD,YAAY,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,uBAAuB,CAAC,IAAI,MAAM,CAAC;IACnC,qBAAqB,CAAC,IAAI,OAAO,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMtD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAwHhG","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { CompactionResult } from \"./compaction.ts\";\n\nexport interface CompactionCycleParams {\n\tmodelTier: \"cheap\" | \"session\";\n\tkeepRecentTokens: number;\n\tchunked: boolean;\n\tdeterministicOnly: boolean;\n}\n\nexport interface ModelAndAuth {\n\tmodel: Model<any>;\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\tfailure?: string;\n}\n\nexport interface CompactionLoopDeps {\n\tmeasureLiveTokens(): number;\n\tgetTriggerThreshold(): number;\n\tgetMargin(): number;\n\tgetBranch(): SessionEntry[];\n\tresolveModelAndAuth(modelTier: CompactionCycleParams[\"modelTier\"]): Promise<ModelAndAuth>;\n\tsummarizeAndVerify(\n\t\tparams: CompactionCycleParams,\n\t\tmodel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tbranch: SessionEntry[],\n\t): Promise<{ result: CompactionResult }>;\n\tbuildDeterministicCheckpoint(): Promise<{ result: CompactionResult }> | { result: CompactionResult };\n\tapply(result: CompactionResult): Promise<void> | void;\n\tonTransition(info: { cycle: number; from: string; cause: string }): void;\n\tgetBaseKeepRecentTokens?(): number;\n\tverifyPostApplyEffect?(): boolean;\n\tsignal?: AbortSignal;\n}\n\nexport type CompactionLoopOutcome =\n\t| { kind: \"success\"; result: CompactionResult; cycles: number }\n\t| { kind: \"skip\"; reason: string }\n\t| { kind: \"failed\"; reason: string; cycles: number };\n\nconst MAX_CYCLES = 4;\nconst MAX_LLM_CYCLES = 3;\nconst DEFAULT_KEEP_RECENT = 20_000;\n\nexport async function runCompactionLoop(deps: CompactionLoopDeps): Promise<CompactionLoopOutcome> {\n\tlet lastCause = \"start\";\n\tlet lastParams: CompactionCycleParams | undefined;\n\tlet lastObservedTokens: number | undefined;\n\tlet appliedResult: CompactionResult | undefined;\n\tlet ownTrailingCompactionId: string | undefined;\n\tlet ownTrailingCompactionNeedsRetry = false;\n\tlet baseKeepRecent = deps.getBaseKeepRecentTokens ? deps.getBaseKeepRecentTokens() : DEFAULT_KEEP_RECENT;\n\tif (!Number.isFinite(baseKeepRecent) || baseKeepRecent <= 0) {\n\t\tbaseKeepRecent = DEFAULT_KEEP_RECENT;\n\t}\n\n\tfor (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t}\n\n\t\tconst branch = deps.getBranch();\n\t\tconst trailingEntry = branch[branch.length - 1];\n\t\tif (branch.length > 0 && trailingEntry?.type === \"compaction\") {\n\t\t\tif (appliedResult && trailingEntry.id === ownTrailingCompactionId) {\n\t\t\t\tif (!ownTrailingCompactionNeedsRetry) return { kind: \"success\", result: appliedResult, cycles: cycle - 1 };\n\t\t\t} else {\n\t\t\t\treturn { kind: \"skip\", reason: \"already compacted\" };\n\t\t\t}\n\t\t}\n\n\t\tconst observedTokens = deps.measureLiveTokens();\n\t\tif (observedTokens <= deps.getTriggerThreshold()) {\n\t\t\treturn {\n\t\t\t\tkind: \"skip\",\n\t\t\t\treason:\n\t\t\t\t\tbranch.length > 0 && branch[branch.length - 1]?.type === \"compaction\"\n\t\t\t\t\t\t? \"already compacted\"\n\t\t\t\t\t\t: \"within threshold\",\n\t\t\t};\n\t\t}\n\n\t\tconst selectedParams = selectCycleParams(cycle, lastCause, lastParams, baseKeepRecent);\n\t\tconst params = enforceMonotonicProgress(selectedParams, lastParams, observedTokens, lastObservedTokens);\n\t\tlastObservedTokens = observedTokens;\n\t\tlastParams = params;\n\n\t\tif (params.deterministicOnly || cycle > MAX_LLM_CYCLES) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { result } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\tawait deps.apply(result);\n\t\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t\t} catch (error) {\n\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(error), cycles: cycle };\n\t\t\t}\n\t\t}\n\n\t\tlet modelInfo: ModelAndAuth;\n\t\ttry {\n\t\t\tmodelInfo = await deps.resolveModelAndAuth(params.modelTier);\n\t\t} catch {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tif (modelInfo.failure) {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet result: CompactionResult;\n\t\ttry {\n\t\t\t({ result } = await deps.summarizeAndVerify(\n\t\t\t\tparams,\n\t\t\t\tmodelInfo.model,\n\t\t\t\tmodelInfo.apiKey,\n\t\t\t\tmodelInfo.headers,\n\t\t\t\tbranch,\n\t\t\t));\n\t\t} catch (error) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t\t}\n\t\t\tlastCause = mapFailureCause(error);\n\t\t\tif (lastCause === \"aborted\") {\n\t\t\t\treturn { kind: \"failed\", reason: lastCause, cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"provider-failure\") {\n\t\t\t\treturn { kind: \"failed\", reason: error instanceof Error ? error.message : String(error), cycles: cycle };\n\t\t\t}\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step3\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t}\n\t\tawait deps.apply(result);\n\t\tappliedResult = result;\n\t\tconst branchAfterApply = deps.getBranch();\n\t\tconst trailingAfterApply = branchAfterApply[branchAfterApply.length - 1];\n\t\townTrailingCompactionId = trailingAfterApply?.type === \"compaction\" ? trailingAfterApply.id : undefined;\n\n\t\tif (deps.verifyPostApplyEffect?.() === false) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\tconst measuredAfter = deps.measureLiveTokens();\n\t\tif (measuredAfter <= deps.getTriggerThreshold() - deps.getMargin()) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\townTrailingCompactionNeedsRetry = true;\n\t\tlastCause = \"effect-not-restored\";\n\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step5\", cause: lastCause });\n\t}\n\n\treturn { kind: \"failed\", reason: \"exhausted-compaction-cycles\", cycles: MAX_CYCLES };\n}\n\nfunction selectCycleParams(\n\tcycle: number,\n\tcause: string,\n\tlastParams: CompactionCycleParams | undefined,\n\tbaseKeepRecent: number,\n): CompactionCycleParams {\n\tif (cycle >= MAX_CYCLES) {\n\t\treturn {\n\t\t\tmodelTier: lastParams?.modelTier ?? \"session\",\n\t\t\tkeepRecentTokens: Math.max(1, Math.floor((lastParams?.keepRecentTokens ?? baseKeepRecent) / 2)),\n\t\t\tchunked: true,\n\t\t\tdeterministicOnly: true,\n\t\t};\n\t}\n\n\tif (!lastParams) {\n\t\treturn {\n\t\t\tmodelTier: \"cheap\",\n\t\t\tkeepRecentTokens: Math.max(1, baseKeepRecent),\n\t\t\tchunked: false,\n\t\t\tdeterministicOnly: false,\n\t\t};\n\t}\n\n\tconst params: CompactionCycleParams = {\n\t\t...lastParams,\n\t\tdeterministicOnly: false,\n\t};\n\n\tif (cause === \"gate-failed\" || cause === \"auth-failed\" || cause === \"length-stop\") {\n\t\tparams.modelTier = \"session\";\n\t} else if (cause === \"input-overflow\") {\n\t\tparams.chunked = true;\n\t} else if (cause === \"effect-not-restored\") {\n\t\tparams.chunked = true;\n\t\tparams.keepRecentTokens = Math.max(1, Math.floor(lastParams.keepRecentTokens / 2));\n\t}\n\n\treturn params;\n}\n\nfunction enforceMonotonicProgress(\n\tparams: CompactionCycleParams,\n\tlastParams: CompactionCycleParams | undefined,\n\tobservedTokens: number,\n\tlastObservedTokens: number | undefined,\n): CompactionCycleParams {\n\tif (\n\t\t!lastParams ||\n\t\tparams.deterministicOnly ||\n\t\t(lastObservedTokens !== undefined && observedTokens < lastObservedTokens)\n\t) {\n\t\treturn params;\n\t}\n\tif (!sameParams(params, lastParams)) {\n\t\treturn params;\n\t}\n\n\tconst keepRecentTokens = Math.max(1, Math.floor(params.keepRecentTokens / 2));\n\tif (keepRecentTokens !== params.keepRecentTokens) {\n\t\treturn { ...params, chunked: true, keepRecentTokens };\n\t}\n\tif (!params.chunked) {\n\t\treturn { ...params, chunked: true };\n\t}\n\treturn { ...params, modelTier: params.modelTier === \"cheap\" ? \"session\" : \"cheap\" };\n}\n\nfunction sameParams(a: CompactionCycleParams, b: CompactionCycleParams): boolean {\n\treturn (\n\t\ta.modelTier === b.modelTier &&\n\t\ta.keepRecentTokens === b.keepRecentTokens &&\n\t\ta.chunked === b.chunked &&\n\t\ta.deterministicOnly === b.deterministicOnly\n\t);\n}\n\nfunction mapFailureCause(error: unknown): string {\n\tconst message = error instanceof Error ? error.message : typeof error === \"string\" ? error : \"\";\n\tif (message.includes(\"gate-failed\")) return \"gate-failed\";\n\tif (message.includes(\"input-overflow\")) return \"input-overflow\";\n\t// A length-stopped summary lost gated sections; escalating the tier buys a larger output cap.\n\tif (message.includes(\"summary-length-stop\")) return \"length-stop\";\n\tif (\n\t\t/stream stalled|overloaded|rate.?limit|too many requests|service.?unavailable|server.?error|network.?error|fetch failed|timeout|timed out/i.test(\n\t\t\tmessage,\n\t\t)\n\t)\n\t\treturn \"provider-failure\";\n\tif (message.includes(\"auto-compaction-cancelled\")) return \"aborted\";\n\tif (message.includes(\"auth\") || message.includes(\"api key\") || message.includes(\"not compacted\"))\n\t\treturn \"auth-failed\";\n\treturn \"unknown-failure\";\n}\n"]}
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/compaction/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,qBAAqB;IACrC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAClC,iBAAiB,IAAI,MAAM,CAAC;IAC5B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,kBAAkB,IAAI,MAAM,CAAC;IAC7B,SAAS,IAAI,YAAY,EAAE,CAAC;IAC5B,mBAAmB,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1F,kBAAkB,CACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,MAAM,EAAE,YAAY,EAAE,GACpB,OAAO,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAAC;IACzC,4BAA4B,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC,GAAG;QAAE,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACrG,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtD,YAAY,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1F,uBAAuB,CAAC,IAAI,MAAM,CAAC;IACnC,qBAAqB,CAAC,IAAI,OAAO,CAAC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMtD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA6IhG","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { SessionEntry } from \"../session/session-manager.ts\";\nimport type { CompactionResult } from \"./compaction.ts\";\n\nexport interface CompactionCycleParams {\n\tmodelTier: \"cheap\" | \"session\";\n\tkeepRecentTokens: number;\n\tchunked: boolean;\n\tdeterministicOnly: boolean;\n}\n\nexport interface ModelAndAuth {\n\tmodel: Model<any>;\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\tfailure?: string;\n}\n\nexport interface CompactionLoopDeps {\n\tmeasureLiveTokens(): number;\n\tshouldCompact(tokens: number): boolean;\n\tgetPostApplyMargin(): number;\n\tgetBranch(): SessionEntry[];\n\tresolveModelAndAuth(modelTier: CompactionCycleParams[\"modelTier\"]): Promise<ModelAndAuth>;\n\tsummarizeAndVerify(\n\t\tparams: CompactionCycleParams,\n\t\tmodel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tbranch: SessionEntry[],\n\t): Promise<{ result: CompactionResult }>;\n\tbuildDeterministicCheckpoint(): Promise<{ result: CompactionResult }> | { result: CompactionResult };\n\tapply(result: CompactionResult): Promise<void> | void;\n\tonTransition(info: { cycle: number; from: string; cause: string; detail?: string }): void;\n\tgetBaseKeepRecentTokens?(): number;\n\tverifyPostApplyEffect?(): boolean;\n\tsignal?: AbortSignal;\n}\n\nexport type CompactionLoopOutcome =\n\t| { kind: \"success\"; result: CompactionResult; cycles: number }\n\t| { kind: \"skip\"; reason: string }\n\t| { kind: \"failed\"; reason: string; cycles: number };\n\nconst MAX_CYCLES = 4;\nconst MAX_LLM_CYCLES = 3;\nconst DEFAULT_KEEP_RECENT = 20_000;\n\nexport async function runCompactionLoop(deps: CompactionLoopDeps): Promise<CompactionLoopOutcome> {\n\tlet lastCause = \"start\";\n\tlet lastParams: CompactionCycleParams | undefined;\n\tlet lastObservedTokens: number | undefined;\n\tlet appliedResult: CompactionResult | undefined;\n\tlet lastModelKey: string | undefined;\n\tlet ownTrailingCompactionId: string | undefined;\n\tlet ownTrailingCompactionNeedsRetry = false;\n\tlet baseKeepRecent = deps.getBaseKeepRecentTokens ? deps.getBaseKeepRecentTokens() : DEFAULT_KEEP_RECENT;\n\tif (!Number.isFinite(baseKeepRecent) || baseKeepRecent <= 0) {\n\t\tbaseKeepRecent = DEFAULT_KEEP_RECENT;\n\t}\n\n\tfor (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t}\n\n\t\tconst branch = deps.getBranch();\n\t\tconst trailingEntry = branch[branch.length - 1];\n\t\tif (branch.length > 0 && trailingEntry?.type === \"compaction\") {\n\t\t\tif (appliedResult && trailingEntry.id === ownTrailingCompactionId) {\n\t\t\t\tif (!ownTrailingCompactionNeedsRetry) return { kind: \"success\", result: appliedResult, cycles: cycle - 1 };\n\t\t\t} else {\n\t\t\t\treturn { kind: \"skip\", reason: \"already compacted\" };\n\t\t\t}\n\t\t}\n\n\t\tconst observedTokens = deps.measureLiveTokens();\n\t\tif (!deps.shouldCompact(observedTokens)) {\n\t\t\treturn {\n\t\t\t\tkind: \"skip\",\n\t\t\t\treason:\n\t\t\t\t\tbranch.length > 0 && branch[branch.length - 1]?.type === \"compaction\"\n\t\t\t\t\t\t? \"already compacted\"\n\t\t\t\t\t\t: \"within threshold\",\n\t\t\t};\n\t\t}\n\n\t\tconst selectedParams = selectCycleParams(cycle, lastCause, lastParams, baseKeepRecent);\n\t\tlet params = enforceMonotonicProgress(selectedParams, lastParams, observedTokens, lastObservedTokens, lastCause);\n\t\tlastObservedTokens = observedTokens;\n\n\t\tif (params.deterministicOnly || cycle > MAX_LLM_CYCLES) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle - 1 };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { result } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\tawait deps.apply(result);\n\t\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t\t} catch (error) {\n\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(error).cause, cycles: cycle };\n\t\t\t}\n\t\t}\n\n\t\tlet modelInfo: ModelAndAuth;\n\t\ttry {\n\t\t\tmodelInfo = await deps.resolveModelAndAuth(params.modelTier);\n\t\t} catch {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tif (modelInfo.failure) {\n\t\t\tlastCause = \"auth-failed\";\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step0\", cause: lastCause });\n\t\t\tcontinue;\n\t\t}\n\t\tconst currentModelKey = modelKey(modelInfo.model);\n\t\tif (\n\t\t\t(lastCause === \"gate-failed\" || lastCause === \"length-stop\") &&\n\t\t\tlastParams &&\n\t\t\tparams.modelTier !== lastParams.modelTier &&\n\t\t\tlastModelKey === currentModelKey\n\t\t) {\n\t\t\tparams = { ...params, modelTier: lastParams.modelTier };\n\t\t}\n\t\tlastParams = params;\n\t\tlastModelKey = currentModelKey;\n\n\t\tlet result: CompactionResult;\n\t\ttry {\n\t\t\t({ result } = await deps.summarizeAndVerify(\n\t\t\t\tparams,\n\t\t\t\tmodelInfo.model,\n\t\t\t\tmodelInfo.apiKey,\n\t\t\t\tmodelInfo.headers,\n\t\t\t\tbranch,\n\t\t\t));\n\t\t} catch (error) {\n\t\t\tif (deps.signal?.aborted) {\n\t\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t\t}\n\t\t\tconst failure = mapFailureCause(error);\n\t\t\tlastCause = failure.cause;\n\t\t\tif (lastCause === \"aborted\") {\n\t\t\t\treturn { kind: \"failed\", reason: lastCause, cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"provider-failure\") {\n\t\t\t\treturn { kind: \"failed\", reason: error instanceof Error ? error.message : String(error), cycles: cycle };\n\t\t\t}\n\t\t\tif (lastCause === \"deterministic-required\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst { result: deterministicResult } = await Promise.resolve(deps.buildDeterministicCheckpoint());\n\t\t\t\t\tawait deps.apply(deterministicResult);\n\t\t\t\t\treturn { kind: \"success\", result: deterministicResult, cycles: cycle };\n\t\t\t\t} catch (deterministicError) {\n\t\t\t\t\treturn { kind: \"failed\", reason: mapFailureCause(deterministicError).cause, cycles: cycle };\n\t\t\t\t}\n\t\t\t}\n\t\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step3\", cause: lastCause, detail: failure.detail });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (deps.signal?.aborted) {\n\t\t\treturn { kind: \"failed\", reason: \"aborted\", cycles: cycle };\n\t\t}\n\t\tawait deps.apply(result);\n\t\tappliedResult = result;\n\t\tconst branchAfterApply = deps.getBranch();\n\t\tconst trailingAfterApply = branchAfterApply[branchAfterApply.length - 1];\n\t\townTrailingCompactionId = trailingAfterApply?.type === \"compaction\" ? trailingAfterApply.id : undefined;\n\n\t\tif (deps.verifyPostApplyEffect?.() === false) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\tconst measuredAfter = deps.measureLiveTokens();\n\t\tif (!deps.shouldCompact(measuredAfter + deps.getPostApplyMargin())) {\n\t\t\townTrailingCompactionNeedsRetry = false;\n\t\t\treturn { kind: \"success\", result, cycles: cycle };\n\t\t}\n\n\t\townTrailingCompactionNeedsRetry = true;\n\t\tlastCause = \"effect-not-restored\";\n\t\tdeps.onTransition({ cycle: cycle + 1, from: \"step5\", cause: lastCause });\n\t}\n\n\treturn { kind: \"failed\", reason: \"exhausted-compaction-cycles\", cycles: MAX_CYCLES };\n}\n\nfunction selectCycleParams(\n\tcycle: number,\n\tcause: string,\n\tlastParams: CompactionCycleParams | undefined,\n\tbaseKeepRecent: number,\n): CompactionCycleParams {\n\tif (cycle >= MAX_CYCLES) {\n\t\treturn {\n\t\t\tmodelTier: lastParams?.modelTier ?? \"session\",\n\t\t\tkeepRecentTokens: Math.max(1, Math.floor((lastParams?.keepRecentTokens ?? baseKeepRecent) / 2)),\n\t\t\tchunked: true,\n\t\t\tdeterministicOnly: true,\n\t\t};\n\t}\n\n\tif (!lastParams) {\n\t\treturn {\n\t\t\tmodelTier: \"cheap\",\n\t\t\tkeepRecentTokens: Math.max(1, baseKeepRecent),\n\t\t\tchunked: false,\n\t\t\tdeterministicOnly: false,\n\t\t};\n\t}\n\n\tconst params: CompactionCycleParams = {\n\t\t...lastParams,\n\t\tdeterministicOnly: false,\n\t};\n\n\tif (cause === \"gate-failed\" || cause === \"auth-failed\" || cause === \"length-stop\") {\n\t\tparams.modelTier = \"session\";\n\t} else if (cause === \"input-overflow\") {\n\t\tparams.chunked = true;\n\t} else if (cause === \"effect-not-restored\") {\n\t\tparams.chunked = true;\n\t\tparams.keepRecentTokens = Math.max(1, Math.floor(lastParams.keepRecentTokens / 2));\n\t}\n\n\treturn params;\n}\n\nfunction enforceMonotonicProgress(\n\tparams: CompactionCycleParams,\n\tlastParams: CompactionCycleParams | undefined,\n\tobservedTokens: number,\n\tlastObservedTokens: number | undefined,\n\tlastCause: string,\n): CompactionCycleParams {\n\tif (\n\t\t!lastParams ||\n\t\tparams.deterministicOnly ||\n\t\t(lastObservedTokens !== undefined && observedTokens < lastObservedTokens)\n\t) {\n\t\treturn params;\n\t}\n\tif (!sameParams(params, lastParams)) {\n\t\treturn params;\n\t}\n\tif (lastCause === \"gate-failed\") {\n\t\treturn params;\n\t}\n\n\tconst keepRecentTokens = Math.max(1, Math.floor(params.keepRecentTokens / 2));\n\tif (keepRecentTokens !== params.keepRecentTokens) {\n\t\treturn { ...params, chunked: true, keepRecentTokens };\n\t}\n\tif (!params.chunked) {\n\t\treturn { ...params, chunked: true };\n\t}\n\treturn { ...params, modelTier: params.modelTier === \"cheap\" ? \"session\" : \"cheap\" };\n}\n\nfunction modelKey(model: Model<any>): string {\n\treturn `${model.provider}:${model.id}:${model.api}:${model.baseUrl ?? \"\"}`;\n}\n\nfunction sameParams(a: CompactionCycleParams, b: CompactionCycleParams): boolean {\n\treturn (\n\t\ta.modelTier === b.modelTier &&\n\t\ta.keepRecentTokens === b.keepRecentTokens &&\n\t\ta.chunked === b.chunked &&\n\t\ta.deterministicOnly === b.deterministicOnly\n\t);\n}\n\nfunction mapFailureCause(error: unknown): { cause: string; detail?: string } {\n\tconst message = error instanceof Error ? error.message : typeof error === \"string\" ? error : \"\";\n\tif (message.includes(\"gate-failed\")) return { cause: \"gate-failed\", detail: boundedDetail(message) };\n\tif (message.includes(\"summary-demand-exceeds-reserve\"))\n\t\treturn { cause: \"deterministic-required\", detail: boundedDetail(message) };\n\tif (message.includes(\"input-overflow\")) return { cause: \"input-overflow\", detail: boundedDetail(message) };\n\t// A length-stopped summary lost gated sections; escalating the tier buys a larger output cap.\n\tif (message.includes(\"summary-length-stop\")) return { cause: \"length-stop\", detail: boundedDetail(message) };\n\tif (\n\t\t/stream stalled|overloaded|rate.?limit|too many requests|service.?unavailable|server.?error|network.?error|fetch failed|timeout|timed out/i.test(\n\t\t\tmessage,\n\t\t)\n\t)\n\t\treturn { cause: \"provider-failure\", detail: boundedDetail(message) };\n\tif (message.includes(\"auto-compaction-cancelled\")) return { cause: \"aborted\", detail: boundedDetail(message) };\n\tif (message.includes(\"auth\") || message.includes(\"api key\") || message.includes(\"not compacted\"))\n\t\treturn { cause: \"auth-failed\", detail: boundedDetail(message) };\n\treturn { cause: \"unknown-failure\", detail: boundedDetail(message) };\n}\n\nfunction boundedDetail(message: string): string | undefined {\n\tif (!message) return undefined;\n\treturn message.length > 500 ? `${message.slice(0, 500)}…` : message;\n}\n"]}