@caupulican/pi-agent-core 0.81.41 → 0.81.43

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 (93) hide show
  1. package/dist/agent-loop.d.ts +23 -2
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +189 -42
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +15 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +9 -38
  8. package/dist/agent.js.map +1 -1
  9. package/dist/compaction/branch-summarization.d.ts +1 -1
  10. package/dist/compaction/branch-summarization.d.ts.map +1 -1
  11. package/dist/compaction/branch-summarization.js +3 -13
  12. package/dist/compaction/branch-summarization.js.map +1 -1
  13. package/dist/compaction/compaction.d.ts +6 -3
  14. package/dist/compaction/compaction.d.ts.map +1 -1
  15. package/dist/compaction/compaction.js +40 -68
  16. package/dist/compaction/compaction.js.map +1 -1
  17. package/dist/compaction/extraction.d.ts.map +1 -1
  18. package/dist/compaction/extraction.js +1 -6
  19. package/dist/compaction/extraction.js.map +1 -1
  20. package/dist/compaction/index.d.ts.map +1 -1
  21. package/dist/compaction/loop.d.ts.map +1 -1
  22. package/dist/compaction/loop.js.map +1 -1
  23. package/dist/compaction/token-budget.d.ts.map +1 -1
  24. package/dist/compaction/token-budget.js +7 -5
  25. package/dist/compaction/token-budget.js.map +1 -1
  26. package/dist/compaction/utils.d.ts +5 -0
  27. package/dist/compaction/utils.d.ts.map +1 -1
  28. package/dist/compaction/utils.js +20 -0
  29. package/dist/compaction/utils.js.map +1 -1
  30. package/dist/compaction/verification.d.ts.map +1 -1
  31. package/dist/compaction/verification.js +6 -5
  32. package/dist/compaction/verification.js.map +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/messages.d.ts.map +1 -1
  35. package/dist/messages.js.map +1 -1
  36. package/dist/node.d.ts.map +1 -1
  37. package/dist/proxy.d.ts +3 -2
  38. package/dist/proxy.d.ts.map +1 -1
  39. package/dist/proxy.js +88 -56
  40. package/dist/proxy.js.map +1 -1
  41. package/dist/reliability/classifier.d.ts.map +1 -1
  42. package/dist/reliability/classifier.js.map +1 -1
  43. package/dist/reliability/index.d.ts.map +1 -1
  44. package/dist/reliability/node.d.ts.map +1 -1
  45. package/dist/reliability/process-tree.d.ts.map +1 -1
  46. package/dist/reliability/process-tree.js.map +1 -1
  47. package/dist/reliability/provider-signatures.d.ts.map +1 -1
  48. package/dist/reliability/retry-controller.d.ts +1 -1
  49. package/dist/reliability/retry-controller.d.ts.map +1 -1
  50. package/dist/reliability/retry-controller.js +2 -7
  51. package/dist/reliability/retry-controller.js.map +1 -1
  52. package/dist/reliability/retry.d.ts.map +1 -1
  53. package/dist/reliability/retry.js.map +1 -1
  54. package/dist/reliability/watchdogs.d.ts.map +1 -1
  55. package/dist/reliability/watchdogs.js +24 -41
  56. package/dist/reliability/watchdogs.js.map +1 -1
  57. package/dist/session/message-retention.d.ts.map +1 -1
  58. package/dist/session/message-retention.js.map +1 -1
  59. package/dist/session/session-manager.d.ts +1 -1
  60. package/dist/session/session-manager.d.ts.map +1 -1
  61. package/dist/session/session-manager.js +12 -22
  62. package/dist/session/session-manager.js.map +1 -1
  63. package/dist/tool-failure-memory.d.ts +6 -0
  64. package/dist/tool-failure-memory.d.ts.map +1 -1
  65. package/dist/tool-failure-memory.js +78 -20
  66. package/dist/tool-failure-memory.js.map +1 -1
  67. package/dist/types.d.ts +73 -1
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js.map +1 -1
  70. package/dist/usage.d.ts +3 -2
  71. package/dist/usage.d.ts.map +1 -1
  72. package/dist/usage.js +2 -10
  73. package/dist/usage.js.map +1 -1
  74. package/dist/utils/paths.d.ts.map +1 -1
  75. package/dist/utils/paths.js.map +1 -1
  76. package/dist/utils/shell-output.d.ts.map +1 -1
  77. package/dist/utils/shell-output.js.map +1 -1
  78. package/dist/utils/streaming-content.d.ts +23 -0
  79. package/dist/utils/streaming-content.d.ts.map +1 -0
  80. package/dist/utils/streaming-content.js +52 -0
  81. package/dist/utils/streaming-content.js.map +1 -0
  82. package/dist/utils/truncate.d.ts.map +1 -1
  83. package/dist/utils/truncate.js +55 -80
  84. package/dist/utils/truncate.js.map +1 -1
  85. package/dist/uuid.d.ts +1 -1
  86. package/dist/uuid.d.ts.map +1 -1
  87. package/dist/uuid.js +1 -1
  88. package/dist/uuid.js.map +1 -1
  89. package/package.json +6 -6
  90. package/dist/utils/streaming-lines.d.ts +0 -20
  91. package/dist/utils/streaming-lines.d.ts.map +0 -1
  92. package/dist/utils/streaming-lines.js +0 -79
  93. package/dist/utils/streaming-lines.js.map +0 -1
