@ponythewhite/base-context 1.0.10 → 1.0.12

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 (125) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/base-context-runtime/pyproject.toml +1 -1
  3. package/dist/base-context-runtime/uv.lock +1 -1
  4. package/dist/build-info.json +1 -1
  5. package/dist/bundle/amazon-bedrock.js +3 -3
  6. package/dist/bundle/{anthropic-5PSCZEER.js → anthropic-BJVPUMPB.js} +2 -2
  7. package/dist/bundle/{azure-openai-responses-AYTLJR33.js → azure-openai-responses-ERRMAXK6.js} +4 -5
  8. package/dist/bundle/{bundled-modules-MX6YNAM2.js → bundled-modules-WCGPZI2H.js} +5 -5
  9. package/dist/bundle/{chunk-D3KOF57W.js → chunk-25VYRAC6.js} +14 -1
  10. package/dist/bundle/{chunk-2IPDSTDR.js → chunk-AEMRBMI2.js} +9 -9
  11. package/dist/bundle/{chunk-Q3MBSBEB.js → chunk-FOGKLC7V.js} +1 -1
  12. package/dist/bundle/{chunk-UHLGPYMI.js → chunk-IAKHCQA2.js} +185 -39
  13. package/dist/bundle/{chunk-KT2BNEPE.js → chunk-MUFZODP5.js} +1 -1
  14. package/dist/bundle/{chunk-QKIFBA7N.js → chunk-Q6UAHLII.js} +2 -2
  15. package/dist/bundle/{chunk-CDXQNS5P.js → chunk-YGV3Y4AD.js} +1009 -185
  16. package/dist/bundle/{cli-main-D7W3WWCV.js → cli-main-CTYDQHFE.js} +4 -4
  17. package/dist/bundle/cli.js +1 -1
  18. package/dist/bundle/{google-HTMCUJ7X.js → google-4GA4YMUX.js} +2 -2
  19. package/dist/bundle/{google-vertex-ZQO65AXF.js → google-vertex-7AMALD7D.js} +2 -2
  20. package/dist/bundle/{main-5J3QBZ5Y.js → main-GVGK354K.js} +5 -5
  21. package/dist/bundle/{mistral-SB7LU56G.js → mistral-74BXMNRM.js} +1 -1
  22. package/dist/bundle/{openai-codex-responses-5CEW4VN3.js → openai-codex-responses-COXNC4IH.js} +2 -2
  23. package/dist/bundle/{openai-completions-BAZJQSS2.js → openai-completions-BCSA5Q6S.js} +2 -2
  24. package/dist/bundle/{openai-responses-VHOE427P.js → openai-responses-ONCIZSJS.js} +3 -3
  25. package/dist/core/agent-session.d.ts +6 -1
  26. package/dist/core/agent-session.d.ts.map +1 -1
  27. package/dist/core/agent-session.js +445 -146
  28. package/dist/core/agent-session.js.map +1 -1
  29. package/dist/core/canonical-context.d.ts +19 -3
  30. package/dist/core/canonical-context.d.ts.map +1 -1
  31. package/dist/core/canonical-context.js +130 -9
  32. package/dist/core/canonical-context.js.map +1 -1
  33. package/dist/core/compaction/compaction.d.ts +9 -1
  34. package/dist/core/compaction/compaction.d.ts.map +1 -1
  35. package/dist/core/compaction/compaction.js +29 -3
  36. package/dist/core/compaction/compaction.js.map +1 -1
  37. package/dist/core/context-epoch-copy.d.ts +1 -1
  38. package/dist/core/context-epoch-copy.d.ts.map +1 -1
  39. package/dist/core/context-epoch-copy.js +18 -7
  40. package/dist/core/context-epoch-copy.js.map +1 -1
  41. package/dist/core/context-epoch.d.ts +4 -0
  42. package/dist/core/context-epoch.d.ts.map +1 -1
  43. package/dist/core/context-epoch.js +8 -1
  44. package/dist/core/context-epoch.js.map +1 -1
  45. package/dist/core/context-tree.d.ts +14 -11
  46. package/dist/core/context-tree.d.ts.map +1 -1
  47. package/dist/core/context-tree.js +145 -9
  48. package/dist/core/context-tree.js.map +1 -1
  49. package/dist/core/history-index-worker.d.ts.map +1 -1
  50. package/dist/core/history-index-worker.js +59 -6
  51. package/dist/core/history-index-worker.js.map +1 -1
  52. package/dist/core/history-index.d.ts +17 -0
  53. package/dist/core/history-index.d.ts.map +1 -1
  54. package/dist/core/history-index.js +22 -0
  55. package/dist/core/history-index.js.map +1 -1
  56. package/dist/core/inference-coordinator.d.ts +1 -1
  57. package/dist/core/inference-coordinator.d.ts.map +1 -1
  58. package/dist/core/inference-coordinator.js +23 -6
  59. package/dist/core/inference-coordinator.js.map +1 -1
  60. package/dist/core/messages.d.ts +2 -0
  61. package/dist/core/messages.d.ts.map +1 -1
  62. package/dist/core/messages.js +2 -0
  63. package/dist/core/messages.js.map +1 -1
  64. package/dist/core/refinement/refinement.d.ts +2 -0
  65. package/dist/core/refinement/refinement.d.ts.map +1 -1
  66. package/dist/core/refinement/refinement.js +10 -1
  67. package/dist/core/refinement/refinement.js.map +1 -1
  68. package/dist/core/request-usage.d.ts +34 -0
  69. package/dist/core/request-usage.d.ts.map +1 -0
  70. package/dist/core/request-usage.js +111 -0
  71. package/dist/core/request-usage.js.map +1 -0
  72. package/dist/core/request-view-selection.d.ts +2 -1
  73. package/dist/core/request-view-selection.d.ts.map +1 -1
  74. package/dist/core/request-view-selection.js +29 -2
  75. package/dist/core/request-view-selection.js.map +1 -1
  76. package/dist/core/session-fork-messages.d.ts +5 -0
  77. package/dist/core/session-fork-messages.d.ts.map +1 -1
  78. package/dist/core/session-fork-messages.js +44 -0
  79. package/dist/core/session-fork-messages.js.map +1 -1
  80. package/dist/core/session-history-index.d.ts +5 -0
  81. package/dist/core/session-history-index.d.ts.map +1 -1
  82. package/dist/core/session-history-index.js +7 -0
  83. package/dist/core/session-history-index.js.map +1 -1
  84. package/dist/core/session-manager.d.ts +2 -0
  85. package/dist/core/session-manager.d.ts.map +1 -1
  86. package/dist/core/session-manager.js +155 -13
  87. package/dist/core/session-manager.js.map +1 -1
  88. package/dist/core/session-tree.d.ts +9 -0
  89. package/dist/core/session-tree.d.ts.map +1 -1
  90. package/dist/core/session-tree.js +71 -11
  91. package/dist/core/session-tree.js.map +1 -1
  92. package/dist/core/settings-manager.d.ts +3 -0
  93. package/dist/core/settings-manager.d.ts.map +1 -1
  94. package/dist/core/settings-manager.js +7 -0
  95. package/dist/core/settings-manager.js.map +1 -1
  96. package/dist/core/system-prompt.d.ts +2 -0
  97. package/dist/core/system-prompt.d.ts.map +1 -1
  98. package/dist/core/system-prompt.js +4 -2
  99. package/dist/core/system-prompt.js.map +1 -1
  100. package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
  101. package/dist/modes/agent-connection/daemon-agent-connection.js +8 -1
  102. package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
  103. package/dist/modes/agent-connection/in-process-agent-connection.d.ts.map +1 -1
  104. package/dist/modes/agent-connection/in-process-agent-connection.js +4 -4
  105. package/dist/modes/agent-connection/in-process-agent-connection.js.map +1 -1
  106. package/dist/modes/daemon/daemon-mode.d.ts.map +1 -1
  107. package/dist/modes/daemon/daemon-mode.js +4 -4
  108. package/dist/modes/daemon/daemon-mode.js.map +1 -1
  109. package/dist/modes/daemon/daemon-protocol.d.ts +13 -3
  110. package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
  111. package/dist/modes/daemon/daemon-protocol.js +10 -2
  112. package/dist/modes/daemon/daemon-protocol.js.map +1 -1
  113. package/dist/modes/daemon/daemon-session-summarizer.d.ts +3 -0
  114. package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
  115. package/dist/modes/daemon/daemon-session-summarizer.js +71 -32
  116. package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
  117. package/dist/modes/interactive/components/context-tree-format.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/context-tree-format.js +49 -0
  119. package/dist/modes/interactive/components/context-tree-format.js.map +1 -1
  120. package/docs/compaction.md +27 -3
  121. package/docs/context-management.md +17 -1
  122. package/docs/rlm.md +1 -1
  123. package/docs/settings.md +12 -0
  124. package/docs/usage.md +24 -1
  125. package/package.json +4 -4
