@mariozechner/pi-coding-agent 0.63.2 → 0.65.0

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 (144) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +11 -5
  3. package/dist/cli/args.d.ts +7 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +37 -15
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-runtime.d.ts +83 -0
  8. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  9. package/dist/core/agent-session-runtime.js +232 -0
  10. package/dist/core/agent-session-runtime.js.map +1 -0
  11. package/dist/core/agent-session-services.d.ts +86 -0
  12. package/dist/core/agent-session-services.d.ts.map +1 -0
  13. package/dist/core/agent-session-services.js +116 -0
  14. package/dist/core/agent-session-services.js.map +1 -0
  15. package/dist/core/agent-session.d.ts +10 -42
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +58 -237
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  20. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  21. package/dist/core/export-html/tool-renderer.js +2 -2
  22. package/dist/core/export-html/tool-renderer.js.map +1 -1
  23. package/dist/core/extensions/index.d.ts +2 -2
  24. package/dist/core/extensions/index.d.ts.map +1 -1
  25. package/dist/core/extensions/index.js +1 -1
  26. package/dist/core/extensions/index.js.map +1 -1
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +1 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +20 -28
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js +10 -0
  33. package/dist/core/extensions/types.js.map +1 -1
  34. package/dist/core/footer-data-provider.d.ts +5 -1
  35. package/dist/core/footer-data-provider.d.ts.map +1 -1
  36. package/dist/core/footer-data-provider.js +70 -8
  37. package/dist/core/footer-data-provider.js.map +1 -1
  38. package/dist/core/index.d.ts +3 -1
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +3 -1
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/keybindings.d.ts +14 -1
  43. package/dist/core/keybindings.d.ts.map +1 -1
  44. package/dist/core/keybindings.js +13 -14
  45. package/dist/core/keybindings.js.map +1 -1
  46. package/dist/core/model-registry.d.ts +3 -1
  47. package/dist/core/model-registry.d.ts.map +1 -1
  48. package/dist/core/model-registry.js +7 -1
  49. package/dist/core/model-registry.js.map +1 -1
  50. package/dist/core/package-manager.d.ts +20 -0
  51. package/dist/core/package-manager.d.ts.map +1 -1
  52. package/dist/core/package-manager.js +32 -0
  53. package/dist/core/package-manager.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +21 -0
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/sdk.d.ts +5 -2
  58. package/dist/core/sdk.d.ts.map +1 -1
  59. package/dist/core/sdk.js +5 -2
  60. package/dist/core/sdk.js.map +1 -1
  61. package/dist/core/session-manager.d.ts +3 -0
  62. package/dist/core/session-manager.d.ts.map +1 -1
  63. package/dist/core/session-manager.js +13 -6
  64. package/dist/core/session-manager.js.map +1 -1
  65. package/dist/core/settings-manager.d.ts +1 -1
  66. package/dist/core/settings-manager.d.ts.map +1 -1
  67. package/dist/core/settings-manager.js +2 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/tools/edit.d.ts.map +1 -1
  70. package/dist/core/tools/edit.js +15 -4
  71. package/dist/core/tools/edit.js.map +1 -1
  72. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  73. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  74. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  75. package/dist/index.d.ts +3 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -3
  78. package/dist/index.js.map +1 -1
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/main.js +205 -427
  81. package/dist/main.js.map +1 -1
  82. package/dist/migrations.d.ts.map +1 -1
  83. package/dist/migrations.js +20 -0
  84. package/dist/migrations.js.map +1 -1
  85. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  86. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/assistant-message.js +14 -3
  88. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  89. package/dist/modes/interactive/components/footer.d.ts +1 -0
  90. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/footer.js +4 -1
  92. package/dist/modes/interactive/components/footer.js.map +1 -1
  93. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  94. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/tree-selector.js +48 -15
  96. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.d.ts +12 -4
  98. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  99. package/dist/modes/interactive/interactive-mode.js +146 -96
  100. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  101. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  102. package/dist/modes/interactive/theme/theme.js +6 -11
  103. package/dist/modes/interactive/theme/theme.js.map +1 -1
  104. package/dist/modes/print-mode.d.ts +2 -2
  105. package/dist/modes/print-mode.d.ts.map +1 -1
  106. package/dist/modes/print-mode.js +41 -36
  107. package/dist/modes/print-mode.js.map +1 -1
  108. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  109. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-mode.js +95 -64
  111. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  112. package/dist/package-manager-cli.d.ts +4 -0
  113. package/dist/package-manager-cli.d.ts.map +1 -0
  114. package/dist/package-manager-cli.js +234 -0
  115. package/dist/package-manager-cli.js.map +1 -0
  116. package/docs/compaction.md +4 -2
  117. package/docs/extensions.md +133 -40
  118. package/docs/json.md +5 -2
  119. package/docs/keybindings.md +2 -0
  120. package/docs/rpc.md +21 -7
  121. package/docs/sdk.md +239 -83
  122. package/docs/settings.md +1 -1
  123. package/docs/tree.md +6 -3
  124. package/examples/extensions/README.md +1 -0
  125. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  126. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  127. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  128. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  129. package/examples/extensions/hello.ts +18 -17
  130. package/examples/extensions/hidden-thinking-label.ts +53 -0
  131. package/examples/extensions/rpc-demo.ts +3 -9
  132. package/examples/extensions/sandbox/index.ts +4 -0
  133. package/examples/extensions/status-line.ts +0 -8
  134. package/examples/extensions/todo.ts +0 -2
  135. package/examples/extensions/tools.ts +0 -5
  136. package/examples/extensions/widget-placement.ts +4 -12
  137. package/examples/extensions/with-deps/package-lock.json +2 -2
  138. package/examples/extensions/with-deps/package.json +1 -1
  139. package/examples/sdk/02-custom-model.ts +1 -1
  140. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  141. package/examples/sdk/12-full-control.ts +1 -1
  142. package/examples/sdk/13-session-runtime.ts +67 -0
  143. package/examples/sdk/README.md +7 -4
  144. package/package.json +4 -4
