@ponythewhite/base-context 1.0.7 → 1.0.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/base-context-runtime/pyproject.toml +1 -1
  3. package/dist/base-context-runtime/src/rlm/__init__.py +3 -0
  4. package/dist/base-context-runtime/src/rlm/harness.py +86 -48
  5. package/dist/base-context-runtime/test/test_harness.py +215 -140
  6. package/dist/base-context-runtime/uv.lock +1 -1
  7. package/dist/build-info.json +1 -1
  8. package/dist/bundle/amazon-bedrock.js +3 -3
  9. package/dist/bundle/{anthropic-UV5GZDGU.js → anthropic-5PSCZEER.js} +2 -7
  10. package/dist/bundle/{azure-openai-responses-WSNPXLYC.js → azure-openai-responses-AYTLJR33.js} +2 -2
  11. package/dist/bundle/{bundled-modules-PS66L3IT.js → bundled-modules-5JRM6P4T.js} +5 -5
  12. package/dist/bundle/{chunk-UPVDERMD.js → chunk-2IPDSTDR.js} +11 -9
  13. package/dist/bundle/chunk-D3KOF57W.js +22040 -0
  14. package/dist/bundle/{chunk-VSIYFWYI.js → chunk-KT2BNEPE.js} +21 -8
  15. package/dist/bundle/{chunk-VCW3YDFF.js → chunk-NEHLB7JV.js} +1548 -574
  16. package/dist/bundle/{chunk-KTNETTHI.js → chunk-Q3MBSBEB.js} +3 -101
  17. package/dist/bundle/{chunk-LFPWHI42.js → chunk-QKIFBA7N.js} +2 -2
  18. package/dist/bundle/{chunk-PFROC4EV.js → chunk-VVI3UKGR.js} +12 -9
  19. package/dist/bundle/{cli-main-MMHTK7EB.js → cli-main-YUDBEYE2.js} +4 -4
  20. package/dist/bundle/cli.js +1 -1
  21. package/dist/bundle/{google-T32FMD4G.js → google-HTMCUJ7X.js} +2 -2
  22. package/dist/bundle/{google-vertex-56KP6I3B.js → google-vertex-ZQO65AXF.js} +2 -2
  23. package/dist/bundle/{main-XAIVH4Y7.js → main-ZLGGAAGU.js} +5 -5
  24. package/dist/bundle/{mistral-WGBMYIVU.js → mistral-SB7LU56G.js} +1 -1
  25. package/dist/bundle/{openai-codex-responses-PIV2RQ3G.js → openai-codex-responses-5CEW4VN3.js} +53 -27
  26. package/dist/bundle/{openai-completions-64C3JPR4.js → openai-completions-BAZJQSS2.js} +1 -1
  27. package/dist/bundle/{openai-responses-UI7RSTDV.js → openai-responses-VHOE427P.js} +48 -5
  28. package/dist/core/agent-session.d.ts +9 -1
  29. package/dist/core/agent-session.d.ts.map +1 -1
  30. package/dist/core/agent-session.js +225 -29
  31. package/dist/core/agent-session.js.map +1 -1
  32. package/dist/core/canonical-context.d.ts +8 -0
  33. package/dist/core/canonical-context.d.ts.map +1 -1
  34. package/dist/core/canonical-context.js +37 -5
  35. package/dist/core/canonical-context.js.map +1 -1
  36. package/dist/core/compaction/compaction.d.ts +2 -2
  37. package/dist/core/compaction/compaction.d.ts.map +1 -1
  38. package/dist/core/compaction/compaction.js +31 -13
  39. package/dist/core/compaction/compaction.js.map +1 -1
  40. package/dist/core/context-epoch.d.ts +1 -1
  41. package/dist/core/context-epoch.d.ts.map +1 -1
  42. package/dist/core/context-epoch.js +2 -1
  43. package/dist/core/context-epoch.js.map +1 -1
  44. package/dist/core/extensions/types.d.ts +2 -2
  45. package/dist/core/extensions/types.d.ts.map +1 -1
  46. package/dist/core/extensions/types.js.map +1 -1
  47. package/dist/core/history-index-worker.d.ts.map +1 -1
  48. package/dist/core/history-index-worker.js +29 -8
  49. package/dist/core/history-index-worker.js.map +1 -1
  50. package/dist/core/history-index.d.ts +11 -1
  51. package/dist/core/history-index.d.ts.map +1 -1
  52. package/dist/core/history-index.js +2 -1
  53. package/dist/core/history-index.js.map +1 -1
  54. package/dist/core/inference-coordinator.d.ts +1 -1
  55. package/dist/core/inference-coordinator.d.ts.map +1 -1
  56. package/dist/core/inference-coordinator.js +110 -15
  57. package/dist/core/inference-coordinator.js.map +1 -1
  58. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  59. package/dist/core/kernel/bootstrap.js +97 -107
  60. package/dist/core/kernel/bootstrap.js.map +1 -1
  61. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  62. package/dist/core/kernel/repl-manager.js +11 -0
  63. package/dist/core/kernel/repl-manager.js.map +1 -1
  64. package/dist/core/kernel/shared.d.ts +2 -0
  65. package/dist/core/kernel/shared.d.ts.map +1 -1
  66. package/dist/core/kernel/shared.js.map +1 -1
  67. package/dist/core/messages.d.ts.map +1 -1
  68. package/dist/core/messages.js +4 -1
  69. package/dist/core/messages.js.map +1 -1
  70. package/dist/core/prompts/rlm.d.ts.map +1 -1
  71. package/dist/core/prompts/rlm.js +4 -2
  72. package/dist/core/prompts/rlm.js.map +1 -1
  73. package/dist/core/public-context.d.ts.map +1 -1
  74. package/dist/core/public-context.js +16 -0
  75. package/dist/core/public-context.js.map +1 -1
  76. package/dist/core/refinement/refinement.d.ts +19 -5
  77. package/dist/core/refinement/refinement.d.ts.map +1 -1
  78. package/dist/core/refinement/refinement.js +90 -35
  79. package/dist/core/refinement/refinement.js.map +1 -1
  80. package/dist/core/request-view-selection.d.ts +13 -3
  81. package/dist/core/request-view-selection.d.ts.map +1 -1
  82. package/dist/core/request-view-selection.js +239 -101
  83. package/dist/core/request-view-selection.js.map +1 -1
  84. package/dist/core/retained-tool-output.d.ts +28 -0
  85. package/dist/core/retained-tool-output.d.ts.map +1 -0
  86. package/dist/core/retained-tool-output.js +102 -0
  87. package/dist/core/retained-tool-output.js.map +1 -0
  88. package/dist/core/selective-recovery.d.ts +47 -3
  89. package/dist/core/selective-recovery.d.ts.map +1 -1
  90. package/dist/core/selective-recovery.js +428 -109
  91. package/dist/core/selective-recovery.js.map +1 -1
  92. package/dist/core/session-history-index.d.ts +3 -1
  93. package/dist/core/session-history-index.d.ts.map +1 -1
  94. package/dist/core/session-history-index.js +1 -1
  95. package/dist/core/session-history-index.js.map +1 -1
  96. package/dist/core/session-manager.d.ts +2 -0
  97. package/dist/core/session-manager.d.ts.map +1 -1
  98. package/dist/core/session-manager.js +38 -13
  99. package/dist/core/session-manager.js.map +1 -1
  100. package/dist/core/settings-manager.d.ts +5 -0
  101. package/dist/core/settings-manager.d.ts.map +1 -1
  102. package/dist/core/settings-manager.js +3 -0
  103. package/dist/core/settings-manager.js.map +1 -1
  104. package/dist/core/system-prompt.d.ts +3 -1
  105. package/dist/core/system-prompt.d.ts.map +1 -1
  106. package/dist/core/system-prompt.js +3 -3
  107. package/dist/core/system-prompt.js.map +1 -1
  108. package/dist/core/task-frame.d.ts +1 -1
  109. package/dist/core/task-frame.d.ts.map +1 -1
  110. package/dist/core/task-frame.js +21 -1
  111. package/dist/core/task-frame.js.map +1 -1
  112. package/dist/core/task-state-reader.d.ts +10 -2
  113. package/dist/core/task-state-reader.d.ts.map +1 -1
  114. package/dist/core/task-state-reader.js +75 -9
  115. package/dist/core/task-state-reader.js.map +1 -1
  116. package/dist/core/tools/ipython.d.ts +5 -0
  117. package/dist/core/tools/ipython.d.ts.map +1 -1
  118. package/dist/core/tools/ipython.js +59 -17
  119. package/dist/core/tools/ipython.js.map +1 -1
  120. package/dist/core/tools/prime-context.d.ts.map +1 -1
  121. package/dist/core/tools/prime-context.js +1 -1
  122. package/dist/core/tools/prime-context.js.map +1 -1
  123. package/dist/core/view-units.d.ts +4 -0
  124. package/dist/core/view-units.d.ts.map +1 -1
  125. package/dist/core/view-units.js +28 -19
  126. package/dist/core/view-units.js.map +1 -1
  127. package/dist/installer.mjs +98 -77
  128. package/dist/modes/interactive/components/refinement-outcome-message.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/refinement-outcome-message.js.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  131. package/dist/modes/interactive/interactive-mode.js +5 -5
  132. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  133. package/dist/skills/skill-creator/SKILL.md +18 -1
  134. package/docs/settings.md +45 -0
  135. package/docs/skills.md +77 -2
  136. package/package.json +4 -4
  137. package/skills/skill-creator/SKILL.md +18 -1
  138. package/dist/bundle/chunk-W46VZFI6.js +0 -21681
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
6
  import { isDeepStrictEqual } from "node:util";
