@draht/coding-agent 2026.3.25 → 2026.4.5

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 (111) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +6 -2
  3. package/dist/core/agent-session-runtime.d.ts +136 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +267 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +22 -44
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +44 -248
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/auth-storage.d.ts +3 -1
  12. package/dist/core/auth-storage.d.ts.map +1 -1
  13. package/dist/core/auth-storage.js +5 -2
  14. package/dist/core/auth-storage.js.map +1 -1
  15. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  16. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  17. package/dist/core/compaction/branch-summarization.js +2 -2
  18. package/dist/core/compaction/branch-summarization.js.map +1 -1
  19. package/dist/core/compaction/compaction.d.ts +2 -2
  20. package/dist/core/compaction/compaction.d.ts.map +1 -1
  21. package/dist/core/compaction/compaction.js +9 -9
  22. package/dist/core/compaction/compaction.js.map +1 -1
  23. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  24. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  25. package/dist/core/export-html/tool-renderer.js +2 -2
  26. package/dist/core/export-html/tool-renderer.js.map +1 -1
  27. package/dist/core/extensions/index.d.ts +2 -2
  28. package/dist/core/extensions/index.d.ts.map +1 -1
  29. package/dist/core/extensions/index.js +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/types.d.ts +16 -16
  32. package/dist/core/extensions/types.d.ts.map +1 -1
  33. package/dist/core/extensions/types.js +10 -0
  34. package/dist/core/extensions/types.js.map +1 -1
  35. package/dist/core/footer-data-provider.d.ts +5 -1
  36. package/dist/core/footer-data-provider.d.ts.map +1 -1
  37. package/dist/core/footer-data-provider.js +70 -8
  38. package/dist/core/footer-data-provider.js.map +1 -1
  39. package/dist/core/index.d.ts +2 -1
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/index.js +2 -1
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/model-registry.d.ts +21 -3
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +90 -70
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts.map +1 -1
  48. package/dist/core/model-resolver.js +4 -4
  49. package/dist/core/model-resolver.js.map +1 -1
  50. package/dist/core/resolve-config-value.d.ts +6 -0
  51. package/dist/core/resolve-config-value.d.ts.map +1 -1
  52. package/dist/core/resolve-config-value.js +37 -5
  53. package/dist/core/resolve-config-value.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts +2 -0
  55. package/dist/core/resource-loader.d.ts.map +1 -1
  56. package/dist/core/resource-loader.js +5 -1
  57. package/dist/core/resource-loader.js.map +1 -1
  58. package/dist/core/sdk.d.ts +6 -3
  59. package/dist/core/sdk.d.ts.map +1 -1
  60. package/dist/core/sdk.js +17 -23
  61. package/dist/core/sdk.js.map +1 -1
  62. package/dist/index.d.ts +3 -3
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -3
  65. package/dist/index.js.map +1 -1
  66. package/dist/main.d.ts.map +1 -1
  67. package/dist/main.js +49 -10
  68. package/dist/main.js.map +1 -1
  69. package/dist/modes/interactive/components/footer.d.ts +1 -0
  70. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/footer.js +4 -1
  72. package/dist/modes/interactive/components/footer.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.d.ts +8 -4
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +90 -87
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  78. package/dist/modes/interactive/theme/theme.js +6 -11
  79. package/dist/modes/interactive/theme/theme.js.map +1 -1
  80. package/dist/modes/print-mode.d.ts +4 -4
  81. package/dist/modes/print-mode.d.ts.map +1 -1
  82. package/dist/modes/print-mode.js +87 -74
  83. package/dist/modes/print-mode.js.map +1 -1
  84. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  85. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.js +69 -49
  87. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  88. package/docs/development.md +2 -2
  89. package/docs/extensions.md +78 -22
  90. package/docs/models.md +6 -0
  91. package/docs/packages.md +3 -3
  92. package/docs/rpc.md +2 -2
  93. package/docs/sdk.md +170 -82
  94. package/docs/tree.md +1 -1
  95. package/examples/extensions/custom-compaction.ts +17 -4
  96. package/examples/extensions/handoff.ts +5 -2
  97. package/examples/extensions/hello.ts +18 -17
  98. package/examples/extensions/qna.ts +5 -2
  99. package/examples/extensions/rpc-demo.ts +3 -9
  100. package/examples/extensions/status-line.ts +0 -8
  101. package/examples/extensions/subagent/index.ts +1 -1
  102. package/examples/extensions/summarize.ts +15 -4
  103. package/examples/extensions/todo.ts +0 -2
  104. package/examples/extensions/tools.ts +0 -5
  105. package/examples/extensions/widget-placement.ts +4 -12
  106. package/examples/sdk/02-custom-model.ts +1 -1
  107. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  108. package/examples/sdk/12-full-control.ts +1 -1
  109. package/examples/sdk/13-session-runtime.ts +49 -0
  110. package/examples/sdk/README.md +5 -4
  111. 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 "@draht/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);