@@ -12,14 +12,14 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname, join, resolve } from "node:path";
17
17
  import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@mariozechner/pi-ai";
18
18
  import { getDocsPath } from "../config.js";
19
19
  import { theme } from "../modes/interactive/theme/theme.js";
20
20
  import { stripFrontmatter } from "../utils/frontmatter.js";
21
21
  import { sleep } from "../utils/sleep.js";
22
- import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
22
+ import { executeBashWithOperations } from "./bash-executor.js";
23
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
24
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
25
  import { exportSessionToHtml } from "./export-html/index.js";
@@ -29,6 +29,7 @@ import { expandPromptTemplate } from "./prompt-templates.js";
29
29
  import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
30
30
  import { createSyntheticSourceInfo } from "./source-info.js";
31
31
  import { buildSystemPrompt } from "./system-prompt.js";
32
+ import { createLocalBashOperations } from "./tools/bash.js";
32
33
  import { createAllToolDefinitions } from "./tools/index.js";
33
34
  import { createToolDefinitionFromAgentTool, wrapToolDefinition } from "./tools/tool-definition-wrapper.js";
34
35
  /**
@@ -95,6 +96,7 @@ export class AgentSession {
95
96
  _extensionRunnerRef;
96
97
  _initialActiveToolNames;
97
98
  _baseToolsOverride;
99
+ _sessionStartEvent;
98
100
  _extensionUIContext;
99
101
  _extensionCommandContextActions;
100
102
  _extensionShutdownHandler;
@@ -121,6 +123,7 @@ export class AgentSession {
121
123
  this._extensionRunnerRef = config.extensionRunnerRef;
122
124
  this._initialActiveToolNames = config.initialActiveToolNames;
123
125
  this._baseToolsOverride = config.baseToolsOverride;
126
+ this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
124
127
  // Always subscribe to agent events for internal handling
125
128
  // (session persistence, extensions, auto-compaction, retry logic)
126
129
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
@@ -160,7 +163,7 @@ export class AgentSession {
160
163
  * happens here instead of in wrappers.
161
164
  */
