linkshell-cli 0.3.28 → 0.4.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 (62) hide show
  1. package/dist/cli/src/commands/setup.js +20 -2
  2. package/dist/cli/src/commands/setup.js.map +1 -1
  3. package/dist/cli/src/commands/upgrade.js +9 -24
  4. package/dist/cli/src/commands/upgrade.js.map +1 -1
  5. package/dist/cli/src/index.js +28 -26
  6. package/dist/cli/src/index.js.map +1 -1
  7. package/dist/cli/src/providers.d.ts +3 -7
  8. package/dist/cli/src/providers.js +76 -19
  9. package/dist/cli/src/providers.js.map +1 -1
  10. package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -10
  11. package/dist/cli/src/runtime/acp/acp-client.js +20 -146
  12. package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
  13. package/dist/cli/src/runtime/acp/agent-session.d.ts +62 -0
  14. package/dist/cli/src/runtime/acp/agent-session.js +1075 -0
  15. package/dist/cli/src/runtime/acp/agent-session.js.map +1 -0
  16. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +2 -27
  17. package/dist/cli/src/runtime/acp/agent-workspace.js +224 -1008
  18. package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
  19. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +1 -3
  20. package/dist/cli/src/runtime/acp/claude-sdk-client.js +50 -82
  21. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
  22. package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +1 -2
  23. package/dist/cli/src/runtime/acp/claude-stream-json-client.js +36 -54
  24. package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -1
  25. package/dist/cli/src/runtime/acp/json-rpc.d.ts +1 -34
  26. package/dist/cli/src/runtime/acp/json-rpc.js +8 -32
  27. package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
  28. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -3
  29. package/dist/cli/src/runtime/acp/provider-resolver.js +6 -26
  30. package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
  31. package/dist/cli/src/runtime/bridge-session.d.ts +31 -1
  32. package/dist/cli/src/runtime/bridge-session.js +991 -81
  33. package/dist/cli/src/runtime/bridge-session.js.map +1 -1
  34. package/dist/cli/src/runtime/screen-fallback.d.ts +1 -1
  35. package/dist/cli/src/runtime/screen-fallback.js +4 -4
  36. package/dist/cli/src/runtime/screen-fallback.js.map +1 -1
  37. package/dist/cli/src/runtime/screen-share.d.ts +1 -1
  38. package/dist/cli/src/runtime/screen-share.js +7 -7
  39. package/dist/cli/src/runtime/screen-share.js.map +1 -1
  40. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  41. package/dist/shared-protocol/src/index.d.ts +11978 -3423
  42. package/dist/shared-protocol/src/index.js +114 -163
  43. package/dist/shared-protocol/src/index.js.map +1 -1
  44. package/package.json +13 -13
  45. package/src/commands/setup.ts +31 -5
  46. package/src/commands/upgrade.ts +8 -28
  47. package/src/index.ts +34 -29
  48. package/src/providers.ts +108 -26
  49. package/src/runtime/acp/acp-client.ts +22 -159
  50. package/src/runtime/acp/agent-session.ts +1180 -0
  51. package/src/runtime/acp/agent-workspace.ts +246 -1094
  52. package/src/runtime/acp/claude-sdk-client.ts +51 -86
  53. package/src/runtime/acp/claude-stream-json-client.ts +42 -59
  54. package/src/runtime/acp/json-rpc.ts +14 -46
  55. package/src/runtime/acp/provider-resolver.ts +6 -29
  56. package/src/runtime/bridge-session.ts +1092 -84
  57. package/src/runtime/screen-fallback.ts +5 -5
  58. package/src/runtime/screen-share.ts +8 -8
  59. package/src/runtime/acp/claude-sessions.ts +0 -563
  60. package/src/runtime/acp/codex-rpc-bridge.ts +0 -233
  61. package/src/runtime/acp/codex-sessions.ts +0 -802
  62. package/src/types/linkshell-gateway.d.ts +0 -18
