@ponythewhite/base-context 1.0.6 → 1.0.8

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 (136) hide show
  1. package/CHANGELOG.md +25 -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-MEACET4U.js → bundled-modules-IGMJH2MU.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-QKI6KEGP.js → chunk-EXEFSSF4.js} +1509 -577
  15. package/dist/bundle/{chunk-VSIYFWYI.js → chunk-KT2BNEPE.js} +21 -8
  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-LIZVVGHR.js → chunk-RVJVXZCS.js} +13 -10
  19. package/dist/bundle/{cli-main-ANUJY7Y4.js → cli-main-BUK7QTHO.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-4GIKOHIT.js → main-2FWHT2WZ.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 +6 -1
  29. package/dist/core/agent-session.d.ts.map +1 -1
  30. package/dist/core/agent-session.js +189 -31
  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 +20 -12
  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.map +1 -1
  55. package/dist/core/inference-coordinator.js +110 -13
  56. package/dist/core/inference-coordinator.js.map +1 -1
  57. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  58. package/dist/core/kernel/bootstrap.js +97 -107
  59. package/dist/core/kernel/bootstrap.js.map +1 -1
  60. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  61. package/dist/core/kernel/repl-manager.js +11 -0
  62. package/dist/core/kernel/repl-manager.js.map +1 -1
  63. package/dist/core/kernel/shared.d.ts +2 -0
  64. package/dist/core/kernel/shared.d.ts.map +1 -1
  65. package/dist/core/kernel/shared.js.map +1 -1
  66. package/dist/core/messages.d.ts.map +1 -1
  67. package/dist/core/messages.js +4 -1
  68. package/dist/core/messages.js.map +1 -1
  69. package/dist/core/prompts/rlm.d.ts.map +1 -1
  70. package/dist/core/prompts/rlm.js +4 -2
  71. package/dist/core/prompts/rlm.js.map +1 -1
  72. package/dist/core/public-context.d.ts.map +1 -1
  73. package/dist/core/public-context.js +16 -0
  74. package/dist/core/public-context.js.map +1 -1
  75. package/dist/core/refinement/refinement.d.ts +19 -5
  76. package/dist/core/refinement/refinement.d.ts.map +1 -1
  77. package/dist/core/refinement/refinement.js +90 -35
  78. package/dist/core/refinement/refinement.js.map +1 -1
  79. package/dist/core/request-view-selection.d.ts +12 -4
  80. package/dist/core/request-view-selection.d.ts.map +1 -1
  81. package/dist/core/request-view-selection.js +242 -100
  82. package/dist/core/request-view-selection.js.map +1 -1
  83. package/dist/core/retained-tool-output.d.ts +28 -0
  84. package/dist/core/retained-tool-output.d.ts.map +1 -0
  85. package/dist/core/retained-tool-output.js +102 -0
  86. package/dist/core/retained-tool-output.js.map +1 -0
  87. package/dist/core/sdk.d.ts.map +1 -1
  88. package/dist/core/sdk.js +1 -1
  89. package/dist/core/sdk.js.map +1 -1
  90. package/dist/core/selective-recovery.d.ts +47 -3
  91. package/dist/core/selective-recovery.d.ts.map +1 -1
  92. package/dist/core/selective-recovery.js +428 -109
  93. package/dist/core/selective-recovery.js.map +1 -1
  94. package/dist/core/session-history-index.d.ts +3 -1
  95. package/dist/core/session-history-index.d.ts.map +1 -1
  96. package/dist/core/session-history-index.js +1 -1
  97. package/dist/core/session-history-index.js.map +1 -1
  98. package/dist/core/settings-manager.d.ts +5 -0
  99. package/dist/core/settings-manager.d.ts.map +1 -1
  100. package/dist/core/settings-manager.js +3 -0
  101. package/dist/core/settings-manager.js.map +1 -1
  102. package/dist/core/system-prompt.d.ts +3 -1
  103. package/dist/core/system-prompt.d.ts.map +1 -1
  104. package/dist/core/system-prompt.js +3 -3
  105. package/dist/core/system-prompt.js.map +1 -1
  106. package/dist/core/task-frame.d.ts +1 -1
  107. package/dist/core/task-frame.d.ts.map +1 -1
  108. package/dist/core/task-frame.js +21 -1
  109. package/dist/core/task-frame.js.map +1 -1
  110. package/dist/core/task-state-reader.d.ts +10 -2
  111. package/dist/core/task-state-reader.d.ts.map +1 -1
  112. package/dist/core/task-state-reader.js +75 -9
  113. package/dist/core/task-state-reader.js.map +1 -1
  114. package/dist/core/tools/ipython.d.ts +5 -0
  115. package/dist/core/tools/ipython.d.ts.map +1 -1
  116. package/dist/core/tools/ipython.js +59 -17
  117. package/dist/core/tools/ipython.js.map +1 -1
  118. package/dist/core/tools/prime-context.d.ts.map +1 -1
  119. package/dist/core/tools/prime-context.js +1 -1
  120. package/dist/core/tools/prime-context.js.map +1 -1
  121. package/dist/core/view-units.d.ts +4 -0
  122. package/dist/core/view-units.d.ts.map +1 -1
  123. package/dist/core/view-units.js +28 -19
  124. package/dist/core/view-units.js.map +1 -1
  125. package/dist/installer.mjs +98 -77
  126. package/dist/modes/interactive/components/refinement-outcome-message.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/refinement-outcome-message.js.map +1 -1
  128. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/dist/modes/interactive/interactive-mode.js +5 -5
  130. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/dist/skills/skill-creator/SKILL.md +18 -1
  132. package/docs/settings.md +45 -0
  133. package/docs/skills.md +77 -2
  134. package/package.json +4 -4
  135. package/skills/skill-creator/SKILL.md +18 -1
  136. 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,7 @@ export class AgentSession {
281
281
  requests;
282
282
  runtimeServices;
283
283
  _contextCompiler = new CanonicalContextCompiler();
284
+ _failedAutomaticCompaction;
284
285
  _contextEpochsEnabled;
285
286
  _initialContextMode;
286
287
  _contextMode;
@@ -444,6 +445,8 @@ export class AgentSession {
444
445
  _modelRegistry;
445
446
  _toolRegistry = new Map();
446
447
  _nativeRecoveryTools = new WeakMap();
448
+ _nativeRecoveryCursors = new NativeRecoveryCursorStore();
449
+ _nativeRecoveryCursorSource;
447
450
  _nativeRecoveryProducer = new AsyncLocalStorage();
448
451
  _toolDefinitions = new Map();
449
452
  _toolPromptSnippets = new Map();
@@ -452,6 +455,7 @@ export class AgentSession {
452
455
  _baseSystemPromptOptions;
453
456
  _assistantTurnsSinceAutoRefine = 0;
454
457
  _lastAutoRefineReviewAt = 0;
458
+ _lastAutoRefineEvidence;
455
459
  _autoRefineInProgress = false;
456
460
  _autoRefineAdmissionClosed = false;
457
461
  _autoRefineOperations = new Set();
@@ -592,7 +596,9 @@ export class AgentSession {
592
596
  try {
593
597
  const messages = await captured.readHistory(async (view) => {
594
598
  const sameSource = controls?.sessionId === view.source.sessionId && controls?.sessionFile === view.source.sessionFile;
595
- const result = await this._contextCompiler.compile(view, limits, sameSource ? omitted : undefined, {}, resource, this._initialContextMode, contextEpochsEnabled);
599
+ const result = await this._contextCompiler.compile(view, limits, sameSource ? omitted : undefined, {}, resource, this._initialContextMode,
600
+ // This MAIN owner binds public admission below, independently of optional token budgets.
601
+ true);
596
602
  if (sameSource && this._contextOmissions === controls) {
597
603
  // Only prune this captured set. A newer control or source switch must survive this read.
598
604
  for (const id of omitted)
@@ -608,7 +614,9 @@ export class AgentSession {
608
614
  const epochContext = getCanonicalEpochContext(messages);
609
615
  if (epochContext)
610
616
  this._contextMode = epochContext.mode;
611
- const nativeSkills = Boolean(contextEpochsEnabled || epochContext?.checkpoint);
617
+ const unbudgetedPublic = !contextEpochsEnabled &&
618
+ Boolean(epochContext?.toolContinuations?.length || epochContext?.taskFrameRebased || epochContext?.checkpoint);
619
+ const nativeSkills = Boolean(contextEpochsEnabled || epochContext?.checkpoint || unbudgetedPublic);
612
620
  const skillPolicy = nativeSkills ? (this._nativeRecoveryEnabled() ? "enabled" : "unavailable") : undefined;
613
621
  if (this._baseSystemPromptOptions.nativeSkillSelection !== skillPolicy) {
614
622
  const previousBase = this._baseSystemPrompt;
@@ -618,20 +626,23 @@ export class AgentSession {
618
626
  if (epochContext?.resourceRevision !== undefined &&
619
627
  getCanonicalViewUnits(messages)?.length !== messages.length)
620
628
  throw new Error("Current resource view requires tracked canonical context");
621
- if (epochContext?.checkpoint && getCanonicalViewUnits(messages)?.length !== messages.length)
629
+ if ((epochContext?.checkpoint || unbudgetedPublic) &&
630
+ getCanonicalViewUnits(messages)?.length !== messages.length)
622
631
  throw new Error("Committed context epoch cannot admit untracked transient messages");
623
632
  if (epochContext &&
624
- (contextEpochsEnabled || epochContext.checkpoint) &&
633
+ (contextEpochsEnabled || epochContext.checkpoint || unbudgetedPublic) &&
625
634
  getCanonicalViewUnits(messages)?.length === messages.length) {
626
635
  let committed = epochContext.checkpoint;
627
636
  let committedEntry = epochContext.checkpointEntry;
628
- const fixed = epochContext.mode === "off" || (committed?.policyOnly === true && !contextEpochsEnabled);
637
+ const fixed = epochContext.mode === "off" ||
638
+ (committed?.policyOnly === true && !contextEpochsEnabled && !unbudgetedPublic);
629
639
  let requestContract = fixed ? retainedContextRequestContract(committed) : undefined;
630
640
  const nativeTail = messages.some((message) => message.role === "toolResult" ||
631
641
  (message.role === "assistant" &&
632
642
  message.content.some((part) => part.type !== "text" || part.textSignature !== undefined)));
633
643
  let accepted;
634
644
  let acceptedBody;
645
+ let acceptedResponseIdentity;
635
646
  let acceptedReplayContract;
636
647
  let acknowledged;
637
648
  const commitFailure = (cause) => {
@@ -651,7 +662,7 @@ export class AgentSession {
651
662
  candidate.projection.pendingPublicMessageGroups?.length))
652
663
  throw new Error("Tool continuation requires its exact public candidate before epoch ACK");
653
664
  assertResourceCurrent(resource);
654
- const representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes);
665
+ const representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes, unbudgetedPublic, candidate.responseItemIdentity);
655
666
  const replayContract = "replayContract" in candidate.projection &&
656
667
  candidate.projection.replayContract === "message-groups"
657
668
  ? "message-groups"
@@ -664,6 +675,7 @@ export class AgentSession {
664
675
  candidate.publicMessages !== undefined,
665
676
  candidate.selectedUnitIds,
666
677
  ]);
678
+ acceptedResponseIdentity = candidate.responseItemIdentity;
667
679
  if (accepted !== undefined) {
668
680
  if (accepted !== selection)
669
681
  throw new Error("Captured epoch request selection changed after acceptance");
@@ -691,7 +703,7 @@ export class AgentSession {
691
703
  const prepared = prepareCanonicalEpoch(candidate.publicMessages ?? messages, candidate.selectedUnitIds, representation, limits.maxSourceBytes, replayContract, publicWindow);
692
704
  if (JSON.stringify(prepared.checkpoint.source) !== JSON.stringify(candidate.source))
693
705
  throw new Error("Context epoch candidate does not match its captured source");
694
- const tokensBefore = candidate.originalAssessment.estimatedInputTokens;
706
+ const tokensBefore = candidate.originalAssessment?.estimatedInputTokens ?? null;
695
707
  const result = {
696
708
  summary: "",
697
709
  firstKeptEntryId: prepared.checkpoint.literalTailId,
@@ -733,12 +745,13 @@ export class AgentSession {
733
745
  }
734
746
  return;
735
747
  }
748
+ if (epochContext.taskFrameRebased && acceptedBody === undefined)
749
+ throw new Error("Task frame rebase requires a committed epoch boundary");
736
750
  if (!committed)
737
751
  return;
738
752
  if (acceptedBody === undefined || request.body !== acceptedBody)
739
753
  throw new Error("Committed context epoch requires a compatible final provider projection");
740
- if (contextEpochRepresentation(request, assessment, limits.maxSourceBytes) !==
741
- committed.representation)
754
+ if (contextEpochRepresentation(request, assessment, limits.maxSourceBytes, unbudgetedPublic, acceptedResponseIdentity) !== committed.representation)
742
755
  throw new Error("Context epoch representation changed without a committed boundary");
743
756
  if (committed.resourceRevision !== epochContext.resourceRevision)
744
757
  throw new Error("Context epoch resource revision requires a committed boundary");
@@ -839,9 +852,41 @@ export class AgentSession {
839
852
  error.source.sessionFile !== owner.sessionFile ||
840
853
  error.source.leafId !== owner.manager.getLeafId())
841
854
  return false;
855
+ // Rebase this captured frame before paying for a summary. The replacement still
856
+ // needs the normal full provider projection and epoch ACK before MAIN can send.
857
+ if (error.taskFrameRebaseAvailable) {
858
+ this._contextCompiler.requestTaskFrameRebase();
859
+ return "reprepare";
860
+ }
861
+ if (error.mandatoryAssessment?.status === "over-budget")
862
+ return false;
842
863
  // The loop released its unsent inference and captured projection before this handoff.
843
864
  // Continue in-place only after the real summary ACK and canonical owner adoption.
844
- return this._runAutoCompaction("threshold", false, owner, true);
865
+ const key = error.getCompactionKey();
866
+ const current = {
867
+ key: key ?? "",
868
+ systemPrompt: this.agent.state.systemPrompt,
869
+ tools: this.agent.state.tools,
870
+ settings: JSON.stringify(this.settingsManager.getCompactionSettings()),
871
+ };
872
+ const failed = this._failedAutomaticCompaction;
873
+ if (key &&
874
+ failed &&
875
+ failed.key === key &&
876
+ failed.systemPrompt === current.systemPrompt &&
877
+ failed.tools === current.tools &&
878
+ failed.settings === current.settings)
879
+ return false;
880
+ this._failedAutomaticCompaction = undefined;
881
+ let succeeded = false;
882
+ try {
883
+ succeeded = await this._runAutoCompaction("threshold", false, owner, true, error);
884
+ return succeeded;
885
+ }
886
+ finally {
887
+ if (!succeeded && key && this._isCompactionSourceOwnerCurrent(owner))
888
+ this._failedAutomaticCompaction = current;
889
+ }
845
890
  });
846
891
  this.agent.bindProviderFailureRecoveryOwner({
847
892
  recover: (message, signal) => this._handleRetryableError(message, signal),
@@ -4102,6 +4147,9 @@ export class AgentSession {
4102
4147
  * the latest state reaches disk instead of racing process exit.
4103
4148
  */
4104
4149
  async disposeAsync(options) {
4150
+ this._failedAutomaticCompaction = undefined;
4151
+ this._contextCompiler.clear();
4152
+ this._nativeRecoveryCursors.clear();
4105
4153
  for (const admission of this._rlmChildAdmissions)
4106
4154
  admission.cancel("Parent session disposed");
4107
4155
  if (this._disposeAsyncPromise)
@@ -4335,6 +4383,9 @@ export class AgentSession {
4335
4383
  return this._disposeCallbacksPromise;
4336
4384
  }
4337
4385
  dispose() {
4386
+ this._failedAutomaticCompaction = undefined;
4387
+ this._contextCompiler.clear();
4388
+ this._nativeRecoveryCursors.clear();
4338
4389
  if (this._disposed) {
4339
4390
  return;
4340
4391
  }
@@ -4578,7 +4629,7 @@ export class AgentSession {
4578
4629
  }
4579
4630
  return Array.from(unique);
4580
4631
  }
4581
- _rebuildSystemPrompt(toolNames, nativeEpoch = this.sessionManager.isPersisted() && this._contextEpochsEnabled) {
4632
+ _rebuildSystemPrompt(toolNames, nativeEpoch = this.sessionManager.isPersisted() && this._contextEpochsEnabled, operation = this._baseSystemPromptOptions?.errorFixSelection?.operation) {
4582
4633
  const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
4583
4634
  const toolSnippets = {};
4584
4635
  const promptGuidelines = [];
@@ -4612,6 +4663,14 @@ export class AgentSession {
4612
4663
  rlmDepth: this._rlmDepth,
4613
4664
  rlmParentAgent: this._rlmParentAgent,
4614
4665
  harnessState: this._loadMergedHarnessState(),
4666
+ errorFixSelection: {
4667
+ enabled: this.settingsManager.getLearningEnabled(),
4668
+ tools: validToolNames,
4669
+ operation,
4670
+ goal: this._goalState.objective,
4671
+ environment: { cwd: this._cwd, platform: process.platform },
4672
+ countTokens: conservativeTextTokenCost,
4673
+ },
4615
4674
  genericMcpServers: this._mcpManager?.getEnabledPersistentGenericServers(),
4616
4675
  };
4617
4676
  return buildSystemPrompt(this._baseSystemPromptOptions);
@@ -6094,6 +6153,12 @@ export class AgentSession {
6094
6153
  const preparationAction = activeTurns().at(-1);
6095
6154
  if (!preparationAction)
6096
6155
  return undefined;
6156
+ if (this.settingsManager.getLearningEnabled() ||
6157
+ this._baseSystemPromptOptions.errorFixSelection?.enabled) {
6158
+ // Capture advice for this task before extensions and provider preparation.
6159
+ // Tool continuations keep the same prompt until the next task boundary.
6160
+ this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames(), this._baseSystemPromptOptions.nativeSkillSelection !== undefined, preparationAction.payload.text);
6161
+ }
6097
6162
  const basePromptSnapshot = this._baseSystemPrompt;
6098
6163
  const result = await this._extensionRunner.emitBeforeAgentStart(preparationAction.payload.text, preparationAction.payload.images, basePromptSnapshot, this._baseSystemPromptOptions);
6099
6164
  if (activeTurns().at(-1) !== preparationAction)
@@ -7754,7 +7819,11 @@ export class AgentSession {
7754
7819
  const summaryRequests = requests[captureNativeCompactionRequests]();
7755
7820
  let summaryFailure;
7756
7821
  try {
7757
- nativeCompaction = await compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, summaryCall, summaryRequests);
7822
+ nativeCompaction = await compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, summaryCall, summaryRequests, options.capacity
7823
+ ? (wrapper) => options.capacity.remainingSummaryTokens(new Set(pathEntries
7824
+ .slice(pathEntries.findIndex((entry) => entry.id === preparation.firstKeptEntryId))
7825
+ .map((entry) => entry.id)), wrapper)
7826
+ : undefined);
7758
7827
  ({ summary, firstKeptEntryId, tokensBefore, details, usage } = nativeCompaction);
7759
7828
  }
7760
7829
  catch (error) {
@@ -7816,7 +7885,7 @@ export class AgentSession {
7816
7885
  try {
7817
7886
  this._assertCompactionOwner(owner);
7818
7887
  const bootstrap = await readSessionBootstrap(owner.manager, this.settingsManager.getCanonicalContextLimits(), {
7819
- allowPendingToolPublic: this._contextEpochsEnabled,
7888
+ purpose: "read",
7820
7889
  initialContextMode: this._initialContextMode,
7821
7890
  });
7822
7891
  this._assertCompactionOwner(owner);
@@ -8315,9 +8384,9 @@ export class AgentSession {
8315
8384
  }
8316
8385
  }
8317
8386
  catch (error) {
8318
- // Auto-refine is opportunistic; manual /refine remains available.
8319
- // Stamp the cooldown so a persistently failing refine doesn't retry
8320
- // (via a retained pending review) on every agent end.
8387
+ // Consume this exact automatic proposal after failure. New evidence can
8388
+ // request a new review; manual /refine remains independent.
8389
+ this._pendingAutoRefineReview = undefined;
8321
8390
  this._lastAutoRefineReviewAt = Date.now();
8322
8391
  if (error instanceof RefineSkippedError) {
8323
8392
  // A skipped round is consumed like a reviewer decline, not retained for retry.
@@ -8357,7 +8426,78 @@ export class AgentSession {
8357
8426
  throw new Error(`autoRefine.model thinkingLevel ${selection.thinkingLevel} is not supported by ${selection.provider}/${selection.modelId}`);
8358
8427
  return { model: { ...model, cost: { ...model.cost } }, thinkingLevel: selection.thinkingLevel, explicit: true };
8359
8428
  }
8429
+ async _autoRefineEvidence(harnessState) {
8430
+ const manager = this.sessionManager;
8431
+ const goalEvidence = ({ active, status, goalId, objective, tokenBudget, lastError }) => ({
8432
+ active,
8433
+ status,
8434
+ goalId,
8435
+ objective,
8436
+ tokenBudget,
8437
+ lastError,
8438
+ });
8439
+ const frontier = manager.supportsCapturedHistoryReads()
8440
+ ? await manager.readBranchHistory(async (history) => {
8441
+ const goal = (await history.branchBootstrap()).goalState;
8442
+ const limits = this.settingsManager.getCanonicalContextLimits();
8443
+ let goalState = emptyGoalState();
8444
+ if (goal) {
8445
+ const hydrated = await history.hydrateEntry(goal.id, limits.maxSourceBytes);
8446
+ if (!hydrated ||
8447
+ hydrated.source.retention === "retained-import" ||
8448
+ hydrated.entry.type !== "custom" ||
8449
+ hydrated.entry.customType !== GOAL_STATE_CUSTOM_TYPE ||
8450
+ !isPersistedGoalState(hydrated.entry.data))
8451
+ throw new RefineSkippedError("Automatic refinement goal source is unavailable or ineligible");
8452
+ goalState = normalizeGoalState(hydrated.entry.data);
8453
+ }
8454
+ let id = history.source.leafId;
8455
+ const maxEntries = limits.maxMessages;
8456
+ for (let scanned = 0; scanned < maxEntries; scanned++) {
8457
+ const entry = id ? await history.get(id) : undefined;
8458
+ if (id && !entry)
8459
+ throw new RefineSkippedError("Automatic refinement task frontier is unavailable");
8460
+ const message = entry?.kind === "message" ? entry : undefined;
8461
+ if (message || !entry)
8462
+ return {
8463
+ sessionId: history.source.sessionId,
8464
+ sessionFile: history.source.sessionFile,
8465
+ message: message ? [message.id, message.revision] : undefined,
8466
+ goal: goalEvidence(goalState),
8467
+ };
8468
+ id = entry.parentId;
8469
+ }
8470
+ throw new RefineSkippedError("Automatic refinement task frontier exceeds the context read limit");
8471
+ })
8472
+ : (() => {
8473
+ const branch = manager.getBranch().slice().reverse();
8474
+ const message = branch.find((entry) => entry.type === "message");
8475
+ return {
8476
+ sessionId: manager.getSessionId(),
8477
+ sessionFile: manager.getSessionFile(),
8478
+ message: message ? [message.id, message.timestamp] : undefined,
8479
+ goal: goalEvidence(this._loadPersistedGoalState()),
8480
+ };
8481
+ })();
8482
+ return {
8483
+ ...frontier,
8484
+ systemPrompt: this.agent.state.systemPrompt,
8485
+ model: JSON.stringify([this.model?.provider, this.model?.id, this.settingsManager.getAutoRefineModel()]),
8486
+ harnessEntries: Object.fromEntries(Object.entries(harnessState.entries).map(([kind, entries]) => [
8487
+ kind,
8488
+ Object.fromEntries(Object.entries(entries).map(([id, { version: _version, created_at: _created, updated_at: _updated, ...entry }]) => [id, entry])),
8489
+ ])),
8490
+ };
8491
+ }
8360
8492
  async _reviewAutoRefine(context, signal) {
8493
+ const harnessState = this._loadMergedHarnessState();
8494
+ const evidence = await this._autoRefineEvidence(harnessState);
8495
+ signal?.throwIfAborted();
8496
+ if (isDeepStrictEqual(evidence, this._lastAutoRefineEvidence))
8497
+ return { shouldRefine: false, rationale: "No new task evidence since the previous automatic review." };
8498
+ // Reserve before any inference wait. Equivalent running, declined or failed
8499
+ // automatic reviews do not buy another call; explicit /refine stays independent.
8500
+ this._lastAutoRefineEvidence = evidence;
8361
8501
  if (this._autoRefineReviewer)
8362
8502
  return this._autoRefineReviewer(context, signal);
8363
8503
  const selected = this._resolveRefinementModel();
@@ -8365,7 +8505,6 @@ export class AgentSession {
8365
8505
  return { shouldRefine: false, rationale: "No model selected." };
8366
8506
  const { model, thinkingLevel, explicit } = selected;
8367
8507
  const messages = structuredClone(this.agent.state.messages);
8368
- const harnessState = this._loadMergedHarnessState();
8369
8508
  const reviewContext = { ...context };
8370
8509
  const requests = this.requests[captureNativeReviewerRequests]();
8371
8510
  try {
@@ -8377,10 +8516,10 @@ export class AgentSession {
8377
8516
  await requests.dispose();
8378
8517
  }
8379
8518
  }
8380
- /** Global harness state overlaid with this session's local state, when persisted. */
8519
+ /** Merge existing session/global memory with the selected project's workspace memory. */
8381
8520
  _loadMergedHarnessState() {
8382
8521
  const localHarnessStateDir = this._localHarnessStateDir();
8383
- return mergeHarnessStates(loadHarnessState(getGlobalHarnessStateDir(), "global"), localHarnessStateDir ? loadHarnessState(localHarnessStateDir, "local") : undefined);
8522
+ return mergeHarnessStates(loadHarnessState(getGlobalHarnessStateDir(), "global"), localHarnessStateDir ? loadHarnessState(localHarnessStateDir, "local") : undefined, loadHarnessState(getWorkspaceHarnessStateDir(this._cwd), "workspace"));
8384
8523
  }
8385
8524
  async _loadRefinementHistory() {
8386
8525
  const limits = {
@@ -8838,7 +8977,21 @@ export class AgentSession {
8838
8977
  }
8839
8978
  if (!this._isCompactionOwnerCurrent(owner))
8840
8979
  return false;
8841
- const assistantIsFromBeforeCompaction = compactionTimestamp !== undefined && assistantMessage.timestamp <= compactionTimestamp;
8980
+ let assistantIsFromBeforeCompaction = compactionTimestamp !== undefined && assistantMessage.timestamp <= compactionTimestamp;
8981
+ const assistantEntryId = this._findAssistantEntryIdForMessage(assistantMessage);
8982
+ if (assistantEntryId && owner.manager.supportsCapturedHistoryReads()) {
8983
+ const ordered = await owner.manager.readBranchHistory(async (history) => {
8984
+ const assistant = await history.get(assistantEntryId);
8985
+ const boundary = (await history.branchBootstrap()).latestCompaction;
8986
+ return assistant && boundary ? assistant.sequence <= boundary.sequence : undefined;
8987
+ });
8988
+ if (!this._isCompactionOwnerCurrent(owner))
8989
+ return false;
8990
+ // Provider messages are timestamped before request preparation, which may
8991
+ // commit an epoch. Canonical append order, not that start time, owns staleness.
8992
+ if (ordered !== undefined)
8993
+ assistantIsFromBeforeCompaction = ordered;
8994
+ }
8842
8995
  // Case 1: Overflow - takes priority over a pending model request so the error
8843
8996
  // strip + retry still happen; the compaction it runs consumes the request.
8844
8997
  if (!assistantIsFromBeforeCompaction &&
@@ -8930,7 +9083,7 @@ export class AgentSession {
8930
9083
  this._emit({ type: "message_start", message: outcomeMessage });
8931
9084
  this._emit({ type: "message_end", message: outcomeMessage });
8932
9085
  }
8933
- async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false) {
9086
+ async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false, capacity) {
8934
9087
  const checkpoint = this._pendingCheckpoint;
8935
9088
  const pending = this._pendingRequestedCompaction;
8936
9089
  const owner = checkpoint?.owner ?? pending?.owner ?? capturedOwner ?? this._captureCompactionOwner();
@@ -9013,6 +9166,7 @@ export class AgentSession {
9013
9166
  signal: abort.signal,
9014
9167
  // Only confirmed public-budget recovery resumes within the same invocation.
9015
9168
  budgetPressure: resumeInPlace,
9169
+ capacity,
9016
9170
  });
9017
9171
  const result = committed.result;
9018
9172
  // Release this captured source before arming success. A later release failure still carries the ACK.
@@ -9608,7 +9762,12 @@ export class AgentSession {
9608
9762
  producer?.skillOwner?.writer.assertCurrent();
9609
9763
  if (this.sessionManager !== manager)
9610
9764
  throw new Error("Native recovery source changed");
9611
- return await manager.readBranchHistory((history) => recoverCapturedHistory(history.branchContext, request, { ...DEFAULT_NATIVE_RECOVERY_LIMITS, maxBytes: responseBytes }, signal));
9765
+ const cursorSource = JSON.stringify([manager.getSessionId(), manager.getSessionFile()]);
9766
+ if (this._nativeRecoveryCursorSource !== cursorSource) {
9767
+ this._nativeRecoveryCursors.clear();
9768
+ this._nativeRecoveryCursorSource = cursorSource;
9769
+ }
9770
+ return await manager.readBranchHistory((history) => recoverCapturedHistory(history.branchContext, request, { ...DEFAULT_NATIVE_RECOVERY_LIMITS, maxBytes: responseBytes }, signal, this._nativeRecoveryCursors));
9612
9771
  }
9613
9772
  catch (error) {
9614
9773
  signal?.throwIfAborted();
@@ -9757,6 +9916,7 @@ export class AgentSession {
9757
9916
  BASE_CONTEXT_RLM_DEPTH: String(this._rlmDepth),
9758
9917
  BASE_CONTEXT_RLM_MAX_DEPTH: String(this._rlmMaxDepth),
9759
9918
  BASE_CONTEXT_GLOBAL_HARNESS_STATE_DIR: getGlobalHarnessStateDir(),
9919
+ BASE_CONTEXT_WORKSPACE_HARNESS_STATE_DIR: getWorkspaceHarnessStateDir(this._cwd),
9760
9920
  };
9761
9921
  const rlmSessionDir = this._ensureRlmSessionDir();
9762
9922
  if (rlmSessionDir) {
@@ -9849,15 +10009,13 @@ export class AgentSession {
9849
10009
  if (message?.role !== "assistant")
9850
10010
  return;
9851
10011
  if (this.sessionManager.isPersisted()) {
9852
- const acknowledged = this._assistantEntryIds.get(message);
9853
- if (!acknowledged ||
9854
- acknowledged.sessionId !== this.sessionId ||
9855
- acknowledged.sessionFile !== this.sessionFile)
10012
+ const entryId = this._findAssistantEntryIdForMessage(message);
10013
+ if (!entryId)
9856
10014
  throw new Error("Cannot identify the acknowledged assistant response removed by retry control");
9857
10015
  if (this._contextOmissions?.sessionId !== this.sessionId ||
9858
10016
  this._contextOmissions?.sessionFile !== this.sessionFile)
9859
10017
  this._contextOmissions = { sessionId: this.sessionId, sessionFile: this.sessionFile, ids: new Set() };
9860
- this._contextOmissions.ids.add(acknowledged.entryId);
10018
+ this._contextOmissions.ids.add(entryId);
9861
10019
  }
9862
10020
  this.agent.state.messages = messages.slice(0, -1);
9863
10021
  }
@@ -11977,7 +12135,7 @@ export class AgentSession {
11977
12135
  if (label && !summaryText) {
11978
12136
  await this.sessionManager.appendLabelChange(targetId, label);
11979
12137
  }
11980
- const { context: sessionContext } = await readSessionBootstrap(this.sessionManager, this.settingsManager.getCanonicalContextLimits(), { allowPendingToolPublic: this._contextEpochsEnabled, initialContextMode: this._initialContextMode });
12138
+ const { context: sessionContext } = await readSessionBootstrap(this.sessionManager, this.settingsManager.getCanonicalContextLimits(), { purpose: "read", initialContextMode: this._initialContextMode });
11981
12139
  this.agent.state.messages = sessionContext.messages;
11982
12140
  this._contextOmissions = undefined;
11983
12141
  this._mergeUnpersistedOutcomes(this.agent.state.messages);