162
165
  _installAgentToolHooks() {
163
- this.agent.setBeforeToolCall(async ({ toolCall, args }) => {
166
+ this.agent.beforeToolCall = async ({ toolCall, args }) => {
164
167
  const runner = this._extensionRunner;
165
168
  if (!runner?.hasHandlers("tool_call")) {
166
169
  return undefined;
@@ -180,8 +183,8 @@ export class AgentSession {
180
183
  }
181
184
  throw new Error(`Extension failed, blocking execution: ${String(err)}`);
182
185
  }
183
- });
184
- this.agent.setAfterToolCall(async ({ toolCall, args, result, isError }) => {
186
+ };
187
+ this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
185
188
  const runner = this._extensionRunner;
186
189
  if (!runner?.hasHandlers("tool_result")) {
187
190
  return undefined;
@@ -202,7 +205,7 @@ export class AgentSession {
202
205
  content: hookResult.content,
203
206
  details: hookResult.details,
204
207
  };
205
- });
208
+ };
206
209
  }
207
210
  // =========================================================================
208
211
  // Event Subscription
@@ -213,6 +216,13 @@ export class AgentSession {
213
216
  l(event);
214
217
  }
215
218
  }
219
+ _emitQueueUpdate() {
220
+ this._emit({
221
+ type: "queue_update",
222
+ steering: [...this._steeringMessages],
223
+ followUp: [...this._followUpMessages],
224
+ });
225
+ }
216
226
  // Track last assistant message for auto-compaction check
217
227
  _lastAssistantMessage = undefined;
218
228
  /** Internal handler for agent events - shared by subscribe and reconnect */
@@ -263,12 +273,14 @@ export class AgentSession {
263
273
  const steeringIndex = this._steeringMessages.indexOf(messageText);
264
274
  if (steeringIndex !== -1) {
265
275
  this._steeringMessages.splice(steeringIndex, 1);
276
+ this._emitQueueUpdate();
266
277
  }
267
278
  else {
268
279
  // Check follow-up queue
269
280
  const followUpIndex = this._followUpMessages.indexOf(messageText);
270
281
  if (followUpIndex !== -1) {
271
282
  this._followUpMessages.splice(followUpIndex, 1);
283
+ this._emitQueueUpdate();
272
284
  }
273
285
  }
274
286
  }
@@ -541,10 +553,10 @@ export class AgentSession {
541
553
  validToolNames.push(name);
542
554
  }
543
555
  }
544
- this.agent.setTools(tools);
556
+ this.agent.state.tools = tools;
545
557
  // Rebuild base system prompt with new tool set
546
558
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
547
- this.agent.setSystemPrompt(this._baseSystemPrompt);
559
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
548
560
  }
549
561
  /** Whether compaction or branch summarization is currently running */
