@mariozechner/pi-coding-agent 0.34.2 → 0.36.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 +210 -0
- package/README.md +246 -107
- package/dist/cli/args.d.ts +3 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -18
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +39 -50
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +166 -197
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +4 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +6 -5
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/exec.d.ts +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +1 -1
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/index.d.ts +10 -0
- package/dist/core/extensions/index.d.ts.map +1 -0
- package/dist/core/extensions/index.js +9 -0
- package/dist/core/extensions/index.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +21 -0
- package/dist/core/extensions/loader.d.ts.map +1 -0
- package/dist/core/extensions/loader.js +400 -0
- package/dist/core/extensions/loader.js.map +1 -0
- package/dist/core/extensions/runner.d.ts +88 -0
- package/dist/core/extensions/runner.d.ts.map +1 -0
- package/dist/core/{hooks → extensions}/runner.js +52 -141
- package/dist/core/extensions/runner.js.map +1 -0
- package/dist/core/extensions/types.d.ts +461 -0
- package/dist/core/extensions/types.d.ts.map +1 -0
- package/dist/core/{hooks → extensions}/types.js +7 -4
- package/dist/core/extensions/types.js.map +1 -0
- package/dist/core/extensions/wrapper.d.ts +25 -0
- package/dist/core/extensions/wrapper.d.ts.map +1 -0
- package/dist/core/{hooks/tool-wrapper.js → extensions/wrapper.js} +39 -24
- package/dist/core/extensions/wrapper.js.map +1 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +7 -7
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -4
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +40 -0
- package/dist/core/prompt-templates.d.ts.map +1 -0
- package/dist/core/{slash-commands.js → prompt-templates.js} +31 -31
- package/dist/core/prompt-templates.js.map +1 -0
- package/dist/core/sdk.d.ts +29 -52
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +111 -211
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +17 -17
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +25 -10
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -6
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -11
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +4 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +36 -33
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +7 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +93 -4
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +1 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +4 -4
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +18 -0
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/{hook-message.js → custom-message.js} +3 -3
- package/dist/modes/interactive/components/custom-message.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +2 -2
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +2 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/{hook-editor.d.ts → extension-editor.d.ts} +3 -3
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/{hook-editor.js → extension-editor.js} +4 -4
- package/dist/modes/interactive/components/extension-editor.js.map +1 -0
- package/dist/modes/interactive/components/{hook-input.d.ts → extension-input.d.ts} +3 -3
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
- package/dist/modes/interactive/components/{hook-input.js → extension-input.js} +3 -3
- package/dist/modes/interactive/components/extension-input.js.map +1 -0
- package/dist/modes/interactive/components/{hook-selector.d.ts → extension-selector.d.ts} +3 -3
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/{hook-selector.js → extension-selector.js} +3 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +3 -3
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -8
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -3
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +9 -9
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +37 -44
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +143 -189
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +10 -33
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -3
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -3
- 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 +33 -57
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +16 -16
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +1053 -0
- package/docs/rpc.md +4 -4
- package/docs/sdk.md +62 -93
- package/docs/session.md +22 -19
- package/docs/skills.md +1 -1
- package/docs/tui.md +1 -1
- package/examples/README.md +9 -15
- package/examples/extensions/README.md +141 -0
- package/examples/{hooks → extensions}/auto-commit-on-exit.ts +3 -3
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/{hooks → extensions}/confirm-destructive.ts +3 -3
- package/examples/{hooks → extensions}/custom-compaction.ts +6 -6
- package/examples/{hooks → extensions}/dirty-repo-guard.ts +8 -4
- package/examples/{hooks → extensions}/file-trigger.ts +3 -3
- package/examples/{hooks → extensions}/git-checkpoint.ts +3 -3
- package/examples/{hooks → extensions}/handoff.ts +3 -3
- package/examples/extensions/hello.ts +25 -0
- package/examples/{hooks → extensions}/permission-gate.ts +3 -3
- package/examples/{hooks → extensions}/pirate.ts +5 -5
- package/examples/{hooks → extensions}/plan-mode.ts +6 -6
- package/examples/{hooks → extensions}/protected-paths.ts +3 -3
- package/examples/{hooks → extensions}/qna.ts +3 -3
- package/examples/{custom-tools/question/index.ts → extensions/question.ts} +13 -17
- package/examples/{hooks → extensions}/snake.ts +3 -3
- package/examples/{hooks → extensions}/status-line.ts +3 -3
- package/examples/{custom-tools → extensions}/subagent/README.md +15 -15
- package/examples/{custom-tools → extensions}/subagent/index.ts +22 -43
- package/examples/{custom-tools/todo/index.ts → extensions/todo.ts} +122 -39
- package/examples/{hooks → extensions}/tools.ts +5 -5
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/05-tools.ts +7 -41
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/12-full-control.ts +10 -29
- package/examples/sdk/README.md +5 -5
- package/package.json +4 -4
- package/dist/core/custom-tools/index.d.ts +0 -7
- package/dist/core/custom-tools/index.d.ts.map +0 -1
- package/dist/core/custom-tools/index.js +0 -6
- package/dist/core/custom-tools/index.js.map +0 -1
- package/dist/core/custom-tools/loader.d.ts +0 -30
- package/dist/core/custom-tools/loader.d.ts.map +0 -1
- package/dist/core/custom-tools/loader.js +0 -276
- package/dist/core/custom-tools/loader.js.map +0 -1
- package/dist/core/custom-tools/types.d.ts +0 -144
- package/dist/core/custom-tools/types.d.ts.map +0 -1
- package/dist/core/custom-tools/types.js +0 -8
- package/dist/core/custom-tools/types.js.map +0 -1
- package/dist/core/custom-tools/wrapper.d.ts +0 -15
- package/dist/core/custom-tools/wrapper.d.ts.map +0 -1
- package/dist/core/custom-tools/wrapper.js +0 -23
- package/dist/core/custom-tools/wrapper.js.map +0 -1
- package/dist/core/hooks/index.d.ts +0 -6
- package/dist/core/hooks/index.d.ts.map +0 -1
- package/dist/core/hooks/index.js +0 -6
- package/dist/core/hooks/index.js.map +0 -1
- package/dist/core/hooks/loader.d.ts +0 -146
- package/dist/core/hooks/loader.d.ts.map +0 -1
- package/dist/core/hooks/loader.js +0 -275
- package/dist/core/hooks/loader.js.map +0 -1
- package/dist/core/hooks/runner.d.ts +0 -173
- package/dist/core/hooks/runner.d.ts.map +0 -1
- package/dist/core/hooks/runner.js.map +0 -1
- package/dist/core/hooks/tool-wrapper.d.ts +0 -17
- package/dist/core/hooks/tool-wrapper.d.ts.map +0 -1
- package/dist/core/hooks/tool-wrapper.js.map +0 -1
- package/dist/core/hooks/types.d.ts +0 -767
- package/dist/core/hooks/types.d.ts.map +0 -1
- package/dist/core/hooks/types.js.map +0 -1
- package/dist/core/slash-commands.d.ts +0 -40
- package/dist/core/slash-commands.d.ts.map +0 -1
- package/dist/core/slash-commands.js.map +0 -1
- package/dist/modes/interactive/components/hook-editor.d.ts.map +0 -1
- package/dist/modes/interactive/components/hook-editor.js.map +0 -1
- package/dist/modes/interactive/components/hook-input.d.ts.map +0 -1
- package/dist/modes/interactive/components/hook-input.js.map +0 -1
- package/dist/modes/interactive/components/hook-message.d.ts +0 -18
- package/dist/modes/interactive/components/hook-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/hook-message.js.map +0 -1
- package/dist/modes/interactive/components/hook-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/hook-selector.js.map +0 -1
- package/docs/custom-tools.md +0 -514
- package/docs/extension-loading.md +0 -1004
- package/docs/hooks.md +0 -979
- package/docs/session-tree-plan.md +0 -441
- package/examples/custom-tools/README.md +0 -114
- package/examples/custom-tools/hello/index.ts +0 -21
- package/examples/hooks/README.md +0 -60
- package/examples/hooks/todo/index.ts +0 -134
- package/examples/sdk/06-hooks.ts +0 -61
- package/examples/sdk/08-slash-commands.ts +0 -42
- /package/examples/{custom-tools → extensions}/subagent/agents/planner.md +0 -0
- /package/examples/{custom-tools → extensions}/subagent/agents/reviewer.md +0 -0
- /package/examples/{custom-tools → extensions}/subagent/agents/scout.md +0 -0
- /package/examples/{custom-tools → extensions}/subagent/agents/worker.md +0 -0
- /package/examples/{custom-tools → extensions}/subagent/agents.ts +0 -0
- /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement-and-review.md +0 -0
- /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/implement.md +0 -0
- /package/examples/{custom-tools/subagent/commands → extensions/subagent/prompts}/scout-and-plan.md +0 -0
|
@@ -17,7 +17,7 @@ import { getAuthPath } from "../config.js";
|
|
|
17
17
|
import { executeBash as executeBashCommand } from "./bash-executor.js";
|
|
18
18
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
19
19
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
20
|
-
import {
|
|
20
|
+
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
21
21
|
// ============================================================================
|
|
22
22
|
// Constants
|
|
23
23
|
// ============================================================================
|
|
@@ -33,7 +33,7 @@ export class AgentSession {
|
|
|
33
33
|
sessionManager;
|
|
34
34
|
settingsManager;
|
|
35
35
|
_scopedModels;
|
|
36
|
-
|
|
36
|
+
_promptTemplates;
|
|
37
37
|
// Event subscription state
|
|
38
38
|
_unsubscribeAgent;
|
|
39
39
|
_eventListeners = [];
|
|
@@ -41,6 +41,8 @@ export class AgentSession {
|
|
|
41
41
|
_steeringMessages = [];
|
|
42
42
|
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
43
43
|
_followUpMessages = [];
|
|
44
|
+
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
45
|
+
_pendingNextTurnMessages = [];
|
|
44
46
|
// Compaction state
|
|
45
47
|
_compactionAbortController = undefined;
|
|
46
48
|
_autoCompactionAbortController = undefined;
|
|
@@ -54,35 +56,32 @@ export class AgentSession {
|
|
|
54
56
|
// Bash execution state
|
|
55
57
|
_bashAbortController = undefined;
|
|
56
58
|
_pendingBashMessages = [];
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
+
// Extension system
|
|
60
|
+
_extensionRunner = undefined;
|
|
59
61
|
_turnIndex = 0;
|
|
60
|
-
// Custom tools for session lifecycle
|
|
61
|
-
_customTools = [];
|
|
62
62
|
_skillsSettings;
|
|
63
63
|
// Model registry for API key resolution
|
|
64
64
|
_modelRegistry;
|
|
65
|
-
// Tool registry for
|
|
65
|
+
// Tool registry for extension getTools/setTools
|
|
66
66
|
_toolRegistry;
|
|
67
67
|
// Function to rebuild system prompt when tools change
|
|
68
68
|
_rebuildSystemPrompt;
|
|
69
|
-
// Base system prompt (without
|
|
69
|
+
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
|
70
70
|
_baseSystemPrompt;
|
|
71
71
|
constructor(config) {
|
|
72
72
|
this.agent = config.agent;
|
|
73
73
|
this.sessionManager = config.sessionManager;
|
|
74
74
|
this.settingsManager = config.settingsManager;
|
|
75
75
|
this._scopedModels = config.scopedModels ?? [];
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
78
|
-
this._customTools = config.customTools ?? [];
|
|
76
|
+
this._promptTemplates = config.promptTemplates ?? [];
|
|
77
|
+
this._extensionRunner = config.extensionRunner;
|
|
79
78
|
this._skillsSettings = config.skillsSettings;
|
|
80
79
|
this._modelRegistry = config.modelRegistry;
|
|
81
80
|
this._toolRegistry = config.toolRegistry ?? new Map();
|
|
82
81
|
this._rebuildSystemPrompt = config.rebuildSystemPrompt;
|
|
83
82
|
this._baseSystemPrompt = config.agent.state.systemPrompt;
|
|
84
83
|
// Always subscribe to agent events for internal handling
|
|
85
|
-
// (session persistence,
|
|
84
|
+
// (session persistence, extensions, auto-compaction, retry logic)
|
|
86
85
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
87
86
|
}
|
|
88
87
|
/** Model registry for API key resolution and model discovery */
|
|
@@ -121,14 +120,14 @@ export class AgentSession {
|
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
|
-
// Emit to
|
|
125
|
-
await this.
|
|
123
|
+
// Emit to extensions first
|
|
124
|
+
await this._emitExtensionEvent(event);
|
|
126
125
|
// Notify all listeners
|
|
127
126
|
this._emit(event);
|
|
128
127
|
// Handle session persistence
|
|
129
128
|
if (event.type === "message_end") {
|
|
130
|
-
// Check if this is a
|
|
131
|
-
if (event.message.role === "
|
|
129
|
+
// Check if this is a custom message from extensions
|
|
130
|
+
if (event.message.role === "custom") {
|
|
132
131
|
// Persist as CustomMessageEntry
|
|
133
132
|
this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
|
|
134
133
|
}
|
|
@@ -197,33 +196,33 @@ export class AgentSession {
|
|
|
197
196
|
}
|
|
198
197
|
return undefined;
|
|
199
198
|
}
|
|
200
|
-
/** Emit
|
|
201
|
-
async
|
|
202
|
-
if (!this.
|
|
199
|
+
/** Emit extension events based on agent events */
|
|
200
|
+
async _emitExtensionEvent(event) {
|
|
201
|
+
if (!this._extensionRunner)
|
|
203
202
|
return;
|
|
204
203
|
if (event.type === "agent_start") {
|
|
205
204
|
this._turnIndex = 0;
|
|
206
|
-
await this.
|
|
205
|
+
await this._extensionRunner.emit({ type: "agent_start" });
|
|
207
206
|
}
|
|
208
207
|
else if (event.type === "agent_end") {
|
|
209
|
-
await this.
|
|
208
|
+
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
210
209
|
}
|
|
211
210
|
else if (event.type === "turn_start") {
|
|
212
|
-
const
|
|
211
|
+
const extensionEvent = {
|
|
213
212
|
type: "turn_start",
|
|
214
213
|
turnIndex: this._turnIndex,
|
|
215
214
|
timestamp: Date.now(),
|
|
216
215
|
};
|
|
217
|
-
await this.
|
|
216
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
218
217
|
}
|
|
219
218
|
else if (event.type === "turn_end") {
|
|
220
|
-
const
|
|
219
|
+
const extensionEvent = {
|
|
221
220
|
type: "turn_end",
|
|
222
221
|
turnIndex: this._turnIndex,
|
|
223
222
|
message: event.message,
|
|
224
223
|
toolResults: event.toolResults,
|
|
225
224
|
};
|
|
226
|
-
await this.
|
|
225
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
227
226
|
this._turnIndex++;
|
|
228
227
|
}
|
|
229
228
|
}
|
|
@@ -353,35 +352,35 @@ export class AgentSession {
|
|
|
353
352
|
get scopedModels() {
|
|
354
353
|
return this._scopedModels;
|
|
355
354
|
}
|
|
356
|
-
/** File-based
|
|
357
|
-
get
|
|
358
|
-
return this.
|
|
355
|
+
/** File-based prompt templates */
|
|
356
|
+
get promptTemplates() {
|
|
357
|
+
return this._promptTemplates;
|
|
359
358
|
}
|
|
360
359
|
// =========================================================================
|
|
361
360
|
// Prompting
|
|
362
361
|
// =========================================================================
|
|
363
362
|
/**
|
|
364
363
|
* Send a prompt to the agent.
|
|
365
|
-
* - Handles
|
|
366
|
-
* - Expands file-based
|
|
364
|
+
* - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming
|
|
365
|
+
* - Expands file-based prompt templates by default
|
|
367
366
|
* - During streaming, queues via steer() or followUp() based on streamingBehavior option
|
|
368
367
|
* - Validates model and API key before sending (when not streaming)
|
|
369
368
|
* @throws Error if streaming and no streamingBehavior specified
|
|
370
369
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
371
370
|
*/
|
|
372
371
|
async prompt(text, options) {
|
|
373
|
-
const
|
|
374
|
-
// Handle
|
|
375
|
-
//
|
|
376
|
-
if (
|
|
377
|
-
const handled = await this.
|
|
372
|
+
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
373
|
+
// Handle extension commands first (execute immediately, even during streaming)
|
|
374
|
+
// Extension commands manage their own LLM interaction via pi.sendMessage()
|
|
375
|
+
if (expandPromptTemplates && text.startsWith("/")) {
|
|
376
|
+
const handled = await this._tryExecuteExtensionCommand(text);
|
|
378
377
|
if (handled) {
|
|
379
|
-
//
|
|
378
|
+
// Extension command executed, no prompt to send
|
|
380
379
|
return;
|
|
381
380
|
}
|
|
382
381
|
}
|
|
383
|
-
// Expand file-based
|
|
384
|
-
const expandedText =
|
|
382
|
+
// Expand file-based prompt templates if requested
|
|
383
|
+
const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this._promptTemplates]) : text;
|
|
385
384
|
// If streaming, queue via steer() or followUp() based on option
|
|
386
385
|
if (this.isStreaming) {
|
|
387
386
|
if (!options?.streamingBehavior) {
|
|
@@ -414,7 +413,7 @@ export class AgentSession {
|
|
|
414
413
|
if (lastAssistant) {
|
|
415
414
|
await this._checkCompaction(lastAssistant, false);
|
|
416
415
|
}
|
|
417
|
-
// Build messages array (
|
|
416
|
+
// Build messages array (custom message if any, then user message)
|
|
418
417
|
const messages = [];
|
|
419
418
|
// Add user message
|
|
420
419
|
const userContent = [{ type: "text", text: expandedText }];
|
|
@@ -426,14 +425,19 @@ export class AgentSession {
|
|
|
426
425
|
content: userContent,
|
|
427
426
|
timestamp: Date.now(),
|
|
428
427
|
});
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
428
|
+
// Inject any pending "nextTurn" messages as context alongside the user message
|
|
429
|
+
for (const msg of this._pendingNextTurnMessages) {
|
|
430
|
+
messages.push(msg);
|
|
431
|
+
}
|
|
432
|
+
this._pendingNextTurnMessages = [];
|
|
433
|
+
// Emit before_agent_start extension event
|
|
434
|
+
if (this._extensionRunner) {
|
|
435
|
+
const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, options?.images);
|
|
436
|
+
// Add all custom messages from extensions
|
|
433
437
|
if (result?.messages) {
|
|
434
438
|
for (const msg of result.messages) {
|
|
435
439
|
messages.push({
|
|
436
|
-
role: "
|
|
440
|
+
role: "custom",
|
|
437
441
|
customType: msg.customType,
|
|
438
442
|
content: msg.content,
|
|
439
443
|
display: msg.display,
|
|
@@ -442,7 +446,7 @@ export class AgentSession {
|
|
|
442
446
|
});
|
|
443
447
|
}
|
|
444
448
|
}
|
|
445
|
-
// Apply
|
|
449
|
+
// Apply extension systemPromptAppend on top of base prompt
|
|
446
450
|
if (result?.systemPromptAppend) {
|
|
447
451
|
this.agent.setSystemPrompt(`${this._baseSystemPrompt}\n\n${result.systemPromptAppend}`);
|
|
448
452
|
}
|
|
@@ -455,28 +459,28 @@ export class AgentSession {
|
|
|
455
459
|
await this.waitForRetry();
|
|
456
460
|
}
|
|
457
461
|
/**
|
|
458
|
-
* Try to execute
|
|
462
|
+
* Try to execute an extension command. Returns true if command was found and executed.
|
|
459
463
|
*/
|
|
460
|
-
async
|
|
461
|
-
if (!this.
|
|
464
|
+
async _tryExecuteExtensionCommand(text) {
|
|
465
|
+
if (!this._extensionRunner)
|
|
462
466
|
return false;
|
|
463
467
|
// Parse command name and args
|
|
464
468
|
const spaceIndex = text.indexOf(" ");
|
|
465
469
|
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
466
470
|
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
467
|
-
const command = this.
|
|
471
|
+
const command = this._extensionRunner.getCommand(commandName);
|
|
468
472
|
if (!command)
|
|
469
473
|
return false;
|
|
470
|
-
// Get command context from
|
|
471
|
-
const ctx = this.
|
|
474
|
+
// Get command context from extension runner (includes session control methods)
|
|
475
|
+
const ctx = this._extensionRunner.createCommandContext();
|
|
472
476
|
try {
|
|
473
477
|
await command.handler(args, ctx);
|
|
474
478
|
return true;
|
|
475
479
|
}
|
|
476
480
|
catch (err) {
|
|
477
|
-
// Emit error via
|
|
478
|
-
this.
|
|
479
|
-
|
|
481
|
+
// Emit error via extension runner
|
|
482
|
+
this._extensionRunner.emitError({
|
|
483
|
+
extensionPath: `command:${commandName}`,
|
|
480
484
|
event: "command",
|
|
481
485
|
error: err instanceof Error ? err.message : String(err),
|
|
482
486
|
});
|
|
@@ -486,35 +490,35 @@ export class AgentSession {
|
|
|
486
490
|
/**
|
|
487
491
|
* Queue a steering message to interrupt the agent mid-run.
|
|
488
492
|
* Delivered after current tool execution, skips remaining tools.
|
|
489
|
-
* Expands file-based
|
|
490
|
-
* @throws Error if text is
|
|
493
|
+
* Expands file-based prompt templates. Errors on extension commands.
|
|
494
|
+
* @throws Error if text is an extension command
|
|
491
495
|
*/
|
|
492
496
|
async steer(text) {
|
|
493
|
-
// Check for
|
|
497
|
+
// Check for extension commands (cannot be queued)
|
|
494
498
|
if (text.startsWith("/")) {
|
|
495
|
-
this.
|
|
499
|
+
this._throwIfExtensionCommand(text);
|
|
496
500
|
}
|
|
497
|
-
// Expand file-based
|
|
498
|
-
const expandedText =
|
|
501
|
+
// Expand file-based prompt templates
|
|
502
|
+
const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
|
|
499
503
|
await this._queueSteer(expandedText);
|
|
500
504
|
}
|
|
501
505
|
/**
|
|
502
506
|
* Queue a follow-up message to be processed after the agent finishes.
|
|
503
507
|
* Delivered only when agent has no more tool calls or steering messages.
|
|
504
|
-
* Expands file-based
|
|
505
|
-
* @throws Error if text is
|
|
508
|
+
* Expands file-based prompt templates. Errors on extension commands.
|
|
509
|
+
* @throws Error if text is an extension command
|
|
506
510
|
*/
|
|
507
511
|
async followUp(text) {
|
|
508
|
-
// Check for
|
|
512
|
+
// Check for extension commands (cannot be queued)
|
|
509
513
|
if (text.startsWith("/")) {
|
|
510
|
-
this.
|
|
514
|
+
this._throwIfExtensionCommand(text);
|
|
511
515
|
}
|
|
512
|
-
// Expand file-based
|
|
513
|
-
const expandedText =
|
|
516
|
+
// Expand file-based prompt templates
|
|
517
|
+
const expandedText = expandPromptTemplate(text, [...this._promptTemplates]);
|
|
514
518
|
await this._queueFollowUp(expandedText);
|
|
515
519
|
}
|
|
516
520
|
/**
|
|
517
|
-
* Internal: Queue a steering message (already expanded, no
|
|
521
|
+
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
518
522
|
*/
|
|
519
523
|
async _queueSteer(text) {
|
|
520
524
|
this._steeringMessages.push(text);
|
|
@@ -525,7 +529,7 @@ export class AgentSession {
|
|
|
525
529
|
});
|
|
526
530
|
}
|
|
527
531
|
/**
|
|
528
|
-
* Internal: Queue a follow-up message (already expanded, no
|
|
532
|
+
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
529
533
|
*/
|
|
530
534
|
async _queueFollowUp(text) {
|
|
531
535
|
this._followUpMessages.push(text);
|
|
@@ -536,41 +540,43 @@ export class AgentSession {
|
|
|
536
540
|
});
|
|
537
541
|
}
|
|
538
542
|
/**
|
|
539
|
-
* Throw an error if the text is
|
|
543
|
+
* Throw an error if the text is an extension command.
|
|
540
544
|
*/
|
|
541
|
-
|
|
542
|
-
if (!this.
|
|
545
|
+
_throwIfExtensionCommand(text) {
|
|
546
|
+
if (!this._extensionRunner)
|
|
543
547
|
return;
|
|
544
548
|
const spaceIndex = text.indexOf(" ");
|
|
545
549
|
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
546
|
-
const command = this.
|
|
550
|
+
const command = this._extensionRunner.getCommand(commandName);
|
|
547
551
|
if (command) {
|
|
548
|
-
throw new Error(`
|
|
552
|
+
throw new Error(`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`);
|
|
549
553
|
}
|
|
550
554
|
}
|
|
551
555
|
/**
|
|
552
|
-
* Send a
|
|
556
|
+
* Send a custom message to the session. Creates a CustomMessageEntry.
|
|
553
557
|
*
|
|
554
558
|
* Handles three cases:
|
|
555
559
|
* - Streaming: queues message, processed when loop pulls from queue
|
|
556
560
|
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
|
557
561
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
558
562
|
*
|
|
559
|
-
* @param message
|
|
563
|
+
* @param message Custom message with customType, content, display, details
|
|
560
564
|
* @param options.triggerTurn If true and not streaming, triggers a new LLM turn
|
|
561
|
-
* @param options.deliverAs
|
|
565
|
+
* @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
|
|
562
566
|
*/
|
|
563
|
-
async
|
|
567
|
+
async sendCustomMessage(message, options) {
|
|
564
568
|
const appMessage = {
|
|
565
|
-
role: "
|
|
569
|
+
role: "custom",
|
|
566
570
|
customType: message.customType,
|
|
567
571
|
content: message.content,
|
|
568
572
|
display: message.display,
|
|
569
573
|
details: message.details,
|
|
570
574
|
timestamp: Date.now(),
|
|
571
575
|
};
|
|
572
|
-
if (
|
|
573
|
-
|
|
576
|
+
if (options?.deliverAs === "nextTurn") {
|
|
577
|
+
this._pendingNextTurnMessages.push(appMessage);
|
|
578
|
+
}
|
|
579
|
+
else if (this.isStreaming) {
|
|
574
580
|
if (options?.deliverAs === "followUp") {
|
|
575
581
|
this.agent.followUp(appMessage);
|
|
576
582
|
}
|
|
@@ -579,11 +585,9 @@ export class AgentSession {
|
|
|
579
585
|
}
|
|
580
586
|
}
|
|
581
587
|
else if (options?.triggerTurn) {
|
|
582
|
-
// Send as prompt - agent loop will emit message events
|
|
583
588
|
await this.agent.prompt(appMessage);
|
|
584
589
|
}
|
|
585
590
|
else {
|
|
586
|
-
// Just append to agent state and session, no turn
|
|
587
591
|
this.agent.appendMessage(appMessage);
|
|
588
592
|
this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
|
|
589
593
|
}
|
|
@@ -629,13 +633,13 @@ export class AgentSession {
|
|
|
629
633
|
* Clears all messages and starts a new session.
|
|
630
634
|
* Listeners are preserved and will continue receiving events.
|
|
631
635
|
* @param options - Optional initial messages and parent session path
|
|
632
|
-
* @returns true if completed, false if cancelled by
|
|
636
|
+
* @returns true if completed, false if cancelled by extension
|
|
633
637
|
*/
|
|
634
638
|
async newSession(options) {
|
|
635
639
|
const previousSessionFile = this.sessionFile;
|
|
636
640
|
// Emit session_before_switch event with reason "new" (can be cancelled)
|
|
637
|
-
if (this.
|
|
638
|
-
const result = (await this.
|
|
641
|
+
if (this._extensionRunner?.hasHandlers("session_before_switch")) {
|
|
642
|
+
const result = (await this._extensionRunner.emit({
|
|
639
643
|
type: "session_before_switch",
|
|
640
644
|
reason: "new",
|
|
641
645
|
}));
|
|
@@ -649,17 +653,17 @@ export class AgentSession {
|
|
|
649
653
|
this.sessionManager.newSession(options);
|
|
650
654
|
this._steeringMessages = [];
|
|
651
655
|
this._followUpMessages = [];
|
|
656
|
+
this._pendingNextTurnMessages = [];
|
|
652
657
|
this._reconnectToAgent();
|
|
653
|
-
// Emit session_switch event with reason "new" to
|
|
654
|
-
if (this.
|
|
655
|
-
await this.
|
|
658
|
+
// Emit session_switch event with reason "new" to extensions
|
|
659
|
+
if (this._extensionRunner) {
|
|
660
|
+
await this._extensionRunner.emit({
|
|
656
661
|
type: "session_switch",
|
|
657
662
|
reason: "new",
|
|
658
663
|
previousSessionFile,
|
|
659
664
|
});
|
|
660
665
|
}
|
|
661
666
|
// Emit session event to custom tools
|
|
662
|
-
await this.emitCustomToolSessionEvent("switch", previousSessionFile);
|
|
663
667
|
return true;
|
|
664
668
|
}
|
|
665
669
|
// =========================================================================
|
|
@@ -846,10 +850,10 @@ export class AgentSession {
|
|
|
846
850
|
}
|
|
847
851
|
throw new Error("Nothing to compact (session too small)");
|
|
848
852
|
}
|
|
849
|
-
let
|
|
850
|
-
let
|
|
851
|
-
if (this.
|
|
852
|
-
const result = (await this.
|
|
853
|
+
let extensionCompaction;
|
|
854
|
+
let fromExtension = false;
|
|
855
|
+
if (this._extensionRunner?.hasHandlers("session_before_compact")) {
|
|
856
|
+
const result = (await this._extensionRunner.emit({
|
|
853
857
|
type: "session_before_compact",
|
|
854
858
|
preparation,
|
|
855
859
|
branchEntries: pathEntries,
|
|
@@ -860,20 +864,20 @@ export class AgentSession {
|
|
|
860
864
|
throw new Error("Compaction cancelled");
|
|
861
865
|
}
|
|
862
866
|
if (result?.compaction) {
|
|
863
|
-
|
|
864
|
-
|
|
867
|
+
extensionCompaction = result.compaction;
|
|
868
|
+
fromExtension = true;
|
|
865
869
|
}
|
|
866
870
|
}
|
|
867
871
|
let summary;
|
|
868
872
|
let firstKeptEntryId;
|
|
869
873
|
let tokensBefore;
|
|
870
874
|
let details;
|
|
871
|
-
if (
|
|
872
|
-
//
|
|
873
|
-
summary =
|
|
874
|
-
firstKeptEntryId =
|
|
875
|
-
tokensBefore =
|
|
876
|
-
details =
|
|
875
|
+
if (extensionCompaction) {
|
|
876
|
+
// Extension provided compaction content
|
|
877
|
+
summary = extensionCompaction.summary;
|
|
878
|
+
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
879
|
+
tokensBefore = extensionCompaction.tokensBefore;
|
|
880
|
+
details = extensionCompaction.details;
|
|
877
881
|
}
|
|
878
882
|
else {
|
|
879
883
|
// Generate compaction result
|
|
@@ -886,17 +890,17 @@ export class AgentSession {
|
|
|
886
890
|
if (this._compactionAbortController.signal.aborted) {
|
|
887
891
|
throw new Error("Compaction cancelled");
|
|
888
892
|
}
|
|
889
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details,
|
|
893
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
890
894
|
const newEntries = this.sessionManager.getEntries();
|
|
891
895
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
892
896
|
this.agent.replaceMessages(sessionContext.messages);
|
|
893
|
-
// Get the saved compaction entry for the
|
|
897
|
+
// Get the saved compaction entry for the extension event
|
|
894
898
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
895
|
-
if (this.
|
|
896
|
-
await this.
|
|
899
|
+
if (this._extensionRunner && savedCompactionEntry) {
|
|
900
|
+
await this._extensionRunner.emit({
|
|
897
901
|
type: "session_compact",
|
|
898
902
|
compactionEntry: savedCompactionEntry,
|
|
899
|
-
|
|
903
|
+
fromExtension,
|
|
900
904
|
});
|
|
901
905
|
}
|
|
902
906
|
return {
|
|
@@ -986,35 +990,35 @@ export class AgentSession {
|
|
|
986
990
|
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
|
|
987
991
|
return;
|
|
988
992
|
}
|
|
989
|
-
let
|
|
990
|
-
let
|
|
991
|
-
if (this.
|
|
992
|
-
const
|
|
993
|
+
let extensionCompaction;
|
|
994
|
+
let fromExtension = false;
|
|
995
|
+
if (this._extensionRunner?.hasHandlers("session_before_compact")) {
|
|
996
|
+
const extensionResult = (await this._extensionRunner.emit({
|
|
993
997
|
type: "session_before_compact",
|
|
994
998
|
preparation,
|
|
995
999
|
branchEntries: pathEntries,
|
|
996
1000
|
customInstructions: undefined,
|
|
997
1001
|
signal: this._autoCompactionAbortController.signal,
|
|
998
1002
|
}));
|
|
999
|
-
if (
|
|
1003
|
+
if (extensionResult?.cancel) {
|
|
1000
1004
|
this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
|
|
1001
1005
|
return;
|
|
1002
1006
|
}
|
|
1003
|
-
if (
|
|
1004
|
-
|
|
1005
|
-
|
|
1007
|
+
if (extensionResult?.compaction) {
|
|
1008
|
+
extensionCompaction = extensionResult.compaction;
|
|
1009
|
+
fromExtension = true;
|
|
1006
1010
|
}
|
|
1007
1011
|
}
|
|
1008
1012
|
let summary;
|
|
1009
1013
|
let firstKeptEntryId;
|
|
1010
1014
|
let tokensBefore;
|
|
1011
1015
|
let details;
|
|
1012
|
-
if (
|
|
1013
|
-
//
|
|
1014
|
-
summary =
|
|
1015
|
-
firstKeptEntryId =
|
|
1016
|
-
tokensBefore =
|
|
1017
|
-
details =
|
|
1016
|
+
if (extensionCompaction) {
|
|
1017
|
+
// Extension provided compaction content
|
|
1018
|
+
summary = extensionCompaction.summary;
|
|
1019
|
+
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1020
|
+
tokensBefore = extensionCompaction.tokensBefore;
|
|
1021
|
+
details = extensionCompaction.details;
|
|
1018
1022
|
}
|
|
1019
1023
|
else {
|
|
1020
1024
|
// Generate compaction result
|
|
@@ -1028,17 +1032,17 @@ export class AgentSession {
|
|
|
1028
1032
|
this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
|
|
1029
1033
|
return;
|
|
1030
1034
|
}
|
|
1031
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details,
|
|
1035
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1032
1036
|
const newEntries = this.sessionManager.getEntries();
|
|
1033
1037
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1034
1038
|
this.agent.replaceMessages(sessionContext.messages);
|
|
1035
|
-
// Get the saved compaction entry for the
|
|
1039
|
+
// Get the saved compaction entry for the extension event
|
|
1036
1040
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1037
|
-
if (this.
|
|
1038
|
-
await this.
|
|
1041
|
+
if (this._extensionRunner && savedCompactionEntry) {
|
|
1042
|
+
await this._extensionRunner.emit({
|
|
1039
1043
|
type: "session_compact",
|
|
1040
1044
|
compactionEntry: savedCompactionEntry,
|
|
1041
|
-
|
|
1045
|
+
fromExtension,
|
|
1042
1046
|
});
|
|
1043
1047
|
}
|
|
1044
1048
|
const result = {
|
|
@@ -1294,13 +1298,13 @@ export class AgentSession {
|
|
|
1294
1298
|
* Switch to a different session file.
|
|
1295
1299
|
* Aborts current operation, loads messages, restores model/thinking.
|
|
1296
1300
|
* Listeners are preserved and will continue receiving events.
|
|
1297
|
-
* @returns true if switch completed, false if cancelled by
|
|
1301
|
+
* @returns true if switch completed, false if cancelled by extension
|
|
1298
1302
|
*/
|
|
1299
1303
|
async switchSession(sessionPath) {
|
|
1300
1304
|
const previousSessionFile = this.sessionManager.getSessionFile();
|
|
1301
1305
|
// Emit session_before_switch event (can be cancelled)
|
|
1302
|
-
if (this.
|
|
1303
|
-
const result = (await this.
|
|
1306
|
+
if (this._extensionRunner?.hasHandlers("session_before_switch")) {
|
|
1307
|
+
const result = (await this._extensionRunner.emit({
|
|
1304
1308
|
type: "session_before_switch",
|
|
1305
1309
|
reason: "resume",
|
|
1306
1310
|
targetSessionFile: sessionPath,
|
|
@@ -1313,20 +1317,20 @@ export class AgentSession {
|
|
|
1313
1317
|
await this.abort();
|
|
1314
1318
|
this._steeringMessages = [];
|
|
1315
1319
|
this._followUpMessages = [];
|
|
1320
|
+
this._pendingNextTurnMessages = [];
|
|
1316
1321
|
// Set new session
|
|
1317
1322
|
this.sessionManager.setSessionFile(sessionPath);
|
|
1318
1323
|
// Reload messages
|
|
1319
1324
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1320
|
-
// Emit session_switch event to
|
|
1321
|
-
if (this.
|
|
1322
|
-
await this.
|
|
1325
|
+
// Emit session_switch event to extensions
|
|
1326
|
+
if (this._extensionRunner) {
|
|
1327
|
+
await this._extensionRunner.emit({
|
|
1323
1328
|
type: "session_switch",
|
|
1324
1329
|
reason: "resume",
|
|
1325
1330
|
previousSessionFile,
|
|
1326
1331
|
});
|
|
1327
1332
|
}
|
|
1328
1333
|
// Emit session event to custom tools
|
|
1329
|
-
await this.emitCustomToolSessionEvent("switch", previousSessionFile);
|
|
1330
1334
|
this.agent.replaceMessages(sessionContext.messages);
|
|
1331
1335
|
// Restore model if saved
|
|
1332
1336
|
if (sessionContext.model) {
|
|
@@ -1345,12 +1349,12 @@ export class AgentSession {
|
|
|
1345
1349
|
}
|
|
1346
1350
|
/**
|
|
1347
1351
|
* Create a branch from a specific entry.
|
|
1348
|
-
* Emits before_branch/branch session events to
|
|
1352
|
+
* Emits before_branch/branch session events to extensions.
|
|
1349
1353
|
*
|
|
1350
1354
|
* @param entryId ID of the entry to branch from
|
|
1351
1355
|
* @returns Object with:
|
|
1352
1356
|
* - selectedText: The text of the selected user message (for editor pre-fill)
|
|
1353
|
-
* - cancelled: True if
|
|
1357
|
+
* - cancelled: True if an extension cancelled the branch
|
|
1354
1358
|
*/
|
|
1355
1359
|
async branch(entryId) {
|
|
1356
1360
|
const previousSessionFile = this.sessionFile;
|
|
@@ -1361,8 +1365,8 @@ export class AgentSession {
|
|
|
1361
1365
|
const selectedText = this._extractUserMessageText(selectedEntry.message.content);
|
|
1362
1366
|
let skipConversationRestore = false;
|
|
1363
1367
|
// Emit session_before_branch event (can be cancelled)
|
|
1364
|
-
if (this.
|
|
1365
|
-
const result = (await this.
|
|
1368
|
+
if (this._extensionRunner?.hasHandlers("session_before_branch")) {
|
|
1369
|
+
const result = (await this._extensionRunner.emit({
|
|
1366
1370
|
type: "session_before_branch",
|
|
1367
1371
|
entryId,
|
|
1368
1372
|
}));
|
|
@@ -1371,6 +1375,8 @@ export class AgentSession {
|
|
|
1371
1375
|
}
|
|
1372
1376
|
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
1373
1377
|
}
|
|
1378
|
+
// Clear pending messages (bound to old session state)
|
|
1379
|
+
this._pendingNextTurnMessages = [];
|
|
1374
1380
|
if (!selectedEntry.parentId) {
|
|
1375
1381
|
this.sessionManager.newSession();
|
|
1376
1382
|
}
|
|
@@ -1379,15 +1385,14 @@ export class AgentSession {
|
|
|
1379
1385
|
}
|
|
1380
1386
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
1381
1387
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1382
|
-
// Emit session_branch event to
|
|
1383
|
-
if (this.
|
|
1384
|
-
await this.
|
|
1388
|
+
// Emit session_branch event to extensions (after branch completes)
|
|
1389
|
+
if (this._extensionRunner) {
|
|
1390
|
+
await this._extensionRunner.emit({
|
|
1385
1391
|
type: "session_branch",
|
|
1386
1392
|
previousSessionFile,
|
|
1387
1393
|
});
|
|
1388
1394
|
}
|
|
1389
1395
|
// Emit session event to custom tools (with reason "branch")
|
|
1390
|
-
await this.emitCustomToolSessionEvent("branch", previousSessionFile);
|
|
1391
1396
|
if (!skipConversationRestore) {
|
|
1392
1397
|
this.agent.replaceMessages(sessionContext.messages);
|
|
1393
1398
|
}
|
|
@@ -1431,11 +1436,11 @@ export class AgentSession {
|
|
|
1431
1436
|
};
|
|
1432
1437
|
// Set up abort controller for summarization
|
|
1433
1438
|
this._branchSummaryAbortController = new AbortController();
|
|
1434
|
-
let
|
|
1435
|
-
let
|
|
1439
|
+
let extensionSummary;
|
|
1440
|
+
let fromExtension = false;
|
|
1436
1441
|
// Emit session_before_tree event
|
|
1437
|
-
if (this.
|
|
1438
|
-
const result = (await this.
|
|
1442
|
+
if (this._extensionRunner?.hasHandlers("session_before_tree")) {
|
|
1443
|
+
const result = (await this._extensionRunner.emit({
|
|
1439
1444
|
type: "session_before_tree",
|
|
1440
1445
|
preparation,
|
|
1441
1446
|
signal: this._branchSummaryAbortController.signal,
|
|
@@ -1444,14 +1449,14 @@ export class AgentSession {
|
|
|
1444
1449
|
return { cancelled: true };
|
|
1445
1450
|
}
|
|
1446
1451
|
if (result?.summary && options.summarize) {
|
|
1447
|
-
|
|
1448
|
-
|
|
1452
|
+
extensionSummary = result.summary;
|
|
1453
|
+
fromExtension = true;
|
|
1449
1454
|
}
|
|
1450
1455
|
}
|
|
1451
1456
|
// Run default summarizer if needed
|
|
1452
1457
|
let summaryText;
|
|
1453
1458
|
let summaryDetails;
|
|
1454
|
-
if (options.summarize && entriesToSummarize.length > 0 && !
|
|
1459
|
+
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
|
|
1455
1460
|
const model = this.model;
|
|
1456
1461
|
const apiKey = await this._modelRegistry.getApiKey(model);
|
|
1457
1462
|
if (!apiKey) {
|
|
@@ -1478,9 +1483,9 @@ export class AgentSession {
|
|
|
1478
1483
|
modifiedFiles: result.modifiedFiles || [],
|
|
1479
1484
|
};
|
|
1480
1485
|
}
|
|
1481
|
-
else if (
|
|
1482
|
-
summaryText =
|
|
1483
|
-
summaryDetails =
|
|
1486
|
+
else if (extensionSummary) {
|
|
1487
|
+
summaryText = extensionSummary.summary;
|
|
1488
|
+
summaryDetails = extensionSummary.details;
|
|
1484
1489
|
}
|
|
1485
1490
|
// Determine the new leaf position based on target type
|
|
1486
1491
|
let newLeafId;
|
|
@@ -1510,7 +1515,7 @@ export class AgentSession {
|
|
|
1510
1515
|
let summaryEntry;
|
|
1511
1516
|
if (summaryText) {
|
|
1512
1517
|
// Create summary at target position (can be null for root)
|
|
1513
|
-
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails,
|
|
1518
|
+
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
|
|
1514
1519
|
summaryEntry = this.sessionManager.getEntry(summaryId);
|
|
1515
1520
|
}
|
|
1516
1521
|
else if (newLeafId === null) {
|
|
@@ -1525,17 +1530,16 @@ export class AgentSession {
|
|
|
1525
1530
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1526
1531
|
this.agent.replaceMessages(sessionContext.messages);
|
|
1527
1532
|
// Emit session_tree event
|
|
1528
|
-
if (this.
|
|
1529
|
-
await this.
|
|
1533
|
+
if (this._extensionRunner) {
|
|
1534
|
+
await this._extensionRunner.emit({
|
|
1530
1535
|
type: "session_tree",
|
|
1531
1536
|
newLeafId: this.sessionManager.getLeafId(),
|
|
1532
1537
|
oldLeafId,
|
|
1533
1538
|
summaryEntry,
|
|
1534
|
-
|
|
1539
|
+
fromExtension: summaryText ? fromExtension : undefined,
|
|
1535
1540
|
});
|
|
1536
1541
|
}
|
|
1537
1542
|
// Emit to custom tools
|
|
1538
|
-
await this.emitCustomToolSessionEvent("tree", this.sessionFile);
|
|
1539
1543
|
this._branchSummaryAbortController = undefined;
|
|
1540
1544
|
return { editorText, cancelled: false, summaryEntry };
|
|
1541
1545
|
}
|
|
@@ -1652,54 +1656,19 @@ export class AgentSession {
|
|
|
1652
1656
|
return text.trim() || undefined;
|
|
1653
1657
|
}
|
|
1654
1658
|
// =========================================================================
|
|
1655
|
-
//
|
|
1659
|
+
// Extension System
|
|
1656
1660
|
// =========================================================================
|
|
1657
1661
|
/**
|
|
1658
|
-
* Check if
|
|
1659
|
-
*/
|
|
1660
|
-
hasHookHandlers(eventType) {
|
|
1661
|
-
return this._hookRunner?.hasHandlers(eventType) ?? false;
|
|
1662
|
-
}
|
|
1663
|
-
/**
|
|
1664
|
-
* Get the hook runner (for setting UI context and error handlers).
|
|
1665
|
-
*/
|
|
1666
|
-
get hookRunner() {
|
|
1667
|
-
return this._hookRunner;
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* Get custom tools (for setting UI context in modes).
|
|
1662
|
+
* Check if extensions have handlers for a specific event type.
|
|
1671
1663
|
*/
|
|
1672
|
-
|
|
1673
|
-
return this.
|
|
1664
|
+
hasExtensionHandlers(eventType) {
|
|
1665
|
+
return this._extensionRunner?.hasHandlers(eventType) ?? false;
|
|
1674
1666
|
}
|
|
1675
1667
|
/**
|
|
1676
|
-
*
|
|
1677
|
-
* Called on session switch, branch, tree navigation, and shutdown.
|
|
1668
|
+
* Get the extension runner (for setting UI context and error handlers).
|
|
1678
1669
|
*/
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
return;
|
|
1682
|
-
const event = { reason, previousSessionFile };
|
|
1683
|
-
const ctx = {
|
|
1684
|
-
sessionManager: this.sessionManager,
|
|
1685
|
-
modelRegistry: this._modelRegistry,
|
|
1686
|
-
model: this.agent.state.model,
|
|
1687
|
-
isIdle: () => !this.isStreaming,
|
|
1688
|
-
hasPendingMessages: () => this.pendingMessageCount > 0,
|
|
1689
|
-
abort: () => {
|
|
1690
|
-
this.abort();
|
|
1691
|
-
},
|
|
1692
|
-
};
|
|
1693
|
-
for (const { tool } of this._customTools) {
|
|
1694
|
-
if (tool.onSession) {
|
|
1695
|
-
try {
|
|
1696
|
-
await tool.onSession(event, ctx);
|
|
1697
|
-
}
|
|
1698
|
-
catch (_err) {
|
|
1699
|
-
// Silently ignore tool errors during session events
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1670
|
+
get extensionRunner() {
|
|
1671
|
+
return this._extensionRunner;
|
|
1703
1672
|
}
|
|
1704
1673
|
}
|
|
1705
1674
|
//# sourceMappingURL=agent-session.js.map
|