@oh-my-pi/pi-coding-agent 3.15.1 → 3.20.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 +51 -1
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- 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/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +35 -60
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line.ts +45 -37
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +643 -113
- package/src/modes/interactive/theme/defaults/index.ts +16 -16
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
|
@@ -8,25 +8,35 @@
|
|
|
8
8
|
* - Commands: JSON objects with `type` field, optional `id` for correlation
|
|
9
9
|
* - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
|
|
10
10
|
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
|
-
* -
|
|
11
|
+
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { nanoid } from "nanoid";
|
|
15
14
|
import type { AgentSession } from "../../core/agent-session";
|
|
16
|
-
import type {
|
|
17
|
-
import { logger } from "../../core/logger";
|
|
15
|
+
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
18
16
|
import { theme } from "../interactive/theme/theme";
|
|
19
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
RpcCommand,
|
|
19
|
+
RpcExtensionUIRequest,
|
|
20
|
+
RpcExtensionUIResponse,
|
|
21
|
+
RpcResponse,
|
|
22
|
+
RpcSessionState,
|
|
23
|
+
} from "./rpc-types";
|
|
20
24
|
|
|
21
25
|
// Re-export types for consumers
|
|
22
|
-
export type {
|
|
26
|
+
export type {
|
|
27
|
+
RpcCommand,
|
|
28
|
+
RpcExtensionUIRequest,
|
|
29
|
+
RpcExtensionUIResponse,
|
|
30
|
+
RpcResponse,
|
|
31
|
+
RpcSessionState,
|
|
32
|
+
} from "./rpc-types";
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* Run in RPC mode.
|
|
26
36
|
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
27
37
|
*/
|
|
28
38
|
export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
29
|
-
const output = (obj: RpcResponse |
|
|
39
|
+
const output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {
|
|
30
40
|
console.log(JSON.stringify(obj));
|
|
31
41
|
};
|
|
32
42
|
|
|
@@ -45,18 +55,21 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
45
55
|
return { id, type: "response", command, success: false, error: message };
|
|
46
56
|
};
|
|
47
57
|
|
|
48
|
-
// Pending
|
|
49
|
-
const
|
|
58
|
+
// Pending extension UI requests waiting for response
|
|
59
|
+
const pendingExtensionRequests = new Map<
|
|
60
|
+
string,
|
|
61
|
+
{ resolve: (value: any) => void; reject: (error: Error) => void }
|
|
62
|
+
>();
|
|
50
63
|
|
|
51
64
|
/**
|
|
52
|
-
* Create
|
|
65
|
+
* Create an extension UI context that uses the RPC protocol.
|
|
53
66
|
*/
|
|
54
|
-
const
|
|
67
|
+
const createExtensionUIContext = (): ExtensionUIContext => ({
|
|
55
68
|
async select(title: string, options: string[]): Promise<string | undefined> {
|
|
56
|
-
const id =
|
|
69
|
+
const id = globalThis.crypto.randomUUID();
|
|
57
70
|
return new Promise((resolve, reject) => {
|
|
58
|
-
|
|
59
|
-
resolve: (response:
|
|
71
|
+
pendingExtensionRequests.set(id, {
|
|
72
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
60
73
|
if ("cancelled" in response && response.cancelled) {
|
|
61
74
|
resolve(undefined);
|
|
62
75
|
} else if ("value" in response) {
|
|
@@ -67,15 +80,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
67
80
|
},
|
|
68
81
|
reject,
|
|
69
82
|
});
|
|
70
|
-
output({ type: "
|
|
83
|
+
output({ type: "extension_ui_request", id, method: "select", title, options } as RpcExtensionUIRequest);
|
|
71
84
|
});
|
|
72
85
|
},
|
|
73
86
|
|
|
74
87
|
async confirm(title: string, message: string): Promise<boolean> {
|
|
75
|
-
const id =
|
|
88
|
+
const id = globalThis.crypto.randomUUID();
|
|
76
89
|
return new Promise((resolve, reject) => {
|
|
77
|
-
|
|
78
|
-
resolve: (response:
|
|
90
|
+
pendingExtensionRequests.set(id, {
|
|
91
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
79
92
|
if ("cancelled" in response && response.cancelled) {
|
|
80
93
|
resolve(false);
|
|
81
94
|
} else if ("confirmed" in response) {
|
|
@@ -86,15 +99,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
86
99
|
},
|
|
87
100
|
reject,
|
|
88
101
|
});
|
|
89
|
-
output({ type: "
|
|
102
|
+
output({ type: "extension_ui_request", id, method: "confirm", title, message } as RpcExtensionUIRequest);
|
|
90
103
|
});
|
|
91
104
|
},
|
|
92
105
|
|
|
93
106
|
async input(title: string, placeholder?: string): Promise<string | undefined> {
|
|
94
|
-
const id =
|
|
107
|
+
const id = globalThis.crypto.randomUUID();
|
|
95
108
|
return new Promise((resolve, reject) => {
|
|
96
|
-
|
|
97
|
-
resolve: (response:
|
|
109
|
+
pendingExtensionRequests.set(id, {
|
|
110
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
98
111
|
if ("cancelled" in response && response.cancelled) {
|
|
99
112
|
resolve(undefined);
|
|
100
113
|
} else if ("value" in response) {
|
|
@@ -105,30 +118,54 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
105
118
|
},
|
|
106
119
|
reject,
|
|
107
120
|
});
|
|
108
|
-
output({ type: "
|
|
121
|
+
output({ type: "extension_ui_request", id, method: "input", title, placeholder } as RpcExtensionUIRequest);
|
|
109
122
|
});
|
|
110
123
|
},
|
|
111
124
|
|
|
112
125
|
notify(message: string, type?: "info" | "warning" | "error"): void {
|
|
113
126
|
// Fire and forget - no response needed
|
|
114
127
|
output({
|
|
115
|
-
type: "
|
|
116
|
-
id:
|
|
128
|
+
type: "extension_ui_request",
|
|
129
|
+
id: globalThis.crypto.randomUUID(),
|
|
117
130
|
method: "notify",
|
|
118
131
|
message,
|
|
119
132
|
notifyType: type,
|
|
120
|
-
} as
|
|
133
|
+
} as RpcExtensionUIRequest);
|
|
121
134
|
},
|
|
122
135
|
|
|
123
136
|
setStatus(key: string, text: string | undefined): void {
|
|
124
137
|
// Fire and forget - no response needed
|
|
125
138
|
output({
|
|
126
|
-
type: "
|
|
127
|
-
id:
|
|
139
|
+
type: "extension_ui_request",
|
|
140
|
+
id: globalThis.crypto.randomUUID(),
|
|
128
141
|
method: "setStatus",
|
|
129
142
|
statusKey: key,
|
|
130
143
|
statusText: text,
|
|
131
|
-
} as
|
|
144
|
+
} as RpcExtensionUIRequest);
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
setWidget(key: string, content: unknown): void {
|
|
148
|
+
// Only support string arrays in RPC mode - factory functions are ignored
|
|
149
|
+
if (content === undefined || Array.isArray(content)) {
|
|
150
|
+
output({
|
|
151
|
+
type: "extension_ui_request",
|
|
152
|
+
id: globalThis.crypto.randomUUID(),
|
|
153
|
+
method: "setWidget",
|
|
154
|
+
widgetKey: key,
|
|
155
|
+
widgetLines: content as string[] | undefined,
|
|
156
|
+
} as RpcExtensionUIRequest);
|
|
157
|
+
}
|
|
158
|
+
// Component factories are not supported in RPC mode - would need TUI access
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
setTitle(title: string): void {
|
|
162
|
+
// Fire and forget - host can implement terminal title control
|
|
163
|
+
output({
|
|
164
|
+
type: "extension_ui_request",
|
|
165
|
+
id: globalThis.crypto.randomUUID(),
|
|
166
|
+
method: "setTitle",
|
|
167
|
+
title,
|
|
168
|
+
} as RpcExtensionUIRequest);
|
|
132
169
|
},
|
|
133
170
|
|
|
134
171
|
async custom() {
|
|
@@ -139,11 +176,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
139
176
|
setEditorText(text: string): void {
|
|
140
177
|
// Fire and forget - host can implement editor control
|
|
141
178
|
output({
|
|
142
|
-
type: "
|
|
143
|
-
id:
|
|
179
|
+
type: "extension_ui_request",
|
|
180
|
+
id: globalThis.crypto.randomUUID(),
|
|
144
181
|
method: "set_editor_text",
|
|
145
182
|
text,
|
|
146
|
-
} as
|
|
183
|
+
} as RpcExtensionUIRequest);
|
|
147
184
|
},
|
|
148
185
|
|
|
149
186
|
getEditorText(): string {
|
|
@@ -153,10 +190,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
153
190
|
},
|
|
154
191
|
|
|
155
192
|
async editor(title: string, prefill?: string): Promise<string | undefined> {
|
|
156
|
-
const id =
|
|
193
|
+
const id = globalThis.crypto.randomUUID();
|
|
157
194
|
return new Promise((resolve, reject) => {
|
|
158
|
-
|
|
159
|
-
resolve: (response:
|
|
195
|
+
pendingExtensionRequests.set(id, {
|
|
196
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
160
197
|
if ("cancelled" in response && response.cancelled) {
|
|
161
198
|
resolve(undefined);
|
|
162
199
|
} else if ("value" in response) {
|
|
@@ -167,7 +204,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
167
204
|
},
|
|
168
205
|
reject,
|
|
169
206
|
});
|
|
170
|
-
output({ type: "
|
|
207
|
+
output({ type: "extension_ui_request", id, method: "editor", title, prefill } as RpcExtensionUIRequest);
|
|
171
208
|
});
|
|
172
209
|
},
|
|
173
210
|
|
|
@@ -176,66 +213,39 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
176
213
|
},
|
|
177
214
|
});
|
|
178
215
|
|
|
179
|
-
// Set up
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
|
|
216
|
+
// Set up extensions with RPC-based UI context
|
|
217
|
+
const extensionRunner = session.extensionRunner;
|
|
218
|
+
if (extensionRunner) {
|
|
219
|
+
extensionRunner.initialize({
|
|
183
220
|
getModel: () => session.agent.state.model,
|
|
184
|
-
sendMessageHandler: (message,
|
|
185
|
-
session.
|
|
186
|
-
output(error(undefined, "
|
|
221
|
+
sendMessageHandler: (message, options) => {
|
|
222
|
+
session.sendCustomMessage(message, options).catch((e) => {
|
|
223
|
+
output(error(undefined, "extension_send", e.message));
|
|
187
224
|
});
|
|
188
225
|
},
|
|
189
226
|
appendEntryHandler: (customType, data) => {
|
|
190
227
|
session.sessionManager.appendCustomEntry(customType, data);
|
|
191
228
|
},
|
|
192
|
-
|
|
229
|
+
getActiveToolsHandler: () => session.getActiveToolNames(),
|
|
230
|
+
getAllToolsHandler: () => session.getAllToolNames(),
|
|
231
|
+
setActiveToolsHandler: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
232
|
+
uiContext: createExtensionUIContext(),
|
|
193
233
|
hasUI: false,
|
|
194
234
|
});
|
|
195
|
-
|
|
196
|
-
output({ type: "
|
|
235
|
+
extensionRunner.onError((err) => {
|
|
236
|
+
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
197
237
|
});
|
|
198
238
|
// Emit session_start event
|
|
199
|
-
await
|
|
239
|
+
await extensionRunner.emit({
|
|
200
240
|
type: "session_start",
|
|
201
241
|
});
|
|
202
242
|
}
|
|
203
243
|
|
|
204
|
-
// Emit session start event to custom tools
|
|
205
|
-
// Note: Tools get no-op UI context in RPC mode (host handles UI via protocol)
|
|
206
|
-
for (const { tool } of session.customTools) {
|
|
207
|
-
if (tool.onSession) {
|
|
208
|
-
try {
|
|
209
|
-
await tool.onSession(
|
|
210
|
-
{
|
|
211
|
-
previousSessionFile: undefined,
|
|
212
|
-
reason: "start",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
sessionManager: session.sessionManager,
|
|
216
|
-
modelRegistry: session.modelRegistry,
|
|
217
|
-
model: session.model,
|
|
218
|
-
isIdle: () => !session.isStreaming,
|
|
219
|
-
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
220
|
-
abort: () => {
|
|
221
|
-
session.abort();
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
} catch (err) {
|
|
226
|
-
logger.warn("Tool onSession error", { error: String(err) });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
244
|
// Output all agent events as JSON
|
|
232
245
|
session.subscribe((event) => {
|
|
233
246
|
output(event);
|
|
234
247
|
});
|
|
235
248
|
|
|
236
|
-
// Serialize prompt commands to prevent concurrent execution
|
|
237
|
-
let activePrompt: Promise<void> | null = null;
|
|
238
|
-
|
|
239
249
|
// Handle a single command
|
|
240
250
|
const handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {
|
|
241
251
|
const id = command.id;
|
|
@@ -246,24 +256,26 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
246
256
|
// =================================================================
|
|
247
257
|
|
|
248
258
|
case "prompt": {
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
activePrompt = session
|
|
259
|
+
// Don't await - events will stream
|
|
260
|
+
// Extension commands are executed immediately, file prompt templates are expanded
|
|
261
|
+
// If streaming and streamingBehavior specified, queues via steer/followUp
|
|
262
|
+
session
|
|
254
263
|
.prompt(command.message, {
|
|
255
264
|
images: command.images,
|
|
265
|
+
streamingBehavior: command.streamingBehavior,
|
|
256
266
|
})
|
|
257
|
-
.catch((e) => output(error(id, "prompt", e.message)))
|
|
258
|
-
.finally(() => {
|
|
259
|
-
activePrompt = null;
|
|
260
|
-
});
|
|
267
|
+
.catch((e) => output(error(id, "prompt", e.message)));
|
|
261
268
|
return success(id, "prompt");
|
|
262
269
|
}
|
|
263
270
|
|
|
264
|
-
case "
|
|
265
|
-
await session.
|
|
266
|
-
return success(id, "
|
|
271
|
+
case "steer": {
|
|
272
|
+
await session.steer(command.message);
|
|
273
|
+
return success(id, "steer");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case "follow_up": {
|
|
277
|
+
await session.followUp(command.message);
|
|
278
|
+
return success(id, "follow_up");
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
case "abort": {
|
|
@@ -287,7 +299,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
287
299
|
thinkingLevel: session.thinkingLevel,
|
|
288
300
|
isStreaming: session.isStreaming,
|
|
289
301
|
isCompacting: session.isCompacting,
|
|
290
|
-
|
|
302
|
+
steeringMode: session.steeringMode,
|
|
303
|
+
followUpMode: session.followUpMode,
|
|
304
|
+
interruptMode: session.interruptMode,
|
|
291
305
|
sessionFile: session.sessionFile,
|
|
292
306
|
sessionId: session.sessionId,
|
|
293
307
|
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
@@ -302,7 +316,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
302
316
|
// =================================================================
|
|
303
317
|
|
|
304
318
|
case "set_model": {
|
|
305
|
-
const models =
|
|
319
|
+
const models = session.getAvailableModels();
|
|
306
320
|
const model = models.find((m) => m.provider === command.provider && m.id === command.modelId);
|
|
307
321
|
if (!model) {
|
|
308
322
|
return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
|
|
@@ -320,7 +334,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
320
334
|
}
|
|
321
335
|
|
|
322
336
|
case "get_available_models": {
|
|
323
|
-
const models =
|
|
337
|
+
const models = session.getAvailableModels();
|
|
324
338
|
return success(id, "get_available_models", { models });
|
|
325
339
|
}
|
|
326
340
|
|
|
@@ -342,12 +356,22 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
342
356
|
}
|
|
343
357
|
|
|
344
358
|
// =================================================================
|
|
345
|
-
// Queue
|
|
359
|
+
// Queue Modes
|
|
346
360
|
// =================================================================
|
|
347
361
|
|
|
348
|
-
case "
|
|
349
|
-
session.
|
|
350
|
-
return success(id, "
|
|
362
|
+
case "set_steering_mode": {
|
|
363
|
+
session.setSteeringMode(command.mode);
|
|
364
|
+
return success(id, "set_steering_mode");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
case "set_follow_up_mode": {
|
|
368
|
+
session.setFollowUpMode(command.mode);
|
|
369
|
+
return success(id, "set_follow_up_mode");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case "set_interrupt_mode": {
|
|
373
|
+
session.setInterruptMode(command.mode);
|
|
374
|
+
return success(id, "set_interrupt_mode");
|
|
351
375
|
}
|
|
352
376
|
|
|
353
377
|
// =================================================================
|
|
@@ -441,42 +465,30 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
441
465
|
}
|
|
442
466
|
};
|
|
443
467
|
|
|
444
|
-
// Listen for JSON input
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (line.trim()) {
|
|
453
|
-
controller.enqueue(line);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
}),
|
|
458
|
-
)
|
|
459
|
-
.getReader();
|
|
468
|
+
// Listen for JSON input using Bun's stdin
|
|
469
|
+
const decoder = new TextDecoder();
|
|
470
|
+
let buffer = "";
|
|
471
|
+
|
|
472
|
+
for await (const chunk of Bun.stdin.stream()) {
|
|
473
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
474
|
+
const lines = buffer.split("\n");
|
|
475
|
+
buffer = lines.pop() || "";
|
|
460
476
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
while (true) {
|
|
464
|
-
const { done, value: line } = await stdinReader.read();
|
|
465
|
-
if (done) break;
|
|
477
|
+
for (const line of lines) {
|
|
478
|
+
if (!line.trim()) continue;
|
|
466
479
|
|
|
467
480
|
try {
|
|
468
481
|
const parsed = JSON.parse(line);
|
|
469
482
|
|
|
470
|
-
// Handle
|
|
471
|
-
if (parsed.type === "
|
|
472
|
-
const response = parsed as
|
|
473
|
-
const pending =
|
|
483
|
+
// Handle extension UI responses
|
|
484
|
+
if (parsed.type === "extension_ui_response") {
|
|
485
|
+
const response = parsed as RpcExtensionUIResponse;
|
|
486
|
+
const pending = pendingExtensionRequests.get(response.id);
|
|
474
487
|
if (pending) {
|
|
475
|
-
|
|
476
|
-
pendingHookRequests.delete(response.id);
|
|
488
|
+
pendingExtensionRequests.delete(response.id);
|
|
477
489
|
pending.resolve(response);
|
|
478
490
|
}
|
|
479
|
-
|
|
491
|
+
continue;
|
|
480
492
|
}
|
|
481
493
|
|
|
482
494
|
// Handle regular commands
|
|
@@ -487,7 +499,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
487
499
|
output(error(undefined, "parse", `Failed to parse command: ${e.message}`));
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
|
-
}
|
|
502
|
+
}
|
|
491
503
|
|
|
492
504
|
// Keep process alive forever
|
|
493
505
|
return new Promise(() => {});
|
|
@@ -17,8 +17,9 @@ import type { CompactionResult } from "../../core/compaction/index";
|
|
|
17
17
|
|
|
18
18
|
export type RpcCommand =
|
|
19
19
|
// Prompting
|
|
20
|
-
| { id?: string; type: "prompt"; message: string; images?: ImageContent[] }
|
|
21
|
-
| { id?: string; type: "
|
|
20
|
+
| { id?: string; type: "prompt"; message: string; images?: ImageContent[]; streamingBehavior?: "steer" | "followUp" }
|
|
21
|
+
| { id?: string; type: "steer"; message: string }
|
|
22
|
+
| { id?: string; type: "follow_up"; message: string }
|
|
22
23
|
| { id?: string; type: "abort" }
|
|
23
24
|
| { id?: string; type: "new_session"; parentSession?: string }
|
|
24
25
|
|
|
@@ -34,8 +35,10 @@ export type RpcCommand =
|
|
|
34
35
|
| { id?: string; type: "set_thinking_level"; level: ThinkingLevel }
|
|
35
36
|
| { id?: string; type: "cycle_thinking_level" }
|
|
36
37
|
|
|
37
|
-
// Queue
|
|
38
|
-
| { id?: string; type: "
|
|
38
|
+
// Queue modes
|
|
39
|
+
| { id?: string; type: "set_steering_mode"; mode: "all" | "one-at-a-time" }
|
|
40
|
+
| { id?: string; type: "set_follow_up_mode"; mode: "all" | "one-at-a-time" }
|
|
41
|
+
| { id?: string; type: "set_interrupt_mode"; mode: "immediate" | "wait" }
|
|
39
42
|
|
|
40
43
|
// Compaction
|
|
41
44
|
| { id?: string; type: "compact"; customInstructions?: string }
|
|
@@ -69,7 +72,9 @@ export interface RpcSessionState {
|
|
|
69
72
|
thinkingLevel: ThinkingLevel;
|
|
70
73
|
isStreaming: boolean;
|
|
71
74
|
isCompacting: boolean;
|
|
72
|
-
|
|
75
|
+
steeringMode: "all" | "one-at-a-time";
|
|
76
|
+
followUpMode: "all" | "one-at-a-time";
|
|
77
|
+
interruptMode: "immediate" | "wait";
|
|
73
78
|
sessionFile?: string;
|
|
74
79
|
sessionId: string;
|
|
75
80
|
autoCompactionEnabled: boolean;
|
|
@@ -85,7 +90,8 @@ export interface RpcSessionState {
|
|
|
85
90
|
export type RpcResponse =
|
|
86
91
|
// Prompting (async - events follow)
|
|
87
92
|
| { id?: string; type: "response"; command: "prompt"; success: true }
|
|
88
|
-
| { id?: string; type: "response"; command: "
|
|
93
|
+
| { id?: string; type: "response"; command: "steer"; success: true }
|
|
94
|
+
| { id?: string; type: "response"; command: "follow_up"; success: true }
|
|
89
95
|
| { id?: string; type: "response"; command: "abort"; success: true }
|
|
90
96
|
| { id?: string; type: "response"; command: "new_session"; success: true; data: { cancelled: boolean } }
|
|
91
97
|
|
|
@@ -125,8 +131,10 @@ export type RpcResponse =
|
|
|
125
131
|
data: { level: ThinkingLevel } | null;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
|
-
// Queue
|
|
129
|
-
| { id?: string; type: "response"; command: "
|
|
134
|
+
// Queue modes
|
|
135
|
+
| { id?: string; type: "response"; command: "set_steering_mode"; success: true }
|
|
136
|
+
| { id?: string; type: "response"; command: "set_follow_up_mode"; success: true }
|
|
137
|
+
| { id?: string; type: "response"; command: "set_interrupt_mode"; success: true }
|
|
130
138
|
|
|
131
139
|
// Compaction
|
|
132
140
|
| { id?: string; type: "response"; command: "compact"; success: true; data: CompactionResult }
|
|
@@ -167,34 +175,48 @@ export type RpcResponse =
|
|
|
167
175
|
| { id?: string; type: "response"; command: string; success: false; error: string };
|
|
168
176
|
|
|
169
177
|
// ============================================================================
|
|
170
|
-
//
|
|
178
|
+
// Extension UI Events (stdout)
|
|
171
179
|
// ============================================================================
|
|
172
180
|
|
|
173
|
-
/** Emitted when
|
|
174
|
-
export type
|
|
175
|
-
| { type: "
|
|
176
|
-
| { type: "
|
|
177
|
-
| { type: "
|
|
178
|
-
| { type: "
|
|
181
|
+
/** Emitted when an extension needs user input */
|
|
182
|
+
export type RpcExtensionUIRequest =
|
|
183
|
+
| { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[] }
|
|
184
|
+
| { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string }
|
|
185
|
+
| { type: "extension_ui_request"; id: string; method: "input"; title: string; placeholder?: string }
|
|
186
|
+
| { type: "extension_ui_request"; id: string; method: "editor"; title: string; prefill?: string }
|
|
179
187
|
| {
|
|
180
|
-
type: "
|
|
188
|
+
type: "extension_ui_request";
|
|
181
189
|
id: string;
|
|
182
190
|
method: "notify";
|
|
183
191
|
message: string;
|
|
184
192
|
notifyType?: "info" | "warning" | "error";
|
|
185
193
|
}
|
|
186
|
-
| {
|
|
187
|
-
|
|
194
|
+
| {
|
|
195
|
+
type: "extension_ui_request";
|
|
196
|
+
id: string;
|
|
197
|
+
method: "setStatus";
|
|
198
|
+
statusKey: string;
|
|
199
|
+
statusText: string | undefined;
|
|
200
|
+
}
|
|
201
|
+
| {
|
|
202
|
+
type: "extension_ui_request";
|
|
203
|
+
id: string;
|
|
204
|
+
method: "setWidget";
|
|
205
|
+
widgetKey: string;
|
|
206
|
+
widgetLines: string[] | undefined;
|
|
207
|
+
}
|
|
208
|
+
| { type: "extension_ui_request"; id: string; method: "setTitle"; title: string }
|
|
209
|
+
| { type: "extension_ui_request"; id: string; method: "set_editor_text"; text: string };
|
|
188
210
|
|
|
189
211
|
// ============================================================================
|
|
190
|
-
//
|
|
212
|
+
// Extension UI Commands (stdin)
|
|
191
213
|
// ============================================================================
|
|
192
214
|
|
|
193
|
-
/** Response to
|
|
194
|
-
export type
|
|
195
|
-
| { type: "
|
|
196
|
-
| { type: "
|
|
197
|
-
| { type: "
|
|
215
|
+
/** Response to an extension UI request */
|
|
216
|
+
export type RpcExtensionUIResponse =
|
|
217
|
+
| { type: "extension_ui_response"; id: string; value: string }
|
|
218
|
+
| { type: "extension_ui_response"; id: string; confirmed: boolean }
|
|
219
|
+
| { type: "extension_ui_response"; id: string; cancelled: true };
|
|
198
220
|
|
|
199
221
|
// ============================================================================
|
|
200
222
|
// Helper type for extracting command types
|
package/src/prompts/task.md
CHANGED
|
@@ -23,6 +23,7 @@ Guidelines:
|
|
|
23
23
|
- For file searches: Use grep/glob when you need to search broadly. Use read when you know the specific file path.
|
|
24
24
|
- For analysis: Start broad and narrow down. Use multiple search strategies if the first doesn't yield results.
|
|
25
25
|
- Be thorough: Check multiple locations, consider different naming conventions, look for related files.
|
|
26
|
+
- When spawning subagents with the Task tool, include a short, user-facing `description` for each task (5-8 words) that summarizes the approach.
|
|
26
27
|
- NEVER create files unless absolutely necessary. ALWAYS prefer editing existing files.
|
|
27
28
|
- NEVER proactively create documentation files (\*.md) or README files unless explicitly requested.
|
|
28
29
|
- Any file paths in your response MUST be absolute. Do NOT use relative paths.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Structured Git operations with safety guards and typed output. Use this tool instead of raw git commands.
|
|
2
|
+
|
|
3
|
+
Operations:
|
|
4
|
+
- READ: status, diff, log, show, blame, branch
|
|
5
|
+
- WRITE: add, restore, commit, checkout, merge, rebase, stash, cherry-pick
|
|
6
|
+
- REMOTE: fetch, pull, push, tag
|
|
7
|
+
- GITHUB: pr, issue, ci, release
|
|
8
|
+
|
|
9
|
+
Returns structured data plus a rendered summary for display. Safety checks may block or require confirmation for destructive actions.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are a voice summarizer for a coding agent.
|
|
2
|
+
|
|
3
|
+
Summarize the assistant response for spoken playback.
|
|
4
|
+
|
|
5
|
+
Rules:
|
|
6
|
+
- Output 1 to 3 sentences, maximum 50 words.
|
|
7
|
+
- No markdown, no code blocks, no inline code, no URLs.
|
|
8
|
+
- If the response already fits, return it unchanged.
|
|
9
|
+
- Preserve the most important question if one is asked.
|
|
10
|
+
- If a decision or missing info is required, ask one short question.
|
|
11
|
+
|
|
12
|
+
Return only the spoken text.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert image to PNG format for terminal display.
|
|
3
|
+
* Kitty graphics protocol requires PNG format (f=100).
|
|
4
|
+
*/
|
|
5
|
+
export async function convertToPng(
|
|
6
|
+
base64Data: string,
|
|
7
|
+
mimeType: string,
|
|
8
|
+
): Promise<{ data: string; mimeType: string } | null> {
|
|
9
|
+
// Already PNG, no conversion needed
|
|
10
|
+
if (mimeType === "image/png") {
|
|
11
|
+
return { data: base64Data, mimeType };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const sharp = (await import("sharp")).default;
|
|
16
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
17
|
+
const pngBuffer = await sharp(buffer).png().toBuffer();
|
|
18
|
+
return {
|
|
19
|
+
data: pngBuffer.toString("base64"),
|
|
20
|
+
mimeType: "image/png",
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
// Sharp not available or conversion failed
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|