7
7
  import { Agent, AgentContinueError, AgentOutputLimitError, } from "@ponythewhite/base-context-agent";
8
- import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isTransientProviderFailure, modelsAreEqual, resetApiProviders, supportsFastMode, } from "@ponythewhite/base-context-ai";
8
+ import { clampThinkingLevel, cleanupSessionResources, conservativeTextTokenCost, getSupportedThinkingLevels, isContextOverflow, isTransientProviderFailure, modelsAreEqual, resetApiProviders, supportsFastMode, } from "@ponythewhite/base-context-ai";
9
9
  import { theme } from "../modes/interactive/theme/theme.js";
10
10
  import { PRODUCT } from "../product-identity.js";
11
11
  import { sleep } from "../utils/sleep.js";
@@ -31,13 +31,13 @@ import { snapshotPathIn } from "./kernel/state-snapshot.js";
31
31
  import { convertToLlm, createCompactionOutcomeMessage, createHeartbeatPromptMessage, createRefinementOutcomeMessage, createRlmChildFailureMessage, createRlmChildTerminalNoticeMessage, createSessionSlashCommandMessage, createSessionSlashCommandResultMessage, HEARTBEAT_PROMPT_CUSTOM_TYPE, HEARTBEAT_PROMPT_PREVIEW_LABEL, IPYTHON_STATE_RESTORED_CUSTOM_TYPE, isSessionSlashCommandMessage, RLM_CHILD_FAILURE_CUSTOM_TYPE, RLM_CHILD_TERMINAL_NOTICE_CUSTOM_TYPE, } from "./messages.js";
