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.
- package/dist/cli/src/index.js +1 -2
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +3 -1
- package/dist/cli/src/runtime/acp/agent-session.js +44 -34
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +7 -5
- package/dist/cli/src/runtime/acp/agent-workspace.js +96 -84
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +1 -0
- package/dist/cli/src/runtime/acp/provider-resolver.js +27 -0
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +1 -1
- package/dist/cli/src/runtime/bridge-session.js +18 -10
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -2
- package/src/runtime/acp/agent-session.ts +45 -35
- package/src/runtime/acp/agent-workspace.ts +97 -84
- package/src/runtime/acp/provider-resolver.ts +29 -0
- package/src/runtime/bridge-session.ts +17 -12
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
580
|
-
|
|
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
|
-
|
|
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
|
|
588
|
-
|
|
589
|
-
|
|
588
|
+
async ensureProviderClient(provider) {
|
|
589
|
+
const existing = this.clients.get(provider);
|
|
590
|
+
if (existing)
|
|
591
|
+
return existing;
|
|
590
592
|
const resolved = resolveAgentCommand({
|
|
591
|
-
provider
|
|
593
|
+
provider,
|
|
592
594
|
command: this.input.command,
|
|
593
595
|
});
|
|
594
596
|
if (!resolved) {
|
|
595
|
-
this.
|
|
596
|
-
|
|
597
|
-
|
|
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.
|
|
601
|
-
|
|
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.
|
|
611
|
+
onExit: (message) => this.handleProviderExit(provider, message),
|
|
609
612
|
});
|
|
610
|
-
await
|
|
611
|
-
this.
|
|
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.
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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:
|
|
635
|
-
providers
|
|
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:
|
|
654
|
-
supportsSessionList:
|
|
655
|
-
supportsSessionLoad:
|
|
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:
|
|
659
|
-
supportsPlan:
|
|
660
|
-
supportsCancel:
|
|
659
|
+
supportsPermission: anyEnabled,
|
|
660
|
+
supportsPlan: anyEnabled,
|
|
661
|
+
supportsCancel: anyEnabled,
|
|
661
662
|
},
|
|
662
663
|
}));
|
|
663
664
|
}
|
|
664
665
|
async openConversation(payload) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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.
|
|
671
|
-
return this.openFailure(payload,
|
|
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
|
|
695
|
-
: await
|
|
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
|
|
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.
|
|
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 || !
|
|
771
|
+
if (!conversation || !conversation.agentSessionId)
|
|
772
|
+
return;
|
|
773
|
+
const client = this.clientForProvider(conversation.provider);
|
|
774
|
+
if (!client)
|
|
768
775
|
return;
|
|
769
|
-
|
|
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
|
|
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.
|
|
1412
|
-
|
|
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
|
-
|
|
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) {
|