@@ -1,9 +1,9 @@
1
- import { isAbsolute, relative, resolve } from "node:path";
2
- import { listClaudeStoredSessions } from "./claude-sessions.js";
1
+ import { homedir } from "node:os";
2
+ import { readdirSync, existsSync, readFileSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
4
  import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
4
5
 
5
6
  type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
6
- type AgentCollaborationMode = "default" | "plan";
7
7
 
8
8
  interface ClaudeContentBlock {
9
9
  type?: string;
@@ -37,13 +37,21 @@ type AgentInputContentBlock = {
37
37
  text?: string;
38
38
  data?: string;
39
39
  mimeType?: string;
40
- path?: string;
41
40
  };
42
41
 
43
42
  function id(prefix: string): string {
44
43
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
45
44
  }
46
45
 
46
+ function projectHash(cwd: string): string {
47
+ return (
48
+ "-" +
49
+ resolve(cwd)
50
+ .replace(/\/$/, "")
51
+ .replace(/\//g, "-")
52
+ );
53
+ }
54
+
47
55
  function extractToolResultText(content: unknown): string {
48
56
  if (typeof content === "string") return content;
49
57
  if (Array.isArray(content)) {
@@ -86,9 +94,6 @@ function toClaudeMessageContent(blocks: AgentInputContentBlock[]): Record<string
86
94
  },
87
95
  };
88
96
  }
89
- if (block.type === "file" && block.path) {
90
- return { type: "text", text: block.text ?? `@${block.path}` };
91
- }
92
97
  return { type: "text", text: block.text ?? "" };
93
98
  })
