@mariozechner/pi-coding-agent 0.64.0 → 0.65.1

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 (137) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +12 -6
  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 +236 -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 +5 -42
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +46 -237
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/bash-executor.d.ts.map +1 -1
  20. package/dist/core/bash-executor.js +19 -7
  21. package/dist/core/bash-executor.js.map +1 -1
  22. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  23. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  24. package/dist/core/export-html/tool-renderer.js +2 -2
  25. package/dist/core/export-html/tool-renderer.js.map +1 -1
  26. package/dist/core/extensions/index.d.ts +2 -2
  27. package/dist/core/extensions/index.d.ts.map +1 -1
  28. package/dist/core/extensions/index.js +1 -1
  29. package/dist/core/extensions/index.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +16 -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/package-manager.d.ts +20 -0
  47. package/dist/core/package-manager.d.ts.map +1 -1
  48. package/dist/core/package-manager.js +55 -9
  49. package/dist/core/package-manager.js.map +1 -1
  50. package/dist/core/resource-loader.d.ts.map +1 -1
  51. package/dist/core/resource-loader.js +25 -3
  52. package/dist/core/resource-loader.js.map +1 -1
  53. package/dist/core/sdk.d.ts +4 -1
  54. package/dist/core/sdk.d.ts.map +1 -1
  55. package/dist/core/sdk.js +4 -1
  56. package/dist/core/sdk.js.map +1 -1
  57. package/dist/core/session-cwd.d.ts +19 -0
  58. package/dist/core/session-cwd.d.ts.map +1 -0
  59. package/dist/core/session-cwd.js +38 -0
  60. package/dist/core/session-cwd.js.map +1 -0
  61. package/dist/core/session-manager.d.ts +5 -1
  62. package/dist/core/session-manager.d.ts.map +1 -1
  63. package/dist/core/session-manager.js +16 -8
  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/bash.d.ts.map +1 -1
  70. package/dist/core/tools/bash.js +19 -9
  71. package/dist/core/tools/bash.js.map +1 -1
  72. package/dist/index.d.ts +3 -3
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +3 -3
  75. package/dist/index.js.map +1 -1
  76. package/dist/main.d.ts.map +1 -1
  77. package/dist/main.js +245 -426
  78. package/dist/main.js.map +1 -1
  79. package/dist/migrations.d.ts.map +1 -1
  80. package/dist/migrations.js +20 -0
  81. package/dist/migrations.js.map +1 -1
  82. package/dist/modes/interactive/components/footer.d.ts +1 -0
  83. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  84. package/dist/modes/interactive/components/footer.js +4 -1
  85. package/dist/modes/interactive/components/footer.js.map +1 -1
  86. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  87. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  88. package/dist/modes/interactive/components/tree-selector.js +48 -15
  89. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.d.ts +10 -4
  91. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  92. package/dist/modes/interactive/interactive-mode.js +160 -94
  93. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  94. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  95. package/dist/modes/interactive/theme/theme.js +15 -11
  96. package/dist/modes/interactive/theme/theme.js.map +1 -1
  97. package/dist/modes/print-mode.d.ts +2 -2
  98. package/dist/modes/print-mode.d.ts.map +1 -1
  99. package/dist/modes/print-mode.js +41 -36
  100. package/dist/modes/print-mode.js.map +1 -1
  101. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  102. package/dist/modes/rpc/rpc-client.js +1 -0
  103. package/dist/modes/rpc/rpc-client.js.map +1 -1
  104. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  105. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  106. package/dist/modes/rpc/rpc-mode.js +92 -64
  107. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  108. package/dist/package-manager-cli.d.ts +4 -0
  109. package/dist/package-manager-cli.d.ts.map +1 -0
  110. package/dist/package-manager-cli.js +234 -0
  111. package/dist/package-manager-cli.js.map +1 -0
  112. package/dist/utils/paths.d.ts +7 -0
  113. package/dist/utils/paths.d.ts.map +1 -0
  114. package/dist/utils/paths.js +19 -0
  115. package/dist/utils/paths.js.map +1 -0
  116. package/docs/extensions.md +72 -40
  117. package/docs/keybindings.md +2 -0
  118. package/docs/sdk.md +227 -74
  119. package/docs/settings.md +1 -1
  120. package/docs/tree.md +6 -3
  121. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  122. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  123. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  124. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  125. package/examples/extensions/doom-overlay/doom/build.sh +2 -2
  126. package/examples/extensions/hello.ts +18 -17
  127. package/examples/extensions/hidden-thinking-label.ts +0 -4
  128. package/examples/extensions/rpc-demo.ts +3 -9
  129. package/examples/extensions/status-line.ts +0 -8
  130. package/examples/extensions/todo.ts +0 -2
  131. package/examples/extensions/tools.ts +0 -5
  132. package/examples/extensions/widget-placement.ts +4 -12
  133. package/examples/extensions/with-deps/package-lock.json +2 -2
  134. package/examples/extensions/with-deps/package.json +1 -1
  135. package/examples/sdk/13-session-runtime.ts +67 -0
  136. package/examples/sdk/README.md +4 -1
  137. 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
