linkshell-cli 0.9.0 → 0.10.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.
Files changed (153) 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/fs.d.ts +3 -3
  42. package/dist/host/src/input.d.ts +4 -4
  43. package/dist/host/src/terminals.d.ts +1 -1
  44. package/dist/wire/src/model.d.ts +6 -6
  45. package/dist/wire/src/relay.d.ts +8 -8
  46. package/dist/wire/src/rpc.d.ts +818 -818
  47. package/dist/wire/src/updates.d.ts +18 -18
  48. package/package.json +4 -13
  49. package/src/commands/gateway.ts +31 -0
  50. package/src/commands/host.ts +13 -2
  51. package/src/commands/setup.ts +4 -2
  52. package/src/index.ts +14 -429
  53. package/src/utils/daemon.ts +2 -27
  54. package/dist/cli/src/config.d.ts +0 -14
  55. package/dist/cli/src/config.js +0 -23
  56. package/dist/cli/src/config.js.map +0 -1
  57. package/dist/cli/src/machine-id.d.ts +0 -6
  58. package/dist/cli/src/machine-id.js +0 -50
  59. package/dist/cli/src/machine-id.js.map +0 -1
  60. package/dist/cli/src/providers.d.ts +0 -13
  61. package/dist/cli/src/providers.js +0 -77
  62. package/dist/cli/src/providers.js.map +0 -1
  63. package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
  64. package/dist/cli/src/runtime/acp/acp-client.js +0 -430
  65. package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
  66. package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
  67. package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
  68. package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
  69. package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
  70. package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
  71. package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
  72. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
  73. package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
  74. package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
  75. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
  76. package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
  77. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
  78. package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
  79. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
  80. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
  81. package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
  82. package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
  83. package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
  84. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
  85. package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
  86. package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
  87. package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
  88. package/dist/cli/src/runtime/bridge-session.js +0 -1364
  89. package/dist/cli/src/runtime/bridge-session.js.map +0 -1
  90. package/dist/cli/src/runtime/hook-command.d.ts +0 -9
  91. package/dist/cli/src/runtime/hook-command.js +0 -80
  92. package/dist/cli/src/runtime/hook-command.js.map +0 -1
  93. package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
  94. package/dist/cli/src/runtime/screen-fallback.js +0 -202
  95. package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
  96. package/dist/cli/src/runtime/screen-share.d.ts +0 -34
  97. package/dist/cli/src/runtime/screen-share.js +0 -376
  98. package/dist/cli/src/runtime/screen-share.js.map +0 -1
  99. package/dist/cli/src/runtime/scrollback.d.ts +0 -11
  100. package/dist/cli/src/runtime/scrollback.js +0 -33
  101. package/dist/cli/src/runtime/scrollback.js.map +0 -1
  102. package/dist/cli/src/runtime/usage-report.d.ts +0 -34
  103. package/dist/cli/src/runtime/usage-report.js +0 -332
  104. package/dist/cli/src/runtime/usage-report.js.map +0 -1
  105. package/dist/cli/src/utils/keep-awake.d.ts +0 -6
  106. package/dist/cli/src/utils/keep-awake.js +0 -48
  107. package/dist/cli/src/utils/keep-awake.js.map +0 -1
  108. package/dist/cli/src/utils/lan-ip.d.ts +0 -9
  109. package/dist/cli/src/utils/lan-ip.js +0 -61
  110. package/dist/cli/src/utils/lan-ip.js.map +0 -1
  111. package/dist/host/src/input-helper.d.ts +0 -1
  112. package/dist/host/src/input-helper.js +0 -564
  113. package/dist/host/src/input-helper.js.map +0 -1
  114. package/dist/shared-protocol/src/index.d.ts +0 -17029
  115. package/dist/shared-protocol/src/index.js +0 -1064
  116. package/dist/shared-protocol/src/index.js.map +0 -1
  117. package/scripts/copy-web.mjs +0 -34
  118. package/src/config.ts +0 -36
  119. package/src/machine-id.ts +0 -61
  120. package/src/providers.ts +0 -97
  121. package/src/runtime/acp/acp-client.ts +0 -521
  122. package/src/runtime/acp/agent-process-discovery.ts +0 -308
  123. package/src/runtime/acp/agent-session.ts +0 -1186
  124. package/src/runtime/acp/agent-workspace.ts +0 -6590
  125. package/src/runtime/acp/claude-sdk-client.ts +0 -1221
  126. package/src/runtime/acp/claude-stream-json-client.ts +0 -633
  127. package/src/runtime/acp/json-rpc.ts +0 -184
  128. package/src/runtime/acp/provider-resolver.ts +0 -154
  129. package/src/runtime/bridge-session.ts +0 -1549
  130. package/src/runtime/hook-command.ts +0 -73
  131. package/src/runtime/screen-fallback.ts +0 -235
  132. package/src/runtime/screen-share.ts +0 -426
  133. package/src/runtime/scrollback.ts +0 -43
  134. package/src/runtime/usage-report.ts +0 -342
  135. package/src/utils/keep-awake.ts +0 -61
  136. package/src/utils/lan-ip.ts +0 -61
  137. package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
  138. package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
  139. package/web/assets/TerminalPanel-CTSklkct.js +0 -1
  140. package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
  141. package/web/assets/index-BsnDaeCt.css +0 -33
  142. package/web/assets/index-q85psYZ5.js +0 -36
  143. package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
  144. package/web/assets/vendor-react-M7xG3qiw.js +0 -49
  145. package/web/assets/vendor-xterm-D15THcI4.js +0 -9
  146. package/web/index.html +0 -37
  147. package/web/logos/claude.png +0 -0
  148. package/web/logos/codex.png +0 -0
  149. package/web/logos/copilot.png +0 -0
  150. package/web/logos/gemini.png +0 -0
  151. package/web/logos/linkshell-1024.png +0 -0
  152. package/web/logos/linkshell-splash.png +0 -0
  153. 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