32
32
  import { throwIfPromptAdmissionCancelled } from "./prompt-admission.js";
33
33
  import { expandPromptTemplate } from "./prompt-templates.js";
34
- import { appendGlobalRefinement, applyRefinementProposal, generateRefinementId, getGlobalHarnessStateDir, getLocalHarnessStateDir, getRefinementHistory, inferRefinementResultScope, loadGlobalRefinementHistory, loadHarnessState, mergeHarnessStates, mergeRefinementHistory, normalizeRefinementProposal, planRefinement, prepareRefinementApplication, REFINE_SKILL_NAME, reviewAutoRefine, saveHarnessState, takeNativePlannerRequestWrite, withRefinementBaseline, } from "./refinement/index.js";
34
+ import { appendGlobalRefinement, applyRefinementProposal, generateRefinementId, getGlobalHarnessStateDir, getLocalHarnessStateDir, getRefinementHistory, getWorkspaceHarnessStateDir, inferRefinementResultScope, loadGlobalRefinementHistory, loadHarnessState, mergeHarnessStates, mergeRefinementHistory, normalizeRefinementProposal, planRefinement, prepareRefinementApplication, REFINE_SKILL_NAME, reviewAutoRefine, saveHarnessState, takeNativePlannerRequestWrite, withRefinementBaseline, } from "./refinement/index.js";
35
35
  import { PublicContextBudgetError } from "./request-view-selection.js";
36
36
  import { resolveConfigValue } from "./resolve-config-value.js";
37
37
  import { assertResourceCurrent } from "./resource-view.js";
38
38
  import { createDefaultRlmSubagentSessionName, createRlmDeleteSubagentHostHandler, createRlmFindModelsHostHandler, createRlmListSubagentsHostHandler, createRlmRunHostHandler, findRlmModelMatches, normalizeRequestedRlmSubagentModel, normalizeRequestedRlmSubagentSessionName, normalizeRequestedRlmSubagentThinkingLevel, } from "./rlm-runtime.js";
39
39
  import { captureSelectedSkill, captureSkillDescriptor, readSkillSelection, sameSelectedSkills, selectedSkillBlock, selectedSkillIdentity, } from "./selected-skills.js";
40
- import { createNativeRecoveryRefusal, DEFAULT_NATIVE_RECOVERY_LIMITS, NativeRecoveryBudgetRefusal, parseNativeRecoveryInput, recoverCapturedHistory, } from "./selective-recovery.js";
40
+ import { createNativeRecoveryRefusal, DEFAULT_NATIVE_RECOVERY_LIMITS, NativeRecoveryBudgetRefusal, NativeRecoveryCursorStore, parseNativeRecoveryInput, recoverCapturedHistory, } from "./selective-recovery.js";
41
41
  import { modelRequestHeaders, SemanticEdgeRecorder, semanticEdgeLedgerPath, wrapStreamFnWithSemanticEdges, } from "./semantic-edges.js";
42
42
  import { ActionStore, canSelectSessionAction, queuedMessageLaneDeliveryPolicy, transitionSessionAction, } from "./session-action-store.js";
43
43
  import { readSessionBootstrap } from "./session-bootstrap.js";