@@ -550,10 +553,10 @@ export class AgentSession {
550
553
  validToolNames.push(name);
551
554
  }
552
555
  }
553
- this.agent.setTools(tools);
556
+ this.agent.state.tools = tools;
554
557
  // Rebuild base system prompt with new tool set
555
558
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
556
- this.agent.setSystemPrompt(this._baseSystemPrompt);
559
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
557
560
  }
558
561
  /** Whether compaction or branch summarization is currently running */
559
562
  get isCompacting() {
@@ -567,11 +570,11 @@ export class AgentSession {
567
570
  }
568
571
  /** Current steering mode */
569
572
  get steeringMode() {
570
- return this.agent.getSteeringMode();
573
+ return this.agent.steeringMode;
571
574
  }
572
575
  /** Current follow-up mode */
573
576
  get followUpMode() {
574
- return this.agent.getFollowUpMode();
577
+ return this.agent.followUpMode;
575
578
  }
576
579
  /** Current session file path, or undefined if sessions are disabled */
577
580
  get sessionFile() {
@@ -762,11 +765,11 @@ export class AgentSession {
762
765
  }
763
766
  // Apply extension-modified system prompt, or reset to base
764
767
  if (result?.systemPrompt) {
765
- this.agent.setSystemPrompt(result.systemPrompt);
768
+ this.agent.state.systemPrompt = result.systemPrompt;
766
769
  }
767
770
  else {
768
771
  // Ensure we're using the base prompt (in case previous turn had modifications)
769
- this.agent.setSystemPrompt(this._baseSystemPrompt);
772
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
770
773
  }
771
774
  }
772
775
  await this.agent.prompt(messages);
@@ -947,7 +950,7 @@ export class AgentSession {
947
950
  await this.agent.prompt(appMessage);
948
951
  }
949
952
  else {
950
- this.agent.appendMessage(appMessage);
953
+ this.agent.state.messages.push(appMessage);
951
954
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
952
955
  this._emit({ type: "message_start", message: appMessage });
953
956
  this._emit({ type: "message_end", message: appMessage });
@@ -1027,54 +1030,6 @@ export class AgentSession {
1027
1030
  this.agent.abort();
1028
1031
  await this.agent.waitForIdle();
1029
1032
  }
1030
- /**
1031
- * Start a new session, optionally with initial messages and parent tracking.
1032
- * Clears all messages and starts a new session.
1033
- * Listeners are preserved and will continue receiving events.
1034
- * @param options.parentSession - Optional parent session path for tracking
1035
- * @param options.setup - Optional callback to initialize session (e.g., append messages)
1036
- * @returns true if completed, false if cancelled by extension
1037
- */
1038
- async newSession(options) {
1039
- const previousSessionFile = this.sessionFile;
1040
- // Emit session_before_switch event with reason "new" (can be cancelled)
1041
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
1042
- const result = (await this._extensionRunner.emit({
1043
- type: "session_before_switch",
1044
- reason: "new",
1045
- }));
1046
- if (result?.cancel) {
1047
- return false;
1048
- }
1049
- }
1050
- this._disconnectFromAgent();
1051
- await this.abort();
1052
- this.agent.reset();
1053
- this.sessionManager.newSession({ parentSession: options?.parentSession });
1054
- this.agent.sessionId = this.sessionManager.getSessionId();
1055
- this._steeringMessages = [];
1056
- this._followUpMessages = [];
1057
- this._pendingNextTurnMessages = [];
1058
- this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1059
- // Run setup callback if provided (e.g., to append initial messages)
1060
- if (options?.setup) {
1061
- await options.setup(this.sessionManager);
1062
- // Sync agent state with session manager after setup
1063
- const sessionContext = this.sessionManager.buildSessionContext();
1064
- this.agent.replaceMessages(sessionContext.messages);
1065
- }
1066
- this._reconnectToAgent();
1067
- // Emit session_switch event with reason "new" to extensions
1068
- if (this._extensionRunner) {
1069
- await this._extensionRunner.emit({
1070
- type: "session_switch",
1071
- reason: "new",
1072
- previousSessionFile,
1073
- });
1074
- }
1075
- // Emit session event to custom tools
1076
- return true;
1077
- }
1078
1033
  // =========================================================================
1079
1034
  // Model Management
1080
1035
  // =========================================================================
@@ -1101,7 +1056,7 @@ export class AgentSession {
1101
1056
  }
1102
1057
  const previousModel = this.model;
1103
1058
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1104
- this.agent.setModel(model);
1059
+ this.agent.state.model = model;
1105
1060
  this.sessionManager.appendModelChange(model.provider, model.id);
1106
1061
  this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
1107
1062
  // Re-clamp thinking level for new model's capabilities
@@ -1120,11 +1075,8 @@ export class AgentSession {
1120
1075
  }
1121
1076
  return this._cycleAvailableModel(direction);
1122
1077
  }