@@ -15,9 +15,9 @@ import { addLoginGuidanceToAuthError, formatAuthenticationFailedMessage, formatN
15
15
  import { addAutonomousContinuation, addAutonomousUsage, autonomousStatus, createAutonomousRuntimeState, nextAutonomousContinuation, refreshAutonomousQualityGates, setAutonomousEnabled, } from "./autonomous.js";
16
16
  import { executeBashWithOperations } from "./bash-executor.js";
17
17
  import { stringifyBoundedJson } from "./bounded-json.js";
18
- import { CanonicalContextCompiler, canonicalRecoveryBoundary, getCanonicalEpochContext, getCanonicalMessageSource, getCanonicalViewUnits, prepareCanonicalEpoch, prepareContextModeEpoch, prepareRecoveryCompaction, readCanonicalContextMode, } from "./canonical-context.js";
19
- import { COMPACT_SKILL_NAME, calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, prepareViewCompaction, serializeConversation, shouldCompact, takeNativeBranchSummaryWrite, } from "./compaction/index.js";
20
- import { appendContextEpoch, assertContextRequestContract, CONTEXT_EPOCH_DETAIL, CONTEXT_SKILL_EPOCH_RENDERER, contextEpochMode, contextEpochRepresentation, contextRequestContract, retainedContextRequestContract, snapshotContextEpoch, } from "./context-epoch.js";
18
+ import { CanonicalContextCompiler, canonicalRecoveryBoundary, getCanonicalEpochContext, getCanonicalMessageSource, getCanonicalViewSelectionSource, getCanonicalViewUnits, MissingRecoveryReplayContractError, prepareCanonicalEpoch, prepareContextModeEpoch, prepareRecoveryCompaction, readCanonicalContextMode, } from "./canonical-context.js";
19
+ import { COMPACT_SKILL_NAME, calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateFixedCompactionTokens, generateBranchSummary, prepareCompaction, prepareViewCompaction, serializeConversation, shouldCompact, takeNativeBranchSummaryWrite, } from "./compaction/index.js";
20
+ import { appendContextEpoch, assertContextRequestContract, CONTEXT_EPOCH_DETAIL, CONTEXT_SKILL_EPOCH_RENDERER, contextEpochMode, contextEpochRepresentation, contextRequestContract, readContextEpoch, retainedContextRequestContract, snapshotContextEpoch, UnsupportedContextEpochConfigurationError, } from "./context-epoch.js";
21
21
  import { ContextTreeRequest, computeOwnAndTotalUsage, loadContextTreeChildFromDisk, loadContextTreeChildrenFromDisk, readContextTreeUsage, readResidentContextTreeUsage, } from "./context-tree.js";
22
22
  import { normalizeHeartbeatDeliveryMode } from "./cron-jobs.js";
23
23
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
@@ -28,11 +28,11 @@ import { emitSessionShutdownEvent } from "./extensions/runner.js";
28
28
  import { createGoalContextMessage, emptyGoalState, GOAL_CONTEXT_CUSTOM_TYPE, GOAL_CONTEXT_PREVIEW_LABEL, GOAL_SKILL_NAME, GOAL_STATE_CUSTOM_TYPE, goalHostResponse, goalTokenDeltaForUsage, isPersistedGoalState, normalizeGoalState, validateGoalBudget, validateGoalObjective, } from "./goals.js";
29
29
  import { captureNativeBranchRequests, captureNativeCompactionRequests, captureNativePlannerRequests, captureNativeReviewerRequests, InferenceCoordinator, takeNativeInferenceAuthSource, } from "./inference-coordinator.js";
30
30
  import { snapshotPathIn } from "./kernel/state-snapshot.js";
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";
31
+ import { convertToLlm, createCompactionOutcomeMessage, createHeartbeatPromptMessage, createRefinementOutcomeMessage, createRlmChildFailureMessage, createRlmChildTerminalNoticeMessage, createSessionSlashCommandMessage, createSessionSlashCommandResultMessage, HARNESS_SNAPSHOT_CUSTOM_TYPE, 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, getWorkspaceHarnessStateDir, inferRefinementResultScope, loadGlobalRefinementHistory, loadHarnessState, mergeHarnessStates, mergeRefinementHistory, normalizeRefinementProposal, planRefinement, prepareRefinementApplication, REFINE_SKILL_NAME, reviewAutoRefine, saveHarnessState, takeNativePlannerRequestWrite, withRefinementBaseline, } from "./refinement/index.js";
35
- import { PublicContextBudgetError } from "./request-view-selection.js";
34
+ import { appendGlobalRefinement, applyRefinementProposal, formatHarnessStateForPrompt, 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
+ import { getRecoveryCompactionAuthorization, 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";
@@ -45,8 +45,9 @@ import { appendSentAgentMessageToToolResult, IPYTHON_SENT_AGENT_MESSAGE_CUSTOM_E
45
45
  import { ContextUsageReader } from "./session-context-usage.js";
46
46
  import { bindNativeEntryWriter, } from "./session-entry-origin.js";
47
47
  import { readUserMessagesForForking } from "./session-fork-messages.js";
48
+ import { hydrateCapturedHistoryEntry, } from "./session-history-index.js";
48
49
  import { exportSessionBranchToJsonl } from "./session-jsonl-export.js";
49
- import { applyChildUsageAttributions, getLatestCompactionEntry, SessionManager, } from "./session-manager.js";
50
+ import { getLatestCompactionEntry, SessionManager, } from "./session-manager.js";
50
51
  import { getPythonSkillRuntimeInfo } from "./skills.js";
51
52
  import { parseRefineCommandOptions, parseSessionSlashCommand, parseSlashCommand, } from "./slash-commands.js";
52
53
  import { createSyntheticSourceInfo } from "./source-info.js";
@@ -191,6 +192,7 @@ function createPostCompactionContinuationSettlement(resume) {
191
192
  return { ...createAgentMessageDeferred(), resume, settled: false };
192
193
  }
193
194
  import { LocalRlmSubagentCapacity, } from "./rlm-max-subagents.js";
195
+ const RLM_PARENT_USAGE_CUSTOM_TYPE = "rlm_parent_usage";
194
196
  const KERNEL_STATE_LISTING_TIMEOUT_MS = 5000;
195
197
  const RLM_MAX_DEPTH_STATE_CUSTOM_TYPE = "rlm_max_depth_state";
196
198
  function noopRlmChildAbort() { }
@@ -435,6 +437,8 @@ export class AgentSession {
435
437
  // Kept alive for retained children so nested updates (e.g. a grandchild cancel)
436
438
  // still forward to root; torn down when the retained child is disposed.
437
439
  _rlmChildUnsubscribes = new Map();
440
+ _rlmChildUsageSources = new Map();
441
+ _rlmParentUsageAttribution;
438
442
  /** Latest recap for this session, written by the daemon summarizer; read by a parent to label its child snapshots. */
439
443
  _currentRecap;
440
444
  _initialGoal;
@@ -559,11 +563,13 @@ export class AgentSession {
559
563
  await this._goalResumeOperation;
560
564
  await this._waitForAgentEventsBeforeContext();
561
565
  await this._waitForChildUsageWrites();
566
+ const assertHarnessSourceCurrent = await this._appendHarnessSnapshotIfChanged();
562
567
  await this.sessionManager.flushNow();
568
+ assertHarnessSourceCurrent();
563
569
  // In-memory sessions have no canonical archive. This is an explicit mode, not an index-error fallback.
564
570
  if (!this.sessionManager.isPersisted()) {
565
571
  if (!this._compactionSetupFailure)
566
- return;
572
+ return { messages: this.agent.state.messages, adoptMessages: true };
567
573
  const rebuilt = await readSessionBootstrap(this.sessionManager, this.settingsManager.getCanonicalContextLimits());
568
574
  this._compactionSetupFailure = undefined;
569
575
  return { messages: rebuilt.context.messages, adoptMessages: true };
@@ -616,7 +622,10 @@ export class AgentSession {
616
622
  if (epochContext)
617
623
  this._contextMode = epochContext.mode;
618
624
  const unbudgetedPublic = !contextEpochsEnabled &&
619
- Boolean(epochContext?.toolContinuations?.length || epochContext?.taskFrameRebased || epochContext?.checkpoint);
625
+ Boolean(epochContext?.toolContinuations?.length ||
626
+ epochContext?.taskFrameRebased ||
627
+ epochContext?.checkpoint ||
628
+ getCanonicalViewSelectionSource(messages)?.recoveryContractRequested);
620
629
  const nativeSkills = Boolean(contextEpochsEnabled || epochContext?.checkpoint || unbudgetedPublic);
621
630
  const skillPolicy = nativeSkills ? (this._nativeRecoveryEnabled() ? "enabled" : "unavailable") : undefined;
622
631
  if (this._baseSystemPromptOptions.nativeSkillSelection !== skillPolicy) {
@@ -663,7 +672,37 @@ export class AgentSession {
663
672
  candidate.projection.pendingPublicMessageGroups?.length))
664
673
  throw new Error("Tool continuation requires its exact public candidate before epoch ACK");
665
674
  assertResourceCurrent(resource);
666
- const representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes, unbudgetedPublic, candidate.responseItemIdentity);
675
+ let representation;
676
+ try {
677
+ representation = contextEpochRepresentation(candidate.request, candidate.assessment, limits.maxSourceBytes, unbudgetedPublic, candidate.responseItemIdentity);
678
+ }
679
+ catch (error) {
680
+ if (error instanceof UnsupportedContextEpochConfigurationError &&
681
+ !contextEpochsEnabled &&
682
+ !candidate.assessment &&
683
+ !committed &&
684
+ !acknowledged &&
685
+ !epochContext.taskFrameRebased &&
686
+ !epochContext.selectedSkills?.length &&
687
+ !epochContext.toolContinuations?.length &&
688
+ !candidate.publicMessages) {
689
+ const source = getCanonicalViewSelectionSource(messages);
690
+ const selected = new Set(candidate.selectedUnitIds);
691
+ if (source.recoveryContractRequested &&
692
+ !source.requiresEpoch &&
693
+ !source.pendingPublicMessageGroups?.length &&
694
+ this.sessionManager === epochManager &&
695
+ compaction.isCurrent() &&
696
+ JSON.stringify(candidate.source) === JSON.stringify(source.source) &&
697
+ candidate.selectedUnitIds.length === source.units.length &&
698
+ selected.size === source.units.length &&
699
+ source.units.every((unit) => selected.has(unit.id))) {
700
+ // Optional capture only. Keep the unchanged full-native offer; grant no epoch or omission.
701
+ return;
702
+ }
703
+ }
704
+ throw error;
705
+ }
667
706
  const replayContract = "replayContract" in candidate.projection &&
668
707
  candidate.projection.replayContract === "message-groups"
669
708
  ? "message-groups"
@@ -2916,7 +2955,8 @@ export class AgentSession {
2916
2955
  if (compactionTimestamp !== undefined && context.message.timestamp <= compactionTimestamp)
2917
2956
  return false;
2918
2957
  const contextTokens = this._getThresholdContextTokens(context.message, compactionTimestamp);
2919
- if (contextTokens === undefined || !shouldCompact(contextTokens, contextWindow, settings))
2958
+ if (contextTokens === undefined ||
2959
+ !shouldCompact(contextTokens, contextWindow, settings, estimateFixedCompactionTokens(this.systemPrompt, this.agent.state.tools, this.messages)))
2920
2960
  return false;
2921
2961
  if (this._hasFailedThresholdCompaction())
2922
2962
  return false;
@@ -3280,7 +3320,7 @@ export class AgentSession {
3280
3320
  }
3281
3321
  return {
3282
3322
  scheduled: true,
3283
- note: "Refinement runs when the current turn ends; the harness rebuilds the system prompt and resumes you automatically. Continue working normally.",
3323
+ note: "Refinement runs when the current turn ends; fresh harness advice is added to the next request and you resume automatically. Continue working normally.",
3284
3324
  };
3285
3325
  }
3286
3326
  default:
@@ -4348,6 +4388,7 @@ export class AgentSession {
4348
4388
  for (const { session } of this._rlmChildSessions.values())
4349
4389
  await drain(() => session.disposeAsync());
4350
4390
  this._rlmChildSessions.clear();
4391
+ this._rlmChildUsageSources.clear();
4351
4392
  this._rlmChildCleanupFailures.clear();
4352
4393
  this._deletedRlmChildIds.clear();
4353
4394
  // Stop kernel-backed tools before waiting for their terminal agent events.
@@ -4443,6 +4484,7 @@ export class AgentSession {
4443
4484
  session.dispose();
4444
4485
  }
4445
4486
  this._rlmChildSessions.clear();
4487
+ this._rlmChildUsageSources.clear();
4446
4488
  this._rlmChildCleanupFailures.clear();
4447
4489
  this._deletedRlmChildIds.clear();
4448
4490
  this._pendingNextTurnMessages = [];
@@ -4687,6 +4729,7 @@ export class AgentSession {
4687
4729
  rlmDepth: this._rlmDepth,
4688
4730
  rlmParentAgent: this._rlmParentAgent,
4689
4731
  harnessState: this._loadMergedHarnessState(),
4732
+ harnessSection: "instructions",
4690
4733
  errorFixSelection: {
4691
4734
  enabled: this.settingsManager.getLearningEnabled(),
4692
4735
  tools: validToolNames,
@@ -4699,6 +4742,118 @@ export class AgentSession {
4699
4742
  };
4700
4743
  return buildSystemPrompt(this._baseSystemPromptOptions);
4701
4744
  }
4745
+ /** Read only snapshot candidates in the active literal tail and accepted epoch recipes. */
4746
+ async _readHarnessSnapshot(view) {
4747
+ const { maxMessages, maxSourceBytes } = this.settingsManager.getCanonicalContextLimits();
4748
+ const refs = [];
4749
+ let summary = null;
4750
+ let cursor;
4751
+ let count = 0;
4752
+ let bytes = 0;
4753
+ do {
4754
+ const page = await view.contextManifest({ cursor, limit: 128 });
4755
+ if (page.selection !== "known")
4756
+ throw new Error(`Harness snapshot context selection is ${page.selection}`);
4757
+ summary = page.summaryRef;
4758
+ count += page.refs.length;
4759
+ if (count + (summary ? 1 : 0) > maxMessages)
4760
+ throw new Error("Harness snapshot context message budget exceeded");
4761
+ refs.push(...page.refs.filter((ref) => ref.kind === "custom_message"));
4762
+ cursor = page.nextCursor ?? undefined;
4763
+ } while (cursor);
4764
+ const read = async (ref, source = view) => {
4765
+ bytes += ref.locator.length;
4766
+ if (bytes > maxSourceBytes)
4767
+ throw new Error("Harness snapshot source byte budget exceeded");
4768
+ const metadata = await source.get(ref.entryId);
4769
+ if (!metadata || metadata.revision !== ref.revision)
4770
+ throw new Error("Harness snapshot source is unavailable");
4771
+ const hydrated = await hydrateCapturedHistoryEntry(metadata, ref.locator.length, source.readPayload);
4772
+ if (!hydrated)
4773
+ throw new Error("Harness snapshot payload is unavailable");
4774
+ return hydrated.entry;
4775
+ };
4776
+ const content = (entry) => entry.type === "custom_message" &&
4777
+ entry.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE &&
4778
+ typeof entry.content === "string"
4779
+ ? entry.content
4780
+ : undefined;
4781
+ for (const ref of refs.reverse()) {
4782
+ const snapshot = content(await read(ref));
4783
+ if (snapshot !== undefined)
4784
+ return snapshot;
4785
+ }
4786
+ if (!summary)
4787
+ return;
4788
+ const entry = await read(summary);
4789
+ if (entry.type !== "compaction" ||
4790
+ summary.qualification !== "native-context-epoch" ||
4791
+ summary.retention === "retained-import")
4792
+ return;
4793
+ const checkpoint = readContextEpoch(entry.details, maxSourceBytes);
4794
+ if (!checkpoint)
4795
+ return;
4796
+ if (count + checkpoint.views.length > maxMessages)
4797
+ throw new Error("Harness snapshot context message budget exceeded");
4798
+ for (const pinned of [...checkpoint.views].reverse()) {
4799
+ if (pinned.ref.kind !== "custom_message")
4800
+ continue;
4801
+ if (!view.atSnapshot)
4802
+ throw new Error("Harness snapshot epoch requires its captured source");
4803
+ const snapshot = content(await read(pinned.ref, await view.atSnapshot(pinned.source)));
4804
+ if (snapshot !== undefined)
4805
+ return snapshot;
4806
+ }
4807
+ }
4808
+ /** Persist mutable advice before the request owner captures its source and epoch ACK boundary. */
4809
+ async _appendHarnessSnapshotIfChanged() {
4810
+ const manager = this.sessionManager;
4811
+ let leafId = manager.getLeafId();
4812
+ const sourceIsCurrent = manager.captureCompactionSourceOwner();
4813
+ const assertCurrent = () => {
4814
+ if (this._disposed || this.sessionManager !== manager || !sourceIsCurrent() || manager.getLeafId() !== leafId)
4815
+ throw new Error("Harness snapshot source changed before request ownership");
4816
+ };
4817
+ let previous;
4818
+ if (manager.isPersisted()) {
4819
+ previous = await manager.readBranchHistory((view) => this._readHarnessSnapshot(view.branchContext));
4820
+ }
4821
+ else {
4822
+ const snapshot = manager
4823
+ .buildSessionContext()
4824
+ .messages.reverse()
4825
+ .find((message) => message.role === "custom" && message.customType === HARNESS_SNAPSHOT_CUSTOM_TYPE);
4826
+ if (snapshot?.role === "custom")
4827
+ previous = snapshot.content;
4828
+ }
4829
+ assertCurrent();
4830
+ const options = this._baseSystemPromptOptions;
4831
+ const tools = options.selectedTools ?? [];
4832
+ const hasIpython = tools.includes("ipython");
4833
+ const content = formatHarnessStateForPrompt(this._loadMergedHarnessState(), {
4834
+ section: "entries",
4835
+ includeIpythonExamples: hasIpython,
4836
+ includeShellExamples: tools.includes("bash"),
4837
+ errorFixSelection: options.errorFixSelection
4838
+ ? { ...options.errorFixSelection, enabled: this.settingsManager.getLearningEnabled() }
4839
+ : undefined,
4840
+ });
4841
+ if (previous === content)
4842
+ return assertCurrent;
4843
+ const entryId = await manager.appendCustomMessageEntryWithRollback(HARNESS_SNAPSHOT_CUSTOM_TYPE, content, false);
4844
+ if (this._disposed || this.sessionManager !== manager || !sourceIsCurrent() || manager.getLeafId() !== entryId)
4845
+ throw new Error("Harness snapshot source changed during append");
4846
+ if (!manager.isPersisted())
4847
+ this.agent.state.messages.push({
4848
+ role: "custom",
4849
+ customType: HARNESS_SNAPSHOT_CUSTOM_TYPE,
4850
+ content,
4851
+ display: false,
4852
+ timestamp: Date.now(),
4853
+ });
4854
+ leafId = entryId;
4855
+ return assertCurrent;
4856
+ }
4702
4857
  _refreshExtensionSystemPrompt(extensionPrompt, baseSnapshot) {
4703
4858
  if (this._baseSystemPrompt === baseSnapshot) {
4704
4859
  return extensionPrompt;
@@ -4755,7 +4910,7 @@ export class AgentSession {
4755
4910
  async _runPreTurnCompaction() {
4756
4911
  const lastAssistant = this._findLastAssistantMessage();
4757
4912
  if (lastAssistant)
4758
- await this._checkCompaction(lastAssistant, false, false);
4913
+ await this._checkCompaction(lastAssistant, false, false, undefined, true);
4759
4914
  }
4760
4915
  async _prepareForCommit(policy, steps) {
4761
4916
  if (policy.initialRefineBarrier === "always" ||
@@ -7727,7 +7882,7 @@ export class AgentSession {
7727
7882
  }
7728
7883
  }
7729
7884
  /** A private compiled context keeps extension summary edits separate from retained source recipes. */
7730
- async _prepareCapturedCompaction(pathEntries, settings, requests, compaction, owner = this._captureCompactionOwner(), allowShortSession = false, budgetPressure = false) {
7885
+ async _prepareCapturedCompaction(pathEntries, settings, requests, compaction, owner = this._captureCompactionOwner(), allowShortSession = false, budgetPressure = false, capacity) {
7731
7886
  this._assertCompactionOwner(owner);
7732
7887
  const limits = this.settingsManager.getCanonicalContextLimits();
7733
7888
  const resource = this._captureKernelResource();
@@ -7744,13 +7899,14 @@ export class AgentSession {
7744
7899
  const context = getCanonicalEpochContext(messages);
7745
7900
  if (context.checkpoint?.includeSummary && pathEntries.at(-1)?.type === "compaction")
7746
7901
  return { preparation: undefined, maxSourceBytes: limits.maxSourceBytes };
7747
- const boundary = canonicalRecoveryBoundary(messages);
7902
+ const authorization = getRecoveryCompactionAuthorization(capacity);
7903
+ const boundary = canonicalRecoveryBoundary(messages, authorization);
7748
7904
  const preparation = budgetPressure || context.checkpoint || boundary
7749
7905
  ? prepareViewCompaction(messages, context.references.map((ref) => ref?.ref.entryId), pathEntries, settings, boundary, allowShortSession, budgetPressure)
7750
7906
  : prepareCompaction(pathEntries, settings, allowShortSession);
7751
7907
  // Refuse unsupported public data or open groups before starting the summary model call.
7752
7908
  if (preparation)
7753
- prepareRecoveryCompaction(messages, preparation.firstKeptEntryId, limits.maxSourceBytes);
7909
+ prepareRecoveryCompaction(messages, preparation.firstKeptEntryId, limits.maxSourceBytes, authorization);
7754
7910
  return {
7755
7911
  preparation,
7756
7912
  messages,
@@ -7766,7 +7922,7 @@ export class AgentSession {
7766
7922
  */
7767
7923
  async _performCompaction(options) {
7768
7924
  const { model, apiKey, headers, customInstructions, signal, thinkingLevel, requests, compaction, semanticEdges, pathEntries, settings, owner = this._captureCompactionOwner(), allowShortSession = false, budgetPressure = false, } = options;
7769
- const prepared = await this._prepareCapturedCompaction(pathEntries, settings, requests, compaction, owner, allowShortSession, budgetPressure);
7925
+ const prepared = await this._prepareCapturedCompaction(pathEntries, settings, requests, compaction, owner, allowShortSession, budgetPressure, options.capacity);
7770
7926
  this._assertCompactionOwner(owner);
7771
7927
  if (prepared.resource)
7772
7928
  assertResourceCurrent(prepared.resource);
@@ -7864,7 +8020,7 @@ export class AgentSession {
7864
8020
  this._assertCompactionOwner(owner);
7865
8021
  const result = JSON.parse(JSON.stringify({ summary, firstKeptEntryId, tokensBefore, details }));
7866
8022
  const recovery = prepared.messages
7867
- ? prepareRecoveryCompaction(prepared.messages, firstKeptEntryId, prepared.maxSourceBytes)
8023
+ ? prepareRecoveryCompaction(prepared.messages, firstKeptEntryId, prepared.maxSourceBytes, getRecoveryCompactionAuthorization(options.capacity))
7868
8024
  : undefined;
7869
8025
  if (prepared.resource)
7870
8026
  assertResourceCurrent(prepared.resource);
@@ -8557,7 +8713,31 @@ export class AgentSession {
8557
8713
  const sessionHistory = (async () => {
8558
8714
  if (residentEntries !== undefined)
8559
8715
  return residentEntries;
8560
- return (await this.sessionManager.materializeSourceHistory(limits)).entries.map(({ entry }) => entry);
8716
+ return this.sessionManager.readSourceHistory(async (history) => {
8717
+ const entries = [];
8718
+ let sourceBytes = 0;
8719
+ let after = 0;
8720
+ for (;;) {
8721
+ const page = await history.page(after, 128);
8722
+ if (page.indexedThrough < history.source.sourceSequence)
8723
+ throw new Error("Captured refinement history has incomplete index coverage");
8724
+ for (const reference of page.events) {
8725
+ if (reference.kind !== "custom")
8726
+ continue;
8727
+ // Refinement consumes custom records, not archived conversation or request bodies.
8728
+ if (entries.length >= limits.maxEntries)
8729
+ throw new Error("Refinement history entry budget exceeded");
8730
+ if (sourceBytes + reference.locator.length > limits.maxSourceBytes)
8731
+ throw new Error("Refinement history source byte budget exceeded");
8732
+ const hydrated = await hydrateCapturedHistoryEntry(reference, limits.maxSourceBytes - sourceBytes, history.readPayload);
8733
+ entries.push(hydrated.entry);
8734
+ sourceBytes += reference.locator.length;
8735
+ }
8736
+ if (page.nextAfter === null)
8737
+ return entries;
8738
+ after = page.nextAfter;
8739
+ }
8740
+ });
8561
8741
  })();
8562
8742
  const [globalResult, sessionResult] = await Promise.allSettled([globalHistory, sessionHistory]);
8563
8743
  if (globalResult.status === "rejected") {
@@ -8869,8 +9049,12 @@ export class AgentSession {
8869
9049
  }
8870
9050
  if (refinementAuditAppendError)
8871
9051
  throw refinementAuditAppendError.error;
8872
- this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
8873
- this.agent.state.systemPrompt = this._baseSystemPrompt;
9052
+ // Keep extension-facing state fresh, but do not rewrite the cached system prefix.
9053
+ this._baseSystemPromptOptions = {
9054
+ ...this._baseSystemPromptOptions,
9055
+ harnessState: this._loadMergedHarnessState(),
9056
+ };
9057
+ // The next owned request appends the new advice before capturing its source.
8874
9058
  try {
8875
9059
  this._emit({ type: "refine_complete", result });
8876
9060
  }
@@ -8935,7 +9119,7 @@ export class AgentSession {
8935
9119
  return undefined;
8936
9120
  return calculateContextTokens(assistantMessage.usage);
8937
9121
  }
8938
- async _checkCompaction(assistantMessage, skipAbortedCheck = true, queueAutonomousContinuation = true, invocationOwner) {
9122
+ async _checkCompaction(assistantMessage, skipAbortedCheck = true, queueAutonomousContinuation = true, invocationOwner, beforeNextTurn = false) {
8939
9123
  if (this._compactionSetupFailure)
8940
9124
  return false;
8941
9125
  if (invocationOwner && !this._isCompactionSourceOwnerCurrent(invocationOwner))
@@ -9036,7 +9220,7 @@ export class AgentSession {
9036
9220
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
9037
9221
  this._removeLastAssistantFromContext();
9038
9222
  }
9039
- return await this._runAutoCompaction("overflow", true, owner);
9223
+ return await this._runAutoCompaction("overflow", true, owner, false, undefined, beforeNextTurn);
9040
9224
  }
9041
9225
  if (this._pendingRequestedCompaction !== undefined) {
9042
9226
  return await this._runAutoCompaction("requested", false, owner);
@@ -9052,7 +9236,7 @@ export class AgentSession {
9052
9236
  const contextTokens = this._getThresholdContextTokens(assistantMessage, compactionTimestamp);
9053
9237
  if (contextTokens === undefined)
9054
9238
  return false;
9055
- if (shouldCompact(contextTokens, contextWindow, settings)) {
9239
+ if (shouldCompact(contextTokens, contextWindow, settings, estimateFixedCompactionTokens(this.systemPrompt, this.agent.state.tools, this.messages))) {
9056
9240
  if (this._hasFailedThresholdCompaction())
9057
9241
  return false;
9058
9242
  if (!this._pendingCheckpoint && queueAutonomousContinuation) {
@@ -9063,7 +9247,7 @@ export class AgentSession {
9063
9247
  }
9064
9248
  if (!this._isCompactionOwnerCurrent(owner) || !this._contextOptimizationAllowed())
9065
9249
  return false;
9066
- return await this._runAutoCompaction("threshold", false, owner);
9250
+ return await this._runAutoCompaction("threshold", false, owner, false, undefined, beforeNextTurn);
9067
9251
  }
9068
9252
  return false;
9069
9253
  }
@@ -9109,7 +9293,7 @@ export class AgentSession {
9109
9293
  this._emit({ type: "message_start", message: outcomeMessage });
9110
9294
  this._emit({ type: "message_end", message: outcomeMessage });
9111
9295
  }
9112
- async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false, capacity) {
9296
+ async _runAutoCompaction(reason, willRetry, capturedOwner, resumeInPlace = false, capacity, beforeNextTurn = false) {
9113
9297
  const checkpoint = this._pendingCheckpoint;
9114
9298
  const pending = this._pendingRequestedCompaction;
9115
9299
  const owner = checkpoint?.owner ?? pending?.owner ?? capturedOwner ?? this._captureCompactionOwner();
@@ -9245,6 +9429,13 @@ export class AgentSession {
9245
9429
  if (!this._isCompactionSourceOwnerCurrent(owner))
9246
9430
  throw failure;
9247
9431
  this._clearQueuedAutonomousContinuationsAfterSkippedThresholdCompaction(reason === "threshold" && shouldContinueAfterCompaction, queuedAutonomousContinuationsForThisCompaction);
9432
+ if (beforeNextTurn && error instanceof MissingRecoveryReplayContractError) {
9433
+ // Let the imminent MAIN obtain its own actual projection/ACK. This was not a recovery attempt.
9434
+ if (reason === "overflow")
9435
+ this._overflowRecovery = "idle";
9436
+ await this._endCompactionUnsuccessfully(reason, "skipped", "Compaction deferred until the next native request establishes recovery replay coverage.", { errorSeverity: "warning", customInstructions, owner });
9437
+ return false;
9438
+ }
9248
9439
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
9249
9440
  const aborted = errorMessage === "Compaction cancelled" ||
9250
9441
  (error instanceof Error && error.name === "AbortError") ||
@@ -10625,6 +10816,7 @@ export class AgentSession {
10625
10816
  this._rlmChildUnsubscribes.get(childId)?.();
10626
10817
  this._rlmChildUnsubscribes.delete(childId);
10627
10818
  this._rlmChildSessions.delete(childId);
10819
+ this._rlmChildUsageSources.delete(childId);
10628
10820
  this._rlmChildCleanupFailures.delete(childId);
10629
10821
  this._abandonedRlmQuiescenceChildIds.delete(childId);
10630
10822
  if (!run || this._activeRlmChildRuns.get(childId) === run) {
@@ -10710,6 +10902,170 @@ export class AgentSession {
10710
10902
  this._removeRlmSubagentTracking(childId);
10711
10903
  return { subagent };
10712
10904
  }
10905
+ async _restoreRlmChildUsageSource(childId, child) {
10906
+ if (this._rlmChildUsageSources.has(childId))
10907
+ return;
10908
+ const readSource = (entry) => {
10909
+ if (entry.type !== "custom" || entry.customType !== RLM_PARENT_USAGE_CUSTOM_TYPE)
10910
+ return;
10911
+ const data = entry.data;
10912
+ if (!data ||
10913
+ data.sessionId !== this.sessionId ||
10914
+ data.sessionFile !== this.sessionFile ||
10915
+ typeof data.entryId !== "string")
10916
+ return;
10917
+ return { sessionId: data.sessionId, sessionFile: data.sessionFile, entryId: data.entryId };
10918
+ };
10919
+ const source = child.sessionManager.supportsCapturedHistoryReads()
10920
+ ? await child.sessionManager.readBranchHistory(async (history) => {
10921
+ let after = 0;
10922
+ while (true) {
10923
+ const page = await history.page(after, 64);
10924
+ for (const ref of page.events) {
10925
+ if (ref.kind !== "custom" || ref.locator.length > 16 * 1024)
10926
+ continue;
10927
+ const record = await history.hydrateEntry(ref.id, 16 * 1024);
10928
+ const source = record && readSource(record.entry);
10929
+ if (source)
10930
+ return source;
10931
+ }
10932
+ if (page.nextAfter === null)
10933
+ return;
10934
+ after = page.nextAfter;
10935
+ }
10936
+ })
10937
+ : child.sessionManager
10938
+ .getBranch()
10939
+ .map(readSource)
10940
+ .find((source) => source !== undefined);
10941
+ if (!source || source.sessionId !== this.sessionId || source.sessionFile !== this.sessionFile)
10942
+ return;
10943
+ const target = await this.sessionManager.readEntry(source.entryId);
10944
+ if (target?.type !== "message" || target.message.role !== "assistant")
10945
+ return;
10946
+ if (source.sessionId !== this.sessionId || source.sessionFile !== this.sessionFile)
10947
+ return;
10948
+ this._rlmChildUsageSources.set(childId, source);
10949
+ }
10950
+ _subscribeRlmChildEvents(childId, child, run, onUsageWrite) {
10951
+ let runningToolCount = 0;
10952
+ const emitChildUpdate = () => {
10953
+ if (run)
10954
+ run.emitUpdate?.();
10955
+ else
10956
+ this._emit({ type: "rlm_child_update", child: this._rlmChildSnapshotForSession(childId, child) });
10957
+ };
10958
+ const attributeUsage = (usage, origin) => {
10959
+ const source = this._rlmChildUsageSources.get(childId);
10960
+ if (!source || source.sessionId !== this.sessionId || source.sessionFile !== this.sessionFile)
10961
+ return Promise.resolve();
10962
+ const delta = structuredClone(usage);
10963
+ const target = this.messages.find((message) => message.role === "assistant" && this._findAssistantEntryIdForMessage(message) === source.entryId);
10964
+ const refreshOutput = this._refreshInvocationOutput;
10965
+ const forwardUsage = this._rlmParentUsageAttribution;
10966
+ const write = this.sessionManager
10967
+ .appendChildUsageAttributionWithAggregate(source.entryId, delta, undefined, origin)
10968
+ .then(async ({ aggregateUsage }) => {
10969
+ if (target) {
10970
+ target.usage = structuredClone(aggregateUsage);
10971
+ refreshOutput?.(target);
10972
+ }
10973
+ // Ancestors receive only this newly acknowledged delta, never the aggregate again.
10974
+ await forwardUsage?.(delta, origin);
10975
+ });
10976
+ this._childUsageWrites.add(write);
10977
+ onUsageWrite?.(write);
10978
+ void write.then(() => this._childUsageWrites.delete(write), (error) => {
10979
+ this._childUsageWrites.delete(write);
10980
+ if (run)
10981
+ run.error = `Child usage attribution was not acknowledged: ${this._asError(error).message}`;
10982
+ this._surfaceSessionInputError(error);
10983
+ });
10984
+ return write;
10985
+ };
10986
+ child._rlmParentUsageAttribution = attributeUsage;
10987
+ const unsubscribe = child.subscribe((event) => {
10988
+ if (event.type === "rlm_child_update") {
10989
+ this._emit(event);
10990
+ return;
10991
+ }
10992
+ if (event.type === "agent_start") {
10993
+ if (run)
10994
+ run.activity = { kind: "waiting" };
10995
+ emitChildUpdate();
10996
+ }
10997
+ else if (event.type === "agent_end") {
10998
+ if (run)
10999
+ run.activity = undefined;
11000
+ emitChildUpdate();
11001
+ }
11002
+ else if (event.type === "message_end" && event.message.role === "assistant") {
11003
+ const assistant = event.message;
11004
+ if (run) {
11005
+ run.terminalAssistantOutcome = {
11006
+ stopReason: assistant.stopReason,
11007
+ errorMessage: assistant.errorMessage,
11008
+ };
11009
+ }
11010
+ const update = () => {
11011
+ const text = compactRlmText(readAssistantText(assistant));
11012
+ if (run && text)
11013
+ run.answerPreview = text;
11014
+ emitChildUpdate();
11015
+ };
11016
+ if (assistant.stopReason !== "error" && assistant.stopReason !== "aborted") {
11017
+ const messages = child.messages;
11018
+ const precedingPrompt = messages
11019
+ .slice(0, messages.lastIndexOf(assistant))
11020
+ .reverse()
11021
+ .find((message) => message.role === "user" ||
11022
+ (message.role === "custom" && message.customType !== HARNESS_SNAPSHOT_CUSTOM_TYPE));
11023
+ const origin = precedingPrompt?.role === "custom" && isAgentSessionMessage(precedingPrompt)
11024
+ ? precedingPrompt.details.id.startsWith("spawn:")
11025
+ ? "spawn_task"
11026
+ : "agent_message"
11027
+ : "direct_user";
11028
+ void attributeUsage(assistant.usage, origin).then(update, () => { });
11029
+ }
11030
+ else
11031
+ update();
11032
+ }
11033
+ else if (event.type === "message_start" || event.type === "message_update") {
11034
+ if (event.message.role === "assistant") {
11035
+ if (run) {
11036
+ const text = compactRlmText(readAssistantText(event.message));
11037
+ if (text)
11038
+ run.answerPreview = text;
11039
+ run.activity = { kind: "writing" };
11040
+ }
11041
+ emitChildUpdate();
11042
+ }
11043
+ }
11044
+ else if (event.type === "tool_execution_start") {
11045
+ if (run) {
11046
+ run.toolUseCount++;
11047
+ runningToolCount++;
11048
+ run.activity = { kind: "executing", toolName: event.toolName };
11049
+ }
11050
+ emitChildUpdate();
11051
+ }
11052
+ else if (event.type === "tool_execution_end") {
11053
+ if (run) {
11054
+ runningToolCount = Math.max(0, runningToolCount - 1);
11055
+ if (runningToolCount === 0)
11056
+ run.activity = { kind: "waiting" };
11057
+ }
11058
+ emitChildUpdate();
11059
+ }
11060
+ else if (event.type === "session_info_changed" || event.type === "recap_update")
11061
+ emitChildUpdate();
11062
+ });
11063
+ return () => {
11064
+ unsubscribe();
11065
+ if (child._rlmParentUsageAttribution === attributeUsage)
11066
+ child._rlmParentUsageAttribution = undefined;
11067
+ };
11068
+ }
10713
11069
  /**
10714
11070
  * Retain a finished child session for the parent lifetime so inspectors and
10715
11071
  * daemon-hosted agent messaging can keep addressing it. Returns false (and disposes
@@ -10742,10 +11098,24 @@ export class AgentSession {
10742
11098
  }
10743
11099
  }
10744
11100
  }
10745
- this._rlmChildSessions.set(childId, { session, run: this._activeRlmChildRuns.get(childId) });
10746
- if (unsubscribe) {
10747
- this._rlmChildUnsubscribes.set(childId, unsubscribe);
11101
+ await this._restoreRlmChildUsageSource(childId, session);
11102
+ if (this._disposed || this._disposing) {
11103
+ await session.disposeAsync();
11104
+ return false;
10748
11105
  }
11106
+ if (this._deletingRlmChildren.has(childId) || this._deletedRlmChildIds.has(childId))
11107
+ return false;
11108
+ const retained = this._rlmChildSessions.get(childId);
11109
+ const run = this._activeRlmChildRuns.get(childId);
11110
+ if (!run?.unsubscribe && (retained?.session !== session || !this._rlmChildUnsubscribes.has(childId))) {
11111
+ this._rlmChildUnsubscribes.get(childId)?.();
11112
+ const ownedUnsubscribe = this._subscribeRlmChildEvents(childId, session);
11113
+ this._rlmChildUnsubscribes.set(childId, () => {
11114
+ ownedUnsubscribe();
11115
+ unsubscribe?.();
11116
+ });
11117
+ }
11118
+ this._rlmChildSessions.set(childId, { session, run });
10749
11119
  return true;
10750
11120
  }
10751
11121
  releaseRlmChildSession(childId, session) {
@@ -10753,8 +11123,10 @@ export class AgentSession {
10753
11123
  if (run?.session === session && run.status === "done") {
10754
11124
  const unsubscribe = run.unsubscribe ?? noopRlmChildEventUnsubscribe;
10755
11125
  return () => {
10756
- run.unsubscribe = undefined;
10757
- this._activeRlmChildRuns.delete(childId);
11126
+ if (run.unsubscribe === unsubscribe)
11127
+ run.unsubscribe = undefined;
11128
+ if (this._activeRlmChildRuns.get(childId) === run)
11129
+ this._activeRlmChildRuns.delete(childId);
10758
11130
  unsubscribe();
10759
11131
  };
10760
11132
  }
@@ -10762,8 +11134,11 @@ export class AgentSession {
10762
11134
  return false;
10763
11135
  const unsubscribe = this._rlmChildUnsubscribes.get(childId) ?? noopRlmChildEventUnsubscribe;
10764
11136
  return () => {
10765
- this._rlmChildUnsubscribes.delete(childId);
10766
- this._rlmChildSessions.delete(childId);
11137
+ if (this._rlmChildSessions.get(childId)?.session === session &&
11138
+ this._rlmChildUnsubscribes.get(childId) === unsubscribe) {
11139
+ this._rlmChildUnsubscribes.delete(childId);
11140
+ this._rlmChildSessions.delete(childId);
11141
+ }
10767
11142
  unsubscribe();
10768
11143
  };
10769
11144
  }
@@ -10890,7 +11265,7 @@ export class AgentSession {
10890
11265
  return [...sessions];
10891
11266
  }
10892
11267
  _hasUnsettledRlmQuiescenceWork() {
10893
- if (this.requests.hasPending)
11268
+ if (this.requests.hasPending || this._childUsageWrites.size > 0)
10894
11269
  return true;
10895
11270
  if (this._hasDeferredRlmTerminalNotices())
10896
11271
  return true;
@@ -10934,6 +11309,7 @@ export class AgentSession {
10934
11309
  return;
10935
11310
  await wait(Promise.all([
10936
11311
  ...unsettledRuns.map((run) => run.settlement.promise),
11312
+ ...this._childUsageWrites,
10937
11313
  ...childSessions.map((child) => child.waitForRlmQuiescence(cancellation.signal)),
10938
11314
  ]));
10939
11315
  // Always loop through the self-active/deferred checks again. Work may
@@ -11101,6 +11477,14 @@ export class AgentSession {
11101
11477
  // firing while the parent is idle) has no such turn; an absent edge beats a wrong one.
11102
11478
  const spawnedByRequestId = this.isStreaming ? this._semanticEdges.lastTurnRequestId : undefined;
11103
11479
  const parentAssistantForUsage = this._findLastAssistantMessage();
11480
+ const parentEntryId = parentAssistantForUsage && this._findAssistantEntryIdForMessage(parentAssistantForUsage);
11481
+ const usageSource = parentEntryId
11482
+ ? {
11483
+ sessionId: this.sessionId,
11484
+ sessionFile: this.sessionFile,
11485
+ entryId: parentEntryId,
11486
+ }
11487
+ : undefined;
11104
11488
  const { name: rawName, model: rawModel, thinking: rawThinking, ...unsupported } = kwargs;
11105
11489
  const unsupportedKwargs = Object.keys(unsupported);
11106
11490
  if (unsupportedKwargs.length > 0) {
@@ -11146,7 +11530,6 @@ export class AgentSession {
11146
11530
  await this._assertRlmSubagentSessionNameAvailable(sessionName);
11147
11531
  admission.assertCurrent();
11148
11532
  const startedAt = Date.now();
11149
- let runningToolCount = 0;
11150
11533
  let childSession;
11151
11534
  let usageSettlement = Promise.resolve();
11152
11535
  const run = {
@@ -11168,6 +11551,8 @@ export class AgentSession {
11168
11551
  throw new Error(run.error ?? "RLM child cancelled");
11169
11552
  };
11170
11553
  this._activeRlmChildRuns.set(run.id, run);
11554
+ if (usageSource)
11555
+ this._rlmChildUsageSources.set(run.id, usageSource);
11171
11556
  this._unsettledRlmChildRuns.add(run);
11172
11557
  const emitChildUpdate = () => {
11173
11558
  const child = this._rlmChildSnapshotForRun(run);
@@ -11258,88 +11643,12 @@ export class AgentSession {
11258
11643
  throwIfCancelled();
11259
11644
  run.status = "running";
11260
11645
  emitChildUpdate();
11261
- const unsubscribeChildEvents = child.subscribe((event) => {
11262
- if (event.type === "rlm_child_update") {
11263
- this._emit(event);
11264
- return;
11265
- }
11266
- if (event.type === "agent_start") {
11267
- run.activity = { kind: "waiting" };
11268
- emitChildUpdate();
11269
- }
11270
- else if (event.type === "agent_end") {
11271
- run.activity = undefined;
11272
- emitChildUpdate();
11273
- }
11274
- else if (event.type === "message_end" && event.message.role === "assistant") {
11275
- const assistant = event.message;
11276
- if (assistant.stopReason !== "error" && assistant.stopReason !== "aborted") {
11277
- if (parentAssistantForUsage) {
11278
- const parentEntryId = this._findAssistantEntryIdForMessage(parentAssistantForUsage);
11279
- if (parentEntryId) {
11280
- const messages = child.messages;
11281
- const assistantIndex = messages.lastIndexOf(assistant);
11282
- const precedingPrompt = messages
11283
- .slice(0, assistantIndex)
11284
- .reverse()
11285
- .find((message) => message.role === "user" || message.role === "custom");
11286
- const origin = precedingPrompt?.role === "custom" && isAgentSessionMessage(precedingPrompt)
11287
- ? precedingPrompt.details.id.startsWith("spawn:")
11288
- ? "spawn_task"
11289
- : "agent_message"
11290
- : "direct_user";
11291
- const text = compactRlmText(readAssistantText(assistant));
11292
- const refreshOutput = this._refreshInvocationOutput;
11293
- const write = this.sessionManager
11294
- .appendChildUsageAttributionWithAggregate(parentEntryId, assistant.usage, undefined, origin)
11295
- .then(({ aggregateUsage }) => {
11296
- parentAssistantForUsage.usage = structuredClone(aggregateUsage);
11297
- refreshOutput?.(parentAssistantForUsage);
11298
- if (text)
11299
- run.answerPreview = text;
11300
- emitChildUpdate();
11301
- });
11302
- this._childUsageWrites.add(write);
11303
- usageSettlement = Promise.all([usageSettlement, write]).then(() => undefined);
11304
- void usageSettlement.catch(() => undefined);
11305
- void write.then(() => this._childUsageWrites.delete(write), (error) => {
11306
- this._childUsageWrites.delete(write);
11307
- run.error = `Child usage attribution was not acknowledged: ${this._asError(error).message}`;
11308
- this._surfaceSessionInputError(error);
11309
- });
11310
- return;
11311
- }
11312
- }
11313
- }
11314
- const text = compactRlmText(readAssistantText(assistant));
11315
- if (text)
11316
- run.answerPreview = text;
11317
- emitChildUpdate();
11318
- }
11319
- else if (event.type === "message_start" || event.type === "message_update") {
11320
- if (event.message.role === "assistant") {
11321
- const text = compactRlmText(readAssistantText(event.message));
11322
- if (text)
11323
- run.answerPreview = text;
11324
- run.activity = { kind: "writing" };
11325
- emitChildUpdate();
11326
- }
11327
- }
11328
- else if (event.type === "tool_execution_start") {
11329
- run.toolUseCount += 1;
11330
- runningToolCount += 1;
11331
- run.activity = { kind: "executing", toolName: event.toolName };
11332
- emitChildUpdate();
11333
- }
11334
- else if (event.type === "tool_execution_end") {
11335
- runningToolCount = Math.max(0, runningToolCount - 1);
11336
- if (runningToolCount === 0)
11337
- run.activity = { kind: "waiting" };
11338
- emitChildUpdate();
11339
- }
11340
- else if (event.type === "session_info_changed" || event.type === "recap_update") {
11341
- emitChildUpdate();
11342
- }
11646
+ if (usageSource) {
11647
+ await child.sessionManager.appendCustomEntry(RLM_PARENT_USAGE_CUSTOM_TYPE, usageSource);
11648
+ }
11649
+ const unsubscribeChildEvents = this._subscribeRlmChildEvents(run.id, child, run, (write) => {
11650
+ usageSettlement = Promise.all([usageSettlement, write]).then(() => undefined);
11651
+ void usageSettlement.catch(() => undefined);
11343
11652
  });
11344
11653
  run.unsubscribe = unsubscribeChildEvents;
11345
11654
  const content = `[task from parent]\n\n${prompt}`;
@@ -11367,8 +11676,19 @@ export class AgentSession {
11367
11676
  source: "extension",
11368
11677
  customMessage: spawnMessage,
11369
11678
  });
11679
+ // promptAndWait can resolve after a provider error or abort. Only outcomes
11680
+ // emitted during this run count; a recovered intermediate error is not terminal.
11681
+ const terminal = run.terminalAssistantOutcome;
11682
+ if (terminal?.stopReason === "aborted") {
11683
+ run.status = "cancelled";
11684
+ run.error ??= terminal.errorMessage || "RLM child aborted";
11685
+ }
11686
+ throwIfCancelled();
11687
+ if (terminal?.stopReason === "error")
11688
+ throw new Error(terminal.errorMessage || "RLM child inference failed");
11370
11689
  await child.waitForRlmQuiescence();
11371
11690
  await usageSettlement;
11691
+ throwIfCancelled();
11372
11692
  if (run.error)
11373
11693
  throw new Error(run.error);
11374
11694
  run.status = "done";
@@ -12282,39 +12602,13 @@ export class AgentSession {
12282
12602
  _contextWindowResolver() {
12283
12603
  return (provider, modelId) => this._modelRegistry.find(provider, modelId)?.contextWindow;
12284
12604
  }
12285
- _ownUsageMemo;
12286
12605
  // Whole-source own spend, identical to the catalog reduction at passivation.
12287
12606
  async getOwnUsageSummary() {
12288
12607
  if (!this.sessionManager.supportsCapturedHistoryReads()) {
12289
- const entries = await this.sessionManager.readEntries();
12608
+ const entries = this.sessionManager.getEntries();
12290
12609
  return sessionUsageSummaryFrom(computeOwnAndTotalUsage(entries, entries).ownUsage);
12291
12610
  }
12292
- const limits = { maxEntries: 16_384, maxSourceBytes: 64 * 1024 * 1024 };
12293
- return this.sessionManager.readSourceHistory(async (history) => {
12294
- const { sessionId, sessionFile, sourceSequence } = history.source;
12295
- const memo = this._ownUsageMemo;
12296
- if (memo &&
12297
- memo.sessionId === sessionId &&
12298
- memo.sessionFile === sessionFile &&
12299
- memo.sourceSequence === sourceSequence &&
12300
- memo.count <= limits.maxEntries &&
12301
- memo.sourceBytes <= limits.maxSourceBytes) {
12302
- return memo.usage ? { ...memo.usage } : undefined;
12303
- }
12304
- const materialized = await history.materialize(limits);
12305
- const entries = materialized.entries.map(({ entry }) => entry);
12306
- applyChildUsageAttributions(entries);
12307
- const usage = sessionUsageSummaryFrom(computeOwnAndTotalUsage(entries, entries).ownUsage);
12308
- this._ownUsageMemo = {
12309
- sessionId,
12310
- sessionFile,
12311
- sourceSequence,
12312
- count: entries.length,
12313
- sourceBytes: materialized.sourceBytes,
12314
- usage,
12315
- };
12316
- return usage ? { ...usage } : undefined;
12317
- });
12611
+ return this.sessionManager.readOwnUsageSummary();
12318
12612
  }
12319
12613
  /**
12320
12614
  * Build the agent context overview for /context: this session as the root
@@ -12384,7 +12678,12 @@ export class AgentSession {
12384
12678
  ? { tokens: estimate.tokens, contextWindow, percent: (estimate.tokens / contextWindow) * 100 }
12385
12679
  : { tokens: null, contextWindow, percent: null }
12386
12680
  : undefined;
12387
- const usageMetadata = { ownUsage: usage.ownUsage, totalUsage: usage.totalUsage, contextUsage };
12681
+ const usageMetadata = {
12682
+ ownUsage: usage.ownUsage,
12683
+ totalUsage: usage.totalUsage,
12684
+ ...(usage.ownRequestUsage ? { ownRequestUsage: usage.ownRequestUsage } : {}),
12685
+ contextUsage,
12686
+ };
12388
12687
  request.retainMetadata({ contextUsage });
12389
12688
  return {
12390
12689
  ...rootNode,