@codex-infinity/pi-infinity 0.63.3 → 0.64.2
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 +101 -0
- package/README.md +7 -3
- package/dist/core/agent-session-runtime.d.ts +134 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -0
- package/dist/core/agent-session-runtime.js +262 -0
- package/dist/core/agent-session-runtime.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 +56 -236
- 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 +1 -1
- package/dist/core/extensions/index.d.ts.map +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 +11 -16
- package/dist/core/extensions/types.d.ts.map +1 -1
- 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 +69 -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 +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +10 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +10 -0
- 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/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/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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +42 -10
- package/dist/main.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 +13 -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 +11 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +112 -89
- 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 +37 -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 +72 -49
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/extensions.md +104 -19
- package/docs/json.md +5 -2
- package/docs/keybindings.md +2 -0
- package/docs/rpc.md +21 -7
- package/docs/sdk.md +168 -75
- 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/hidden-thinking-label.ts +53 -0
- 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/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 "@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
|
/**
|
|
@@ -121,6 +122,7 @@ export class AgentSession {
|
|
|
121
122
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
122
123
|
this._autoNextSteps = config.autoNextSteps ?? false;
|
|
123
124
|
this._autoNextIdea = config.autoNextIdea ?? false;
|
|
125
|
+
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
124
126
|
// Always subscribe to agent events for internal handling
|
|
125
127
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
126
128
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
@@ -160,7 +162,7 @@ export class AgentSession {
|
|
|
160
162
|
* happens here instead of in wrappers.
|
|
161
163
|
*/
|
|
162
164
|
_installAgentToolHooks() {
|
|
163
|
-
this.agent.
|
|
165
|
+
this.agent.beforeToolCall = async ({ toolCall, args }) => {
|
|
164
166
|
const runner = this._extensionRunner;
|
|
165
167
|
if (!runner?.hasHandlers("tool_call")) {
|
|
166
168
|
return undefined;
|
|
@@ -180,8 +182,8 @@ export class AgentSession {
|
|
|
180
182
|
}
|
|
181
183
|
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
182
184
|
}
|
|
183
|
-
}
|
|
184
|
-
this.agent.
|
|
185
|
+
};
|
|
186
|
+
this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
|
|
185
187
|
const runner = this._extensionRunner;
|
|
186
188
|
if (!runner?.hasHandlers("tool_result")) {
|
|
187
189
|
return undefined;
|
|
@@ -202,7 +204,7 @@ export class AgentSession {
|
|
|
202
204
|
content: hookResult.content,
|
|
203
205
|
details: hookResult.details,
|
|
204
206
|
};
|
|
205
|
-
}
|
|
207
|
+
};
|
|
206
208
|
}
|
|
207
209
|
// =========================================================================
|
|
208
210
|
// Event Subscription
|
|
@@ -213,6 +215,13 @@ export class AgentSession {
|
|
|
213
215
|
l(event);
|
|
214
216
|
}
|
|
215
217
|
}
|
|
218
|
+
_emitQueueUpdate() {
|
|
219
|
+
this._emit({
|
|
220
|
+
type: "queue_update",
|
|
221
|
+
steering: [...this._steeringMessages],
|
|
222
|
+
followUp: [...this._followUpMessages],
|
|
223
|
+
});
|
|
224
|
+
}
|
|
216
225
|
_createRetryPromiseForAgentEnd(event) {
|
|
217
226
|
if (event.type !== "agent_end" || this._retryPromise) {
|
|
218
227
|
return;
|
|
@@ -249,12 +258,14 @@ export class AgentSession {
|
|
|
249
258
|
const steeringIndex = this._steeringMessages.indexOf(messageText);
|
|
250
259
|
if (steeringIndex !== -1) {
|
|
251
260
|
this._steeringMessages.splice(steeringIndex, 1);
|
|
261
|
+
this._emitQueueUpdate();
|
|
252
262
|
}
|
|
253
263
|
else {
|
|
254
264
|
// Check follow-up queue
|
|
255
265
|
const followUpIndex = this._followUpMessages.indexOf(messageText);
|
|
256
266
|
if (followUpIndex !== -1) {
|
|
257
267
|
this._followUpMessages.splice(followUpIndex, 1);
|
|
268
|
+
this._emitQueueUpdate();
|
|
258
269
|
}
|
|
259
270
|
}
|
|
260
271
|
}
|
|
@@ -529,10 +540,10 @@ export class AgentSession {
|
|
|
529
540
|
validToolNames.push(name);
|
|
530
541
|
}
|
|
531
542
|
}
|
|
532
|
-
this.agent.
|
|
543
|
+
this.agent.state.tools = tools;
|
|
533
544
|
// Rebuild base system prompt with new tool set
|
|
534
545
|
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
|
535
|
-
this.agent.
|
|
546
|
+
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
536
547
|
}
|
|
537
548
|
/** Whether compaction or branch summarization is currently running */
|
|
538
549
|
get isCompacting() {
|
|
@@ -546,11 +557,11 @@ export class AgentSession {
|
|
|
546
557
|
}
|
|
547
558
|
/** Current steering mode */
|
|
548
559
|
get steeringMode() {
|
|
549
|
-
return this.agent.
|
|
560
|
+
return this.agent.steeringMode;
|
|
550
561
|
}
|
|
551
562
|
/** Current follow-up mode */
|
|
552
563
|
get followUpMode() {
|
|
553
|
-
return this.agent.
|
|
564
|
+
return this.agent.followUpMode;
|
|
554
565
|
}
|
|
555
566
|
/** Current session file path, or undefined if sessions are disabled */
|
|
556
567
|
get sessionFile() {
|
|
@@ -741,11 +752,11 @@ export class AgentSession {
|
|
|
741
752
|
}
|
|
742
753
|
// Apply extension-modified system prompt, or reset to base
|
|
743
754
|
if (result?.systemPrompt) {
|
|
744
|
-
this.agent.
|
|
755
|
+
this.agent.state.systemPrompt = result.systemPrompt;
|
|
745
756
|
}
|
|
746
757
|
else {
|
|
747
758
|
// Ensure we're using the base prompt (in case previous turn had modifications)
|
|
748
|
-
this.agent.
|
|
759
|
+
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
749
760
|
}
|
|
750
761
|
}
|
|
751
762
|
await this.agent.prompt(messages);
|
|
@@ -850,6 +861,7 @@ export class AgentSession {
|
|
|
850
861
|
*/
|
|
851
862
|
async _queueSteer(text, images) {
|
|
852
863
|
this._steeringMessages.push(text);
|
|
864
|
+
this._emitQueueUpdate();
|
|
853
865
|
const content = [{ type: "text", text }];
|
|
854
866
|
if (images) {
|
|
855
867
|
content.push(...images);
|
|
@@ -865,6 +877,7 @@ export class AgentSession {
|
|
|
865
877
|
*/
|
|
866
878
|
async _queueFollowUp(text, images) {
|
|
867
879
|
this._followUpMessages.push(text);
|
|
880
|
+
this._emitQueueUpdate();
|
|
868
881
|
const content = [{ type: "text", text }];
|
|
869
882
|
if (images) {
|
|
870
883
|
content.push(...images);
|
|
@@ -924,7 +937,7 @@ export class AgentSession {
|
|
|
924
937
|
await this.agent.prompt(appMessage);
|
|
925
938
|
}
|
|
926
939
|
else {
|
|
927
|
-
this.agent.
|
|
940
|
+
this.agent.state.messages.push(appMessage);
|
|
928
941
|
this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
|
|
929
942
|
this._emit({ type: "message_start", message: appMessage });
|
|
930
943
|
this._emit({ type: "message_end", message: appMessage });
|
|
@@ -978,6 +991,7 @@ export class AgentSession {
|
|
|
978
991
|
this._steeringMessages = [];
|
|
979
992
|
this._followUpMessages = [];
|
|
980
993
|
this.agent.clearAllQueues();
|
|
994
|
+
this._emitQueueUpdate();
|
|
981
995
|
return { steering, followUp };
|
|
982
996
|
}
|
|
983
997
|
/** Number of pending messages (includes both steering and follow-up) */
|
|
@@ -1003,54 +1017,6 @@ export class AgentSession {
|
|
|
1003
1017
|
this.agent.abort();
|
|
1004
1018
|
await this.agent.waitForIdle();
|
|
1005
1019
|
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Start a new session, optionally with initial messages and parent tracking.
|
|
1008
|
-
* Clears all messages and starts a new session.
|
|
1009
|
-
* Listeners are preserved and will continue receiving events.
|
|
1010
|
-
* @param options.parentSession - Optional parent session path for tracking
|
|
1011
|
-
* @param options.setup - Optional callback to initialize session (e.g., append messages)
|
|
1012
|
-
* @returns true if completed, false if cancelled by extension
|
|
1013
|
-
*/
|
|
1014
|
-
async newSession(options) {
|
|
1015
|
-
const previousSessionFile = this.sessionFile;
|
|
1016
|
-
// Emit session_before_switch event with reason "new" (can be cancelled)
|
|
1017
|
-
if (this._extensionRunner?.hasHandlers("session_before_switch")) {
|
|
1018
|
-
const result = (await this._extensionRunner.emit({
|
|
1019
|
-
type: "session_before_switch",
|
|
1020
|
-
reason: "new",
|
|
1021
|
-
}));
|
|
1022
|
-
if (result?.cancel) {
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
this._disconnectFromAgent();
|
|
1027
|
-
await this.abort();
|
|
1028
|
-
this.agent.reset();
|
|
1029
|
-
this.sessionManager.newSession({ parentSession: options?.parentSession });
|
|
1030
|
-
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
1031
|
-
this._steeringMessages = [];
|
|
1032
|
-
this._followUpMessages = [];
|
|
1033
|
-
this._pendingNextTurnMessages = [];
|
|
1034
|
-
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
|
|
1035
|
-
// Run setup callback if provided (e.g., to append initial messages)
|
|
1036
|
-
if (options?.setup) {
|
|
1037
|
-
await options.setup(this.sessionManager);
|
|
1038
|
-
// Sync agent state with session manager after setup
|
|
1039
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1040
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
1041
|
-
}
|
|
1042
|
-
this._reconnectToAgent();
|
|
1043
|
-
// Emit session_switch event with reason "new" to extensions
|
|
1044
|
-
if (this._extensionRunner) {
|
|
1045
|
-
await this._extensionRunner.emit({
|
|
1046
|
-
type: "session_switch",
|
|
1047
|
-
reason: "new",
|
|
1048
|
-
previousSessionFile,
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
// Emit session event to custom tools
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
1020
|
// =========================================================================
|
|
1055
1021
|
// Model Management
|
|
1056
1022
|
// =========================================================================
|
|
@@ -1077,7 +1043,7 @@ export class AgentSession {
|
|
|
1077
1043
|
}
|
|
1078
1044
|
const previousModel = this.model;
|
|
1079
1045
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1080
|
-
this.agent.
|
|
1046
|
+
this.agent.state.model = model;
|
|
1081
1047
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1082
1048
|
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
1083
1049
|
// Re-clamp thinking level for new model's capabilities
|
|
@@ -1096,11 +1062,8 @@ export class AgentSession {
|
|
|
1096
1062
|
}
|
|
1097
1063
|
return this._cycleAvailableModel(direction);
|
|
1098
1064
|
}
|
|
1099
|
-
_getScopedModelsWithAuth() {
|
|
1100
|
-
return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
|
|
1101
|
-
}
|
|
1102
1065
|
async _cycleScopedModel(direction) {
|
|
1103
|
-
const scopedModels = this.
|
|
1066
|
+
const scopedModels = this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
|
|
1104
1067
|
if (scopedModels.length <= 1)
|
|
1105
1068
|
return undefined;
|
|
1106
1069
|
const currentModel = this.model;
|
|
@@ -1112,7 +1075,7 @@ export class AgentSession {
|
|
|
1112
1075
|
const next = scopedModels[nextIndex];
|
|
1113
1076
|
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1114
1077
|
// Apply model
|
|
1115
|
-
this.agent.
|
|
1078
|
+
this.agent.state.model = next.model;
|
|
1116
1079
|
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
|
1117
1080
|
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
|
1118
1081
|
// Apply thinking level.
|
|
@@ -1135,7 +1098,7 @@ export class AgentSession {
|
|
|
1135
1098
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
1136
1099
|
const nextModel = availableModels[nextIndex];
|
|
1137
1100
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1138
|
-
this.agent.
|
|
1101
|
+
this.agent.state.model = nextModel;
|
|
1139
1102
|
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
|
1140
1103
|
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
1141
1104
|
// Re-clamp thinking level for new model's capabilities
|
|
@@ -1156,7 +1119,7 @@ export class AgentSession {
|
|
|
1156
1119
|
const effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);
|
|
1157
1120
|
// Only persist if actually changing
|
|
1158
1121
|
const isChanging = effectiveLevel !== this.agent.state.thinkingLevel;
|
|
1159
|
-
this.agent.
|
|
1122
|
+
this.agent.state.thinkingLevel = effectiveLevel;
|
|
1160
1123
|
if (isChanging) {
|
|
1161
1124
|
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
1162
1125
|
if (this.supportsThinking() || effectiveLevel !== "off") {
|
|
@@ -1235,7 +1198,7 @@ export class AgentSession {
|
|
|
1235
1198
|
* Saves to settings.
|
|
1236
1199
|
*/
|
|
1237
1200
|
setSteeringMode(mode) {
|
|
1238
|
-
this.agent.
|
|
1201
|
+
this.agent.steeringMode = mode;
|
|
1239
1202
|
this.settingsManager.setSteeringMode(mode);
|
|
1240
1203
|
}
|
|
1241
1204
|
/**
|
|
@@ -1243,7 +1206,7 @@ export class AgentSession {
|
|
|
1243
1206
|
* Saves to settings.
|
|
1244
1207
|
*/
|
|
1245
1208
|
setFollowUpMode(mode) {
|
|
1246
|
-
this.agent.
|
|
1209
|
+
this.agent.followUpMode = mode;
|
|
1247
1210
|
this.settingsManager.setFollowUpMode(mode);
|
|
1248
1211
|
}
|
|
1249
1212
|
// =========================================================================
|
|
@@ -1318,7 +1281,7 @@ export class AgentSession {
|
|
|
1318
1281
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1319
1282
|
const newEntries = this.sessionManager.getEntries();
|
|
1320
1283
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1321
|
-
this.agent.
|
|
1284
|
+
this.agent.state.messages = sessionContext.messages;
|
|
1322
1285
|
// Get the saved compaction entry for the extension event
|
|
1323
1286
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1324
1287
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -1424,7 +1387,7 @@ export class AgentSession {
|
|
|
1424
1387
|
// but we don't want it in context for the retry)
|
|
1425
1388
|
const messages = this.agent.state.messages;
|
|
1426
1389
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
1427
|
-
this.agent.
|
|
1390
|
+
this.agent.state.messages = messages.slice(0, -1);
|
|
1428
1391
|
}
|
|
1429
1392
|
await this._runAutoCompaction("overflow", true);
|
|
1430
1393
|
return;
|
|
@@ -1555,7 +1518,7 @@ export class AgentSession {
|
|
|
1555
1518
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1556
1519
|
const newEntries = this.sessionManager.getEntries();
|
|
1557
1520
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1558
|
-
this.agent.
|
|
1521
|
+
this.agent.state.messages = sessionContext.messages;
|
|
1559
1522
|
// Get the saved compaction entry for the extension event
|
|
1560
1523
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1561
1524
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -1576,7 +1539,7 @@ export class AgentSession {
|
|
|
1576
1539
|
const messages = this.agent.state.messages;
|
|
1577
1540
|
const lastMsg = messages[messages.length - 1];
|
|
1578
1541
|
if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
|
|
1579
|
-
this.agent.
|
|
1542
|
+
this.agent.state.messages = messages.slice(0, -1);
|
|
1580
1543
|
}
|
|
1581
1544
|
setTimeout(() => {
|
|
1582
1545
|
this.agent.continue().catch(() => { });
|
|
@@ -1632,8 +1595,8 @@ export class AgentSession {
|
|
|
1632
1595
|
}
|
|
1633
1596
|
if (this._extensionRunner) {
|
|
1634
1597
|
this._applyExtensionBindings(this._extensionRunner);
|
|
1635
|
-
await this._extensionRunner.emit(
|
|
1636
|
-
await this.extendResourcesFromExtensions("startup");
|
|
1598
|
+
await this._extensionRunner.emit(this._sessionStartEvent);
|
|
1599
|
+
await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
|
|
1637
1600
|
}
|
|
1638
1601
|
}
|
|
1639
1602
|
async extendResourcesFromExtensions(reason) {
|
|
@@ -1651,7 +1614,7 @@ export class AgentSession {
|
|
|
1651
1614
|
};
|
|
1652
1615
|
this._resourceLoader.extendResources(extensionPaths);
|
|
1653
1616
|
this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
|
|
1654
|
-
this.agent.
|
|
1617
|
+
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
1655
1618
|
}
|
|
1656
1619
|
buildExtensionResourcePaths(entries) {
|
|
1657
1620
|
return entries.map((entry) => {
|
|
@@ -1693,7 +1656,7 @@ export class AgentSession {
|
|
|
1693
1656
|
if (!refreshedModel || refreshedModel === currentModel) {
|
|
1694
1657
|
return;
|
|
1695
1658
|
}
|
|
1696
|
-
this.agent.
|
|
1659
|
+
this.agent.state.model = refreshedModel;
|
|
1697
1660
|
}
|
|
1698
1661
|
_bindExtensionCore(runner) {
|
|
1699
1662
|
const getCommands = () => {
|
|
@@ -1917,7 +1880,7 @@ export class AgentSession {
|
|
|
1917
1880
|
this._extensionShutdownHandler ||
|
|
1918
1881
|
this._extensionErrorListener;
|
|
1919
1882
|
if (this._extensionRunner && hasBindings) {
|
|
1920
|
-
await this._extensionRunner.emit({ type: "session_start" });
|
|
1883
|
+
await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
|
|
1921
1884
|
await this.extendResourcesFromExtensions("reload");
|
|
1922
1885
|
}
|
|
1923
1886
|
}
|
|
@@ -2018,7 +1981,7 @@ export class AgentSession {
|
|
|
2018
1981
|
// Remove error message from agent state (keep in session for history)
|
|
2019
1982
|
const messages = this.agent.state.messages;
|
|
2020
1983
|
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2021
|
-
this.agent.
|
|
1984
|
+
this.agent.state.messages = messages.slice(0, -1);
|
|
2022
1985
|
}
|
|
2023
1986
|
// Wait with exponential backoff (abortable)
|
|
2024
1987
|
this._retryAbortController = new AbortController();
|
|
@@ -2061,9 +2024,11 @@ export class AgentSession {
|
|
|
2061
2024
|
* Returns immediately if no retry is in progress.
|
|
2062
2025
|
*/
|
|
2063
2026
|
async waitForRetry() {
|
|
2064
|
-
if (this._retryPromise) {
|
|
2065
|
-
|
|
2027
|
+
if (!this._retryPromise) {
|
|
2028
|
+
return;
|
|
2066
2029
|
}
|
|
2030
|
+
await this._retryPromise;
|
|
2031
|
+
await this.agent.waitForIdle();
|
|
2067
2032
|
}
|
|
2068
2033
|
/** Whether auto-retry is currently in progress */
|
|
2069
2034
|
get isRetrying() {
|
|
@@ -2096,15 +2061,10 @@ export class AgentSession {
|
|
|
2096
2061
|
const prefix = this.settingsManager.getShellCommandPrefix();
|
|
2097
2062
|
const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
|
|
2098
2063
|
try {
|
|
2099
|
-
const result = options?.operations
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
})
|
|
2104
|
-
: await executeBashCommand(resolvedCommand, {
|
|
2105
|
-
onChunk,
|
|
2106
|
-
signal: this._bashAbortController.signal,
|
|
2107
|
-
});
|
|
2064
|
+
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations(), {
|
|
2065
|
+
onChunk,
|
|
2066
|
+
signal: this._bashAbortController.signal,
|
|
2067
|
+
});
|
|
2108
2068
|
this.recordBashResult(command, result, options);
|
|
2109
2069
|
return result;
|
|
2110
2070
|
}
|
|
@@ -2135,7 +2095,7 @@ export class AgentSession {
|
|
|
2135
2095
|
}
|
|
2136
2096
|
else {
|
|
2137
2097
|
// Add to agent state immediately
|
|
2138
|
-
this.agent.
|
|
2098
|
+
this.agent.state.messages.push(bashMessage);
|
|
2139
2099
|
// Save to session
|
|
2140
2100
|
this.sessionManager.appendMessage(bashMessage);
|
|
2141
2101
|
}
|
|
@@ -2163,7 +2123,7 @@ export class AgentSession {
|
|
|
2163
2123
|
return;
|
|
2164
2124
|
for (const bashMessage of this._pendingBashMessages) {
|
|
2165
2125
|
// Add to agent state
|
|
2166
|
-
this.agent.
|
|
2126
|
+
this.agent.state.messages.push(bashMessage);
|
|
2167
2127
|
// Save to session
|
|
2168
2128
|
this.sessionManager.appendMessage(bashMessage);
|
|
2169
2129
|
}
|
|
@@ -2172,130 +2132,12 @@ export class AgentSession {
|
|
|
2172
2132
|
// =========================================================================
|
|
2173
2133
|
// Session Management
|
|
2174
2134
|
// =========================================================================
|
|
2175
|
-
/**
|
|
2176
|
-
* Switch to a different session file.
|
|
2177
|
-
* Aborts current operation, loads messages, restores model/thinking.
|
|
2178
|
-
* Listeners are preserved and will continue receiving events.
|
|
2179
|
-
* @returns true if switch completed, false if cancelled by extension
|
|
2180
|
-
*/
|
|
2181
|
-
async switchSession(sessionPath) {
|
|
2182
|
-
const previousSessionFile = this.sessionManager.getSessionFile();
|
|
2183
|
-
// Emit session_before_switch event (can be cancelled)
|
|
2184
|
-
if (this._extensionRunner?.hasHandlers("session_before_switch")) {
|
|
2185
|
-
const result = (await this._extensionRunner.emit({
|
|
2186
|
-
type: "session_before_switch",
|
|
2187
|
-
reason: "resume",
|
|
2188
|
-
targetSessionFile: sessionPath,
|
|
2189
|
-
}));
|
|
2190
|
-
if (result?.cancel) {
|
|
2191
|
-
return false;
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
this._disconnectFromAgent();
|
|
2195
|
-
await this.abort();
|
|
2196
|
-
this._steeringMessages = [];
|
|
2197
|
-
this._followUpMessages = [];
|
|
2198
|
-
this._pendingNextTurnMessages = [];
|
|
2199
|
-
// Set new session
|
|
2200
|
-
this.sessionManager.setSessionFile(sessionPath);
|
|
2201
|
-
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2202
|
-
// Reload messages
|
|
2203
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2204
|
-
// Emit session_switch event to extensions
|
|
2205
|
-
if (this._extensionRunner) {
|
|
2206
|
-
await this._extensionRunner.emit({
|
|
2207
|
-
type: "session_switch",
|
|
2208
|
-
reason: "resume",
|
|
2209
|
-
previousSessionFile,
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
// Emit session event to custom tools
|
|
2213
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
2214
|
-
// Restore model if saved
|
|
2215
|
-
if (sessionContext.model) {
|
|
2216
|
-
const previousModel = this.model;
|
|
2217
|
-
const availableModels = await this._modelRegistry.getAvailable();
|
|
2218
|
-
const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
|
|
2219
|
-
if (match) {
|
|
2220
|
-
this.agent.setModel(match);
|
|
2221
|
-
await this._emitModelSelect(match, previousModel, "restore");
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
|
|
2225
|
-
const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
2226
|
-
if (hasThinkingEntry) {
|
|
2227
|
-
// Restore thinking level if saved (setThinkingLevel clamps to model capabilities)
|
|
2228
|
-
this.setThinkingLevel(sessionContext.thinkingLevel);
|
|
2229
|
-
}
|
|
2230
|
-
else {
|
|
2231
|
-
const availableLevels = this.getAvailableThinkingLevels();
|
|
2232
|
-
const effectiveLevel = availableLevels.includes(defaultThinkingLevel)
|
|
2233
|
-
? defaultThinkingLevel
|
|
2234
|
-
: this._clampThinkingLevel(defaultThinkingLevel, availableLevels);
|
|
2235
|
-
this.agent.setThinkingLevel(effectiveLevel);
|
|
2236
|
-
this.sessionManager.appendThinkingLevelChange(effectiveLevel);
|
|
2237
|
-
}
|
|
2238
|
-
this._reconnectToAgent();
|
|
2239
|
-
return true;
|
|
2240
|
-
}
|
|
2241
2135
|
/**
|
|
2242
2136
|
* Set a display name for the current session.
|
|
2243
2137
|
*/
|
|
2244
2138
|
setSessionName(name) {
|
|
2245
2139
|
this.sessionManager.appendSessionInfo(name);
|
|
2246
2140
|
}
|
|
2247
|
-
/**
|
|
2248
|
-
* Create a fork from a specific entry.
|
|
2249
|
-
* Emits before_fork/fork session events to extensions.
|
|
2250
|
-
*
|
|
2251
|
-
* @param entryId ID of the entry to fork from
|
|
2252
|
-
* @returns Object with:
|
|
2253
|
-
* - selectedText: The text of the selected user message (for editor pre-fill)
|
|
2254
|
-
* - cancelled: True if an extension cancelled the fork
|
|
2255
|
-
*/
|
|
2256
|
-
async fork(entryId) {
|
|
2257
|
-
const previousSessionFile = this.sessionFile;
|
|
2258
|
-
const selectedEntry = this.sessionManager.getEntry(entryId);
|
|
2259
|
-
if (!selectedEntry || selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
|
|
2260
|
-
throw new Error("Invalid entry ID for forking");
|
|
2261
|
-
}
|
|
2262
|
-
const selectedText = this._extractUserMessageText(selectedEntry.message.content);
|
|
2263
|
-
let skipConversationRestore = false;
|
|
2264
|
-
// Emit session_before_fork event (can be cancelled)
|
|
2265
|
-
if (this._extensionRunner?.hasHandlers("session_before_fork")) {
|
|
2266
|
-
const result = (await this._extensionRunner.emit({
|
|
2267
|
-
type: "session_before_fork",
|
|
2268
|
-
entryId,
|
|
2269
|
-
}));
|
|
2270
|
-
if (result?.cancel) {
|
|
2271
|
-
return { selectedText, cancelled: true };
|
|
2272
|
-
}
|
|
2273
|
-
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
2274
|
-
}
|
|
2275
|
-
// Clear pending messages (bound to old session state)
|
|
2276
|
-
this._pendingNextTurnMessages = [];
|
|
2277
|
-
if (!selectedEntry.parentId) {
|
|
2278
|
-
this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
2279
|
-
}
|
|
2280
|
-
else {
|
|
2281
|
-
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
2282
|
-
}
|
|
2283
|
-
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2284
|
-
// Reload messages from entries (works for both file and in-memory mode)
|
|
2285
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2286
|
-
// Emit session_fork event to extensions (after fork completes)
|
|
2287
|
-
if (this._extensionRunner) {
|
|
2288
|
-
await this._extensionRunner.emit({
|
|
2289
|
-
type: "session_fork",
|
|
2290
|
-
previousSessionFile,
|
|
2291
|
-
});
|
|
2292
|
-
}
|
|
2293
|
-
// Emit session event to custom tools (with reason "fork")
|
|
2294
|
-
if (!skipConversationRestore) {
|
|
2295
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
2296
|
-
}
|
|
2297
|
-
return { selectedText, cancelled: false };
|
|
2298
|
-
}
|
|
2299
2141
|
// =========================================================================
|
|
2300
2142
|
// Tree Navigation
|
|
2301
2143
|
// =========================================================================
|
|
@@ -2451,7 +2293,7 @@ export class AgentSession {
|
|
|
2451
2293
|
}
|
|
2452
2294
|
// Update agent state
|
|
2453
2295
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2454
|
-
this.agent.
|
|
2296
|
+
this.agent.state.messages = sessionContext.messages;
|
|
2455
2297
|
// Emit session_tree event
|
|
2456
2298
|
if (this._extensionRunner) {
|
|
2457
2299
|
await this._extensionRunner.emit({
|
|
@@ -2591,6 +2433,7 @@ export class AgentSession {
|
|
|
2591
2433
|
const toolRenderer = createToolHtmlRenderer({
|
|
2592
2434
|
getToolDefinition: (name) => this.getToolDefinition(name),
|
|
2593
2435
|
theme,
|
|
2436
|
+
cwd: this.sessionManager.getCwd(),
|
|
2594
2437
|
});
|
|
2595
2438
|
return await exportSessionToHtml(this.sessionManager, this.state, {
|
|
2596
2439
|
outputPath,
|
|
@@ -2629,29 +2472,6 @@ export class AgentSession {
|
|
|
2629
2472
|
writeFileSync(filePath, `${lines.join("\n")}\n`);
|
|
2630
2473
|
return filePath;
|
|
2631
2474
|
}
|
|
2632
|
-
/**
|
|
2633
|
-
* Import a JSONL session file.
|
|
2634
|
-
* Copies the file into the session directory and switches to it (like /resume).
|
|
2635
|
-
* @param inputPath Path to the JSONL file to import.
|
|
2636
|
-
* @returns true if the session was switched successfully.
|
|
2637
|
-
*/
|
|
2638
|
-
async importFromJsonl(inputPath) {
|
|
2639
|
-
const resolved = resolve(inputPath);
|
|
2640
|
-
if (!existsSync(resolved)) {
|
|
2641
|
-
throw new Error(`File not found: ${resolved}`);
|
|
2642
|
-
}
|
|
2643
|
-
// Copy into the session directory so we don't modify the original
|
|
2644
|
-
const sessionDir = this.sessionManager.getSessionDir();
|
|
2645
|
-
if (!existsSync(sessionDir)) {
|
|
2646
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
2647
|
-
}
|
|
2648
|
-
const destPath = join(sessionDir, basename(resolved));
|
|
2649
|
-
// Avoid overwriting if source and destination are the same file
|
|
2650
|
-
if (resolve(destPath) !== resolved) {
|
|
2651
|
-
copyFileSync(resolved, destPath);
|
|
2652
|
-
}
|
|
2653
|
-
return this.switchSession(destPath);
|
|
2654
|
-
}
|
|
2655
2475
|
// =========================================================================
|
|
2656
2476
|
// Utilities
|
|
2657
2477
|
// =========================================================================
|