@caupulican/pi-agent-core 0.90.2 → 0.90.5

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 (33) hide show
  1. package/dist/agent-loop.d.ts.map +1 -1
  2. package/dist/agent-loop.js +9 -3
  3. package/dist/agent-loop.js.map +1 -1
  4. package/dist/compaction/compaction.d.ts +3 -0
  5. package/dist/compaction/compaction.d.ts.map +1 -1
  6. package/dist/compaction/compaction.js +90 -67
  7. package/dist/compaction/compaction.js.map +1 -1
  8. package/dist/compaction/extraction.d.ts +5 -0
  9. package/dist/compaction/extraction.d.ts.map +1 -1
  10. package/dist/compaction/extraction.js +48 -2
  11. package/dist/compaction/extraction.js.map +1 -1
  12. package/dist/compaction/utils.d.ts +1 -1
  13. package/dist/compaction/utils.d.ts.map +1 -1
  14. package/dist/compaction/utils.js +2 -0
  15. package/dist/compaction/utils.js.map +1 -1
  16. package/dist/compaction/verification.d.ts +1 -1
  17. package/dist/compaction/verification.d.ts.map +1 -1
  18. package/dist/compaction/verification.js +23 -17
  19. package/dist/compaction/verification.js.map +1 -1
  20. package/dist/provider-tool-projection.d.ts.map +1 -1
  21. package/dist/provider-tool-projection.js +28 -1
  22. package/dist/provider-tool-projection.js.map +1 -1
  23. package/dist/tool-failure-memory.d.ts +1 -0
  24. package/dist/tool-failure-memory.d.ts.map +1 -1
  25. package/dist/tool-failure-memory.js +56 -13
  26. package/dist/tool-failure-memory.js.map +1 -1
  27. package/dist/tool-failure-recovery-gate.d.ts +7 -1
  28. package/dist/tool-failure-recovery-gate.d.ts.map +1 -1
  29. package/dist/tool-failure-recovery-gate.js +19 -3
  30. package/dist/tool-failure-recovery-gate.js.map +1 -1
  31. package/dist/tool-failure-recovery-protocol.js +5 -5
  32. package/dist/tool-failure-recovery-protocol.js.map +1 -1
  33. package/package.json +2 -2
@@ -8,10 +8,23 @@ import { completeSimple } from "@caupulican/pi-ai/stream";
8
8
  import { uuidv7 } from "@caupulican/pi-ai/uuid";
9
9
  import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
10
10
  import { buildSessionContext } from "../session/session-manager.js";
11
- import { addUsage, combineUsage, createEmptyUsage } from "../usage.js";
12
- import { extractCompactionFacts, renderFactsBlock } from "./extraction.js";
11
+ import { addUsage, createEmptyUsage } from "../usage.js";
12
+ import { ACTIVE_TASK_SOURCE_MAX_CHARS, extractCompactionFacts, mergePersistentCompactionUserFacts, renderFactsBlock, } from "./extraction.js";
13
13
  import { addPersistedFileOperations, computeFileLists, createFileOps, extractFileOpsFromMessage, isPlainRecord, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
14
14
  import { buildRetryPrompt, CompactionVerificationError, deterministicallyFillSummaryGaps, isCompactionSummaryStructurallyUsable, verifySummary, } from "./verification.js";
15
+ function readPersistedCompactionUserFacts(details) {
16
+ if (!isPlainRecord(details))
17
+ return undefined;
18
+ if (typeof details.activeTaskSource !== "string")
19
+ return undefined;
20
+ if (!Array.isArray(details.prohibitions) || !details.prohibitions.every((rule) => typeof rule === "string")) {
21
+ return undefined;
22
+ }
23
+ return {
24
+ activeTaskSource: details.activeTaskSource.slice(0, ACTIVE_TASK_SOURCE_MAX_CHARS),
25
+ prohibitions: [...details.prohibitions],
26
+ };
27
+ }
15
28
  /**
16
29
  * Extract file operations from messages and previous compaction entries.
17
30
  */
@@ -724,10 +737,12 @@ export function prepareCompaction(pathEntries, settings, options) {
724
737
  }
725
738
  }
726
739
  let previousSummary;
740
+ let previousUserFacts;
727
741
  let boundaryStart = 0;
728
742
  if (prevCompactionIndex >= 0) {
729
743
  const prevCompaction = pathEntries[prevCompactionIndex];
730
744
  previousSummary = prevCompaction.summary;
745
+ previousUserFacts = readPersistedCompactionUserFacts(prevCompaction.details);
731
746
  const firstKeptEntryIndex = pathEntries.findIndex((entry) => entry.id === prevCompaction.firstKeptEntryId);
732
747
  boundaryStart = firstKeptEntryIndex >= 0 ? firstKeptEntryIndex : prevCompactionIndex + 1;
733
748
  }
