linkshell-cli 0.2.89 → 0.2.90

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.
@@ -485,21 +485,10 @@ function providerLabel(provider) {
485
485
  return "Claude";
486
486
  return "Custom";
487
487
  }
488
- function providerSetupReason(provider, activeProvider, error) {
489
- if (provider === activeProvider) {
490
- return error ?? `${providerLabel(provider)} Agent 正在初始化或不可用。`;
491
- }
492
- if (provider === "codex") {
493
- return `当前 CLI 启用的是 ${providerLabel(activeProvider)} Agent。`;
494
- }
495
- if (provider === "claude") {
496
- return "Claude ACP adapter 尚未启用,请用 --agent-provider claude --agent-command 配置。";
497
- }
498
- return "Custom Agent 需要用 --agent-provider custom --agent-command 配置后才能使用。";
499
- }
500
488
  export class AgentWorkspaceProxy {
501
489
  input;
502
- client;
490
+ clients = new Map();
491
+ agentProtocols = new Map();
503
492
  initialized = false;
504
493
  status = "unavailable";
505
494
  error;
@@ -515,7 +504,6 @@ export class AgentWorkspaceProxy {
515
504
  pendingStructuredInputs = new Map();
516
505
  structuredInputWaiters = new Map();
517
506
  toolConversationIds = new Map();
518
- agentProtocol;
519
507
  constructor(input) {
520
508
  this.input = input;
521
509
  }
@@ -554,7 +542,8 @@ export class AgentWorkspaceProxy {
554
542
  const payload = parseTypedPayload("agent.v2.cancel", envelope.payload);
555
543
  const conversation = this.conversations.get(payload.conversationId);
556
544
  this.cancelPendingPermissions(payload.conversationId);
557
- this.client?.cancel({
545
+ const cancelClient = conversation ? this.clientForProvider(conversation.provider) : undefined;
546
+ cancelClient?.cancel({
558
547
  sessionId: conversation?.agentSessionId,
559
548
  turnId: this.currentTurnId,
560
549
  });
@@ -576,99 +565,114 @@ export class AgentWorkspaceProxy {
576
565
  }
577
566
  }
578
567
  stop() {
579
- this.client?.stop();
580
- this.client = undefined;
568
+ for (const client of this.clients.values()) {
569
+ client.stop();
570
+ }
571
+ this.clients.clear();
572
+ }
573
+ clientForProvider(provider) {
574
+ return this.clients.get(provider);
575
+ }
576
+ protocolForProvider(provider) {
577
+ return this.agentProtocols.get(provider);
581
578
  }
582
579
  async initialize() {
583
580
  if (this.initialized)
584
581
  return;
585
- await this.ensureClient();
582
+ // trigger capability report immediately, lazy-start providers on first use
583
+ this.initialized = true;
584
+ this.status = "idle";
585
+ this.error = undefined;
586
+ this.sendCapabilities();
586
587
  }
587
- async ensureClient() {
588
- if (this.client)
589
- return;
588
+ async ensureProviderClient(provider) {
589
+ const existing = this.clients.get(provider);
590
+ if (existing)
591
+ return existing;
590
592
  const resolved = resolveAgentCommand({
591
- provider: this.input.provider,
593
+ provider,
592
594
  command: this.input.command,
593
595
  });
594
596
  if (!resolved) {
595
- this.status = "unavailable";
596
- this.error = `Agent Workspace requires --agent-command for ${this.input.provider}`;
597
- return;
597
+ if (this.input.verbose) {
598
+ process.stderr.write(`[agent:v2] no command for provider ${provider}\n`);
599
+ }
600
+ return undefined;
598
601
  }
599
602
  try {
600
- this.agentProtocol = resolved.protocol;
601
- this.client = new AcpClient({
603
+ this.agentProtocols.set(provider, resolved.protocol);
604
+ const client = new AcpClient({
602
605
  command: resolved.command,
603
606
  protocol: resolved.protocol,
604
607
  framing: resolved.framing,
605
608
  cwd: this.input.cwd,
606
609
  onNotification: (method, params) => this.handleNotification(method, params),
607
610
  onRequest: (method, params) => this.handleRequest(method, params),
608
- onExit: (message) => this.handleExit(message),
611
+ onExit: (message) => this.handleProviderExit(provider, message),
609
612
  });
610
- await this.client.initialize();
611
- this.initialized = true;
613
+ await client.initialize();
614
+ this.clients.set(provider, client);
612
615
  this.status = "idle";
613
616
  this.error = undefined;
617
+ this.sendCapabilities();
618
+ return client;
614
619
  }
615
620
  catch (error) {
616
- this.client?.stop();
617
- this.client = undefined;
618
- this.status = "error";
619
- this.error = error instanceof Error ? error.message : String(error);
621
+ if (this.input.verbose) {
622
+ process.stderr.write(`[agent:v2] failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
623
+ }
624
+ return undefined;
620
625
  }
621
626
  }
622
627
  sendCapabilities() {
623
- const enabled = Boolean(this.client && this.initialized && !this.error);
624
- const supportsImages = enabled && this.agentProtocol === "codex-app-server";
625
- const activeProvider = this.input.provider;
626
- const providerIds = ["codex", "claude"];
627
- if (activeProvider === "custom")
628
- providerIds.push("custom");
628
+ const providers = this.input.availableProviders.map((provider) => {
629
+ const client = this.clients.get(provider);
630
+ const protocol = this.agentProtocols.get(provider);
631
+ const enabled = Boolean(client);
632
+ const supportsImages = enabled && protocol === "codex-app-server";
633
+ return {
634
+ id: provider,
635
+ label: providerLabel(provider),
636
+ enabled,
637
+ reason: enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`,
638
+ supportsImages,
639
+ supportsPermission: enabled,
640
+ supportsPlan: enabled,
641
+ supportsCancel: enabled,
642
+ };
643
+ });
644
+ const anyEnabled = providers.some((p) => p.enabled);
629
645
  this.input.send(createEnvelope({
630
646
  type: "agent.v2.capabilities",
631
647
  sessionId: this.input.sessionId,
632
648
  payload: {
633
- enabled,
634
- provider: activeProvider,
635
- providers: providerIds.map((provider) => {
636
- const isActive = provider === activeProvider;
637
- const canUse = isActive && enabled;
638
- return {
639
- id: provider,
640
- label: providerLabel(provider),
641
- enabled: canUse,
642
- reason: canUse
643
- ? undefined
644
- : providerSetupReason(provider, activeProvider, isActive ? this.error : undefined),
645
- supportsImages: canUse && supportsImages,
646
- supportsPermission: canUse,
647
- supportsPlan: canUse,
648
- supportsCancel: canUse,
649
- };
650
- }),
649
+ enabled: anyEnabled,
650
+ provider: this.input.availableProviders[0] ?? "codex",
651
+ providers,
651
652
  protocolVersion: 1,
652
653
  workspaceProtocolVersion: 2,
653
- error: enabled ? undefined : this.error,
654
- supportsSessionList: enabled,
655
- supportsSessionLoad: enabled,
656
- supportsImages,
654
+ error: anyEnabled ? undefined : "没有可用的 Agent provider。请安装 Claude Code 或 Codex CLI。",
655
+ supportsSessionList: anyEnabled,
656
+ supportsSessionLoad: anyEnabled,
657
+ supportsImages: providers.some((p) => p.supportsImages),
657
658
  supportsAudio: false,
658
- supportsPermission: enabled,
659
- supportsPlan: enabled,
660
- supportsCancel: enabled,
659
+ supportsPermission: anyEnabled,
660
+ supportsPlan: anyEnabled,
661
+ supportsCancel: anyEnabled,
661
662
  },
662
663
  }));
663
664
  }
664
665
  async openConversation(payload) {
665
- await this.ensureClient();
666
- this.sendCapabilities();
667
- if (payload.provider && payload.provider !== this.input.provider) {
668
- return this.openFailure(payload, `当前 CLI 只启用了 ${providerLabel(this.input.provider)} Agent,不能在这个会话里启动 ${providerLabel(payload.provider)}。`);
666
+ const provider = payload.provider ?? this.input.availableProviders[0];
667
+ if (!provider) {
668
+ return this.openFailure(payload, "没有可用的 Agent provider。");
669
669
  }
670
- if (!this.client) {
671
- return this.openFailure(payload, this.error ?? "Agent Workspace 不可用,请确认 CLI 已使用 --agent-ui 启动。");
670
+ if (!this.input.availableProviders.includes(provider)) {
671
+ return this.openFailure(payload, `${providerLabel(provider)} 未安装或不可用。`);
672
+ }
673
+ const client = await this.ensureProviderClient(provider);
674
+ if (!client) {
675
+ return this.openFailure(payload, `${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`);
672
676
  }
673
677
  const cwd = payload.cwd ?? this.input.cwd;
674
678
  let agentSessionId = payload.agentSessionId;
@@ -691,8 +695,8 @@ export class AgentWorkspaceProxy {
691
695
  }
692
696
  try {
693
697
  const result = agentSessionId
694
- ? await this.client.loadSession({ sessionId: agentSessionId, cwd })
695
- : await this.client.newSession({ cwd });
698
+ ? await client.loadSession({ sessionId: agentSessionId, cwd })
699
+ : await client.newSession({ cwd });
696
700
  agentSessionId = this.extractSessionId(result) ?? agentSessionId ?? id("agent-session");
697
701
  const now = Date.now();
698
702
  const conversationId = payload.conversationId ?? `agent:${agentSessionId}`;
@@ -700,7 +704,7 @@ export class AgentWorkspaceProxy {
700
704
  ...existingConversation,
701
705
  id: conversationId,
702
706
  agentSessionId,
703
- provider: payload.provider ?? this.input.provider,
707
+ provider,
704
708
  cwd,
705
709
  title: payload.title ?? existingConversation?.title ?? titleFromCwd(cwd),
706
710
  model: payload.model ?? existingConversation?.model,
@@ -733,7 +737,7 @@ export class AgentWorkspaceProxy {
733
737
  const now = Date.now();
734
738
  const conversation = {
735
739
  id: fallbackId,
736
- provider: payload.provider ?? this.input.provider,
740
+ provider: payload.provider ?? this.input.availableProviders[0] ?? "codex",
737
741
  cwd,
738
742
  title: payload.title ?? titleFromCwd(cwd),
739
743
  model: payload.model,
@@ -764,9 +768,13 @@ export class AgentWorkspaceProxy {
764
768
  async sendPrompt(payload) {
765
769
  const conversation = this.conversations.get(payload.conversationId) ??
766
770
  await this.openConversation({ conversationId: payload.conversationId });
767
- if (!conversation || !this.client || !conversation.agentSessionId)
771
+ if (!conversation || !conversation.agentSessionId)
772
+ return;
773
+ const client = this.clientForProvider(conversation.provider);
774
+ if (!client)
768
775
  return;
769
- if (payload.contentBlocks.some((block) => block.type === "image") && this.agentProtocol !== "codex-app-server") {
776
+ const protocol = this.protocolForProvider(conversation.provider);
777
+ if (payload.contentBlocks.some((block) => block.type === "image") && protocol !== "codex-app-server") {
770
778
  conversation.status = "idle";
771
779
  conversation.lastActivityAt = Date.now();
772
780
  this.emitConversation(conversation);
@@ -797,7 +805,7 @@ export class AgentWorkspaceProxy {
797
805
  });
798
806
  this.emitConversation(conversation);
799
807
  try {
800
- const result = await this.client.prompt({
808
+ const result = await client.prompt({
801
809
  sessionId: conversation.agentSessionId,
802
810
  content: payload.contentBlocks,
803
811
  clientMessageId: payload.clientMessageId,
@@ -1408,8 +1416,10 @@ export class AgentWorkspaceProxy {
1408
1416
  this.permissionSources.delete(payload.requestId);
1409
1417
  }
1410
1418
  else {
1411
- this.client?.respondPermission({
1412
- sessionId: this.conversations.get(payload.conversationId)?.agentSessionId,
1419
+ const conversation = this.conversations.get(payload.conversationId);
1420
+ const respondClient = conversation ? this.clientForProvider(conversation.provider) : undefined;
1421
+ respondClient?.respondPermission({
1422
+ sessionId: conversation?.agentSessionId,
1413
1423
  requestId: payload.requestId,
1414
1424
  outcome: payload.outcome === "cancelled" ? "deny" : payload.outcome,
1415
1425
  optionId: selectedOptionId,
@@ -1641,12 +1651,13 @@ export class AgentWorkspaceProxy {
1641
1651
  return this.conversationByAgentSessionId.get(threadId);
1642
1652
  return undefined;
1643
1653
  }
1644
- handleExit(message) {
1654
+ handleProviderExit(provider, message) {
1655
+ this.clients.delete(provider);
1656
+ this.agentProtocols.delete(provider);
1645
1657
  this.cancelPendingPermissions();
1646
- this.status = "error";
1647
- this.error = message;
1648
- this.client = undefined;
1649
1658
  for (const conversation of this.conversations.values()) {
1659
+ if (conversation.provider !== provider)
1660
+ continue;
1650
1661
  conversation.status = "error";
1651
1662
  conversation.lastMessagePreview = message;
1652
1663
  conversation.lastActivityAt = Date.now();
@@ -1659,6 +1670,7 @@ export class AgentWorkspaceProxy {
1659
1670
  createdAt: Date.now(),
1660
1671
  });
1661
1672
  }
1673
+ this.sendCapabilities();
1662
1674
  }
1663
1675
  cancelPendingPermissions(conversationId) {
1664
1676
  for (const [requestId, waiter] of this.permissionWaiters) {