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.
- package/dist/cli/src/commands/setup.js +20 -2
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/commands/upgrade.js +9 -24
- package/dist/cli/src/commands/upgrade.js.map +1 -1
- package/dist/cli/src/index.js +28 -26
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/providers.d.ts +3 -7
- package/dist/cli/src/providers.js +76 -19
- package/dist/cli/src/providers.js.map +1 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -10
- package/dist/cli/src/runtime/acp/acp-client.js +20 -146
- package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +62 -0
- package/dist/cli/src/runtime/acp/agent-session.js +1075 -0
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -0
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +2 -27
- package/dist/cli/src/runtime/acp/agent-workspace.js +224 -1008
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +1 -3
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +50 -82
- 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 -2
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +36 -54
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +1 -34
- package/dist/cli/src/runtime/acp/json-rpc.js +8 -32
- package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -3
- package/dist/cli/src/runtime/acp/provider-resolver.js +6 -26
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +31 -1
- package/dist/cli/src/runtime/bridge-session.js +991 -81
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/src/runtime/screen-fallback.d.ts +1 -1
- package/dist/cli/src/runtime/screen-fallback.js +4 -4
- package/dist/cli/src/runtime/screen-fallback.js.map +1 -1
- package/dist/cli/src/runtime/screen-share.d.ts +1 -1
- package/dist/cli/src/runtime/screen-share.js +7 -7
- package/dist/cli/src/runtime/screen-share.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +11978 -3423
- package/dist/shared-protocol/src/index.js +114 -163
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +13 -13
- package/src/commands/setup.ts +31 -5
- package/src/commands/upgrade.ts +8 -28
- package/src/index.ts +34 -29
- package/src/providers.ts +108 -26
- package/src/runtime/acp/acp-client.ts +22 -159
- package/src/runtime/acp/agent-session.ts +1180 -0
- package/src/runtime/acp/agent-workspace.ts +246 -1094
- package/src/runtime/acp/claude-sdk-client.ts +51 -86
- package/src/runtime/acp/claude-stream-json-client.ts +42 -59
- package/src/runtime/acp/json-rpc.ts +14 -46
- package/src/runtime/acp/provider-resolver.ts +6 -29
- package/src/runtime/bridge-session.ts +1092 -84
- package/src/runtime/screen-fallback.ts +5 -5
- package/src/runtime/screen-share.ts +8 -8
- package/src/runtime/acp/claude-sessions.ts +0 -563
- package/src/runtime/acp/codex-rpc-bridge.ts +0 -233
- package/src/runtime/acp/codex-sessions.ts +0 -802
- package/src/types/linkshell-gateway.d.ts +0 -18
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { basename, join, relative } from "node:path";
|
|
5
4
|
import {
|
|
@@ -10,9 +9,6 @@ import {
|
|
|
10
9
|
import { AcpClient } from "./acp-client.js";
|
|
11
10
|
import { ClaudeSdkClient } from "./claude-sdk-client.js";
|
|
12
11
|
import { ClaudeStreamJsonClient } from "./claude-stream-json-client.js";
|
|
13
|
-
import { CodexRpcBridge } from "./codex-rpc-bridge.js";
|
|
14
|
-
import { listClaudeStoredSessions, loadClaudeStoredTimeline } from "./claude-sessions.js";
|
|
15
|
-
import { loadCodexStoredTimeline } from "./codex-sessions.js";
|
|
16
12
|
import type { AgentProtocol, AgentProvider } from "./provider-resolver.js";
|
|
17
13
|
import { resolveAgentCommand } from "./provider-resolver.js";
|
|
18
14
|
|
|
@@ -21,15 +17,12 @@ type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
|
|
|
21
17
|
type AgentCollaborationMode = "default" | "plan";
|
|
22
18
|
type AgentCommandExecutionKind = "prompt" | "native" | "local_ui";
|
|
23
19
|
type AgentCommandSource = "built_in" | "custom" | "project" | "user" | "linkshell";
|
|
24
|
-
type AgentSyncSource = "device" | "device-history" | "device-live" | "app-server" | "cache";
|
|
25
20
|
|
|
26
21
|
interface AgentContentBlock {
|
|
27
|
-
type: "text" | "image"
|
|
22
|
+
type: "text" | "image";
|
|
28
23
|
text?: string;
|
|
29
24
|
data?: string;
|
|
30
25
|
mimeType?: string;
|
|
31
|
-
path?: string;
|
|
32
|
-
name?: string;
|
|
33
26
|
}
|
|
34
27
|
|
|
35
28
|
interface AgentToolCall {
|
|
@@ -165,16 +158,10 @@ interface AgentConversation {
|
|
|
165
158
|
title?: string;
|
|
166
159
|
model?: string;
|
|
167
160
|
reasoningEffort?: string;
|
|
168
|
-
serviceTier?: string;
|
|
169
161
|
permissionMode?: AgentPermissionMode;
|
|
170
162
|
collaborationMode?: AgentCollaborationMode;
|
|
171
163
|
status: AgentStatus;
|
|
172
164
|
archived: boolean;
|
|
173
|
-
timelineRevision?: number;
|
|
174
|
-
historyComplete?: boolean;
|
|
175
|
-
runningTurnId?: string;
|
|
176
|
-
source?: AgentSyncSource;
|
|
177
|
-
canonical?: boolean;
|
|
178
165
|
lastMessagePreview?: string;
|
|
179
166
|
lastActivityAt: number;
|
|
180
167
|
createdAt: number;
|
|
@@ -185,9 +172,6 @@ interface AgentTimelineItem {
|
|
|
185
172
|
conversationId: string;
|
|
186
173
|
type: "message" | "tool_call" | "plan" | "permission" | "status" | "error";
|
|
187
174
|
kind?: AgentTimelineKind;
|
|
188
|
-
revision?: number;
|
|
189
|
-
source?: AgentSyncSource;
|
|
190
|
-
canonical?: boolean;
|
|
191
175
|
turnId?: string;
|
|
192
176
|
itemId?: string;
|
|
193
177
|
role?: "user" | "assistant" | "system";
|
|
@@ -219,51 +203,13 @@ interface PendingStructuredInputWaiter {
|
|
|
219
203
|
source?: string;
|
|
220
204
|
}
|
|
221
205
|
|
|
222
|
-
interface AgentRevisionEvent {
|
|
223
|
-
revision: number;
|
|
224
|
-
item?: AgentTimelineItem;
|
|
225
|
-
conversation?: AgentConversation;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
206
|
const PERMISSION_TIMEOUT_MS = 5 * 60_000;
|
|
229
207
|
const MAX_TIMELINE_ITEMS = 200;
|
|
230
|
-
const MAX_SNAPSHOT_ITEMS = 80;
|
|
231
|
-
const MAX_SNAPSHOT_TEXT_BYTES = 128 * 1024;
|
|
232
|
-
const MAX_DELTA_EVENTS = 500;
|
|
233
|
-
const HISTORY_PAGE_MAX_ITEMS = 500;
|
|
234
208
|
|
|
235
209
|
function id(prefix: string): string {
|
|
236
210
|
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
237
211
|
}
|
|
238
212
|
|
|
239
|
-
function clampHistoryCursor(value: string | undefined, fallback: number, max: number): number {
|
|
240
|
-
if (!value) return fallback;
|
|
241
|
-
const parsed = Number.parseInt(value, 10);
|
|
242
|
-
if (!Number.isFinite(parsed)) return fallback;
|
|
243
|
-
return Math.max(0, Math.min(max, parsed));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function appServerText(value: unknown): string | undefined {
|
|
247
|
-
if (typeof value === "string") {
|
|
248
|
-
const text = value.trim();
|
|
249
|
-
return text || undefined;
|
|
250
|
-
}
|
|
251
|
-
if (Array.isArray(value)) {
|
|
252
|
-
const text = value
|
|
253
|
-
.map((part) => appServerText(part))
|
|
254
|
-
.filter(Boolean)
|
|
255
|
-
.join("\n")
|
|
256
|
-
.trim();
|
|
257
|
-
return text || undefined;
|
|
258
|
-
}
|
|
259
|
-
const record = asRecord(value);
|
|
260
|
-
if (!record) return undefined;
|
|
261
|
-
if (typeof record.text === "string") return appServerText(record.text);
|
|
262
|
-
if (typeof record.content === "string") return appServerText(record.content);
|
|
263
|
-
if (typeof record.message === "string") return appServerText(record.message);
|
|
264
|
-
return appServerText(record.content ?? record.message ?? record.parts);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
213
|
function stringify(value: unknown): string {
|
|
268
214
|
if (typeof value === "string") return value;
|
|
269
215
|
try {
|
|
@@ -273,71 +219,6 @@ function stringify(value: unknown): string {
|
|
|
273
219
|
}
|
|
274
220
|
}
|
|
275
221
|
|
|
276
|
-
function truncateUtf8(value: string | undefined, maxBytes = MAX_SNAPSHOT_TEXT_BYTES): string | undefined {
|
|
277
|
-
if (!value) return value;
|
|
278
|
-
if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
|
|
279
|
-
let end = Math.min(value.length, maxBytes);
|
|
280
|
-
while (end > 0 && Buffer.byteLength(value.slice(0, end), "utf8") > maxBytes) {
|
|
281
|
-
end = Math.floor(end * 0.9);
|
|
282
|
-
}
|
|
283
|
-
return `${value.slice(0, end)}\n\n[truncated by LinkShell: original ${Buffer.byteLength(value, "utf8")} bytes]`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function snapshotContentBlocks(
|
|
287
|
-
blocks: AgentContentBlock[] | undefined,
|
|
288
|
-
options: { stripImages?: boolean } = {},
|
|
289
|
-
): AgentContentBlock[] | undefined {
|
|
290
|
-
if (!blocks) return undefined;
|
|
291
|
-
return blocks.map((block) =>
|
|
292
|
-
block.type === "image" && options.stripImages !== false
|
|
293
|
-
? { ...block, data: undefined, text: block.text || "图片附件" }
|
|
294
|
-
: { ...block, text: truncateUtf8(block.text) },
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function snapshotTimelineItem(
|
|
299
|
-
item: AgentTimelineItem,
|
|
300
|
-
options: { stripImages?: boolean } = {},
|
|
301
|
-
): AgentTimelineItem {
|
|
302
|
-
return {
|
|
303
|
-
...item,
|
|
304
|
-
content: snapshotContentBlocks(item.content, options),
|
|
305
|
-
text: truncateUtf8(item.text),
|
|
306
|
-
toolCall: item.toolCall
|
|
307
|
-
? {
|
|
308
|
-
...item.toolCall,
|
|
309
|
-
input: truncateUtf8(item.toolCall.input),
|
|
310
|
-
output: truncateUtf8(item.toolCall.output),
|
|
311
|
-
}
|
|
312
|
-
: undefined,
|
|
313
|
-
commandExecution: item.commandExecution
|
|
314
|
-
? {
|
|
315
|
-
...item.commandExecution,
|
|
316
|
-
command: truncateUtf8(item.commandExecution.command, 16 * 1024),
|
|
317
|
-
output: truncateUtf8(item.commandExecution.output),
|
|
318
|
-
}
|
|
319
|
-
: undefined,
|
|
320
|
-
fileChange: item.fileChange
|
|
321
|
-
? {
|
|
322
|
-
...item.fileChange,
|
|
323
|
-
diff: truncateUtf8(item.fileChange.diff),
|
|
324
|
-
summary: truncateUtf8(item.fileChange.summary),
|
|
325
|
-
}
|
|
326
|
-
: undefined,
|
|
327
|
-
permission: item.permission
|
|
328
|
-
? {
|
|
329
|
-
...item.permission,
|
|
330
|
-
toolInput: truncateUtf8(item.permission.toolInput),
|
|
331
|
-
context: truncateUtf8(item.permission.context),
|
|
332
|
-
}
|
|
333
|
-
: undefined,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function snapshotTimelineItems(items: AgentTimelineItem[]): AgentTimelineItem[] {
|
|
338
|
-
return items.slice(-MAX_SNAPSHOT_ITEMS).map((item) => snapshotTimelineItem(item));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
222
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
342
223
|
return typeof value === "object" && value ? value as Record<string, unknown> : undefined;
|
|
343
224
|
}
|
|
@@ -643,12 +524,7 @@ function titleFromCwd(cwd: string): string {
|
|
|
643
524
|
|
|
644
525
|
function textFromBlocks(blocks: AgentContentBlock[]): string {
|
|
645
526
|
return blocks
|
|
646
|
-
.map((block) => {
|
|
647
|
-
if (block.type === "text") return block.text ?? "";
|
|
648
|
-
if (block.type === "image") return `[${block.mimeType ?? "image"} attachment]`;
|
|
649
|
-
if (block.type === "file" && block.path) return `@${block.path}`;
|
|
650
|
-
return "";
|
|
651
|
-
})
|
|
527
|
+
.map((block) => block.type === "text" ? block.text ?? "" : `[${block.mimeType ?? "image"} attachment]`)
|
|
652
528
|
.filter(Boolean)
|
|
653
529
|
.join("\n");
|
|
654
530
|
}
|
|
@@ -681,11 +557,6 @@ function contentBlocksFromValue(value: unknown): AgentContentBlock[] {
|
|
|
681
557
|
const text = firstString(raw, ["text", "content", "message"]);
|
|
682
558
|
return text ? [{ type: "text", text }] : [];
|
|
683
559
|
}
|
|
684
|
-
if (normalizedType === "file" || normalizedType === "inputfile" || normalizedType === "filereference") {
|
|
685
|
-
const path = firstString(raw, ["path", "file", "filePath", "file_path", "absolutePath", "relativePath"]);
|
|
686
|
-
const text = firstString(raw, ["text", "name", "label"]);
|
|
687
|
-
return path ? [{ type: "file", path, name: text, text: text ?? `@${path}` }] : [];
|
|
688
|
-
}
|
|
689
560
|
const nested = raw.content ?? raw.contentItems ?? raw.parts;
|
|
690
561
|
if (Array.isArray(nested)) return contentBlocksFromValue(nested);
|
|
691
562
|
const text = firstString(raw, ["text", "message", "content"]);
|
|
@@ -881,54 +752,20 @@ function providerLabel(provider: AgentProvider): string {
|
|
|
881
752
|
interface AgentModelOption {
|
|
882
753
|
id: string;
|
|
883
754
|
label: string;
|
|
884
|
-
reasoningEfforts?: string[];
|
|
885
|
-
defaultReasoningEffort?: string;
|
|
886
|
-
speedTiers?: string[];
|
|
887
|
-
supportsImages?: boolean;
|
|
888
|
-
description?: string;
|
|
889
755
|
}
|
|
890
756
|
|
|
891
757
|
interface ProviderRuntimeCapabilities {
|
|
892
758
|
models?: AgentModelOption[];
|
|
893
759
|
defaultModel?: string;
|
|
894
760
|
reasoningEfforts?: string[];
|
|
895
|
-
speedTiers?: string[];
|
|
896
|
-
defaultServiceTier?: string;
|
|
897
761
|
commands?: AgentCommandDescriptor[];
|
|
898
762
|
modes?: AgentModeDescriptor[];
|
|
899
763
|
currentMode?: string;
|
|
900
764
|
}
|
|
901
765
|
|
|
902
766
|
const ALL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
903
|
-
const
|
|
904
|
-
const
|
|
905
|
-
const AGENT_PERMISSION_MODES: AgentPermissionMode[] = ["read_only", "workspace_write", "full_access"];
|
|
906
|
-
const COMMAND_OUTPUT_MAX_BYTES = 96 * 1024;
|
|
907
|
-
const LINKSHELL_NATIVE_COMMANDS: Array<{
|
|
908
|
-
name: string;
|
|
909
|
-
description: string;
|
|
910
|
-
category: string;
|
|
911
|
-
argsMode?: AgentCommandDescriptor["argsMode"];
|
|
912
|
-
destructive?: boolean;
|
|
913
|
-
providers?: AgentProvider[];
|
|
914
|
-
}> = [
|
|
915
|
-
{ name: "status", description: "Show current Agent and workspace status", category: "LinkShell", argsMode: "none" },
|
|
916
|
-
{ name: "plan", description: "Enter Plan mode for the next turn", category: "Agent", argsMode: "none" },
|
|
917
|
-
{ name: "exit-plan", description: "Exit Plan mode", category: "Agent", argsMode: "none" },
|
|
918
|
-
{ name: "review", description: "Ask the Agent to review current local changes", category: "Agent", argsMode: "optional" },
|
|
919
|
-
{ name: "subagents", description: "Ask the Agent to split work across subagents when useful", category: "Agent", argsMode: "optional" },
|
|
920
|
-
{ name: "compact", description: "Compact the active Codex context", category: "Codex", argsMode: "none", providers: ["codex"] },
|
|
921
|
-
{ name: "new", description: "Start a fresh Codex thread", category: "Codex", argsMode: "none", providers: ["codex"], destructive: true },
|
|
922
|
-
{ name: "clear", description: "Start a fresh Agent context for this conversation", category: "Agent", argsMode: "none", destructive: true },
|
|
923
|
-
{ name: "git-status", description: "Show branch and working tree status", category: "Git", argsMode: "none" },
|
|
924
|
-
{ name: "git-diff", description: "Show a compact diffstat for current changes", category: "Git", argsMode: "none" },
|
|
925
|
-
{ name: "git-commit", description: "Commit staged changes with the given message", category: "Git", argsMode: "required" },
|
|
926
|
-
{ name: "git-pull", description: "Pull with fast-forward only", category: "Git", argsMode: "none" },
|
|
927
|
-
{ name: "git-push", description: "Push the current branch", category: "Git", argsMode: "none" },
|
|
928
|
-
{ name: "git-stash", description: "Stash current working tree changes", category: "Git", argsMode: "optional" },
|
|
929
|
-
{ name: "git-stash-pop", description: "Pop the latest stash", category: "Git", argsMode: "none" },
|
|
930
|
-
];
|
|
931
|
-
const CODEX_JSON_RPC_COMMANDS = new Set(["plan", "exit-plan", "review", "compact", "new"]);
|
|
767
|
+
const ALL_PERMISSION_MODES = ["read_only", "workspace_write", "full_access"] as const;
|
|
768
|
+
const CODEX_COMMAND_NAMES = ["plan", "exit-plan", "compact", "clear", "status", "review", "subagents"] as const;
|
|
932
769
|
const CLAUDE_REMOTE_HIDDEN_COMMANDS = new Set([
|
|
933
770
|
"add-dir",
|
|
934
771
|
"agents",
|
|
@@ -1201,31 +1038,57 @@ function customClaudeCommands(cwd: string): AgentCommandDescriptor[] {
|
|
|
1201
1038
|
|
|
1202
1039
|
function defaultProviderCommands(provider: AgentProvider, cwd: string, enabled: boolean): AgentCommandDescriptor[] {
|
|
1203
1040
|
const disabledReason = enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`;
|
|
1204
|
-
|
|
1205
|
-
.
|
|
1206
|
-
.filter((command) => !command.providers || command.providers.includes(provider))
|
|
1207
|
-
.map((command) => makeCommand({
|
|
1041
|
+
if (provider === "codex") {
|
|
1042
|
+
return CODEX_COMMAND_NAMES.map((name) => makeCommand({
|
|
1208
1043
|
provider,
|
|
1209
|
-
name
|
|
1210
|
-
description: command.description,
|
|
1044
|
+
name,
|
|
1211
1045
|
source: "linkshell",
|
|
1212
|
-
category:
|
|
1213
|
-
|
|
1214
|
-
|
|
1046
|
+
category: name === "plan" || name === "exit-plan" ? "Modes" : "Codex",
|
|
1047
|
+
description: {
|
|
1048
|
+
"plan": "Enter Codex plan mode",
|
|
1049
|
+
"exit-plan": "Exit Codex plan mode",
|
|
1050
|
+
compact: "Compact the current thread",
|
|
1051
|
+
clear: "Start a fresh Codex thread",
|
|
1052
|
+
status: "Show LinkShell agent status",
|
|
1053
|
+
review: "Ask Codex to review local changes",
|
|
1054
|
+
subagents: "Insert a delegation prompt",
|
|
1055
|
+
}[name],
|
|
1056
|
+
argsMode: name === "review" || name === "subagents" ? "optional" : "none",
|
|
1057
|
+
destructive: name === "clear",
|
|
1215
1058
|
disabledReason,
|
|
1216
|
-
executionKind: "native",
|
|
1059
|
+
executionKind: name === "review" || name === "subagents" ? "prompt" : "native",
|
|
1217
1060
|
}));
|
|
1218
|
-
if (provider === "codex") {
|
|
1219
|
-
return linkshellCommands;
|
|
1220
1061
|
}
|
|
1221
1062
|
if (provider === "claude") {
|
|
1063
|
+
const builtIns = CLAUDE_BUILT_IN_COMMANDS
|
|
1064
|
+
.filter((entry) => isClaudeRemoteFriendlyCommand(entry.name))
|
|
1065
|
+
.map((entry) => makeCommand({
|
|
1066
|
+
provider,
|
|
1067
|
+
name: entry.name,
|
|
1068
|
+
description: entry.description,
|
|
1069
|
+
argsMode: entry.argsMode,
|
|
1070
|
+
destructive: entry.destructive,
|
|
1071
|
+
disabledReason,
|
|
1072
|
+
executionKind: "prompt",
|
|
1073
|
+
}));
|
|
1222
1074
|
const custom = customClaudeCommands(cwd).map((command) => ({
|
|
1223
1075
|
...command,
|
|
1224
1076
|
disabledReason: command.disabledReason ?? disabledReason,
|
|
1225
1077
|
}));
|
|
1226
|
-
return [...
|
|
1078
|
+
return [...builtIns, ...custom];
|
|
1227
1079
|
}
|
|
1228
|
-
return
|
|
1080
|
+
return [
|
|
1081
|
+
makeCommand({
|
|
1082
|
+
provider,
|
|
1083
|
+
name: "status",
|
|
1084
|
+
source: "linkshell",
|
|
1085
|
+
category: "LinkShell",
|
|
1086
|
+
description: "Show LinkShell agent status",
|
|
1087
|
+
argsMode: "none",
|
|
1088
|
+
disabledReason,
|
|
1089
|
+
executionKind: "native",
|
|
1090
|
+
}),
|
|
1091
|
+
];
|
|
1229
1092
|
}
|
|
1230
1093
|
|
|
1231
1094
|
function mergeCommands(...groups: Array<AgentCommandDescriptor[] | undefined>): AgentCommandDescriptor[] {
|
|
@@ -1244,81 +1107,6 @@ function mergeCommands(...groups: Array<AgentCommandDescriptor[] | undefined>):
|
|
|
1244
1107
|
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1245
1108
|
}
|
|
1246
1109
|
|
|
1247
|
-
function isGitNativeCommand(name: string): boolean {
|
|
1248
|
-
return name === "git-status" ||
|
|
1249
|
-
name === "git-diff" ||
|
|
1250
|
-
name === "git-commit" ||
|
|
1251
|
-
name === "git-pull" ||
|
|
1252
|
-
name === "git-push" ||
|
|
1253
|
-
name === "git-stash" ||
|
|
1254
|
-
name === "git-stash-pop";
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
function gitCommandArgs(name: string, args?: string): { display: string; argv: string[] } {
|
|
1258
|
-
const message = args?.trim();
|
|
1259
|
-
switch (name) {
|
|
1260
|
-
case "git-status":
|
|
1261
|
-
return { display: "git status --short --branch", argv: ["status", "--short", "--branch"] };
|
|
1262
|
-
case "git-diff":
|
|
1263
|
-
return { display: "git diff --stat", argv: ["diff", "--stat"] };
|
|
1264
|
-
case "git-commit":
|
|
1265
|
-
if (!message) throw new Error("请先输入提交信息,例如 /git-commit fix mobile agent timeline");
|
|
1266
|
-
return { display: `git commit -m ${JSON.stringify(message)}`, argv: ["commit", "-m", message] };
|
|
1267
|
-
case "git-pull":
|
|
1268
|
-
return { display: "git pull --ff-only", argv: ["pull", "--ff-only"] };
|
|
1269
|
-
case "git-push":
|
|
1270
|
-
return { display: "git push", argv: ["push"] };
|
|
1271
|
-
case "git-stash":
|
|
1272
|
-
return {
|
|
1273
|
-
display: "git stash push -u",
|
|
1274
|
-
argv: ["stash", "push", "-u", "-m", message || "LinkShell mobile stash"],
|
|
1275
|
-
};
|
|
1276
|
-
case "git-stash-pop":
|
|
1277
|
-
return { display: "git stash pop", argv: ["stash", "pop"] };
|
|
1278
|
-
default:
|
|
1279
|
-
throw new Error(`未知 Git 命令:/${name}`);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
function runProcess(
|
|
1284
|
-
command: string,
|
|
1285
|
-
args: string[],
|
|
1286
|
-
options: { cwd: string; maxBytes?: number },
|
|
1287
|
-
): Promise<{ output: string; exitCode: number | null; signal: NodeJS.Signals | null }> {
|
|
1288
|
-
return new Promise((resolve, reject) => {
|
|
1289
|
-
const child = spawn(command, args, {
|
|
1290
|
-
cwd: options.cwd,
|
|
1291
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1292
|
-
windowsHide: true,
|
|
1293
|
-
});
|
|
1294
|
-
const maxBytes = options.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES;
|
|
1295
|
-
let output = "";
|
|
1296
|
-
let bytes = 0;
|
|
1297
|
-
let truncated = false;
|
|
1298
|
-
const append = (chunk: Buffer) => {
|
|
1299
|
-
if (bytes >= maxBytes) {
|
|
1300
|
-
truncated = true;
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
const remaining = maxBytes - bytes;
|
|
1304
|
-
const slice = chunk.byteLength > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
1305
|
-
output += slice.toString("utf8");
|
|
1306
|
-
bytes += slice.byteLength;
|
|
1307
|
-
if (slice.byteLength < chunk.byteLength) truncated = true;
|
|
1308
|
-
};
|
|
1309
|
-
child.stdout.on("data", append);
|
|
1310
|
-
child.stderr.on("data", append);
|
|
1311
|
-
child.once("error", reject);
|
|
1312
|
-
child.once("close", (exitCode, signal) => {
|
|
1313
|
-
resolve({
|
|
1314
|
-
output: `${output.trimEnd()}${truncated ? "\n\n[truncated by LinkShell]" : ""}`,
|
|
1315
|
-
exitCode,
|
|
1316
|
-
signal,
|
|
1317
|
-
});
|
|
1318
|
-
});
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
1110
|
function runtimeCommands(provider: AgentProvider, value: unknown): AgentCommandDescriptor[] {
|
|
1323
1111
|
const raw = asRecord(value);
|
|
1324
1112
|
const commandsValue =
|
|
@@ -1362,14 +1150,10 @@ function parseModelListCapabilities(value: unknown): ProviderRuntimeCapabilities
|
|
|
1362
1150
|
const raw = asRecord(value);
|
|
1363
1151
|
const modelsValue =
|
|
1364
1152
|
Array.isArray(value) ? value :
|
|
1365
|
-
Array.isArray(raw?.data) ? raw.data :
|
|
1366
1153
|
Array.isArray(raw?.models) ? raw.models :
|
|
1367
1154
|
Array.isArray(raw?.items) ? raw.items :
|
|
1368
1155
|
Array.isArray(raw?.modelOptions) ? raw.modelOptions :
|
|
1369
1156
|
[];
|
|
1370
|
-
const effortSet = new Set<string>();
|
|
1371
|
-
const speedTierSet = new Set<string>(["standard"]);
|
|
1372
|
-
let defaultModelFromEntry: string | undefined;
|
|
1373
1157
|
const models = modelsValue
|
|
1374
1158
|
.map((entry, index) => {
|
|
1375
1159
|
const model = asRecord(entry);
|
|
@@ -1379,40 +1163,13 @@ function parseModelListCapabilities(value: unknown): ProviderRuntimeCapabilities
|
|
|
1379
1163
|
: undefined;
|
|
1380
1164
|
}
|
|
1381
1165
|
const modelId = firstString(model, ["id", "model", "name", "value"]) ?? `model-${index + 1}`;
|
|
1382
|
-
const label = firstString(model, ["
|
|
1383
|
-
|
|
1384
|
-
const modelEfforts = arrayFromKeys(model, ["supportedReasoningEfforts", "reasoningEfforts"])
|
|
1385
|
-
.map((effort) => {
|
|
1386
|
-
const rawEffort = asRecord(effort);
|
|
1387
|
-
return firstString(rawEffort, ["reasoningEffort", "id", "value"]) ?? (typeof effort === "string" ? effort : undefined);
|
|
1388
|
-
})
|
|
1389
|
-
.filter((effort): effort is string => Boolean(effort) && ALL_REASONING_EFFORTS.includes(effort as typeof ALL_REASONING_EFFORTS[number]));
|
|
1390
|
-
modelEfforts.forEach((effort) => effortSet.add(effort));
|
|
1391
|
-
const defaultReasoningEffort = firstString(model, ["defaultReasoningEffort", "default_reasoning_effort"]);
|
|
1392
|
-
if (defaultReasoningEffort && ALL_REASONING_EFFORTS.includes(defaultReasoningEffort as typeof ALL_REASONING_EFFORTS[number])) {
|
|
1393
|
-
effortSet.add(defaultReasoningEffort);
|
|
1394
|
-
}
|
|
1395
|
-
const modalities = arrayFromKeys(model, ["inputModalities", "modalities"]);
|
|
1396
|
-
const speedTiers = arrayFromKeys(model, ["additionalSpeedTiers", "additional_speed_tiers", "serviceTiers", "service_tiers"])
|
|
1397
|
-
.map((tier) => typeof tier === "string" ? tier : firstString(asRecord(tier), ["id", "tier", "value"]))
|
|
1398
|
-
.filter((tier): tier is string => Boolean(tier) && ALL_SERVICE_TIERS.includes(tier as typeof ALL_SERVICE_TIERS[number]));
|
|
1399
|
-
speedTiers.forEach((tier) => speedTierSet.add(tier));
|
|
1400
|
-
if (model.isDefault === true) defaultModelFromEntry = modelId;
|
|
1401
|
-
return {
|
|
1402
|
-
id: modelId,
|
|
1403
|
-
label,
|
|
1404
|
-
...(modelEfforts.length > 0 ? { reasoningEfforts: modelEfforts } : {}),
|
|
1405
|
-
...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
|
|
1406
|
-
...(speedTiers.length > 0 ? { speedTiers: ["standard", ...speedTiers.filter((tier) => tier !== "standard")] } : {}),
|
|
1407
|
-
...(modalities.length > 0 ? { supportsImages: modalities.includes("image") } : {}),
|
|
1408
|
-
...(firstString(model, ["description"]) ? { description: firstString(model, ["description"]) } : {}),
|
|
1409
|
-
};
|
|
1166
|
+
const label = firstString(model, ["label", "title", "displayName", "name"]) ?? modelId;
|
|
1167
|
+
return { id: modelId, label };
|
|
1410
1168
|
})
|
|
1411
1169
|
.filter((entry): entry is AgentModelOption => Boolean(entry));
|
|
1412
1170
|
const defaultModel =
|
|
1413
1171
|
firstString(raw, ["defaultModel", "default_model", "currentModel"]) ??
|
|
1414
|
-
firstString(asRecord(raw?.defaults), ["model"])
|
|
1415
|
-
defaultModelFromEntry;
|
|
1172
|
+
firstString(asRecord(raw?.defaults), ["model"]);
|
|
1416
1173
|
const effortsValue =
|
|
1417
1174
|
Array.isArray(raw?.reasoningEfforts) ? raw.reasoningEfforts :
|
|
1418
1175
|
Array.isArray(raw?.reasoning_efforts) ? raw.reasoning_efforts :
|
|
@@ -1420,14 +1177,11 @@ function parseModelListCapabilities(value: unknown): ProviderRuntimeCapabilities
|
|
|
1420
1177
|
undefined;
|
|
1421
1178
|
const reasoningEfforts = effortsValue
|
|
1422
1179
|
?.filter((entry): entry is string => typeof entry === "string" && ALL_REASONING_EFFORTS.includes(entry as typeof ALL_REASONING_EFFORTS[number]));
|
|
1423
|
-
|
|
1424
|
-
const resolvedSpeedTiers = [...speedTierSet];
|
|
1425
|
-
if (models.length === 0 && !defaultModel && resolvedReasoningEfforts.length === 0 && resolvedSpeedTiers.length <= 1) return undefined;
|
|
1180
|
+
if (models.length === 0 && !defaultModel && !reasoningEfforts?.length) return undefined;
|
|
1426
1181
|
return {
|
|
1427
|
-
...(models.length > 0 ? { models } : {}),
|
|
1182
|
+
...(models.length > 0 ? { models: [{ id: "default", label: "默认模型" }, ...models] } : {}),
|
|
1428
1183
|
...(defaultModel ? { defaultModel } : {}),
|
|
1429
|
-
...(
|
|
1430
|
-
...(resolvedSpeedTiers.length > 0 ? { speedTiers: resolvedSpeedTiers } : {}),
|
|
1184
|
+
...(reasoningEfforts?.length ? { reasoningEfforts } : {}),
|
|
1431
1185
|
};
|
|
1432
1186
|
}
|
|
1433
1187
|
|
|
@@ -1449,9 +1203,6 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1449
1203
|
model?: string;
|
|
1450
1204
|
createdAt?: number;
|
|
1451
1205
|
lastActivityAt?: number;
|
|
1452
|
-
archived?: boolean;
|
|
1453
|
-
status?: AgentStatus;
|
|
1454
|
-
runningTurnId?: string;
|
|
1455
1206
|
}> {
|
|
1456
1207
|
const raw = asRecord(value);
|
|
1457
1208
|
const sessionsValue =
|
|
@@ -1467,9 +1218,6 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1467
1218
|
model?: string;
|
|
1468
1219
|
createdAt?: number;
|
|
1469
1220
|
lastActivityAt?: number;
|
|
1470
|
-
archived?: boolean;
|
|
1471
|
-
status?: AgentStatus;
|
|
1472
|
-
runningTurnId?: string;
|
|
1473
1221
|
}> = [];
|
|
1474
1222
|
for (const entry of sessionsValue) {
|
|
1475
1223
|
const session = asRecord(entry);
|
|
@@ -1488,34 +1236,16 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1488
1236
|
model: firstString(source, ["model", "modelId"]),
|
|
1489
1237
|
createdAt: parseTimestamp(source.createdAt ?? source.created_at),
|
|
1490
1238
|
lastActivityAt: parseTimestamp(source.lastActivityAt ?? source.updatedAt ?? source.modifiedAt ?? source.lastModified ?? source.updated_at),
|
|
1491
|
-
archived: typeof source.archived === "boolean" ? source.archived : undefined,
|
|
1492
|
-
status: normalizeAgentStatus(firstString(source, ["status", "state", "phase"])),
|
|
1493
|
-
runningTurnId: firstString(source, ["runningTurnId", "running_turn_id", "turnId", "activeTurnId"]),
|
|
1494
1239
|
});
|
|
1495
1240
|
}
|
|
1496
1241
|
return result;
|
|
1497
1242
|
}
|
|
1498
1243
|
|
|
1499
|
-
function normalizeAgentStatus(value: string | undefined): AgentStatus | undefined {
|
|
1500
|
-
if (!value) return undefined;
|
|
1501
|
-
const normalized = value.toLowerCase();
|
|
1502
|
-
if (normalized === "running" || normalized === "in_progress" || normalized === "busy") return "running";
|
|
1503
|
-
if (normalized === "waiting_permission" || normalized === "waiting" || normalized === "blocked") return "waiting_permission";
|
|
1504
|
-
if (normalized === "error" || normalized === "failed") return "error";
|
|
1505
|
-
if (normalized === "idle" || normalized === "completed" || normalized === "done") return "idle";
|
|
1506
|
-
return undefined;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1244
|
export class AgentWorkspaceProxy {
|
|
1510
1245
|
private clients = new Map<AgentProvider, AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient>();
|
|
1511
|
-
private codexRpcBridge: CodexRpcBridge | undefined;
|
|
1512
1246
|
private agentProtocols = new Map<AgentProvider, AgentProtocol>();
|
|
1513
1247
|
private providerCapabilities = new Map<AgentProvider, ProviderRuntimeCapabilities>();
|
|
1514
|
-
private providerCapabilityErrors = new Map<AgentProvider, string>();
|
|
1515
|
-
private capabilitiesRevision = 0;
|
|
1516
1248
|
private initialized = false;
|
|
1517
|
-
private initializePromise: Promise<void> | undefined;
|
|
1518
|
-
private providerStartPromises = new Map<AgentProvider, Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined>>();
|
|
1519
1249
|
private status: AgentStatus = "unavailable";
|
|
1520
1250
|
private error: string | undefined;
|
|
1521
1251
|
private activeConversationId: string | undefined;
|
|
@@ -1524,8 +1254,6 @@ export class AgentWorkspaceProxy {
|
|
|
1524
1254
|
private conversations = new Map<string, AgentConversation>();
|
|
1525
1255
|
private conversationByAgentSessionId = new Map<string, string>();
|
|
1526
1256
|
private timelines = new Map<string, AgentTimelineItem[]>();
|
|
1527
|
-
private conversationRevisions = new Map<string, number>();
|
|
1528
|
-
private revisionEvents = new Map<string, AgentRevisionEvent[]>();
|
|
1529
1257
|
private toolOutputBuffers = new Map<string, string>();
|
|
1530
1258
|
private pendingPermissions = new Map<string, AgentPermission>();
|
|
1531
1259
|
private permissionWaiters = new Map<string, PendingPermissionWaiter>();
|
|
@@ -1537,7 +1265,7 @@ export class AgentWorkspaceProxy {
|
|
|
1537
1265
|
|
|
1538
1266
|
constructor(
|
|
1539
1267
|
private readonly input: {
|
|
1540
|
-
|
|
1268
|
+
sessionId: string;
|
|
1541
1269
|
cwd: string;
|
|
1542
1270
|
availableProviders: AgentProvider[];
|
|
1543
1271
|
command?: string;
|
|
@@ -1548,31 +1276,6 @@ export class AgentWorkspaceProxy {
|
|
|
1548
1276
|
|
|
1549
1277
|
async handleEnvelope(envelope: Envelope): Promise<void> {
|
|
1550
1278
|
switch (envelope.type) {
|
|
1551
|
-
case "agent.codex.rpc": {
|
|
1552
|
-
const payload = parseTypedPayload("agent.codex.rpc", envelope.payload);
|
|
1553
|
-
try {
|
|
1554
|
-
this.codexRpc().send(payload as never);
|
|
1555
|
-
} catch (error) {
|
|
1556
|
-
const idValue = payload && typeof payload === "object" && "id" in payload
|
|
1557
|
-
? (payload as { id?: unknown }).id
|
|
1558
|
-
: undefined;
|
|
1559
|
-
if (typeof idValue === "string" || typeof idValue === "number") {
|
|
1560
|
-
this.input.send(createEnvelope({
|
|
1561
|
-
type: "agent.codex.rpc",
|
|
1562
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
1563
|
-
payload: {
|
|
1564
|
-
jsonrpc: "2.0",
|
|
1565
|
-
id: idValue,
|
|
1566
|
-
error: {
|
|
1567
|
-
code: -32000,
|
|
1568
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1569
|
-
},
|
|
1570
|
-
},
|
|
1571
|
-
}));
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
break;
|
|
1575
|
-
}
|
|
1576
1279
|
case "agent.v2.capabilities.request":
|
|
1577
1280
|
await this.initialize();
|
|
1578
1281
|
this.sendCapabilities();
|
|
@@ -1590,8 +1293,8 @@ export class AgentWorkspaceProxy {
|
|
|
1590
1293
|
);
|
|
1591
1294
|
this.input.send(createEnvelope({
|
|
1592
1295
|
type: "agent.v2.conversation.list.result",
|
|
1593
|
-
|
|
1594
|
-
payload: { conversations
|
|
1296
|
+
sessionId: this.input.sessionId,
|
|
1297
|
+
payload: { conversations },
|
|
1595
1298
|
}));
|
|
1596
1299
|
break;
|
|
1597
1300
|
}
|
|
@@ -1600,16 +1303,6 @@ export class AgentWorkspaceProxy {
|
|
|
1600
1303
|
this.sendSnapshot(payload.conversationId);
|
|
1601
1304
|
break;
|
|
1602
1305
|
}
|
|
1603
|
-
case "agent.v2.history.request": {
|
|
1604
|
-
const payload = parseTypedPayload("agent.v2.history.request", envelope.payload);
|
|
1605
|
-
await this.sendHistoryPage(payload);
|
|
1606
|
-
break;
|
|
1607
|
-
}
|
|
1608
|
-
case "agent.v2.delta.request": {
|
|
1609
|
-
const payload = parseTypedPayload("agent.v2.delta.request", envelope.payload);
|
|
1610
|
-
this.sendDelta(payload);
|
|
1611
|
-
break;
|
|
1612
|
-
}
|
|
1613
1306
|
case "agent.v2.prompt": {
|
|
1614
1307
|
const payload = parseTypedPayload("agent.v2.prompt", envelope.payload);
|
|
1615
1308
|
await this.sendPrompt(payload);
|
|
@@ -1648,68 +1341,35 @@ export class AgentWorkspaceProxy {
|
|
|
1648
1341
|
}
|
|
1649
1342
|
|
|
1650
1343
|
stop(): void {
|
|
1651
|
-
this.codexRpcBridge?.stop();
|
|
1652
|
-
this.codexRpcBridge = undefined;
|
|
1653
1344
|
for (const client of this.clients.values()) {
|
|
1654
1345
|
client.stop();
|
|
1655
1346
|
}
|
|
1656
1347
|
this.clients.clear();
|
|
1657
|
-
this.providerStartPromises.clear();
|
|
1658
|
-
this.initializePromise = undefined;
|
|
1659
1348
|
}
|
|
1660
1349
|
|
|
1661
1350
|
private clientForProvider(provider: AgentProvider): AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined {
|
|
1662
1351
|
return this.clients.get(provider);
|
|
1663
1352
|
}
|
|
1664
1353
|
|
|
1665
|
-
private codexRpc(): CodexRpcBridge {
|
|
1666
|
-
this.codexRpcBridge ??= new CodexRpcBridge({
|
|
1667
|
-
command: this.input.command,
|
|
1668
|
-
cwd: this.input.cwd,
|
|
1669
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
1670
|
-
send: this.input.send,
|
|
1671
|
-
verbose: this.input.verbose,
|
|
1672
|
-
});
|
|
1673
|
-
return this.codexRpcBridge;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
1354
|
private protocolForProvider(provider: AgentProvider): AgentProtocol | undefined {
|
|
1677
1355
|
return this.agentProtocols.get(provider);
|
|
1678
1356
|
}
|
|
1679
1357
|
|
|
1680
1358
|
private async initialize(): Promise<void> {
|
|
1681
1359
|
if (this.initialized) return;
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
this.initialized = true;
|
|
1690
|
-
this.status = "idle";
|
|
1691
|
-
this.error = undefined;
|
|
1692
|
-
this.sendCapabilities();
|
|
1693
|
-
})().finally(() => {
|
|
1694
|
-
this.initializePromise = undefined;
|
|
1695
|
-
});
|
|
1696
|
-
return this.initializePromise;
|
|
1360
|
+
this.initialized = true;
|
|
1361
|
+
// Eagerly start all detected providers so capabilities report real status
|
|
1362
|
+
const startPromises = this.input.availableProviders.map((p) => this.ensureProviderClient(p));
|
|
1363
|
+
await Promise.allSettled(startPromises);
|
|
1364
|
+
this.status = "idle";
|
|
1365
|
+
this.error = undefined;
|
|
1366
|
+
this.sendCapabilities();
|
|
1697
1367
|
}
|
|
1698
1368
|
|
|
1699
1369
|
private async ensureProviderClient(provider: AgentProvider): Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined> {
|
|
1700
1370
|
const existing = this.clients.get(provider);
|
|
1701
1371
|
if (existing) return existing;
|
|
1702
|
-
const starting = this.providerStartPromises.get(provider);
|
|
1703
|
-
if (starting) return starting;
|
|
1704
1372
|
|
|
1705
|
-
const startPromise = this.startProviderClient(provider).finally(() => {
|
|
1706
|
-
this.providerStartPromises.delete(provider);
|
|
1707
|
-
});
|
|
1708
|
-
this.providerStartPromises.set(provider, startPromise);
|
|
1709
|
-
return startPromise;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
private async startProviderClient(provider: AgentProvider): Promise<AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient | undefined> {
|
|
1713
1373
|
const resolved = resolveAgentCommand({
|
|
1714
1374
|
provider,
|
|
1715
1375
|
command: this.input.command,
|
|
@@ -1809,12 +1469,8 @@ export class AgentWorkspaceProxy {
|
|
|
1809
1469
|
try {
|
|
1810
1470
|
const result = await listModels.call(client);
|
|
1811
1471
|
const runtimeCapabilities = parseModelListCapabilities(result);
|
|
1812
|
-
if (runtimeCapabilities)
|
|
1813
|
-
this.providerCapabilities.set(provider, runtimeCapabilities);
|
|
1814
|
-
this.providerCapabilityErrors.delete(provider);
|
|
1815
|
-
}
|
|
1472
|
+
if (runtimeCapabilities) this.providerCapabilities.set(provider, runtimeCapabilities);
|
|
1816
1473
|
} catch (error) {
|
|
1817
|
-
this.providerCapabilityErrors.set(provider, error instanceof Error ? error.message : String(error));
|
|
1818
1474
|
if (this.input.verbose) {
|
|
1819
1475
|
process.stderr.write(`[agent:v2] model/list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1820
1476
|
}
|
|
@@ -1823,11 +1479,36 @@ export class AgentWorkspaceProxy {
|
|
|
1823
1479
|
|
|
1824
1480
|
private async syncProviderSessions(): Promise<void> {
|
|
1825
1481
|
await this.initialize();
|
|
1826
|
-
this.upsertProviderSessions("claude", listClaudeStoredSessions(this.input.cwd));
|
|
1827
1482
|
for (const [provider, client] of this.clients) {
|
|
1828
1483
|
try {
|
|
1829
1484
|
const result = await client.listSessions();
|
|
1830
|
-
|
|
1485
|
+
for (const remote of parseRemoteSessions(result)) {
|
|
1486
|
+
const agentSessionId = remote.id;
|
|
1487
|
+
const existingId = this.conversationByAgentSessionId.get(agentSessionId);
|
|
1488
|
+
const now = Date.now();
|
|
1489
|
+
const conversationId = existingId ?? `agent:${agentSessionId}`;
|
|
1490
|
+
const existing = this.conversations.get(conversationId);
|
|
1491
|
+
const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
|
|
1492
|
+
const conversation: AgentConversation = {
|
|
1493
|
+
id: conversationId,
|
|
1494
|
+
agentSessionId,
|
|
1495
|
+
provider,
|
|
1496
|
+
cwd,
|
|
1497
|
+
title: remote.title ?? existing?.title ?? titleFromCwd(cwd),
|
|
1498
|
+
model: remote.model ?? existing?.model,
|
|
1499
|
+
reasoningEffort: existing?.reasoningEffort,
|
|
1500
|
+
permissionMode: existing?.permissionMode,
|
|
1501
|
+
collaborationMode: existing?.collaborationMode,
|
|
1502
|
+
status: existing?.status ?? "idle",
|
|
1503
|
+
archived: existing?.archived ?? false,
|
|
1504
|
+
lastMessagePreview: existing?.lastMessagePreview,
|
|
1505
|
+
lastActivityAt: remote.lastActivityAt ?? existing?.lastActivityAt ?? now,
|
|
1506
|
+
createdAt: remote.createdAt ?? existing?.createdAt ?? now,
|
|
1507
|
+
};
|
|
1508
|
+
this.conversations.set(conversation.id, conversation);
|
|
1509
|
+
this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
|
|
1510
|
+
this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
|
|
1511
|
+
}
|
|
1831
1512
|
} catch (error) {
|
|
1832
1513
|
if (this.input.verbose) {
|
|
1833
1514
|
process.stderr.write(`[agent:v2] session list failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -1836,55 +1517,16 @@ export class AgentWorkspaceProxy {
|
|
|
1836
1517
|
}
|
|
1837
1518
|
}
|
|
1838
1519
|
|
|
1839
|
-
private upsertProviderSessions(provider: AgentProvider, result: unknown): void {
|
|
1840
|
-
for (const remote of parseRemoteSessions(result)) {
|
|
1841
|
-
const agentSessionId = remote.id;
|
|
1842
|
-
const existingId = this.conversationByAgentSessionId.get(agentSessionId);
|
|
1843
|
-
const now = Date.now();
|
|
1844
|
-
const conversationId = existingId ?? `agent:${agentSessionId}`;
|
|
1845
|
-
const existing = this.conversations.get(conversationId);
|
|
1846
|
-
const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
|
|
1847
|
-
const conversation: AgentConversation = {
|
|
1848
|
-
id: conversationId,
|
|
1849
|
-
agentSessionId,
|
|
1850
|
-
provider,
|
|
1851
|
-
cwd,
|
|
1852
|
-
title: remote.title ?? existing?.title ?? titleFromCwd(cwd),
|
|
1853
|
-
model: remote.model ?? existing?.model,
|
|
1854
|
-
reasoningEffort: existing?.reasoningEffort,
|
|
1855
|
-
serviceTier: existing?.serviceTier,
|
|
1856
|
-
permissionMode: existing?.permissionMode,
|
|
1857
|
-
collaborationMode: existing?.collaborationMode,
|
|
1858
|
-
status: remote.status ?? existing?.status ?? "idle",
|
|
1859
|
-
archived: remote.archived ?? existing?.archived ?? false,
|
|
1860
|
-
timelineRevision: existing?.timelineRevision ?? this.getRevision(conversationId),
|
|
1861
|
-
historyComplete: existing?.historyComplete ?? false,
|
|
1862
|
-
runningTurnId: remote.runningTurnId ?? this.currentTurnIds.get(conversationId),
|
|
1863
|
-
source: "device",
|
|
1864
|
-
canonical: true,
|
|
1865
|
-
lastMessagePreview: existing?.lastMessagePreview,
|
|
1866
|
-
lastActivityAt: remote.lastActivityAt ?? existing?.lastActivityAt ?? now,
|
|
1867
|
-
createdAt: remote.createdAt ?? existing?.createdAt ?? now,
|
|
1868
|
-
};
|
|
1869
|
-
this.conversations.set(conversation.id, conversation);
|
|
1870
|
-
this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
|
|
1871
|
-
if (remote.runningTurnId) this.rememberTurnConversationId(conversation.id, remote.runningTurnId);
|
|
1872
|
-
this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
1520
|
private sendCapabilities(): void {
|
|
1877
1521
|
const providers = this.input.availableProviders.map((provider) => {
|
|
1878
1522
|
const client = this.clients.get(provider);
|
|
1879
|
-
const protocol =
|
|
1523
|
+
const protocol = this.agentProtocols.get(provider);
|
|
1880
1524
|
const runtimeCapabilities = this.providerCapabilities.get(provider);
|
|
1881
|
-
const enabled =
|
|
1882
|
-
const hasRuntimeModels = Boolean(runtimeCapabilities?.models?.length);
|
|
1525
|
+
const enabled = Boolean(client);
|
|
1883
1526
|
const supportsImages = enabled && protocolSupportsImages(protocol);
|
|
1884
|
-
const isClaudeSdk = protocol === "claude-agent-sdk";
|
|
1885
1527
|
const isClaudeFallback = protocol === "claude-stream-json";
|
|
1886
|
-
const supportsPermission = enabled &&
|
|
1887
|
-
const supportsReasoningEffort = enabled;
|
|
1528
|
+
const supportsPermission = enabled && !isClaudeFallback;
|
|
1529
|
+
const supportsReasoningEffort = enabled && !isClaudeFallback;
|
|
1888
1530
|
const commands = mergeCommands(
|
|
1889
1531
|
defaultProviderCommands(provider, this.input.cwd, enabled),
|
|
1890
1532
|
runtimeCapabilities?.commands,
|
|
@@ -1895,23 +1537,21 @@ export class AgentWorkspaceProxy {
|
|
|
1895
1537
|
label: providerLabel(provider),
|
|
1896
1538
|
enabled,
|
|
1897
1539
|
reason: enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`,
|
|
1898
|
-
providerProtocol: protocol,
|
|
1899
1540
|
supportsImages,
|
|
1900
1541
|
supportsPermission,
|
|
1901
1542
|
supportsPlan: enabled,
|
|
1902
1543
|
supportsCancel: enabled,
|
|
1903
1544
|
models: runtimeCapabilities?.models ?? [{ id: "default", label: "默认模型" }],
|
|
1904
|
-
defaultModel: runtimeCapabilities?.defaultModel,
|
|
1905
|
-
modelsSource: hasRuntimeModels ? "runtime" : enabled ? "fallback" : "unavailable",
|
|
1906
|
-
modelListError: this.providerCapabilityErrors.get(provider),
|
|
1545
|
+
defaultModel: runtimeCapabilities?.defaultModel ?? "default",
|
|
1907
1546
|
reasoningEfforts: supportsReasoningEffort
|
|
1908
|
-
? runtimeCapabilities?.reasoningEfforts ??
|
|
1547
|
+
? runtimeCapabilities?.reasoningEfforts ?? [...ALL_REASONING_EFFORTS]
|
|
1909
1548
|
: [],
|
|
1910
|
-
|
|
1911
|
-
defaultServiceTier: provider === "codex" ? runtimeCapabilities?.defaultServiceTier : undefined,
|
|
1912
|
-
permissionModes: supportsPermission ? AGENT_PERMISSION_MODES : [],
|
|
1549
|
+
permissionModes: supportsPermission ? [...ALL_PERMISSION_MODES] : [],
|
|
1913
1550
|
commands,
|
|
1914
|
-
modes: runtimeCapabilities?.modes ?? [
|
|
1551
|
+
modes: runtimeCapabilities?.modes ?? (provider === "codex" ? [
|
|
1552
|
+
{ id: "default", title: "Default", description: "Run normal implementation turns" },
|
|
1553
|
+
{ id: "plan", title: "Plan", description: "Discuss and produce an implementation plan first" },
|
|
1554
|
+
] : []),
|
|
1915
1555
|
currentMode,
|
|
1916
1556
|
features: {
|
|
1917
1557
|
images: supportsImages,
|
|
@@ -1919,8 +1559,6 @@ export class AgentWorkspaceProxy {
|
|
|
1919
1559
|
plan: enabled,
|
|
1920
1560
|
cancel: enabled,
|
|
1921
1561
|
reasoningEffort: supportsReasoningEffort,
|
|
1922
|
-
serviceTier: provider === "codex" && Boolean(runtimeCapabilities?.speedTiers?.includes("fast")),
|
|
1923
|
-
claudeSdk: isClaudeSdk,
|
|
1924
1562
|
streamJsonFallback: isClaudeFallback,
|
|
1925
1563
|
},
|
|
1926
1564
|
};
|
|
@@ -1929,14 +1567,13 @@ export class AgentWorkspaceProxy {
|
|
|
1929
1567
|
const anyPermission = providers.some((p) => p.supportsPermission);
|
|
1930
1568
|
this.input.send(createEnvelope({
|
|
1931
1569
|
type: "agent.v2.capabilities",
|
|
1932
|
-
|
|
1570
|
+
sessionId: this.input.sessionId,
|
|
1933
1571
|
payload: {
|
|
1934
1572
|
enabled: anyEnabled,
|
|
1935
1573
|
provider: this.input.availableProviders[0] ?? "codex",
|
|
1936
1574
|
providers,
|
|
1937
1575
|
protocolVersion: 1,
|
|
1938
1576
|
workspaceProtocolVersion: 2,
|
|
1939
|
-
capabilitiesRevision: ++this.capabilitiesRevision,
|
|
1940
1577
|
error: anyEnabled ? undefined : "没有可用的 Agent provider。请安装 Claude Code 或 Codex CLI。",
|
|
1941
1578
|
supportsSessionList: anyEnabled,
|
|
1942
1579
|
supportsSessionLoad: anyEnabled,
|
|
@@ -1956,12 +1593,29 @@ export class AgentWorkspaceProxy {
|
|
|
1956
1593
|
provider?: AgentProvider;
|
|
1957
1594
|
model?: string;
|
|
1958
1595
|
reasoningEffort?: string;
|
|
1959
|
-
serviceTier?: string;
|
|
1960
1596
|
permissionMode?: AgentPermissionMode;
|
|
1961
1597
|
collaborationMode?: AgentCollaborationMode;
|
|
1962
1598
|
title?: string;
|
|
1963
1599
|
}): Promise<AgentConversation | undefined> {
|
|
1964
1600
|
const provider = payload.provider ?? this.input.availableProviders[0];
|
|
1601
|
+
if (!provider) {
|
|
1602
|
+
return this.openFailure(payload, "没有可用的 Agent provider。");
|
|
1603
|
+
}
|
|
1604
|
+
if (!this.input.availableProviders.includes(provider)) {
|
|
1605
|
+
return this.openFailure(
|
|
1606
|
+
payload,
|
|
1607
|
+
`${providerLabel(provider)} 未安装或不可用。`,
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
const client = await this.ensureProviderClient(provider);
|
|
1612
|
+
if (!client) {
|
|
1613
|
+
return this.openFailure(
|
|
1614
|
+
payload,
|
|
1615
|
+
`${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`,
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1965
1619
|
const cwd = payload.cwd ?? this.input.cwd;
|
|
1966
1620
|
let agentSessionId = payload.agentSessionId;
|
|
1967
1621
|
let existingConversation =
|
|
@@ -1972,43 +1626,18 @@ export class AgentWorkspaceProxy {
|
|
|
1972
1626
|
if (payload.conversationId && existingConversation.id !== payload.conversationId) {
|
|
1973
1627
|
existingConversation = this.adoptConversationId(existingConversation.id, payload.conversationId);
|
|
1974
1628
|
}
|
|
1975
|
-
this.hydrateStoredTimeline(existingConversation);
|
|
1976
1629
|
this.activeConversationId = existingConversation.id;
|
|
1977
|
-
const snapshot = this.latestSnapshot(existingConversation.id);
|
|
1978
1630
|
this.input.send(createEnvelope({
|
|
1979
1631
|
type: "agent.v2.conversation.opened",
|
|
1980
|
-
|
|
1632
|
+
sessionId: this.input.sessionId,
|
|
1981
1633
|
payload: {
|
|
1982
|
-
conversation:
|
|
1983
|
-
snapshot:
|
|
1984
|
-
revision: this.getRevision(existingConversation.id),
|
|
1985
|
-
cursor: snapshot.cursor,
|
|
1986
|
-
hasMore: snapshot.hasMore,
|
|
1987
|
-
source: "device-history",
|
|
1988
|
-
canonical: true,
|
|
1634
|
+
conversation: existingConversation,
|
|
1635
|
+
snapshot: this.timelines.get(existingConversation.id) ?? [],
|
|
1989
1636
|
},
|
|
1990
1637
|
}));
|
|
1991
1638
|
return existingConversation;
|
|
1992
1639
|
}
|
|
1993
1640
|
|
|
1994
|
-
if (!provider) {
|
|
1995
|
-
return this.openFailure(payload, "没有可用的 Agent provider。");
|
|
1996
|
-
}
|
|
1997
|
-
if (!this.input.availableProviders.includes(provider)) {
|
|
1998
|
-
return this.openFailure(
|
|
1999
|
-
payload,
|
|
2000
|
-
`${providerLabel(provider)} 未安装或不可用。`,
|
|
2001
|
-
);
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
const client = await this.ensureProviderClient(provider);
|
|
2005
|
-
if (!client) {
|
|
2006
|
-
return this.openFailure(
|
|
2007
|
-
payload,
|
|
2008
|
-
`${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`,
|
|
2009
|
-
);
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
1641
|
try {
|
|
2013
1642
|
const result = agentSessionId
|
|
2014
1643
|
? await client.loadSession({ sessionId: agentSessionId, cwd })
|
|
@@ -2025,16 +1654,10 @@ export class AgentWorkspaceProxy {
|
|
|
2025
1654
|
title: payload.title ?? existingConversation?.title ?? titleFromCwd(cwd),
|
|
2026
1655
|
model: payload.model ?? existingConversation?.model,
|
|
2027
1656
|
reasoningEffort: payload.reasoningEffort ?? existingConversation?.reasoningEffort,
|
|
2028
|
-
serviceTier: payload.serviceTier ?? existingConversation?.serviceTier,
|
|
2029
1657
|
permissionMode: payload.permissionMode ?? existingConversation?.permissionMode,
|
|
2030
1658
|
collaborationMode: payload.collaborationMode ?? existingConversation?.collaborationMode,
|
|
2031
1659
|
status: "idle",
|
|
2032
1660
|
archived: existingConversation?.archived ?? false,
|
|
2033
|
-
timelineRevision: existingConversation?.timelineRevision ?? this.getRevision(conversationId),
|
|
2034
|
-
historyComplete: existingConversation?.historyComplete ?? false,
|
|
2035
|
-
runningTurnId: this.currentTurnIds.get(conversationId),
|
|
2036
|
-
source: "device",
|
|
2037
|
-
canonical: true,
|
|
2038
1661
|
lastMessagePreview: existingConversation?.status === "error" ? undefined : existingConversation?.lastMessagePreview,
|
|
2039
1662
|
lastActivityAt: now,
|
|
2040
1663
|
createdAt: existingConversation?.createdAt ?? now,
|
|
@@ -2043,20 +1666,10 @@ export class AgentWorkspaceProxy {
|
|
|
2043
1666
|
this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
|
|
2044
1667
|
this.activeConversationId = conversation.id;
|
|
2045
1668
|
this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
|
|
2046
|
-
this.hydrateStoredTimeline(conversation);
|
|
2047
|
-
const snapshot = this.latestSnapshot(conversation.id);
|
|
2048
1669
|
this.input.send(createEnvelope({
|
|
2049
1670
|
type: "agent.v2.conversation.opened",
|
|
2050
|
-
|
|
2051
|
-
payload: {
|
|
2052
|
-
conversation: this.conversationSnapshot(conversation),
|
|
2053
|
-
snapshot: snapshot.items,
|
|
2054
|
-
revision: this.getRevision(conversation.id),
|
|
2055
|
-
cursor: snapshot.cursor,
|
|
2056
|
-
hasMore: snapshot.hasMore,
|
|
2057
|
-
source: "device-history",
|
|
2058
|
-
canonical: true,
|
|
2059
|
-
},
|
|
1671
|
+
sessionId: this.input.sessionId,
|
|
1672
|
+
payload: { conversation, snapshot: this.timelines.get(conversation.id) ?? [] },
|
|
2060
1673
|
}));
|
|
2061
1674
|
return conversation;
|
|
2062
1675
|
} catch (error) {
|
|
@@ -2092,10 +1705,6 @@ export class AgentWorkspaceProxy {
|
|
|
2092
1705
|
collaborationMode: payload.collaborationMode,
|
|
2093
1706
|
status: "error",
|
|
2094
1707
|
archived: false,
|
|
2095
|
-
timelineRevision: this.getRevision(fallbackId),
|
|
2096
|
-
historyComplete: false,
|
|
2097
|
-
source: "device",
|
|
2098
|
-
canonical: true,
|
|
2099
1708
|
lastMessagePreview: message,
|
|
2100
1709
|
lastActivityAt: now,
|
|
2101
1710
|
createdAt: now,
|
|
@@ -2111,15 +1720,8 @@ export class AgentWorkspaceProxy {
|
|
|
2111
1720
|
});
|
|
2112
1721
|
this.input.send(createEnvelope({
|
|
2113
1722
|
type: "agent.v2.conversation.opened",
|
|
2114
|
-
|
|
2115
|
-
payload: {
|
|
2116
|
-
conversation: this.conversationSnapshot(conversation),
|
|
2117
|
-
snapshot: snapshotTimelineItems(this.timelines.get(conversation.id) ?? []),
|
|
2118
|
-
revision: this.getRevision(conversation.id),
|
|
2119
|
-
hasMore: false,
|
|
2120
|
-
source: "device",
|
|
2121
|
-
canonical: true,
|
|
2122
|
-
},
|
|
1723
|
+
sessionId: this.input.sessionId,
|
|
1724
|
+
payload: { conversation, snapshot: this.timelines.get(conversation.id) ?? [] },
|
|
2123
1725
|
}));
|
|
2124
1726
|
return conversation;
|
|
2125
1727
|
}
|
|
@@ -2128,11 +1730,10 @@ export class AgentWorkspaceProxy {
|
|
|
2128
1730
|
conversationId: string;
|
|
2129
1731
|
clientMessageId: string;
|
|
2130
1732
|
contentBlocks: AgentContentBlock[];
|
|
2131
|
-
model?: string
|
|
2132
|
-
reasoningEffort?: string
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
collaborationMode?: AgentCollaborationMode | null;
|
|
1733
|
+
model?: string;
|
|
1734
|
+
reasoningEffort?: string;
|
|
1735
|
+
permissionMode?: AgentPermissionMode;
|
|
1736
|
+
collaborationMode?: AgentCollaborationMode;
|
|
2136
1737
|
}): Promise<void> {
|
|
2137
1738
|
const conversation =
|
|
2138
1739
|
this.conversations.get(payload.conversationId) ??
|
|
@@ -2175,25 +1776,44 @@ export class AgentWorkspaceProxy {
|
|
|
2175
1776
|
return;
|
|
2176
1777
|
}
|
|
2177
1778
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
if (Object.prototype.hasOwnProperty.call(payload, "permissionMode")) {
|
|
2188
|
-
conversation.permissionMode = payload.permissionMode ?? undefined;
|
|
2189
|
-
}
|
|
2190
|
-
if (Object.prototype.hasOwnProperty.call(payload, "collaborationMode")) {
|
|
2191
|
-
conversation.collaborationMode = payload.collaborationMode ?? "default";
|
|
2192
|
-
}
|
|
1779
|
+
const priorSettings = {
|
|
1780
|
+
model: conversation.model,
|
|
1781
|
+
reasoningEffort: conversation.reasoningEffort,
|
|
1782
|
+
permissionMode: conversation.permissionMode,
|
|
1783
|
+
};
|
|
1784
|
+
conversation.model = payload.model ?? conversation.model;
|
|
1785
|
+
conversation.reasoningEffort = payload.reasoningEffort ?? conversation.reasoningEffort;
|
|
1786
|
+
conversation.permissionMode = payload.permissionMode ?? conversation.permissionMode;
|
|
1787
|
+
conversation.collaborationMode = payload.collaborationMode ?? conversation.collaborationMode;
|
|
2193
1788
|
conversation.status = "running";
|
|
2194
1789
|
conversation.lastActivityAt = Date.now();
|
|
2195
1790
|
this.activeConversationId = conversation.id;
|
|
2196
1791
|
|
|
1792
|
+
if (payload.model && payload.model !== priorSettings.model) {
|
|
1793
|
+
const runtimeCapabilities = this.providerCapabilities.get(conversation.provider);
|
|
1794
|
+
const label = runtimeCapabilities?.models?.find((m) => m.id === payload.model)?.label ?? payload.model;
|
|
1795
|
+
this.emitNotice({
|
|
1796
|
+
conversationId: conversation.id,
|
|
1797
|
+
kind: "model_changed",
|
|
1798
|
+
title: `已切换模型 · ${label}`,
|
|
1799
|
+
detail: `${providerLabel(conversation.provider)} 下次回复将使用 ${payload.model}`,
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
if (payload.reasoningEffort && payload.reasoningEffort !== priorSettings.reasoningEffort) {
|
|
1803
|
+
this.emitNotice({
|
|
1804
|
+
conversationId: conversation.id,
|
|
1805
|
+
kind: "effort_changed",
|
|
1806
|
+
title: `思考强度 · ${payload.reasoningEffort}`,
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
if (payload.permissionMode && payload.permissionMode !== priorSettings.permissionMode) {
|
|
1810
|
+
this.emitNotice({
|
|
1811
|
+
conversationId: conversation.id,
|
|
1812
|
+
kind: "permission_changed",
|
|
1813
|
+
title: `权限模式 · ${payload.permissionMode}`,
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
|
|
2197
1817
|
const userText = textFromBlocks(payload.contentBlocks);
|
|
2198
1818
|
this.addItem(conversation.id, {
|
|
2199
1819
|
id: payload.clientMessageId,
|
|
@@ -2211,11 +1831,10 @@ export class AgentWorkspaceProxy {
|
|
|
2211
1831
|
sessionId: conversation.agentSessionId,
|
|
2212
1832
|
content: payload.contentBlocks,
|
|
2213
1833
|
clientMessageId: payload.clientMessageId,
|
|
2214
|
-
model:
|
|
2215
|
-
reasoningEffort:
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
collaborationMode: conversation.collaborationMode,
|
|
1834
|
+
model: payload.model,
|
|
1835
|
+
reasoningEffort: payload.reasoningEffort,
|
|
1836
|
+
permissionMode: payload.permissionMode,
|
|
1837
|
+
collaborationMode: payload.collaborationMode ?? conversation.collaborationMode,
|
|
2219
1838
|
cwd: conversation.cwd,
|
|
2220
1839
|
});
|
|
2221
1840
|
const nextAgentSessionId = this.extractSessionId(result);
|
|
@@ -2308,7 +1927,6 @@ export class AgentWorkspaceProxy {
|
|
|
2308
1927
|
contentBlocks: [{ type: "text", text: rawText }],
|
|
2309
1928
|
model: conversation.model,
|
|
2310
1929
|
reasoningEffort: conversation.reasoningEffort,
|
|
2311
|
-
serviceTier: conversation.serviceTier,
|
|
2312
1930
|
permissionMode: conversation.permissionMode,
|
|
2313
1931
|
collaborationMode: conversation.collaborationMode,
|
|
2314
1932
|
});
|
|
@@ -2352,8 +1970,21 @@ export class AgentWorkspaceProxy {
|
|
|
2352
1970
|
return;
|
|
2353
1971
|
}
|
|
2354
1972
|
|
|
2355
|
-
if (
|
|
2356
|
-
|
|
1973
|
+
if (conversation.provider !== "codex") {
|
|
1974
|
+
const message = `${command.title} 暂无 ${providerLabel(conversation.provider)} 原生实现。`;
|
|
1975
|
+
this.emitNotice({
|
|
1976
|
+
conversationId: conversation.id,
|
|
1977
|
+
kind: "native_unsupported",
|
|
1978
|
+
title: `${command.title} 不支持`,
|
|
1979
|
+
detail: `${providerLabel(conversation.provider)} 当前未实现这个命令。`,
|
|
1980
|
+
});
|
|
1981
|
+
this.addItem(conversation.id, {
|
|
1982
|
+
id: id("error"),
|
|
1983
|
+
conversationId: conversation.id,
|
|
1984
|
+
type: "error",
|
|
1985
|
+
error: message,
|
|
1986
|
+
createdAt: now,
|
|
1987
|
+
});
|
|
2357
1988
|
return;
|
|
2358
1989
|
}
|
|
2359
1990
|
|
|
@@ -2370,23 +2001,6 @@ export class AgentWorkspaceProxy {
|
|
|
2370
2001
|
return;
|
|
2371
2002
|
}
|
|
2372
2003
|
|
|
2373
|
-
if (command.name === "review" || command.name === "subagents") {
|
|
2374
|
-
const prompt = command.name === "review"
|
|
2375
|
-
? args || "Review the current local changes."
|
|
2376
|
-
: args || "Run subagents for distinct tasks in parallel when useful, then synthesize the results.";
|
|
2377
|
-
await this.sendPrompt({
|
|
2378
|
-
conversationId: conversation.id,
|
|
2379
|
-
clientMessageId: id(command.name),
|
|
2380
|
-
contentBlocks: [{ type: "text", text: prompt }],
|
|
2381
|
-
model: conversation.model,
|
|
2382
|
-
reasoningEffort: conversation.reasoningEffort,
|
|
2383
|
-
serviceTier: conversation.serviceTier,
|
|
2384
|
-
permissionMode: conversation.permissionMode,
|
|
2385
|
-
collaborationMode: conversation.collaborationMode,
|
|
2386
|
-
});
|
|
2387
|
-
return;
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
2004
|
if (command.name === "compact") {
|
|
2391
2005
|
if (!(client instanceof AcpClient)) throw new Error("当前 Codex runtime 不支持原生 compact。");
|
|
2392
2006
|
conversation.status = "running";
|
|
@@ -2407,7 +2021,7 @@ export class AgentWorkspaceProxy {
|
|
|
2407
2021
|
return;
|
|
2408
2022
|
}
|
|
2409
2023
|
|
|
2410
|
-
if (command.name === "clear"
|
|
2024
|
+
if (command.name === "clear") {
|
|
2411
2025
|
if (!client) throw new Error("Agent provider 不在线。");
|
|
2412
2026
|
const result = await client.newSession({ cwd: conversation.cwd });
|
|
2413
2027
|
const nextAgentSessionId = this.extractSessionId(result) ?? id("agent-session");
|
|
@@ -2424,6 +2038,22 @@ export class AgentWorkspaceProxy {
|
|
|
2424
2038
|
return;
|
|
2425
2039
|
}
|
|
2426
2040
|
|
|
2041
|
+
if (command.name === "review" || command.name === "subagents") {
|
|
2042
|
+
const prompt = command.name === "review"
|
|
2043
|
+
? args || "Review the current local changes."
|
|
2044
|
+
: args || "Run subagents for distinct tasks in parallel when useful, then synthesize the results.";
|
|
2045
|
+
await this.sendPrompt({
|
|
2046
|
+
conversationId: conversation.id,
|
|
2047
|
+
clientMessageId: id(command.name),
|
|
2048
|
+
contentBlocks: [{ type: "text", text: prompt }],
|
|
2049
|
+
model: conversation.model,
|
|
2050
|
+
reasoningEffort: conversation.reasoningEffort,
|
|
2051
|
+
permissionMode: conversation.permissionMode,
|
|
2052
|
+
collaborationMode: conversation.collaborationMode,
|
|
2053
|
+
});
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2427
2057
|
throw new Error(`命令暂未实现:/${command.name}`);
|
|
2428
2058
|
} catch (error) {
|
|
2429
2059
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2438,46 +2068,6 @@ export class AgentWorkspaceProxy {
|
|
|
2438
2068
|
}
|
|
2439
2069
|
}
|
|
2440
2070
|
|
|
2441
|
-
private async executeGitNativeCommand(
|
|
2442
|
-
conversation: AgentConversation,
|
|
2443
|
-
commandName: string,
|
|
2444
|
-
args?: string,
|
|
2445
|
-
): Promise<void> {
|
|
2446
|
-
const git = gitCommandArgs(commandName, args);
|
|
2447
|
-
const toolId = id(commandName);
|
|
2448
|
-
const now = Date.now();
|
|
2449
|
-
conversation.status = "running";
|
|
2450
|
-
conversation.lastMessagePreview = git.display;
|
|
2451
|
-
conversation.lastActivityAt = now;
|
|
2452
|
-
this.emitConversation(conversation);
|
|
2453
|
-
this.upsertTool(conversation.id, {
|
|
2454
|
-
id: toolId,
|
|
2455
|
-
name: "命令",
|
|
2456
|
-
input: `${git.display}\n\ncwd: ${conversation.cwd}`,
|
|
2457
|
-
createdAt: now,
|
|
2458
|
-
status: "running",
|
|
2459
|
-
});
|
|
2460
|
-
const result = await runProcess("git", git.argv, {
|
|
2461
|
-
cwd: conversation.cwd,
|
|
2462
|
-
maxBytes: COMMAND_OUTPUT_MAX_BYTES,
|
|
2463
|
-
});
|
|
2464
|
-
const ok = result.exitCode === 0;
|
|
2465
|
-
this.upsertTool(conversation.id, {
|
|
2466
|
-
id: toolId,
|
|
2467
|
-
name: "命令",
|
|
2468
|
-
input: `${git.display}\n\ncwd: ${conversation.cwd}`,
|
|
2469
|
-
output: result.output || (ok ? "完成" : `退出码 ${result.exitCode ?? "unknown"}`),
|
|
2470
|
-
createdAt: now,
|
|
2471
|
-
status: ok ? "completed" : "failed",
|
|
2472
|
-
});
|
|
2473
|
-
conversation.status = ok ? "idle" : "error";
|
|
2474
|
-
conversation.lastMessagePreview = ok
|
|
2475
|
-
? `${git.display} 完成`
|
|
2476
|
-
: `${git.display} 失败`;
|
|
2477
|
-
conversation.lastActivityAt = Date.now();
|
|
2478
|
-
this.emitConversation(conversation);
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
2071
|
private handleRequest(method: string, params: unknown): Promise<unknown> | unknown {
|
|
2482
2072
|
if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
|
|
2483
2073
|
return this.handleStructuredInput(params, true);
|
|
@@ -2512,12 +2102,20 @@ export class AgentWorkspaceProxy {
|
|
|
2512
2102
|
this.sendCapabilities();
|
|
2513
2103
|
}
|
|
2514
2104
|
}
|
|
2105
|
+
const initModel = firstString(asRecord(params), ["model", "currentModel"]);
|
|
2106
|
+
if (initModel && conversationId) {
|
|
2107
|
+
const conversation = this.conversations.get(conversationId);
|
|
2108
|
+
if (conversation && conversation.model !== initModel) {
|
|
2109
|
+
conversation.model = initModel;
|
|
2110
|
+
conversation.lastActivityAt = Date.now();
|
|
2111
|
+
this.emitConversation(conversation);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2515
2114
|
return;
|
|
2516
2115
|
}
|
|
2517
2116
|
if (
|
|
2518
2117
|
method.startsWith("account/") ||
|
|
2519
2118
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
2520
|
-
method.startsWith("remoteControl/") ||
|
|
2521
2119
|
method === "thread/status/changed" ||
|
|
2522
2120
|
method === "thread/tokenUsage/updated" ||
|
|
2523
2121
|
method === "serverRequest/resolved" ||
|
|
@@ -2545,11 +2143,12 @@ export class AgentWorkspaceProxy {
|
|
|
2545
2143
|
this.conversationByAgentSessionId.set(agentSessionId, conversationId);
|
|
2546
2144
|
const conversation = this.conversations.get(conversationId);
|
|
2547
2145
|
if (conversation) {
|
|
2548
|
-
if (conversation.agentSessionId && conversation.agentSessionId !== agentSessionId) {
|
|
2549
|
-
this.conversationByAgentSessionId.delete(conversation.agentSessionId);
|
|
2550
|
-
}
|
|
2551
2146
|
conversation.agentSessionId = agentSessionId;
|
|
2552
2147
|
conversation.lastActivityAt = Date.now();
|
|
2148
|
+
const startedModel = firstString(asRecord(params), ["model", "currentModel"]);
|
|
2149
|
+
if (startedModel && conversation.model !== startedModel) {
|
|
2150
|
+
conversation.model = startedModel;
|
|
2151
|
+
}
|
|
2553
2152
|
this.emitConversation(conversation);
|
|
2554
2153
|
}
|
|
2555
2154
|
}
|
|
@@ -3129,7 +2728,7 @@ export class AgentWorkspaceProxy {
|
|
|
3129
2728
|
this.upsertItem(conversationId, item);
|
|
3130
2729
|
this.input.send(createEnvelope({
|
|
3131
2730
|
type: "agent.v2.permission.request",
|
|
3132
|
-
|
|
2731
|
+
sessionId: this.input.sessionId,
|
|
3133
2732
|
payload: { conversationId, ...permission, item },
|
|
3134
2733
|
}));
|
|
3135
2734
|
|
|
@@ -3140,14 +2739,6 @@ export class AgentWorkspaceProxy {
|
|
|
3140
2739
|
this.permissionWaiters.delete(requestId);
|
|
3141
2740
|
this.permissionSources.delete(requestId);
|
|
3142
2741
|
resolve(formatPermissionResponse(source, "cancelled", "cancelled"));
|
|
3143
|
-
this.markPermission(conversationId, requestId, {
|
|
3144
|
-
permissionOutcome: "cancelled",
|
|
3145
|
-
optionId: "cancelled",
|
|
3146
|
-
permissionLive: false,
|
|
3147
|
-
permissionPending: false,
|
|
3148
|
-
permissionExpired: true,
|
|
3149
|
-
permissionError: "等待授权超时",
|
|
3150
|
-
});
|
|
3151
2742
|
this.updateConversationStatus(conversationId, "idle");
|
|
3152
2743
|
}, PERMISSION_TIMEOUT_MS);
|
|
3153
2744
|
this.permissionWaiters.set(requestId, { resolve, timer });
|
|
@@ -3187,8 +2778,6 @@ export class AgentWorkspaceProxy {
|
|
|
3187
2778
|
this.markPermission(payload.conversationId, payload.requestId, {
|
|
3188
2779
|
permissionOutcome: payload.outcome,
|
|
3189
2780
|
optionId: selectedOptionId,
|
|
3190
|
-
permissionLive: false,
|
|
3191
|
-
permissionExpired: false,
|
|
3192
2781
|
permissionError: undefined,
|
|
3193
2782
|
permissionPending: false,
|
|
3194
2783
|
});
|
|
@@ -3249,228 +2838,6 @@ export class AgentWorkspaceProxy {
|
|
|
3249
2838
|
});
|
|
3250
2839
|
}
|
|
3251
2840
|
|
|
3252
|
-
private getRevision(conversationId: string): number {
|
|
3253
|
-
return this.conversationRevisions.get(conversationId) ??
|
|
3254
|
-
this.conversations.get(conversationId)?.timelineRevision ??
|
|
3255
|
-
0;
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
|
-
private setRevisionFloor(conversationId: string, revision: number): number {
|
|
3259
|
-
const nextRevision = Math.max(this.getRevision(conversationId), revision);
|
|
3260
|
-
this.conversationRevisions.set(conversationId, nextRevision);
|
|
3261
|
-
const conversation = this.conversations.get(conversationId);
|
|
3262
|
-
if (conversation) {
|
|
3263
|
-
conversation.timelineRevision = nextRevision;
|
|
3264
|
-
conversation.runningTurnId = this.currentTurnIds.get(conversationId);
|
|
3265
|
-
}
|
|
3266
|
-
return nextRevision;
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
private conversationSnapshot(conversation: AgentConversation): AgentConversation {
|
|
3270
|
-
return {
|
|
3271
|
-
...conversation,
|
|
3272
|
-
timelineRevision: this.getRevision(conversation.id),
|
|
3273
|
-
historyComplete: conversation.historyComplete ?? false,
|
|
3274
|
-
runningTurnId: this.currentTurnIds.get(conversation.id),
|
|
3275
|
-
source: conversation.source ?? "device",
|
|
3276
|
-
canonical: conversation.canonical ?? true,
|
|
3277
|
-
};
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
private annotateTimelineItem(
|
|
3281
|
-
item: AgentTimelineItem,
|
|
3282
|
-
revision: number | undefined,
|
|
3283
|
-
source: AgentSyncSource,
|
|
3284
|
-
): AgentTimelineItem {
|
|
3285
|
-
return {
|
|
3286
|
-
...item,
|
|
3287
|
-
revision: revision ?? item.revision,
|
|
3288
|
-
source: item.source ?? source,
|
|
3289
|
-
canonical: item.canonical ?? true,
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
|
|
3293
|
-
private recordRevisionEvent(
|
|
3294
|
-
conversationId: string,
|
|
3295
|
-
change: { item?: AgentTimelineItem; conversation?: AgentConversation },
|
|
3296
|
-
): AgentRevisionEvent {
|
|
3297
|
-
const revision = this.getRevision(conversationId) + 1;
|
|
3298
|
-
this.conversationRevisions.set(conversationId, revision);
|
|
3299
|
-
const conversation = this.conversations.get(conversationId);
|
|
3300
|
-
if (conversation) {
|
|
3301
|
-
conversation.timelineRevision = revision;
|
|
3302
|
-
conversation.runningTurnId = this.currentTurnIds.get(conversationId);
|
|
3303
|
-
}
|
|
3304
|
-
const event: AgentRevisionEvent = {
|
|
3305
|
-
revision,
|
|
3306
|
-
item: change.item ? this.annotateTimelineItem(change.item, revision, "device-live") : undefined,
|
|
3307
|
-
conversation: change.conversation ? this.conversationSnapshot(change.conversation) : undefined,
|
|
3308
|
-
};
|
|
3309
|
-
const events = this.revisionEvents.get(conversationId) ?? [];
|
|
3310
|
-
events.push(event);
|
|
3311
|
-
if (events.length > MAX_DELTA_EVENTS) {
|
|
3312
|
-
events.splice(0, events.length - MAX_DELTA_EVENTS);
|
|
3313
|
-
}
|
|
3314
|
-
this.revisionEvents.set(conversationId, events);
|
|
3315
|
-
return event;
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
private storedTimeline(conversation: AgentConversation, maxItems = HISTORY_PAGE_MAX_ITEMS): AgentTimelineItem[] {
|
|
3319
|
-
if (!conversation.agentSessionId) return [];
|
|
3320
|
-
const result = conversation.provider === "codex"
|
|
3321
|
-
? loadCodexStoredTimeline(conversation.agentSessionId, conversation.id, conversation.cwd || this.input.cwd, { maxItems })
|
|
3322
|
-
: conversation.provider === "claude"
|
|
3323
|
-
? loadClaudeStoredTimeline(conversation.agentSessionId, conversation.id, { maxItems })
|
|
3324
|
-
: { items: [] };
|
|
3325
|
-
return result.items
|
|
3326
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3327
|
-
.map((item, index) => this.annotateTimelineItem(item as AgentTimelineItem, index + 1, "device-history"));
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
|
-
private canonicalTimeline(conversation: AgentConversation, maxItems = HISTORY_PAGE_MAX_ITEMS): AgentTimelineItem[] {
|
|
3331
|
-
const merged = new Map<string, AgentTimelineItem>();
|
|
3332
|
-
for (const item of this.storedTimeline(conversation, maxItems)) {
|
|
3333
|
-
merged.set(item.id, item);
|
|
3334
|
-
}
|
|
3335
|
-
for (const item of this.timelines.get(conversation.id) ?? []) {
|
|
3336
|
-
merged.set(item.id, this.annotateTimelineItem(item, item.revision, item.source ?? "device-live"));
|
|
3337
|
-
}
|
|
3338
|
-
const items = [...merged.values()]
|
|
3339
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3340
|
-
.slice(-maxItems);
|
|
3341
|
-
this.setRevisionFloor(conversation.id, Math.max(this.getRevision(conversation.id), items.length));
|
|
3342
|
-
return items;
|
|
3343
|
-
}
|
|
3344
|
-
|
|
3345
|
-
private async appServerTimeline(conversation: AgentConversation, maxItems = HISTORY_PAGE_MAX_ITEMS): Promise<AgentTimelineItem[]> {
|
|
3346
|
-
if (conversation.provider !== "codex" || !conversation.agentSessionId) return [];
|
|
3347
|
-
if (this.protocolForProvider(conversation.provider) !== "codex-app-server") return [];
|
|
3348
|
-
const client = this.clientForProvider(conversation.provider);
|
|
3349
|
-
const listTurns = (client as { listTurns?: (input: { sessionId: string; limit?: number }) => Promise<unknown> } | undefined)?.listTurns;
|
|
3350
|
-
if (typeof listTurns !== "function") return [];
|
|
3351
|
-
try {
|
|
3352
|
-
const result = await listTurns.call(client, { sessionId: conversation.agentSessionId, limit: maxItems });
|
|
3353
|
-
return this.timelineFromAppServerTurns(conversation.id, conversation.agentSessionId, result);
|
|
3354
|
-
} catch (error) {
|
|
3355
|
-
if (this.input.verbose) {
|
|
3356
|
-
process.stderr.write(`[agent:v2] thread/turns/list failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
3357
|
-
}
|
|
3358
|
-
return [];
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
private timelineFromAppServerTurns(
|
|
3363
|
-
conversationId: string,
|
|
3364
|
-
agentSessionId: string,
|
|
3365
|
-
value: unknown,
|
|
3366
|
-
): AgentTimelineItem[] {
|
|
3367
|
-
const raw = asRecord(value);
|
|
3368
|
-
const turns =
|
|
3369
|
-
Array.isArray(value) ? value :
|
|
3370
|
-
Array.isArray(raw?.turns) ? raw.turns :
|
|
3371
|
-
Array.isArray(raw?.items) ? raw.items :
|
|
3372
|
-
Array.isArray(raw?.entries) ? raw.entries :
|
|
3373
|
-
[];
|
|
3374
|
-
const items: AgentTimelineItem[] = [];
|
|
3375
|
-
turns.forEach((entry, index) => {
|
|
3376
|
-
const turn = asRecord(entry);
|
|
3377
|
-
if (!turn) return;
|
|
3378
|
-
const turnId = firstString(turn, ["id", "turnId", "turn_id"]) ?? `turn-${index + 1}`;
|
|
3379
|
-
const createdAt = parseTimestamp(turn.createdAt ?? turn.created_at ?? turn.startedAt ?? turn.started_at) ?? Date.now() + index;
|
|
3380
|
-
const updatedAt = parseTimestamp(turn.updatedAt ?? turn.updated_at ?? turn.completedAt ?? turn.completed_at);
|
|
3381
|
-
const userText = appServerText(turn.input ?? turn.prompt ?? turn.user ?? turn.userMessage ?? turn.request);
|
|
3382
|
-
if (userText) {
|
|
3383
|
-
items.push({
|
|
3384
|
-
id: `app-server:${agentSessionId}:${turnId}:user`,
|
|
3385
|
-
conversationId,
|
|
3386
|
-
type: "message",
|
|
3387
|
-
kind: "chat",
|
|
3388
|
-
turnId,
|
|
3389
|
-
role: "user",
|
|
3390
|
-
content: [{ type: "text", text: userText }],
|
|
3391
|
-
text: userText,
|
|
3392
|
-
createdAt,
|
|
3393
|
-
updatedAt,
|
|
3394
|
-
metadata: { source: "app-server", provider: "codex" },
|
|
3395
|
-
});
|
|
3396
|
-
}
|
|
3397
|
-
const assistantText = appServerText(turn.output ?? turn.response ?? turn.assistant ?? turn.assistantMessage ?? turn.result);
|
|
3398
|
-
if (assistantText) {
|
|
3399
|
-
items.push({
|
|
3400
|
-
id: `app-server:${agentSessionId}:${turnId}:assistant`,
|
|
3401
|
-
conversationId,
|
|
3402
|
-
type: "message",
|
|
3403
|
-
kind: "chat",
|
|
3404
|
-
turnId,
|
|
3405
|
-
role: "assistant",
|
|
3406
|
-
content: [{ type: "text", text: assistantText }],
|
|
3407
|
-
text: assistantText,
|
|
3408
|
-
createdAt: updatedAt ?? createdAt + 1,
|
|
3409
|
-
updatedAt,
|
|
3410
|
-
metadata: { source: "app-server", provider: "codex" },
|
|
3411
|
-
});
|
|
3412
|
-
}
|
|
3413
|
-
const nestedItems = Array.isArray(turn.items) ? turn.items : Array.isArray(turn.messages) ? turn.messages : [];
|
|
3414
|
-
for (const nested of nestedItems) {
|
|
3415
|
-
const nestedRecord = asRecord(nested);
|
|
3416
|
-
if (!nestedRecord) continue;
|
|
3417
|
-
const text = appServerText(nestedRecord.content ?? nestedRecord.text ?? nestedRecord.message);
|
|
3418
|
-
const role = nestedRecord.role === "user" || nestedRecord.role === "assistant" || nestedRecord.role === "system"
|
|
3419
|
-
? nestedRecord.role
|
|
3420
|
-
: undefined;
|
|
3421
|
-
if (!text || !role) continue;
|
|
3422
|
-
const itemId = firstString(nestedRecord, ["id", "itemId", "messageId"]) ?? `${role}-${items.length + 1}`;
|
|
3423
|
-
items.push({
|
|
3424
|
-
id: `app-server:${agentSessionId}:${turnId}:${itemId}`,
|
|
3425
|
-
conversationId,
|
|
3426
|
-
type: "message",
|
|
3427
|
-
kind: "chat",
|
|
3428
|
-
turnId,
|
|
3429
|
-
itemId,
|
|
3430
|
-
role,
|
|
3431
|
-
content: [{ type: "text", text }],
|
|
3432
|
-
text,
|
|
3433
|
-
createdAt: parseTimestamp(nestedRecord.createdAt ?? nestedRecord.created_at) ?? createdAt + items.length,
|
|
3434
|
-
updatedAt: parseTimestamp(nestedRecord.updatedAt ?? nestedRecord.updated_at),
|
|
3435
|
-
metadata: { source: "app-server", provider: "codex" },
|
|
3436
|
-
});
|
|
3437
|
-
}
|
|
3438
|
-
});
|
|
3439
|
-
return items
|
|
3440
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3441
|
-
.map((item, index) => this.annotateTimelineItem(item, index + 1, "app-server"));
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
private mergeCanonicalTimelineItems(items: AgentTimelineItem[], maxItems: number): AgentTimelineItem[] {
|
|
3445
|
-
const byKey = new Map<string, AgentTimelineItem>();
|
|
3446
|
-
for (const item of items.sort((a, b) => a.createdAt - b.createdAt)) {
|
|
3447
|
-
const key = item.type === "message" && item.role && item.text
|
|
3448
|
-
? `${item.role}:${item.text.replace(/\s+/g, " ").trim().slice(0, 500)}`
|
|
3449
|
-
: item.id;
|
|
3450
|
-
const existing = byKey.get(key);
|
|
3451
|
-
if (!existing || item.source === "app-server" || (item.updatedAt ?? item.createdAt) >= (existing.updatedAt ?? existing.createdAt)) {
|
|
3452
|
-
byKey.set(key, item);
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
return [...byKey.values()]
|
|
3456
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3457
|
-
.slice(-maxItems);
|
|
3458
|
-
}
|
|
3459
|
-
|
|
3460
|
-
private latestSnapshot(conversationId: string): {
|
|
3461
|
-
items: AgentTimelineItem[];
|
|
3462
|
-
cursor?: string;
|
|
3463
|
-
hasMore: boolean;
|
|
3464
|
-
} {
|
|
3465
|
-
const timeline = this.timelines.get(conversationId) ?? [];
|
|
3466
|
-
const start = Math.max(0, timeline.length - MAX_SNAPSHOT_ITEMS);
|
|
3467
|
-
return {
|
|
3468
|
-
items: timeline.slice(start).map((item) => snapshotTimelineItem(item)),
|
|
3469
|
-
cursor: start > 0 ? String(start) : undefined,
|
|
3470
|
-
hasMore: start > 0,
|
|
3471
|
-
};
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
2841
|
private addItem(conversationId: string, item: AgentTimelineItem): void {
|
|
3475
2842
|
this.rememberItemConversationId(conversationId, item);
|
|
3476
2843
|
const timeline = this.timelines.get(conversationId) ?? [];
|
|
@@ -3508,8 +2875,6 @@ export class AgentWorkspaceProxy {
|
|
|
3508
2875
|
...existing,
|
|
3509
2876
|
...toolCall,
|
|
3510
2877
|
id: targetToolId,
|
|
3511
|
-
input: toolCall.input ?? existing?.input,
|
|
3512
|
-
output: toolCall.output ?? existing?.output,
|
|
3513
2878
|
createdAt: existing?.createdAt ?? toolCall.createdAt ?? Date.now(),
|
|
3514
2879
|
};
|
|
3515
2880
|
this.toolConversationIds.set(toolCall.id, conversationId);
|
|
@@ -3588,7 +2953,6 @@ export class AgentWorkspaceProxy {
|
|
|
3588
2953
|
conversation.title = conversation.title ?? target.title;
|
|
3589
2954
|
conversation.model = conversation.model ?? target.model;
|
|
3590
2955
|
conversation.reasoningEffort = conversation.reasoningEffort ?? target.reasoningEffort;
|
|
3591
|
-
conversation.serviceTier = conversation.serviceTier ?? target.serviceTier;
|
|
3592
2956
|
conversation.permissionMode = conversation.permissionMode ?? target.permissionMode;
|
|
3593
2957
|
conversation.lastMessagePreview = conversation.lastMessagePreview ?? target.lastMessagePreview;
|
|
3594
2958
|
conversation.createdAt = Math.min(conversation.createdAt, target.createdAt);
|
|
@@ -3605,17 +2969,6 @@ export class AgentWorkspaceProxy {
|
|
|
3605
2969
|
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3606
2970
|
.slice(-MAX_TIMELINE_ITEMS),
|
|
3607
2971
|
);
|
|
3608
|
-
const oldRevision = this.conversationRevisions.get(oldId) ?? 0;
|
|
3609
|
-
if (oldRevision > 0) {
|
|
3610
|
-
this.conversationRevisions.delete(oldId);
|
|
3611
|
-
this.setRevisionFloor(newId, oldRevision);
|
|
3612
|
-
}
|
|
3613
|
-
const oldEvents = this.revisionEvents.get(oldId);
|
|
3614
|
-
if (oldEvents) {
|
|
3615
|
-
this.revisionEvents.delete(oldId);
|
|
3616
|
-
const existingEvents = this.revisionEvents.get(newId) ?? [];
|
|
3617
|
-
this.revisionEvents.set(newId, [...existingEvents, ...oldEvents].slice(-MAX_DELTA_EVENTS));
|
|
3618
|
-
}
|
|
3619
2972
|
|
|
3620
2973
|
for (const [agentSessionId, conversationId] of this.conversationByAgentSessionId) {
|
|
3621
2974
|
if (conversationId === oldId) {
|
|
@@ -3651,48 +3004,18 @@ export class AgentWorkspaceProxy {
|
|
|
3651
3004
|
|
|
3652
3005
|
private emitItem(conversationId: string, item: AgentTimelineItem): void {
|
|
3653
3006
|
const conversation = this.conversations.get(conversationId);
|
|
3654
|
-
const itemSnapshot = snapshotTimelineItem(item, { stripImages: false });
|
|
3655
|
-
const event = this.recordRevisionEvent(conversationId, { conversation, item: itemSnapshot });
|
|
3656
3007
|
this.input.send(createEnvelope({
|
|
3657
3008
|
type: "agent.v2.event",
|
|
3658
|
-
|
|
3659
|
-
payload: {
|
|
3660
|
-
conversationId,
|
|
3661
|
-
conversation: event.conversation,
|
|
3662
|
-
item: event.item,
|
|
3663
|
-
revision: event.revision,
|
|
3664
|
-
source: "device-live",
|
|
3665
|
-
canonical: true,
|
|
3666
|
-
},
|
|
3009
|
+
sessionId: this.input.sessionId,
|
|
3010
|
+
payload: { conversationId, conversation, item },
|
|
3667
3011
|
}));
|
|
3668
3012
|
}
|
|
3669
3013
|
|
|
3670
3014
|
private emitConversation(conversation: AgentConversation): void {
|
|
3671
|
-
const event = this.recordRevisionEvent(conversation.id, { conversation });
|
|
3672
3015
|
this.input.send(createEnvelope({
|
|
3673
3016
|
type: "agent.v2.event",
|
|
3674
|
-
|
|
3675
|
-
payload: {
|
|
3676
|
-
conversationId: conversation.id,
|
|
3677
|
-
conversation: event.conversation,
|
|
3678
|
-
revision: event.revision,
|
|
3679
|
-
source: "device-live",
|
|
3680
|
-
canonical: true,
|
|
3681
|
-
},
|
|
3682
|
-
}));
|
|
3683
|
-
this.input.send(createEnvelope({
|
|
3684
|
-
type: "agent.v2.running_state",
|
|
3685
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
3686
|
-
payload: {
|
|
3687
|
-
conversationId: conversation.id,
|
|
3688
|
-
status: conversation.status,
|
|
3689
|
-
runningTurnId: this.currentTurnIds.get(conversation.id),
|
|
3690
|
-
revision: event.revision,
|
|
3691
|
-
error: conversation.status === "error" ? conversation.lastMessagePreview : undefined,
|
|
3692
|
-
updatedAt: conversation.lastActivityAt,
|
|
3693
|
-
source: "device-live",
|
|
3694
|
-
canonical: true,
|
|
3695
|
-
},
|
|
3017
|
+
sessionId: this.input.sessionId,
|
|
3018
|
+
payload: { conversationId: conversation.id, conversation },
|
|
3696
3019
|
}));
|
|
3697
3020
|
}
|
|
3698
3021
|
|
|
@@ -3707,6 +3030,26 @@ export class AgentWorkspaceProxy {
|
|
|
3707
3030
|
});
|
|
3708
3031
|
}
|
|
3709
3032
|
|
|
3033
|
+
private emitNotice(input: {
|
|
3034
|
+
conversationId?: string;
|
|
3035
|
+
kind:
|
|
3036
|
+
| "model_changed"
|
|
3037
|
+
| "effort_changed"
|
|
3038
|
+
| "permission_changed"
|
|
3039
|
+
| "native_unsupported"
|
|
3040
|
+
| "info"
|
|
3041
|
+
| "warning";
|
|
3042
|
+
title: string;
|
|
3043
|
+
detail?: string;
|
|
3044
|
+
durationMs?: number;
|
|
3045
|
+
}): void {
|
|
3046
|
+
this.input.send(createEnvelope({
|
|
3047
|
+
type: "agent.v2.notice",
|
|
3048
|
+
sessionId: this.input.sessionId,
|
|
3049
|
+
payload: input,
|
|
3050
|
+
}));
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3710
3053
|
private updateConversationPreview(
|
|
3711
3054
|
conversationId: string,
|
|
3712
3055
|
text: string,
|
|
@@ -3734,186 +3077,21 @@ export class AgentWorkspaceProxy {
|
|
|
3734
3077
|
}
|
|
3735
3078
|
|
|
3736
3079
|
private sendSnapshot(conversationId?: string): void {
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
const conversation = this.conversations.get(this.activeConversationId);
|
|
3742
|
-
if (conversation) this.hydrateStoredTimeline(conversation);
|
|
3743
|
-
}
|
|
3744
|
-
const conversations = [...this.conversations.values()].map((conversation) => this.conversationSnapshot(conversation));
|
|
3745
|
-
const snapshot = conversationId ? this.latestSnapshot(conversationId) : { items: [], cursor: undefined, hasMore: false };
|
|
3080
|
+
const conversations = [...this.conversations.values()];
|
|
3081
|
+
const items = conversationId
|
|
3082
|
+
? this.timelines.get(conversationId) ?? []
|
|
3083
|
+
: [...this.timelines.values()].flat();
|
|
3746
3084
|
this.input.send(createEnvelope({
|
|
3747
3085
|
type: "agent.v2.snapshot",
|
|
3748
|
-
|
|
3086
|
+
sessionId: this.input.sessionId,
|
|
3749
3087
|
payload: {
|
|
3750
3088
|
conversations,
|
|
3751
3089
|
activeConversationId: this.activeConversationId,
|
|
3752
|
-
items
|
|
3753
|
-
revision: conversationId ? this.getRevision(conversationId) : undefined,
|
|
3754
|
-
cursor: snapshot.cursor,
|
|
3755
|
-
hasMore: snapshot.hasMore,
|
|
3756
|
-
source: "device-history",
|
|
3757
|
-
canonical: true,
|
|
3090
|
+
items,
|
|
3758
3091
|
},
|
|
3759
3092
|
}));
|
|
3760
3093
|
}
|
|
3761
3094
|
|
|
3762
|
-
private async sendHistoryPage(payload: {
|
|
3763
|
-
conversationId: string;
|
|
3764
|
-
cursor?: string;
|
|
3765
|
-
limit?: number;
|
|
3766
|
-
direction?: "older" | "newer";
|
|
3767
|
-
}): Promise<void> {
|
|
3768
|
-
const conversation = this.conversations.get(payload.conversationId);
|
|
3769
|
-
if (!conversation) return;
|
|
3770
|
-
const limit = Math.min(Math.max(payload.limit ?? MAX_SNAPSHOT_ITEMS, 1), 200);
|
|
3771
|
-
const items = this.mergeCanonicalTimelineItems([
|
|
3772
|
-
...this.canonicalTimeline(conversation, HISTORY_PAGE_MAX_ITEMS),
|
|
3773
|
-
...await this.appServerTimeline(conversation, HISTORY_PAGE_MAX_ITEMS),
|
|
3774
|
-
], HISTORY_PAGE_MAX_ITEMS);
|
|
3775
|
-
this.timelines.set(
|
|
3776
|
-
conversation.id,
|
|
3777
|
-
items.slice(-MAX_TIMELINE_ITEMS).map((item) => this.annotateTimelineItem(item, item.revision, item.source ?? "device-history")),
|
|
3778
|
-
);
|
|
3779
|
-
for (const item of this.timelines.get(conversation.id) ?? []) {
|
|
3780
|
-
this.rememberItemConversationId(conversation.id, item);
|
|
3781
|
-
}
|
|
3782
|
-
|
|
3783
|
-
const direction = payload.direction ?? "older";
|
|
3784
|
-
let page: AgentTimelineItem[];
|
|
3785
|
-
let cursor: string | undefined;
|
|
3786
|
-
let hasMore = false;
|
|
3787
|
-
if (direction === "newer") {
|
|
3788
|
-
const start = clampHistoryCursor(payload.cursor, 0, items.length);
|
|
3789
|
-
const end = Math.min(items.length, start + limit);
|
|
3790
|
-
page = items.slice(start, end);
|
|
3791
|
-
hasMore = end < items.length;
|
|
3792
|
-
cursor = hasMore ? String(end) : undefined;
|
|
3793
|
-
} else {
|
|
3794
|
-
const end = clampHistoryCursor(payload.cursor, items.length, items.length);
|
|
3795
|
-
const start = Math.max(0, end - limit);
|
|
3796
|
-
page = items.slice(start, end);
|
|
3797
|
-
hasMore = start > 0;
|
|
3798
|
-
cursor = hasMore ? String(start) : undefined;
|
|
3799
|
-
}
|
|
3800
|
-
conversation.historyComplete = !hasMore;
|
|
3801
|
-
const revision = this.setRevisionFloor(conversation.id, Math.max(this.getRevision(conversation.id), items.length));
|
|
3802
|
-
this.input.send(createEnvelope({
|
|
3803
|
-
type: "agent.v2.history.page",
|
|
3804
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
3805
|
-
payload: {
|
|
3806
|
-
conversationId: conversation.id,
|
|
3807
|
-
conversation: this.conversationSnapshot(conversation),
|
|
3808
|
-
items: page.map((item) => snapshotTimelineItem(item)),
|
|
3809
|
-
revision,
|
|
3810
|
-
cursor,
|
|
3811
|
-
hasMore,
|
|
3812
|
-
source: "device-history",
|
|
3813
|
-
canonical: true,
|
|
3814
|
-
},
|
|
3815
|
-
}));
|
|
3816
|
-
}
|
|
3817
|
-
|
|
3818
|
-
private sendDelta(payload: {
|
|
3819
|
-
conversationId: string;
|
|
3820
|
-
sinceRevision?: number;
|
|
3821
|
-
limit?: number;
|
|
3822
|
-
}): void {
|
|
3823
|
-
const conversation = this.conversations.get(payload.conversationId);
|
|
3824
|
-
if (!conversation) return;
|
|
3825
|
-
this.hydrateStoredTimeline(conversation);
|
|
3826
|
-
const sinceRevision = payload.sinceRevision ?? 0;
|
|
3827
|
-
const limit = Math.min(Math.max(payload.limit ?? 100, 1), 500);
|
|
3828
|
-
const events = this.revisionEvents.get(conversation.id) ?? [];
|
|
3829
|
-
const oldestAvailable = events[0]?.revision ?? this.getRevision(conversation.id);
|
|
3830
|
-
const newestRevision = this.getRevision(conversation.id);
|
|
3831
|
-
const reset = sinceRevision > 0 &&
|
|
3832
|
-
(
|
|
3833
|
-
sinceRevision > newestRevision ||
|
|
3834
|
-
(
|
|
3835
|
-
sinceRevision < newestRevision &&
|
|
3836
|
-
(events.length === 0 || sinceRevision < oldestAvailable - 1)
|
|
3837
|
-
)
|
|
3838
|
-
);
|
|
3839
|
-
|
|
3840
|
-
if (reset) {
|
|
3841
|
-
const snapshot = this.latestSnapshot(conversation.id);
|
|
3842
|
-
this.input.send(createEnvelope({
|
|
3843
|
-
type: "agent.v2.delta",
|
|
3844
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
3845
|
-
payload: {
|
|
3846
|
-
conversationId: conversation.id,
|
|
3847
|
-
conversation: this.conversationSnapshot(conversation),
|
|
3848
|
-
items: snapshot.items,
|
|
3849
|
-
sinceRevision,
|
|
3850
|
-
revision: newestRevision,
|
|
3851
|
-
reset: true,
|
|
3852
|
-
cursor: snapshot.cursor,
|
|
3853
|
-
hasMore: snapshot.hasMore,
|
|
3854
|
-
source: "device-history",
|
|
3855
|
-
canonical: true,
|
|
3856
|
-
},
|
|
3857
|
-
}));
|
|
3858
|
-
return;
|
|
3859
|
-
}
|
|
3860
|
-
|
|
3861
|
-
const changed = events
|
|
3862
|
-
.filter((event) => event.revision > sinceRevision)
|
|
3863
|
-
.slice(-limit);
|
|
3864
|
-
const itemsById = new Map<string, AgentTimelineItem>();
|
|
3865
|
-
for (const event of changed) {
|
|
3866
|
-
if (event.item) itemsById.set(event.item.id, event.item);
|
|
3867
|
-
}
|
|
3868
|
-
this.input.send(createEnvelope({
|
|
3869
|
-
type: "agent.v2.delta",
|
|
3870
|
-
hostDeviceId: this.input.hostDeviceId,
|
|
3871
|
-
payload: {
|
|
3872
|
-
conversationId: conversation.id,
|
|
3873
|
-
conversation: this.conversationSnapshot(conversation),
|
|
3874
|
-
items: [...itemsById.values()].map((item) => snapshotTimelineItem(item)),
|
|
3875
|
-
sinceRevision,
|
|
3876
|
-
revision: newestRevision,
|
|
3877
|
-
reset: false,
|
|
3878
|
-
hasMore: changed.length === limit && events.some((event) => event.revision > sinceRevision && event.revision < changed[0]!.revision),
|
|
3879
|
-
source: "device-live",
|
|
3880
|
-
canonical: true,
|
|
3881
|
-
},
|
|
3882
|
-
}));
|
|
3883
|
-
}
|
|
3884
|
-
|
|
3885
|
-
private hydrateStoredTimeline(conversation: AgentConversation): void {
|
|
3886
|
-
if (!conversation.agentSessionId) return;
|
|
3887
|
-
const existing = this.timelines.get(conversation.id) ?? [];
|
|
3888
|
-
if (existing.length > 0) {
|
|
3889
|
-
this.setRevisionFloor(conversation.id, Math.max(
|
|
3890
|
-
existing.length,
|
|
3891
|
-
...existing.map((item) => item.revision ?? 0),
|
|
3892
|
-
));
|
|
3893
|
-
return;
|
|
3894
|
-
}
|
|
3895
|
-
const stored = this.storedTimeline(conversation, MAX_TIMELINE_ITEMS);
|
|
3896
|
-
conversation.historyComplete = stored.length <= MAX_SNAPSHOT_ITEMS;
|
|
3897
|
-
if (stored.length === 0) {
|
|
3898
|
-
this.setRevisionFloor(conversation.id, this.getRevision(conversation.id));
|
|
3899
|
-
return;
|
|
3900
|
-
}
|
|
3901
|
-
const items = stored
|
|
3902
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
3903
|
-
.slice(-MAX_TIMELINE_ITEMS);
|
|
3904
|
-
this.timelines.set(conversation.id, items);
|
|
3905
|
-
for (const item of items) this.rememberItemConversationId(conversation.id, item);
|
|
3906
|
-
this.setRevisionFloor(conversation.id, Math.max(items.length, ...items.map((item) => item.revision ?? 0)));
|
|
3907
|
-
const lastMessage = [...items].reverse().find((item) => item.text?.trim());
|
|
3908
|
-
if (lastMessage?.text && !conversation.lastMessagePreview) {
|
|
3909
|
-
conversation.lastMessagePreview = previewText(lastMessage.text);
|
|
3910
|
-
}
|
|
3911
|
-
const lastActivityAt = items.at(-1)?.createdAt;
|
|
3912
|
-
if (lastActivityAt) {
|
|
3913
|
-
conversation.lastActivityAt = Math.max(conversation.lastActivityAt, lastActivityAt);
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
|
|
3917
3095
|
private conversationIdFromParams(params: unknown): string | undefined {
|
|
3918
3096
|
const raw = asRecord(params);
|
|
3919
3097
|
if (!raw) return undefined;
|
|
@@ -3971,8 +3149,6 @@ export class AgentWorkspaceProxy {
|
|
|
3971
3149
|
private rememberTurnConversationId(conversationId: string, turnId: string): void {
|
|
3972
3150
|
this.currentTurnIds.set(conversationId, turnId);
|
|
3973
3151
|
this.turnConversationIds.set(turnId, conversationId);
|
|
3974
|
-
const conversation = this.conversations.get(conversationId);
|
|
3975
|
-
if (conversation) conversation.runningTurnId = turnId;
|
|
3976
3152
|
}
|
|
3977
3153
|
|
|
3978
3154
|
private forgetCurrentTurn(conversationId: string, turnId?: string): void {
|
|
@@ -3980,8 +3156,6 @@ export class AgentWorkspaceProxy {
|
|
|
3980
3156
|
this.currentTurnIds.delete(conversationId);
|
|
3981
3157
|
if (turnId) this.turnConversationIds.delete(turnId);
|
|
3982
3158
|
if (currentTurnId && currentTurnId !== turnId) this.turnConversationIds.delete(currentTurnId);
|
|
3983
|
-
const conversation = this.conversations.get(conversationId);
|
|
3984
|
-
if (conversation) conversation.runningTurnId = undefined;
|
|
3985
3159
|
}
|
|
3986
3160
|
|
|
3987
3161
|
private rememberItemConversationId(conversationId: string, item: AgentTimelineItem): void {
|
|
@@ -4023,54 +3197,32 @@ export class AgentWorkspaceProxy {
|
|
|
4023
3197
|
|
|
4024
3198
|
private cancelPendingPermissions(conversationId?: string): void {
|
|
4025
3199
|
for (const [requestId, waiter] of this.permissionWaiters) {
|
|
4026
|
-
const ownerConversationId = this.conversationIdForPermissionRequest(requestId);
|
|
4027
|
-
if (conversationId && ownerConversationId && ownerConversationId !== conversationId) continue;
|
|
4028
3200
|
clearTimeout(waiter.timer);
|
|
4029
3201
|
waiter.resolve(formatPermissionResponse(
|
|
4030
3202
|
this.permissionSources.get(requestId),
|
|
4031
3203
|
"cancelled",
|
|
4032
3204
|
"cancelled",
|
|
4033
3205
|
));
|
|
4034
|
-
if (ownerConversationId) {
|
|
4035
|
-
this.markPermission(ownerConversationId, requestId, {
|
|
4036
|
-
permissionOutcome: "cancelled",
|
|
4037
|
-
optionId: "cancelled",
|
|
4038
|
-
permissionLive: false,
|
|
4039
|
-
permissionPending: false,
|
|
4040
|
-
permissionExpired: false,
|
|
4041
|
-
permissionError: "已停止",
|
|
4042
|
-
});
|
|
4043
|
-
}
|
|
4044
|
-
this.permissionWaiters.delete(requestId);
|
|
4045
3206
|
this.pendingPermissions.delete(requestId);
|
|
4046
3207
|
this.permissionSources.delete(requestId);
|
|
4047
3208
|
}
|
|
3209
|
+
this.permissionWaiters.clear();
|
|
4048
3210
|
for (const [requestId, waiter] of this.structuredInputWaiters) {
|
|
4049
|
-
const pending = this.pendingStructuredInputs.get(requestId);
|
|
4050
|
-
if (conversationId && pending?.conversationId && pending.conversationId !== conversationId) continue;
|
|
4051
3211
|
clearTimeout(waiter.timer);
|
|
4052
3212
|
waiter.resolve(formatStructuredInputResponse({}));
|
|
3213
|
+
const pending = this.pendingStructuredInputs.get(requestId);
|
|
4053
3214
|
if (pending) {
|
|
4054
3215
|
this.markStructuredInput(pending.conversationId, requestId, {
|
|
4055
3216
|
inputPending: false,
|
|
4056
3217
|
inputError: "已停止",
|
|
4057
3218
|
});
|
|
4058
3219
|
}
|
|
4059
|
-
this.structuredInputWaiters.delete(requestId);
|
|
4060
3220
|
this.pendingStructuredInputs.delete(requestId);
|
|
4061
3221
|
}
|
|
3222
|
+
this.structuredInputWaiters.clear();
|
|
4062
3223
|
if (conversationId) this.updateConversationStatus(conversationId, "idle");
|
|
4063
3224
|
}
|
|
4064
3225
|
|
|
4065
|
-
private conversationIdForPermissionRequest(requestId: string): string | undefined {
|
|
4066
|
-
for (const [conversationId, timeline] of this.timelines) {
|
|
4067
|
-
if (timeline.some((item) => item.type === "permission" && item.permission?.requestId === requestId)) {
|
|
4068
|
-
return conversationId;
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
return undefined;
|
|
4072
|
-
}
|
|
4073
|
-
|
|
4074
3226
|
private extractSessionId(value: unknown): string | undefined {
|
|
4075
3227
|
const raw = asRecord(value);
|
|
4076
3228
|
if (!raw) return undefined;
|