@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.
- package/CHANGELOG.md +110 -0
- package/README.md +12 -6
- package/dist/cli/args.d.ts +7 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +37 -15
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +83 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -0
- package/dist/core/agent-session-runtime.js +236 -0
- package/dist/core/agent-session-runtime.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +86 -0
- package/dist/core/agent-session-services.d.ts.map +1 -0
- package/dist/core/agent-session-services.js +116 -0
- package/dist/core/agent-session-services.js.map +1 -0
- package/dist/core/agent-session.d.ts +5 -42
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +46 -237
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +19 -7
- package/dist/core/bash-executor.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 -28
- 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 +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +14 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +13 -14
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/package-manager.d.ts +20 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +55 -9
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +25 -3
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +4 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-cwd.d.ts +19 -0
- package/dist/core/session-cwd.d.ts.map +1 -0
- package/dist/core/session-cwd.js +38 -0
- package/dist/core/session-cwd.js.map +1 -0
- package/dist/core/session-manager.d.ts +5 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +16 -8
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +2 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +19 -9
- package/dist/core/tools/bash.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 +245 -426
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +20 -0
- package/dist/migrations.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/components/tree-selector.d.ts +4 -2
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +48 -15
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +10 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +160 -94
- 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 +15 -11
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +2 -2
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +41 -36
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +1 -0
- package/dist/modes/rpc/rpc-client.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 +92 -64
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts +4 -0
- package/dist/package-manager-cli.d.ts.map +1 -0
- package/dist/package-manager-cli.js +234 -0
- package/dist/package-manager-cli.js.map +1 -0
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +19 -0
- package/dist/utils/paths.js.map +1 -0
- package/docs/extensions.md +72 -40
- package/docs/keybindings.md +2 -0
- package/docs/sdk.md +227 -74
- package/docs/settings.md +1 -1
- package/docs/tree.md +6 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/doom-overlay/doom/build.sh +2 -2
- package/examples/extensions/hello.ts +18 -17
- package/examples/extensions/hidden-thinking-label.ts +0 -4
- package/examples/extensions/rpc-demo.ts +3 -9
- package/examples/extensions/status-line.ts +0 -8
- package/examples/extensions/todo.ts +0 -2
- package/examples/extensions/tools.ts +0 -5
- package/examples/extensions/widget-placement.ts +4 -12
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/13-session-runtime.ts +67 -0
- package/examples/sdk/README.md +4 -1
- 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 "@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 {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
573
|
+
return this.agent.steeringMode;
|
|
571
574
|
}
|
|
572
575
|
/** Current follow-up mode */
|
|
573
576
|
get followUpMode() {
|
|
574
|
-
return this.agent.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
// =========================================================================
|