@ponythewhite/base-context 1.0.11 → 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 (80) hide show
  1. package/CHANGELOG.md +11 -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-PEFT72QN.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-JZHGOOFM.js → chunk-IAKHCQA2.js} +118 -22
  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-TOOVREMP.js → chunk-YGV3Y4AD.js} +515 -107
  16. package/dist/bundle/{cli-main-BRXGYTGK.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-POUXGEL6.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 -0
  26. package/dist/core/agent-session.d.ts.map +1 -1
  27. package/dist/core/agent-session.js +362 -103
  28. package/dist/core/agent-session.js.map +1 -1
  29. package/dist/core/compaction/compaction.d.ts +9 -1
  30. package/dist/core/compaction/compaction.d.ts.map +1 -1
  31. package/dist/core/compaction/compaction.js +29 -3
  32. package/dist/core/compaction/compaction.js.map +1 -1
  33. package/dist/core/context-tree.d.ts +13 -10
  34. package/dist/core/context-tree.d.ts.map +1 -1
  35. package/dist/core/context-tree.js +49 -3
  36. package/dist/core/context-tree.js.map +1 -1
  37. package/dist/core/inference-coordinator.d.ts +1 -1
  38. package/dist/core/inference-coordinator.d.ts.map +1 -1
  39. package/dist/core/inference-coordinator.js +18 -5
  40. package/dist/core/inference-coordinator.js.map +1 -1
  41. package/dist/core/messages.d.ts +2 -0
  42. package/dist/core/messages.d.ts.map +1 -1
  43. package/dist/core/messages.js +2 -0
  44. package/dist/core/messages.js.map +1 -1
  45. package/dist/core/refinement/refinement.d.ts +2 -0
  46. package/dist/core/refinement/refinement.d.ts.map +1 -1
  47. package/dist/core/refinement/refinement.js +10 -1
  48. package/dist/core/refinement/refinement.js.map +1 -1
  49. package/dist/core/request-usage.d.ts +34 -0
  50. package/dist/core/request-usage.d.ts.map +1 -0
  51. package/dist/core/request-usage.js +111 -0
  52. package/dist/core/request-usage.js.map +1 -0
  53. package/dist/core/settings-manager.d.ts +3 -0
  54. package/dist/core/settings-manager.d.ts.map +1 -1
  55. package/dist/core/settings-manager.js +7 -0
  56. package/dist/core/settings-manager.js.map +1 -1
  57. package/dist/core/system-prompt.d.ts +2 -0
  58. package/dist/core/system-prompt.d.ts.map +1 -1
  59. package/dist/core/system-prompt.js +4 -2
  60. package/dist/core/system-prompt.js.map +1 -1
  61. package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
  62. package/dist/modes/agent-connection/daemon-agent-connection.js +8 -1
  63. package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
  64. package/dist/modes/daemon/daemon-protocol.d.ts +13 -3
  65. package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
  66. package/dist/modes/daemon/daemon-protocol.js +10 -2
  67. package/dist/modes/daemon/daemon-protocol.js.map +1 -1
  68. package/dist/modes/daemon/daemon-session-summarizer.d.ts +3 -0
  69. package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
  70. package/dist/modes/daemon/daemon-session-summarizer.js +71 -32
  71. package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
  72. package/dist/modes/interactive/components/context-tree-format.d.ts.map +1 -1
  73. package/dist/modes/interactive/components/context-tree-format.js +49 -0
  74. package/dist/modes/interactive/components/context-tree-format.js.map +1 -1
  75. package/docs/compaction.md +27 -3
  76. package/docs/context-management.md +17 -1
  77. package/docs/rlm.md +1 -1
  78. package/docs/settings.md +12 -0
  79. package/docs/usage.md +24 -1
  80. package/package.json +4 -4
@@ -16,8 +16,8 @@ import { addAutonomousContinuation, addAutonomousUsage, autonomousStatus, create
16
16
  import { executeBashWithOperations } from "./bash-executor.js";
17
17
  import { stringifyBoundedJson } from "./bounded-json.js";
18
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, 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, UnsupportedContextEpochConfigurationError, } from "./context-epoch.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,10 +28,10 @@ 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";
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
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";
@@ -45,7 +45,7 @@ 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
+ import { hydrateCapturedHistoryEntry, } from "./session-history-index.js";
49
49
  import { exportSessionBranchToJsonl } from "./session-jsonl-export.js";
50
50
  import { getLatestCompactionEntry, SessionManager, } from "./session-manager.js";
51
51
  import { getPythonSkillRuntimeInfo } from "./skills.js";
@@ -192,6 +192,7 @@ function createPostCompactionContinuationSettlement(resume) {
192
192
  return { ...createAgentMessageDeferred(), resume, settled: false };
193
193
  }
194
194
  import { LocalRlmSubagentCapacity, } from "./rlm-max-subagents.js";
195
+ const RLM_PARENT_USAGE_CUSTOM_TYPE = "rlm_parent_usage";
195
196
  const KERNEL_STATE_LISTING_TIMEOUT_MS = 5000;
196
197
  const RLM_MAX_DEPTH_STATE_CUSTOM_TYPE = "rlm_max_depth_state";
197
198
  function noopRlmChildAbort() { }
@@ -436,6 +437,8 @@ export class AgentSession {
436
437
  // Kept alive for retained children so nested updates (e.g. a grandchild cancel)
437
438
  // still forward to root; torn down when the retained child is disposed.
438
439
  _rlmChildUnsubscribes = new Map();
440
+ _rlmChildUsageSources = new Map();
441
+ _rlmParentUsageAttribution;
439
442
  /** Latest recap for this session, written by the daemon summarizer; read by a parent to label its child snapshots. */
440
443
  _currentRecap;
441
444
  _initialGoal;
@@ -560,11 +563,13 @@ export class AgentSession {
560
563
  await this._goalResumeOperation;
561
564
  await this._waitForAgentEventsBeforeContext();
562
565
  await this._waitForChildUsageWrites();
566
+ const assertHarnessSourceCurrent = await this._appendHarnessSnapshotIfChanged();
563
567
  await this.sessionManager.flushNow();
568
+ assertHarnessSourceCurrent();
564
569
  // In-memory sessions have no canonical archive. This is an explicit mode, not an index-error fallback.
565
570
  if (!this.sessionManager.isPersisted()) {
566
571
  if (!this._compactionSetupFailure)
567
- return;
572
+ return { messages: this.agent.state.messages, adoptMessages: true };
568
573
  const rebuilt = await readSessionBootstrap(this.sessionManager, this.settingsManager.getCanonicalContextLimits());
569
574
  this._compactionSetupFailure = undefined;
570
575
  return { messages: rebuilt.context.messages, adoptMessages: true };
@@ -2950,7 +2955,8 @@ export class AgentSession {
2950
2955
  if (compactionTimestamp !== undefined && context.message.timestamp <= compactionTimestamp)
2951
2956
  return false;
2952
2957
  const contextTokens = this._getThresholdContextTokens(context.message, compactionTimestamp);
2953
- 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)))
2954
2960
  return false;
2955
2961
  if (this._hasFailedThresholdCompaction())
2956
2962
  return false;
@@ -3314,7 +3320,7 @@ export class AgentSession {
3314
3320
  }
3315
3321
  return {
3316
3322
  scheduled: true,
3317
- 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.",
3318
3324
  };
3319
3325
  }
3320
3326
  default:
@@ -4382,6 +4388,7 @@ export class AgentSession {
4382
4388
  for (const { session } of this._rlmChildSessions.values())
4383
4389
  await drain(() => session.disposeAsync());
4384
4390
  this._rlmChildSessions.clear();
4391
+ this._rlmChildUsageSources.clear();
4385
4392
  this._rlmChildCleanupFailures.clear();
4386
4393
  this._deletedRlmChildIds.clear();
4387
4394
  // Stop kernel-backed tools before waiting for their terminal agent events.
@@ -4477,6 +4484,7 @@ export class AgentSession {
4477
4484
  session.dispose();
4478
4485
  }
4479
4486
  this._rlmChildSessions.clear();
4487
+ this._rlmChildUsageSources.clear();
4480
4488
  this._rlmChildCleanupFailures.clear();
4481
4489
  this._deletedRlmChildIds.clear();
4482
4490
  this._pendingNextTurnMessages = [];
@@ -4721,6 +4729,7 @@ export class AgentSession {
4721
4729
  rlmDepth: this._rlmDepth,
4722
4730
  rlmParentAgent: this._rlmParentAgent,
4723
4731
  harnessState: this._loadMergedHarnessState(),
4732
+ harnessSection: "instructions",
4724
4733
  errorFixSelection: {
4725
4734
  enabled: this.settingsManager.getLearningEnabled(),
4726
4735
  tools: validToolNames,
@@ -4733,6 +4742,118 @@ export class AgentSession {
4733
4742
  };
4734
4743
  return buildSystemPrompt(this._baseSystemPromptOptions);
4735
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
+ }
4736
4857
  _refreshExtensionSystemPrompt(extensionPrompt, baseSnapshot) {
4737
4858
  if (this._baseSystemPrompt === baseSnapshot) {
4738
4859
  return extensionPrompt;
@@ -8928,8 +9049,12 @@ export class AgentSession {
8928
9049
  }
8929
9050
  if (refinementAuditAppendError)
8930
9051
  throw refinementAuditAppendError.error;
8931
- this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
8932
- 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.
8933
9058
  try {
8934
9059
  this._emit({ type: "refine_complete", result });
8935
9060
  }
@@ -9111,7 +9236,7 @@ export class AgentSession {
9111
9236
  const contextTokens = this._getThresholdContextTokens(assistantMessage, compactionTimestamp);
9112
9237
  if (contextTokens === undefined)
9113
9238
  return false;
9114
- if (shouldCompact(contextTokens, contextWindow, settings)) {
9239
+ if (shouldCompact(contextTokens, contextWindow, settings, estimateFixedCompactionTokens(this.systemPrompt, this.agent.state.tools, this.messages))) {
9115
9240
  if (this._hasFailedThresholdCompaction())
9116
9241
  return false;
9117
9242
  if (!this._pendingCheckpoint && queueAutonomousContinuation) {
@@ -10691,6 +10816,7 @@ export class AgentSession {
10691
10816
  this._rlmChildUnsubscribes.get(childId)?.();
10692
10817
  this._rlmChildUnsubscribes.delete(childId);
10693
10818
  this._rlmChildSessions.delete(childId);
10819
+ this._rlmChildUsageSources.delete(childId);
10694
10820
  this._rlmChildCleanupFailures.delete(childId);
10695
10821
  this._abandonedRlmQuiescenceChildIds.delete(childId);
10696
10822
  if (!run || this._activeRlmChildRuns.get(childId) === run) {
@@ -10776,6 +10902,170 @@ export class AgentSession {
10776
10902
  this._removeRlmSubagentTracking(childId);
10777
10903
  return { subagent };
10778
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
+ }
10779
11069
  /**
10780
11070
  * Retain a finished child session for the parent lifetime so inspectors and
10781
11071
  * daemon-hosted agent messaging can keep addressing it. Returns false (and disposes
@@ -10808,10 +11098,24 @@ export class AgentSession {
10808
11098
  }
10809
11099
  }
10810
11100
  }
10811
- this._rlmChildSessions.set(childId, { session, run: this._activeRlmChildRuns.get(childId) });
10812
- if (unsubscribe) {
10813
- this._rlmChildUnsubscribes.set(childId, unsubscribe);
11101
+ await this._restoreRlmChildUsageSource(childId, session);
11102
+ if (this._disposed || this._disposing) {
11103
+ await session.disposeAsync();
11104
+ return false;
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
+ });
10814
11117
  }
11118
+ this._rlmChildSessions.set(childId, { session, run });
10815
11119
  return true;
10816
11120
  }
10817
11121
  releaseRlmChildSession(childId, session) {
@@ -10819,8 +11123,10 @@ export class AgentSession {
10819
11123
  if (run?.session === session && run.status === "done") {
10820
11124
  const unsubscribe = run.unsubscribe ?? noopRlmChildEventUnsubscribe;
10821
11125
  return () => {
10822
- run.unsubscribe = undefined;
10823
- 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);
10824
11130
  unsubscribe();
10825
11131
  };
10826
11132
  }
@@ -10828,8 +11134,11 @@ export class AgentSession {
10828
11134
  return false;
10829
11135
  const unsubscribe = this._rlmChildUnsubscribes.get(childId) ?? noopRlmChildEventUnsubscribe;
10830
11136
  return () => {
10831
- this._rlmChildUnsubscribes.delete(childId);
10832
- 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
+ }
10833
11142
  unsubscribe();
10834
11143
  };
10835
11144
  }
@@ -10956,7 +11265,7 @@ export class AgentSession {
10956
11265
  return [...sessions];
10957
11266
  }
10958
11267
  _hasUnsettledRlmQuiescenceWork() {
10959
- if (this.requests.hasPending)
11268
+ if (this.requests.hasPending || this._childUsageWrites.size > 0)
10960
11269
  return true;
10961
11270
  if (this._hasDeferredRlmTerminalNotices())
10962
11271
  return true;
@@ -11000,6 +11309,7 @@ export class AgentSession {
11000
11309
  return;
11001
11310
  await wait(Promise.all([
11002
11311
  ...unsettledRuns.map((run) => run.settlement.promise),
11312
+ ...this._childUsageWrites,
11003
11313
  ...childSessions.map((child) => child.waitForRlmQuiescence(cancellation.signal)),
11004
11314
  ]));
11005
11315
  // Always loop through the self-active/deferred checks again. Work may
@@ -11167,6 +11477,14 @@ export class AgentSession {
11167
11477
  // firing while the parent is idle) has no such turn; an absent edge beats a wrong one.
11168
11478
  const spawnedByRequestId = this.isStreaming ? this._semanticEdges.lastTurnRequestId : undefined;
11169
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;
11170
11488
  const { name: rawName, model: rawModel, thinking: rawThinking, ...unsupported } = kwargs;
11171
11489
  const unsupportedKwargs = Object.keys(unsupported);
11172
11490
  if (unsupportedKwargs.length > 0) {
@@ -11212,7 +11530,6 @@ export class AgentSession {
11212
11530
  await this._assertRlmSubagentSessionNameAvailable(sessionName);
11213
11531
  admission.assertCurrent();
11214
11532
  const startedAt = Date.now();
11215
- let runningToolCount = 0;
11216
11533
  let childSession;
11217
11534
  let usageSettlement = Promise.resolve();
11218
11535
  const run = {
@@ -11234,6 +11551,8 @@ export class AgentSession {
11234
11551
  throw new Error(run.error ?? "RLM child cancelled");
11235
11552
  };
11236
11553
  this._activeRlmChildRuns.set(run.id, run);
11554
+ if (usageSource)
11555
+ this._rlmChildUsageSources.set(run.id, usageSource);
11237
11556
  this._unsettledRlmChildRuns.add(run);
11238
11557
  const emitChildUpdate = () => {
11239
11558
  const child = this._rlmChildSnapshotForRun(run);
@@ -11324,88 +11643,12 @@ export class AgentSession {
11324
11643
  throwIfCancelled();
11325
11644
  run.status = "running";
11326
11645
  emitChildUpdate();
11327
- const unsubscribeChildEvents = child.subscribe((event) => {
11328
- if (event.type === "rlm_child_update") {
11329
- this._emit(event);
11330
- return;
11331
- }
11332
- if (event.type === "agent_start") {
11333
- run.activity = { kind: "waiting" };
11334
- emitChildUpdate();
11335
- }
11336
- else if (event.type === "agent_end") {
11337
- run.activity = undefined;
11338
- emitChildUpdate();
11339
- }
11340
- else if (event.type === "message_end" && event.message.role === "assistant") {
11341
- const assistant = event.message;
11342
- if (assistant.stopReason !== "error" && assistant.stopReason !== "aborted") {
11343
- if (parentAssistantForUsage) {
11344
- const parentEntryId = this._findAssistantEntryIdForMessage(parentAssistantForUsage);
11345
- if (parentEntryId) {
11346
- const messages = child.messages;
11347
- const assistantIndex = messages.lastIndexOf(assistant);
11348
- const precedingPrompt = messages
11349
- .slice(0, assistantIndex)
11350
- .reverse()
11351
- .find((message) => message.role === "user" || message.role === "custom");
11352
- const origin = precedingPrompt?.role === "custom" && isAgentSessionMessage(precedingPrompt)
11353
- ? precedingPrompt.details.id.startsWith("spawn:")
11354
- ? "spawn_task"
11355
- : "agent_message"
11356
- : "direct_user";
11357
- const text = compactRlmText(readAssistantText(assistant));
11358
- const refreshOutput = this._refreshInvocationOutput;
11359
- const write = this.sessionManager
11360
- .appendChildUsageAttributionWithAggregate(parentEntryId, assistant.usage, undefined, origin)
11361
- .then(({ aggregateUsage }) => {
11362
- parentAssistantForUsage.usage = structuredClone(aggregateUsage);
11363
- refreshOutput?.(parentAssistantForUsage);
11364
- if (text)
11365
- run.answerPreview = text;
11366
- emitChildUpdate();
11367
- });
11368
- this._childUsageWrites.add(write);
11369
- usageSettlement = Promise.all([usageSettlement, write]).then(() => undefined);
11370
- void usageSettlement.catch(() => undefined);
11371
- void write.then(() => this._childUsageWrites.delete(write), (error) => {
11372
- this._childUsageWrites.delete(write);
11373
- run.error = `Child usage attribution was not acknowledged: ${this._asError(error).message}`;
11374
- this._surfaceSessionInputError(error);
11375
- });
11376
- return;
11377
- }
11378
- }
11379
- }
11380
- const text = compactRlmText(readAssistantText(assistant));
11381
- if (text)
11382
- run.answerPreview = text;
11383
- emitChildUpdate();
11384
- }
11385
- else if (event.type === "message_start" || event.type === "message_update") {
11386
- if (event.message.role === "assistant") {
11387
- const text = compactRlmText(readAssistantText(event.message));
11388
- if (text)
11389
- run.answerPreview = text;
11390
- run.activity = { kind: "writing" };
11391
- emitChildUpdate();
11392
- }
11393
- }
11394
- else if (event.type === "tool_execution_start") {
11395
- run.toolUseCount += 1;
11396
- runningToolCount += 1;
11397
- run.activity = { kind: "executing", toolName: event.toolName };
11398
- emitChildUpdate();
11399
- }
11400
- else if (event.type === "tool_execution_end") {
11401
- runningToolCount = Math.max(0, runningToolCount - 1);
11402
- if (runningToolCount === 0)
11403
- run.activity = { kind: "waiting" };
11404
- emitChildUpdate();
11405
- }
11406
- else if (event.type === "session_info_changed" || event.type === "recap_update") {
11407
- emitChildUpdate();
11408
- }
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);
11409
11652
  });
11410
11653
  run.unsubscribe = unsubscribeChildEvents;
11411
11654
  const content = `[task from parent]\n\n${prompt}`;
@@ -11433,8 +11676,19 @@ export class AgentSession {
11433
11676
  source: "extension",
11434
11677
  customMessage: spawnMessage,
11435
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");
11436
11689
  await child.waitForRlmQuiescence();
11437
11690
  await usageSettlement;
11691
+ throwIfCancelled();
11438
11692
  if (run.error)
11439
11693
  throw new Error(run.error);
11440
11694
  run.status = "done";
@@ -12424,7 +12678,12 @@ export class AgentSession {
12424
12678
  ? { tokens: estimate.tokens, contextWindow, percent: (estimate.tokens / contextWindow) * 100 }
12425
12679
  : { tokens: null, contextWindow, percent: null }
12426
12680
  : undefined;
12427
- 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
+ };
12428
12687
  request.retainMetadata({ contextUsage });
12429
12688
  return {
12430
12689
  ...rootNode,