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