550
562
  get isCompacting() {
@@ -558,11 +570,11 @@ export class AgentSession {
558
570
  }
559
571
  /** Current steering mode */
560
572
  get steeringMode() {
561
- return this.agent.getSteeringMode();
573
+ return this.agent.steeringMode;
562
574
  }
563
575
  /** Current follow-up mode */
564
576
  get followUpMode() {
565
- return this.agent.getFollowUpMode();
577
+ return this.agent.followUpMode;
566
578
  }
567
579
  /** Current session file path, or undefined if sessions are disabled */
568
580
  get sessionFile() {
@@ -753,11 +765,11 @@ export class AgentSession {
753
765
  }
754
766
  // Apply extension-modified system prompt, or reset to base
755
767
  if (result?.systemPrompt) {
756
- this.agent.setSystemPrompt(result.systemPrompt);
768
+ this.agent.state.systemPrompt = result.systemPrompt;
757
769
  }
758
770
  else {
759
771
  // Ensure we're using the base prompt (in case previous turn had modifications)
760
- this.agent.setSystemPrompt(this._baseSystemPrompt);
772
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
761
773
  }
762
774
  }
763
775
  await this.agent.prompt(messages);
@@ -862,6 +874,7 @@ export class AgentSession {
862
874
  */
863
875
  async _queueSteer(text, images) {
864
876
  this._steeringMessages.push(text);
877
+ this._emitQueueUpdate();
865
878
  const content = [{ type: "text", text }];
866
879
  if (images) {
867
880
  content.push(...images);
@@ -877,6 +890,7 @@ export class AgentSession {
877
890
  */
878
891
  async _queueFollowUp(text, images) {
879
892
  this._followUpMessages.push(text);
893
+ this._emitQueueUpdate();
880
894
  const content = [{ type: "text", text }];
881
895
  if (images) {
882
896
  content.push(...images);
@@ -936,7 +950,7 @@ export class AgentSession {
936
950
  await this.agent.prompt(appMessage);
937
951
  }
938
952
  else {
939
- this.agent.appendMessage(appMessage);
953
+ this.agent.state.messages.push(appMessage);
940
954
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
941
955
  this._emit({ type: "message_start", message: appMessage });
942
956
  this._emit({ type: "message_end", message: appMessage });
@@ -990,6 +1004,7 @@ export class AgentSession {
990
1004
  this._steeringMessages = [];
991
1005
  this._followUpMessages = [];
992
1006
  this.agent.clearAllQueues();
1007
+ this._emitQueueUpdate();
993
1008
  return { steering, followUp };
994
1009
  }
995
1010
  /** Number of pending messages (includes both steering and follow-up) */
@@ -1015,54 +1030,6 @@ export class AgentSession {
1015
1030
  this.agent.abort();
1016
1031
  await this.agent.waitForIdle();
1017
1032
  }
1018
- /**
1019
- * Start a new session, optionally with initial messages and parent tracking.
1020
- * Clears all messages and starts a new session.
1021
- * Listeners are preserved and will continue receiving events.
1022
- * @param options.parentSession - Optional parent session path for tracking
1023
- * @param options.setup - Optional callback to initialize session (e.g., append messages)
1024
- * @returns true if completed, false if cancelled by extension
1025
- */
1026
- async newSession(options) {
1027
- const previousSessionFile = this.sessionFile;
1028
- // Emit session_before_switch event with reason "new" (can be cancelled)
1029
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
1030
- const result = (await this._extensionRunner.emit({
1031
- type: "session_before_switch",
1032
- reason: "new",
1033
- }));
1034
- if (result?.cancel) {
1035
- return false;
1036
- }
1037
- }
1038
- this._disconnectFromAgent();
1039
- await this.abort();
1040
- this.agent.reset();
1041
- this.sessionManager.newSession({ parentSession: options?.parentSession });
1042
- this.agent.sessionId = this.sessionManager.getSessionId();
1043
- this._steeringMessages = [];
1044
- this._followUpMessages = [];
1045
- this._pendingNextTurnMessages = [];
1046
- this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1047
- // Run setup callback if provided (e.g., to append initial messages)
1048
- if (options?.setup) {
1049
- await options.setup(this.sessionManager);
1050
- // Sync agent state with session manager after setup
1051
- const sessionContext = this.sessionManager.buildSessionContext();
1052
- this.agent.replaceMessages(sessionContext.messages);
1053
- }
1054
- this._reconnectToAgent();
1055
- // Emit session_switch event with reason "new" to extensions
1056
- if (this._extensionRunner) {
1057
- await this._extensionRunner.emit({
1058
- type: "session_switch",
1059
- reason: "new",
1060
- previousSessionFile,
1061
- });
1062
- }
1063
- // Emit session event to custom tools
1064
- return true;
1065
- }
1066
1033
  // =========================================================================
1067
1034
  // Model Management
1068
1035
  // =========================================================================
@@ -1089,7 +1056,7 @@ export class AgentSession {
1089
1056
  }
1090
1057
  const previousModel = this.model;
1091
1058
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1092
- this.agent.setModel(model);
1059
+ this.agent.state.model = model;
1093
1060
  this.sessionManager.appendModelChange(model.provider, model.id);
1094
1061
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1095
1062
  // Re-clamp thinking level for new model's capabilities
@@ -1108,11 +1075,8 @@ export class AgentSession {
1108
1075
  }
1109
1076
  return this._cycleAvailableModel(direction);
1110
1077
  }
1111
- _getScopedModelsWithAuth() {
1112
- return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1113
- }
1114
1078
  async _cycleScopedModel(direction) {
1115
- const scopedModels = this._getScopedModelsWithAuth();
1079
+ const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1116
1080
  if (scopedModels.length <= 1)
1117
1081
  return undefined;
1118
1082
  const currentModel = this.model;
@@ -1124,7 +1088,7 @@ export class AgentSession {
1124
1088
  const next = scopedModels[nextIndex];
1125
1089
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1126
1090
  // Apply model
1127
- this.agent.setModel(next.model);
1091
+ this.agent.state.model = next.model;
1128
1092
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1129
1093
  this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
1130
1094
  // Apply thinking level.
@@ -1147,7 +1111,7 @@ export class AgentSession {
1147
1111
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1148
1112
  const nextModel = availableModels[nextIndex];
1149
1113
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1150
- this.agent.setModel(nextModel);
1114
+ this.agent.state.model = nextModel;
1151
1115
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1152
1116
  this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
1153
1117
  // Re-clamp thinking level for new model's capabilities
@@ -1168,7 +1132,7 @@ export class AgentSession {
1168
1132
  const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
1169
1133
  // Only persist if actually changing
1170
1134
  const isChanging = effectiveLevel !== this.agent.state.thinkingLevel;
1171
- this.agent.setThinkingLevel(effectiveLevel);
1135
+ this.agent.state.thinkingLevel = effectiveLevel;
1172
1136
  if (isChanging) {
1173
1137
  this.sessionManager.appendThinkingLevelChange(effectiveLevel);
1174
1138
  if (this.supportsThinking() || effectiveLevel !== "off") {
@@ -1247,7 +1211,7 @@ export class AgentSession {
1247
1211
  * Saves to settings.
1248
1212
  */
1249
1213
  setSteeringMode(mode) {
1250
- this.agent.setSteeringMode(mode);
1214
+ this.agent.steeringMode = mode;
1251
1215
  this.settingsManager.setSteeringMode(mode);
1252
1216
  }
1253
1217
  /**
@@ -1255,7 +1219,7 @@ export class AgentSession {
1255
1219
  * Saves to settings.
1256
1220
  */
1257
1221
  setFollowUpMode(mode) {
1258
- this.agent.setFollowUpMode(mode);
1222
+ this.agent.followUpMode = mode;
1259
1223
  this.settingsManager.setFollowUpMode(mode);
1260
1224
  }
1261
1225
  // =========================================================================
@@ -1330,7 +1294,7 @@ export class AgentSession {
1330
1294
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1331
1295
  const newEntries = this.sessionManager.getEntries();
1332
1296
  const sessionContext = this.sessionManager.buildSessionContext();
1333
- this.agent.replaceMessages(sessionContext.messages);
1297
+ this.agent.state.messages = sessionContext.messages;
1334
1298
  // Get the saved compaction entry for the extension event
1335
1299
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1336
1300
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1436,7 +1400,7 @@ export class AgentSession {
1436
1400
  // but we don't want it in context for the retry)
1437
1401
  const messages = this.agent.state.messages;
1438
1402
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1439
- this.agent.replaceMessages(messages.slice(0, -1));
1403
+ this.agent.state.messages = messages.slice(0, -1);
1440
1404
  }
1441
1405
  await this._runAutoCompaction("overflow", true);
1442
1406
  return;
@@ -1567,7 +1531,7 @@ export class AgentSession {
1567
1531
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1568
1532
  const newEntries = this.sessionManager.getEntries();
1569
1533
  const sessionContext = this.sessionManager.buildSessionContext();
1570
- this.agent.replaceMessages(sessionContext.messages);
1534
+ this.agent.state.messages = sessionContext.messages;
1571
1535
  // Get the saved compaction entry for the extension event
1572
1536
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1573
1537
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1588,7 +1552,7 @@ export class AgentSession {
1588
1552
  const messages = this.agent.state.messages;
1589
1553
  const lastMsg = messages[messages.length - 1];
1590
1554
  if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
1591
- this.agent.replaceMessages(messages.slice(0, -1));
1555
+ this.agent.state.messages = messages.slice(0, -1);
1592
1556
  }
1593
1557
  setTimeout(() => {
1594
1558
  this.agent.continue().catch(() => { });
@@ -1644,8 +1608,8 @@ export class AgentSession {
1644
1608
  }
1645
1609
  if (this._extensionRunner) {
1646
1610
  this._applyExtensionBindings(this._extensionRunner);
1647
- await this._extensionRunner.emit({ type: "session_start" });
1648
- await this.extendResourcesFromExtensions("startup");
1611
+ await this._extensionRunner.emit(this._sessionStartEvent);
1612
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
1649
1613
  }
1650
1614
  }
1651
1615
  async extendResourcesFromExtensions(reason) {
@@ -1663,7 +1627,7 @@ export class AgentSession {
1663
1627
  };
1664
1628
  this._resourceLoader.extendResources(extensionPaths);
1665
1629
  this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
1666
- this.agent.setSystemPrompt(this._baseSystemPrompt);
1630
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
1667
1631
  }
1668
1632
  buildExtensionResourcePaths(entries) {
1669
1633
  return entries.map((entry) => {
@@ -1705,7 +1669,7 @@ export class AgentSession {
1705
1669
  if (!refreshedModel || refreshedModel === currentModel) {
1706
1670
  return;
1707
1671
  }
1708
- this.agent.setModel(refreshedModel);
1672
+ this.agent.state.model = refreshedModel;
1709
1673
  }
1710
1674
  _bindExtensionCore(runner) {
1711
1675
  const getCommands = () => {
@@ -1916,7 +1880,7 @@ export class AgentSession {
1916
1880
  async reload() {
1917
1881
  const previousFlagValues = this._extensionRunner?.getFlagValues();
1918
1882
  await this._extensionRunner?.emit({ type: "session_shutdown" });
1919
- this.settingsManager.reload();
1883
+ await this.settingsManager.reload();
1920
1884
  resetApiProviders();
1921
1885
  await this._resourceLoader.reload();
1922
1886
  this._buildRuntime({
@@ -1929,7 +1893,7 @@ export class AgentSession {
1929
1893
  this._extensionShutdownHandler ||
1930
1894
  this._extensionErrorListener;
1931
1895
  if (this._extensionRunner && hasBindings) {
1932
- await this._extensionRunner.emit({ type: "session_start" });
1896
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
1933
1897
  await this.extendResourcesFromExtensions("reload");
1934
1898
  }
1935
1899
  }
@@ -1992,7 +1956,7 @@ export class AgentSession {
1992
1956
  // Remove error message from agent state (keep in session for history)
1993
1957
  const messages = this.agent.state.messages;
1994
1958
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1995
- this.agent.replaceMessages(messages.slice(0, -1));
1959
+ this.agent.state.messages = messages.slice(0, -1);
1996
1960
  }
1997
1961
  // Wait with exponential backoff (abortable)
1998
1962
  this._retryAbortController = new AbortController();
@@ -2035,9 +1999,11 @@ export class AgentSession {
2035
1999
  * Returns immediately if no retry is in progress.
2036
2000
  */
2037
2001
  async waitForRetry() {
2038
- if (this._retryPromise) {
2039
- await this._retryPromise;
2002
+ if (!this._retryPromise) {
2003
+ return;
2040
2004
  }
2005
+ await this._retryPromise;
2006
+ await this.agent.waitForIdle();
2041
2007
  }
2042
2008
  /** Whether auto-retry is currently in progress */
2043
2009
  get isRetrying() {
@@ -2070,15 +2036,10 @@ export class AgentSession {
2070
2036
  const prefix = this.settingsManager.getShellCommandPrefix();
2071
2037
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
2072
2038
  try {
2073
- const result = options?.operations
2074
- ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
2075
- onChunk,
2076
- signal: this._bashAbortController.signal,
2077
- })
2078
- : await executeBashCommand(resolvedCommand, {
2079
- onChunk,
2080
- signal: this._bashAbortController.signal,
2081
- });
2039
+ const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations(), {
2040
+ onChunk,
2041
+ signal: this._bashAbortController.signal,
2042
+ });
2082
2043
  this.recordBashResult(command, result, options);
2083
2044
  return result;
2084
2045
  }
@@ -2109,7 +2070,7 @@ export class AgentSession {
2109
2070
  }
2110
2071
  else {
2111
2072
  // Add to agent state immediately
2112
- this.agent.appendMessage(bashMessage);
2073
+ this.agent.state.messages.push(bashMessage);
2113
2074
  // Save to session
2114
2075
  this.sessionManager.appendMessage(bashMessage);
2115
2076
  }
@@ -2137,7 +2098,7 @@ export class AgentSession {
2137
2098
  return;
2138
2099
  for (const bashMessage of this._pendingBashMessages) {
2139
2100
  // Add to agent state
2140
- this.agent.appendMessage(bashMessage);
2101
+ this.agent.state.messages.push(bashMessage);
2141
2102
  // Save to session
2142
2103
  this.sessionManager.appendMessage(bashMessage);
2143
2104
  }
@@ -2146,130 +2107,12 @@ export class AgentSession {
2146
2107
  // =========================================================================
2147
2108
  // Session Management
2148
2109
  // =========================================================================
2149
- /**
2150
- * Switch to a different session file.
2151
- * Aborts current operation, loads messages, restores model/thinking.
2152
- * Listeners are preserved and will continue receiving events.
2153
- * @returns true if switch completed, false if cancelled by extension
2154
- */
2155
- async switchSession(sessionPath) {
2156
- const previousSessionFile = this.sessionManager.getSessionFile();
2157
- // Emit session_before_switch event (can be cancelled)
2158
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
2159
- const result = (await this._extensionRunner.emit({
2160
- type: "session_before_switch",
2161
- reason: "resume",
2162
- targetSessionFile: sessionPath,
2163
- }));
2164
- if (result?.cancel) {
2165
- return false;
2166
- }
2167
- }
2168
- this._disconnectFromAgent();
2169
- await this.abort();
2170
- this._steeringMessages = [];
2171
- this._followUpMessages = [];
2172
- this._pendingNextTurnMessages = [];
2173
- // Set new session
2174
- this.sessionManager.setSessionFile(sessionPath);
2175
- this.agent.sessionId = this.sessionManager.getSessionId();
2176
- // Reload messages
2177
- const sessionContext = this.sessionManager.buildSessionContext();
2178
- // Emit session_switch event to extensions
2179
- if (this._extensionRunner) {
2180
- await this._extensionRunner.emit({
2181
- type: "session_switch",
2182
- reason: "resume",
2183
- previousSessionFile,
2184
- });
2185
- }
2186
- // Emit session event to custom tools
2187
- this.agent.replaceMessages(sessionContext.messages);
2188
- // Restore model if saved
2189
- if (sessionContext.model) {
2190
- const previousModel = this.model;
2191
- const availableModels = await this._modelRegistry.getAvailable();
2192
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2193
- if (match) {
2194
- this.agent.setModel(match);
2195
- await this._emitModelSelect(match, previousModel, "restore");
2196
- }
2197
- }
2198
- const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2199
- const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
2200
- if (hasThinkingEntry) {
2201
- // Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
2202
- this.setThinkingLevel(sessionContext.thinkingLevel);
2203
- }
2204
- else {
2205
- const availableLevels = this.getAvailableThinkingLevels();
2206
- const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
2207
- ? defaultThinkingLevel
2208
- : this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
2209
- this.agent.setThinkingLevel(effectiveLevel);
2210
- this.sessionManager.appendThinkingLevelChange(effectiveLevel);
2211
- }
2212
- this._reconnectToAgent();
2213
- return true;
2214
- }
2215
2110
  /**
2216
2111
  * Set a display name for the current session.
2217
2112
  */
2218
2113
  setSessionName(name) {
2219
2114
  this.sessionManager.appendSessionInfo(name);
2220
2115
  }
2221
- /**
2222
- * Create a fork from a specific entry.
2223
- * Emits before_fork/fork session events to extensions.
2224
- *
2225
- * @param entryId ID of the entry to fork from
2226
- * @returns Object with:
2227
- * - selectedText: The text of the selected user message (for editor pre-fill)
2228
- * - cancelled: True if an extension cancelled the fork
2229
- */
2230
- async fork(entryId) {
2231
- const previousSessionFile = this.sessionFile;
2232
- const selectedEntry = this.sessionManager.getEntry(entryId);
2233
- if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
2234
- throw new Error("Invalid entry ID for forking");
2235
- }
2236
- const selectedText = this._extractUserMessageText(selectedEntry.message.content);
2237
- let skipConversationRestore = false;
2238
- // Emit session_before_fork event (can be cancelled)
2239
- if (this._extensionRunner?.hasHandlers("session_before_fork")) {
2240
- const result = (await this._extensionRunner.emit({
2241
- type: "session_before_fork",
2242
- entryId,
2243
- }));
2244
- if (result?.cancel) {
2245
- return { selectedText, cancelled: true };
2246
- }
2247
- skipConversationRestore = result?.skipConversationRestore ?? false;
2248
- }
2249
- // Clear pending messages (bound to old session state)
2250
- this._pendingNextTurnMessages = [];
2251
- if (!selectedEntry.parentId) {
2252
- this.sessionManager.newSession({ parentSession: previousSessionFile });
2253
- }
2254
- else {
2255
- this.sessionManager.createBranchedSession(selectedEntry.parentId);
2256
- }
2257
- this.agent.sessionId = this.sessionManager.getSessionId();
2258
- // Reload messages from entries (works for both file and in-memory mode)
2259
- const sessionContext = this.sessionManager.buildSessionContext();
2260
- // Emit session_fork event to extensions (after fork completes)
2261
- if (this._extensionRunner) {
2262
- await this._extensionRunner.emit({
2263
- type: "session_fork",
2264
- previousSessionFile,
2265
- });
2266
- }
2267
- // Emit session event to custom tools (with reason "fork")
2268
- if (!skipConversationRestore) {
2269
- this.agent.replaceMessages(sessionContext.messages);
2270
- }
2271
- return { selectedText, cancelled: false };
2272
- }
2273
2116
  // =========================================================================
2274
2117
  // Tree Navigation
2275
2118
  // =========================================================================
@@ -2425,7 +2268,7 @@ export class AgentSession {
2425
2268
  }
2426
2269
  // Update agent state
2427
2270
  const sessionContext = this.sessionManager.buildSessionContext();
2428
- this.agent.replaceMessages(sessionContext.messages);
2271
+ this.agent.state.messages = sessionContext.messages;
2429
2272
  // Emit session_tree event
2430
2273
  if (this._extensionRunner) {
2431
2274
  await this._extensionRunner.emit({
@@ -2565,6 +2408,7 @@ export class AgentSession {
2565
2408
  const toolRenderer = createToolHtmlRenderer({
2566
2409
  getToolDefinition: (name) => this.getToolDefinition(name),
2567
2410
  theme,
2411
+ cwd: this.sessionManager.getCwd(),
2568
2412
  });
2569
2413
  return await exportSessionToHtml(this.sessionManager, this.state, {
2570
2414
  outputPath,
@@ -2603,29 +2447,6 @@ export class AgentSession {
2603
2447
  writeFileSync(filePath, `${lines.join("\n")}\n`);
2604
2448
  return filePath;
2605
2449
  }
2606
- /**
2607
- * Import a JSONL session file.
2608
- * Copies the file into the session directory and switches to it (like /resume).
2609
- * @param inputPath Path to the JSONL file to import.
2610
- * @returns true if the session was switched successfully.
2611
- */
2612
- async importFromJsonl(inputPath) {
2613
- const resolved = resolve(inputPath);
2614
- if (!existsSync(resolved)) {
2615
- throw new Error(`File not found: ${resolved}`);
2616
- }
2617
- // Copy into the session directory so we don't modify the original
2618
- const sessionDir = this.sessionManager.getSessionDir();
2619
- if (!existsSync(sessionDir)) {
2620
- mkdirSync(sessionDir, { recursive: true });
2621
- }
2622
- const destPath = join(sessionDir, basename(resolved));
2623
- // Avoid overwriting if source and destination are the same file
2624
- if (resolve(destPath) !== resolved) {
2625
- copyFileSync(resolved, destPath);
2626
- }
2627
- return this.switchSession(destPath);
2628
- }
2629
2450
  // =========================================================================
2630
2451
  // Utilities
2631
2452
  // =========================================================================