linkshell-cli 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/src/index.js +1 -1
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +16 -0
- package/dist/cli/src/runtime/acp/acp-client.js +40 -2
- package/dist/cli/src/runtime/acp/acp-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace-git.d.ts +63 -0
- package/dist/cli/src/runtime/acp/agent-workspace-git.js +297 -0
- package/dist/cli/src/runtime/acp/agent-workspace-git.js.map +1 -0
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +6 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +690 -81
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +17 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +331 -19
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +69 -14
- 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 -0
- package/dist/cli/src/runtime/acp/json-rpc.js +6 -0
- package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
- package/dist/cli/src/runtime/acp-relay.d.ts +23 -0
- package/dist/cli/src/runtime/acp-relay.js +73 -0
- package/dist/cli/src/runtime/acp-relay.js.map +1 -0
- package/dist/cli/src/runtime/bridge-session.d.ts +5 -0
- package/dist/cli/src/runtime/bridge-session.js +104 -57
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +2505 -2479
- package/dist/shared-protocol/src/index.js +46 -0
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/runtime/acp/acp-client.ts +53 -2
- package/src/runtime/acp/agent-workspace.ts +752 -76
- package/src/runtime/acp/claude-sdk-client.ts +352 -31
- package/src/runtime/acp/claude-stream-json-client.ts +66 -16
- package/src/runtime/acp/json-rpc.ts +5 -0
- package/src/runtime/bridge-session.ts +114 -61
- package/dist/cli/src/runtime/acp/claude-sessions.d.ts +0 -57
- package/dist/cli/src/runtime/acp/claude-sessions.js +0 -506
- package/dist/cli/src/runtime/acp/claude-sessions.js.map +0 -1
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.d.ts +0 -34
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js +0 -204
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js.map +0 -1
- package/dist/cli/src/runtime/acp/codex-sessions.d.ts +0 -60
- package/dist/cli/src/runtime/acp/codex-sessions.js +0 -731
- package/dist/cli/src/runtime/acp/codex-sessions.js.map +0 -1
|
@@ -201,6 +201,7 @@ interface PendingStructuredInputWaiter {
|
|
|
201
201
|
resolve: (value: unknown) => void;
|
|
202
202
|
timer: ReturnType<typeof setTimeout>;
|
|
203
203
|
source?: string;
|
|
204
|
+
input: AgentStructuredInput;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
const PERMISSION_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -210,6 +211,17 @@ function id(prefix: string): string {
|
|
|
210
211
|
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
function normalizeAgentSessionIdSegment(value: string): string {
|
|
215
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function makeAgentV2RemoteConversationId(
|
|
219
|
+
provider: AgentProvider,
|
|
220
|
+
agentSessionId: string,
|
|
221
|
+
): string {
|
|
222
|
+
return `agent-remote-${provider}-${normalizeAgentSessionIdSegment(agentSessionId)}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
213
225
|
function stringify(value: unknown): string {
|
|
214
226
|
if (typeof value === "string") return value;
|
|
215
227
|
try {
|
|
@@ -264,6 +276,12 @@ function extractItem(value: unknown): Record<string, unknown> | undefined {
|
|
|
264
276
|
return asRecord(raw.item) ?? raw;
|
|
265
277
|
}
|
|
266
278
|
|
|
279
|
+
function extractTurn(value: unknown): Record<string, unknown> | undefined {
|
|
280
|
+
const raw = asRecord(value);
|
|
281
|
+
if (!raw) return undefined;
|
|
282
|
+
return asRecord(raw.turn) ?? raw;
|
|
283
|
+
}
|
|
284
|
+
|
|
267
285
|
function stringifyDefined(value: unknown): string | undefined {
|
|
268
286
|
if (value === undefined || value === null || value === "") return undefined;
|
|
269
287
|
return stringify(value);
|
|
@@ -302,6 +320,29 @@ function normalizePlanStatus(value: unknown): AgentPlanStep["status"] {
|
|
|
302
320
|
return "pending";
|
|
303
321
|
}
|
|
304
322
|
|
|
323
|
+
function agentStatusFromThreadStatus(value: unknown): AgentStatus | undefined {
|
|
324
|
+
const raw = asRecord(value);
|
|
325
|
+
const status = typeof value === "string"
|
|
326
|
+
? value
|
|
327
|
+
: firstString(raw, ["status", "state", "phase", "type"]) ??
|
|
328
|
+
firstString(asRecord(raw?.status), ["status", "state", "phase", "type"]);
|
|
329
|
+
const normalized = normalizedIdentifier(status);
|
|
330
|
+
if (!normalized) return undefined;
|
|
331
|
+
if (["active", "running", "inprogress", "busy", "working", "streaming"].includes(normalized)) {
|
|
332
|
+
return "running";
|
|
333
|
+
}
|
|
334
|
+
if (["idle", "ready", "completed", "complete", "done", "finished"].includes(normalized)) {
|
|
335
|
+
return "idle";
|
|
336
|
+
}
|
|
337
|
+
if (["waitingpermission", "waitingforpermission", "requirespermission", "needspermission"].includes(normalized)) {
|
|
338
|
+
return "waiting_permission";
|
|
339
|
+
}
|
|
340
|
+
if (["error", "failed", "systemerror", "fatal"].includes(normalized)) {
|
|
341
|
+
return "error";
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
305
346
|
function nameFromToolMethod(method: string): string {
|
|
306
347
|
if (method.includes("commandExecution")) return "命令";
|
|
307
348
|
if (method.includes("fileChange")) return "文件修改";
|
|
@@ -670,7 +711,9 @@ function parseStructuredInputQuestion(value: unknown, index: number): AgentStruc
|
|
|
670
711
|
question,
|
|
671
712
|
isOther: raw.isOther === true,
|
|
672
713
|
isSecret: raw.isSecret === true || raw.secret === true,
|
|
673
|
-
selectionLimit:
|
|
714
|
+
selectionLimit: raw.multiSelect === true
|
|
715
|
+
? options.length || undefined
|
|
716
|
+
: firstNumber(raw, ["selectionLimit", "maxSelections"]),
|
|
674
717
|
options: options.length > 0 ? options : undefined,
|
|
675
718
|
};
|
|
676
719
|
}
|
|
@@ -1237,6 +1280,7 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1237
1280
|
const raw = asRecord(value);
|
|
1238
1281
|
const sessionsValue =
|
|
1239
1282
|
Array.isArray(value) ? value :
|
|
1283
|
+
Array.isArray(raw?.data) ? raw.data :
|
|
1240
1284
|
Array.isArray(raw?.threads) ? raw.threads :
|
|
1241
1285
|
Array.isArray(raw?.sessions) ? raw.sessions :
|
|
1242
1286
|
Array.isArray(raw?.items) ? raw.items :
|
|
@@ -1262,7 +1306,7 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1262
1306
|
result.push({
|
|
1263
1307
|
id,
|
|
1264
1308
|
cwd: firstString(source, ["cwd", "workingDirectory", "workspacePath"]),
|
|
1265
|
-
title: firstString(source, ["title", "name", "summary"]),
|
|
1309
|
+
title: firstString(source, ["title", "name", "summary", "preview"]),
|
|
1266
1310
|
model: firstString(source, ["model", "modelId"]),
|
|
1267
1311
|
createdAt: parseTimestamp(source.createdAt ?? source.created_at),
|
|
1268
1312
|
lastActivityAt: parseTimestamp(source.lastActivityAt ?? source.updatedAt ?? source.modifiedAt ?? source.lastModified ?? source.updated_at),
|
|
@@ -1271,10 +1315,279 @@ function parseRemoteSessions(value: unknown): Array<{
|
|
|
1271
1315
|
return result;
|
|
1272
1316
|
}
|
|
1273
1317
|
|
|
1318
|
+
function threadFromProviderResult(value: unknown): Record<string, unknown> | undefined {
|
|
1319
|
+
const raw = asRecord(value);
|
|
1320
|
+
return asRecord(raw?.thread) ?? raw;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function threadFromTurnsListResult(sessionId: string, value: unknown): Record<string, unknown> | undefined {
|
|
1324
|
+
const raw = asRecord(value);
|
|
1325
|
+
const order = firstString(raw, ["sortDirection", "order", "sortOrder", "sort"]);
|
|
1326
|
+
const turns =
|
|
1327
|
+
Array.isArray(raw?.turns) ? raw.turns :
|
|
1328
|
+
Array.isArray(raw?.data) ? raw.data :
|
|
1329
|
+
Array.isArray(raw?.items) ? raw.items :
|
|
1330
|
+
Array.isArray(value) ? value :
|
|
1331
|
+
undefined;
|
|
1332
|
+
if (!turns) return undefined;
|
|
1333
|
+
const extracted = turns
|
|
1334
|
+
.map(extractTurn)
|
|
1335
|
+
.filter((turn): turn is Record<string, unknown> => Boolean(turn));
|
|
1336
|
+
const chronological = order && /asc/i.test(order)
|
|
1337
|
+
? extracted
|
|
1338
|
+
: [...extracted].reverse();
|
|
1339
|
+
return { id: sessionId, turns: chronological };
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function turnItemsFromThread(thread: Record<string, unknown> | undefined): Record<string, unknown>[] {
|
|
1343
|
+
if (!thread) return [];
|
|
1344
|
+
const turnsValue = Array.isArray(thread.turns)
|
|
1345
|
+
? thread.turns
|
|
1346
|
+
: Array.isArray(thread.items)
|
|
1347
|
+
? [{ id: firstString(thread, ["turnId", "id"]), items: thread.items }]
|
|
1348
|
+
: [];
|
|
1349
|
+
const result: Record<string, unknown>[] = [];
|
|
1350
|
+
for (const turnEntry of turnsValue) {
|
|
1351
|
+
const turn = extractTurn(turnEntry);
|
|
1352
|
+
if (!turn) continue;
|
|
1353
|
+
const turnId = firstString(turn, ["id", "turnId"]);
|
|
1354
|
+
const createdAt = parseTimestamp(
|
|
1355
|
+
turn.createdAt ??
|
|
1356
|
+
turn.created_at ??
|
|
1357
|
+
turn.startedAt ??
|
|
1358
|
+
turn.started_at ??
|
|
1359
|
+
turn.completedAt ??
|
|
1360
|
+
turn.completed_at ??
|
|
1361
|
+
turn.updatedAt ??
|
|
1362
|
+
turn.updated_at,
|
|
1363
|
+
);
|
|
1364
|
+
const itemsRecord = asRecord(turn.items);
|
|
1365
|
+
const items = Array.isArray(turn.items)
|
|
1366
|
+
? turn.items
|
|
1367
|
+
: Array.isArray(itemsRecord?.data)
|
|
1368
|
+
? itemsRecord.data
|
|
1369
|
+
: Array.isArray(itemsRecord?.items)
|
|
1370
|
+
? itemsRecord.items
|
|
1371
|
+
: Array.isArray(turn.item)
|
|
1372
|
+
? turn.item
|
|
1373
|
+
: Array.isArray(turn.events)
|
|
1374
|
+
? turn.events
|
|
1375
|
+
: [];
|
|
1376
|
+
for (const itemEntry of items) {
|
|
1377
|
+
const item = extractItem(itemEntry);
|
|
1378
|
+
if (!item) continue;
|
|
1379
|
+
result.push({
|
|
1380
|
+
...item,
|
|
1381
|
+
...(turnId && !firstString(item, ["turnId"]) ? { turnId } : {}),
|
|
1382
|
+
__turnCreatedAt: createdAt,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return result;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function activeTurnIdFromThread(thread: Record<string, unknown> | undefined): string | undefined {
|
|
1390
|
+
if (!thread) return undefined;
|
|
1391
|
+
const turnsValue = Array.isArray(thread.turns)
|
|
1392
|
+
? thread.turns
|
|
1393
|
+
: Array.isArray(thread.items)
|
|
1394
|
+
? [{ id: firstString(thread, ["turnId", "id"]), items: thread.items, status: thread.status }]
|
|
1395
|
+
: [];
|
|
1396
|
+
let activeTurnId: string | undefined;
|
|
1397
|
+
for (const turnEntry of turnsValue) {
|
|
1398
|
+
const turn = extractTurn(turnEntry);
|
|
1399
|
+
if (!turn) continue;
|
|
1400
|
+
const turnId = firstString(turn, ["id", "turnId"]);
|
|
1401
|
+
if (!turnId) continue;
|
|
1402
|
+
const status =
|
|
1403
|
+
firstString(turn, ["status", "state", "phase"]) ??
|
|
1404
|
+
firstString(asRecord(turn.status), ["status", "state", "phase", "type"]);
|
|
1405
|
+
const normalized = normalizedIdentifier(status);
|
|
1406
|
+
if ([
|
|
1407
|
+
"active",
|
|
1408
|
+
"running",
|
|
1409
|
+
"inprogress",
|
|
1410
|
+
"busy",
|
|
1411
|
+
"working",
|
|
1412
|
+
"streaming",
|
|
1413
|
+
"waitingpermission",
|
|
1414
|
+
"waitingforpermission",
|
|
1415
|
+
"requirespermission",
|
|
1416
|
+
"needspermission",
|
|
1417
|
+
].includes(normalized)) {
|
|
1418
|
+
activeTurnId = turnId;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return activeTurnId;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function timelineItemFromProviderItem(
|
|
1425
|
+
item: Record<string, unknown>,
|
|
1426
|
+
conversationId: string,
|
|
1427
|
+
index: number,
|
|
1428
|
+
): AgentTimelineItem | undefined {
|
|
1429
|
+
const itemType = firstString(item, ["type"]);
|
|
1430
|
+
const normalized = normalizedIdentifier(itemType);
|
|
1431
|
+
const itemId = firstString(item, ["id", "itemId", "messageId", "toolCallId"]) ?? `history-${index + 1}`;
|
|
1432
|
+
const createdAt =
|
|
1433
|
+
parseTimestamp(item.createdAt ?? item.created_at ?? item.timestamp ?? item.__turnCreatedAt) ??
|
|
1434
|
+
Date.now() + index;
|
|
1435
|
+
const base = {
|
|
1436
|
+
id: itemId,
|
|
1437
|
+
conversationId,
|
|
1438
|
+
turnId: firstString(item, ["turnId"]),
|
|
1439
|
+
itemId,
|
|
1440
|
+
createdAt,
|
|
1441
|
+
updatedAt: parseTimestamp(item.updatedAt ?? item.updated_at) ?? createdAt,
|
|
1442
|
+
isStreaming: false,
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
if (
|
|
1446
|
+
normalized === "usermessage" ||
|
|
1447
|
+
normalized === "userinput" ||
|
|
1448
|
+
(normalized === "message" && item.role === "user")
|
|
1449
|
+
) {
|
|
1450
|
+
const content = contentBlocksFromItem(item);
|
|
1451
|
+
const text = textFromBlocks(content);
|
|
1452
|
+
if (!content.length && !text) return undefined;
|
|
1453
|
+
return {
|
|
1454
|
+
...base,
|
|
1455
|
+
type: "message",
|
|
1456
|
+
kind: "chat",
|
|
1457
|
+
role: "user",
|
|
1458
|
+
content,
|
|
1459
|
+
text,
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
if (
|
|
1464
|
+
normalized === "agentmessage" ||
|
|
1465
|
+
normalized === "assistantmessage" ||
|
|
1466
|
+
(normalized === "message" && item.role !== "user")
|
|
1467
|
+
) {
|
|
1468
|
+
const content = contentBlocksFromItem(item);
|
|
1469
|
+
const text = textFromBlocks(content);
|
|
1470
|
+
if (!content.length && !text) return undefined;
|
|
1471
|
+
return {
|
|
1472
|
+
...base,
|
|
1473
|
+
type: "message",
|
|
1474
|
+
kind: "chat",
|
|
1475
|
+
role: item.role === "system" ? "system" : "assistant",
|
|
1476
|
+
content,
|
|
1477
|
+
text,
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
if (normalized === "reasoning" || normalized === "thinking") {
|
|
1482
|
+
const text = firstString(item, ["text", "content", "summary", "message"]) ??
|
|
1483
|
+
stringifyDefined(item.contentItems ?? item.summary);
|
|
1484
|
+
if (!text) return undefined;
|
|
1485
|
+
return {
|
|
1486
|
+
...base,
|
|
1487
|
+
type: "status",
|
|
1488
|
+
kind: "thinking",
|
|
1489
|
+
role: "system",
|
|
1490
|
+
text,
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if (normalized === "enteredreviewmode" || normalized === "exitedreviewmode") {
|
|
1495
|
+
const text = firstString(item, ["review", "text", "content", "summary", "message"]);
|
|
1496
|
+
return {
|
|
1497
|
+
...base,
|
|
1498
|
+
type: "status",
|
|
1499
|
+
kind: "review",
|
|
1500
|
+
role: "system",
|
|
1501
|
+
text: text ?? (normalized === "enteredreviewmode" ? "正在审查" : "审查已完成"),
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (normalized === "contextcompaction") {
|
|
1506
|
+
return {
|
|
1507
|
+
...base,
|
|
1508
|
+
type: "status",
|
|
1509
|
+
kind: "context_compaction",
|
|
1510
|
+
role: "system",
|
|
1511
|
+
text: firstString(item, ["text", "summary", "message"]) ?? "上下文已压缩",
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (isSubagentItemType(itemType)) {
|
|
1516
|
+
const subagent = decodeSubagentAction(item, normalizeToolStatus(item.status, true));
|
|
1517
|
+
if (!subagent) return undefined;
|
|
1518
|
+
return {
|
|
1519
|
+
...base,
|
|
1520
|
+
type: "status",
|
|
1521
|
+
kind: "subagent_action",
|
|
1522
|
+
role: "system",
|
|
1523
|
+
text: summarizeSubagentAction(subagent),
|
|
1524
|
+
subagent,
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
if (isToolItemType(itemType) || toolNameFromItem(item)) {
|
|
1529
|
+
const toolId = firstString(item, ["id", "itemId", "toolCallId"]) ?? itemId;
|
|
1530
|
+
const toolCall: AgentToolCall = {
|
|
1531
|
+
id: toolId,
|
|
1532
|
+
name: toolNameFromItem(item) ?? "工具",
|
|
1533
|
+
input: toolInputFromItem(item),
|
|
1534
|
+
output:
|
|
1535
|
+
normalized === "filechange" || normalized === "diff"
|
|
1536
|
+
? extractDiffText(item) ?? firstString(item, ["output", "summary"])
|
|
1537
|
+
: firstString(item, ["aggregatedOutput", "output", "stdout", "stderr"]) ??
|
|
1538
|
+
stringifyDefined(item.result ?? item.error ?? item.contentItems),
|
|
1539
|
+
createdAt,
|
|
1540
|
+
status: normalizeToolStatus(item.status, true),
|
|
1541
|
+
};
|
|
1542
|
+
const kind: AgentTimelineKind = toolCall.name.includes("文件") || normalized === "filechange" || normalized === "diff"
|
|
1543
|
+
? "file_change"
|
|
1544
|
+
: toolCall.name.includes("命令") || normalized === "commandexecution"
|
|
1545
|
+
? "command_execution"
|
|
1546
|
+
: "tool_activity";
|
|
1547
|
+
return {
|
|
1548
|
+
...base,
|
|
1549
|
+
id: `tool:${toolId}`,
|
|
1550
|
+
type: "tool_call",
|
|
1551
|
+
kind,
|
|
1552
|
+
itemId: toolId,
|
|
1553
|
+
toolCall,
|
|
1554
|
+
commandExecution: kind === "command_execution" ? commandExecutionFromTool(toolCall) : undefined,
|
|
1555
|
+
fileChange: kind === "file_change" ? fileChangeFromTool(toolCall) : undefined,
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
return undefined;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function timelineItemsFromProviderThread(
|
|
1563
|
+
value: unknown,
|
|
1564
|
+
conversationId: string,
|
|
1565
|
+
): AgentTimelineItem[] {
|
|
1566
|
+
const items = turnItemsFromThread(threadFromProviderResult(value))
|
|
1567
|
+
.map((item, index) => timelineItemFromProviderItem(item, conversationId, index))
|
|
1568
|
+
.filter((item): item is AgentTimelineItem => Boolean(item));
|
|
1569
|
+
const byId = new Map<string, AgentTimelineItem>();
|
|
1570
|
+
for (const item of items) {
|
|
1571
|
+
byId.set(item.id, item);
|
|
1572
|
+
}
|
|
1573
|
+
return [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).slice(-MAX_TIMELINE_ITEMS);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function previewFromTimelineItem(item: AgentTimelineItem): string | undefined {
|
|
1577
|
+
if (item.error) return item.error;
|
|
1578
|
+
if (item.text) return item.text;
|
|
1579
|
+
if (item.content?.length) return textFromBlocks(item.content);
|
|
1580
|
+
if (item.toolCall) return `${item.toolCall.name} · ${item.toolCall.status}`;
|
|
1581
|
+
if (item.permission) return `需要授权 ${item.permission.toolName ?? ""}`.trim();
|
|
1582
|
+
if (item.subagent) return summarizeSubagentAction(item.subagent);
|
|
1583
|
+
return undefined;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1274
1586
|
export class AgentWorkspaceProxy {
|
|
1275
1587
|
private clients = new Map<AgentProvider, AcpClient | ClaudeSdkClient | ClaudeStreamJsonClient>();
|
|
1276
1588
|
private agentProtocols = new Map<AgentProvider, AgentProtocol>();
|
|
1277
1589
|
private providerCapabilities = new Map<AgentProvider, ProviderRuntimeCapabilities>();
|
|
1590
|
+
private providerErrors = new Map<AgentProvider, string>();
|
|
1278
1591
|
private initialized = false;
|
|
1279
1592
|
private status: AgentStatus = "unavailable";
|
|
1280
1593
|
private error: string | undefined;
|
|
@@ -1347,11 +1660,21 @@ export class AgentWorkspaceProxy {
|
|
|
1347
1660
|
case "agent.v2.cancel": {
|
|
1348
1661
|
const payload = parseTypedPayload("agent.v2.cancel", envelope.payload);
|
|
1349
1662
|
const conversation = this.conversations.get(payload.conversationId);
|
|
1663
|
+
if (!conversation) break;
|
|
1664
|
+
const turnId = this.currentTurnIds.get(payload.conversationId);
|
|
1665
|
+
if (this.protocolForProvider(conversation.provider) === "codex-app-server" && !turnId) {
|
|
1666
|
+
this.rejectAgentAction(
|
|
1667
|
+
conversation,
|
|
1668
|
+
"无法停止 Codex:当前运行 turn 尚未同步,请重新打开对话后再试。",
|
|
1669
|
+
conversation.status,
|
|
1670
|
+
);
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1350
1673
|
this.cancelPendingPermissions(payload.conversationId);
|
|
1351
|
-
const cancelClient =
|
|
1674
|
+
const cancelClient = this.clientForProvider(conversation.provider);
|
|
1352
1675
|
cancelClient?.cancel({
|
|
1353
|
-
sessionId: conversation
|
|
1354
|
-
turnId
|
|
1676
|
+
sessionId: conversation.agentSessionId,
|
|
1677
|
+
turnId,
|
|
1355
1678
|
});
|
|
1356
1679
|
this.forgetCurrentTurn(payload.conversationId);
|
|
1357
1680
|
this.updateConversationStatus(payload.conversationId, "idle");
|
|
@@ -1386,6 +1709,13 @@ export class AgentWorkspaceProxy {
|
|
|
1386
1709
|
return this.agentProtocols.get(provider);
|
|
1387
1710
|
}
|
|
1388
1711
|
|
|
1712
|
+
private defaultModelForProvider(provider: AgentProvider): string | undefined {
|
|
1713
|
+
const capabilities = this.providerCapabilities.get(provider);
|
|
1714
|
+
const defaultModel = capabilities?.defaultModel?.trim();
|
|
1715
|
+
if (defaultModel) return defaultModel;
|
|
1716
|
+
return capabilities?.models?.find((model) => model.id && model.id !== "default")?.id;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1389
1719
|
private async initialize(): Promise<void> {
|
|
1390
1720
|
if (this.initialized) return;
|
|
1391
1721
|
this.initialized = true;
|
|
@@ -1452,6 +1782,7 @@ export class AgentWorkspaceProxy {
|
|
|
1452
1782
|
try {
|
|
1453
1783
|
const client = await tryCreateClient(resolved);
|
|
1454
1784
|
this.clients.set(provider, client);
|
|
1785
|
+
this.providerErrors.delete(provider);
|
|
1455
1786
|
await this.refreshProviderCapabilities(provider, client, resolved.protocol);
|
|
1456
1787
|
this.status = "idle";
|
|
1457
1788
|
this.error = undefined;
|
|
@@ -1471,6 +1802,7 @@ export class AgentWorkspaceProxy {
|
|
|
1471
1802
|
};
|
|
1472
1803
|
const client = await tryCreateClient(fallback);
|
|
1473
1804
|
this.clients.set(provider, client);
|
|
1805
|
+
this.providerErrors.delete(provider);
|
|
1474
1806
|
await this.refreshProviderCapabilities(provider, client, fallback.protocol);
|
|
1475
1807
|
this.status = "idle";
|
|
1476
1808
|
this.error = undefined;
|
|
@@ -1485,6 +1817,8 @@ export class AgentWorkspaceProxy {
|
|
|
1485
1817
|
if (this.input.verbose) {
|
|
1486
1818
|
process.stderr.write(`[agent:v2] failed to start ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1487
1819
|
}
|
|
1820
|
+
this.providerErrors.set(provider, error instanceof Error ? error.message : String(error));
|
|
1821
|
+
this.sendCapabilities();
|
|
1488
1822
|
return undefined;
|
|
1489
1823
|
}
|
|
1490
1824
|
}
|
|
@@ -1537,7 +1871,7 @@ export class AgentWorkspaceProxy {
|
|
|
1537
1871
|
const agentSessionId = remote.id;
|
|
1538
1872
|
const existingId = this.conversationByAgentSessionId.get(agentSessionId);
|
|
1539
1873
|
const now = Date.now();
|
|
1540
|
-
const conversationId = existingId ??
|
|
1874
|
+
const conversationId = existingId ?? makeAgentV2RemoteConversationId(provider, agentSessionId);
|
|
1541
1875
|
const existing = this.conversations.get(conversationId);
|
|
1542
1876
|
const cwd = remote.cwd ?? existing?.cwd ?? this.input.cwd;
|
|
1543
1877
|
const conversation: AgentConversation = {
|
|
@@ -1568,6 +1902,83 @@ export class AgentWorkspaceProxy {
|
|
|
1568
1902
|
}
|
|
1569
1903
|
}
|
|
1570
1904
|
|
|
1905
|
+
private async hydrateConversationFromProvider(
|
|
1906
|
+
conversation: AgentConversation,
|
|
1907
|
+
seedResult?: unknown,
|
|
1908
|
+
): Promise<void> {
|
|
1909
|
+
const client = this.clientForProvider(conversation.provider);
|
|
1910
|
+
if (!client || !conversation.agentSessionId) return;
|
|
1911
|
+
let source = seedResult;
|
|
1912
|
+
const hasSeedTurns = turnItemsFromThread(threadFromProviderResult(seedResult)).length > 0;
|
|
1913
|
+
if (!hasSeedTurns) {
|
|
1914
|
+
const readSession = (client as { readSession?: (input: { sessionId: string; includeTurns?: boolean }) => Promise<unknown> }).readSession;
|
|
1915
|
+
if (typeof readSession === "function") {
|
|
1916
|
+
try {
|
|
1917
|
+
source = await readSession.call(client, {
|
|
1918
|
+
sessionId: conversation.agentSessionId,
|
|
1919
|
+
includeTurns: true,
|
|
1920
|
+
});
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
if (this.input.verbose) {
|
|
1923
|
+
process.stderr.write(`[agent:v2] thread/read hydration failed for ${conversation.provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (turnItemsFromThread(threadFromProviderResult(source)).length === 0) {
|
|
1928
|
+
const listTurns = (client as { listTurns?: (input: {
|
|
1929
|
+
sessionId: string;
|
|
1930
|
+
limit?: number;
|
|
1931
|
+
cursor?: string;
|
|
1932
|
+
sortDirection?: "asc" | "desc";
|
|
1933
|
+
itemsView?: "summary" | "full";
|
|
1934
|
+
}) => Promise<unknown> }).listTurns;
|
|
1935
|
+
if (typeof listTurns === "function") {
|
|
1936
|
+
try {
|
|
1937
|
+
const turnsResult = await listTurns.call(client, {
|
|
1938
|
+
sessionId: conversation.agentSessionId,
|
|
1939
|
+
limit: MAX_TIMELINE_ITEMS,
|
|
1940
|
+
sortDirection: "desc",
|
|
1941
|
+
itemsView: "full",
|
|
1942
|
+
});
|
|
1943
|
+
const turnsThread = threadFromTurnsListResult(conversation.agentSessionId, turnsResult);
|
|
1944
|
+
if (turnsThread) source = { thread: turnsThread };
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
if (this.input.verbose) {
|
|
1947
|
+
process.stderr.write(`[agent:v2] thread/turns/list hydration failed for ${conversation.provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const thread = threadFromProviderResult(source);
|
|
1955
|
+
const activeTurnId = activeTurnIdFromThread(thread);
|
|
1956
|
+
if (activeTurnId) this.rememberTurnConversationId(conversation.id, activeTurnId);
|
|
1957
|
+
const model = firstString(thread, ["model", "modelId", "currentModel"]);
|
|
1958
|
+
if (model && !conversation.model) conversation.model = model;
|
|
1959
|
+
|
|
1960
|
+
const hydratedItems = timelineItemsFromProviderThread(source, conversation.id);
|
|
1961
|
+
if (hydratedItems.length === 0) return;
|
|
1962
|
+
const existing = this.timelines.get(conversation.id) ?? [];
|
|
1963
|
+
const merged = new Map<string, AgentTimelineItem>();
|
|
1964
|
+
for (const item of [...existing, ...hydratedItems]) {
|
|
1965
|
+
merged.set(item.id, item);
|
|
1966
|
+
}
|
|
1967
|
+
const nextItems = [...merged.values()]
|
|
1968
|
+
.sort((a, b) => a.createdAt - b.createdAt)
|
|
1969
|
+
.slice(-MAX_TIMELINE_ITEMS);
|
|
1970
|
+
this.timelines.set(conversation.id, nextItems);
|
|
1971
|
+
for (const item of nextItems) {
|
|
1972
|
+
this.rememberItemConversationId(conversation.id, item);
|
|
1973
|
+
}
|
|
1974
|
+
const lastPreview = [...nextItems].reverse()
|
|
1975
|
+
.map((item) => previewText(previewFromTimelineItem(item) ?? ""))
|
|
1976
|
+
.find(Boolean);
|
|
1977
|
+
if (lastPreview && !conversation.lastMessagePreview) {
|
|
1978
|
+
conversation.lastMessagePreview = lastPreview;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1571
1982
|
private sendCapabilities(): void {
|
|
1572
1983
|
const providers = this.input.availableProviders.map((provider) => {
|
|
1573
1984
|
const client = this.clients.get(provider);
|
|
@@ -1587,7 +1998,7 @@ export class AgentWorkspaceProxy {
|
|
|
1587
1998
|
id: provider,
|
|
1588
1999
|
label: providerLabel(provider),
|
|
1589
2000
|
enabled,
|
|
1590
|
-
reason: enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`,
|
|
2001
|
+
reason: enabled ? undefined : this.providerErrors.get(provider) ?? `${providerLabel(provider)} 未安装或启动失败`,
|
|
1591
2002
|
supportsImages,
|
|
1592
2003
|
supportsPermission,
|
|
1593
2004
|
supportsPlan: enabled,
|
|
@@ -1659,24 +2070,63 @@ export class AgentWorkspaceProxy {
|
|
|
1659
2070
|
);
|
|
1660
2071
|
}
|
|
1661
2072
|
|
|
1662
|
-
|
|
1663
|
-
if (!client) {
|
|
1664
|
-
return this.openFailure(
|
|
1665
|
-
payload,
|
|
1666
|
-
`${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`,
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
const cwd = payload.cwd ?? this.input.cwd;
|
|
2073
|
+
let cwd = payload.cwd ?? this.input.cwd;
|
|
1671
2074
|
let agentSessionId = payload.agentSessionId;
|
|
1672
2075
|
let existingConversation =
|
|
1673
2076
|
(payload.conversationId ? this.conversations.get(payload.conversationId) : undefined) ??
|
|
1674
2077
|
(agentSessionId ? this.conversations.get(this.conversationByAgentSessionId.get(agentSessionId) ?? "") : undefined);
|
|
2078
|
+
if (!payload.cwd && existingConversation?.cwd) cwd = existingConversation.cwd;
|
|
2079
|
+
|
|
2080
|
+
const client = await this.ensureProviderClient(provider);
|
|
2081
|
+
if (!client) {
|
|
2082
|
+
const message = this.providerErrors.get(provider) ?? `${providerLabel(provider)} 启动失败。请确认 CLI 已安装并可用。`;
|
|
2083
|
+
if (existingConversation) {
|
|
2084
|
+
this.activeConversationId = existingConversation.id;
|
|
2085
|
+
this.input.send(createEnvelope({
|
|
2086
|
+
type: "agent.v2.conversation.opened",
|
|
2087
|
+
sessionId: this.input.sessionId,
|
|
2088
|
+
payload: {
|
|
2089
|
+
conversation: existingConversation,
|
|
2090
|
+
snapshot: this.timelines.get(existingConversation.id) ?? [],
|
|
2091
|
+
requestedConversationId: payload.conversationId,
|
|
2092
|
+
providerError: message,
|
|
2093
|
+
},
|
|
2094
|
+
}));
|
|
2095
|
+
this.sendCapabilities();
|
|
2096
|
+
return existingConversation;
|
|
2097
|
+
}
|
|
2098
|
+
if (payload.conversationId) {
|
|
2099
|
+
return this.openOfflineHistory(payload, message, cwd);
|
|
2100
|
+
}
|
|
2101
|
+
return this.openFailure(payload, message, cwd);
|
|
2102
|
+
}
|
|
1675
2103
|
|
|
1676
2104
|
if (existingConversation && existingConversation.status !== "error" && existingConversation.agentSessionId) {
|
|
1677
|
-
|
|
2105
|
+
const requestedCanonicalId = makeAgentV2RemoteConversationId(provider, existingConversation.agentSessionId);
|
|
2106
|
+
if (
|
|
2107
|
+
payload.conversationId &&
|
|
2108
|
+
payload.conversationId === requestedCanonicalId &&
|
|
2109
|
+
existingConversation.id !== payload.conversationId
|
|
2110
|
+
) {
|
|
1678
2111
|
existingConversation = this.adoptConversationId(existingConversation.id, payload.conversationId);
|
|
1679
2112
|
}
|
|
2113
|
+
if ((this.timelines.get(existingConversation.id) ?? []).length === 0 && existingConversation.status !== "running") {
|
|
2114
|
+
try {
|
|
2115
|
+
const existingAgentSessionId = existingConversation.agentSessionId!;
|
|
2116
|
+
const result = await client.loadSession({ sessionId: existingAgentSessionId, cwd });
|
|
2117
|
+
const nextAgentSessionId = this.extractSessionId(result);
|
|
2118
|
+
if (nextAgentSessionId && nextAgentSessionId !== existingAgentSessionId) {
|
|
2119
|
+
this.conversationByAgentSessionId.delete(existingAgentSessionId);
|
|
2120
|
+
existingConversation.agentSessionId = nextAgentSessionId;
|
|
2121
|
+
this.conversationByAgentSessionId.set(nextAgentSessionId, existingConversation.id);
|
|
2122
|
+
}
|
|
2123
|
+
await this.hydrateConversationFromProvider(existingConversation, result);
|
|
2124
|
+
} catch (error) {
|
|
2125
|
+
if (this.input.verbose) {
|
|
2126
|
+
process.stderr.write(`[agent:v2] resume failed for ${provider}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
1680
2130
|
this.activeConversationId = existingConversation.id;
|
|
1681
2131
|
this.input.send(createEnvelope({
|
|
1682
2132
|
type: "agent.v2.conversation.opened",
|
|
@@ -1684,6 +2134,7 @@ export class AgentWorkspaceProxy {
|
|
|
1684
2134
|
payload: {
|
|
1685
2135
|
conversation: existingConversation,
|
|
1686
2136
|
snapshot: this.timelines.get(existingConversation.id) ?? [],
|
|
2137
|
+
requestedConversationId: payload.conversationId,
|
|
1687
2138
|
},
|
|
1688
2139
|
}));
|
|
1689
2140
|
return existingConversation;
|
|
@@ -1695,7 +2146,7 @@ export class AgentWorkspaceProxy {
|
|
|
1695
2146
|
: await client.newSession({ cwd });
|
|
1696
2147
|
agentSessionId = this.extractSessionId(result) ?? agentSessionId ?? id("agent-session");
|
|
1697
2148
|
const now = Date.now();
|
|
1698
|
-
const conversationId =
|
|
2149
|
+
const conversationId = makeAgentV2RemoteConversationId(provider, agentSessionId);
|
|
1699
2150
|
const conversation: AgentConversation = {
|
|
1700
2151
|
...existingConversation,
|
|
1701
2152
|
id: conversationId,
|
|
@@ -1717,10 +2168,15 @@ export class AgentWorkspaceProxy {
|
|
|
1717
2168
|
this.conversationByAgentSessionId.set(agentSessionId, conversation.id);
|
|
1718
2169
|
this.activeConversationId = conversation.id;
|
|
1719
2170
|
this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
|
|
2171
|
+
await this.hydrateConversationFromProvider(conversation, result);
|
|
1720
2172
|
this.input.send(createEnvelope({
|
|
1721
2173
|
type: "agent.v2.conversation.opened",
|
|
1722
2174
|
sessionId: this.input.sessionId,
|
|
1723
|
-
payload: {
|
|
2175
|
+
payload: {
|
|
2176
|
+
conversation,
|
|
2177
|
+
snapshot: this.timelines.get(conversation.id) ?? [],
|
|
2178
|
+
requestedConversationId: payload.conversationId,
|
|
2179
|
+
},
|
|
1724
2180
|
}));
|
|
1725
2181
|
return conversation;
|
|
1726
2182
|
} catch (error) {
|
|
@@ -1772,8 +2228,62 @@ export class AgentWorkspaceProxy {
|
|
|
1772
2228
|
this.input.send(createEnvelope({
|
|
1773
2229
|
type: "agent.v2.conversation.opened",
|
|
1774
2230
|
sessionId: this.input.sessionId,
|
|
1775
|
-
payload: {
|
|
2231
|
+
payload: {
|
|
2232
|
+
conversation,
|
|
2233
|
+
snapshot: this.timelines.get(conversation.id) ?? [],
|
|
2234
|
+
requestedConversationId: payload.conversationId,
|
|
2235
|
+
},
|
|
2236
|
+
}));
|
|
2237
|
+
return conversation;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
private openOfflineHistory(
|
|
2241
|
+
payload: {
|
|
2242
|
+
conversationId?: string;
|
|
2243
|
+
agentSessionId?: string;
|
|
2244
|
+
cwd?: string;
|
|
2245
|
+
provider?: AgentProvider;
|
|
2246
|
+
model?: string;
|
|
2247
|
+
reasoningEffort?: string;
|
|
2248
|
+
permissionMode?: AgentPermissionMode;
|
|
2249
|
+
collaborationMode?: AgentCollaborationMode;
|
|
2250
|
+
title?: string;
|
|
2251
|
+
},
|
|
2252
|
+
message: string,
|
|
2253
|
+
cwd = payload.cwd ?? this.input.cwd,
|
|
2254
|
+
): AgentConversation {
|
|
2255
|
+
const now = Date.now();
|
|
2256
|
+
const conversation: AgentConversation = {
|
|
2257
|
+
id: payload.conversationId!,
|
|
2258
|
+
agentSessionId: payload.agentSessionId,
|
|
2259
|
+
provider: payload.provider ?? this.input.availableProviders[0] ?? "codex",
|
|
2260
|
+
cwd,
|
|
2261
|
+
title: payload.title ?? titleFromCwd(cwd),
|
|
2262
|
+
model: payload.model,
|
|
2263
|
+
reasoningEffort: payload.reasoningEffort,
|
|
2264
|
+
permissionMode: payload.permissionMode,
|
|
2265
|
+
collaborationMode: payload.collaborationMode,
|
|
2266
|
+
status: "idle",
|
|
2267
|
+
archived: false,
|
|
2268
|
+
lastActivityAt: now,
|
|
2269
|
+
createdAt: now,
|
|
2270
|
+
};
|
|
2271
|
+
this.conversations.set(conversation.id, conversation);
|
|
2272
|
+
if (conversation.agentSessionId) {
|
|
2273
|
+
this.conversationByAgentSessionId.set(conversation.agentSessionId, conversation.id);
|
|
2274
|
+
}
|
|
2275
|
+
this.activeConversationId = conversation.id;
|
|
2276
|
+
this.input.send(createEnvelope({
|
|
2277
|
+
type: "agent.v2.conversation.opened",
|
|
2278
|
+
sessionId: this.input.sessionId,
|
|
2279
|
+
payload: {
|
|
2280
|
+
conversation,
|
|
2281
|
+
snapshot: this.timelines.get(conversation.id) ?? [],
|
|
2282
|
+
requestedConversationId: payload.conversationId,
|
|
2283
|
+
providerError: message,
|
|
2284
|
+
},
|
|
1776
2285
|
}));
|
|
2286
|
+
this.sendCapabilities();
|
|
1777
2287
|
return conversation;
|
|
1778
2288
|
}
|
|
1779
2289
|
|
|
@@ -1781,6 +2291,8 @@ export class AgentWorkspaceProxy {
|
|
|
1781
2291
|
conversationId: string;
|
|
1782
2292
|
clientMessageId: string;
|
|
1783
2293
|
contentBlocks: AgentContentBlock[];
|
|
2294
|
+
delivery?: "auto" | "new_turn" | "steer";
|
|
2295
|
+
targetTurnId?: string;
|
|
1784
2296
|
model?: string;
|
|
1785
2297
|
reasoningEffort?: string;
|
|
1786
2298
|
permissionMode?: AgentPermissionMode;
|
|
@@ -1791,39 +2303,28 @@ export class AgentWorkspaceProxy {
|
|
|
1791
2303
|
await this.openConversation({ conversationId: payload.conversationId });
|
|
1792
2304
|
if (!conversation) return;
|
|
1793
2305
|
if (!conversation.agentSessionId) {
|
|
1794
|
-
this.
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
error: "Agent session 尚未就绪,消息没有发送。请重新打开对话后再试。",
|
|
1799
|
-
createdAt: Date.now(),
|
|
1800
|
-
});
|
|
2306
|
+
this.rejectAgentAction(
|
|
2307
|
+
conversation,
|
|
2308
|
+
"Agent session 尚未就绪,消息没有发送。请重新打开对话后再试。",
|
|
2309
|
+
);
|
|
1801
2310
|
return;
|
|
1802
2311
|
}
|
|
1803
|
-
const client = this.clientForProvider(conversation.provider);
|
|
2312
|
+
const client = this.clientForProvider(conversation.provider) ?? await this.ensureProviderClient(conversation.provider);
|
|
1804
2313
|
if (!client) {
|
|
1805
|
-
this.
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
createdAt: Date.now(),
|
|
1811
|
-
});
|
|
2314
|
+
this.rejectAgentAction(
|
|
2315
|
+
conversation,
|
|
2316
|
+
this.providerErrors.get(conversation.provider) ?? `${providerLabel(conversation.provider)} 未连接,消息没有发送。`,
|
|
2317
|
+
"error",
|
|
2318
|
+
);
|
|
1812
2319
|
return;
|
|
1813
2320
|
}
|
|
1814
2321
|
|
|
1815
2322
|
const protocol = this.protocolForProvider(conversation.provider);
|
|
1816
2323
|
if (payload.contentBlocks.some((block) => block.type === "image") && !protocolSupportsImages(protocol)) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
id: id("error"),
|
|
1822
|
-
conversationId: conversation.id,
|
|
1823
|
-
type: "error",
|
|
1824
|
-
error: "当前 Agent provider 暂不支持图片输入,请升级 CLI 或切换到 Codex。",
|
|
1825
|
-
createdAt: Date.now(),
|
|
1826
|
-
});
|
|
2324
|
+
this.rejectAgentAction(
|
|
2325
|
+
conversation,
|
|
2326
|
+
"当前 Agent provider 暂不支持图片输入,请升级 CLI 或切换到 Codex。",
|
|
2327
|
+
);
|
|
1827
2328
|
return;
|
|
1828
2329
|
}
|
|
1829
2330
|
|
|
@@ -1832,7 +2333,35 @@ export class AgentWorkspaceProxy {
|
|
|
1832
2333
|
reasoningEffort: conversation.reasoningEffort,
|
|
1833
2334
|
permissionMode: conversation.permissionMode,
|
|
1834
2335
|
};
|
|
1835
|
-
|
|
2336
|
+
const effectiveModel = payload.model ??
|
|
2337
|
+
conversation.model ??
|
|
2338
|
+
(protocol === "codex-app-server" ? this.defaultModelForProvider(conversation.provider) : undefined);
|
|
2339
|
+
const wasRunning = conversation.status === "running";
|
|
2340
|
+
const activeTurnId = payload.targetTurnId ?? this.currentTurnIds.get(conversation.id);
|
|
2341
|
+
const shouldSteer = Boolean(
|
|
2342
|
+
payload.delivery !== "new_turn" &&
|
|
2343
|
+
wasRunning &&
|
|
2344
|
+
protocol === "codex-app-server" &&
|
|
2345
|
+
activeTurnId,
|
|
2346
|
+
);
|
|
2347
|
+
if (wasRunning && payload.delivery !== "new_turn" && protocol !== "codex-app-server") {
|
|
2348
|
+
this.addItem(conversation.id, {
|
|
2349
|
+
id: id("error"),
|
|
2350
|
+
conversationId: conversation.id,
|
|
2351
|
+
type: "error",
|
|
2352
|
+
error: `${providerLabel(conversation.provider)} 当前不支持运行中追加输入。请等待本轮结束,或先停止后再发送。`,
|
|
2353
|
+
createdAt: Date.now(),
|
|
2354
|
+
});
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
if (payload.delivery === "steer" && protocol === "codex-app-server" && !activeTurnId) {
|
|
2358
|
+
this.rejectAgentAction(
|
|
2359
|
+
conversation,
|
|
2360
|
+
"当前没有可追加输入的 Codex turn。请等待本轮开始后重试,或作为新消息发送。",
|
|
2361
|
+
);
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
conversation.model = effectiveModel ?? conversation.model;
|
|
1836
2365
|
conversation.reasoningEffort = payload.reasoningEffort ?? conversation.reasoningEffort;
|
|
1837
2366
|
conversation.permissionMode = payload.permissionMode ?? conversation.permissionMode;
|
|
1838
2367
|
conversation.collaborationMode = payload.collaborationMode ?? conversation.collaborationMode;
|
|
@@ -1840,14 +2369,14 @@ export class AgentWorkspaceProxy {
|
|
|
1840
2369
|
conversation.lastActivityAt = Date.now();
|
|
1841
2370
|
this.activeConversationId = conversation.id;
|
|
1842
2371
|
|
|
1843
|
-
if (
|
|
2372
|
+
if (effectiveModel && effectiveModel !== priorSettings.model) {
|
|
1844
2373
|
const runtimeCapabilities = this.providerCapabilities.get(conversation.provider);
|
|
1845
|
-
const label = runtimeCapabilities?.models?.find((m) => m.id ===
|
|
2374
|
+
const label = runtimeCapabilities?.models?.find((m) => m.id === effectiveModel)?.label ?? effectiveModel;
|
|
1846
2375
|
this.emitNotice({
|
|
1847
2376
|
conversationId: conversation.id,
|
|
1848
2377
|
kind: "model_changed",
|
|
1849
2378
|
title: `已切换模型 · ${label}`,
|
|
1850
|
-
detail: `${providerLabel(conversation.provider)} 下次回复将使用 ${
|
|
2379
|
+
detail: `${providerLabel(conversation.provider)} 下次回复将使用 ${effectiveModel}`,
|
|
1851
2380
|
});
|
|
1852
2381
|
}
|
|
1853
2382
|
if (payload.reasoningEffort && payload.reasoningEffort !== priorSettings.reasoningEffort) {
|
|
@@ -1873,21 +2402,62 @@ export class AgentWorkspaceProxy {
|
|
|
1873
2402
|
role: "user",
|
|
1874
2403
|
content: payload.contentBlocks,
|
|
1875
2404
|
text: userText,
|
|
2405
|
+
metadata: shouldSteer ? { delivery: "steer", targetTurnId: activeTurnId } : undefined,
|
|
1876
2406
|
createdAt: Date.now(),
|
|
1877
2407
|
});
|
|
1878
2408
|
this.emitConversation(conversation);
|
|
1879
2409
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
sessionId: conversation.agentSessionId
|
|
2410
|
+
const promptAsNewTurn = () =>
|
|
2411
|
+
client.prompt({
|
|
2412
|
+
sessionId: conversation.agentSessionId!,
|
|
1883
2413
|
content: payload.contentBlocks,
|
|
1884
2414
|
clientMessageId: payload.clientMessageId,
|
|
1885
|
-
model:
|
|
2415
|
+
model: effectiveModel,
|
|
1886
2416
|
reasoningEffort: payload.reasoningEffort,
|
|
1887
2417
|
permissionMode: payload.permissionMode,
|
|
1888
2418
|
collaborationMode: payload.collaborationMode ?? conversation.collaborationMode,
|
|
1889
2419
|
cwd: conversation.cwd,
|
|
1890
2420
|
});
|
|
2421
|
+
|
|
2422
|
+
try {
|
|
2423
|
+
const steer = (client as { steer?: (input: { sessionId: string; turnId: string; content: AgentContentBlock[] }) => Promise<unknown> }).steer;
|
|
2424
|
+
let result: unknown;
|
|
2425
|
+
if (shouldSteer && typeof steer === "function") {
|
|
2426
|
+
try {
|
|
2427
|
+
result = await steer.call(client, {
|
|
2428
|
+
sessionId: conversation.agentSessionId,
|
|
2429
|
+
turnId: activeTurnId!,
|
|
2430
|
+
content: payload.contentBlocks,
|
|
2431
|
+
});
|
|
2432
|
+
} catch (steerError) {
|
|
2433
|
+
this.forgetCurrentTurn(conversation.id, activeTurnId);
|
|
2434
|
+
const userItem = this.findItem(conversation.id, payload.clientMessageId);
|
|
2435
|
+
if (userItem) {
|
|
2436
|
+
this.upsertItem(conversation.id, {
|
|
2437
|
+
...userItem,
|
|
2438
|
+
metadata: {
|
|
2439
|
+
...(userItem.metadata ?? {}),
|
|
2440
|
+
delivery: "new_turn",
|
|
2441
|
+
fallbackFrom: "steer",
|
|
2442
|
+
failedTargetTurnId: activeTurnId,
|
|
2443
|
+
},
|
|
2444
|
+
updatedAt: Date.now(),
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
this.addItem(conversation.id, {
|
|
2448
|
+
id: id("status"),
|
|
2449
|
+
conversationId: conversation.id,
|
|
2450
|
+
type: "status",
|
|
2451
|
+
role: "system",
|
|
2452
|
+
text: "Codex 未接收运行中追加输入,已改为发送新消息。",
|
|
2453
|
+
status: "running",
|
|
2454
|
+
createdAt: Date.now(),
|
|
2455
|
+
});
|
|
2456
|
+
result = await promptAsNewTurn();
|
|
2457
|
+
}
|
|
2458
|
+
} else {
|
|
2459
|
+
result = await promptAsNewTurn();
|
|
2460
|
+
}
|
|
1891
2461
|
const nextAgentSessionId = this.extractSessionId(result);
|
|
1892
2462
|
if (nextAgentSessionId && nextAgentSessionId !== conversation.agentSessionId) {
|
|
1893
2463
|
if (conversation.agentSessionId) this.conversationByAgentSessionId.delete(conversation.agentSessionId);
|
|
@@ -2120,6 +2690,9 @@ export class AgentWorkspaceProxy {
|
|
|
2120
2690
|
}
|
|
2121
2691
|
|
|
2122
2692
|
private handleRequest(method: string, params: unknown): Promise<unknown> | unknown {
|
|
2693
|
+
if (method === "claude/askUserQuestion") {
|
|
2694
|
+
return this.handleStructuredInput({ ...(asRecord(params) ?? {}), source: method }, true);
|
|
2695
|
+
}
|
|
2123
2696
|
if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
|
|
2124
2697
|
return this.handleStructuredInput(params, true);
|
|
2125
2698
|
}
|
|
@@ -2167,7 +2740,6 @@ export class AgentWorkspaceProxy {
|
|
|
2167
2740
|
if (
|
|
2168
2741
|
method.startsWith("account/") ||
|
|
2169
2742
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
2170
|
-
method === "thread/status/changed" ||
|
|
2171
2743
|
method === "thread/tokenUsage/updated" ||
|
|
2172
2744
|
method === "serverRequest/resolved" ||
|
|
2173
2745
|
method === "mcpServer/oauthLogin/completed"
|
|
@@ -2176,6 +2748,25 @@ export class AgentWorkspaceProxy {
|
|
|
2176
2748
|
}
|
|
2177
2749
|
|
|
2178
2750
|
const conversationId = this.conversationIdFromParams(params) ?? this.fallbackConversationId();
|
|
2751
|
+
if (method === "thread/status/changed") {
|
|
2752
|
+
const status = agentStatusFromThreadStatus(params);
|
|
2753
|
+
if (conversationId && status) {
|
|
2754
|
+
const raw = asRecord(params);
|
|
2755
|
+
const turnId = this.extractTurnId(raw);
|
|
2756
|
+
if (turnId && (status === "running" || status === "waiting_permission")) {
|
|
2757
|
+
this.rememberTurnConversationId(conversationId, turnId);
|
|
2758
|
+
}
|
|
2759
|
+
if (status === "idle" || status === "error") {
|
|
2760
|
+
this.forgetCurrentTurn(conversationId, turnId);
|
|
2761
|
+
}
|
|
2762
|
+
const message =
|
|
2763
|
+
status === "error"
|
|
2764
|
+
? firstString(raw, ["message", "error", "reason"])
|
|
2765
|
+
: undefined;
|
|
2766
|
+
this.updateConversationStatus(conversationId, status, message);
|
|
2767
|
+
}
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2179
2770
|
if (method === "item/tool/requestUserInput" || method === "tool/requestUserInput") {
|
|
2180
2771
|
this.handleStructuredInput(params);
|
|
2181
2772
|
return;
|
|
@@ -2685,9 +3276,8 @@ export class AgentWorkspaceProxy {
|
|
|
2685
3276
|
const raw = asRecord(params) ?? {};
|
|
2686
3277
|
const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
|
|
2687
3278
|
const source = firstString(raw, ["method", "source", "requestMethod"]);
|
|
2688
|
-
const formatResponse =
|
|
2689
|
-
|
|
2690
|
-
: formatStructuredInputResponse;
|
|
3279
|
+
const formatResponse = (answers: Record<string, string[]>, input?: AgentStructuredInput) =>
|
|
3280
|
+
formatStructuredInputResponseForSource(source, answers, input);
|
|
2691
3281
|
if (!conversationId) return waitForResponse ? Promise.resolve(formatResponse({})) : undefined;
|
|
2692
3282
|
const structuredInput = decodeStructuredInput(raw);
|
|
2693
3283
|
if (!structuredInput) return waitForResponse ? Promise.resolve(formatResponse({})) : undefined;
|
|
@@ -2711,13 +3301,13 @@ export class AgentWorkspaceProxy {
|
|
|
2711
3301
|
const timer = setTimeout(() => {
|
|
2712
3302
|
this.pendingStructuredInputs.delete(structuredInput.requestId);
|
|
2713
3303
|
this.structuredInputWaiters.delete(structuredInput.requestId);
|
|
2714
|
-
resolve(formatResponse({}));
|
|
3304
|
+
resolve(formatResponse({}, structuredInput));
|
|
2715
3305
|
this.markStructuredInput(conversationId, structuredInput.requestId, {
|
|
2716
3306
|
inputPending: false,
|
|
2717
3307
|
inputError: "等待用户输入超时",
|
|
2718
3308
|
});
|
|
2719
3309
|
}, PERMISSION_TIMEOUT_MS);
|
|
2720
|
-
this.structuredInputWaiters.set(structuredInput.requestId, { resolve, timer, source });
|
|
3310
|
+
this.structuredInputWaiters.set(structuredInput.requestId, { resolve, timer, source, input: structuredInput });
|
|
2721
3311
|
});
|
|
2722
3312
|
}
|
|
2723
3313
|
|
|
@@ -2773,6 +3363,12 @@ export class AgentWorkspaceProxy {
|
|
|
2773
3363
|
conversationId,
|
|
2774
3364
|
type: "permission",
|
|
2775
3365
|
permission,
|
|
3366
|
+
metadata: {
|
|
3367
|
+
protocol: "v2",
|
|
3368
|
+
permissionLive: true,
|
|
3369
|
+
permissionExpired: false,
|
|
3370
|
+
permissionPending: false,
|
|
3371
|
+
},
|
|
2776
3372
|
createdAt: Date.now(),
|
|
2777
3373
|
updatedAt: Date.now(),
|
|
2778
3374
|
};
|
|
@@ -2803,10 +3399,13 @@ export class AgentWorkspaceProxy {
|
|
|
2803
3399
|
optionId?: string;
|
|
2804
3400
|
}): void {
|
|
2805
3401
|
const permission = this.pendingPermissions.get(payload.requestId);
|
|
3402
|
+
const waiter = this.permissionWaiters.get(payload.requestId);
|
|
3403
|
+
const existingItem = this.findItem(payload.conversationId, `permission:${payload.requestId}`);
|
|
3404
|
+
const alreadyResolved = Boolean(existingItem?.metadata?.permissionOutcome);
|
|
3405
|
+
if (!permission && !waiter && alreadyResolved) return;
|
|
2806
3406
|
this.pendingPermissions.delete(payload.requestId);
|
|
2807
3407
|
const selectedOptionId =
|
|
2808
3408
|
payload.optionId ?? selectPermissionOption(permission, payload.outcome);
|
|
2809
|
-
const waiter = this.permissionWaiters.get(payload.requestId);
|
|
2810
3409
|
if (waiter) {
|
|
2811
3410
|
clearTimeout(waiter.timer);
|
|
2812
3411
|
this.permissionWaiters.delete(payload.requestId);
|
|
@@ -2832,7 +3431,9 @@ export class AgentWorkspaceProxy {
|
|
|
2832
3431
|
permissionError: undefined,
|
|
2833
3432
|
permissionPending: false,
|
|
2834
3433
|
});
|
|
2835
|
-
|
|
3434
|
+
if (permission || waiter || existingItem) {
|
|
3435
|
+
this.updateConversationStatus(payload.conversationId, "running");
|
|
3436
|
+
}
|
|
2836
3437
|
}
|
|
2837
3438
|
|
|
2838
3439
|
private respondStructuredInput(payload: {
|
|
@@ -2841,15 +3442,15 @@ export class AgentWorkspaceProxy {
|
|
|
2841
3442
|
answers: Record<string, string[]>;
|
|
2842
3443
|
}): void {
|
|
2843
3444
|
const pending = this.pendingStructuredInputs.get(payload.requestId);
|
|
2844
|
-
this.pendingStructuredInputs.delete(payload.requestId);
|
|
2845
3445
|
const waiter = this.structuredInputWaiters.get(payload.requestId);
|
|
3446
|
+
const existingItem = this.findItem(payload.conversationId, `input:${payload.requestId}`);
|
|
3447
|
+
const alreadySubmitted = existingItem?.metadata?.inputSubmitted === true;
|
|
3448
|
+
if (!pending && !waiter && alreadySubmitted) return;
|
|
3449
|
+
this.pendingStructuredInputs.delete(payload.requestId);
|
|
2846
3450
|
if (waiter) {
|
|
2847
3451
|
clearTimeout(waiter.timer);
|
|
2848
3452
|
this.structuredInputWaiters.delete(payload.requestId);
|
|
2849
|
-
|
|
2850
|
-
? formatMcpElicitationResponse
|
|
2851
|
-
: formatStructuredInputResponse;
|
|
2852
|
-
waiter.resolve(formatResponse(payload.answers));
|
|
3453
|
+
waiter.resolve(formatStructuredInputResponseForSource(waiter.source, payload.answers, waiter.input));
|
|
2853
3454
|
}
|
|
2854
3455
|
this.markStructuredInput(payload.conversationId, payload.requestId, {
|
|
2855
3456
|
inputPending: false,
|
|
@@ -2858,7 +3459,9 @@ export class AgentWorkspaceProxy {
|
|
|
2858
3459
|
inputError: undefined,
|
|
2859
3460
|
answers: payload.answers,
|
|
2860
3461
|
});
|
|
2861
|
-
|
|
3462
|
+
if (pending || waiter || existingItem) {
|
|
3463
|
+
this.updateConversationStatus(pending?.conversationId ?? payload.conversationId, "running");
|
|
3464
|
+
}
|
|
2862
3465
|
}
|
|
2863
3466
|
|
|
2864
3467
|
private markPermission(
|
|
@@ -3058,7 +3661,7 @@ export class AgentWorkspaceProxy {
|
|
|
3058
3661
|
this.input.send(createEnvelope({
|
|
3059
3662
|
type: "agent.v2.event",
|
|
3060
3663
|
sessionId: this.input.sessionId,
|
|
3061
|
-
payload: { conversationId, conversation, item },
|
|
3664
|
+
payload: { conversationId, conversation: conversation ? { ...conversation } : undefined, item },
|
|
3062
3665
|
}));
|
|
3063
3666
|
}
|
|
3064
3667
|
|
|
@@ -3066,7 +3669,7 @@ export class AgentWorkspaceProxy {
|
|
|
3066
3669
|
this.input.send(createEnvelope({
|
|
3067
3670
|
type: "agent.v2.event",
|
|
3068
3671
|
sessionId: this.input.sessionId,
|
|
3069
|
-
payload: { conversationId: conversation.id, conversation },
|
|
3672
|
+
payload: { conversationId: conversation.id, conversation: { ...conversation } },
|
|
3070
3673
|
}));
|
|
3071
3674
|
}
|
|
3072
3675
|
|
|
@@ -3101,6 +3704,21 @@ export class AgentWorkspaceProxy {
|
|
|
3101
3704
|
}));
|
|
3102
3705
|
}
|
|
3103
3706
|
|
|
3707
|
+
private rejectAgentAction(
|
|
3708
|
+
conversation: AgentConversation,
|
|
3709
|
+
message: string,
|
|
3710
|
+
status: AgentStatus = "idle",
|
|
3711
|
+
): void {
|
|
3712
|
+
this.updateConversationStatus(conversation.id, status, message);
|
|
3713
|
+
this.addItem(conversation.id, {
|
|
3714
|
+
id: id("error"),
|
|
3715
|
+
conversationId: conversation.id,
|
|
3716
|
+
type: "error",
|
|
3717
|
+
error: message,
|
|
3718
|
+
createdAt: Date.now(),
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3104
3722
|
private updateConversationPreview(
|
|
3105
3723
|
conversationId: string,
|
|
3106
3724
|
text: string,
|
|
@@ -3228,12 +3846,22 @@ export class AgentWorkspaceProxy {
|
|
|
3228
3846
|
private handleProviderExit(provider: AgentProvider, message: string): void {
|
|
3229
3847
|
this.clients.delete(provider);
|
|
3230
3848
|
this.agentProtocols.delete(provider);
|
|
3231
|
-
this.
|
|
3849
|
+
this.providerErrors.set(provider, message);
|
|
3850
|
+
for (const conversation of this.conversations.values()) {
|
|
3851
|
+
if (conversation.provider === provider && conversation.status === "waiting_permission") {
|
|
3852
|
+
this.cancelPendingPermissions(conversation.id, false);
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3232
3855
|
for (const conversation of this.conversations.values()) {
|
|
3233
3856
|
if (conversation.provider !== provider) continue;
|
|
3857
|
+
if (conversation.status !== "running" && conversation.status !== "waiting_permission") {
|
|
3858
|
+
this.emitConversation(conversation);
|
|
3859
|
+
continue;
|
|
3860
|
+
}
|
|
3234
3861
|
conversation.status = "error";
|
|
3235
3862
|
conversation.lastMessagePreview = message;
|
|
3236
3863
|
conversation.lastActivityAt = Date.now();
|
|
3864
|
+
this.forgetCurrentTurn(conversation.id);
|
|
3237
3865
|
this.emitConversation(conversation);
|
|
3238
3866
|
this.addItem(conversation.id, {
|
|
3239
3867
|
id: id("error"),
|
|
@@ -3246,8 +3874,9 @@ export class AgentWorkspaceProxy {
|
|
|
3246
3874
|
this.sendCapabilities();
|
|
3247
3875
|
}
|
|
3248
3876
|
|
|
3249
|
-
private cancelPendingPermissions(conversationId?: string): void {
|
|
3877
|
+
private cancelPendingPermissions(conversationId?: string, updateStatus = true): void {
|
|
3250
3878
|
for (const [requestId, waiter] of this.permissionWaiters) {
|
|
3879
|
+
if (conversationId && this.itemConversationIds.get(requestId) !== conversationId) continue;
|
|
3251
3880
|
clearTimeout(waiter.timer);
|
|
3252
3881
|
waiter.resolve(formatPermissionResponse(
|
|
3253
3882
|
this.permissionSources.get(requestId),
|
|
@@ -3257,11 +3886,12 @@ export class AgentWorkspaceProxy {
|
|
|
3257
3886
|
this.pendingPermissions.delete(requestId);
|
|
3258
3887
|
this.permissionSources.delete(requestId);
|
|
3259
3888
|
}
|
|
3260
|
-
this.permissionWaiters.clear();
|
|
3889
|
+
if (!conversationId) this.permissionWaiters.clear();
|
|
3261
3890
|
for (const [requestId, waiter] of this.structuredInputWaiters) {
|
|
3262
|
-
clearTimeout(waiter.timer);
|
|
3263
|
-
waiter.resolve(formatStructuredInputResponse({}));
|
|
3264
3891
|
const pending = this.pendingStructuredInputs.get(requestId);
|
|
3892
|
+
if (conversationId && pending?.conversationId !== conversationId) continue;
|
|
3893
|
+
clearTimeout(waiter.timer);
|
|
3894
|
+
waiter.resolve(formatStructuredInputResponseForSource(waiter.source, {}, waiter.input));
|
|
3265
3895
|
if (pending) {
|
|
3266
3896
|
this.markStructuredInput(pending.conversationId, requestId, {
|
|
3267
3897
|
inputPending: false,
|
|
@@ -3269,9 +3899,10 @@ export class AgentWorkspaceProxy {
|
|
|
3269
3899
|
});
|
|
3270
3900
|
}
|
|
3271
3901
|
this.pendingStructuredInputs.delete(requestId);
|
|
3902
|
+
this.structuredInputWaiters.delete(requestId);
|
|
3272
3903
|
}
|
|
3273
|
-
this.structuredInputWaiters.clear();
|
|
3274
|
-
if (conversationId) this.updateConversationStatus(conversationId, "idle");
|
|
3904
|
+
if (!conversationId) this.structuredInputWaiters.clear();
|
|
3905
|
+
if (conversationId && updateStatus) this.updateConversationStatus(conversationId, "idle");
|
|
3275
3906
|
}
|
|
3276
3907
|
|
|
3277
3908
|
private extractSessionId(value: unknown): string | undefined {
|
|
@@ -3359,6 +3990,37 @@ function formatStructuredInputResponse(answers: Record<string, string[]>): unkno
|
|
|
3359
3990
|
};
|
|
3360
3991
|
}
|
|
3361
3992
|
|
|
3993
|
+
function formatClaudeAskUserQuestionResponse(
|
|
3994
|
+
answers: Record<string, string[]>,
|
|
3995
|
+
input?: AgentStructuredInput,
|
|
3996
|
+
): unknown {
|
|
3997
|
+
const questions = input?.questions ?? [];
|
|
3998
|
+
const byQuestionText: Record<string, string | string[]> = {};
|
|
3999
|
+
for (const question of questions) {
|
|
4000
|
+
const values = answers[question.id] ?? answers[question.question] ?? [];
|
|
4001
|
+
const cleaned = values.map((value) => value.trim()).filter(Boolean);
|
|
4002
|
+
if (cleaned.length === 0) continue;
|
|
4003
|
+
byQuestionText[question.question] = question.selectionLimit && question.selectionLimit > 1
|
|
4004
|
+
? cleaned
|
|
4005
|
+
: cleaned[0] ?? "";
|
|
4006
|
+
}
|
|
4007
|
+
return {
|
|
4008
|
+
behavior: "allow",
|
|
4009
|
+
updatedInput: {
|
|
4010
|
+
questions: questions.map((question) => ({
|
|
4011
|
+
question: question.question,
|
|
4012
|
+
header: question.header,
|
|
4013
|
+
options: question.options?.map((option) => ({
|
|
4014
|
+
label: option.label,
|
|
4015
|
+
description: option.description,
|
|
4016
|
+
})) ?? [],
|
|
4017
|
+
multiSelect: Boolean(question.selectionLimit && question.selectionLimit > 1),
|
|
4018
|
+
})),
|
|
4019
|
+
answers: byQuestionText,
|
|
4020
|
+
},
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
|
|
3362
4024
|
function formatMcpElicitationResponse(answers: Record<string, string[]>): unknown {
|
|
3363
4025
|
const content = Object.fromEntries(
|
|
3364
4026
|
Object.entries(answers).map(([questionId, values]) => [
|
|
@@ -3373,6 +4035,20 @@ function formatMcpElicitationResponse(answers: Record<string, string[]>): unknow
|
|
|
3373
4035
|
};
|
|
3374
4036
|
}
|
|
3375
4037
|
|
|
4038
|
+
function formatStructuredInputResponseForSource(
|
|
4039
|
+
source: string | undefined,
|
|
4040
|
+
answers: Record<string, string[]>,
|
|
4041
|
+
input?: AgentStructuredInput,
|
|
4042
|
+
): unknown {
|
|
4043
|
+
if (source === "mcpServer/elicitation/request") {
|
|
4044
|
+
return formatMcpElicitationResponse(answers);
|
|
4045
|
+
}
|
|
4046
|
+
if (source === "claude/askUserQuestion") {
|
|
4047
|
+
return formatClaudeAskUserQuestionResponse(answers, input);
|
|
4048
|
+
}
|
|
4049
|
+
return formatStructuredInputResponse(answers);
|
|
4050
|
+
}
|
|
4051
|
+
|
|
3376
4052
|
function formatPermissionResponse(
|
|
3377
4053
|
source: string | undefined,
|
|
3378
4054
|
outcome: "allow" | "deny" | "cancelled",
|