@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.
- package/CHANGELOG.md +107 -0
- package/README.md +6 -2
- package/dist/core/agent-session-runtime.d.ts +136 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -0
- package/dist/core/agent-session-runtime.js +267 -0
- package/dist/core/agent-session-runtime.js.map +1 -0
- package/dist/core/agent-session.d.ts +22 -44
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +44 -248
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +3 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +5 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +9 -9
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts +2 -0
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +2 -2
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +16 -16
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +10 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +5 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +70 -8
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-registry.d.ts +21 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +90 -70
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -4
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +6 -0
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +37 -5
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +5 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +6 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +17 -23
- package/dist/core/sdk.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +49 -10
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +90 -87
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +6 -11
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +4 -4
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +87 -74
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +69 -49
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/development.md +2 -2
- package/docs/extensions.md +78 -22
- package/docs/models.md +6 -0
- package/docs/packages.md +3 -3
- package/docs/rpc.md +2 -2
- package/docs/sdk.md +170 -82
- package/docs/tree.md +1 -1
- package/examples/extensions/custom-compaction.ts +17 -4
- package/examples/extensions/handoff.ts +5 -2
- package/examples/extensions/hello.ts +18 -17
- package/examples/extensions/qna.ts +5 -2
- package/examples/extensions/rpc-demo.ts +3 -9
- package/examples/extensions/status-line.ts +0 -8
- package/examples/extensions/subagent/index.ts +1 -1
- package/examples/extensions/summarize.ts +15 -4
- package/examples/extensions/todo.ts +0 -2
- package/examples/extensions/tools.ts +0 -5
- package/examples/extensions/widget-placement.ts +4 -12
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
- package/examples/sdk/12-full-control.ts +1 -1
- package/examples/sdk/13-session-runtime.ts +49 -0
- package/examples/sdk/README.md +5 -4
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
1069
|
-
* @throws Error if no
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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.
|
|
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
|
// =========================================================================
|