wave-code 1.0.6 → 1.0.8
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/dist/components/AgentsManager.d.ts +7 -0
- package/dist/components/AgentsManager.js +109 -0
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/ConfirmationDetails.d.ts +1 -0
- package/dist/components/ConfirmationDetails.js +5 -3
- package/dist/components/ConfirmationSelector.js +17 -3
- package/dist/components/InputBox.js +7 -21
- package/dist/components/LoginCommand.js +31 -2
- package/dist/components/MarketplaceAddForm.js +16 -2
- package/dist/components/RewindCommand.js +11 -4
- package/dist/constants/commands.js +6 -0
- package/dist/contexts/useChat.d.ts +18 -2
- package/dist/contexts/useChat.js +114 -9
- package/dist/daemon/commands.d.ts +49 -0
- package/dist/daemon/commands.js +341 -0
- package/dist/daemon/jsonRpcClient.d.ts +38 -0
- package/dist/daemon/jsonRpcClient.js +129 -0
- package/dist/daemon/socketClient.d.ts +13 -0
- package/dist/daemon/socketClient.js +26 -0
- package/dist/hooks/useInputManager.d.ts +2 -0
- package/dist/hooks/useInputManager.js +8 -0
- package/dist/index.js +88 -0
- package/dist/managers/inputHandlers.js +3 -0
- package/dist/managers/inputReducer.d.ts +4 -0
- package/dist/managers/inputReducer.js +8 -0
- package/dist/reducers/agentsManagerReducer.d.ts +26 -0
- package/dist/reducers/agentsManagerReducer.js +54 -0
- package/dist/stdio/agentBridge.d.ts +15 -0
- package/dist/stdio/agentBridge.js +101 -20
- package/dist/stdio/protocol.d.ts +1 -1
- package/dist/utils/usageSummary.d.ts +0 -4
- package/dist/utils/usageSummary.js +1 -34
- package/package.json +2 -2
- package/src/components/AgentsManager.tsx +290 -0
- package/src/components/ChatInterface.tsx +2 -0
- package/src/components/ConfirmationDetails.tsx +6 -0
- package/src/components/ConfirmationSelector.tsx +18 -3
- package/src/components/InputBox.tsx +54 -45
- package/src/components/LoginCommand.tsx +35 -2
- package/src/components/MarketplaceAddForm.tsx +17 -2
- package/src/components/RewindCommand.tsx +10 -4
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +146 -7
- package/src/daemon/commands.ts +444 -0
- package/src/daemon/jsonRpcClient.ts +158 -0
- package/src/daemon/socketClient.ts +34 -0
- package/src/hooks/useInputManager.ts +8 -0
- package/src/index.ts +130 -0
- package/src/managers/inputHandlers.ts +2 -0
- package/src/managers/inputReducer.ts +10 -0
- package/src/reducers/agentsManagerReducer.ts +91 -0
- package/src/stdio/agentBridge.ts +123 -19
- package/src/stdio/protocol.ts +4 -0
- package/src/utils/usageSummary.ts +2 -46
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JsonRpcClient — minimal JSON-RPC transport over a line-delimited duplex
|
|
3
|
+
* stream (one JSON object per line).
|
|
4
|
+
*
|
|
5
|
+
* Used by `wave daemon` subcommands to talk to the wave daemon's unix socket.
|
|
6
|
+
* Mirrors packages/desktop/src/main/stdio/jsonRpcClient.ts (packages/code
|
|
7
|
+
* cannot import from packages/desktop). Subclasses own the transport and hook
|
|
8
|
+
* in:
|
|
9
|
+
* - `writeLine(message)` writes one JSON line to the peer.
|
|
10
|
+
* - `attachReadable(readable)` wires the inbound half (socket).
|
|
11
|
+
* - `handleClosed(reason)` marks the transport dead and rejects every pending
|
|
12
|
+
* request. Idempotent — safe to call from both dispose() and an exit/close
|
|
13
|
+
* event on the underlying transport.
|
|
14
|
+
*/
|
|
15
|
+
import { createInterface } from "readline";
|
|
16
|
+
export class JsonRpcClient {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.nextId = 1;
|
|
19
|
+
this.pending = new Map();
|
|
20
|
+
this.handlers = new Map();
|
|
21
|
+
this.closedHandlers = [];
|
|
22
|
+
this.closed = false;
|
|
23
|
+
}
|
|
24
|
+
/** Wire an inbound Readable (socket) to the line parser. */
|
|
25
|
+
attachReadable(readable) {
|
|
26
|
+
const rl = createInterface({ input: readable });
|
|
27
|
+
rl.on("line", (line) => this.handleLine(line));
|
|
28
|
+
// readline re-emits input errors on the Interface; the transport subclass
|
|
29
|
+
// already handles errors on the underlying stream, swallow them here so
|
|
30
|
+
// they never surface as an uncaught 'error' on the Interface.
|
|
31
|
+
rl.on("error", () => { });
|
|
32
|
+
}
|
|
33
|
+
/** Mark the transport closed: reject every pending request. Idempotent. */
|
|
34
|
+
handleClosed(reason) {
|
|
35
|
+
if (this.closed)
|
|
36
|
+
return;
|
|
37
|
+
this.closed = true;
|
|
38
|
+
const error = new Error(reason);
|
|
39
|
+
for (const p of this.pending.values())
|
|
40
|
+
p.reject(error);
|
|
41
|
+
this.pending.clear();
|
|
42
|
+
for (const handler of this.closedHandlers)
|
|
43
|
+
handler();
|
|
44
|
+
this.closedHandlers = [];
|
|
45
|
+
}
|
|
46
|
+
get isClosed() {
|
|
47
|
+
return this.closed;
|
|
48
|
+
}
|
|
49
|
+
/** Observe transport teardown (dispose or unexpected close), fired once. */
|
|
50
|
+
onClosed(handler) {
|
|
51
|
+
this.closedHandlers.push(handler);
|
|
52
|
+
}
|
|
53
|
+
async request(method, params, sessionId) {
|
|
54
|
+
if (this.closed) {
|
|
55
|
+
throw new Error("连接已断开。wave 进程已退出,请重启编辑器或检查 CLI 安装。");
|
|
56
|
+
}
|
|
57
|
+
const id = this.nextId++;
|
|
58
|
+
const envelope = { id, method, params };
|
|
59
|
+
if (sessionId)
|
|
60
|
+
envelope.sessionId = sessionId;
|
|
61
|
+
const message = JSON.stringify(envelope) + "\n";
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
this.pending.set(id, { resolve, reject });
|
|
64
|
+
this.writeLine(message);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
notify(method, params, sessionId) {
|
|
68
|
+
if (this.closed)
|
|
69
|
+
return;
|
|
70
|
+
const envelope = { method, params };
|
|
71
|
+
if (sessionId)
|
|
72
|
+
envelope.sessionId = sessionId;
|
|
73
|
+
this.writeLine(JSON.stringify(envelope) + "\n");
|
|
74
|
+
}
|
|
75
|
+
onNotification(method, handler) {
|
|
76
|
+
let set = this.handlers.get(method);
|
|
77
|
+
if (!set) {
|
|
78
|
+
set = new Set();
|
|
79
|
+
this.handlers.set(method, set);
|
|
80
|
+
}
|
|
81
|
+
set.add(handler);
|
|
82
|
+
}
|
|
83
|
+
offNotification(method, handler) {
|
|
84
|
+
this.handlers.get(method)?.delete(handler);
|
|
85
|
+
}
|
|
86
|
+
// ── Internal ──────────────────────────────────────────────────
|
|
87
|
+
handleLine(line) {
|
|
88
|
+
let msg;
|
|
89
|
+
try {
|
|
90
|
+
msg = JSON.parse(line);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
console.error("[wave-jsonrpc] Failed to parse:", line);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (typeof msg !== "object" || msg === null)
|
|
97
|
+
return;
|
|
98
|
+
const obj = msg;
|
|
99
|
+
// Response (has id + result/error)
|
|
100
|
+
if ("id" in obj && ("result" in obj || "error" in obj)) {
|
|
101
|
+
const id = Number(obj.id);
|
|
102
|
+
const pending = this.pending.get(id);
|
|
103
|
+
if (pending) {
|
|
104
|
+
this.pending.delete(id);
|
|
105
|
+
if (obj.error) {
|
|
106
|
+
const err = obj.error;
|
|
107
|
+
pending.reject(new Error(err.message));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
pending.resolve(obj.result);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Notification (has method, no id)
|
|
116
|
+
if ("method" in obj && !("id" in obj)) {
|
|
117
|
+
const method = obj.method;
|
|
118
|
+
const params = obj.params;
|
|
119
|
+
const sessionId = typeof obj.sessionId === "string" ? obj.sessionId : undefined;
|
|
120
|
+
const set = this.handlers.get(method);
|
|
121
|
+
if (set) {
|
|
122
|
+
for (const handler of set) {
|
|
123
|
+
handler(params, sessionId);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocketClient — JSON-RPC transport over a unix socket connection to the wave
|
|
3
|
+
* daemon. Mirrors packages/desktop/src/main/stdio/socketClient.ts (packages/code
|
|
4
|
+
* cannot import from packages/desktop).
|
|
5
|
+
*/
|
|
6
|
+
import type { Socket } from "net";
|
|
7
|
+
import { JsonRpcClient } from "./jsonRpcClient.js";
|
|
8
|
+
export declare class SocketClient extends JsonRpcClient {
|
|
9
|
+
private socket;
|
|
10
|
+
constructor(socket: Socket);
|
|
11
|
+
protected writeLine(message: string): void;
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocketClient — JSON-RPC transport over a unix socket connection to the wave
|
|
3
|
+
* daemon. Mirrors packages/desktop/src/main/stdio/socketClient.ts (packages/code
|
|
4
|
+
* cannot import from packages/desktop).
|
|
5
|
+
*/
|
|
6
|
+
import { JsonRpcClient } from "./jsonRpcClient.js";
|
|
7
|
+
export class SocketClient extends JsonRpcClient {
|
|
8
|
+
constructor(socket) {
|
|
9
|
+
super();
|
|
10
|
+
this.socket = socket;
|
|
11
|
+
this.attachReadable(socket);
|
|
12
|
+
socket.on("close", () => {
|
|
13
|
+
this.handleClosed("远端连接已断开。");
|
|
14
|
+
});
|
|
15
|
+
socket.on("error", (err) => {
|
|
16
|
+
console.error("[wave-daemon] Socket error:", err.message);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
writeLine(message) {
|
|
20
|
+
this.socket.write(message);
|
|
21
|
+
}
|
|
22
|
+
dispose() {
|
|
23
|
+
this.handleClosed("远端连接已断开。");
|
|
24
|
+
this.socket.destroy();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -16,6 +16,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
|
|
|
16
16
|
historySearchQuery: string;
|
|
17
17
|
showBackgroundTaskManager: boolean;
|
|
18
18
|
showMcpManager: boolean;
|
|
19
|
+
showAgentsManager: boolean;
|
|
19
20
|
showRewindManager: boolean;
|
|
20
21
|
showHelp: boolean;
|
|
21
22
|
showStatusCommand: boolean;
|
|
@@ -49,6 +50,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
|
|
|
49
50
|
processSelectorInput: (char: string) => void;
|
|
50
51
|
setShowBackgroundTaskManager: (show: boolean) => void;
|
|
51
52
|
setShowMcpManager: (show: boolean) => void;
|
|
53
|
+
setShowAgentsManager: (show: boolean) => void;
|
|
52
54
|
setShowRewindManager: (show: boolean) => void;
|
|
53
55
|
setShowHelp: (show: boolean) => void;
|
|
54
56
|
setShowStatusCommand: (show: boolean) => void;
|
|
@@ -174,6 +174,9 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
174
174
|
else if (command === "mcp") {
|
|
175
175
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
|
|
176
176
|
}
|
|
177
|
+
else if (command === "agents") {
|
|
178
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
|
|
179
|
+
}
|
|
177
180
|
else if (command === "rewind") {
|
|
178
181
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
179
182
|
}
|
|
@@ -378,6 +381,9 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
378
381
|
const setShowMcpManager = useCallback((show) => {
|
|
379
382
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: show });
|
|
380
383
|
}, []);
|
|
384
|
+
const setShowAgentsManager = useCallback((show) => {
|
|
385
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: show });
|
|
386
|
+
}, []);
|
|
381
387
|
const setShowRewindManager = useCallback((show) => {
|
|
382
388
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: show });
|
|
383
389
|
}, []);
|
|
@@ -506,6 +512,7 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
506
512
|
historySearchQuery: state.historySearchQuery,
|
|
507
513
|
showBackgroundTaskManager: state.showBackgroundTaskManager,
|
|
508
514
|
showMcpManager: state.showMcpManager,
|
|
515
|
+
showAgentsManager: state.showAgentsManager,
|
|
509
516
|
showRewindManager: state.showRewindManager,
|
|
510
517
|
showHelp: state.showHelp,
|
|
511
518
|
showStatusCommand: state.showStatusCommand,
|
|
@@ -545,6 +552,7 @@ export const useInputManager = (callbacks = {}) => {
|
|
|
545
552
|
// Bash/MCP Manager
|
|
546
553
|
setShowBackgroundTaskManager,
|
|
547
554
|
setShowMcpManager,
|
|
555
|
+
setShowAgentsManager,
|
|
548
556
|
setShowRewindManager,
|
|
549
557
|
setShowHelp,
|
|
550
558
|
setShowStatusCommand,
|
package/dist/index.js
CHANGED
|
@@ -178,6 +178,94 @@ export async function main() {
|
|
|
178
178
|
const { updatePluginCommand } = await import("./commands/plugin/update.js");
|
|
179
179
|
await updatePluginCommand(argv);
|
|
180
180
|
});
|
|
181
|
+
})
|
|
182
|
+
.command("daemon", "Manage the wave daemon (client subcommands — to START a daemon use `wave --daemon <socket>` instead)", (yargs) => {
|
|
183
|
+
return yargs
|
|
184
|
+
.help()
|
|
185
|
+
.command("list", "List sessions hosted by the daemon (in-memory registry)", {}, async () => {
|
|
186
|
+
const { daemonListCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
|
|
187
|
+
await daemonListCommand(DEFAULT_DAEMON_SOCKET);
|
|
188
|
+
})
|
|
189
|
+
.command("status <sessionId>", "Show a session's progress and recent messages", (yargs) => {
|
|
190
|
+
return yargs
|
|
191
|
+
.positional("sessionId", {
|
|
192
|
+
describe: "Session ID hosted by the daemon",
|
|
193
|
+
type: "string",
|
|
194
|
+
})
|
|
195
|
+
.option("lines", {
|
|
196
|
+
describe: "Number of recent messages to show",
|
|
197
|
+
default: 20,
|
|
198
|
+
type: "number",
|
|
199
|
+
});
|
|
200
|
+
}, async (argv) => {
|
|
201
|
+
const { daemonStatusCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
|
|
202
|
+
await daemonStatusCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.lines);
|
|
203
|
+
})
|
|
204
|
+
.command("send <sessionId> <message>", "Inject a message into a session and wait for the reply", (yargs) => {
|
|
205
|
+
return yargs
|
|
206
|
+
.positional("sessionId", {
|
|
207
|
+
describe: "Session ID hosted by the daemon",
|
|
208
|
+
type: "string",
|
|
209
|
+
})
|
|
210
|
+
.positional("message", {
|
|
211
|
+
describe: "Message to send",
|
|
212
|
+
type: "string",
|
|
213
|
+
})
|
|
214
|
+
.option("timeout", {
|
|
215
|
+
describe: "Seconds to wait for the reply (0 = no limit)",
|
|
216
|
+
default: 600,
|
|
217
|
+
type: "number",
|
|
218
|
+
});
|
|
219
|
+
}, async (argv) => {
|
|
220
|
+
const { daemonSendCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
|
|
221
|
+
await daemonSendCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.message, { timeout: argv.timeout });
|
|
222
|
+
})
|
|
223
|
+
.command("respond <sessionId> <requestId>", "Respond to a pending permission request", (yargs) => {
|
|
224
|
+
return yargs
|
|
225
|
+
.positional("sessionId", {
|
|
226
|
+
describe: "Session ID hosting the pending request",
|
|
227
|
+
type: "string",
|
|
228
|
+
})
|
|
229
|
+
.positional("requestId", {
|
|
230
|
+
describe: "Pending permission request ID",
|
|
231
|
+
type: "string",
|
|
232
|
+
})
|
|
233
|
+
.option("allow", {
|
|
234
|
+
describe: "Allow the operation",
|
|
235
|
+
type: "boolean",
|
|
236
|
+
})
|
|
237
|
+
.option("deny", {
|
|
238
|
+
describe: "Deny the operation",
|
|
239
|
+
type: "boolean",
|
|
240
|
+
})
|
|
241
|
+
.option("reason", {
|
|
242
|
+
describe: "Reason for the decision (deny)",
|
|
243
|
+
type: "string",
|
|
244
|
+
})
|
|
245
|
+
.option("answer", {
|
|
246
|
+
describe: "Answers JSON for AskUserQuestion requests",
|
|
247
|
+
type: "string",
|
|
248
|
+
})
|
|
249
|
+
.option("rule", {
|
|
250
|
+
describe: "Persist an allowed rule (e.g. Bash(ls))",
|
|
251
|
+
type: "string",
|
|
252
|
+
})
|
|
253
|
+
.option("mode", {
|
|
254
|
+
describe: "Switch the session's permission mode",
|
|
255
|
+
type: "string",
|
|
256
|
+
});
|
|
257
|
+
}, async (argv) => {
|
|
258
|
+
const { daemonRespondCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
|
|
259
|
+
await daemonRespondCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.requestId, {
|
|
260
|
+
allow: argv.allow,
|
|
261
|
+
deny: argv.deny,
|
|
262
|
+
reason: argv.reason,
|
|
263
|
+
answer: argv.answer,
|
|
264
|
+
rule: argv.rule,
|
|
265
|
+
mode: argv.mode,
|
|
266
|
+
});
|
|
267
|
+
})
|
|
268
|
+
.demandCommand(1, "Please specify a daemon subcommand");
|
|
181
269
|
})
|
|
182
270
|
.command("update", "Update WAVE Code to the latest version", {}, async () => {
|
|
183
271
|
const { updateCommand } = await import("./commands/update.js");
|
|
@@ -264,6 +264,9 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
|
|
|
264
264
|
else if (command === "mcp") {
|
|
265
265
|
dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
|
|
266
266
|
}
|
|
267
|
+
else if (command === "agents") {
|
|
268
|
+
dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
|
|
269
|
+
}
|
|
267
270
|
else if (command === "rewind") {
|
|
268
271
|
dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
|
|
269
272
|
}
|
|
@@ -111,6 +111,7 @@ export interface InputState {
|
|
|
111
111
|
imageIdCounter: number;
|
|
112
112
|
showBackgroundTaskManager: boolean;
|
|
113
113
|
showMcpManager: boolean;
|
|
114
|
+
showAgentsManager: boolean;
|
|
114
115
|
showRewindManager: boolean;
|
|
115
116
|
showHelp: boolean;
|
|
116
117
|
showStatusCommand: boolean;
|
|
@@ -187,6 +188,9 @@ export type InputAction = {
|
|
|
187
188
|
} | {
|
|
188
189
|
type: "SET_SHOW_MCP_MANAGER";
|
|
189
190
|
payload: boolean;
|
|
191
|
+
} | {
|
|
192
|
+
type: "SET_SHOW_AGENTS_MANAGER";
|
|
193
|
+
payload: boolean;
|
|
190
194
|
} | {
|
|
191
195
|
type: "SET_SHOW_REWIND_MANAGER";
|
|
192
196
|
payload: boolean;
|
|
@@ -26,6 +26,7 @@ export const initialState = {
|
|
|
26
26
|
imageIdCounter: 1,
|
|
27
27
|
showBackgroundTaskManager: false,
|
|
28
28
|
showMcpManager: false,
|
|
29
|
+
showAgentsManager: false,
|
|
29
30
|
showRewindManager: false,
|
|
30
31
|
showHelp: false,
|
|
31
32
|
showStatusCommand: false,
|
|
@@ -347,6 +348,12 @@ export function inputReducer(state, action) {
|
|
|
347
348
|
showMcpManager: action.payload,
|
|
348
349
|
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
349
350
|
};
|
|
351
|
+
case "SET_SHOW_AGENTS_MANAGER":
|
|
352
|
+
return {
|
|
353
|
+
...state,
|
|
354
|
+
showAgentsManager: action.payload,
|
|
355
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
356
|
+
};
|
|
350
357
|
case "SET_SHOW_REWIND_MANAGER":
|
|
351
358
|
return {
|
|
352
359
|
...state,
|
|
@@ -695,6 +702,7 @@ export function inputReducer(state, action) {
|
|
|
695
702
|
}
|
|
696
703
|
if (!(state.showBackgroundTaskManager ||
|
|
697
704
|
state.showMcpManager ||
|
|
705
|
+
state.showAgentsManager ||
|
|
698
706
|
state.showRewindManager ||
|
|
699
707
|
state.showHelp ||
|
|
700
708
|
state.showStatusCommand ||
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Key } from "ink";
|
|
2
|
+
export type PendingEffect = {
|
|
3
|
+
type: "CANCEL";
|
|
4
|
+
};
|
|
5
|
+
export interface AgentsManagerState {
|
|
6
|
+
selectedIndex: number;
|
|
7
|
+
viewMode: "list" | "detail";
|
|
8
|
+
pendingEffect: PendingEffect | null;
|
|
9
|
+
}
|
|
10
|
+
export type AgentsManagerAction = {
|
|
11
|
+
type: "MOVE_UP";
|
|
12
|
+
} | {
|
|
13
|
+
type: "MOVE_DOWN";
|
|
14
|
+
itemCount: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "SET_VIEW_MODE";
|
|
17
|
+
viewMode: "list" | "detail";
|
|
18
|
+
} | {
|
|
19
|
+
type: "HANDLE_KEY";
|
|
20
|
+
input: string;
|
|
21
|
+
key: Key;
|
|
22
|
+
itemCount: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: "CLEAR_PENDING_EFFECT";
|
|
25
|
+
};
|
|
26
|
+
export declare function agentsManagerReducer(state: AgentsManagerState, action: AgentsManagerAction): AgentsManagerState;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function agentsManagerReducer(state, action) {
|
|
2
|
+
switch (action.type) {
|
|
3
|
+
case "MOVE_UP":
|
|
4
|
+
return {
|
|
5
|
+
...state,
|
|
6
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
7
|
+
};
|
|
8
|
+
case "MOVE_DOWN":
|
|
9
|
+
return {
|
|
10
|
+
...state,
|
|
11
|
+
selectedIndex: Math.min(Math.max(0, action.itemCount - 1), state.selectedIndex + 1),
|
|
12
|
+
};
|
|
13
|
+
case "SET_VIEW_MODE":
|
|
14
|
+
return { ...state, viewMode: action.viewMode };
|
|
15
|
+
case "HANDLE_KEY": {
|
|
16
|
+
const { key, itemCount } = action;
|
|
17
|
+
if (key.return) {
|
|
18
|
+
if (state.viewMode === "list") {
|
|
19
|
+
return { ...state, viewMode: "detail" };
|
|
20
|
+
}
|
|
21
|
+
// Aligned with Claude Code AgentDetail: Enter returns to the list.
|
|
22
|
+
return { ...state, viewMode: "list" };
|
|
23
|
+
}
|
|
24
|
+
if (key.escape) {
|
|
25
|
+
if (state.viewMode === "detail") {
|
|
26
|
+
return { ...state, viewMode: "list" };
|
|
27
|
+
}
|
|
28
|
+
return { ...state, pendingEffect: { type: "CANCEL" } };
|
|
29
|
+
}
|
|
30
|
+
// Detail view does not respond to arrow keys (aligned with CC
|
|
31
|
+
// AgentDetail, which only Esc/Enter back to the list).
|
|
32
|
+
if (state.viewMode === "detail") {
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
if (key.upArrow) {
|
|
36
|
+
return {
|
|
37
|
+
...state,
|
|
38
|
+
selectedIndex: Math.max(0, state.selectedIndex - 1),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (key.downArrow) {
|
|
42
|
+
return {
|
|
43
|
+
...state,
|
|
44
|
+
selectedIndex: Math.min(Math.max(0, itemCount - 1), state.selectedIndex + 1),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
case "CLEAR_PENDING_EFFECT":
|
|
50
|
+
return { ...state, pendingEffect: null };
|
|
51
|
+
default:
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -56,7 +56,18 @@ export declare class AgentBridge {
|
|
|
56
56
|
private removeWorktreeSession;
|
|
57
57
|
private getSessionInfo;
|
|
58
58
|
private updateConfig;
|
|
59
|
+
private getConfiguredModels;
|
|
60
|
+
private setModel;
|
|
59
61
|
private sendMessage;
|
|
62
|
+
/**
|
|
63
|
+
* Webview hosts (desktop/vscode/jetbrains) send pasted images as inline
|
|
64
|
+
* data URLs — there is no local file behind them. Persist each to a temp
|
|
65
|
+
* file so the model gets a real path it can reference with tools (aligned
|
|
66
|
+
* with Claude Code's `[Image source: <path>]` metadata). Real paths pass
|
|
67
|
+
* through untouched; unparseable data URLs pass through as-is and are
|
|
68
|
+
* skipped by the SDK rather than blocking the message.
|
|
69
|
+
*/
|
|
70
|
+
private persistDataUrlImages;
|
|
60
71
|
private bang;
|
|
61
72
|
private askBtw;
|
|
62
73
|
private abortMessage;
|
|
@@ -80,6 +91,7 @@ export declare class AgentBridge {
|
|
|
80
91
|
private connectMcpServer;
|
|
81
92
|
private disconnectMcpServer;
|
|
82
93
|
private getSlashCommands;
|
|
94
|
+
private getSubagentConfigurations;
|
|
83
95
|
private searchFiles;
|
|
84
96
|
/**
|
|
85
97
|
* Writes an uploaded file (from the desktop/webview "+上传文件" flow) into the
|
|
@@ -96,6 +108,9 @@ export declare class AgentBridge {
|
|
|
96
108
|
* client disconnected (daemon mode). Responding to any listed requestId
|
|
97
109
|
* resolves the in-process promise. */
|
|
98
110
|
private listPendingPermissions;
|
|
111
|
+
/** Daemon list: expose the in-memory session registry (live sessions only,
|
|
112
|
+
* not disk-scanning). Registration order is preserved. */
|
|
113
|
+
private listDaemonSessions;
|
|
99
114
|
private getAuthStatus;
|
|
100
115
|
private login;
|
|
101
116
|
private logout;
|