linkshell-cli 0.9.0 → 0.10.1

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.
Files changed (168) hide show
  1. package/README.md +11 -6
  2. package/dist/cli/src/commands/gateway.d.ts +2 -0
  3. package/dist/cli/src/commands/gateway.js +29 -0
  4. package/dist/cli/src/commands/gateway.js.map +1 -0
  5. package/dist/cli/src/commands/host.d.ts +2 -0
  6. package/dist/cli/src/commands/host.js +12 -2
  7. package/dist/cli/src/commands/host.js.map +1 -1
  8. package/dist/cli/src/commands/setup.js +4 -2
  9. package/dist/cli/src/commands/setup.js.map +1 -1
  10. package/dist/cli/src/index.js +12 -334
  11. package/dist/cli/src/index.js.map +1 -1
  12. package/dist/cli/src/utils/daemon.d.ts +0 -7
  13. package/dist/cli/src/utils/daemon.js +1 -18
  14. package/dist/cli/src/utils/daemon.js.map +1 -1
  15. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  16. package/dist/gateway/src/accounts.d.ts +6 -0
  17. package/dist/gateway/src/accounts.js +32 -0
  18. package/dist/gateway/src/accounts.js.map +1 -0
  19. package/dist/gateway/src/index.d.ts +4 -0
  20. package/dist/gateway/src/index.js +5 -0
  21. package/dist/gateway/src/index.js.map +1 -0
  22. package/dist/gateway/src/main.d.ts +1 -0
  23. package/dist/gateway/src/main.js +57 -0
  24. package/dist/gateway/src/main.js.map +1 -0
  25. package/dist/gateway/src/rate-limit.d.ts +22 -0
  26. package/dist/gateway/src/rate-limit.js +66 -0
  27. package/dist/gateway/src/rate-limit.js.map +1 -0
  28. package/dist/gateway/src/relay.d.ts +84 -0
  29. package/dist/gateway/src/relay.js +434 -0
  30. package/dist/gateway/src/relay.js.map +1 -0
  31. package/dist/gateway/src/serve.d.ts +34 -0
  32. package/dist/gateway/src/serve.js +102 -0
  33. package/dist/gateway/src/serve.js.map +1 -0
  34. package/dist/gateway/src/store.d.ts +35 -0
  35. package/dist/gateway/src/store.js +109 -0
  36. package/dist/gateway/src/store.js.map +1 -0
  37. package/dist/gateway/src/subscription.d.ts +18 -0
  38. package/dist/gateway/src/subscription.js +46 -0
  39. package/dist/gateway/src/subscription.js.map +1 -0
  40. package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
  41. package/dist/host/src/drivers/codex/app-server.d.ts +31 -6
  42. package/dist/host/src/drivers/codex/app-server.js +99 -24
  43. package/dist/host/src/drivers/codex/app-server.js.map +1 -1
  44. package/dist/host/src/drivers/codex/desktop-ipc.d.ts +8 -0
  45. package/dist/host/src/drivers/codex/desktop-ipc.js +124 -0
  46. package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -0
  47. package/dist/host/src/drivers/codex/driver.d.ts +111 -2
  48. package/dist/host/src/drivers/codex/driver.js +629 -63
  49. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  50. package/dist/host/src/drivers/codex/mapper.d.ts +21 -0
  51. package/dist/host/src/drivers/codex/mapper.js +45 -6
  52. package/dist/host/src/drivers/codex/mapper.js.map +1 -1
  53. package/dist/host/src/drivers/types.d.ts +6 -0
  54. package/dist/host/src/fs.d.ts +3 -3
  55. package/dist/host/src/hub.js +8 -1
  56. package/dist/host/src/hub.js.map +1 -1
  57. package/dist/host/src/input.d.ts +4 -4
  58. package/dist/host/src/terminals.d.ts +1 -1
  59. package/dist/wire/src/model.d.ts +6 -6
  60. package/dist/wire/src/relay.d.ts +8 -8
  61. package/dist/wire/src/rpc.d.ts +818 -818
  62. package/dist/wire/src/updates.d.ts +18 -18
  63. package/package.json +5 -14
  64. package/src/commands/gateway.ts +31 -0
  65. package/src/commands/host.ts +13 -2
  66. package/src/commands/setup.ts +4 -2
  67. package/src/index.ts +14 -429
  68. package/src/utils/daemon.ts +2 -27
  69. package/dist/cli/src/config.d.ts +0 -14
  70. package/dist/cli/src/config.js +0 -23
  71. package/dist/cli/src/config.js.map +0 -1
  72. package/dist/cli/src/machine-id.d.ts +0 -6
  73. package/dist/cli/src/machine-id.js +0 -50
  74. package/dist/cli/src/machine-id.js.map +0 -1
  75. package/dist/cli/src/providers.d.ts +0 -13
  76. package/dist/cli/src/providers.js +0 -77
  77. package/dist/cli/src/providers.js.map +0 -1
  78. package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
  79. package/dist/cli/src/runtime/acp/acp-client.js +0 -430
  80. package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
  81. package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
  82. package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
  83. package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
  84. package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
  85. package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
  86. package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
  87. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
  88. package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
  89. package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
  90. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
  91. package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
  92. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
  93. package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
  94. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
  95. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
  96. package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
  97. package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
  98. package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
  99. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
  100. package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
  101. package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
  102. package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
  103. package/dist/cli/src/runtime/bridge-session.js +0 -1364
  104. package/dist/cli/src/runtime/bridge-session.js.map +0 -1
  105. package/dist/cli/src/runtime/hook-command.d.ts +0 -9
  106. package/dist/cli/src/runtime/hook-command.js +0 -80
  107. package/dist/cli/src/runtime/hook-command.js.map +0 -1
  108. package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
  109. package/dist/cli/src/runtime/screen-fallback.js +0 -202
  110. package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
  111. package/dist/cli/src/runtime/screen-share.d.ts +0 -34
  112. package/dist/cli/src/runtime/screen-share.js +0 -376
  113. package/dist/cli/src/runtime/screen-share.js.map +0 -1
  114. package/dist/cli/src/runtime/scrollback.d.ts +0 -11
  115. package/dist/cli/src/runtime/scrollback.js +0 -33
  116. package/dist/cli/src/runtime/scrollback.js.map +0 -1
  117. package/dist/cli/src/runtime/usage-report.d.ts +0 -34
  118. package/dist/cli/src/runtime/usage-report.js +0 -332
  119. package/dist/cli/src/runtime/usage-report.js.map +0 -1
  120. package/dist/cli/src/utils/keep-awake.d.ts +0 -6
  121. package/dist/cli/src/utils/keep-awake.js +0 -48
  122. package/dist/cli/src/utils/keep-awake.js.map +0 -1
  123. package/dist/cli/src/utils/lan-ip.d.ts +0 -9
  124. package/dist/cli/src/utils/lan-ip.js +0 -61
  125. package/dist/cli/src/utils/lan-ip.js.map +0 -1
  126. package/dist/host/src/input-helper.d.ts +0 -1
  127. package/dist/host/src/input-helper.js +0 -564
  128. package/dist/host/src/input-helper.js.map +0 -1
  129. package/dist/shared-protocol/src/index.d.ts +0 -17029
  130. package/dist/shared-protocol/src/index.js +0 -1064
  131. package/dist/shared-protocol/src/index.js.map +0 -1
  132. package/scripts/copy-web.mjs +0 -34
  133. package/src/config.ts +0 -36
  134. package/src/machine-id.ts +0 -61
  135. package/src/providers.ts +0 -97
  136. package/src/runtime/acp/acp-client.ts +0 -521
  137. package/src/runtime/acp/agent-process-discovery.ts +0 -308
  138. package/src/runtime/acp/agent-session.ts +0 -1186
  139. package/src/runtime/acp/agent-workspace.ts +0 -6590
  140. package/src/runtime/acp/claude-sdk-client.ts +0 -1221
  141. package/src/runtime/acp/claude-stream-json-client.ts +0 -633
  142. package/src/runtime/acp/json-rpc.ts +0 -184
  143. package/src/runtime/acp/provider-resolver.ts +0 -154
  144. package/src/runtime/bridge-session.ts +0 -1549
  145. package/src/runtime/hook-command.ts +0 -73
  146. package/src/runtime/screen-fallback.ts +0 -235
  147. package/src/runtime/screen-share.ts +0 -426
  148. package/src/runtime/scrollback.ts +0 -43
  149. package/src/runtime/usage-report.ts +0 -342
  150. package/src/utils/keep-awake.ts +0 -61
  151. package/src/utils/lan-ip.ts +0 -61
  152. package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
  153. package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
  154. package/web/assets/TerminalPanel-CTSklkct.js +0 -1
  155. package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
  156. package/web/assets/index-BsnDaeCt.css +0 -33
  157. package/web/assets/index-q85psYZ5.js +0 -36
  158. package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
  159. package/web/assets/vendor-react-M7xG3qiw.js +0 -49
  160. package/web/assets/vendor-xterm-D15THcI4.js +0 -9
  161. package/web/index.html +0 -37
  162. package/web/logos/claude.png +0 -0
  163. package/web/logos/codex.png +0 -0
  164. package/web/logos/copilot.png +0 -0
  165. package/web/logos/gemini.png +0 -0
  166. package/web/logos/linkshell-1024.png +0 -0
  167. package/web/logos/linkshell-splash.png +0 -0
  168. package/web/logos/linkshell.png +0 -0
