linkshell-cli 0.2.121 → 0.2.123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/src/runtime/acp/agent-workspace.js +64 -10
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +19 -0
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +38 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/runtime/acp/agent-workspace.ts +64 -10
- package/src/runtime/acp/claude-sdk-client.ts +19 -0
- package/src/runtime/acp/claude-stream-json-client.ts +39 -1
|
@@ -529,6 +529,49 @@ function textFromBlocks(blocks: AgentContentBlock[]): string {
|
|
|
529
529
|
.join("\n");
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
function contentBlocksFromValue(value: unknown): AgentContentBlock[] {
|
|
533
|
+
if (typeof value === "string") {
|
|
534
|
+
return value.trim() ? [{ type: "text", text: value }] : [];
|
|
535
|
+
}
|
|
536
|
+
if (Array.isArray(value)) {
|
|
537
|
+
return value.flatMap((entry) => contentBlocksFromValue(entry));
|
|
538
|
+
}
|
|
539
|
+
const raw = asRecord(value);
|
|
540
|
+
if (!raw) return [];
|
|
541
|
+
const rawType = firstString(raw, ["type", "kind"]);
|
|
542
|
+
const normalizedType = normalizedIdentifier(rawType);
|
|
543
|
+
if (normalizedType === "image" || normalizedType === "inputimage" || normalizedType === "outputimage") {
|
|
544
|
+
const data = firstString(raw, [
|
|
545
|
+
"data",
|
|
546
|
+
"url",
|
|
547
|
+
"uri",
|
|
548
|
+
"imageUrl",
|
|
549
|
+
"image_url",
|
|
550
|
+
"base64",
|
|
551
|
+
]);
|
|
552
|
+
const mimeType = firstString(raw, ["mimeType", "mime_type", "mediaType", "media_type"]);
|
|
553
|
+
const text = firstString(raw, ["text", "alt", "caption", "name"]);
|
|
554
|
+
return [{ type: "image", data, mimeType, text }];
|
|
555
|
+
}
|
|
556
|
+
if (normalizedType === "text" || normalizedType === "outputtext" || normalizedType === "inputtext") {
|
|
557
|
+
const text = firstString(raw, ["text", "content", "message"]);
|
|
558
|
+
return text ? [{ type: "text", text }] : [];
|
|
559
|
+
}
|
|
560
|
+
const nested = raw.content ?? raw.contentItems ?? raw.parts;
|
|
561
|
+
if (Array.isArray(nested)) return contentBlocksFromValue(nested);
|
|
562
|
+
const text = firstString(raw, ["text", "message", "content"]);
|
|
563
|
+
return text ? [{ type: "text", text }] : [];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function contentBlocksFromItem(item: Record<string, unknown>): AgentContentBlock[] {
|
|
567
|
+
for (const key of ["content", "contentItems", "parts", "message"]) {
|
|
568
|
+
const blocks = contentBlocksFromValue(item[key]);
|
|
569
|
+
if (blocks.length > 0) return blocks;
|
|
570
|
+
}
|
|
571
|
+
const text = firstString(item, ["text", "message"]);
|
|
572
|
+
return text ? [{ type: "text", text }] : [];
|
|
573
|
+
}
|
|
574
|
+
|
|
532
575
|
function protocolSupportsImages(protocol: AgentProtocol | undefined): boolean {
|
|
533
576
|
return protocol === "codex-app-server" ||
|
|
534
577
|
protocol === "claude-agent-sdk" ||
|
|
@@ -2053,7 +2096,11 @@ export class AgentWorkspaceProxy {
|
|
|
2053
2096
|
if (agentSessionId && conversationId) {
|
|
2054
2097
|
this.conversationByAgentSessionId.set(agentSessionId, conversationId);
|
|
2055
2098
|
const conversation = this.conversations.get(conversationId);
|
|
2056
|
-
if (conversation)
|
|
2099
|
+
if (conversation) {
|
|
2100
|
+
conversation.agentSessionId = agentSessionId;
|
|
2101
|
+
conversation.lastActivityAt = Date.now();
|
|
2102
|
+
this.emitConversation(conversation);
|
|
2103
|
+
}
|
|
2057
2104
|
}
|
|
2058
2105
|
return;
|
|
2059
2106
|
}
|
|
@@ -2387,20 +2434,24 @@ export class AgentWorkspaceProxy {
|
|
|
2387
2434
|
if (!conversationId) return;
|
|
2388
2435
|
const itemId = firstString(item, ["id"]) ?? id("msg");
|
|
2389
2436
|
const existing = this.findItem(conversationId, itemId);
|
|
2390
|
-
const content =
|
|
2391
|
-
|
|
2437
|
+
const content = contentBlocksFromItem(item);
|
|
2438
|
+
const nextContent = content.length > 0
|
|
2439
|
+
? content
|
|
2440
|
+
: existing?.content ?? (existing?.text ? [{ type: "text", text: existing.text }] : []);
|
|
2441
|
+
const text = textFromBlocks(nextContent);
|
|
2442
|
+
if (!nextContent.length && !text) return;
|
|
2392
2443
|
this.upsertItem(conversationId, {
|
|
2393
2444
|
id: itemId,
|
|
2394
2445
|
conversationId,
|
|
2395
2446
|
type: "message",
|
|
2396
2447
|
role: "assistant",
|
|
2397
|
-
content:
|
|
2398
|
-
text
|
|
2448
|
+
content: nextContent,
|
|
2449
|
+
text,
|
|
2399
2450
|
createdAt: existing?.createdAt ?? Date.now(),
|
|
2400
2451
|
updatedAt: Date.now(),
|
|
2401
2452
|
isStreaming: streaming,
|
|
2402
2453
|
});
|
|
2403
|
-
this.updateConversationPreview(conversationId,
|
|
2454
|
+
this.updateConversationPreview(conversationId, text || "图片附件", streaming ? "running" : "idle");
|
|
2404
2455
|
}
|
|
2405
2456
|
|
|
2406
2457
|
private handleSessionUpdate(params: unknown): void {
|
|
@@ -2409,7 +2460,8 @@ export class AgentWorkspaceProxy {
|
|
|
2409
2460
|
const text =
|
|
2410
2461
|
firstString(raw, ["delta", "text", "content", "message"]) ??
|
|
2411
2462
|
firstString(nested, ["delta", "text", "content", "message"]);
|
|
2412
|
-
|
|
2463
|
+
const content = contentBlocksFromItem(raw);
|
|
2464
|
+
if (!text && content.length === 0) return;
|
|
2413
2465
|
const conversationId = this.conversationIdFromParams(raw) ?? this.fallbackConversationId();
|
|
2414
2466
|
if (!conversationId) return;
|
|
2415
2467
|
if (firstString(raw, ["toolName", "tool", "name"])) {
|
|
@@ -2426,18 +2478,20 @@ export class AgentWorkspaceProxy {
|
|
|
2426
2478
|
return;
|
|
2427
2479
|
}
|
|
2428
2480
|
const role = raw.role === "user" || raw.role === "system" ? raw.role : "assistant";
|
|
2481
|
+
const blocks = content.length > 0 ? content : [{ type: "text" as const, text }];
|
|
2482
|
+
const preview = textFromBlocks(blocks);
|
|
2429
2483
|
this.upsertItem(conversationId, {
|
|
2430
2484
|
id: firstString(raw, ["messageId", "id"]) ?? id("msg"),
|
|
2431
2485
|
conversationId,
|
|
2432
2486
|
type: "message",
|
|
2433
2487
|
role,
|
|
2434
|
-
content:
|
|
2435
|
-
text,
|
|
2488
|
+
content: blocks,
|
|
2489
|
+
text: preview,
|
|
2436
2490
|
createdAt: Date.now(),
|
|
2437
2491
|
updatedAt: Date.now(),
|
|
2438
2492
|
isStreaming: raw.done === false || raw.isStreaming === true,
|
|
2439
2493
|
});
|
|
2440
|
-
this.updateConversationPreview(conversationId,
|
|
2494
|
+
this.updateConversationPreview(conversationId, preview || "图片附件", raw.done === true ? "idle" : "running");
|
|
2441
2495
|
}
|
|
2442
2496
|
|
|
2443
2497
|
private handleSemanticSystemItem(
|
|
@@ -271,8 +271,18 @@ export class ClaudeSdkClient {
|
|
|
271
271
|
let currentToolId: string | undefined;
|
|
272
272
|
let currentToolName: string | undefined;
|
|
273
273
|
let currentMessageId: string | undefined;
|
|
274
|
+
const progressItemId = `claude-progress:${input.clientMessageId}`;
|
|
274
275
|
|
|
275
276
|
try {
|
|
277
|
+
this.input.onNotification("item/started", {
|
|
278
|
+
sessionId: input.sessionId ?? this.claudeSessionId,
|
|
279
|
+
item: {
|
|
280
|
+
id: progressItemId,
|
|
281
|
+
type: "thinking",
|
|
282
|
+
text: "Claude 正在处理请求",
|
|
283
|
+
status: "running",
|
|
284
|
+
},
|
|
285
|
+
});
|
|
276
286
|
const queryPrompt = hasImages ? singleUserMessage(toClaudeMessageContent(inputBlocks)) : prompt;
|
|
277
287
|
for await (const message of this.query({ prompt: queryPrompt, options: sdkOptions })) {
|
|
278
288
|
if (abortController.signal.aborted) break;
|
|
@@ -295,6 +305,15 @@ export class ClaudeSdkClient {
|
|
|
295
305
|
}
|
|
296
306
|
return { sessionId: this.claudeSessionId, status: abortController.signal.aborted ? "cancelled" : "completed" };
|
|
297
307
|
} finally {
|
|
308
|
+
this.input.onNotification("item/completed", {
|
|
309
|
+
sessionId: this.claudeSessionId ?? input.sessionId,
|
|
310
|
+
item: {
|
|
311
|
+
id: progressItemId,
|
|
312
|
+
type: "thinking",
|
|
313
|
+
text: abortController.signal.aborted ? "Claude 已停止" : "Claude 已完成",
|
|
314
|
+
status: abortController.signal.aborted ? "failed" : "completed",
|
|
315
|
+
},
|
|
316
|
+
});
|
|
298
317
|
if (this.abortController === abortController) this.abortController = undefined;
|
|
299
318
|
}
|
|
300
319
|
}
|
|
@@ -222,10 +222,21 @@ export class ClaudeStreamJsonClient {
|
|
|
222
222
|
let currentToolId: string | undefined;
|
|
223
223
|
let currentToolName: string | undefined;
|
|
224
224
|
let currentMessageId: string | undefined;
|
|
225
|
+
const progressItemId = `claude-progress:${input.clientMessageId}`;
|
|
225
226
|
// Map tool_use_id → tool_name so tool_result can look up the correct name
|
|
226
227
|
// even when multiple tools are in flight
|
|
227
228
|
const toolNames = new Map<string, string>();
|
|
228
229
|
|
|
230
|
+
this.input.onNotification("item/started", {
|
|
231
|
+
sessionId: input.sessionId ?? this.claudeSessionId,
|
|
232
|
+
item: {
|
|
233
|
+
id: progressItemId,
|
|
234
|
+
type: "thinking",
|
|
235
|
+
text: "Claude 正在处理请求",
|
|
236
|
+
status: "running",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
229
240
|
rl.on("line", (line: string) => {
|
|
230
241
|
if (this.pendingCancel) {
|
|
231
242
|
child.kill("SIGTERM");
|
|
@@ -375,6 +386,16 @@ export class ClaudeStreamJsonClient {
|
|
|
375
386
|
}
|
|
376
387
|
|
|
377
388
|
case "result": {
|
|
389
|
+
const isError = event.subtype === "error" || event.is_error === true;
|
|
390
|
+
this.input.onNotification("item/completed", {
|
|
391
|
+
sessionId: this.claudeSessionId ?? input.sessionId,
|
|
392
|
+
item: {
|
|
393
|
+
id: progressItemId,
|
|
394
|
+
type: "thinking",
|
|
395
|
+
text: isError ? "Claude 运行出错" : "Claude 已完成",
|
|
396
|
+
status: isError ? "failed" : "completed",
|
|
397
|
+
},
|
|
398
|
+
});
|
|
378
399
|
// Mark the last agent message as complete so isStreaming flips to false
|
|
379
400
|
if (currentMessageId) {
|
|
380
401
|
this.input.onNotification("item/completed", {
|
|
@@ -387,7 +408,6 @@ export class ClaudeStreamJsonClient {
|
|
|
387
408
|
});
|
|
388
409
|
}
|
|
389
410
|
// Turn complete
|
|
390
|
-
const isError = event.subtype === "error" || event.is_error === true;
|
|
391
411
|
this.input.onNotification("turn/completed", {
|
|
392
412
|
sessionId: this.claudeSessionId,
|
|
393
413
|
stopReason: event.stop_reason ?? (isError ? "error" : "end_turn"),
|
|
@@ -408,11 +428,29 @@ export class ClaudeStreamJsonClient {
|
|
|
408
428
|
});
|
|
409
429
|
|
|
410
430
|
child.on("error", (err) => {
|
|
431
|
+
this.input.onNotification("item/completed", {
|
|
432
|
+
sessionId: this.claudeSessionId ?? input.sessionId,
|
|
433
|
+
item: {
|
|
434
|
+
id: progressItemId,
|
|
435
|
+
type: "thinking",
|
|
436
|
+
text: "Claude 运行出错",
|
|
437
|
+
status: "failed",
|
|
438
|
+
},
|
|
439
|
+
});
|
|
411
440
|
finish(err, undefined);
|
|
412
441
|
});
|
|
413
442
|
|
|
414
443
|
child.on("exit", (code, signal) => {
|
|
415
444
|
if (!settled) {
|
|
445
|
+
this.input.onNotification("item/completed", {
|
|
446
|
+
sessionId: this.claudeSessionId ?? input.sessionId,
|
|
447
|
+
item: {
|
|
448
|
+
id: progressItemId,
|
|
449
|
+
type: "thinking",
|
|
450
|
+
text: this.pendingCancel ? "Claude 已停止" : "Claude 意外退出",
|
|
451
|
+
status: "failed",
|
|
452
|
+
},
|
|
453
|
+
});
|
|
416
454
|
finish(
|
|
417
455
|
new Error(`Claude exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"})`),
|
|
418
456
|
undefined,
|