linkshell-cli 0.2.99 → 0.2.100

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 (28) hide show
  1. package/dist/cli/src/runtime/acp/acp-client.d.ts +1 -0
  2. package/dist/cli/src/runtime/acp/acp-client.js +17 -3
  3. package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
  4. package/dist/cli/src/runtime/acp/agent-session.d.ts +1 -0
  5. package/dist/cli/src/runtime/acp/agent-session.js +73 -19
  6. package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
  7. package/dist/cli/src/runtime/acp/agent-workspace.d.ts +6 -1
  8. package/dist/cli/src/runtime/acp/agent-workspace.js +346 -57
  9. package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
  10. package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +51 -0
  11. package/dist/cli/src/runtime/acp/claude-sdk-client.js +318 -0
  12. package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -0
  13. package/dist/cli/src/runtime/acp/provider-resolver.d.ts +1 -1
  14. package/dist/cli/src/runtime/acp/provider-resolver.js +22 -2
  15. package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
  16. package/dist/cli/src/runtime/bridge-session.js +1 -0
  17. package/dist/cli/src/runtime/bridge-session.js.map +1 -1
  18. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  19. package/dist/shared-protocol/src/index.d.ts +1885 -265
  20. package/dist/shared-protocol/src/index.js +36 -10
  21. package/dist/shared-protocol/src/index.js.map +1 -1
  22. package/package.json +8 -5
  23. package/src/runtime/acp/acp-client.ts +18 -3
  24. package/src/runtime/acp/agent-session.ts +68 -14
  25. package/src/runtime/acp/agent-workspace.ts +376 -54
  26. package/src/runtime/acp/claude-sdk-client.ts +372 -0
  27. package/src/runtime/acp/provider-resolver.ts +24 -3
  28. package/src/runtime/bridge-session.ts +1 -0
@@ -1,6 +1,7 @@
1
1
  import { basename } from "node:path";
2
2
  import { createEnvelope, parseTypedPayload, } from "@linkshell/protocol";
3
3
  import { AcpClient } from "./acp-client.js";
4
+ import { ClaudeSdkClient } from "./claude-sdk-client.js";
4
5
  import { ClaudeStreamJsonClient } from "./claude-stream-json-client.js";
5
6
  import { resolveAgentCommand } from "./provider-resolver.js";
6
7
  const PERMISSION_TIMEOUT_MS = 5 * 60_000;
@@ -219,9 +220,20 @@ function commandExecutionFromTool(toolCall) {
219
220
  status: toolCall.status,
220
221
  };
221
222
  }