@@ -134,6 +137,23 @@ export class AgentSession {
134
137
  get modelRegistry() {
135
138
  return this._modelRegistry;
136
139
  }
140
+ async _getRequiredRequestAuth(model) {
141
+ const result = await this._modelRegistry.getApiKeyAndHeaders(model);
142
+ if (!result.ok) {
143
+ throw new Error(result.error);
144
+ }
145
+ if (result.apiKey) {
146
+ return { apiKey: result.apiKey, headers: result.headers };
147
+ }
148
+ const isOAuth = this._modelRegistry.isUsingOAuth(model);
149
+ if (isOAuth) {
150
+ throw new Error(`Authentication failed for "${model.provider}". ` +
151
+ `Credentials may have expired or network is unavailable. ` +
152
+ `Run '/login ${model.provider}' to re-authenticate.`);
153
+ }
154
+ throw new Error(`No API key found for ${model.provider}.\n\n` +
155
+ `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`);
156
+ }
137
157
  /**
138
158
  * Install tool hooks once on the Agent instance.
139
159
  *
@@ -686,9 +706,7 @@ export class AgentSession {
686
706
  `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}\n\n` +
687
707
  "Then use /model to select a model.");
688
708
  }
689
- // Validate API key
690
- const apiKey = await this._modelRegistry.getApiKey(this.model);
691
- if (!apiKey) {
709
+ if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
692
710
  const isOAuth = this._modelRegistry.isUsingOAuth(this.model);
693
711
  if (isOAuth) {
694
712
  throw new Error(`Authentication failed for "${this.model.provider}". ` +
@@ -1000,54 +1018,6 @@ export class AgentSession {
1000
1018
  this.agent.abort();
1001
1019
  await this.agent.waitForIdle();
1002
1020
  }
1003
- /**
1004
- * Start a new session, optionally with initial messages and parent tracking.
1005
- * Clears all messages and starts a new session.
1006
- * Listeners are preserved and will continue receiving events.
1007
- * @param options.parentSession - Optional parent session path for tracking
1008
- * @param options.setup - Optional callback to initialize session (e.g., append messages)
1009
- * @returns true if completed, false if cancelled by extension
1010
- */
1011
- async newSession(options) {
1012
- const previousSessionFile = this.sessionFile;
1013
- // Emit session_before_switch event with reason "new" (can be cancelled)
1014
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
1015
- const result = (await this._extensionRunner.emit({
1016
- type: "session_before_switch",
1017
- reason: "new",
1018
- }));
1019
- if (result?.cancel) {
1020
- return false;
1021
- }
1022
- }
1023
- this._disconnectFromAgent();
1024
- await this.abort();
1025
- this.agent.reset();
1026
- this.sessionManager.newSession({ parentSession: options?.parentSession });
1027
- this.agent.sessionId = this.sessionManager.getSessionId();
1028
- this._steeringMessages = [];
1029
- this._followUpMessages = [];
1030
- this._pendingNextTurnMessages = [];
1031
- this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1032
- // Run setup callback if provided (e.g., to append initial messages)
1033
- if (options?.setup) {
1034
- await options.setup(this.sessionManager);
1035
- // Sync agent state with session manager after setup
1036
- const sessionContext = this.sessionManager.buildSessionContext();
1037
- this.agent.replaceMessages(sessionContext.messages);
1038
- }
1039
- this._reconnectToAgent();
1040
- // Emit session_switch event with reason "new" to extensions
1041
- if (this._extensionRunner) {
1042
- await this._extensionRunner.emit({
1043
- type: "session_switch",
1044
- reason: "new",
1045
- previousSessionFile,
1046
- });
1047
- }
1048
- // Emit session event to custom tools
1049
- return true;
1050
- }
1051
1021
  // =========================================================================