@@ -4,12 +4,13 @@
4
4
  * Pure functions for compaction logic. The session manager handles I/O,
5
5
  * and after compaction the session is reloaded.
6
6
  */
7
- import { completeSimple, uuidv7 } from "@caupulican/pi-ai";
7
+ import { completeSimple } from "@caupulican/pi-ai/stream";
8
+ import { uuidv7 } from "@caupulican/pi-ai/uuid";
8
9
  import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
9
10
  import { buildSessionContext } from "../session/session-manager.js";
10
11
  import { addUsage, combineUsage, createEmptyUsage } from "../usage.js";
11
12
  import { extractCompactionFacts, renderFactsBlock } from "./extraction.js";
12
- import { computeFileLists, createFileOps, extractFileOpsFromMessage, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
13
+ import { addPersistedFileOperations, computeFileLists, createFileOps, extractFileOpsFromMessage, isPlainRecord, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
13
14
  import { buildRetryPrompt, CompactionVerificationError, deterministicallyFillSummaryGaps, isCompactionSummaryStructurallyUsable, verifySummary, } from "./verification.js";
14
15
  /**
15
16
  * Extract file operations from messages and previous compaction entries.
@@ -21,15 +22,7 @@ function extractFileOperations(messages, entries, prevCompactionIndex) {
21
22
  const prevCompaction = entries[prevCompactionIndex];
22
23
  if (!prevCompaction.fromHook && prevCompaction.details) {
23
24
  // fromHook field kept for session file compatibility
24
- const details = prevCompaction.details;
25
- if (Array.isArray(details.readFiles)) {
26
- for (const f of details.readFiles)
27
- fileOps.read.add(f);
28
- }
29
- if (Array.isArray(details.modifiedFiles)) {
30
- for (const f of details.modifiedFiles)
31
- fileOps.edited.add(f);
32
- }
25
+ addPersistedFileOperations(fileOps, prevCompaction.details);
33
26
  }
34
27
  }
35
28
  // Extract from tool calls in messages
@@ -94,12 +87,6 @@ function cloneVerificationReport(report) {
94
87
  failures: report.failures.map((failure) => ({ ...failure })),
95
88
  };
96
89
  }
97
- function isPlainRecord(value) {
98
- if (!value || typeof value !== "object" || Array.isArray(value))
99
- return false;
100
- const prototype = Object.getPrototypeOf(value);
101
- return prototype === Object.prototype || prototype === null;
102
- }
103
90
  function aggregateVerificationChecks(reports) {
104
91
  const checks = new Map();
105
92
  for (const report of reports) {
@@ -124,7 +111,7 @@ export const DEFAULT_COMPACTION_SETTINGS = {
124
111
  enabled: true,
125
112
  reserveTokens: 16384,
126
113
  keepRecentTokens: 20000,
127
- triggerPercent: 0.7,
114
+ triggerPercent: 0.6,
128
115
  };
129
116
  // ============================================================================
130
117
  // Token calculation
@@ -501,13 +488,33 @@ function createSummarizationOptions(model, maxTokens, apiKey, headers, signal, t
501
488
  }
502
489
  return options;
503
490
  }
504
- async function completeSummarization(model, context, options, streamFn) {
491
+ async function completeSummarization(model, context, options, streamFn, completion) {
492
+ if (completion)
493
+ return completion(model, context, options);
505
494
  if (!streamFn) {
506
495
  return completeSimple(model, context, options);
507
496
  }
508
497
  const stream = await streamFn(model, context, options);
509
498
  return stream.result();
510
499
  }
500
+ async function completeSummarizationPrompt(promptText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion, usage, failureLabel = "Summarization") {
501
+ const response = await completeSummarization(model, {
502
+ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
503
+ messages: [
504
+ {
505
+ role: "user",
506
+ content: [{ type: "text", text: promptText }],
507
+ timestamp: Date.now(),
508
+ },
509
+ ],
510
+ }, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn, completion);
511
+ if (usage)
512
+ addUsage(usage, response.usage);
513
+ if (response.stopReason === "error") {
514
+ throw new Error(`${failureLabel} failed: ${response.errorMessage || "Unknown error"}`);
515
+ }
516
+ return response;
517
+ }
511
518
  /**
512
519
  * Serialize messages to conversation text and, if a `preDigest` callback is supplied, run it
513
520
  * through that pass (a cheaper curation-model call that compresses older chunks — see
@@ -540,10 +547,10 @@ async function prepareSummarizationConversationText(currentMessages, preDigest,
540
547
  * the same message span across multiple attempts (a verification-gate retry), compute this once
541
548
  * via {@link prepareSummarizationConversationText} and pass it to every attempt.
542
549
  */
543
- export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText) {
544
- return (await generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock, chunked, precomputedConversationText)).text;
550
+ export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText, completion) {
551
+ return (await generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock, chunked, precomputedConversationText, completion)).text;
545
552
  }