@@ -281,6 +281,8 @@ export class AgentSession {
281
281
  requests;
282
282
  runtimeServices;
283
283
  _contextCompiler = new CanonicalContextCompiler();
284
+ _failedAutomaticCompaction;
285
+ _failedThresholdCompaction;
284
286
  _contextEpochsEnabled;
285
287
  _initialContextMode;
286
288
  _contextMode;
@@ -444,6 +446,8 @@ export class AgentSession {
444
446
  _modelRegistry;
445
447
  _toolRegistry = new Map();
446
448
  _nativeRecoveryTools = new WeakMap();
449
+ _nativeRecoveryCursors = new NativeRecoveryCursorStore();
450
+ _nativeRecoveryCursorSource;
447
451
  _nativeRecoveryProducer = new AsyncLocalStorage();
448
452
  _toolDefinitions = new Map();
449
453
  _toolPromptSnippets = new Map();
@@ -452,6 +456,7 @@ export class AgentSession {
452
456
  _baseSystemPromptOptions;
453
457
  _assistantTurnsSinceAutoRefine = 0;
454
458
  _lastAutoRefineReviewAt = 0;
459
+ _lastAutoRefineEvidence;
455
460
  _autoRefineInProgress = false;
456
461
  _autoRefineAdmissionClosed = false;
457
462
  _autoRefineOperations = new Set();
@@ -610,7 +615,8 @@ export class AgentSession {
610
615
  const epochContext = getCanonicalEpochContext(messages);
611
616
  if (epochContext)
612
617
  this._contextMode = epochContext.mode;
613
- const unbudgetedPublic = !contextEpochsEnabled && Boolean(epochContext?.toolContinuations?.length);
618
+ const unbudgetedPublic = !contextEpochsEnabled &&
619
+ Boolean(epochContext?.toolContinuations?.length || epochContext?.taskFrameRebased || epochContext?.checkpoint);
614
620
  const nativeSkills = Boolean(contextEpochsEnabled || epochContext?.checkpoint || unbudgetedPublic);
615
621
  const skillPolicy = nativeSkills ? (this._nativeRecoveryEnabled() ? "enabled" : "unavailable") : undefined;
616
622
  if (this._baseSystemPromptOptions.nativeSkillSelection !== skillPolicy) {
@@ -637,6 +643,7 @@ export class AgentSession {
637
643
  message.content.some((part) => part.type !== "text" || part.textSignature !== undefined)));
638
644
  let accepted;
639
645
  let acceptedBody;
646
+ let acceptedResponseIdentity;
640
647
  let acceptedReplayContract;
641
648
  let acknowledged;
642
649
  const commitFailure = (cause) => {
@@ -656,7 +663,7 @@ export class AgentSession {
656
663
  candidate.projection.pendingPublicMessageGroups?.length))
657
664
  throw new Error("Tool continuation requires its exact public candidate before epoch ACK");
658
665
  assertResourceCurrent(resource);
659
- const representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes, unbudgetedPublic);
666
+ const representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes, unbudgetedPublic, candidate.responseItemIdentity);
660
667
  const replayContract = "replayContract" in candidate.projection &&
661
668
  candidate.projection.replayContract === "message-groups"
662
669
  ? "message-groups"
@@ -669,6 +676,7 @@ export class AgentSession {
669
676
  candidate.publicMessages !== undefined,
670
677
  candidate.selectedUnitIds,
671
678
  ]);
679
+ acceptedResponseIdentity = candidate.responseItemIdentity;
672
680
  if (accepted !== undefined) {
673
681
  if (accepted !== selection)
674
682
  throw new Error("Captured epoch request selection changed after acceptance");
@@ -707,7 +715,7 @@ export class AgentSession {
707
715
  acknowledged = { entryId, result };
708
716
  try {
709
717
  assertResourceCurrent(resource);
710
- if (this.sessionManager !== epochManager || epochManager.getLeafId() !== entryId)
718
+ if (this.sessionManager !== epochManager || !compaction.isCurrent())
711
719
  throw new Error("Context epoch source changed before adoption");
712
720
  this.agent.state.messages = prepared.messages;
713
721
  committed = prepared.checkpoint;
@@ -738,12 +746,13 @@ export class AgentSession {
738
746
  }
739
747
  return;
740
748
  }
749
+ if (epochContext.taskFrameRebased && acceptedBody === undefined)
750
+ throw new Error("Task frame rebase requires a committed epoch boundary");
741
751
  if (!committed)
742
752
  return;
743
753
  if (acceptedBody === undefined || request.body !== acceptedBody)
744
754
  throw new Error("Committed context epoch requires a compatible final provider projection");
745
- if (contextEpochRepresentation(request, assessment, limits.maxSourceBytes, unbudgetedPublic) !==
746
- committed.representation)
755
+ if (contextEpochRepresentation(request, assessment, limits.maxSourceBytes, unbudgetedPublic, acceptedResponseIdentity) !== committed.representation)
747
756
  throw new Error("Context epoch representation changed without a committed boundary");
748
757
  if (committed.resourceRevision !== epochContext.resourceRevision)
749
758
  throw new Error("Context epoch resource revision requires a committed boundary");
@@ -797,7 +806,7 @@ export class AgentSession {
797
806
  committedEntry = { sessionId: epochContext.source.sessionId, entryId };
798
807
  requestContract = nextContract;
799
808
  assertResourceCurrent(resource);
800
- if (this.sessionManager !== epochManager || epochManager.getLeafId() !== entryId)
809
+ if (this.sessionManager !== epochManager || !compaction.isCurrent())
801
810
  throw new Error("Context contract source changed before acceptance");
802
811
  }
803
812
  if (!sameSelectedSkills(committed?.selectedSkills, epochContext.selectedSkills))
@@ -814,7 +823,7 @@ export class AgentSession {
814
823
  throw commitFailure(cause);
815
824
  }
816
825
  }
817
- : undefined);
826
+ : undefined, compaction.isCurrent);
818
827
  }
819
828
  this._compactionSetupFailure = undefined;
820
829
  return { messages, adoptMessages: true, streamContext: captured, release: () => captured.dispose() };
@@ -842,11 +851,43 @@ export class AgentSession {
842
851
  !error.source.persistent ||
843
852
  error.source.sessionId !== owner.sessionId ||
844
853
  error.source.sessionFile !== owner.sessionFile ||
845
- error.source.leafId !== owner.manager.getLeafId())
854
+ !(error.isSourceCurrent?.() ?? error.source.leafId === owner.manager.getLeafId()))
855
+ return false;
856
+ // Rebase this captured frame before paying for a summary. The replacement still
857
+ // needs the normal full provider projection and epoch ACK before MAIN can send.
858
+ if (error.taskFrameRebaseAvailable) {
859
+ this._contextCompiler.requestTaskFrameRebase();
860
+ return "reprepare";
861
+ }
862
+ if (error.mandatoryAssessment?.status === "over-budget")
846
863
  return false;
847
864
  // The loop released its unsent inference and captured projection before this handoff.
848
865
  // Continue in-place only after the real summary ACK and canonical owner adoption.
849
- return this._runAutoCompaction("threshold", false, owner, true);
866
+ const key = error.getCompactionKey();
867
+ const current = {
868
+ key: key ?? "",
869
+ systemPrompt: this.agent.state.systemPrompt,
870
+ tools: this.agent.state.tools,
871
+ settings: JSON.stringify(this.settingsManager.getCompactionSettings()),
872
+ };
873
+ const failed = this._failedAutomaticCompaction;
874
+ if (key &&
875
+ failed &&
876
+ failed.key === key &&
877
+ failed.systemPrompt === current.systemPrompt &&
878
+ failed.tools === current.tools &&
879
+ failed.settings === current.settings)
880
+ return false;
881
+ this._failedAutomaticCompaction = undefined;
882
+ let succeeded = false;
883
+ try {
884
+ succeeded = await this._runAutoCompaction("threshold", false, owner, true, error);
885
+ return succeeded;
886
+ }
887
+ finally {
888
+ if (!succeeded && key && this._isCompactionSourceOwnerCurrent(owner))
889
+ this._failedAutomaticCompaction = current;
890
+ }
850
891
  });