@@ -1,1081 +0,0 @@
1
- import { createEnvelope, parseTypedPayload, } from "@linkshell/protocol";
2
- import { AcpClient } from "./acp-client.js";
3
- import { ClaudeSdkClient } from "./claude-sdk-client.js";
4
- import { ClaudeStreamJsonClient } from "./claude-stream-json-client.js";
5
- import { resolveAgentCommand } from "./provider-resolver.js";
6
- function protocolSupportsImages(protocol) {
7
- return protocol === "codex-app-server" ||
8
- protocol === "claude-agent-sdk" ||
9
- protocol === "claude-stream-json";
10
- }
11
- const PERMISSION_TIMEOUT_MS = 5 * 60_000;
12
- function id(prefix) {
13
- return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
14
- }
15
- function stringify(value) {
16
- if (typeof value === "string")
17
- return value;
18
- try {
19
- return JSON.stringify(value, null, 2);
20
- }
21
- catch {
22
- return String(value);
23
- }
24
- }
25
- function firstString(value, keys) {
26
- for (const key of keys) {
27
- const next = value[key];
28
- if (typeof next === "string" && next.length > 0)
29
- return next;
30
- }
31
- return undefined;
32
- }
33
- function asRecord(value) {
34
- return typeof value === "object" && value ? value : undefined;
35
- }
36
- function extractItem(value) {
37
- const raw = asRecord(value);
38
- if (!raw)
39
- return undefined;
40
- return asRecord(raw.item) ?? raw;
41
- }
42
- function stringifyDefined(value) {
43
- if (value === undefined || value === null || value === "")
44
- return undefined;
45
- return stringify(value);
46
- }
47
- function appendCapped(current, delta, maxLength) {
48
- const next = `${current ?? ""}${delta}`;
49
- if (next.length <= maxLength)
50
- return next;
51
- return next.slice(next.length - maxLength);
52
- }
53
- function decodeBase64(value) {
54
- if (!value)
55
- return undefined;
56
- try {
57
- return Buffer.from(value, "base64").toString("utf8");
58
- }
59
- catch {
60
- return undefined;
61
- }
62
- }
63
- function normalizeToolStatus(value, completedFallback = false) {
64
- if (value === "completed" || value === "succeeded" || value === "success" || value === "applied") {
65
- return "completed";
66
- }
67
- if (value === "failed" || value === "error" || value === "declined" || value === "cancelled") {
68
- return "failed";
69
- }
70
- if (value === "pending" || value === "queued")
71
- return "pending";
72
- if (value === "running" || value === "inProgress" || value === "executing")
73
- return "running";
74
- return completedFallback ? "completed" : "running";
75
- }
76
- function normalizePlanStatus(value) {
77
- if (value === "completed" || value === "done")
78
- return "completed";
79
- if (value === "inProgress" || value === "running" || value === "active")
80
- return "in_progress";
81
- return "pending";
82
- }
83
- function planStepFromItem(item) {
84
- const text = firstString(item, ["text", "title", "description", "message"]);
85
- if (!text)
86
- return undefined;
87
- return {
88
- id: firstString(item, ["id", "itemId"]) ?? id("plan"),
89
- text,
90
- status: normalizePlanStatus(item.status),
91
- };
92
- }
93
- function nameFromToolMethod(method) {
94
- if (method.includes("commandExecution"))
95
- return "命令";
96
- if (method.includes("fileChange"))
97
- return "文件修改";
98
- if (method.includes("mcpToolCall"))
99
- return "MCP 工具";
100
- return "工具";
101
- }
102
- function isToolItemType(itemType) {
103
- return (itemType === "commandExecution" ||
104
- itemType === "fileChange" ||
105
- itemType === "mcpToolCall" ||
106
- itemType === "dynamicToolCall");
107
- }
108
- function toolNameFromItem(item) {
109
- const itemType = firstString(item, ["type"]);
110
- if (itemType === "commandExecution")
111
- return "命令";
112
- if (itemType === "fileChange")
113
- return "文件修改";
114
- if (itemType === "mcpToolCall") {
115
- const server = firstString(item, ["server"]);
116
- const tool = firstString(item, ["tool", "toolName", "name"]);
117
- return [server, tool].filter(Boolean).join(" · ") || "MCP 工具";
118
- }
119
- if (itemType === "dynamicToolCall") {
120
- const namespace = firstString(item, ["namespace"]);
121
- const tool = firstString(item, ["tool", "toolName", "name"]);
122
- return [namespace, tool].filter(Boolean).join(" · ") || "工具";
123
- }
124
- return firstString(item, ["toolName", "tool", "name", "title"]);
125
- }
126
- function toolInputFromItem(item) {
127
- const itemType = firstString(item, ["type"]);
128
- if (itemType === "commandExecution") {
129
- const command = firstString(item, ["command"]);
130
- const cwd = firstString(item, ["cwd"]);
131
- if (command && cwd)
132
- return `${command}\n\ncwd: ${cwd}`;
133
- return command ?? cwd;
134
- }
135
- if (itemType === "fileChange") {
136
- const changes = Array.isArray(item.changes) ? item.changes : [];
137
- return summarizeFileChanges(changes) ?? firstString(item, ["path", "file", "filePath", "absolutePath", "relativePath"]);
138
- }
139
- return stringifyDefined(item.arguments ?? item.input ?? item.toolInput);
140
- }
141
- function looksLikeDiff(text) {
142
- const value = text.trim();
143
- return (value.startsWith("diff --git ") ||
144
- value.startsWith("@@ ") ||
145
- value.includes("\n@@ ") ||
146
- (value.includes("\n--- ") && value.includes("\n+++ ")));
147
- }
148
- function collectDiffStrings(value, depth = 0) {
149
- if (depth > 6 || value === undefined || value === null)
150
- return [];
151
- if (typeof value === "string")
152
- return looksLikeDiff(value) ? [value] : [];
153
- if (Array.isArray(value))
154
- return value.flatMap((entry) => collectDiffStrings(entry, depth + 1));
155
- const raw = asRecord(value);
156
- if (!raw)
157
- return [];
158
- const direct = [];
159
- const nested = [];
160
- for (const [key, entry] of Object.entries(raw)) {
161
- const lowerKey = key.toLowerCase();
162
- const isDiffField = lowerKey.includes("diff") ||
163
- lowerKey.includes("patch") ||
164
- lowerKey.includes("unified");
165
- if (typeof entry === "string" && isDiffField && entry.trim()) {
166
- direct.push(entry);
167
- }
168
- else if (typeof entry === "object" && entry) {
169
- nested.push(...collectDiffStrings(entry, depth + 1));
170
- }
171
- }
172
- return [...direct, ...nested].filter((entry) => looksLikeDiff(entry));
173
- }
174
- function extractDiffText(value) {
175
- const diffs = collectDiffStrings(value)
176
- .map((entry) => entry.trim())
177
- .filter(Boolean);
178
- if (diffs.length === 0)
179
- return undefined;
180
- return diffs
181
- .filter((entry, index, array) => array.indexOf(entry) === index)
182
- .join("\n\n")
183
- .slice(0, 24_000);
184
- }
185
- function summarizeFileChanges(changes) {
186
- const lines = changes
187
- .map((change) => {
188
- const raw = asRecord(change);
189
- if (!raw)
190
- return undefined;
191
- const path = firstString(raw, ["path", "file", "filePath", "absolutePath", "relativePath"]) ??
192
- firstString(asRecord(raw.update) ?? {}, ["path", "file", "filePath"]);
193
- const kind = firstString(raw, ["kind", "type", "operation", "action"]);
194
- return [kind, path].filter(Boolean).join(" ") || path;
195
- })
196
- .filter((line) => Boolean(line));
197
- return lines.length > 0 ? lines.slice(0, 8).join("\n") : undefined;
198
- }
199
- export class AgentSessionProxy {
200
- input;
201
- client;
202
- activeProvider;
203
- activeProtocol;
204
- agentSessionId;
205
- status = "unavailable";
206
- error;
207
- initialized = false;
208
- currentTurnId;
209
- messages = [];
210
- toolCalls = new Map();
211
- toolOutputBuffers = new Map();
212
- plan = [];
213
- planDeltaBuffers = new Map();
214
- pendingPermissions = new Map();
215
- permissionWaiters = new Map();
216
- permissionSources = new Map();
217
- constructor(input) {
218
- this.input = input;
219
- }
220
- async handleEnvelope(envelope) {
221
- switch (envelope.type) {
222
- case "agent.initialize":
223
- await this.initialize();
224
- this.sendSnapshot();
225
- break;
226
- case "agent.session.new": {
227
- const payload = parseTypedPayload("agent.session.new", envelope.payload);
228
- await this.ensureSession(payload.cwd ?? this.input.cwd, payload.mcpServers);
229
- this.sendSnapshot();
230
- break;
231
- }
232
- case "agent.session.load": {
233
- const payload = parseTypedPayload("agent.session.load", envelope.payload);
234
- await this.ensureClient();
235
- if (!this.client)
236
- return;
237
- const result = await this.client.loadSession({
238
- sessionId: payload.agentSessionId,
239
- cwd: payload.cwd ?? this.input.cwd,
240
- });
241
- this.agentSessionId = this.extractSessionId(result) ?? payload.agentSessionId;
242
- this.status = "idle";
243
- this.error = undefined;
244
- this.sendSnapshot();
245
- break;
246
- }
247
- case "agent.session.list":
248
- await this.sendSessionList();
249
- break;
250
- case "agent.prompt": {
251
- const payload = parseTypedPayload("agent.prompt", envelope.payload);
252
- await this.sendPrompt(payload);
253
- break;
254
- }
255
- case "agent.cancel": {
256
- const payload = parseTypedPayload("agent.cancel", envelope.payload);
257
- this.cancelPendingPermissions();
258
- this.client?.cancel({
259
- sessionId: payload.agentSessionId ?? this.agentSessionId,
260
- turnId: this.currentTurnId,
261
- });
262
- this.currentTurnId = undefined;
263
- this.status = "idle";
264
- this.sendUpdate({ kind: "status", status: "idle" });
265
- break;
266
- }
267
- case "agent.permission.response": {
268
- const payload = parseTypedPayload("agent.permission.response", envelope.payload);
269
- const permission = this.pendingPermissions.get(payload.requestId);
270
- this.pendingPermissions.delete(payload.requestId);
271
- const selectedOptionId = payload.optionId ?? selectPermissionOption(permission, payload.outcome);
272
- const waiter = this.permissionWaiters.get(payload.requestId);
273
- if (waiter) {
274
- clearTimeout(waiter.timer);
275
- this.permissionWaiters.delete(payload.requestId);
276
- waiter.resolve(formatPermissionResponse(this.permissionSources.get(payload.requestId), payload.outcome, selectedOptionId));
277
- this.permissionSources.delete(payload.requestId);
278
- }
279
- else {
280
- this.client?.respondPermission({
281
- sessionId: payload.agentSessionId ?? this.agentSessionId,
282
- requestId: payload.requestId,
283
- outcome: payload.outcome === "cancelled" ? "deny" : payload.outcome,
284
- optionId: selectedOptionId,
285
- });
286
- }
287
- this.status = "running";
288
- this.sendSnapshot();
289
- break;
290
- }
291
- }
292
- }
293
- stop() {
294
- this.client?.stop();
295
- this.client = undefined;
296
- }
297
- async initialize() {
298
- if (this.initialized) {
299
- this.sendCapabilities();
300
- return;
301
- }
302
- await this.tryStartFirstAvailable();
303
- }
304
- async tryStartFirstAvailable() {
305
- if (this.client)
306
- return;
307
- for (const provider of this.input.availableProviders) {
308
- const resolved = resolveAgentCommand({
309
- provider,
310
- command: this.input.command,
311
- });
312
- if (!resolved)
313
- continue;
314
- const tryCreateClient = async (config) => {
315
- const isClaudeSdk = config.protocol === "claude-agent-sdk";
316
- const isClaudeStreamJson = config.protocol === "claude-stream-json";
317
- const client = isClaudeSdk
318
- ? new ClaudeSdkClient({
319
- command: config.command,
320
- protocol: config.protocol,
321
- framing: config.framing,
322
- cwd: this.input.cwd,
323
- onNotification: (method, params) => this.handleNotification(method, params),
324
- onRequest: (method, params) => this.handleRequest(method, params),
325
- onExit: (message) => this.handleExit(message),
326
- })
327
- : isClaudeStreamJson
328
- ? new ClaudeStreamJsonClient({
329
- command: config.command,
330
- protocol: config.protocol,
331
- framing: config.framing,
332
- cwd: this.input.cwd,
333
- onNotification: (method, params) => this.handleNotification(method, params),
334
- onRequest: (method, params) => this.handleRequest(method, params),
335
- onExit: (message) => this.handleExit(message),
336
- })
337
- : new AcpClient({
338
- command: config.command,
339
- protocol: config.protocol,
340
- framing: config.framing,
341
- cwd: this.input.cwd,
342
- onNotification: (method, params) => this.handleNotification(method, params),
343
- onRequest: (method, params) => this.handleRequest(method, params),
344
- onExit: (message) => this.handleExit(message),
345
- });
346
- await client.initialize();
347
- return client;
348
- };
349
- try {
350
- this.client = await tryCreateClient(resolved);
351
- this.activeProvider = provider;
352
- this.activeProtocol = resolved.protocol;
353
- this.initialized = true;
354
- this.status = "idle";
355
- this.error = undefined;
356
- this.sendCapabilities();
357
- return;
358
- }
359
- catch (error) {
360
- if (provider === "claude" && resolved.protocol === "claude-agent-sdk") {
361
- if (this.input.verbose) {
362
- process.stderr.write(`[agent] Claude SDK failed, falling back to stream-json: ${error instanceof Error ? error.message : String(error)}\n`);
363
- }
364
- try {
365
- const fallback = {
366
- provider,
367
- command: "claude --print --output-format stream-json --input-format stream-json --verbose --permission-mode bypassPermissions",
368
- protocol: "claude-stream-json",
369
- framing: "newline",
370
- };
371
- this.client = await tryCreateClient(fallback);
372
- this.activeProvider = provider;
373
- this.activeProtocol = fallback.protocol;
374
- this.initialized = true;
375
- this.status = "idle";
376
- this.error = undefined;
377
- this.sendCapabilities();
378
- return;
379
- }
380
- catch (fallbackError) {
381
- this.client?.stop();
382
- this.client = undefined;
383
- this.activeProtocol = undefined;
384
- if (this.input.verbose) {
385
- process.stderr.write(`[agent] Claude stream-json fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}\n`);
386
- }
387
- }
388
- }
389
- this.client?.stop();
390
- this.client = undefined;
391
- this.activeProtocol = undefined;
392
- if (this.input.verbose) {
393
- process.stderr.write(`[agent] failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
394
- }
395
- }
396
- }
397
- this.status = "unavailable";
398
- this.error = "没有可用的 Agent provider。请安装 Claude Code 或 Codex CLI。";
399
- this.sendCapabilities();
400
- }
401
- async ensureClient() {
402
- if (this.client)
403
- return;
404
- await this.tryStartFirstAvailable();
405
- }
406
- async ensureSession(cwd, mcpServers) {
407
- await this.ensureClient();
408
- if (!this.client || this.agentSessionId)
409
- return;
410
- try {
411
- const result = await this.client.newSession({ cwd, mcpServers });
412
- this.agentSessionId = this.extractSessionId(result) ?? id("agent-session");
413
- this.status = "idle";
414
- this.error = undefined;
415
- this.sendUpdate({ kind: "status", status: "idle" });
416
- }
417
- catch (error) {
418
- this.status = "error";
419
- this.error = error instanceof Error ? error.message : String(error);
420
- this.sendUpdate({ kind: "error", error: this.error, status: "error" });
421
- }
422
- }
423
- async sendPrompt(payload) {
424
- await this.ensureSession(this.input.cwd);
425
- if (!this.client || !this.agentSessionId)
426
- return;
427
- const content = payload.contentBlocks
428
- .map((block) => (block.type === "text" ? block.text ?? "" : `[${block.mimeType ?? "image"} attachment]`))
429
- .filter(Boolean)
430
- .join("\n");
431
- const userMessage = {
432
- id: payload.clientMessageId,
433
- role: "user",
434
- content,
435
- createdAt: Date.now(),
436
- };
437
- this.messages.push(userMessage);
438
- this.status = "running";
439
- this.sendUpdate({ kind: "message", message: userMessage, status: "running" });
440
- try {
441
- const result = await this.client.prompt({
442
- sessionId: payload.agentSessionId ?? this.agentSessionId,
443
- content: payload.contentBlocks,
444
- clientMessageId: payload.clientMessageId,
445
- model: payload.model,
446
- reasoningEffort: payload.reasoningEffort,
447
- permissionMode: payload.permissionMode,
448
- cwd: this.input.cwd,
449
- });
450
- this.currentTurnId = this.extractTurnId(result) ?? this.currentTurnId;
451
- if (this.status === "running") {
452
- this.status = "idle";
453
- this.sendUpdate({ kind: "status", status: "idle" });
454
- }
455
- }
456
- catch (error) {
457
- this.status = "error";
458
- this.error = error instanceof Error ? error.message : String(error);
459
- this.sendUpdate({ kind: "error", error: this.error, status: "error" });
460
- }
461
- }
462
- handleRequest(method, params) {
463
- if (isPermissionRequestMethod(method)) {
464
- return this.handlePermission(params, true, method);
465
- }
466
- if (this.input.verbose) {
467
- process.stderr.write(`[agent:request] unsupported ${method}\n`);
468
- }
469
- return {};
470
- }
471
- async sendSessionList() {
472
- await this.ensureClient();
473
- if (!this.client)
474
- return;
475
- try {
476
- const result = await this.client.listSessions();
477
- this.sendUpdate({
478
- kind: "status",
479
- status: "idle",
480
- delta: stringify(result).slice(0, 4000),
481
- });
482
- }
483
- catch (error) {
484
- this.sendUpdate({
485
- kind: "error",
486
- error: error instanceof Error ? error.message : String(error),
487
- status: "error",
488
- });
489
- }
490
- }
491
- handleNotification(method, params) {
492
- if (this.input.verbose) {
493
- process.stderr.write(`[agent] ${method} ${stringify(params).slice(0, 500)}\n`);
494
- }
495
- if (method === "initialized" ||
496
- method.startsWith("account/") ||
497
- method.startsWith("mcpServer/startupStatus/") ||
498
- method === "thread/status/changed" ||
499
- method === "thread/tokenUsage/updated" ||
500
- method === "serverRequest/resolved" ||
501
- method === "mcpServer/oauthLogin/completed") {
502
- return;
503
- }
504
- if (method === "thread/started") {
505
- this.agentSessionId = this.extractSessionId(params) ?? this.agentSessionId;
506
- this.status = "idle";
507
- this.sendUpdate({ kind: "status", status: "idle" });
508
- return;
509
- }
510
- if (method === "turn/started") {
511
- this.currentTurnId = this.extractTurnId(params) ?? this.currentTurnId;
512
- this.status = "running";
513
- this.sendUpdate({ kind: "status", status: "running" });
514
- return;
515
- }
516
- if (method === "turn/completed") {
517
- this.currentTurnId = undefined;
518
- this.status = "idle";
519
- this.sendUpdate({ kind: "status", status: "idle" });
520
- return;
521
- }
522
- if (isPermissionRequestMethod(method)) {
523
- this.handlePermission(params, false, method);
524
- return;
525
- }
526
- switch (method) {
527
- case "item/agentMessage/delta":
528
- this.handleAgentMessageDelta(params);
529
- return;
530
- case "turn/plan/updated":
531
- this.handlePlanUpdated(params);
532
- return;
533
- case "item/plan/delta":
534
- this.handlePlanDelta(params);
535
- return;
536
- case "item/started":
537
- this.handleItemStarted(params);
538
- return;
539
- case "item/completed":
540
- this.handleItemCompleted(params);
541
- return;
542
- case "item/commandExecution/outputDelta":
543
- case "item/fileChange/outputDelta":
544
- case "item/mcpToolCall/progress":
545
- this.handleToolDelta(method, params);
546
- return;
547
- case "item/fileChange/patchUpdated":
548
- this.handleFilePatchUpdated(params);
549
- return;
550
- case "turn/diff/updated":
551
- this.handleTurnDiffUpdated(params);
552
- return;
553
- case "command/exec/outputDelta":
554
- this.handleCommandExecDelta(params);
555
- return;
556
- case "item/autoApprovalReview/started":
557
- case "item/autoApprovalReview/completed":
558
- case "item/commandExecution/terminalInteraction":
559
- return;
560
- }
561
- if (method === "session/update") {
562
- this.handleUpdate(params);
563
- return;
564
- }
565
- if (this.input.verbose) {
566
- process.stderr.write(`[agent] ignored ${method}\n`);
567
- }
568
- }
569
- handlePermission(params, waitForResponse, source) {
570
- const raw = typeof params === "object" && params ? params : {};
571
- const requestId = firstString(raw, ["requestId", "id", "permissionId"]) ?? id("perm");
572
- const rawToolCall = typeof raw.toolCall === "object" && raw.toolCall
573
- ? raw.toolCall
574
- : raw;
575
- const permission = {
576
- requestId,
577
- toolName: firstString(rawToolCall, ["toolName", "tool", "name", "title", "kind"]),
578
- toolInput: stringify(rawToolCall.input ?? rawToolCall.toolInput ?? rawToolCall),
579
- context: firstString(raw, ["context", "description", "message", "title"]),
580
- options: parsePermissionOptions(raw.options),
581
- };
582
- this.pendingPermissions.set(requestId, permission);
583
- if (source)
584
- this.permissionSources.set(requestId, source);
585
- this.status = "waiting_permission";
586
- this.input.send(createEnvelope({
587
- type: "agent.permission.request",
588
- sessionId: this.input.sessionId,
589
- payload: { agentSessionId: this.agentSessionId, ...permission },
590
- }));
591
- if (!waitForResponse)
592
- return;
593
- return new Promise((resolve) => {
594
- const timer = setTimeout(() => {
595
- this.pendingPermissions.delete(requestId);
596
- this.permissionWaiters.delete(requestId);
597
- this.permissionSources.delete(requestId);
598
- resolve(formatPermissionResponse(source, "cancelled", "cancelled"));
599
- this.sendSnapshot();
600
- }, PERMISSION_TIMEOUT_MS);
601
- this.permissionWaiters.set(requestId, { resolve, timer });
602
- });
603
- }
604
- handleAgentMessageDelta(params) {
605
- const raw = asRecord(params);
606
- if (!raw)
607
- return;
608
- const itemId = firstString(raw, ["itemId", "id", "messageId"]) ?? id("msg");
609
- const delta = firstString(raw, ["delta", "text", "content"]);
610
- if (!delta)
611
- return;
612
- const current = this.messages.find((message) => message.id === itemId);
613
- const message = {
614
- id: itemId,
615
- role: "assistant",
616
- content: `${current?.content ?? ""}${delta}`,
617
- createdAt: current?.createdAt ?? Date.now(),
618
- isStreaming: true,
619
- };
620
- this.upsertMessage(message);
621
- this.status = "running";
622
- this.sendUpdate({ kind: "message_delta", message, delta, status: "running" });
623
- }
624
- handlePlanUpdated(params) {
625
- const raw = asRecord(params);
626
- const plan = Array.isArray(raw?.plan) ? raw.plan : [];
627
- this.plan = plan
628
- .map((entry, index) => {
629
- const step = asRecord(entry);
630
- const text = firstString(step ?? {}, ["text", "title", "description", "message"]);
631
- if (!text)
632
- return undefined;
633
- return {
634
- id: firstString(step ?? {}, ["id"]) ?? `plan-${index + 1}`,
635
- text,
636
- status: normalizePlanStatus(step?.status),
637
- };
638
- })
639
- .filter((step) => Boolean(step));
640
- if (this.plan.length > 0) {
641
- this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
642
- }
643
- }
644
- handlePlanDelta(params) {
645
- const raw = asRecord(params);
646
- if (!raw)
647
- return;
648
- const itemId = firstString(raw, ["itemId", "id"]) ?? id("plan");
649
- const delta = firstString(raw, ["delta", "text"]);
650
- if (!delta)
651
- return;
652
- const prev = this.planDeltaBuffers.get(itemId) ?? "";
653
- // Codex sometimes re-sends each "delta" as the FULL plan-so-far (a
654
- // cumulative snapshot) instead of a true increment. Naive concatenation
655
- // then doubled the text inside a single plan step (the reported bug). If
656
- // the incoming chunk already contains the buffer as a prefix, treat it as a
657
- // snapshot and replace; otherwise append as a genuine increment.
658
- const text = delta.startsWith(prev) ? delta : `${prev}${delta}`;
659
- this.planDeltaBuffers.set(itemId, text);
660
- const existing = this.plan.findIndex((step) => step.id === itemId);
661
- const step = { id: itemId, text, status: "in_progress" };
662
- if (existing >= 0)
663
- this.plan[existing] = step;
664
- else
665
- this.plan.push(step);
666
- this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
667
- }
668
- handleItemStarted(params) {
669
- const item = extractItem(params);
670
- if (!item)
671
- return;
672
- const itemType = firstString(item, ["type"]);
673
- if (itemType === "agentMessage" || itemType === "assistantMessage") {
674
- this.handleCompletedMessageItem(item, true);
675
- return;
676
- }
677
- if (itemType === "plan") {
678
- const planStep = planStepFromItem(item);
679
- if (planStep) {
680
- const existing = this.plan.findIndex((step) => step.id === planStep.id);
681
- if (existing >= 0)
682
- this.plan[existing] = planStep;
683
- else
684
- this.plan.push(planStep);
685
- this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
686
- }
687
- return;
688
- }
689
- const toolCall = this.withToolCreatedAt(this.toolCallFromItem(item, "running"));
690
- if (!toolCall)
691
- return;
692
- this.toolCalls.set(toolCall.id, toolCall);
693
- this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
694
- }
695
- handleItemCompleted(params) {
696
- const item = extractItem(params);
697
- if (!item)
698
- return;
699
- const itemType = firstString(item, ["type"]);
700
- if (itemType === "agentMessage" || itemType === "assistantMessage") {
701
- this.handleCompletedMessageItem(item, false);
702
- return;
703
- }
704
- if (itemType === "plan") {
705
- const planStep = planStepFromItem(item);
706
- if (planStep) {
707
- const existing = this.plan.findIndex((step) => step.id === planStep.id);
708
- const completed = { ...planStep, status: "completed" };
709
- if (existing >= 0)
710
- this.plan[existing] = completed;
711
- else
712
- this.plan.push(completed);
713
- this.sendUpdate({ kind: "plan", plan: this.plan, status: this.status === "running" ? "running" : "idle" });
714
- }
715
- return;
716
- }
717
- const toolCall = this.withToolCreatedAt(this.toolCallFromItem(item, normalizeToolStatus(item.status, true)));
718
- if (!toolCall)
719
- return;
720
- const bufferedOutput = this.toolOutputBuffers.get(toolCall.id);
721
- if (bufferedOutput && !toolCall.output)
722
- toolCall.output = bufferedOutput;
723
- this.toolCalls.set(toolCall.id, toolCall);
724
- this.sendUpdate({ kind: "tool_result", toolCall, status: this.status === "running" ? "running" : "idle" });
725
- }
726
- handleToolDelta(method, params) {
727
- const raw = asRecord(params);
728
- if (!raw)
729
- return;
730
- const itemId = firstString(raw, ["itemId", "id", "toolCallId"]) ?? id("tool");
731
- const delta = firstString(raw, ["delta", "message", "text"]);
732
- if (!delta)
733
- return;
734
- const output = appendCapped(this.toolOutputBuffers.get(itemId), delta, 6000);
735
- this.toolOutputBuffers.set(itemId, output);
736
- const existing = this.toolCalls.get(itemId);
737
- const toolCall = {
738
- id: itemId,
739
- name: existing?.name ?? nameFromToolMethod(method),
740
- input: existing?.input,
741
- output,
742
- createdAt: existing?.createdAt ?? Date.now(),
743
- status: "running",
744
- };
745
- this.toolCalls.set(itemId, toolCall);
746
- this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
747
- }
748
- handleFilePatchUpdated(params) {
749
- const raw = asRecord(params);
750
- if (!raw)
751
- return;
752
- const itemId = firstString(raw, ["itemId", "id"]) ?? id("file");
753
- const changes = Array.isArray(raw.changes) ? raw.changes : [];
754
- const output = extractDiffText(raw) ?? summarizeFileChanges(changes);
755
- const existing = this.toolCalls.get(itemId);
756
- const toolCall = this.withToolCreatedAt({
757
- id: itemId,
758
- name: existing?.name ?? "文件修改",
759
- input: existing?.input,
760
- output: output || existing?.output,
761
- createdAt: existing?.createdAt ?? Date.now(),
762
- status: existing?.status ?? "running",
763
- });
764
- if (!toolCall)
765
- return;
766
- if (toolCall.id !== itemId)
767
- this.toolCalls.delete(itemId);
768
- this.toolCalls.set(toolCall.id, toolCall);
769
- this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
770
- }
771
- handleTurnDiffUpdated(params) {
772
- const raw = asRecord(params);
773
- if (!raw)
774
- return;
775
- const diff = extractDiffText(raw);
776
- if (!diff)
777
- return;
778
- const itemId = firstString(raw, ["itemId", "id", "turnId"]) ?? "workspace-diff";
779
- const changes = Array.isArray(raw.changes) ? raw.changes : [];
780
- const existing = this.toolCalls.get(itemId);
781
- const toolCall = this.withToolCreatedAt({
782
- id: itemId,
783
- name: existing?.name ?? "文件修改",
784
- input: existing?.input ?? summarizeFileChanges(changes),
785
- output: diff,
786
- createdAt: existing?.createdAt ?? Date.now(),
787
- status: existing?.status ?? "running",
788
- });
789
- if (!toolCall)
790
- return;
791
- if (toolCall.id !== itemId)
792
- this.toolCalls.delete(itemId);
793
- this.toolCalls.set(toolCall.id, toolCall);
794
- this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
795
- }
796
- handleCommandExecDelta(params) {
797
- const raw = asRecord(params);
798
- if (!raw)
799
- return;
800
- const processId = firstString(raw, ["processId", "id"]) ?? id("exec");
801
- const delta = firstString(raw, ["delta", "text"]) ??
802
- decodeBase64(firstString(raw, ["deltaBase64"]));
803
- if (!delta)
804
- return;
805
- const output = appendCapped(this.toolOutputBuffers.get(processId), delta, 6000);
806
- this.toolOutputBuffers.set(processId, output);
807
- const existing = this.toolCalls.get(processId);
808
- const toolCall = {
809
- id: processId,
810
- name: existing?.name ?? "命令输出",
811
- input: existing?.input,
812
- output,
813
- createdAt: existing?.createdAt ?? Date.now(),
814
- status: "running",
815
- };
816
- this.toolCalls.set(processId, toolCall);
817
- this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
818
- }
819
- handleCompletedMessageItem(item, streaming) {
820
- const itemId = firstString(item, ["id"]) ?? id("msg");
821
- const existing = this.messages.find((message) => message.id === itemId);
822
- const content = firstString(item, ["text", "content", "message"]) ?? existing?.content;
823
- if (!content)
824
- return;
825
- const message = {
826
- id: itemId,
827
- role: "assistant",
828
- content,
829
- createdAt: existing?.createdAt ?? Date.now(),
830
- isStreaming: streaming,
831
- };
832
- this.upsertMessage(message);
833
- this.sendUpdate({
834
- kind: streaming ? "message_delta" : "message",
835
- message,
836
- status: this.status === "running" ? "running" : "idle",
837
- });
838
- }
839
- toolCallFromItem(item, fallbackStatus) {
840
- const itemId = firstString(item, ["id", "itemId", "toolCallId"]);
841
- if (!itemId)
842
- return undefined;
843
- const itemType = firstString(item, ["type"]);
844
- const name = toolNameFromItem(item);
845
- if (!name && !isToolItemType(itemType))
846
- return undefined;
847
- const bufferedOutput = this.toolOutputBuffers.get(itemId);
848
- const rawOutput = firstString(item, ["aggregatedOutput", "output", "stdout", "stderr"]) ??
849
- stringifyDefined(item.result ?? item.error ?? item.contentItems);
850
- const output = itemType === "fileChange"
851
- ? extractDiffText(item) ?? bufferedOutput ?? rawOutput
852
- : rawOutput ?? bufferedOutput;
853
- return {
854
- id: itemId,
855
- name: name ?? "工具",
856
- input: toolInputFromItem(item),
857
- output,
858
- createdAt: Date.now(),
859
- status: normalizeToolStatus(item.status, fallbackStatus === "completed"),
860
- };
861
- }
862
- handleUpdate(params) {
863
- const raw = typeof params === "object" && params ? params : {};
864
- const nested = typeof raw.params === "object" && raw.params ? raw.params : {};
865
- const text = firstString(raw, ["delta", "text", "content", "message"]) ??
866
- firstString(nested, ["delta", "text", "content", "message"]) ??
867
- undefined;
868
- if (!text)
869
- return;
870
- const role = raw.role === "user" || raw.role === "system" ? raw.role : "assistant";
871
- if (firstString(raw, ["toolName", "tool", "name"])) {
872
- const toolCall = {
873
- id: firstString(raw, ["toolCallId", "callId", "id"]) ?? id("tool"),
874
- name: firstString(raw, ["toolName", "tool", "name"]) ?? "tool",
875
- input: stringify(raw.input ?? raw.toolInput ?? ""),
876
- output: stringify(raw.output ?? raw.result ?? ""),
877
- createdAt: Date.now(),
878
- status: raw.status === "completed" || raw.status === "failed" || raw.status === "running"
879
- ? raw.status
880
- : "running",
881
- };
882
- const nextToolCall = this.withToolCreatedAt(toolCall);
883
- if (!nextToolCall)
884
- return;
885
- this.toolCalls.set(nextToolCall.id, nextToolCall);
886
- this.sendUpdate({ kind: "tool_call", toolCall: nextToolCall, status: "running" });
887
- return;
888
- }
889
- const message = {
890
- id: firstString(raw, ["messageId", "id"]) ?? id("msg"),
891
- role,
892
- content: text,
893
- createdAt: Date.now(),
894
- isStreaming: raw.done === false || raw.isStreaming === true,
895
- };
896
- this.upsertMessage(message);
897
- this.status = raw.done === true ? "idle" : "running";
898
- this.sendUpdate({
899
- kind: "message",
900
- message,
901
- status: this.status === "running" ? "running" : "idle",
902
- });
903
- }
904
- handleExit(message) {
905
- this.cancelPendingPermissions();
906
- this.status = "error";
907
- this.error = message;
908
- this.client = undefined;
909
- this.activeProtocol = undefined;
910
- this.sendUpdate({ kind: "error", error: message, status: "error" });
911
- }
912
- cancelPendingPermissions() {
913
- for (const [requestId, waiter] of this.permissionWaiters) {
914
- clearTimeout(waiter.timer);
915
- waiter.resolve(formatPermissionResponse(this.permissionSources.get(requestId), "cancelled", "cancelled"));
916
- this.pendingPermissions.delete(requestId);
917
- this.permissionSources.delete(requestId);
918
- }
919
- this.permissionWaiters.clear();
920
- }
921
- upsertMessage(message) {
922
- const existing = this.messages.findIndex((entry) => entry.id === message.id);
923
- if (existing >= 0)
924
- this.messages[existing] = message;
925
- else
926
- this.messages.push(message);
927
- if (this.messages.length > 100)
928
- this.messages.shift();
929
- }
930
- withToolCreatedAt(toolCall) {
931
- if (!toolCall)
932
- return undefined;
933
- const duplicate = this.findDuplicateFileTool(toolCall);
934
- const existing = duplicate ?? this.toolCalls.get(toolCall.id);
935
- return {
936
- ...existing,
937
- ...toolCall,
938
- id: existing?.id ?? toolCall.id,
939
- createdAt: existing?.createdAt ?? toolCall.createdAt ?? Date.now(),
940
- };
941
- }
942
- findDuplicateFileTool(toolCall) {
943
- const output = toolCall.output?.trim();
944
- if (!toolCall.name.includes("文件") || !output)
945
- return undefined;
946
- return [...this.toolCalls.values()].find((existing) => existing.id !== toolCall.id &&
947
- existing.name.includes("文件") &&
948
- existing.output?.trim() === output);
949
- }
950
- sendCapabilities() {
951
- const enabled = Boolean(this.client && this.initialized && !this.error);
952
- const activeProvider = this.activeProvider ?? this.input.availableProviders[0];
953
- const supportsPermission = enabled && this.activeProtocol !== "claude-stream-json";
954
- this.input.send(createEnvelope({
955
- type: "agent.capabilities",
956
- sessionId: this.input.sessionId,
957
- payload: {
958
- enabled,
959
- provider: activeProvider ?? "codex",
960
- protocolVersion: 1,
961
- error: enabled ? undefined : this.error,
962
- supportsSessionList: enabled,
963
- supportsSessionLoad: enabled,
964
- supportsImages: enabled && protocolSupportsImages(this.activeProtocol),
965
- supportsAudio: false,
966
- supportsPermission,
967
- supportsPlan: enabled,
968
- supportsCancel: enabled,
969
- },
970
- }));
971
- }
972
- sendSnapshot() {
973
- this.input.send(createEnvelope({
974
- type: "agent.snapshot",
975
- sessionId: this.input.sessionId,
976
- payload: {
977
- agentSessionId: this.agentSessionId,
978
- messages: this.messages,
979
- toolCalls: [...this.toolCalls.values()],
980
- pendingPermissions: [...this.pendingPermissions.values()],
981
- status: this.status,
982
- error: this.error,
983
- },
984
- }));
985
- }
986
- sendUpdate(payload) {
987
- this.input.send(createEnvelope({
988
- type: "agent.update",
989
- sessionId: this.input.sessionId,
990
- payload: { agentSessionId: this.agentSessionId, ...payload },
991
- }));
992
- }
993
- extractSessionId(value) {
994
- if (!value || typeof value !== "object")
995
- return undefined;
996
- const raw = value;
997
- if (raw.thread && typeof raw.thread === "object") {
998
- const threadId = firstString(raw.thread, ["id", "threadId"]);
999
- if (threadId)
1000
- return threadId;
1001
- }
1002
- return firstString(raw, ["sessionId", "id", "agentSessionId"]);
1003
- }
1004
- extractTurnId(value) {
1005
- if (!value || typeof value !== "object")
1006
- return undefined;
1007
- const raw = value;
1008
- if (raw.turn && typeof raw.turn === "object") {
1009
- const turnId = firstString(raw.turn, ["id", "turnId"]);
1010
- if (turnId)
1011
- return turnId;
1012
- }
1013
- return firstString(raw, ["turnId", "id"]);
1014
- }
1015
- }
1016
- function parsePermissionOptions(value) {
1017
- if (!Array.isArray(value)) {
1018
- return [
1019
- { id: "allow", label: "允许", kind: "allow" },
1020
- { id: "deny", label: "拒绝", kind: "deny" },
1021
- ];
1022
- }
1023
- const options = value
1024
- .map((entry, index) => {
1025
- const raw = typeof entry === "object" && entry ? entry : {};
1026
- const idValue = raw.optionId ?? raw.id ?? raw.kind ?? `option-${index + 1}`;
1027
- const labelValue = raw.name ?? raw.label ?? raw.kind ?? String(idValue);
1028
- const id = String(idValue);
1029
- const label = String(labelValue);
1030
- const normalized = `${id} ${label}`.toLowerCase();
1031
- const kind = normalized.includes("reject") || normalized.includes("deny")
1032
- ? "deny"
1033
- : normalized.includes("allow")
1034
- ? "allow"
1035
- : "other";
1036
- return { id, label, kind };
1037
- })
1038
- .filter((option) => option.id.length > 0 && option.label.length > 0);
1039
- return options.length > 0
1040
- ? options
1041
- : [
1042
- { id: "allow", label: "允许", kind: "allow" },
1043
- { id: "deny", label: "拒绝", kind: "deny" },
1044
- ];
1045
- }
1046
- function selectPermissionOption(permission, outcome) {
1047
- if (outcome === "cancelled")
1048
- return "cancelled";
1049
- const option = permission?.options.find((item) => item.kind === outcome);
1050
- return option?.id ?? outcome;
1051
- }
1052
- function isPermissionRequestMethod(method) {
1053
- return (method === "session/request_permission" ||
1054
- method.endsWith("/requestApproval") ||
1055
- method === "mcpServer/elicitation/request" ||
1056
- method === "item/tool/requestUserInput" ||
1057
- method === "claude/requestApproval");
1058
- }
1059
- function formatPermissionResponse(source, outcome, optionId) {
1060
- if (source === "item/commandExecution/requestApproval" || source === "item/fileChange/requestApproval") {
1061
- return { decision: outcome === "allow" ? "accept" : outcome === "deny" ? "decline" : "cancel" };
1062
- }
1063
- if (source === "item/permissions/requestApproval") {
1064
- if (outcome === "allow") {
1065
- return {
1066
- permissions: { type: "managed", network: { enabled: true }, fileSystem: { type: "fullAccess" } },
1067
- scope: optionId.includes("session") ? "session" : "turn",
1068
- };
1069
- }
1070
- return { permissions: { type: "managed", network: { enabled: false }, fileSystem: { type: "readOnly" } } };
1071
- }
1072
- if (source === "claude/requestApproval") {
1073
- return { behavior: outcome === "allow" ? "allow" : "deny" };
1074
- }
1075
- return {
1076
- outcome: outcome === "cancelled"
1077
- ? { outcome: "cancelled" }
1078
- : { outcome: "selected", optionId },
1079
- };
1080
- }
1081
- //# sourceMappingURL=agent-session.js.map