1123
- _getScopedModelsWithAuth() {
1124
- return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1125
- }
1126
1078
  async _cycleScopedModel(direction) {
1127
- const scopedModels = this._getScopedModelsWithAuth();
1079
+ const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1128
1080
  if (scopedModels.length <= 1)
1129
1081
  return undefined;
1130
1082
  const currentModel = this.model;
@@ -1136,7 +1088,7 @@ export class AgentSession {
1136
1088
  const next = scopedModels[nextIndex];
1137
1089
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1138
1090
  // Apply model
1139
- this.agent.setModel(next.model);
1091
+ this.agent.state.model = next.model;
1140
1092
  this.sessionManager.appendModelChange(next.model.provider, next.model.id);
1141
1093
  this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
1142
1094
  // Apply thinking level.
@@ -1159,7 +1111,7 @@ export class AgentSession {
1159
1111
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1160
1112
  const nextModel = availableModels[nextIndex];
1161
1113
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1162
- this.agent.setModel(nextModel);
1114
+ this.agent.state.model = nextModel;
1163
1115
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
1164
1116
  this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
1165
1117
  // Re-clamp thinking level for new model's capabilities
@@ -1180,7 +1132,7 @@ export class AgentSession {
1180
1132
  const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
1181
1133
  // Only persist if actually changing
1182
1134
  const isChanging = effectiveLevel !== this.agent.state.thinkingLevel;
1183
- this.agent.setThinkingLevel(effectiveLevel);
1135
+ this.agent.state.thinkingLevel = effectiveLevel;
1184
1136
  if (isChanging) {
1185
1137
  this.sessionManager.appendThinkingLevelChange(effectiveLevel);
1186
1138
  if (this.supportsThinking() || effectiveLevel !== "off") {
@@ -1259,7 +1211,7 @@ export class AgentSession {
1259
1211
  * Saves to settings.
1260
1212
  */
1261
1213
  setSteeringMode(mode) {
1262
- this.agent.setSteeringMode(mode);
1214
+ this.agent.steeringMode = mode;
1263
1215
  this.settingsManager.setSteeringMode(mode);
1264
1216
  }
1265
1217
  /**
@@ -1267,7 +1219,7 @@ export class AgentSession {
1267
1219
  * Saves to settings.
1268
1220
  */
1269
1221
  setFollowUpMode(mode) {
1270
- this.agent.setFollowUpMode(mode);
1222
+ this.agent.followUpMode = mode;
1271
1223
  this.settingsManager.setFollowUpMode(mode);
1272
1224
  }
1273
1225
  // =========================================================================
@@ -1342,7 +1294,7 @@ export class AgentSession {
1342
1294
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1343
1295
  const newEntries = this.sessionManager.getEntries();
1344
1296
  const sessionContext = this.sessionManager.buildSessionContext();
1345
- this.agent.replaceMessages(sessionContext.messages);
1297
+ this.agent.state.messages = sessionContext.messages;
1346
1298
  // Get the saved compaction entry for the extension event
1347
1299
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1348
1300
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1448,7 +1400,7 @@ export class AgentSession {
1448
1400
  // but we don't want it in context for the retry)
1449
1401
  const messages = this.agent.state.messages;
1450
1402
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
1451
- this.agent.replaceMessages(messages.slice(0, -1));
1403
+ this.agent.state.messages = messages.slice(0, -1);
1452
1404
  }
1453
1405
  await this._runAutoCompaction("overflow", true);
1454
1406
  return;
@@ -1579,7 +1531,7 @@ export class AgentSession {
1579
1531
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1580
1532
  const newEntries = this.sessionManager.getEntries();
1581
1533
  const sessionContext = this.sessionManager.buildSessionContext();
1582
- this.agent.replaceMessages(sessionContext.messages);
1534
+ this.agent.state.messages = sessionContext.messages;
1583
1535
  // Get the saved compaction entry for the extension event
1584
1536
  const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
1585
1537
  if (this._extensionRunner && savedCompactionEntry) {
@@ -1600,7 +1552,7 @@ export class AgentSession {
1600
1552
  const messages = this.agent.state.messages;
1601
1553
  const lastMsg = messages[messages.length - 1];
1602
1554
  if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
1603
- this.agent.replaceMessages(messages.slice(0, -1));
1555
+ this.agent.state.messages = messages.slice(0, -1);
1604
1556
  }
1605
1557
  setTimeout(() => {
1606
1558
  this.agent.continue().catch(() => { });
@@ -1656,8 +1608,8 @@ export class AgentSession {
1656
1608
  }
1657
1609
  if (this._extensionRunner) {
1658
1610
  this._applyExtensionBindings(this._extensionRunner);
1659
- await this._extensionRunner.emit({ type: "session_start" });
1660
- await this.extendResourcesFromExtensions("startup");
1611
+ await this._extensionRunner.emit(this._sessionStartEvent);
1612
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
1661
1613
  }
1662
1614
  }
1663
1615
  async extendResourcesFromExtensions(reason) {
@@ -1675,7 +1627,7 @@ export class AgentSession {
1675
1627
  };
1676
1628
  this._resourceLoader.extendResources(extensionPaths);
1677
1629
  this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
1678
- this.agent.setSystemPrompt(this._baseSystemPrompt);
1630
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
1679
1631
  }
1680
1632
  buildExtensionResourcePaths(entries) {
1681
1633
  return entries.map((entry) => {
@@ -1717,7 +1669,7 @@ export class AgentSession {
1717
1669
  if (!refreshedModel || refreshedModel === currentModel) {
1718
1670
  return;
1719
1671
  }
1720
- this.agent.setModel(refreshedModel);
1672
+ this.agent.state.model = refreshedModel;
1721
1673
  }
1722
1674
  _bindExtensionCore(runner) {
1723
1675
  const getCommands = () => {
@@ -1928,7 +1880,7 @@ export class AgentSession {
1928
1880
  async reload() {
1929
1881
  const previousFlagValues = this._extensionRunner?.getFlagValues();
1930
1882
  await this._extensionRunner?.emit({ type: "session_shutdown" });
1931
- this.settingsManager.reload();
1883
+ await this.settingsManager.reload();
1932
1884
  resetApiProviders();
1933
1885
  await this._resourceLoader.reload();
1934
1886
  this._buildRuntime({
@@ -1941,7 +1893,7 @@ export class AgentSession {
1941
1893
  this._extensionShutdownHandler ||
1942
1894
  this._extensionErrorListener;
1943
1895
  if (this._extensionRunner && hasBindings) {
1944
- await this._extensionRunner.emit({ type: "session_start" });
1896
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
1945
1897
  await this.extendResourcesFromExtensions("reload");
1946
1898
  }
1947
1899
  }
@@ -2004,7 +1956,7 @@ export class AgentSession {
2004
1956
  // Remove error message from agent state (keep in session for history)
2005
1957
  const messages = this.agent.state.messages;
2006
1958
  if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
2007
- this.agent.replaceMessages(messages.slice(0, -1));
1959
+ this.agent.state.messages = messages.slice(0, -1);
2008
1960
  }
2009
1961
  // Wait with exponential backoff (abortable)
2010
1962
  this._retryAbortController = new AbortController();
@@ -2047,9 +1999,11 @@ export class AgentSession {
2047
1999
  * Returns immediately if no retry is in progress.
2048
2000
  */
2049
2001
  async waitForRetry() {
2050
- if (this._retryPromise) {
2051
- await this._retryPromise;
2002
+ if (!this._retryPromise) {
2003
+ return;
2052
2004
  }
2005
+ await this._retryPromise;
2006
+ await this.agent.waitForIdle();
2053
2007
  }
2054
2008
  /** Whether auto-retry is currently in progress */
2055
2009
  get isRetrying() {
@@ -2082,15 +2036,10 @@ export class AgentSession {
2082
2036
  const prefix = this.settingsManager.getShellCommandPrefix();
2083
2037
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
2084
2038
  try {
2085
- const result = options?.operations
2086
- ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
2087
- onChunk,
2088
- signal: this._bashAbortController.signal,
2089
- })
2090
- : await executeBashCommand(resolvedCommand, {
2091
- onChunk,
2092
- signal: this._bashAbortController.signal,
2093
- });
2039
+ const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations(), {
2040
+ onChunk,
2041
+ signal: this._bashAbortController.signal,
2042
+ });
2094
2043
  this.recordBashResult(command, result, options);
2095
2044
  return result;
2096
2045
  }
@@ -2121,7 +2070,7 @@ export class AgentSession {
2121
2070
  }
2122
2071
  else {
2123
2072
  // Add to agent state immediately
2124
- this.agent.appendMessage(bashMessage);
2073
+ this.agent.state.messages.push(bashMessage);
2125
2074
  // Save to session
2126
2075
  this.sessionManager.appendMessage(bashMessage);
2127
2076
  }
@@ -2149,7 +2098,7 @@ export class AgentSession {
2149
2098
  return;
2150
2099
  for (const bashMessage of this._pendingBashMessages) {
2151
2100
  // Add to agent state
2152
- this.agent.appendMessage(bashMessage);
2101
+ this.agent.state.messages.push(bashMessage);
2153
2102
  // Save to session
2154
2103
  this.sessionManager.appendMessage(bashMessage);
2155
2104
  }
@@ -2158,130 +2107,12 @@ export class AgentSession {
2158
2107
  // =========================================================================
2159
2108
  // Session Management
2160
2109
  // =========================================================================
2161
- /**
2162
- * Switch to a different session file.
2163
- * Aborts current operation, loads messages, restores model/thinking.
2164
- * Listeners are preserved and will continue receiving events.
2165
- * @returns true if switch completed, false if cancelled by extension
2166
- */
2167
- async switchSession(sessionPath) {
2168
- const previousSessionFile = this.sessionManager.getSessionFile();
2169
- // Emit session_before_switch event (can be cancelled)
2170
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
2171
- const result = (await this._extensionRunner.emit({
2172
- type: "session_before_switch",
2173
- reason: "resume",
2174
- targetSessionFile: sessionPath,
2175
- }));
2176
- if (result?.cancel) {
2177
- return false;
2178
- }
2179
- }
2180
- this._disconnectFromAgent();
2181
- await this.abort();
2182
- this._steeringMessages = [];
2183
- this._followUpMessages = [];
2184
- this._pendingNextTurnMessages = [];
2185
- // Set new session
2186
- this.sessionManager.setSessionFile(sessionPath);
2187
- this.agent.sessionId = this.sessionManager.getSessionId();
2188
- // Reload messages
2189
- const sessionContext = this.sessionManager.buildSessionContext();
2190
- // Emit session_switch event to extensions
2191
- if (this._extensionRunner) {
2192
- await this._extensionRunner.emit({
2193
- type: "session_switch",
2194
- reason: "resume",
2195
- previousSessionFile,
2196
- });
2197
- }
2198
- // Emit session event to custom tools
2199
- this.agent.replaceMessages(sessionContext.messages);
2200
- // Restore model if saved
2201
- if (sessionContext.model) {
2202
- const previousModel = this.model;
2203
- const availableModels = await this._modelRegistry.getAvailable();
2204
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2205
- if (match) {
2206
- this.agent.setModel(match);
2207
- await this._emitModelSelect(match, previousModel, "restore");
2208
- }
2209
- }
2210
- const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2211
- const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
2212
- if (hasThinkingEntry) {
2213
- // Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
2214
- this.setThinkingLevel(sessionContext.thinkingLevel);
2215
- }
2216
- else {
2217
- const availableLevels = this.getAvailableThinkingLevels();
2218
- const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
2219
- ? defaultThinkingLevel
2220
- : this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
2221
- this.agent.setThinkingLevel(effectiveLevel);
2222
- this.sessionManager.appendThinkingLevelChange(effectiveLevel);
2223
- }
2224
- this._reconnectToAgent();
2225
- return true;
2226
- }
2227
2110
  /**
2228
2111
  * Set a display name for the current session.
2229
2112
  */
2230
2113
  setSessionName(name) {
2231
2114
  this.sessionManager.appendSessionInfo(name);
2232
2115
  }
2233
- /**
2234
- * Create a fork from a specific entry.
2235
- * Emits before_fork/fork session events to extensions.
2236
- *
2237
- * @param entryId ID of the entry to fork from
2238
- * @returns Object with:
2239
- * - selectedText: The text of the selected user message (for editor pre-fill)
2240
- * - cancelled: True if an extension cancelled the fork
2241
- */
2242
- async fork(entryId) {
2243
- const previousSessionFile = this.sessionFile;
2244
- const selectedEntry = this.sessionManager.getEntry(entryId);
2245
- if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
2246
- throw new Error("Invalid entry ID for forking");
2247
- }
2248
- const selectedText = this._extractUserMessageText(selectedEntry.message.content);
2249
- let skipConversationRestore = false;
2250
- // Emit session_before_fork event (can be cancelled)
2251
- if (this._extensionRunner?.hasHandlers("session_before_fork")) {
2252
- const result = (await this._extensionRunner.emit({
2253
- type: "session_before_fork",
2254
- entryId,
2255
- }));
2256
- if (result?.cancel) {
2257
- return { selectedText, cancelled: true };
2258
- }
2259
- skipConversationRestore = result?.skipConversationRestore ?? false;
2260
- }
2261
- // Clear pending messages (bound to old session state)
2262
- this._pendingNextTurnMessages = [];
2263
- if (!selectedEntry.parentId) {
2264
- this.sessionManager.newSession({ parentSession: previousSessionFile });
2265
- }
2266
- else {
2267
- this.sessionManager.createBranchedSession(selectedEntry.parentId);
2268
- }
2269
- this.agent.sessionId = this.sessionManager.getSessionId();
2270
- // Reload messages from entries (works for both file and in-memory mode)
2271
- const sessionContext = this.sessionManager.buildSessionContext();
2272
- // Emit session_fork event to extensions (after fork completes)
2273
- if (this._extensionRunner) {
2274
- await this._extensionRunner.emit({
2275
- type: "session_fork",
2276
- previousSessionFile,
2277
- });
2278
- }
2279
- // Emit session event to custom tools (with reason "fork")
2280
- if (!skipConversationRestore) {
2281
- this.agent.replaceMessages(sessionContext.messages);
2282
- }
2283
- return { selectedText, cancelled: false };
2284
- }
2285
2116
  // =========================================================================
2286
2117
  // Tree Navigation
2287
2118
  // =========================================================================
@@ -2437,7 +2268,7 @@ export class AgentSession {
2437
2268
  }
2438
2269
  // Update agent state
2439
2270
  const sessionContext = this.sessionManager.buildSessionContext();
2440
- this.agent.replaceMessages(sessionContext.messages);
2271
+ this.agent.state.messages = sessionContext.messages;
2441
2272
  // Emit session_tree event
2442
2273
  if (this._extensionRunner) {
2443
2274
  await this._extensionRunner.emit({
@@ -2577,6 +2408,7 @@ export class AgentSession {
2577
2408
  const toolRenderer = createToolHtmlRenderer({
2578
2409
  getToolDefinition: (name) => this.getToolDefinition(name),
2579
2410
  theme,
2411
+ cwd: this.sessionManager.getCwd(),
2580
2412
  });
2581
2413
  return await exportSessionToHtml(this.sessionManager, this.state, {
2582
2414
  outputPath,
@@ -2615,29 +2447,6 @@ export class AgentSession {
2615
2447
  writeFileSync(filePath, `${lines.join("\n")}\n`);
2616
2448
  return filePath;
2617
2449
  }
2618
- /**
2619
- * Import a JSONL session file.
2620
- * Copies the file into the session directory and switches to it (like /resume).
2621
- * @param inputPath Path to the JSONL file to import.
2622
- * @returns true if the session was switched successfully.
2623
- */
2624
- async importFromJsonl(inputPath) {
2625
- const resolved = resolve(inputPath);
2626
- if (!existsSync(resolved)) {
2627
- throw new Error(`File not found: ${resolved}`);
2628
- }
2629
- // Copy into the session directory so we don't modify the original
2630
- const sessionDir = this.sessionManager.getSessionDir();
2631
- if (!existsSync(sessionDir)) {
2632
- mkdirSync(sessionDir, { recursive: true });
2633
- }
2634
- const destPath = join(sessionDir, basename(resolved));
2635
- // Avoid overwriting if source and destination are the same file
2636
- if (resolve(destPath) !== resolved) {
2637
- copyFileSync(resolved, destPath);
2638
- }
2639
- return this.switchSession(destPath);
2640
- }
2641
2450
  // =========================================================================
2642
2451
  // Utilities
2643
2452
  // =========================================================================