94
99
  .filter((block) =>
@@ -135,42 +140,6 @@ function isInsideCwd(cwd: string, candidate: string): boolean {
135
140
  return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
136
141
  }
137
142
 
138
- function samePath(left: string, right: string): boolean {
139
- return resolve(left) === resolve(right);
140
- }
141
-
142
- function isClaudeFileTool(toolName: string): boolean {
143
- return toolName === "Write" ||
144
- toolName === "Edit" ||
145
- toolName === "MultiEdit" ||
146
- toolName === "NotebookEdit";
147
- }
148
-
149
- function filePathFromToolInput(toolInput: unknown): string | undefined {
150
- return stringField(toolInput, ["file_path", "path", "notebook_path"]);
151
- }
152
-
153
- function fileChangeKind(toolName: string): string {
154
- return toolName === "Write" ? "create" : "update";
155
- }
156
-
157
- function claudeEffort(value: string | undefined): "low" | "medium" | "high" | "xhigh" | undefined {
158
- if (value === "low" || value === "medium" || value === "high" || value === "xhigh") return value;
159
- if (value === "minimal") return "low";
160
- return undefined;
161
- }
162
-
163
- function claudePermissionMode(
164
- permissionMode: AgentPermissionMode | undefined,
165
- collaborationMode: AgentCollaborationMode | undefined,
166
- ): "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" | undefined {
167
- if (collaborationMode === "plan") return "plan";
168
- if (permissionMode === "full_access") return "bypassPermissions";
169
- if (permissionMode === "workspace_write") return "acceptEdits";
170
- if (permissionMode === "read_only") return "dontAsk";
171
- return undefined;
172
- }
173
-
174
143
  function outcomeFromPermissionResponse(value: unknown): "allow" | "deny" {
175
144
  const raw = value && typeof value === "object" ? value as Record<string, unknown> : {};
176
145
  if (raw.behavior === "allow") return "allow";
@@ -228,11 +197,6 @@ export class ClaudeSdkClient {
228
197
  }
229
198
 
230
199
  async loadSession(input: { sessionId: string; cwd: string; mcpServers?: unknown }): Promise<unknown> {
231
- if (!isRealClaudeSessionId(input.sessionId)) {
232
- this.claudeSessionId = undefined;
233
- return { sessionId: undefined, status: "ready", cwd: input.cwd };
234
- }
235
- this.assertResumeCwd(input.sessionId, input.cwd);
236
200
  this.claudeSessionId = input.sessionId;
237
201
  return { sessionId: input.sessionId, status: "loaded", cwd: input.cwd };
238
202
  }
@@ -244,15 +208,11 @@ export class ClaudeSdkClient {
244
208
  model?: string;
245
209
  reasoningEffort?: string;
246
210
  permissionMode?: AgentPermissionMode;
247
- collaborationMode?: AgentCollaborationMode;
211
+ collaborationMode?: "default" | "plan";
248
212
  cwd: string;
249
213
  }): Promise<unknown> {
250
214
  if (!this.query) await this.initialize();
251
215
  if (!this.query) throw new Error("Claude Agent SDK is not initialized");
252
- const resumeSessionId = isRealClaudeSessionId(input.sessionId ?? this.claudeSessionId)
253
- ? input.sessionId ?? this.claudeSessionId
254
- : undefined;
255
- if (resumeSessionId) this.assertResumeCwd(resumeSessionId, input.cwd ?? this.input.cwd);
256
216
 
257
217
  this.abortController?.abort();
258
218
  const abortController = new AbortController();
@@ -263,7 +223,6 @@ export class ClaudeSdkClient {
263
223
  const prompt = inputBlocks
264
224
  .map((block) => {
265
225
  if (block.type === "image") return `[${block.mimeType ?? "image"} attachment]`;
266
- if (block.type === "file" && block.path) return block.text ?? `@${block.path}`;
267
226
  return block.text ?? "";
268
227
  })
269
228
  .filter(Boolean)
@@ -277,11 +236,11 @@ export class ClaudeSdkClient {
277
236
  if (["Read", "Glob", "Grep", "LS", "NotebookRead", "TodoRead"].includes(toolName)) {
278
237
  return { behavior: "allow" };
279
238
  }
280
- if (input.permissionMode === "read_only" && ["Write", "Edit", "MultiEdit", "NotebookEdit", "Bash"].includes(toolName)) {
239
+ if (input.permissionMode === "read_only" && ["Write", "Edit", "MultiEdit", "Bash"].includes(toolName)) {
281
240
  return { behavior: "deny", message: "Read-only mode is active." };
282
241
  }
283
- if (input.permissionMode === "workspace_write" && isClaudeFileTool(toolName)) {
284
- const filePath = filePathFromToolInput(toolInput);
242
+ if (input.permissionMode === "workspace_write" && ["Write", "Edit", "MultiEdit"].includes(toolName)) {
243
+ const filePath = stringField(toolInput, ["file_path", "path", "notebook_path"]);
285
244
  if (filePath && isInsideCwd(input.cwd ?? this.input.cwd, filePath)) {
286
245
  return { behavior: "allow" };
287
246
  }
@@ -304,15 +263,8 @@ export class ClaudeSdkClient {
304
263
  },
305
264
  };
306
265
  if (input.model) sdkOptions.model = input.model;
307
- const effort = claudeEffort(input.reasoningEffort);
308
- if (effort) sdkOptions.effort = effort;
309
- const permissionMode = claudePermissionMode(input.permissionMode, input.collaborationMode);
310
- if (permissionMode) {
311
- sdkOptions.permissionMode = permissionMode;
312
- if (permissionMode === "bypassPermissions") sdkOptions.allowDangerouslySkipPermissions = true;
313
- }
314
- if (resumeSessionId) {
315
- sdkOptions.resume = resumeSessionId;
266
+ if (isRealClaudeSessionId(input.sessionId ?? this.claudeSessionId)) {
267
+ sdkOptions.resume = input.sessionId ?? this.claudeSessionId;
316
268
  }
317
269
 
318
270
  const toolNames = new Map<string, string>();
@@ -381,14 +333,42 @@ export class ClaudeSdkClient {
381
333
  }
382
334
 
383
335
  async listSessions(): Promise<unknown> {
384
- return listClaudeStoredSessions(this.input.cwd);
336
+ const projectDir = join(homedir(), ".claude", "projects", projectHash(this.input.cwd));
337
+ if (!existsSync(projectDir)) return { sessions: [] };
338
+ const sessions: Array<{ id: string; cwd: string; lastModified: number }> = [];
339
+ try {
340
+ for (const entry of readdirSync(projectDir)) {
341
+ if (entry.endsWith(".jsonl")) {
342
+ sessions.push({
343
+ id: entry.replace(".jsonl", ""),
344
+ cwd: this.input.cwd,
345
+ lastModified: 0,
346
+ });
347
+ }
348
+ }
349
+ } catch {
350
+ // Ignore unreadable Claude project storage.
351
+ }
352
+ return { sessions };
385
353
  }
386
354
 
387
355
  async listModels(): Promise<unknown> {
356
+ let defaultModel: string = "default";
357
+ try {
358
+ const settingsPath = join(homedir(), ".claude", "settings.json");
359
+ if (existsSync(settingsPath)) {
360
+ const raw = JSON.parse(readFileSync(settingsPath, "utf8")) as { model?: unknown };
361
+ if (typeof raw.model === "string" && raw.model.trim().length > 0) {
362
+ defaultModel = raw.model.trim();
363
+ }
364
+ }
365
+ } catch {
366
+ // Fallback to "default" if settings file is unreadable.
367
+ }
388
368
  return {
389
- defaultModel: "default",
390
- reasoningEfforts: ["low", "medium", "high", "xhigh"],
369
+ defaultModel,
391
370
  models: [
371
+ { id: "default", label: "默认模型" },
392
372
  { id: "sonnet", label: "Sonnet" },
393
373
  { id: "opus", label: "Opus" },
394
374
  { id: "haiku", label: "Haiku" },
@@ -462,17 +442,10 @@ export class ClaudeSdkClient {
462
442
  sessionId: this.claudeSessionId,
463
443
  item: {
464
444
  id: toolId,
465
- type: toolName === "Bash" ? "commandExecution" : isClaudeFileTool(toolName) ? "fileChange" : "toolCall",
445
+ type: toolName === "Bash" ? "commandExecution" : toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit" ? "fileChange" : "toolCall",
466
446
  toolName,
467
447
  tool: toolName,
468
448
  input: block.input,
469
- path: isClaudeFileTool(toolName) ? filePathFromToolInput(block.input) : undefined,
470
- changes: isClaudeFileTool(toolName)
471
- ? [{
472
- path: filePathFromToolInput(block.input),
473
- kind: fileChangeKind(toolName),
474
- }].filter((entry) => Boolean(entry.path))
475
- : undefined,
476
449
  command: block.input?.command as string | undefined,
477
450
  cwd: block.input?.cwd as string | undefined ?? state.cwd,
478
451
  status: "running",
@@ -501,7 +474,7 @@ export class ClaudeSdkClient {
501
474
  sessionId: this.claudeSessionId,
502
475
  item: {
503
476
  id: toolId ?? id("tool"),
504
- type: toolName === "Bash" ? "commandExecution" : toolName && isClaudeFileTool(toolName) ? "fileChange" : "toolCall",
477
+ type: "toolCall",
505
478
  toolName,
506
479
  tool: toolName,
507
480
  status: block.is_error ? "failed" : "completed",
@@ -526,12 +499,4 @@ export class ClaudeSdkClient {
526
499
  }
527
500
  }
528
501
  }
529
-
530
- private assertResumeCwd(sessionId: string, cwd: string): void {
531
- const stored = listClaudeStoredSessions(cwd).sessions.find((session) => session.id === sessionId);
532
- if (!stored || samePath(stored.cwd, cwd)) return;
533
- throw new Error(
534
- `Claude session ${sessionId} belongs to ${stored.cwd}. Reopen that workspace or start a new Claude session from ${cwd}.`,
535
- );
536
- }
537
502
  }
@@ -1,17 +1,17 @@
1
1
  import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import { createInterface } from "node:readline";
3
- import { listClaudeStoredSessions } from "./claude-sessions.js";
3
+ import { homedir } from "node:os";
4
+ import { readdirSync, existsSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
4
6
  import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
5
7
 
6
8
  type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
7
- type AgentCollaborationMode = "default" | "plan";
8
9
 
9
10
  type AgentInputContentBlock = {
10
11
  type?: string;
11
12
  text?: string;
12
13
  data?: string;
13
14
  mimeType?: string;
14
- path?: string;
15
15
  };
16
16
 
17
17
  interface ClaudeStreamEvent {
@@ -37,6 +37,16 @@ interface ClaudeContentBlock {
37
37
  signature?: string;
38
38
  }
39
39
 
40
+ // Hash a directory path the same way Claude Code does for project storage
41
+ function projectHash(cwd: string): string {
42
+ return (
43
+ "-" +
44
+ resolve(cwd)
45
+ .replace(/\/$/, "")
46
+ .replace(/\//g, "-")
47
+ );
48
+ }
49
+
40
50
  function id(prefix: string): string {
41
51
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
42
52
  }
@@ -71,39 +81,6 @@ function splitImageDataUrl(value: string, fallbackMimeType = "image/png"): { dat
71
81
  };
72
82
  }
73
83
 
74
- function claudeEffort(value: string | undefined): "low" | "medium" | "high" | "xhigh" | undefined {
75
- if (value === "low" || value === "medium" || value === "high" || value === "xhigh") return value;
76
- if (value === "minimal") return "low";
77
- return undefined;
78
- }
79
-
80
- function claudePermissionMode(
81
- permissionMode: AgentPermissionMode | undefined,
82
- collaborationMode: AgentCollaborationMode | undefined,
83
- ): "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk" {
84
- if (collaborationMode === "plan") return "plan";
85
- if (permissionMode === "full_access") return "bypassPermissions";
86
- if (permissionMode === "workspace_write") return "acceptEdits";
87
- if (permissionMode === "read_only") return "dontAsk";
88
- return "default";
89
- }
90
-
91
- function isClaudeFileTool(toolName: string): boolean {
92
- return toolName === "Write" ||
93
- toolName === "Edit" ||
94
- toolName === "MultiEdit" ||
95
- toolName === "NotebookEdit";
96
- }
97
-
98
- function filePathFromToolInput(toolInput: Record<string, unknown> | undefined): string | undefined {
99
- const filePath = toolInput?.file_path ?? toolInput?.path ?? toolInput?.notebook_path;
100
- return typeof filePath === "string" && filePath.trim() ? filePath : undefined;
101
- }
102
-
103
- function fileChangeKind(toolName: string): string {
104
- return toolName === "Write" ? "create" : "update";
105
- }
106
-
107
84
  export class ClaudeStreamJsonClient {
108
85
  private child: ChildProcessWithoutNullStreams | undefined;
109
86
  private claudeSessionId: string | undefined;
@@ -153,7 +130,7 @@ export class ClaudeStreamJsonClient {
153
130
  model?: string;
154
131
  reasoningEffort?: string;
155
132
  permissionMode?: AgentPermissionMode;
156
- collaborationMode?: AgentCollaborationMode;
133
+ collaborationMode?: "default" | "plan";
157
134
  cwd: string;
158
135
  }): Promise<unknown> {
159
136
  if (this.child) {
@@ -168,11 +145,8 @@ export class ClaudeStreamJsonClient {
168
145
  "--output-format", "stream-json",
169
146
  "--input-format", "stream-json",
170
147
  "--verbose",
171
- "--permission-mode", claudePermissionMode(input.permissionMode, input.collaborationMode),
148
+ "--permission-mode", "bypassPermissions",
172
149
  ];
173
- if (input.permissionMode === "read_only") {
174
- args.push("--disallowedTools", "Bash,Write,Edit,MultiEdit,NotebookEdit");
175
- }
176
150
 
177
151
  // Use stored session for --resume (only when we have a real session ID from system.init)
178
152
  if (this.claudeSessionId) {
@@ -186,10 +160,6 @@ export class ClaudeStreamJsonClient {
186
160
  if (input.model) {
187
161
  args.push("--model", input.model);
188
162
  }
189
- const effort = claudeEffort(input.reasoningEffort);
190
- if (effort) {
191
- args.push("--effort", effort);
192
- }
193
163
 
194
164
  // Build the user message
195
165
  const contentBlocks = (input.content as AgentInputContentBlock[]).map(
@@ -198,9 +168,6 @@ export class ClaudeStreamJsonClient {
198
168
  const image = splitImageDataUrl(block.data, block.mimeType);
199
169
  return { type: "image", source: { type: "base64", media_type: image.mimeType, data: image.data } };
200
170
  }
201
- if (block.type === "file" && block.path) {
202
- return { type: "text", text: block.text ?? `@${block.path}` };
203
- }
204
171
  return { type: "text", text: block.text ?? "" };
205
172
  },
206
173
  );
@@ -352,17 +319,10 @@ export class ClaudeStreamJsonClient {
352
319
  sessionId: this.claudeSessionId,
353
320
  item: {
354
321
  id: block.id ?? id("tool"),
355
- type: toolName === "Bash" ? "commandExecution" : isClaudeFileTool(toolName) ? "fileChange" : "toolCall",
322
+ type: toolName === "Bash" ? "commandExecution" : toolName === "Write" || toolName === "Edit" ? "fileChange" : "toolCall",
356
323
  toolName: block.name,
357
324
  tool: block.name,
358
325
  input: block.input,
359
- path: isClaudeFileTool(toolName) ? filePathFromToolInput(block.input) : undefined,
360
- changes: isClaudeFileTool(toolName)
361
- ? [{
362
- path: filePathFromToolInput(block.input),
363
- kind: fileChangeKind(toolName),
364
- }].filter((entry) => Boolean(entry.path))
365
- : undefined,
366
326
  command: block.input?.command as string | undefined,
367
327
  cwd: block.input?.cwd as string | undefined ?? cwd,
368
328
  status: "running",
@@ -411,7 +371,7 @@ export class ClaudeStreamJsonClient {
411
371
  sessionId: this.claudeSessionId,
412
372
  item: {
413
373
  id: toolId ?? id("tool"),
414
- type: toolName === "Bash" ? "commandExecution" : toolName && isClaudeFileTool(toolName) ? "fileChange" : "toolCall",
374
+ type: "toolCall",
415
375
  toolName,
416
376
  tool: toolName,
417
377
  status: isError ? "failed" : "completed",
@@ -514,14 +474,37 @@ export class ClaudeStreamJsonClient {
514
474
  }
515
475
 
516
476
  async listSessions(): Promise<unknown> {
517
- return listClaudeStoredSessions(this.input.cwd);
477
+ const home = homedir();
478
+ const projectDir = join(home, ".claude", "projects", projectHash(this.input.cwd));
479
+
480
+ if (!existsSync(projectDir)) {
481
+ return { sessions: [] };
482
+ }
483
+
484
+ const sessions: Array<{ id: string; cwd: string; lastModified: number }> = [];
485
+ try {
486
+ for (const entry of readdirSync(projectDir)) {
487
+ if (entry.endsWith(".jsonl")) {
488
+ const sessionId = entry.replace(".jsonl", "");
489
+ sessions.push({
490
+ id: sessionId,
491
+ cwd: this.input.cwd,
492
+ lastModified: 0, // would need fs.statSync for accurate time
493
+ });
494
+ }
495
+ }
496
+ } catch {
497
+ // directory read failed
498
+ }
499
+
500
+ return { sessions };
518
501
  }
519
502
 
520
503
  async listModels(): Promise<unknown> {
521
504
  return {
522
505
  defaultModel: "default",
523
- reasoningEfforts: ["low", "medium", "high", "xhigh"],
524
506
  models: [
507
+ { id: "default", label: "默认模型" },
525
508
  { id: "sonnet", label: "Sonnet" },
526
509
  { id: "opus", label: "Opus" },
527
510
  { id: "haiku", label: "Haiku" },
@@ -1,33 +1,27 @@
1
1
  import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import type { AgentFraming } from "./provider-resolver.js";
3
3
 
4
- export interface JsonRpcRequest {
5
- jsonrpc?: "2.0";
4
+ interface JsonRpcRequest {
5
+ jsonrpc: "2.0";
6
6
  id: number | string;
7
7
  method: string;
8
8
  params?: unknown;
9
9
  }
10
10
 
11
- export interface JsonRpcResponse {
12
- jsonrpc?: "2.0";
11
+ interface JsonRpcResponse {
12
+ jsonrpc: "2.0";
13
13
  id: number | string;
14
14
  result?: unknown;
15
15
  error?: { code: number; message: string; data?: unknown };
16
16
  }
17
17
 
18
- export interface JsonRpcNotification {
19
- jsonrpc?: "2.0";
18
+ interface JsonRpcNotification {
19
+ jsonrpc: "2.0";
20
20
  method: string;
21
21
  params?: unknown;
22
22
  }
23
23
 
24
- export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
25
-
26
- interface JsonRpcTransportOptions {
27
- includeJsonrpc?: boolean;
28
- verbose?: boolean;
29
- onStderrData?: (message: string) => void;
30
- }
24
+ type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
31
25
 
32
26
  export class JsonRpcStdioTransport {
33
27
  private child: ChildProcessWithoutNullStreams | undefined;
@@ -48,8 +42,6 @@ export class JsonRpcStdioTransport {
48
42
  private readonly onNotification: (method: string, params: unknown) => void,
49
43
  private readonly onRequest: (method: string, params: unknown) => Promise<unknown> | unknown,
50
44
  private readonly onExit: (message: string) => void,
51
- private readonly onMessage?: (message: JsonRpcMessage) => boolean | void,
52
- private readonly options: JsonRpcTransportOptions = {},
53
45
  ) {}
54
46
 
55
47
  start(cwd: string): void {
@@ -65,10 +57,7 @@ export class JsonRpcStdioTransport {
65
57
  this.child.stdout.on("data", (chunk: string) => this.read(chunk));
66
58
  this.child.stderr.on("data", (chunk: string) => {
67
59
  const trimmed = chunk.trim();
68
- if (trimmed) {
69
- process.stderr.write(`[agent:stderr] ${trimmed}\n`);
70
- this.options.onStderrData?.(trimmed);
71
- }
60
+ if (trimmed) process.stderr.write(`[agent:stderr] ${trimmed}\n`);
72
61
  });
73
62
  this.child.on("error", (error) => this.failAll(error.message));
74
63
  this.child.on("exit", (code, signal) => {
@@ -81,7 +70,7 @@ export class JsonRpcStdioTransport {
81
70
  return Promise.reject(new Error("ACP agent is not running"));
82
71
  }
83
72
  const id = this.nextId++;
84
- const message: JsonRpcRequest = this.withJsonrpc({ id, method, params });
73
+ const message: JsonRpcRequest = { jsonrpc: "2.0", id, method, params };
85
74
  return new Promise((resolve, reject) => {
86
75
  const timer = timeoutMs && timeoutMs > 0
87
76
  ? setTimeout(() => {
@@ -95,11 +84,7 @@ export class JsonRpcStdioTransport {
95
84
  }
96
85
 
97
86
  notify(method: string, params?: unknown): void {
98
- this.write(this.withJsonrpc({ method, params }));
99
- }
100
-
101
- send(message: JsonRpcMessage): void {
102
- this.write(this.normalizeOutgoing(message));
87
+ this.write({ jsonrpc: "2.0", method, params });
103
88
  }
104
89
 
105
90
  stop(): void {
@@ -109,17 +94,6 @@ export class JsonRpcStdioTransport {
109
94
  this.failAll("ACP transport stopped");
110
95
  }
111
96
 
112
- private normalizeOutgoing<T extends JsonRpcMessage>(message: T): T {
113
- const includeJsonrpc = this.options.includeJsonrpc ?? true;
114
- if (includeJsonrpc) return { jsonrpc: "2.0", ...message } as T;
115
- const { jsonrpc: _jsonrpc, ...rest } = message;
116
- return rest as T;
117
- }
118
-
119
- private withJsonrpc<T extends JsonRpcMessage>(message: T): T {
120
- return this.normalizeOutgoing(message);
121
- }
122
-
123
97
  private write(message: JsonRpcMessage): void {
124
98
  const raw = JSON.stringify(message);
125
99
  if (this.framing === "newline") {
@@ -159,13 +133,6 @@ export class JsonRpcStdioTransport {
159
133
  try {
160
134
  message = JSON.parse(raw) as JsonRpcMessage;
161
135
  } catch {
162
- if (this.options.verbose) {
163
- process.stderr.write(`[agent:rpc] ignored invalid JSON: ${raw.slice(0, 200)}\n`);
164
- }
165
- return;
166
- }
167
-
168
- if (this.onMessage?.(message)) {
169
136
  return;
170
137
  }
171
138
 
@@ -182,15 +149,16 @@ export class JsonRpcStdioTransport {
182
149
 
183
150
  if ("method" in message && "id" in message) {
184
151
  Promise.resolve(this.onRequest(message.method, message.params))
185
- .then((result) => this.write(this.withJsonrpc({ id: message.id, result })))
152
+ .then((result) => this.write({ jsonrpc: "2.0", id: message.id, result }))
186
153
  .catch((error) => {
187
- this.write(this.withJsonrpc({
154
+ this.write({
155
+ jsonrpc: "2.0",
188
156
  id: message.id,
189
157
  error: {
190
158
  code: -32000,
191
159
  message: error instanceof Error ? error.message : String(error),
192
160
  },
193
- }));
161
+ });
194
162
  });
195
163
  return;
196
164
  }
@@ -16,34 +16,6 @@ export interface AgentCommandConfig {
16
16
  framing: AgentFraming;
17
17
  }
18
18
 
19
- export function resolveCodexAppServerCommands(input: {
20
- command?: string;
21
- }): AgentCommandConfig[] {
22
- const explicit = input.command?.trim();
23
- if (explicit) {
24
- return [{
25
- provider: "codex",
26
- command: explicit,
27
- protocol: "codex-app-server",
28
- framing: "newline",
29
- }];
30
- }
31
- return [
32
- {
33
- provider: "codex",
34
- command: "codex app-server --listen stdio://",
35
- protocol: "codex-app-server",
36
- framing: "newline",
37
- },
38
- {
39
- provider: "codex",
40
- command: "codex app-server proxy",
41
- protocol: "codex-app-server",
42
- framing: "newline",
43
- },
44
- ];
45
- }
46
-
47
19
  export function resolveAgentCommand(input: {
48
20
  provider: AgentProvider;
49
21
  command?: string;
@@ -61,7 +33,12 @@ export function resolveAgentCommand(input: {
61
33
  }
62
34
 
63
35
  if (input.provider === "codex") {
64
- return resolveCodexAppServerCommands({ command: input.command })[0] ?? null;
36
+ return {
37
+ provider: "codex",
38
+ command: "codex app-server --listen stdio://",
39
+ protocol: "codex-app-server",
40
+ framing: "newline",
41
+ };
65
42
  }
66
43
 
67
44
  if (input.provider === "claude") {