@@ -767,7 +782,13 @@ export function prepareCompaction(pathEntries, settings, options) {
767
782
  extractFileOpsFromMessage(msg, fileOps);
768
783
  }
769
784
  }
770
- const facts = extractCompactionFacts(pathEntries, boundaryStart, boundaryEnd);
785
+ let facts = extractCompactionFacts(pathEntries, boundaryStart, boundaryEnd);
786
+ if (prevCompactionIndex >= 0) {
787
+ // Older checkpoints did not persist these exact facts. Pay one linear migration scan, then
788
+ // persist the result below so later compactions stay incremental instead of becoming quadratic.
789
+ const persistedUserFacts = previousUserFacts ?? extractCompactionFacts(pathEntries, 0, boundaryEnd);
790
+ facts = mergePersistentCompactionUserFacts(facts, persistedUserFacts);
791
+ }
771
792
  return {
772
793
  firstKeptEntryId,
773
794
  messagesToSummarize,
@@ -780,21 +801,6 @@ export function prepareCompaction(pathEntries, settings, options) {
780
801
  settings,
781
802
  };
782
803
  }
783
- // ============================================================================
784
- // Main compaction function
785
- // ============================================================================
786
- const TURN_PREFIX_SUMMARIZATION_PROMPT = `Summarize discarded early part of split turn; recent suffix remains.
787
-
788
- ## Original Request
789
- [User request]
790
-
791
- ## Early Progress
792
- - [Decisions/work]
793
-
794
- ## Context for Suffix
795
- - [Facts needed by retained work]
796
-
797
- Only facts needed to understand suffix.`;
798
804
  async function generateVerifiedSummary(options) {
799
805
  let retryInstructions = options.customInstructions;
800
806
  const usage = createEmptyUsage();
@@ -805,11 +811,8 @@ async function generateVerifiedSummary(options) {
805
811
  addUsage(usage, generated.usage);
806
812
  const summary = generated.text;
807
813
  const verification = verifySummary(summary, options.facts);
808
- if (verification.ok) {
809
- return { summary, usage, verification, verificationGateFailures, deterministicGapFills: 0 };
810
- }
811
- verificationGateFailures.push(verification);
812
814
  if (!isCompactionSummaryStructurallyUsable(summary)) {
815
+ verificationGateFailures.push(verification);
813
816
  if (attempt >= 1)
814
817
  throw new CompactionVerificationError(verificationGateFailures);
815
818
  retryInstructions = buildRetryPrompt(verification, summary);
@@ -817,6 +820,8 @@ async function generateVerifiedSummary(options) {
817
820
  }
818
821
  const filled = deterministicallyFillSummaryGaps(summary, options.facts);
819
822
  if (filled.verification.ok) {
823
+ if (!verification.ok)
824
+ verificationGateFailures.push(verification);
820
825
  return {
821
826
  summary: filled.summary,
822
827
  usage,
@@ -825,6 +830,7 @@ async function generateVerifiedSummary(options) {
825
830
  deterministicGapFills: filled.changed ? 1 : 0,
826
831
  };
827
832
  }
833
+ verificationGateFailures.push(verification);
828
834
  throw new CompactionVerificationError(verificationGateFailures);
829
835
  }
830
836
  throw new CompactionVerificationError(verificationGateFailures);
@@ -849,31 +855,27 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
849
855
  delegatedWorkerFacts: [],
850
856
  };
851
857
  const factsBlock = renderFactsBlock(facts);
852
- const verified = isSplitTurn && messagesToSummarize.length === 0
853
- ? undefined
854
- : await generateVerifiedSummary({
855
- messages: messagesToSummarize,
856
- model,
857
- reserveTokens: settings.reserveTokens,
858
- apiKey,
859
- headers,
860
- signal,
861
- customInstructions,
862
- previousSummary,
863
- thinkingLevel,
864
- streamFn,
865
- completion: executionOptions?.completion,
866
- preDigest,
867
- facts,
868
- factsBlock,
869
- chunked: executionOptions?.chunked ?? false,
870
- });
871
- let summary = verified?.summary ?? "No prior history.";
872
- let summaryUsage = verified?.usage;
858
+ const verified = await generateVerifiedSummary({
859
+ messages: isSplitTurn && messagesToSummarize.length === 0 ? turnPrefixMessages : messagesToSummarize,
860
+ model,
861
+ reserveTokens: settings.reserveTokens,
862
+ apiKey,
863
+ headers,
864
+ signal,
865
+ customInstructions,
866
+ previousSummary,
867
+ thinkingLevel,
868
+ streamFn,
869
+ completion: executionOptions?.completion,
870
+ preDigest,
871
+ facts,
872
+ factsBlock,
873
+ chunked: executionOptions?.chunked ?? false,
874
+ });
875
+ let summary = verified.summary;
876
+ const summaryUsage = verified.usage;
873
877
  if (isSplitTurn) {
874
- const turnPrefix = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn, executionOptions?.completion);
875
- summary = `${summary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefix.text}`;
876
- summaryUsage = combineUsage(summaryUsage, turnPrefix.usage);
878
+ summary = `${summary}\n\n---\n\n**Turn Context (split turn):**\n\n${createTurnPrefixSummary(turnPrefixMessages)}`;
877
879
  }
878
880
  const { readFiles, modifiedFiles } = computeFileLists(fileOps);
879
881
  if (!firstKeptEntryId) {
@@ -887,13 +889,15 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
887
889
  details: {
888
890
  readFiles,
889
891
  modifiedFiles,
892
+ activeTaskSource: facts.activeTaskSource,
893
+ prohibitions: [...facts.prohibitions],
890
894
  verificationGateFailures: 0,
891
- deterministicGapFills: verified?.deterministicGapFills ?? 0,
895
+ deterministicGapFills: verified.deterministicGapFills,
892
896
  },
893
- verification: verified?.verification,
897
+ verification: verified.verification,
894
898
  verificationGateFailures: [],
895
- deterministicGapFills: verified?.deterministicGapFills ?? 0,
896
- }, verified?.verificationGateFailures ?? []);
899
+ deterministicGapFills: verified.deterministicGapFills,
900
+ }, verified.verificationGateFailures);
897
901
  }