851
892
  this.agent.bindProviderFailureRecoveryOwner({
852
893
  recover: (message, signal) => this._handleRetryableError(message, signal),
@@ -1138,7 +1179,7 @@ export class AgentSession {
1138
1179
  };
1139
1180
  // ACK is authoritative even if later setup or release fails.
1140
1181
  this._contextMode = mode;
1141
- if (this.sessionManager !== manager || manager.getLeafId() !== entryId)
1182
+ if (this.sessionManager !== manager || !compaction.isCurrent())
1142
1183
  throw new Error("Context mode source changed before adoption");
1143
1184
  assertResourceCurrent(resource);
1144
1185
  this.agent.state.messages = prepared.messages;
@@ -2840,6 +2881,25 @@ export class AgentSession {
2840
2881
  return timestamp;
2841
2882
  });
2842
2883
  }
2884
+ _thresholdCompactionConfiguration() {
2885
+ return JSON.stringify({
2886
+ model: this.model,
2887
+ thinkingLevel: this.thinkingLevel,
2888
+ settings: this.settingsManager.getCompactionSettings(),
2889
+ });
2890
+ }
2891
+ _hasFailedThresholdCompaction() {
2892
+ const failed = this._failedThresholdCompaction;
2893
+ if (!failed)
2894
+ return false;
2895
+ if (failed.isCurrent() &&
2896
+ failed.configuration === this._thresholdCompactionConfiguration() &&
2897
+ failed.systemPrompt === this.agent.state.systemPrompt &&
2898
+ failed.tools === this.agent.state.tools)
2899
+ return true;
2900
+ this._failedThresholdCompaction = undefined;
2901
+ return false;
2902
+ }
2843
2903
  async _thresholdCompactionNeeded(context, owner = this._captureCompactionOwner(), goalOwner = {
2844
2904
  ...this._captureGoalContinuationOwner(owner.signal),
2845
2905
  checkpointOwner: owner,
@@ -2858,6 +2918,8 @@ export class AgentSession {
2858
2918
  const contextTokens = this._getThresholdContextTokens(context.message, compactionTimestamp);
2859
2919
  if (contextTokens === undefined || !shouldCompact(contextTokens, contextWindow, settings))
2860
2920
  return false;
2921
+ if (this._hasFailedThresholdCompaction())
2922
+ return false;
2861
2923
  // Keep the existing threshold-specific goal winner; do not import W74's natural wait policy here.
2862
2924
  if (!this._isGoalContinuationOwnerCurrent(goalOwner))
2863
2925
  return true;
@@ -4107,6 +4169,10 @@ export class AgentSession {
4107
4169
  * the latest state reaches disk instead of racing process exit.
4108
4170
  */
4109
4171
  async disposeAsync(options) {
4172
+ this._failedAutomaticCompaction = undefined;
4173
+ this._failedThresholdCompaction = undefined;
4174
+ this._contextCompiler.clear();
4175
+ this._nativeRecoveryCursors.clear();
4110
4176
  for (const admission of this._rlmChildAdmissions)
4111
4177
  admission.cancel("Parent session disposed");
4112
4178
  if (this._disposeAsyncPromise)
@@ -4340,6 +4406,10 @@ export class AgentSession {
4340
4406
  return this._disposeCallbacksPromise;
4341
4407
  }
4342
4408
  dispose() {
4409
+ this._failedAutomaticCompaction = undefined;
4410
+ this._failedThresholdCompaction = undefined;
4411
+ this._contextCompiler.clear();
4412
+ this._nativeRecoveryCursors.clear();
4343
4413
  if (this._disposed) {
4344
4414
  return;
4345
4415
  }
@@ -4583,7 +4653,7 @@ export class AgentSession {
4583
4653
  }
4584
4654
  return Array.from(unique);
4585
4655
  }
4586
- _rebuildSystemPrompt(toolNames, nativeEpoch = this.sessionManager.isPersisted() && this._contextEpochsEnabled) {
4656
+ _rebuildSystemPrompt(toolNames, nativeEpoch = this.sessionManager.isPersisted() && this._contextEpochsEnabled, operation = this._baseSystemPromptOptions?.errorFixSelection?.operation) {
4587
4657
  const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
4588
4658
  const toolSnippets = {};
4589
4659
  const promptGuidelines = [];
@@ -4617,6 +4687,14 @@ export class AgentSession {
4617
4687
  rlmDepth: this._rlmDepth,
4618
4688
  rlmParentAgent: this._rlmParentAgent,
4619
4689
  harnessState: this._loadMergedHarnessState(),
4690
+ errorFixSelection: {
4691
+ enabled: this.settingsManager.getLearningEnabled(),
4692
+ tools: validToolNames,
4693
+ operation,
4694
+ goal: this._goalState.objective,
4695
+ environment: { cwd: this._cwd, platform: process.platform },
4696
+ countTokens: conservativeTextTokenCost,
4697
+ },
4620
4698
  genericMcpServers: this._mcpManager?.getEnabledPersistentGenericServers(),
4621
4699
  };
4622
4700
  return buildSystemPrompt(this._baseSystemPromptOptions);
@@ -6099,6 +6177,12 @@ export class AgentSession {
6099
6177
  const preparationAction = activeTurns().at(-1);
6100
6178
  if (!preparationAction)
6101
6179
  return undefined;
6180
+ if (this.settingsManager.getLearningEnabled() ||
6181
+ this._baseSystemPromptOptions.errorFixSelection?.enabled) {
6182
+ // Capture advice for this task before extensions and provider preparation.
6183
+ // Tool continuations keep the same prompt until the next task boundary.
6184
+ this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames(), this._baseSystemPromptOptions.nativeSkillSelection !== undefined, preparationAction.payload.text);
6185
+ }
6102
6186
  const basePromptSnapshot = this._baseSystemPrompt;
6103
6187
  const result = await this._extensionRunner.emitBeforeAgentStart(preparationAction.payload.text, preparationAction.payload.images, basePromptSnapshot, this._baseSystemPromptOptions);
6104
6188
  if (activeTurns().at(-1) !== preparationAction)
@@ -7759,7 +7843,11 @@ export class AgentSession {
7759
7843
  const summaryRequests = requests[captureNativeCompactionRequests]();
7760
7844
  let summaryFailure;
7761
7845
  try {
7762
- nativeCompaction = await compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, summaryCall, summaryRequests);
7846
+ nativeCompaction = await compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, summaryCall, summaryRequests, options.capacity
7847
+ ? (wrapper) => options.capacity.remainingSummaryTokens(new Set(pathEntries
7848
+ .slice(pathEntries.findIndex((entry) => entry.id === preparation.firstKeptEntryId))
7849
+ .map((entry) => entry.id)), wrapper)
7850
+ : undefined);
7763
7851
  ({ summary, firstKeptEntryId, tokensBefore, details, usage } = nativeCompaction);
7764
7852
  }
7765
7853
  catch (error) {
@@ -8320,9 +8408,9 @@ export class AgentSession {
8320
8408
  }
8321
8409
  }
8322
8410
  catch (error) {
8323
- // Auto-refine is opportunistic; manual /refine remains available.
8324
- // Stamp the cooldown so a persistently failing refine doesn't retry
8325
- // (via a retained pending review) on every agent end.
8411
+ // Consume this exact automatic proposal after failure. New evidence can
8412
+ // request a new review; manual /refine remains independent.
8413
+ this._pendingAutoRefineReview = undefined;
8326
8414
  this._lastAutoRefineReviewAt = Date.now();
8327
8415
  if (error instanceof RefineSkippedError) {
8328
8416
  // A skipped round is consumed like a reviewer decline, not retained for retry.
@@ -8362,7 +8450,78 @@ export class AgentSession {
8362
8450
  throw new Error(`autoRefine.model thinkingLevel ${selection.thinkingLevel} is not supported by ${selection.provider}/${selection.modelId}`);
8363
8451
  return { model: { ...model, cost: { ...model.cost } }, thinkingLevel: selection.thinkingLevel, explicit: true };
8364
8452
  }
8453
+ async _autoRefineEvidence(harnessState) {
8454
+ const manager = this.sessionManager;
8455
+ const goalEvidence = ({ active, status, goalId, objective, tokenBudget, lastError }) => ({
8456
+ active,
8457
+ status,
8458
+ goalId,
8459
+ objective,
8460
+ tokenBudget,
8461
+ lastError,
8462
+ });
8463
+ const frontier = manager.supportsCapturedHistoryReads()
8464
+ ? await manager.readBranchHistory(async (history) => {
8465
+ const goal = (await history.branchBootstrap()).goalState;
8466
+ const limits = this.settingsManager.getCanonicalContextLimits();
8467
+ let goalState = emptyGoalState();
8468
+ if (goal) {
8469
+ const hydrated = await history.hydrateEntry(goal.id, limits.maxSourceBytes);
8470
+ if (!hydrated ||
8471
+ hydrated.source.retention === "retained-import" ||
8472
+ hydrated.entry.type !== "custom" ||
8473
+ hydrated.entry.customType !== GOAL_STATE_CUSTOM_TYPE ||
8474
+ !isPersistedGoalState(hydrated.entry.data))
8475
+ throw new RefineSkippedError("Automatic refinement goal source is unavailable or ineligible");
8476
+ goalState = normalizeGoalState(hydrated.entry.data);
8477
+ }
8478
+ let id = history.source.leafId;
8479
+ const maxEntries = limits.maxMessages;
8480
+ for (let scanned = 0; scanned < maxEntries; scanned++) {
8481
+ const entry = id ? await history.get(id) : undefined;
8482
+ if (id && !entry)
8483
+ throw new RefineSkippedError("Automatic refinement task frontier is unavailable");
8484
+ const message = entry?.kind === "message" ? entry : undefined;
8485
+ if (message || !entry)
8486
+ return {
8487
+ sessionId: history.source.sessionId,
8488
+ sessionFile: history.source.sessionFile,
8489
+ message: message ? [message.id, message.revision] : undefined,
8490
+ goal: goalEvidence(goalState),
8491
+ };
8492
+ id = entry.parentId;
8493
+ }
8494
+ throw new RefineSkippedError("Automatic refinement task frontier exceeds the context read limit");
8495
+ })
8496
+ : (() => {
8497
+ const branch = manager.getBranch().slice().reverse();
8498
+ const message = branch.find((entry) => entry.type === "message");
8499
+ return {
8500
+ sessionId: manager.getSessionId(),
8501
+ sessionFile: manager.getSessionFile(),
8502
+ message: message ? [message.id, message.timestamp] : undefined,
8503
+ goal: goalEvidence(this._loadPersistedGoalState()),
8504
+ };
8505
+ })();
8506
+ return {
8507
+ ...frontier,
8508
+ systemPrompt: this.agent.state.systemPrompt,
8509
+ model: JSON.stringify([this.model?.provider, this.model?.id, this.settingsManager.getAutoRefineModel()]),
8510
+ harnessEntries: Object.fromEntries(Object.entries(harnessState.entries).map(([kind, entries]) => [
8511
+ kind,
8512
+ Object.fromEntries(Object.entries(entries).map(([id, { version: _version, created_at: _created, updated_at: _updated, ...entry }]) => [id, entry])),
8513
+ ])),
8514
+ };
8515
+ }
8365
8516
  async _reviewAutoRefine(context, signal) {
8517
+ const harnessState = this._loadMergedHarnessState();
8518
+ const evidence = await this._autoRefineEvidence(harnessState);
8519
+ signal?.throwIfAborted();
8520
+ if (isDeepStrictEqual(evidence, this._lastAutoRefineEvidence))
8521
+ return { shouldRefine: false, rationale: "No new task evidence since the previous automatic review." };
8522
+ // Reserve before any inference wait. Equivalent running, declined or failed
8523
+ // automatic reviews do not buy another call; explicit /refine stays independent.
8524
+ this._lastAutoRefineEvidence = evidence;
8366
8525
  if (this._autoRefineReviewer)
8367
8526
  return this._autoRefineReviewer(context, signal);
8368
8527
  const selected = this._resolveRefinementModel();
@@ -8370,7 +8529,6 @@ export class AgentSession {
8370
8529
  return { shouldRefine: false, rationale: "No model selected." };
8371
8530
  const { model, thinkingLevel, explicit } = selected;
8372
8531
  const messages = structuredClone(this.agent.state.messages);
8373
- const harnessState = this._loadMergedHarnessState();
8374
8532
  const reviewContext = { ...context };
8375
8533
  const requests = this.requests[captureNativeReviewerRequests]();
8376
8534
  try {
@@ -8382,10 +8540,10 @@ export class AgentSession {
8382
8540
  await requests.dispose();
8383
8541
  }
8384
8542
  }
8385
- /** Global harness state overlaid with this session's local state, when persisted. */
8543
+ /** Merge existing session/global memory with the selected project's workspace memory. */
8386
8544
  _loadMergedHarnessState() {
8387
8545
  const localHarnessStateDir = this._localHarnessStateDir();
8388
- return mergeHarnessStates(loadHarnessState(getGlobalHarnessStateDir(), "global"), localHarnessStateDir ? loadHarnessState(localHarnessStateDir, "local") : undefined);
8546
+ return mergeHarnessStates(loadHarnessState(getGlobalHarnessStateDir(), "global"), localHarnessStateDir ? loadHarnessState(localHarnessStateDir, "local") : undefined, loadHarnessState(getWorkspaceHarnessStateDir(this._cwd), "workspace"));
8389
8547
  }
8390
8548
  async _loadRefinementHistory() {
8391
8549
  const limits = {
@@ -8843,7 +9001,21 @@ export class AgentSession {
8843
9001
  }
8844
9002
  if (!this._isCompactionOwnerCurrent(owner))
8845
9003
  return false;
8846
- const assistantIsFromBeforeCompaction = compactionTimestamp !== undefined && assistantMessage.timestamp <= compactionTimestamp;
9004
+ let assistantIsFromBeforeCompaction = compactionTimestamp !== undefined && assistantMessage.timestamp <= compactionTimestamp;
9005
+ const assistantEntryId = this._findAssistantEntryIdForMessage(assistantMessage);
9006
+ if (assistantEntryId && owner.manager.supportsCapturedHistoryReads()) {
9007
+ const ordered = await owner.manager.readBranchHistory(async (history) => {
9008
+ const assistant = await history.get(assistantEntryId);
9009
+ const boundary = (await history.branchBootstrap()).latestCompaction;
9010
+ return assistant && boundary ? assistant.sequence <= boundary.sequence : undefined;
9011
+ });
9012
+ if (!this._isCompactionOwnerCurrent(owner))
9013
+ return false;
9014
+ // Provider messages are timestamped before request preparation, which may
9015
+ // commit an epoch. Canonical append order, not that start time, owns staleness.
9016
+ if (ordered !== undefined)
9017
+ assistantIsFromBeforeCompaction = ordered;
9018
+ }
8847
9019
  // Case 1: Overflow - takes priority over a pending model request so the error
8848
9020
  // strip + retry still happen; the compaction it runs consumes the request.
8849
9021
  if (!assistantIsFromBeforeCompaction &&
@@ -8881,6 +9053,8 @@ export class AgentSession {
8881
9053
  if (contextTokens === undefined)
8882
9054
  return false;
8883
9055
  if (shouldCompact(contextTokens, contextWindow, settings)) {
9056
+ if (this._hasFailedThresholdCompaction())
9057
+ return false;
8884
9058
  if (!this._pendingCheckpoint && queueAutonomousContinuation) {
8885
9059
  if (!(await this._queueGoalContinuationForThresholdCompaction(assistantMessage, goalOwner)) &&
8886
9060
  this._isGoalContinuationOwnerCurrent(goalOwner)) {
@@ -8935,7 +9109,7 @@ export class AgentSession {
8935
9109
  this._emit({ type: "message_start", message: outcomeMessage });
8936
9110
  this._emit({ type: "message_end", message: outcomeMessage });
8937
9111
  }
8938
- async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false) {
9112
+ async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false, capacity) {
8939
9113
  const checkpoint = this._pendingCheckpoint;
8940
9114
  const pending = this._pendingRequestedCompaction;
8941
9115
  const owner = checkpoint?.owner ?? pending?.owner ?? capturedOwner ?? this._captureCompactionOwner();
@@ -8980,6 +9154,21 @@ export class AgentSession {
8980
9154
  let compaction;
8981
9155
  let committed;
8982
9156
  let failure;
9157
+ const thresholdConfiguration = reason === "threshold" && !resumeInPlace
9158
+ ? {
9159
+ configuration: this._thresholdCompactionConfiguration(),
9160
+ systemPrompt: this.agent.state.systemPrompt,
9161
+ tools: this.agent.state.tools,
9162
+ }
9163
+ : undefined;
9164
+ const rememberThresholdFailure = () => {
9165
+ if (thresholdConfiguration &&
9166
+ !abort.signal.aborted &&
9167
+ this._isCompactionOwnerCurrent(owner) &&
9168
+ compaction?.isCurrent()) {
9169
+ this._failedThresholdCompaction = { ...thresholdConfiguration, isCurrent: compaction.isCurrent };
9170
+ }
9171
+ };
8983
9172
  try {
8984
9173
  const selected = this._resolveCompactionModel();
8985
9174
  const model = selected?.model;
@@ -9000,6 +9189,7 @@ export class AgentSession {
9000
9189
  : authResult.error;
9001
9190
  await this._endCompactionUnsuccessfully(reason, "failed", `Compaction failed: ${detail}`, { owner });
9002
9191
  this._clearQueuedAutonomousContinuationsAfterSkippedThresholdCompaction(reason === "threshold" && shouldContinueAfterCompaction, queuedAutonomousContinuationsForThisCompaction);
9192
+ rememberThresholdFailure();
9003
9193
  resumeAfterFailure();
9004
9194
  return false;
9005
9195
  }
@@ -9018,6 +9208,7 @@ export class AgentSession {
9018
9208
  signal: abort.signal,
9019
9209
  // Only confirmed public-budget recovery resumes within the same invocation.
9020
9210
  budgetPressure: resumeInPlace,
9211
+ capacity,
9021
9212
  });
9022
9213
  const result = committed.result;
9023
9214
  // Release this captured source before arming success. A later release failure still carries the ACK.
@@ -9075,6 +9266,7 @@ export class AgentSession {
9075
9266
  ? `Requested compaction failed: ${errorMessage}`
9076
9267
  : `Auto-compaction failed: ${errorMessage}`, { customInstructions, owner });
9077
9268
  }
9269
+ rememberThresholdFailure();
9078
9270
  resumeAfterFailure();
9079
9271
  return false;
9080
9272
  }
@@ -9613,7 +9805,12 @@ export class AgentSession {
9613
9805
  producer?.skillOwner?.writer.assertCurrent();
9614
9806
  if (this.sessionManager !== manager)
9615
9807
  throw new Error("Native recovery source changed");
9616
- return await manager.readBranchHistory((history) => recoverCapturedHistory(history.branchContext, request, { ...DEFAULT_NATIVE_RECOVERY_LIMITS, maxBytes: responseBytes }, signal));
9808
+ const cursorSource = JSON.stringify([manager.getSessionId(), manager.getSessionFile()]);
9809
+ if (this._nativeRecoveryCursorSource !== cursorSource) {
9810
+ this._nativeRecoveryCursors.clear();
9811
+ this._nativeRecoveryCursorSource = cursorSource;
9812
+ }
9813
+ return await manager.readBranchHistory((history) => recoverCapturedHistory(history.branchContext, request, { ...DEFAULT_NATIVE_RECOVERY_LIMITS, maxBytes: responseBytes }, signal, this._nativeRecoveryCursors));
9617
9814
  }
9618
9815
  catch (error) {
9619
9816
  signal?.throwIfAborted();
@@ -9762,6 +9959,7 @@ export class AgentSession {
9762
9959
  BASE_CONTEXT_RLM_DEPTH: String(this._rlmDepth),
9763
9960
  BASE_CONTEXT_RLM_MAX_DEPTH: String(this._rlmMaxDepth),
9764
9961
  BASE_CONTEXT_GLOBAL_HARNESS_STATE_DIR: getGlobalHarnessStateDir(),
9962
+ BASE_CONTEXT_WORKSPACE_HARNESS_STATE_DIR: getWorkspaceHarnessStateDir(this._cwd),
9765
9963
  };
9766
9964
  const rlmSessionDir = this._ensureRlmSessionDir();
9767
9965
  if (rlmSessionDir) {
@@ -9854,15 +10052,13 @@ export class AgentSession {
9854
10052
  if (message?.role !== "assistant")
9855
10053
  return;
9856
10054
  if (this.sessionManager.isPersisted()) {
9857
- const acknowledged = this._assistantEntryIds.get(message);
9858
- if (!acknowledged ||
9859
- acknowledged.sessionId !== this.sessionId ||
9860
- acknowledged.sessionFile !== this.sessionFile)
10055
+ const entryId = this._findAssistantEntryIdForMessage(message);
10056
+ if (!entryId)
9861
10057
  throw new Error("Cannot identify the acknowledged assistant response removed by retry control");
9862
10058
  if (this._contextOmissions?.sessionId !== this.sessionId ||
9863
10059
  this._contextOmissions?.sessionFile !== this.sessionFile)
9864
10060
  this._contextOmissions = { sessionId: this.sessionId, sessionFile: this.sessionFile, ids: new Set() };
9865
- this._contextOmissions.ids.add(acknowledged.entryId);
10061
+ this._contextOmissions.ids.add(entryId);
9866
10062
  }
9867
10063
  this.agent.state.messages = messages.slice(0, -1);
9868
10064
  }