linkshell-cli 0.2.108 → 0.2.110
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/runtime/acp/acp-client.d.ts +5 -0
- package/dist/cli/src/runtime/acp/acp-client.js +16 -0
- package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +3 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +414 -3
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +1 -0
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +1 -0
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +2 -0
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.js +2 -1
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +1573 -448
- package/dist/shared-protocol/src/index.js +34 -0
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +3 -3
- package/src/runtime/acp/acp-client.ts +19 -0
- package/src/runtime/acp/agent-workspace.ts +489 -2
- package/src/runtime/acp/claude-sdk-client.ts +1 -0
- package/src/runtime/acp/claude-stream-json-client.ts +2 -0
- package/src/runtime/bridge-session.ts +2 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
|
|
2
2
|
type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
|
|
3
|
+
type AgentCollaborationMode = "default" | "plan";
|
|
3
4
|
export declare class AcpClient {
|
|
4
5
|
private readonly transport;
|
|
5
6
|
private readonly protocol;
|
|
@@ -31,6 +32,7 @@ export declare class AcpClient {
|
|
|
31
32
|
model?: string;
|
|
32
33
|
reasoningEffort?: string;
|
|
33
34
|
permissionMode?: AgentPermissionMode;
|
|
35
|
+
collaborationMode?: AgentCollaborationMode;
|
|
34
36
|
cwd: string;
|
|
35
37
|
}): Promise<unknown>;
|
|
36
38
|
cancel(input: {
|
|
@@ -43,6 +45,9 @@ export declare class AcpClient {
|
|
|
43
45
|
outcome: "allow" | "deny";
|
|
44
46
|
optionId?: string;
|
|
45
47
|
}): void;
|
|
48
|
+
compact(input: {
|
|
49
|
+
sessionId: string;
|
|
50
|
+
}): Promise<unknown>;
|
|
46
51
|
stop(): void;
|
|
47
52
|
}
|
|
48
53
|
export {};
|
|
@@ -96,11 +96,21 @@ export class AcpClient {
|
|
|
96
96
|
}
|
|
97
97
|
prompt(input) {
|
|
98
98
|
if (this.protocol === "codex-app-server") {
|
|
99
|
+
const collaborationMode = input.collaborationMode
|
|
100
|
+
? {
|
|
101
|
+
mode: input.collaborationMode,
|
|
102
|
+
settings: {
|
|
103
|
+
model: input.model ?? null,
|
|
104
|
+
reasoning_effort: input.reasoningEffort ?? null,
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
: undefined;
|
|
99
108
|
return this.transport.request("turn/start", {
|
|
100
109
|
threadId: input.sessionId,
|
|
101
110
|
model: input.model,
|
|
102
111
|
effort: input.reasoningEffort,
|
|
103
112
|
permissions: permissionsForMode(input.permissionMode, input.cwd),
|
|
113
|
+
collaborationMode,
|
|
104
114
|
input: input.content.map((block) => {
|
|
105
115
|
const raw = block;
|
|
106
116
|
if (raw.type === "image" && raw.data) {
|
|
@@ -136,6 +146,12 @@ export class AcpClient {
|
|
|
136
146
|
respondPermission(input) {
|
|
137
147
|
this.transport.notify("session/respond_permission", input);
|
|
138
148
|
}
|
|
149
|
+
compact(input) {
|
|
150
|
+
if (this.protocol === "codex-app-server") {
|
|
151
|
+
return this.transport.request("thread/compact/start", { threadId: input.sessionId });
|
|
152
|
+
}
|
|
153
|
+
return Promise.reject(new Error("Native compact is not supported by this provider."));
|
|
154
|
+
}
|
|
139
155
|
stop() {
|
|
140
156
|
this.transport.stop();
|
|
141
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-client.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/acp-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"acp-client.js","sourceRoot":"","sources":["../../../../../src/runtime/acp/acp-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,9 @@ export declare class AgentWorkspaceProxy {
|
|
|
40
40
|
private openConversation;
|
|
41
41
|
private openFailure;
|
|
42
42
|
private sendPrompt;
|
|
43
|
+
private commandForConversation;
|
|
44
|
+
private executeCommand;
|
|
45
|
+
private executeNativeCommand;
|
|
43
46
|
private handleRequest;
|
|
44
47
|
private handleNotification;
|
|
45
48
|
private handleAgentMessageDelta;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join, relative } from "node:path";
|
|
2
4
|
import { createEnvelope, parseTypedPayload, } from "@linkshell/protocol";
|
|
3
5
|
import { AcpClient } from "./acp-client.js";
|
|
4
6
|
import { ClaudeSdkClient } from "./claude-sdk-client.js";
|
|
@@ -509,6 +511,222 @@ function providerLabel(provider) {
|
|
|
509
511
|
}
|
|
510
512
|
const ALL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
511
513
|
const ALL_PERMISSION_MODES = ["read_only", "workspace_write", "full_access"];
|
|
514
|
+
const CODEX_COMMAND_NAMES = ["plan", "exit-plan", "compact", "clear", "status", "review", "subagents"];
|
|
515
|
+
const CLAUDE_BUILT_IN_COMMANDS = [
|
|
516
|
+
{ name: "add-dir", description: "Add additional working directories" },
|
|
517
|
+
{ name: "agents", description: "Manage subagents" },
|
|
518
|
+
{ name: "bug", description: "Report a Claude Code bug" },
|
|
519
|
+
{ name: "clear", description: "Clear conversation context", argsMode: "none", destructive: true },
|
|
520
|
+
{ name: "compact", description: "Compact conversation history" },
|
|
521
|
+
{ name: "config", description: "Open configuration" },
|
|
522
|
+
{ name: "cost", description: "Show usage cost" },
|
|
523
|
+
{ name: "doctor", description: "Check Claude Code health" },
|
|
524
|
+
{ name: "exit", description: "Exit Claude Code", argsMode: "none", destructive: true },
|
|
525
|
+
{ name: "export", description: "Export conversation" },
|
|
526
|
+
{ name: "help", description: "Show help" },
|
|
527
|
+
{ name: "ide", description: "Manage IDE integration" },
|
|
528
|
+
{ name: "init", description: "Create or update CLAUDE.md" },
|
|
529
|
+
{ name: "login", description: "Sign in" },
|
|
530
|
+
{ name: "logout", description: "Sign out" },
|
|
531
|
+
{ name: "mcp", description: "Manage MCP servers" },
|
|
532
|
+
{ name: "memory", description: "Edit memory files" },
|
|
533
|
+
{ name: "model", description: "Switch model" },
|
|
534
|
+
{ name: "permissions", description: "Manage permissions" },
|
|
535
|
+
{ name: "pr-comments", description: "Fetch PR comments" },
|
|
536
|
+
{ name: "release-notes", description: "Show release notes" },
|
|
537
|
+
{ name: "resume", description: "Resume a conversation" },
|
|
538
|
+
{ name: "review", description: "Review local changes" },
|
|
539
|
+
{ name: "security-review", description: "Run a security review" },
|
|
540
|
+
{ name: "status", description: "Show status" },
|
|
541
|
+
{ name: "statusline", description: "Configure status line" },
|
|
542
|
+
{ name: "terminal-setup", description: "Configure terminal integration" },
|
|
543
|
+
{ name: "upgrade", description: "Upgrade Claude Code" },
|
|
544
|
+
{ name: "vim", description: "Toggle vim mode" },
|
|
545
|
+
];
|
|
546
|
+
function commandId(provider, name, source = "built_in") {
|
|
547
|
+
return `${provider}:${source}:${name.replace(/^\/+/, "")}`;
|
|
548
|
+
}
|
|
549
|
+
function commandTitle(name) {
|
|
550
|
+
return `/${name.replace(/^\/+/, "")}`;
|
|
551
|
+
}
|
|
552
|
+
function makeCommand(input) {
|
|
553
|
+
const cleanName = input.name.replace(/^\/+/, "");
|
|
554
|
+
const source = input.source ?? "built_in";
|
|
555
|
+
return {
|
|
556
|
+
id: commandId(input.provider, cleanName, source),
|
|
557
|
+
name: cleanName,
|
|
558
|
+
title: commandTitle(cleanName),
|
|
559
|
+
description: input.description,
|
|
560
|
+
provider: input.provider,
|
|
561
|
+
source,
|
|
562
|
+
category: input.category,
|
|
563
|
+
argsMode: input.argsMode ?? "optional",
|
|
564
|
+
requiresIdle: input.requiresIdle,
|
|
565
|
+
destructive: input.destructive,
|
|
566
|
+
disabledReason: input.disabledReason,
|
|
567
|
+
executionKind: input.executionKind ?? "prompt",
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function commandFromMarkdownFile(provider, root, filePath, source) {
|
|
571
|
+
if (!filePath.endsWith(".md"))
|
|
572
|
+
return undefined;
|
|
573
|
+
const rel = relative(root, filePath).replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
574
|
+
const name = rel.split("/").filter(Boolean).join(":");
|
|
575
|
+
if (!name)
|
|
576
|
+
return undefined;
|
|
577
|
+
let description;
|
|
578
|
+
try {
|
|
579
|
+
const text = readFileSync(filePath, "utf8");
|
|
580
|
+
description = text.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !line.startsWith("---"))?.slice(0, 160);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
description = undefined;
|
|
584
|
+
}
|
|
585
|
+
return makeCommand({
|
|
586
|
+
provider,
|
|
587
|
+
name,
|
|
588
|
+
description: description || "Custom Claude command",
|
|
589
|
+
source,
|
|
590
|
+
category: source === "project" ? "Project commands" : "User commands",
|
|
591
|
+
argsMode: "raw",
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function walkMarkdownCommands(provider, root, source) {
|
|
595
|
+
if (!existsSync(root))
|
|
596
|
+
return [];
|
|
597
|
+
const result = [];
|
|
598
|
+
const walk = (dir) => {
|
|
599
|
+
let entries = [];
|
|
600
|
+
try {
|
|
601
|
+
entries = readdirSync(dir);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
for (const entry of entries) {
|
|
607
|
+
const path = join(dir, entry);
|
|
608
|
+
let stat;
|
|
609
|
+
try {
|
|
610
|
+
stat = statSync(path);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (stat.isDirectory())
|
|
616
|
+
walk(path);
|
|
617
|
+
else if (stat.isFile()) {
|
|
618
|
+
const command = commandFromMarkdownFile(provider, root, path, source);
|
|
619
|
+
if (command)
|
|
620
|
+
result.push(command);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
walk(root);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
function customClaudeCommands(cwd) {
|
|
628
|
+
const projectCommands = walkMarkdownCommands("claude", join(cwd, ".claude", "commands"), "project");
|
|
629
|
+
const userCommands = walkMarkdownCommands("claude", join(homedir(), ".claude", "commands"), "user");
|
|
630
|
+
return [...projectCommands, ...userCommands];
|
|
631
|
+
}
|
|
632
|
+
function defaultProviderCommands(provider, cwd, enabled) {
|
|
633
|
+
const disabledReason = enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`;
|
|
634
|
+
if (provider === "codex") {
|
|
635
|
+
return CODEX_COMMAND_NAMES.map((name) => makeCommand({
|
|
636
|
+
provider,
|
|
637
|
+
name,
|
|
638
|
+
source: "linkshell",
|
|
639
|
+
category: name === "plan" || name === "exit-plan" ? "Modes" : "Codex",
|
|
640
|
+
description: {
|
|
641
|
+
"plan": "Enter Codex plan mode",
|
|
642
|
+
"exit-plan": "Exit Codex plan mode",
|
|
643
|
+
compact: "Compact the current thread",
|
|
644
|
+
clear: "Start a fresh Codex thread",
|
|
645
|
+
status: "Show LinkShell agent status",
|
|
646
|
+
review: "Ask Codex to review local changes",
|
|
647
|
+
subagents: "Insert a delegation prompt",
|
|
648
|
+
}[name],
|
|
649
|
+
argsMode: name === "review" || name === "subagents" ? "optional" : "none",
|
|
650
|
+
destructive: name === "clear",
|
|
651
|
+
disabledReason,
|
|
652
|
+
executionKind: name === "review" || name === "subagents" ? "prompt" : "native",
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
if (provider === "claude") {
|
|
656
|
+
const builtIns = CLAUDE_BUILT_IN_COMMANDS.map((entry) => makeCommand({
|
|
657
|
+
provider,
|
|
658
|
+
name: entry.name,
|
|
659
|
+
description: entry.description,
|
|
660
|
+
argsMode: entry.argsMode,
|
|
661
|
+
destructive: entry.destructive,
|
|
662
|
+
disabledReason,
|
|
663
|
+
executionKind: "prompt",
|
|
664
|
+
}));
|
|
665
|
+
const custom = customClaudeCommands(cwd).map((command) => ({
|
|
666
|
+
...command,
|
|
667
|
+
disabledReason: command.disabledReason ?? disabledReason,
|
|
668
|
+
}));
|
|
669
|
+
return [...builtIns, ...custom];
|
|
670
|
+
}
|
|
671
|
+
return [
|
|
672
|
+
makeCommand({
|
|
673
|
+
provider,
|
|
674
|
+
name: "status",
|
|
675
|
+
source: "linkshell",
|
|
676
|
+
category: "LinkShell",
|
|
677
|
+
description: "Show LinkShell agent status",
|
|
678
|
+
argsMode: "none",
|
|
679
|
+
disabledReason,
|
|
680
|
+
executionKind: "native",
|
|
681
|
+
}),
|
|
682
|
+
];
|
|
683
|
+
}
|
|
684
|
+
function mergeCommands(...groups) {
|
|
685
|
+
const map = new Map();
|
|
686
|
+
for (const group of groups) {
|
|
687
|
+
for (const command of group ?? []) {
|
|
688
|
+
const key = `${command.provider ?? ""}:${command.name}`;
|
|
689
|
+
map.set(key, { ...map.get(key), ...command });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
693
|
+
}
|
|
694
|
+
function runtimeCommands(provider, value) {
|
|
695
|
+
const raw = asRecord(value);
|
|
696
|
+
const commandsValue = Array.isArray(value) ? value :
|
|
697
|
+
Array.isArray(raw?.commands) ? raw.commands :
|
|
698
|
+
Array.isArray(raw?.slashCommands) ? raw.slashCommands :
|
|
699
|
+
Array.isArray(raw?.slash_commands) ? raw.slash_commands :
|
|
700
|
+
Array.isArray(raw?.available_commands) ? raw.available_commands :
|
|
701
|
+
[];
|
|
702
|
+
return commandsValue
|
|
703
|
+
.map((entry) => {
|
|
704
|
+
if (typeof entry === "string") {
|
|
705
|
+
return makeCommand({
|
|
706
|
+
provider,
|
|
707
|
+
name: entry,
|
|
708
|
+
description: undefined,
|
|
709
|
+
source: "built_in",
|
|
710
|
+
argsMode: "raw",
|
|
711
|
+
executionKind: "prompt",
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const record = asRecord(entry);
|
|
715
|
+
const name = firstString(record, ["name", "command", "id"]);
|
|
716
|
+
if (!name)
|
|
717
|
+
return undefined;
|
|
718
|
+
return makeCommand({
|
|
719
|
+
provider,
|
|
720
|
+
name,
|
|
721
|
+
description: firstString(record, ["description", "summary"]),
|
|
722
|
+
source: "built_in",
|
|
723
|
+
category: firstString(record, ["category", "group"]),
|
|
724
|
+
argsMode: "raw",
|
|
725
|
+
executionKind: "prompt",
|
|
726
|
+
});
|
|
727
|
+
})
|
|
728
|
+
.filter((entry) => Boolean(entry));
|
|
729
|
+
}
|
|
512
730
|
function parseModelListCapabilities(value) {
|
|
513
731
|
const raw = asRecord(value);
|
|
514
732
|
const modelsValue = Array.isArray(value) ? value :
|
|
@@ -641,6 +859,11 @@ export class AgentWorkspaceProxy {
|
|
|
641
859
|
await this.sendPrompt(payload);
|
|
642
860
|
break;
|
|
643
861
|
}
|
|
862
|
+
case "agent.v2.command.execute": {
|
|
863
|
+
const payload = parseTypedPayload("agent.v2.command.execute", envelope.payload);
|
|
864
|
+
await this.executeCommand(payload);
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
644
867
|
case "agent.v2.cancel": {
|
|
645
868
|
const payload = parseTypedPayload("agent.v2.cancel", envelope.payload);
|
|
646
869
|
const conversation = this.conversations.get(payload.conversationId);
|
|
@@ -820,6 +1043,7 @@ export class AgentWorkspaceProxy {
|
|
|
820
1043
|
model: remote.model ?? existing?.model,
|
|
821
1044
|
reasoningEffort: existing?.reasoningEffort,
|
|
822
1045
|
permissionMode: existing?.permissionMode,
|
|
1046
|
+
collaborationMode: existing?.collaborationMode,
|
|
823
1047
|
status: existing?.status ?? "idle",
|
|
824
1048
|
archived: existing?.archived ?? false,
|
|
825
1049
|
lastMessagePreview: existing?.lastMessagePreview,
|
|
@@ -848,6 +1072,8 @@ export class AgentWorkspaceProxy {
|
|
|
848
1072
|
const isClaudeFallback = protocol === "claude-stream-json";
|
|
849
1073
|
const supportsPermission = enabled && !isClaudeFallback;
|
|
850
1074
|
const supportsReasoningEffort = enabled && !isClaudeFallback;
|
|
1075
|
+
const commands = mergeCommands(defaultProviderCommands(provider, this.input.cwd, enabled), runtimeCapabilities?.commands);
|
|
1076
|
+
const currentMode = [...this.conversations.values()].find((conversation) => conversation.provider === provider)?.collaborationMode;
|
|
851
1077
|
return {
|
|
852
1078
|
id: provider,
|
|
853
1079
|
label: providerLabel(provider),
|
|
@@ -863,6 +1089,12 @@ export class AgentWorkspaceProxy {
|
|
|
863
1089
|
? runtimeCapabilities?.reasoningEfforts ?? [...ALL_REASONING_EFFORTS]
|
|
864
1090
|
: [],
|
|
865
1091
|
permissionModes: supportsPermission ? [...ALL_PERMISSION_MODES] : [],
|
|
1092
|
+
commands,
|
|
1093
|
+
modes: runtimeCapabilities?.modes ?? (provider === "codex" ? [
|
|
1094
|
+
{ id: "default", title: "Default", description: "Run normal implementation turns" },
|
|
1095
|
+
{ id: "plan", title: "Plan", description: "Discuss and produce an implementation plan first" },
|
|
1096
|
+
] : []),
|
|
1097
|
+
currentMode,
|
|
866
1098
|
features: {
|
|
867
1099
|
images: supportsImages,
|
|
868
1100
|
permissions: supportsPermission,
|
|
@@ -943,6 +1175,7 @@ export class AgentWorkspaceProxy {
|
|
|
943
1175
|
model: payload.model ?? existingConversation?.model,
|
|
944
1176
|
reasoningEffort: payload.reasoningEffort ?? existingConversation?.reasoningEffort,
|
|
945
1177
|
permissionMode: payload.permissionMode ?? existingConversation?.permissionMode,
|
|
1178
|
+
collaborationMode: payload.collaborationMode ?? existingConversation?.collaborationMode,
|
|
946
1179
|
status: "idle",
|
|
947
1180
|
archived: existingConversation?.archived ?? false,
|
|
948
1181
|
lastMessagePreview: existingConversation?.status === "error" ? undefined : existingConversation?.lastMessagePreview,
|
|
@@ -976,6 +1209,7 @@ export class AgentWorkspaceProxy {
|
|
|
976
1209
|
model: payload.model,
|
|
977
1210
|
reasoningEffort: payload.reasoningEffort,
|
|
978
1211
|
permissionMode: payload.permissionMode,
|
|
1212
|
+
collaborationMode: payload.collaborationMode,
|
|
979
1213
|
status: "error",
|
|
980
1214
|
archived: false,
|
|
981
1215
|
lastMessagePreview: message,
|
|
@@ -1023,6 +1257,7 @@ export class AgentWorkspaceProxy {
|
|
|
1023
1257
|
conversation.model = payload.model ?? conversation.model;
|
|
1024
1258
|
conversation.reasoningEffort = payload.reasoningEffort ?? conversation.reasoningEffort;
|
|
1025
1259
|
conversation.permissionMode = payload.permissionMode ?? conversation.permissionMode;
|
|
1260
|
+
conversation.collaborationMode = payload.collaborationMode ?? conversation.collaborationMode;
|
|
1026
1261
|
conversation.status = "running";
|
|
1027
1262
|
conversation.lastActivityAt = Date.now();
|
|
1028
1263
|
this.activeConversationId = conversation.id;
|
|
@@ -1045,6 +1280,7 @@ export class AgentWorkspaceProxy {
|
|
|
1045
1280
|
model: payload.model,
|
|
1046
1281
|
reasoningEffort: payload.reasoningEffort,
|
|
1047
1282
|
permissionMode: payload.permissionMode,
|
|
1283
|
+
collaborationMode: payload.collaborationMode ?? conversation.collaborationMode,
|
|
1048
1284
|
cwd: conversation.cwd,
|
|
1049
1285
|
});
|
|
1050
1286
|
const nextAgentSessionId = this.extractSessionId(result);
|
|
@@ -1073,6 +1309,166 @@ export class AgentWorkspaceProxy {
|
|
|
1073
1309
|
});
|
|
1074
1310
|
}
|
|
1075
1311
|
}
|
|
1312
|
+
commandForConversation(conversation, commandId) {
|
|
1313
|
+
const runtimeCapabilities = this.providerCapabilities.get(conversation.provider);
|
|
1314
|
+
const commands = mergeCommands(defaultProviderCommands(conversation.provider, conversation.cwd, true), runtimeCapabilities?.commands);
|
|
1315
|
+
return commands.find((command) => command.id === commandId ||
|
|
1316
|
+
command.name === commandId ||
|
|
1317
|
+
`/${command.name}` === commandId);
|
|
1318
|
+
}
|
|
1319
|
+
async executeCommand(payload) {
|
|
1320
|
+
const conversation = this.conversations.get(payload.conversationId) ??
|
|
1321
|
+
await this.openConversation({ conversationId: payload.conversationId });
|
|
1322
|
+
if (!conversation || !conversation.agentSessionId)
|
|
1323
|
+
return;
|
|
1324
|
+
const command = this.commandForConversation(conversation, payload.commandId);
|
|
1325
|
+
if (!command) {
|
|
1326
|
+
this.addItem(conversation.id, {
|
|
1327
|
+
id: id("error"),
|
|
1328
|
+
conversationId: conversation.id,
|
|
1329
|
+
type: "error",
|
|
1330
|
+
error: `未知命令:${payload.commandId}`,
|
|
1331
|
+
createdAt: Date.now(),
|
|
1332
|
+
});
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (command.disabledReason) {
|
|
1336
|
+
this.addItem(conversation.id, {
|
|
1337
|
+
id: id("error"),
|
|
1338
|
+
conversationId: conversation.id,
|
|
1339
|
+
type: "error",
|
|
1340
|
+
error: command.disabledReason,
|
|
1341
|
+
createdAt: Date.now(),
|
|
1342
|
+
});
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
const rawText = payload.rawText?.trim() || `/${command.name}${payload.args?.trim() ? ` ${payload.args.trim()}` : ""}`;
|
|
1346
|
+
if (command.executionKind === "prompt") {
|
|
1347
|
+
await this.sendPrompt({
|
|
1348
|
+
conversationId: conversation.id,
|
|
1349
|
+
clientMessageId: payload.clientMessageId,
|
|
1350
|
+
contentBlocks: [{ type: "text", text: rawText }],
|
|
1351
|
+
model: conversation.model,
|
|
1352
|
+
reasoningEffort: conversation.reasoningEffort,
|
|
1353
|
+
permissionMode: conversation.permissionMode,
|
|
1354
|
+
collaborationMode: conversation.collaborationMode,
|
|
1355
|
+
});
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
this.addItem(conversation.id, {
|
|
1359
|
+
id: payload.clientMessageId,
|
|
1360
|
+
conversationId: conversation.id,
|
|
1361
|
+
type: "message",
|
|
1362
|
+
kind: "chat",
|
|
1363
|
+
role: "user",
|
|
1364
|
+
content: [{ type: "text", text: rawText }],
|
|
1365
|
+
text: rawText,
|
|
1366
|
+
metadata: { commandId: command.id, commandExecutionKind: command.executionKind },
|
|
1367
|
+
createdAt: Date.now(),
|
|
1368
|
+
});
|
|
1369
|
+
if (command.executionKind === "local_ui") {
|
|
1370
|
+
this.emitStatus(conversation.id, "idle", `${command.title} 已由移动端处理。`);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
await this.executeNativeCommand(conversation, command, payload.args?.trim());
|
|
1374
|
+
}
|
|
1375
|
+
async executeNativeCommand(conversation, command, args) {
|
|
1376
|
+
const client = this.clientForProvider(conversation.provider);
|
|
1377
|
+
const now = Date.now();
|
|
1378
|
+
try {
|
|
1379
|
+
if (command.name === "status") {
|
|
1380
|
+
this.emitStatus(conversation.id, conversation.status, `${providerLabel(conversation.provider)} · ${conversation.collaborationMode === "plan" ? "Plan mode" : "Default mode"} · ${conversation.cwd}`);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (conversation.provider !== "codex") {
|
|
1384
|
+
this.addItem(conversation.id, {
|
|
1385
|
+
id: id("error"),
|
|
1386
|
+
conversationId: conversation.id,
|
|
1387
|
+
type: "error",
|
|
1388
|
+
error: `${command.title} 暂无 ${providerLabel(conversation.provider)} 原生实现。`,
|
|
1389
|
+
createdAt: now,
|
|
1390
|
+
});
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if (command.name === "plan" || command.name === "exit-plan") {
|
|
1394
|
+
conversation.collaborationMode = command.name === "plan" ? "plan" : "default";
|
|
1395
|
+
conversation.status = "idle";
|
|
1396
|
+
conversation.lastMessagePreview = command.name === "plan" ? "已进入 Plan mode" : "已退出 Plan mode";
|
|
1397
|
+
conversation.lastActivityAt = now;
|
|
1398
|
+
this.emitConversation(conversation);
|
|
1399
|
+
this.sendCapabilities();
|
|
1400
|
+
this.emitStatus(conversation.id, "idle", command.name === "plan"
|
|
1401
|
+
? "已进入 Plan mode。下一条消息会先制定计划。"
|
|
1402
|
+
: "已退出 Plan mode。");
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (command.name === "compact") {
|
|
1406
|
+
if (!(client instanceof AcpClient))
|
|
1407
|
+
throw new Error("当前 Codex runtime 不支持原生 compact。");
|
|
1408
|
+
conversation.status = "running";
|
|
1409
|
+
this.emitConversation(conversation);
|
|
1410
|
+
this.addItem(conversation.id, {
|
|
1411
|
+
id: id("compact"),
|
|
1412
|
+
conversationId: conversation.id,
|
|
1413
|
+
type: "status",
|
|
1414
|
+
kind: "context_compaction",
|
|
1415
|
+
text: "正在压缩上下文",
|
|
1416
|
+
status: "running",
|
|
1417
|
+
isStreaming: true,
|
|
1418
|
+
createdAt: now,
|
|
1419
|
+
});
|
|
1420
|
+
await client.compact({ sessionId: conversation.agentSessionId });
|
|
1421
|
+
this.updateConversationStatus(conversation.id, "idle", "上下文压缩完成");
|
|
1422
|
+
this.emitStatus(conversation.id, "idle", "上下文压缩完成。");
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (command.name === "clear") {
|
|
1426
|
+
if (!client)
|
|
1427
|
+
throw new Error("Agent provider 不在线。");
|
|
1428
|
+
const result = await client.newSession({ cwd: conversation.cwd });
|
|
1429
|
+
const nextAgentSessionId = this.extractSessionId(result) ?? id("agent-session");
|
|
1430
|
+
if (conversation.agentSessionId)
|
|
1431
|
+
this.conversationByAgentSessionId.delete(conversation.agentSessionId);
|
|
1432
|
+
conversation.agentSessionId = nextAgentSessionId;
|
|
1433
|
+
conversation.collaborationMode = "default";
|
|
1434
|
+
conversation.status = "idle";
|
|
1435
|
+
conversation.lastMessagePreview = "上下文已重置";
|
|
1436
|
+
conversation.lastActivityAt = now;
|
|
1437
|
+
this.conversationByAgentSessionId.set(nextAgentSessionId, conversation.id);
|
|
1438
|
+
this.timelines.set(conversation.id, []);
|
|
1439
|
+
this.emitConversation(conversation);
|
|
1440
|
+
this.emitStatus(conversation.id, "idle", "上下文已重置,已创建新的 Codex thread。");
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
if (command.name === "review" || command.name === "subagents") {
|
|
1444
|
+
const prompt = command.name === "review"
|
|
1445
|
+
? args || "Review the current local changes."
|
|
1446
|
+
: args || "Run subagents for distinct tasks in parallel when useful, then synthesize the results.";
|
|
1447
|
+
await this.sendPrompt({
|
|
1448
|
+
conversationId: conversation.id,
|
|
1449
|
+
clientMessageId: id(command.name),
|
|
1450
|
+
contentBlocks: [{ type: "text", text: prompt }],
|
|
1451
|
+
model: conversation.model,
|
|
1452
|
+
reasoningEffort: conversation.reasoningEffort,
|
|
1453
|
+
permissionMode: conversation.permissionMode,
|
|
1454
|
+
collaborationMode: conversation.collaborationMode,
|
|
1455
|
+
});
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
throw new Error(`命令暂未实现:/${command.name}`);
|
|
1459
|
+
}
|
|
1460
|
+
catch (error) {
|
|
1461
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1462
|
+
this.updateConversationStatus(conversation.id, "error", message);
|
|
1463
|
+
this.addItem(conversation.id, {
|
|
1464
|
+
id: id("error"),
|
|
1465
|
+
conversationId: conversation.id,
|
|
1466
|
+
type: "error",
|
|
1467
|
+
error: message,
|
|
1468
|
+
createdAt: Date.now(),
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1076
1472
|
handleRequest(method, params) {
|
|
1077
1473
|
if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
|
|
1078
1474
|
return this.handleStructuredInput(params, true);
|
|
@@ -1092,8 +1488,23 @@ export class AgentWorkspaceProxy {
|
|
|
1092
1488
|
if (this.input.verbose) {
|
|
1093
1489
|
process.stderr.write(`[agent:v2] ${method} ${stringify(params).slice(0, 500)}\n`);
|
|
1094
1490
|
}
|
|
1095
|
-
if (method === "initialized"
|
|
1096
|
-
|
|
1491
|
+
if (method === "initialized") {
|
|
1492
|
+
const conversationId = this.conversationIdFromParams(params) ?? this.activeConversationId;
|
|
1493
|
+
const provider = conversationId ? this.conversations.get(conversationId)?.provider : this.input.availableProviders[0];
|
|
1494
|
+
if (provider) {
|
|
1495
|
+
const commands = runtimeCommands(provider, params);
|
|
1496
|
+
if (commands.length > 0) {
|
|
1497
|
+
const existing = this.providerCapabilities.get(provider);
|
|
1498
|
+
this.providerCapabilities.set(provider, {
|
|
1499
|
+
...(existing ?? {}),
|
|
1500
|
+
commands: mergeCommands(existing?.commands, commands),
|
|
1501
|
+
});
|
|
1502
|
+
this.sendCapabilities();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
if (method.startsWith("account/") ||
|
|
1097
1508
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
1098
1509
|
method === "thread/status/changed" ||
|
|
1099
1510
|
method === "thread/tokenUsage/updated" ||
|