546
- export async function generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText) {
553
+ export async function generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, preDigest, factsBlock = "verification demands:\nfiles-modified-recall (must appear in ## Files):\nfiles-read-recall (must appear as exact paths in ## Files, path recall threshold applies):\nworking-set-recall (must appear in ## Working Set):\nopen-errors-recall (must appear in ## Open Problems):\nactions-recall (must appear in ## Done):\nmandatory-rules-recall (must appear in ### Mandatory Rules):\nactive-task-containment (must appear in ## Active Task):\ncancelled-work-dropped (must NOT appear outside ### Mandatory Rules):", chunked = false, precomputedConversationText, completion) {
547
554
  const usage = createEmptyUsage();
548
555
  const summaryBudget = getSummaryBudget(reserveTokens, model, factsBlock);
549
556
  const maxTokens = summaryBudget;
@@ -560,26 +567,13 @@ export async function generateSummaryWithUsage(currentMessages, model, reserveTo
560
567
  if (!chunked) {
561
568
  throw new Error("input-overflow: summarization request exceeds summarizer window");
562
569
  }
563
- conversationText = await summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage);
570
+ conversationText = await summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage, completion);
564
571
  }
565
572
  const promptText = buildSummarizationPrompt(conversationText, previousSummary, promptSuffix);
566
573
  if (estimateStringTokens(promptText) > inputBound) {
567
574
  throw new Error("input-overflow: chunked summarization merge still exceeds summarizer window");
568
575
  }
569
- const response = await completeSummarization(model, {
570
- systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
571
- messages: [
572
- {
573
- role: "user",
574
- content: [{ type: "text", text: promptText }],
575
- timestamp: Date.now(),
576
- },
577
- ],
578
- }, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn);
579
- addUsage(usage, response.usage);
580
- if (response.stopReason === "error") {
581
- throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
582
- }
576
+ const response = await completeSummarizationPrompt(promptText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion, usage);
583
577
  // A length-stopped checkpoint silently lost its tail sections — gating it as if complete
584
578
  // guarantees a verification failure. Fail loudly so the compaction ladder escalates instead.