222
- function fileChangeFromTool(toolCall) {
223
- const diff = toolCall.output && looksLikeDiff(toolCall.output) ? toolCall.output : undefined;
224
- const entries = (toolCall.input ?? "")
223
+ function fileChangeFromStructuredInput(input) {
224
+ const raw = input?.trim();
225
+ if (!raw)
226
+ return [];
227
+ try {
228
+ const parsed = JSON.parse(raw);
229
+ if (parsed && typeof parsed === "object") {
230
+ return fileChangeEntriesFromItem(parsed);
231
+ }
232
+ }
233
+ catch {
234
+ // Fall through to line parser.
235
+ }
236
+ return raw
225
237
  .split("\n")
226
238
  .map((line) => line.trim())
227
239
  .filter(Boolean)
@@ -234,6 +246,10 @@ function fileChangeFromTool(toolCall) {
234
246
  return entry;
235
247
  })
236
248
  .filter((entry) => entry.path.length > 0);
249
+ }
250
+ function fileChangeFromTool(toolCall) {
251
+ const diff = toolCall.output && looksLikeDiff(toolCall.output) ? toolCall.output : undefined;
252
+ const entries = fileChangeFromStructuredInput(toolCall.input);
237
253
  if (entries.length === 0 && !diff && !toolCall.output)
238
254
  return undefined;
239
255
  return {
@@ -486,35 +502,95 @@ function providerLabel(provider) {
486
502
  return "Claude";
487
503
  return "Custom";
488
504
  }
489
- function providerModels(provider) {
490
- if (provider === "claude") {
491
- return [
492
- { id: "default", label: "默认模型" },
493
- { id: "opus", label: "Opus 4.7" },
494
- { id: "sonnet", label: "Sonnet 4.6" },
495
- { id: "haiku", label: "Haiku 4.5" },
496
- ];
505
+ const ALL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"];
506
+ const ALL_PERMISSION_MODES = ["read_only", "workspace_write", "full_access"];
507
+ function parseModelListCapabilities(value) {
508
+ const raw = asRecord(value);
509
+ const modelsValue = Array.isArray(value) ? value :
510
+ Array.isArray(raw?.models) ? raw.models :
511
+ Array.isArray(raw?.items) ? raw.items :
512
+ Array.isArray(raw?.modelOptions) ? raw.modelOptions :
513
+ [];
514
+ const models = modelsValue
515
+ .map((entry, index) => {
516
+ const model = asRecord(entry);
517
+ if (!model) {
518
+ return typeof entry === "string" && entry
519
+ ? { id: entry, label: entry }
520
+ : undefined;
521
+ }
522
+ const modelId = firstString(model, ["id", "model", "name", "value"]) ?? `model-${index + 1}`;
523
+ const label = firstString(model, ["label", "title", "displayName", "name"]) ?? modelId;
524
+ return { id: modelId, label };
525
+ })
526
+ .filter((entry) => Boolean(entry));
527
+ const defaultModel = firstString(raw, ["defaultModel", "default_model", "currentModel"]) ??
528
+ firstString(asRecord(raw?.defaults), ["model"]);
529
+ const effortsValue = Array.isArray(raw?.reasoningEfforts) ? raw.reasoningEfforts :
530
+ Array.isArray(raw?.reasoning_efforts) ? raw.reasoning_efforts :
531
+ Array.isArray(raw?.efforts) ? raw.efforts :
532
+ undefined;
533
+ const reasoningEfforts = effortsValue
534
+ ?.filter((entry) => typeof entry === "string" && ALL_REASONING_EFFORTS.includes(entry));
535
+ if (models.length === 0 && !defaultModel && !reasoningEfforts?.length)
536
+ return undefined;
537
+ return {
538
+ ...(models.length > 0 ? { models: [{ id: "default", label: "默认模型" }, ...models] } : {}),
539
+ ...(defaultModel ? { defaultModel } : {}),
540
+ ...(reasoningEfforts?.length ? { reasoningEfforts } : {}),
541
+ };
542
+ }
543
+ function parseTimestamp(value) {
544
+ if (typeof value === "number" && Number.isFinite(value)) {
545
+ return value > 10_000_000_000 ? value : value * 1000;
497
546
  }
498
- if (provider === "codex") {
499
- return [
500
- { id: "default", label: "默认模型" },
501
- { id: "gpt-5.5", label: "GPT-5.5" },
502
- { id: "gpt-5.4", label: "GPT-5.4" },
503
- { id: "gpt-5.4-mini", label: "GPT-5.4 Mini" },
504
- { id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
505
- ];
547
+ if (typeof value === "string" && value.trim()) {
548
+ const parsed = Date.parse(value);
549
+ return Number.isNaN(parsed) ? undefined : parsed;
506
550
  }
507
- return [{ id: "default", label: "默认模型" }];
551
+ return undefined;
552
+ }
553
+ function parseRemoteSessions(value) {
554
+ const raw = asRecord(value);
555
+ const sessionsValue = Array.isArray(value) ? value :
556
+ Array.isArray(raw?.threads) ? raw.threads :
557
+ Array.isArray(raw?.sessions) ? raw.sessions :
558
+ Array.isArray(raw?.items) ? raw.items :
559
+ [];
560
+ const result = [];
561
+ for (const entry of sessionsValue) {
562
+ const session = asRecord(entry);
563
+ if (!session) {
564
+ if (typeof entry === "string" && entry)
565
+ result.push({ id: entry });
566
+ continue;
567
+ }
568
+ const nestedThread = asRecord(session.thread);
569
+ const source = nestedThread ?? session;
570
+ const id = firstString(source, ["id", "threadId", "sessionId", "agentSessionId"]);
571
+ if (!id)
572
+ continue;
573
+ result.push({
574
+ id,
575
+ cwd: firstString(source, ["cwd", "workingDirectory", "workspacePath"]),
576
+ title: firstString(source, ["title", "name", "summary"]),
577
+ model: firstString(source, ["model", "modelId"]),
578
+ createdAt: parseTimestamp(source.createdAt ?? source.created_at),
579
+ lastActivityAt: parseTimestamp(source.lastActivityAt ?? source.updatedAt ?? source.modifiedAt ?? source.lastModified ?? source.updated_at),
580
+ });
581
+ }
582
+ return result;
508
583
  }
509
584
  export class AgentWorkspaceProxy {
510
585
  input;
511
586
  clients = new Map();
512
587
  agentProtocols = new Map();
588
+ providerCapabilities = new Map();
513
589
  initialized = false;
514
590
  status = "unavailable";
515
591
  error;
516
592
  activeConversationId;
517
- currentTurnId;
593
+ currentTurnIds = new Map();
518
594
  conversations = new Map();
519
595
  conversationByAgentSessionId = new Map();
520
596
  timelines = new Map();
@@ -541,6 +617,7 @@ export class AgentWorkspaceProxy {
541
617
  }
542
618
  case "agent.v2.conversation.list": {
543
619
  const payload = parseTypedPayload("agent.v2.conversation.list", envelope.payload);
620
+ await this.syncProviderSessions();
544
621
  const conversations = [...this.conversations.values()].filter((conversation) => payload.includeArchived ? true : !conversation.archived);
545
622
  this.input.send(createEnvelope({
546
623
  type: "agent.v2.conversation.list.result",
@@ -566,9 +643,9 @@ export class AgentWorkspaceProxy {
566
643
  const cancelClient = conversation ? this.clientForProvider(conversation.provider) : undefined;
567
644
  cancelClient?.cancel({
568
645
  sessionId: conversation?.agentSessionId,
569
- turnId: this.currentTurnId,
646
+ turnId: this.currentTurnIds.get(payload.conversationId),
570
647
  });
571
- this.currentTurnId = undefined;
648
+ this.currentTurnIds.delete(payload.conversationId);
572
649
  this.updateConversationStatus(payload.conversationId, "idle");
573
650
  this.emitStatus(payload.conversationId, "idle", "已停止");
574
651
  break;
@@ -622,61 +699,174 @@ export class AgentWorkspaceProxy {
622
699
  }
623
700
  return undefined;
624
701
  }
625
- try {
626
- this.agentProtocols.set(provider, resolved.protocol);
627
- const isClaudeStreamJson = resolved.protocol === "claude-stream-json";
628
- const client = isClaudeStreamJson
629
- ? new ClaudeStreamJsonClient({
630
- command: resolved.command,
631
- protocol: resolved.protocol,
632
- framing: resolved.framing,
702
+ const tryCreateClient = async (config) => {
703
+ this.agentProtocols.set(provider, config.protocol);
704
+ const isClaudeSdk = config.protocol === "claude-agent-sdk";
705
+ const isClaudeStreamJson = config.protocol === "claude-stream-json";
706
+ const client = isClaudeSdk
707
+ ? new ClaudeSdkClient({
708
+ command: config.command,
709
+ protocol: config.protocol,
710
+ framing: config.framing,
633
711
  cwd: this.input.cwd,
634
712
  onNotification: (method, params) => this.handleNotification(method, params),
635
713
  onRequest: (method, params) => this.handleRequest(method, params),
636
714
  onExit: (message) => this.handleProviderExit(provider, message),
637
715
  })
638
- : new AcpClient({
639
- command: resolved.command,
640
- protocol: resolved.protocol,
641
- framing: resolved.framing,
642
- cwd: this.input.cwd,
643
- onNotification: (method, params) => this.handleNotification(method, params),
644
- onRequest: (method, params) => this.handleRequest(method, params),
645
- onExit: (message) => this.handleProviderExit(provider, message),
646
- });
716
+ : isClaudeStreamJson
717
+ ? new ClaudeStreamJsonClient({
718
+ command: config.command,
719
+ protocol: config.protocol,
720
+ framing: config.framing,
721
+ cwd: this.input.cwd,
722
+ onNotification: (method, params) => this.handleNotification(method, params),
723
+ onRequest: (method, params) => this.handleRequest(method, params),
724
+ onExit: (message) => this.handleProviderExit(provider, message),
725
+ })
726
+ : new AcpClient({
727
+ command: config.command,
728
+ protocol: config.protocol,
729
+ framing: config.framing,
730
+ cwd: this.input.cwd,
731
+ onNotification: (method, params) => this.handleNotification(method, params),
732
+ onRequest: (method, params) => this.handleRequest(method, params),
733
+ onExit: (message) => this.handleProviderExit(provider, message),
734
+ });
647
735
  await client.initialize();
736
+ return client;
737
+ };
738
+ try {
739
+ const client = await tryCreateClient(resolved);
648
740
  this.clients.set(provider, client);
741
+ await this.refreshProviderCapabilities(provider, client, resolved.protocol);
649
742
  this.status = "idle";
650
743
  this.error = undefined;
651
744
  this.sendCapabilities();
652
745
  return client;
653
746
  }
654
747
  catch (error) {
748
+ if (provider === "claude" && resolved.protocol === "claude-agent-sdk") {
749
+ if (this.input.verbose) {
750
+ process.stderr.write(`[agent:v2] Claude SDK failed, falling back to stream-json: ${error instanceof Error ? error.message : String(error)}\n`);
751
+ }
752
+ try {
753
+ const fallback = {
754
+ provider,
755
+ command: "claude --print --output-format stream-json --input-format stream-json --verbose --permission-mode bypassPermissions",
756
+ protocol: "claude-stream-json",
757
+ framing: "newline",
758
+ };
759
+ const client = await tryCreateClient(fallback);
760
+ this.clients.set(provider, client);
761
+ await this.refreshProviderCapabilities(provider, client, fallback.protocol);
762
+ this.status = "idle";
763
+ this.error = undefined;
764
+ this.sendCapabilities();
765
+ return client;
766
+ }
767
+ catch (fallbackError) {
768
+ if (this.input.verbose) {
769
+ process.stderr.write(`[agent:v2] Claude stream-json fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}\n`);
770
+ }
771
+ }
772
+ }
655
773
  if (this.input.verbose) {
656
774
  process.stderr.write(`[agent:v2] failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
657
775
  }
658
776
  return undefined;
659
777
  }
660
778
  }
779
+ async refreshProviderCapabilities(provider, client, protocol) {
780
+ if (!(client instanceof AcpClient) || protocol !== "codex-app-server")
781
+ return;
782
+ try {
783
+ const result = await client.listModels();
784
+ const runtimeCapabilities = parseModelListCapabilities(result);
785
+ if (runtimeCapabilities)
786
+ this.providerCapabilities.set(provider, runtimeCapabilities);
787
+ }
788
+ catch (error) {
789
+ if (this.input.verbose) {
790
+ process.stderr.write(`[agent:v2] model/list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
791
+ }
792
+ }
793
+ }
794
+ async syncProviderSessions() {
795
+ await this.initialize();
796
+ for (const [provider, client] of this.clients) {
797
+ try {
798
+ const result = await client.listSessions();
799
+ for (const remote of parseRemoteSessions(result)) {
800
+ const agentSessionId = remote.id;
801
+ const existingId = this.conversationByAgentSessionId.get(agentSessionId);
802
+ const now = Date.now();
803
+ const conversationId = existingId ?? `agent:${agentSessionId}`;
804
+ const existing = this.conversations.get(conversationId);
805
+ const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
806
+ const conversation = {
807
+ id: conversationId,
808
+ agentSessionId,
809
+ provider,
810
+ cwd,
811
+ title: remote.title ?? existing?.title ?? titleFromCwd(cwd),
812
+ model: remote.model ?? existing?.model,
813
+ reasoningEffort: existing?.reasoningEffort,
814
+ permissionMode: existing?.permissionMode,
815
+ status: existing?.status ?? "idle",
816
+ archived: existing?.archived ?? false,
817
+ lastMessagePreview: existing?.lastMessagePreview,
818
+ lastActivityAt: remote.lastActivityAt ?? existing?.lastActivityAt ?? now,
819
+ createdAt: remote.createdAt ?? existing?.createdAt ?? now,
820
+ };
821
+ this.conversations.set(conversation.id, conversation);
822
+ this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
823
+ this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
824
+ }
825
+ }
826
+ catch (error) {
827
+ if (this.input.verbose) {
828
+ process.stderr.write(`[agent:v2] session list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
829
+ }
830
+ }
831
+ }
832
+ }
661
833
  sendCapabilities() {
662
834
  const providers = this.input.availableProviders.map((provider) => {
663
835
  const client = this.clients.get(provider);
664
836
  const protocol = this.agentProtocols.get(provider);
837
+ const runtimeCapabilities = this.providerCapabilities.get(provider);
665
838
  const enabled = Boolean(client);
666
839
  const supportsImages = enabled && protocol === "codex-app-server";
840
+ const isClaudeFallback = protocol === "claude-stream-json";
841
+ const supportsPermission = enabled && !isClaudeFallback;
842
+ const supportsReasoningEffort = enabled && !isClaudeFallback;
667
843
  return {
668
844
  id: provider,
669
845
  label: providerLabel(provider),
670
846
  enabled,
671
847
  reason: enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`,
672
848
  supportsImages,
673
- supportsPermission: enabled,
849
+ supportsPermission,
674
850
  supportsPlan: enabled,
675
851
  supportsCancel: enabled,
676
- models: providerModels(provider),
852
+ models: runtimeCapabilities?.models ?? [{ id: "default", label: "默认模型" }],
853
+ defaultModel: runtimeCapabilities?.defaultModel ?? "default",
854
+ reasoningEfforts: supportsReasoningEffort
855
+ ? runtimeCapabilities?.reasoningEfforts ?? [...ALL_REASONING_EFFORTS]
856
+ : [],
857
+ permissionModes: supportsPermission ? [...ALL_PERMISSION_MODES] : [],
858
+ features: {
859
+ images: supportsImages,
860
+ permissions: supportsPermission,
861
+ plan: enabled,
862
+ cancel: enabled,
863
+ reasoningEffort: supportsReasoningEffort,
864
+ streamJsonFallback: isClaudeFallback,
865
+ },
677
866
  };
678
867
  });
679
868
  const anyEnabled = providers.some((p) => p.enabled);
869
+ const anyPermission = providers.some((p) => p.supportsPermission);
680
870
  this.input.send(createEnvelope({
681
871
  type: "agent.v2.capabilities",
682
872
  sessionId: this.input.sessionId,
@@ -691,7 +881,7 @@ export class AgentWorkspaceProxy {
691
881
  supportsSessionLoad: anyEnabled,
692
882
  supportsImages: providers.some((p) => p.supportsImages),
693
883
  supportsAudio: false,
694
- supportsPermission: anyEnabled,
884
+ supportsPermission: anyPermission,
695
885
  supportsPlan: anyEnabled,
696
886
  supportsCancel: anyEnabled,
697
887
  },
@@ -849,8 +1039,17 @@ export class AgentWorkspaceProxy {
849
1039
  permissionMode: payload.permissionMode,
850
1040
  cwd: conversation.cwd,
851
1041
  });
852
- this.currentTurnId = this.extractTurnId(result) ?? this.currentTurnId;
853
- if (conversation.status === "running") {
1042
+ const nextAgentSessionId = this.extractSessionId(result);
1043
+ if (nextAgentSessionId && nextAgentSessionId !== conversation.agentSessionId) {
1044
+ if (conversation.agentSessionId)
1045
+ this.conversationByAgentSessionId.delete(conversation.agentSessionId);
1046
+ conversation.agentSessionId = nextAgentSessionId;
1047
+ this.conversationByAgentSessionId.set(nextAgentSessionId, conversation.id);
1048
+ }
1049
+ const turnId = this.extractTurnId(result);
1050
+ if (turnId)
1051
+ this.currentTurnIds.set(conversation.id, turnId);
1052
+ if (conversation.status === "running" && protocol !== "codex-app-server") {
854
1053
  this.updateConversationStatus(conversation.id, "idle");
855
1054
  }
856
1055
  }
@@ -870,6 +1069,9 @@ export class AgentWorkspaceProxy {
870
1069
  if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
871
1070
  return this.handleStructuredInput(params, true);
872
1071
  }
1072
+ if (method === "mcpServer/elicitation/request") {
1073
+ return this.handleStructuredInput({ ...(asRecord(params) ?? {}), source: method }, true);
1074
+ }
873
1075
  if (isPermissionRequestMethod(method)) {
874
1076
  return this.handlePermission(params, true, method);
875
1077
  }
@@ -896,6 +1098,10 @@ export class AgentWorkspaceProxy {
896
1098
  this.handleStructuredInput(params);
897
1099
  return;
898
1100
  }
1101
+ if (method === "mcpServer/elicitation/request") {
1102
+ this.handleStructuredInput({ ...(asRecord(params) ?? {}), source: method });
1103
+ return;
1104
+ }
899
1105
  if (isPermissionRequestMethod(method)) {
900
1106
  this.handlePermission(params, false, method);
901
1107
  return;
@@ -911,15 +1117,19 @@ export class AgentWorkspaceProxy {
911
1117
  return;
912
1118
  }
913
1119
  if (method === "turn/started") {
914
- this.currentTurnId = this.extractTurnId(params) ?? this.currentTurnId;
915
- if (conversationId)
1120
+ if (conversationId) {
1121
+ const turnId = this.extractTurnId(params);
1122
+ if (turnId)
1123
+ this.currentTurnIds.set(conversationId, turnId);
916
1124
  this.updateConversationStatus(conversationId, "running");
1125
+ }
917
1126
  return;
918
1127
  }
919
1128
  if (method === "turn/completed") {
920
- this.currentTurnId = undefined;
921
- if (conversationId)
1129
+ if (conversationId) {
1130
+ this.currentTurnIds.delete(conversationId);
922
1131
  this.updateConversationStatus(conversationId, "idle");
1132
+ }
923
1133
  return;
924
1134
  }
925
1135
  if (method === "session/request_permission") {
@@ -957,7 +1167,11 @@ export class AgentWorkspaceProxy {
957
1167
  this.handleCommandExecDelta(params);
958
1168
  return;
959
1169
  case "item/autoApprovalReview/started":
1170
+ this.handleAutoApprovalReview(params, true);
1171
+ return;
960
1172
  case "item/autoApprovalReview/completed":
1173
+ this.handleAutoApprovalReview(params, false);
1174
+ return;
961
1175
  case "item/commandExecution/terminalInteraction":
962
1176
  return;
963
1177
  }
@@ -1199,6 +1413,36 @@ export class AgentWorkspaceProxy {
1199
1413
  status: "running",
1200
1414
  });
1201
1415
  }
1416
+ handleAutoApprovalReview(params, streaming) {
1417
+ const raw = asRecord(params) ?? {};
1418
+ const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
1419
+ if (!conversationId)
1420
+ return;
1421
+ const itemId = firstString(raw, ["itemId", "id", "reviewId"]) ?? "auto-approval-review";
1422
+ const existing = this.findItem(conversationId, itemId);
1423
+ const decision = firstString(raw, ["decision", "result", "outcome", "status"]);
1424
+ const summary = firstString(raw, ["summary", "message", "text", "reason"]) ??
1425
+ stringifyDefined(raw.review ?? raw.details);
1426
+ this.upsertItem(conversationId, {
1427
+ id: itemId,
1428
+ conversationId,
1429
+ type: "status",
1430
+ kind: "review",
1431
+ role: "system",
1432
+ turnId: this.extractTurnId(raw) ?? this.currentTurnIds.get(conversationId),
1433
+ itemId,
1434
+ text: summary ?? (streaming ? "正在审查自动授权" : decision ? `自动授权审查:${decision}` : "已完成自动授权审查"),
1435
+ metadata: {
1436
+ ...(existing?.metadata ?? {}),
1437
+ autoApprovalReview: true,
1438
+ ...(decision ? { decision } : {}),
1439
+ },
1440
+ createdAt: existing?.createdAt ?? Date.now(),
1441
+ updatedAt: Date.now(),
1442
+ isStreaming: streaming,
1443
+ });
1444
+ this.updateConversationPreview(conversationId, streaming ? "正在审查自动授权" : "已完成自动授权审查", streaming ? "running" : undefined);
1445
+ }
1202
1446
  handleCompletedMessageItem(item, streaming) {
1203
1447
  const conversationId = this.conversationIdFromParams(item) ?? this.activeConversationId;
1204
1448
  if (!conversationId)
@@ -1271,7 +1515,7 @@ export class AgentWorkspaceProxy {
1271
1515
  conversationId,
1272
1516
  type: "status",
1273
1517
  role: "system",
1274
- turnId: this.extractTurnId(item) ?? this.currentTurnId,
1518
+ turnId: this.extractTurnId(item) ?? this.currentTurnIds.get(conversationId),
1275
1519
  itemId,
1276
1520
  createdAt: existing?.createdAt ?? Date.now(),
1277
1521
  updatedAt: Date.now(),
@@ -1322,7 +1566,7 @@ export class AgentWorkspaceProxy {
1322
1566
  type: "status",
1323
1567
  kind: "subagent_action",
1324
1568
  role: "system",
1325
- turnId: this.extractTurnId(item) ?? this.currentTurnId,
1569
+ turnId: this.extractTurnId(item) ?? this.currentTurnIds.get(conversationId),
1326
1570
  itemId,
1327
1571
  text,
1328
1572
  subagent,
@@ -1335,11 +1579,15 @@ export class AgentWorkspaceProxy {
1335
1579
  handleStructuredInput(params, waitForResponse = false) {
1336
1580
  const raw = asRecord(params) ?? {};
1337
1581
  const conversationId = this.conversationIdFromParams(raw) ?? this.activeConversationId;
1582
+ const source = firstString(raw, ["method", "source", "requestMethod"]);
1583
+ const formatResponse = source === "mcpServer/elicitation/request"
1584
+ ? formatMcpElicitationResponse
1585
+ : formatStructuredInputResponse;
1338
1586
  if (!conversationId)
1339
- return waitForResponse ? Promise.resolve(formatStructuredInputResponse({})) : undefined;
1587
+ return waitForResponse ? Promise.resolve(formatResponse({})) : undefined;
1340
1588
  const structuredInput = decodeStructuredInput(raw);
1341
1589
  if (!structuredInput)
1342
- return waitForResponse ? Promise.resolve(formatStructuredInputResponse({})) : undefined;
1590
+ return waitForResponse ? Promise.resolve(formatResponse({})) : undefined;
1343
1591
  const text = structuredInput.questions.map((question) => question.question).join("\n");
1344
1592
  this.pendingStructuredInputs.set(structuredInput.requestId, { conversationId, input: structuredInput });
1345
1593
  this.upsertItem(conversationId, {
@@ -1361,13 +1609,13 @@ export class AgentWorkspaceProxy {
1361
1609
  const timer = setTimeout(() => {
1362
1610
  this.pendingStructuredInputs.delete(structuredInput.requestId);
1363
1611
  this.structuredInputWaiters.delete(structuredInput.requestId);
1364
- resolve(formatStructuredInputResponse({}));
1612
+ resolve(formatResponse({}));
1365
1613
  this.markStructuredInput(conversationId, structuredInput.requestId, {
1366
1614
  inputPending: false,
1367
1615
  inputError: "等待用户输入超时",
1368
1616
  });
1369
1617
  }, PERMISSION_TIMEOUT_MS);
1370
- this.structuredInputWaiters.set(structuredInput.requestId, { resolve, timer });
1618
+ this.structuredInputWaiters.set(structuredInput.requestId, { resolve, timer, source });
1371
1619
  });
1372
1620
  }
1373
1621
  toolCallFromItem(item, fallbackStatus) {
@@ -1460,6 +1708,12 @@ export class AgentWorkspaceProxy {
1460
1708
  optionId: selectedOptionId,
1461
1709
  });
1462
1710
  }
1711
+ this.markPermission(payload.conversationId, payload.requestId, {
1712
+ permissionOutcome: payload.outcome,
1713
+ optionId: selectedOptionId,
1714
+ permissionError: undefined,
1715
+ permissionPending: false,
1716
+ });
1463
1717
  this.updateConversationStatus(payload.conversationId, "running");
1464
1718
  }
1465
1719
  respondStructuredInput(payload) {
@@ -1469,15 +1723,30 @@ export class AgentWorkspaceProxy {
1469
1723
  if (waiter) {
1470
1724
  clearTimeout(waiter.timer);
1471
1725
  this.structuredInputWaiters.delete(payload.requestId);
1472
- waiter.resolve(formatStructuredInputResponse(payload.answers));
1726
+ const formatResponse = waiter.source === "mcpServer/elicitation/request"
1727
+ ? formatMcpElicitationResponse
1728
+ : formatStructuredInputResponse;
1729
+ waiter.resolve(formatResponse(payload.answers));
1473
1730
  }
1474
1731
  this.markStructuredInput(payload.conversationId, payload.requestId, {
1475
1732
  inputPending: false,
1476
1733
  inputSubmitted: true,
1734
+ inputSubmitting: false,
1735
+ inputError: undefined,
1477
1736
  answers: payload.answers,
1478
1737
  });
1479
1738
  this.updateConversationStatus(pending?.conversationId ?? payload.conversationId, "running");
1480
1739
  }
1740
+ markPermission(conversationId, requestId, metadata) {
1741
+ const item = this.findItem(conversationId, `permission:${requestId}`);
1742
+ if (!item)
1743
+ return;
1744
+ this.upsertItem(conversationId, {
1745
+ ...item,
1746
+ metadata: { ...(item.metadata ?? {}), ...metadata },
1747
+ updatedAt: Date.now(),
1748
+ });
1749
+ }
1481
1750
  markStructuredInput(conversationId, requestId, metadata) {
1482
1751
  const item = this.findItem(conversationId, `input:${requestId}`);
1483
1752
  if (!item)
@@ -1795,7 +2064,8 @@ function selectPermissionOption(permission, outcome) {
1795
2064
  function isPermissionRequestMethod(method) {
1796
2065
  return (method === "session/request_permission" ||
1797
2066
  method.endsWith("/requestApproval") ||
1798
- method === "mcpServer/elicitation/request");
2067
+ method === "mcpServer/elicitation/request" ||
2068
+ method === "claude/requestApproval");
1799
2069
  }
1800
2070
  function formatStructuredInputResponse(answers) {
1801
2071
  return {
@@ -1805,6 +2075,17 @@ function formatStructuredInputResponse(answers) {
1805
2075
  ])),
1806
2076
  };
1807
2077
  }
2078
+ function formatMcpElicitationResponse(answers) {
2079
+ const content = Object.fromEntries(Object.entries(answers).map(([questionId, values]) => [
2080
+ questionId,
2081
+ values.length <= 1 ? values[0] ?? "" : values,
2082
+ ]));
2083
+ return {
2084
+ action: Object.keys(content).length > 0 ? "accept" : "cancel",
2085
+ content,
2086
+ _meta: { source: "linkshell" },
2087
+ };
2088
+ }
1808
2089
  function formatPermissionResponse(source, outcome, optionId) {
1809
2090
  if (source === "item/commandExecution/requestApproval" || source === "item/fileChange/requestApproval") {
1810
2091
  return { decision: outcome === "allow" ? "accept" : outcome === "deny" ? "decline" : "cancel" };
@@ -1818,6 +2099,14 @@ function formatPermissionResponse(source, outcome, optionId) {
1818
2099
  }
1819
2100
  return { permissions: { type: "managed", network: { enabled: false }, fileSystem: { type: "readOnly" } } };
1820
2101
  }
2102
+ if (source === "claude/requestApproval") {
2103
+ return { behavior: outcome === "allow" ? "allow" : "deny" };
2104
+ }
2105
+ if (source === "mcpServer/elicitation/request") {
2106
+ return outcome === "allow"
2107
+ ? { action: "accept", content: { optionId }, _meta: { source: "linkshell" } }
2108
+ : { action: outcome === "cancelled" ? "cancel" : "decline", content: {}, _meta: { source: "linkshell" } };
2109
+ }
1821
2110
  return {
1822
2111
  outcome: outcome === "cancelled"
1823
2112
  ? { outcome: "cancelled" }