1052
1022
  // Model Management
1053
1023
  // =========================================================================
@@ -1065,12 +1035,11 @@ export class AgentSession {
1065
1035
  }
1066
1036
  /**
1067
1037
  * Set model directly.
1068
- * Validates API key, saves to session and settings.
1069
- * @throws Error if no API key available for the model
1038
+ * Validates that auth is configured, saves to session and settings.
1039
+ * @throws Error if no auth is configured for the model
1070
1040
  */
1071
1041
  async setModel(model) {
1072
- const apiKey = await this._modelRegistry.getApiKey(model);
1073
- if (!apiKey) {
1042
+ if (!this._modelRegistry.hasConfiguredAuth(model)) {
1074
1043
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1075
1044
  }
1076
1045
  const previousModel = this.model;
@@ -1094,27 +1063,8 @@ export class AgentSession {
1094
1063
  }
1095
1064
  return this._cycleAvailableModel(direction);
1096
1065
  }
1097
- async _getScopedModelsWithApiKey() {
1098
- const apiKeysByProvider = new Map();
1099
- const result = [];
1100
- for (const scoped of this._scopedModels) {
1101
- const provider = scoped.model.provider;
1102
- let apiKey;
1103
- if (apiKeysByProvider.has(provider)) {
1104
- apiKey = apiKeysByProvider.get(provider);
1105
- }
1106
- else {
1107
- apiKey = await this._modelRegistry.getApiKeyForProvider(provider);
1108
- apiKeysByProvider.set(provider, apiKey);
1109
- }
1110
- if (apiKey) {
1111
- result.push(scoped);
1112
- }
1113
- }
1114
- return result;
1115
- }
1116
1066
  async _cycleScopedModel(direction) {
1117
- const scopedModels = await this._getScopedModelsWithApiKey();
1067
+ const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1118
1068
  if (scopedModels.length <= 1)
1119
1069
  return undefined;
1120
1070
  const currentModel = this.model;
@@ -1148,10 +1098,6 @@ export class AgentSession {
1148
1098
  const len = availableModels.length;
1149
1099
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1150
1100
  const nextModel = availableModels[nextIndex];
1151
- const apiKey = await this._modelRegistry.getApiKey(nextModel);
1152
- if (!apiKey) {
1153
- throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1154
- }
1155
1101
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1156
1102
  this.agent.setModel(nextModel);
1157
1103
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
@@ -1280,10 +1226,7 @@ export class AgentSession {
1280
1226
  if (!this.model) {
1281
1227
  throw new Error("No model selected");
1282
1228
  }
1283
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1284
- if (!apiKey) {
1285
- throw new Error(`No API key for ${this.model.provider}`);
1286
- }
1229
+ const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
1287
1230
  const pathEntries = this.sessionManager.getBranch();
1288
1231
  const settings = this.settingsManager.getCompactionSettings();
1289
1232
  const preparation = prepareCompaction(pathEntries, settings);
@@ -1326,7 +1269,7 @@ export class AgentSession {
1326
1269
  }
1327
1270
  else {
1328
1271
  // Generate compaction result
1329
- const result = await compact(preparation, this.model, apiKey, customInstructions, this._compactionAbortController.signal);
1272
+ const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal);
1330
1273
  summary = result.summary;
1331
1274
  firstKeptEntryId = result.firstKeptEntryId;
1332
1275
  tokensBefore = result.tokensBefore;
@@ -1456,11 +1399,12 @@ export class AgentSession {
1456
1399
  this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1457
1400
  return;
1458
1401
  }
1459
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1460
- if (!apiKey) {
1402
+ const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
1403
+ if (!authResult.ok || !authResult.apiKey) {
1461
1404
  this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1462
1405
  return;
1463
1406
  }
1407
+ const { apiKey, headers } = authResult;
1464
1408
  const pathEntries = this.sessionManager.getBranch();
1465
1409
  const preparation = prepareCompaction(pathEntries, settings);
1466
1410
  if (!preparation) {
@@ -1499,7 +1443,7 @@ export class AgentSession {
1499
1443
  }
1500
1444
  else {
1501
1445
  // Generate compaction result
1502
- const compactResult = await compact(preparation, this.model, apiKey, undefined, this._autoCompactionAbortController.signal);
1446
+ const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal);
1503
1447
  summary = compactResult.summary;
1504
1448
  firstKeptEntryId = compactResult.firstKeptEntryId;
1505
1449
  tokensBefore = compactResult.tokensBefore;
@@ -1588,8 +1532,8 @@ export class AgentSession {
1588
1532
  }
1589
1533
  if (this._extensionRunner) {
1590
1534
  this._applyExtensionBindings(this._extensionRunner);
1591
- await this._extensionRunner.emit({ type: "session_start" });
1592
- await this.extendResourcesFromExtensions("startup");
1535
+ await this._extensionRunner.emit(this._sessionStartEvent);
1536
+ await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
1593
1537
  }
1594
1538
  }
1595
1539
  async extendResourcesFromExtensions(reason) {
@@ -1710,8 +1654,7 @@ export class AgentSession {
1710
1654
  refreshTools: () => this._refreshToolRegistry(),
1711
1655
  getCommands,
1712
1656
  setModel: async (model) => {
1713
- const key = await this.modelRegistry.getApiKey(model);
1714
- if (!key)
1657
+ if (!this.modelRegistry.hasConfiguredAuth(model))
1715
1658
  return false;
1716
1659
  await this.setModel(model);
1717
1660
  return true;
@@ -1873,7 +1816,7 @@ export class AgentSession {
1873
1816
  this._extensionShutdownHandler ||
1874
1817
  this._extensionErrorListener;
1875
1818
  if (this._extensionRunner && hasBindings) {
1876
- await this._extensionRunner.emit({ type: "session_start" });
1819
+ await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
1877
1820
  await this.extendResourcesFromExtensions("reload");
1878
1821
  }
1879
1822
  }
@@ -2014,15 +1957,10 @@ export class AgentSession {
2014
1957
  const prefix = this.settingsManager.getShellCommandPrefix();
2015
1958
  const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
2016
1959
  try {
2017
- const result = options?.operations
2018
- ? await executeBashWithOperations(resolvedCommand, process.cwd(), options.operations, {
2019
- onChunk,
2020
- signal: this._bashAbortController.signal,
2021
- })
2022
- : await executeBashCommand(resolvedCommand, {
2023
- onChunk,
2024
- signal: this._bashAbortController.signal,
2025
- });
1960
+ const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations(), {
1961
+ onChunk,
1962
+ signal: this._bashAbortController.signal,
1963
+ });
2026
1964
  this.recordBashResult(command, result, options);
2027
1965
  return result;
2028
1966
  }
@@ -2090,130 +2028,12 @@ export class AgentSession {
2090
2028
  // =========================================================================
2091
2029
  // Session Management
2092
2030
  // =========================================================================
2093
- /**
2094
- * Switch to a different session file.
2095
- * Aborts current operation, loads messages, restores model/thinking.
2096
- * Listeners are preserved and will continue receiving events.
2097
- * @returns true if switch completed, false if cancelled by extension
2098
- */
2099
- async switchSession(sessionPath) {
2100
- const previousSessionFile = this.sessionManager.getSessionFile();
2101
- // Emit session_before_switch event (can be cancelled)
2102
- if (this._extensionRunner?.hasHandlers("session_before_switch")) {
2103
- const result = (await this._extensionRunner.emit({
2104
- type: "session_before_switch",
2105
- reason: "resume",
2106
- targetSessionFile: sessionPath,
2107
- }));
2108
- if (result?.cancel) {
2109
- return false;
2110
- }
2111
- }
2112
- this._disconnectFromAgent();
2113
- await this.abort();
2114
- this._steeringMessages = [];
2115
- this._followUpMessages = [];
2116
- this._pendingNextTurnMessages = [];
2117
- // Set new session
2118
- this.sessionManager.setSessionFile(sessionPath);
2119
- this.agent.sessionId = this.sessionManager.getSessionId();
2120
- // Reload messages
2121
- const sessionContext = this.sessionManager.buildSessionContext();
2122
- // Emit session_switch event to extensions
2123
- if (this._extensionRunner) {
2124
- await this._extensionRunner.emit({
2125
- type: "session_switch",
2126
- reason: "resume",
2127
- previousSessionFile,
2128
- });
2129
- }
2130
- // Emit session event to custom tools
2131
- this.agent.replaceMessages(sessionContext.messages);
2132
- // Restore model if saved
2133
- if (sessionContext.model) {
2134
- const previousModel = this.model;
2135
- const availableModels = await this._modelRegistry.getAvailable();
2136
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2137
- if (match) {
2138
- this.agent.setModel(match);
2139
- await this._emitModelSelect(match, previousModel, "restore");
2140
- }
2141
- }
2142
- const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2143
- const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
2144
- if (hasThinkingEntry) {
2145
- // Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
2146
- this.setThinkingLevel(sessionContext.thinkingLevel);
2147
- }
2148
- else {
2149
- const availableLevels = this.getAvailableThinkingLevels();
2150
- const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
2151
- ? defaultThinkingLevel
2152
- : this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
2153
- this.agent.setThinkingLevel(effectiveLevel);
2154
- this.sessionManager.appendThinkingLevelChange(effectiveLevel);
2155
- }
2156
- this._reconnectToAgent();
2157
- return true;
2158
- }
2159
2031
  /**
2160
2032
  * Set a display name for the current session.
2161
2033
  */
2162
2034
  setSessionName(name) {
2163
2035
  this.sessionManager.appendSessionInfo(name);
2164
2036
  }
2165
- /**
2166
- * Create a fork from a specific entry.
2167
- * Emits before_fork/fork session events to extensions.
2168
- *
2169
- * @param entryId ID of the entry to fork from
2170
- * @returns Object with:
2171
- * - selectedText: The text of the selected user message (for editor pre-fill)
2172
- * - cancelled: True if an extension cancelled the fork
2173
- */
2174
- async fork(entryId) {
2175
- const previousSessionFile = this.sessionFile;
2176
- const selectedEntry = this.sessionManager.getEntry(entryId);
2177
- if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
2178
- throw new Error("Invalid entry ID for forking");
2179
- }
2180
- const selectedText = this._extractUserMessageText(selectedEntry.message.content);
2181
- let skipConversationRestore = false;
2182
- // Emit session_before_fork event (can be cancelled)
2183
- if (this._extensionRunner?.hasHandlers("session_before_fork")) {
2184
- const result = (await this._extensionRunner.emit({
2185
- type: "session_before_fork",
2186
- entryId,
2187
- }));
2188
- if (result?.cancel) {
2189
- return { selectedText, cancelled: true };
2190
- }
2191
- skipConversationRestore = result?.skipConversationRestore ?? false;
2192
- }
2193
- // Clear pending messages (bound to old session state)
2194
- this._pendingNextTurnMessages = [];
2195
- if (!selectedEntry.parentId) {
2196
- this.sessionManager.newSession({ parentSession: previousSessionFile });
2197
- }
2198
- else {
2199
- this.sessionManager.createBranchedSession(selectedEntry.parentId);
2200
- }
2201
- this.agent.sessionId = this.sessionManager.getSessionId();
2202
- // Reload messages from entries (works for both file and in-memory mode)
2203
- const sessionContext = this.sessionManager.buildSessionContext();
2204
- // Emit session_fork event to extensions (after fork completes)
2205
- if (this._extensionRunner) {
2206
- await this._extensionRunner.emit({
2207
- type: "session_fork",
2208
- previousSessionFile,
2209
- });
2210
- }
2211
- // Emit session event to custom tools (with reason "fork")
2212
- if (!skipConversationRestore) {
2213
- this.agent.replaceMessages(sessionContext.messages);
2214
- }
2215
- return { selectedText, cancelled: false };
2216
- }
2217
2037
  // =========================================================================
2218
2038
  // Tree Navigation
2219
2039
  // =========================================================================
@@ -2292,14 +2112,12 @@ export class AgentSession {
2292
2112
  let summaryDetails;
2293
2113
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2294
2114
  const model = this.model;
2295
- const apiKey = await this._modelRegistry.getApiKey(model);
2296
- if (!apiKey) {
2297
- throw new Error(`No API key for ${model.provider}`);
2298
- }
2115
+ const { apiKey, headers } = await this._getRequiredRequestAuth(model);
2299
2116
  const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
2300
2117
  const result = await generateBranchSummary(entriesToSummarize, {
2301
2118
  model,
2302
2119
  apiKey,
2120
+ headers,
2303
2121
  signal: this._branchSummaryAbortController.signal,
2304
2122
  customInstructions,
2305
2123
  replaceInstructions,
@@ -2511,6 +2329,7 @@ export class AgentSession {
2511
2329
  const toolRenderer = createToolHtmlRenderer({
2512
2330
  getToolDefinition: (name) => this.getToolDefinition(name),
2513
2331
  theme,
2332
+ cwd: this.sessionManager.getCwd(),
2514
2333
  });
2515
2334
  return await exportSessionToHtml(this.sessionManager, this.state, {
2516
2335
  outputPath,
@@ -2549,29 +2368,6 @@ export class AgentSession {
2549
2368
  writeFileSync(filePath, `${lines.join("\n")}\n`);
2550
2369
  return filePath;
2551
2370
  }
2552
- /**
2553
- * Import a JSONL session file.
2554
- * Copies the file into the session directory and switches to it (like /resume).
2555
- * @param inputPath Path to the JSONL file to import.
2556
- * @returns true if the session was switched successfully.
2557
- */
2558
- async importFromJsonl(inputPath) {
2559
- const resolved = resolve(inputPath);
2560
- if (!existsSync(resolved)) {
2561
- throw new Error(`File not found: ${resolved}`);
2562
- }
2563
- // Copy into the session directory so we don't modify the original
2564
- const sessionDir = this.sessionManager.getSessionDir();
2565
- if (!existsSync(sessionDir)) {
2566
- mkdirSync(sessionDir, { recursive: true });
2567
- }
2568
- const destPath = join(sessionDir, basename(resolved));
2569
- // Avoid overwriting if source and destination are the same file
2570
- if (resolve(destPath) !== resolved) {
2571
- copyFileSync(resolved, destPath);
2572
- }
2573
- return this.switchSession(destPath);
2574
- }
2575
2371
  // =========================================================================
2576
2372
  // Utilities
2577
2373
  // =========================================================================