585
579
  if (response.stopReason === "length") {
@@ -647,10 +641,10 @@ export function getChunkSummarizationTokenBudget(inputBound) {
647
641
  export function buildChunkSummarizationPrompt(chunk, index, total) {
648
642
  return `<conversation-chunk index="${index}" total="${total}">\n${chunk}\n</conversation-chunk>\n\nSummarize this chunk for a later checkpoint merge. Preserve exact file paths, commands, errors, user prohibitions, and active work. Output concise notes only.`;
649
643
  }
650
- async function summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage) {
644
+ async function summarizeChunks(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, previousSummary, promptSuffix, usage, completion) {
651
645
  let reducedText = conversationText;
652
646
  for (let pass = 0; pass < 3; pass++) {
653
- const summary = await summarizeChunkPass(reducedText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage);
647
+ const summary = await summarizeChunkPass(reducedText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage, completion);
654
648
  if (estimateStringTokens(buildSummarizationPrompt(summary, previousSummary, promptSuffix)) <= inputBound) {
655
649
  return summary;
656
650
  }
@@ -658,27 +652,14 @@ async function summarizeChunks(conversationText, model, maxTokens, apiKey, heade
658
652
  }
659
653
  throw new Error("input-overflow: chunked summarization merge still exceeds summarizer window");
660
654
  }
661
- async function summarizeChunkPass(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage) {
655
+ async function summarizeChunkPass(conversationText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, inputBound, usage, completion) {
662
656
  const maxChunkTokens = getChunkSummarizationTokenBudget(inputBound);
663
657
  const maxChunkChars = Math.max(1, maxChunkTokens * 4);
664
658
  const chunks = splitText(conversationText, maxChunkChars);
665
659
  const summaries = [];
666
660
  for (let i = 0; i < chunks.length; i++) {
667
661
  const promptText = buildChunkSummarizationPrompt(chunks[i], i + 1, chunks.length);
668
- const response = await completeSummarization(model, {
669
- systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
670
- messages: [
671
- {
672
- role: "user",
673
- content: [{ type: "text", text: promptText }],
674
- timestamp: Date.now(),
675
- },
676
- ],
677
- }, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn);
678
- addUsage(usage, response.usage);
679
- if (response.stopReason === "error") {
680
- throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
681
- }
662
+ const response = await completeSummarizationPrompt(promptText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion, usage);
682
663
  summaries.push(extractTextContent(response));
683
664
  }
684
665
  return summaries.join("\n\n");
@@ -829,7 +810,7 @@ async function generateVerifiedSummary(options) {
829
810
  const verificationGateFailures = [];
830
811
  const precomputedConversationText = await prepareSummarizationConversationText(options.messages, options.preDigest, options.signal);
831
812
  for (let attempt = 0; attempt < 2; attempt++) {
832
- const generated = await generateSummaryWithUsage(options.messages, options.model, options.reserveTokens, options.apiKey, options.headers, options.signal, retryInstructions, options.previousSummary, options.thinkingLevel, options.streamFn, options.preDigest, options.factsBlock, options.chunked, precomputedConversationText);
813
+ const generated = await generateSummaryWithUsage(options.messages, options.model, options.reserveTokens, options.apiKey, options.headers, options.signal, retryInstructions, options.previousSummary, options.thinkingLevel, options.streamFn, options.preDigest, options.factsBlock, options.chunked, precomputedConversationText, options.completion);
833
814
  addUsage(usage, generated.usage);
834
815
  const summary = generated.text;
835
816
  const verification = verifySummary(summary, options.facts);
@@ -890,6 +871,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
890
871
  previousSummary,
891
872
  thinkingLevel,
892
873
  streamFn,
874
+ completion: executionOptions?.completion,
893
875
  preDigest,
894
876
  facts,
895
877
  factsBlock,
@@ -898,7 +880,7 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
898
880
  let summary = verified?.summary ?? "No prior history.";
899
881
  let summaryUsage = verified?.usage;
900
882
  if (isSplitTurn) {
901
- const turnPrefix = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn);
883
+ const turnPrefix = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn, executionOptions?.completion);
902
884
  summary = `${summary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefix.text}`;
903
885
  summaryUsage = combineUsage(summaryUsage, turnPrefix.usage);
904
886
  }
@@ -997,22 +979,12 @@ export function createDeterministicCompaction(preparation) {
997
979
  /**
998
980
  * Generate a summary for a turn prefix (when splitting a turn).
999
981
  */
1000
- async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn) {
982
+ async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion) {
1001
983
  const maxTokens = Math.min(Math.floor(0.5 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY); // Smaller budget for turn prefix
1002
984
  const llmMessages = convertToLlm(messages);
1003
985
  const conversationText = serializeConversation(llmMessages);
1004
986
  const promptText = `<conversation>\n${conversationText}\n</conversation>\n\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;
1005
- const summarizationMessages = [
1006
- {
1007
- role: "user",
1008
- content: [{ type: "text", text: promptText }],
1009
- timestamp: Date.now(),
1010
- },
1011
- ];
1012
- const response = await completeSummarization(model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel), streamFn);
1013
- if (response.stopReason === "error") {
1014
- throw new Error(`Turn prefix summarization failed: ${response.errorMessage || "Unknown error"}`);
1015
- }
987
+ const response = await completeSummarizationPrompt(promptText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion, undefined, "Turn prefix summarization");
1016
988
  return {
1017
989
  text: response.content
1018
990
  .filter((c) => c.type === "text")