898
902
  export function createDeterministicCompaction(preparation) {
899
903
  const { firstKeptEntryId, tokensBefore, fileOps, facts } = preparation;
@@ -964,24 +968,43 @@ export function createDeterministicCompaction(preparation) {
964
968
  summary,
965
969
  firstKeptEntryId,
966
970
  tokensBefore,
967
- details: { readFiles, modifiedFiles, verificationGateFailures: 0, deterministicGapFills: 0 },
971
+ details: {
972
+ readFiles,
973
+ modifiedFiles,
974
+ activeTaskSource: facts?.activeTaskSource ?? "",
975
+ prohibitions: [...(facts?.prohibitions ?? [])],
976
+ verificationGateFailures: 0,
977
+ deterministicGapFills: 0,
978
+ },
968
979
  };
969
980
  }
970
- /**
971
- * Generate a summary for a turn prefix (when splitting a turn).
972
- */
973
- async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion) {
974
- const maxTokens = Math.min(Math.floor(0.5 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY); // Smaller budget for turn prefix
975
- const llmMessages = convertToLlm(messages);
976
- const conversationText = serializeConversation(llmMessages);
977
- const promptText = `CHAT\n${conversationText}\n\nTASK\n${TURN_PREFIX_SUMMARIZATION_PROMPT}`;
978
- const response = await completeSummarizationPrompt(promptText, model, maxTokens, apiKey, headers, signal, thinkingLevel, streamFn, completion, undefined, "Turn prefix summarization");
979
- return {
980
- text: response.content
981
- .filter((c) => c.type === "text")
982
- .map((c) => c.text)
983
- .join("\n"),
984
- usage: response.usage,
985
- };
981
+ /** Split-turn context is intentionally mechanical: user intent is copied, never summarized. */
982
+ function createTurnPrefixSummary(messages) {
983
+ let originalRequest = "";
984
+ for (const message of messages) {
985
+ if (message.role !== "user")
986
+ continue;
987
+ if (typeof message.content === "string") {
988
+ originalRequest = message.content;
989
+ }
990
+ else {
991
+ originalRequest = message.content
992
+ .filter((block) => block.type === "text")
993
+ .map((block) => block.text)
994
+ .join("");
995
+ }
996
+ break;
997
+ }
998
+ const boundedRequest = originalRequest.slice(0, ACTIVE_TASK_SOURCE_MAX_CHARS) || "(none)";
999
+ return [
1000
+ "## Original Request",
1001
+ boundedRequest,
1002
+ "",
1003
+ "## Early Progress",
1004
+ `- ${messages.length} earlier message(s) compacted; durable facts are in the main checkpoint.`,
1005
+ "",
1006
+ "## Context for Suffix",
1007
+ "- Retained messages continue the original request.",
1008
+ ].join("\n");
986
1009
  }
987
1010
  //# sourceMappingURL=compaction.js.map