linkshell-cli 0.2.66 → 0.2.68
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 +14 -1
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +14 -0
- package/dist/cli/src/runtime/acp/agent-session.js +389 -8
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +11 -1
- package/src/runtime/acp/agent-session.ts +390 -7
|
@@ -25,6 +25,12 @@ interface AgentToolCall {
|
|
|
25
25
|
status: "pending" | "running" | "completed" | "failed";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
interface AgentPlanStep {
|
|
29
|
+
id: string;
|
|
30
|
+
text: string;
|
|
31
|
+
status: "pending" | "in_progress" | "completed";
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
interface AgentPermission {
|
|
29
35
|
requestId: string;
|
|
30
36
|
toolName?: string;
|
|
@@ -61,6 +67,118 @@ function firstString(value: Record<string, unknown>, keys: string[]): string | u
|
|
|
61
67
|
return undefined;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
71
|
+
return typeof value === "object" && value ? value as Record<string, unknown> : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function extractItem(value: unknown): Record<string, unknown> | undefined {
|
|
75
|
+
const raw = asRecord(value);
|
|
76
|
+
if (!raw) return undefined;
|
|
77
|
+
return asRecord(raw.item) ?? raw;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stringifyDefined(value: unknown): string | undefined {
|
|
81
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
82
|
+
return stringify(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function appendCapped(current: string | undefined, delta: string, maxLength: number): string {
|
|
86
|
+
const next = `${current ?? ""}${delta}`;
|
|
87
|
+
if (next.length <= maxLength) return next;
|
|
88
|
+
return next.slice(next.length - maxLength);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decodeBase64(value: string | undefined): string | undefined {
|
|
92
|
+
if (!value) return undefined;
|
|
93
|
+
try {
|
|
94
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
95
|
+
} catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeToolStatus(value: unknown, completedFallback = false): AgentToolCall["status"] {
|
|
101
|
+
if (value === "completed" || value === "succeeded" || value === "success" || value === "applied") {
|
|
102
|
+
return "completed";
|
|
103
|
+
}
|
|
104
|
+
if (value === "failed" || value === "error" || value === "declined" || value === "cancelled") {
|
|
105
|
+
return "failed";
|
|
106
|
+
}
|
|
107
|
+
if (value === "pending" || value === "queued") return "pending";
|
|
108
|
+
if (value === "running" || value === "inProgress" || value === "executing") return "running";
|
|
109
|
+
return completedFallback ? "completed" : "running";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizePlanStatus(value: unknown): AgentPlanStep["status"] {
|
|
113
|
+
if (value === "completed" || value === "done") return "completed";
|
|
114
|
+
if (value === "inProgress" || value === "running" || value === "active") return "in_progress";
|
|
115
|
+
return "pending";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function planStepFromItem(item: Record<string, unknown>): AgentPlanStep | undefined {
|
|
119
|
+
const text = firstString(item, ["text", "title", "description", "message"]);
|
|
120
|
+
if (!text) return undefined;
|
|
121
|
+
return {
|
|
122
|
+
id: firstString(item, ["id", "itemId"]) ?? id("plan"),
|
|
123
|
+
text,
|
|
124
|
+
status: normalizePlanStatus(item.status),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function nameFromToolMethod(method: string): string {
|
|
129
|
+
if (method.includes("commandExecution")) return "命令";
|
|
130
|
+
if (method.includes("fileChange")) return "文件修改";
|
|
131
|
+
if (method.includes("mcpToolCall")) return "MCP 工具";
|
|
132
|
+
return "工具";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function toolNameFromItem(item: Record<string, unknown>): string | undefined {
|
|
136
|
+
const itemType = firstString(item, ["type"]);
|
|
137
|
+
if (itemType === "commandExecution") return "命令";
|
|
138
|
+
if (itemType === "fileChange") return "文件修改";
|
|
139
|
+
if (itemType === "mcpToolCall") {
|
|
140
|
+
const server = firstString(item, ["server"]);
|
|
141
|
+
const tool = firstString(item, ["tool", "toolName", "name"]);
|
|
142
|
+
return [server, tool].filter(Boolean).join(" · ") || "MCP 工具";
|
|
143
|
+
}
|
|
144
|
+
if (itemType === "dynamicToolCall") {
|
|
145
|
+
const namespace = firstString(item, ["namespace"]);
|
|
146
|
+
const tool = firstString(item, ["tool", "toolName", "name"]);
|
|
147
|
+
return [namespace, tool].filter(Boolean).join(" · ") || "工具";
|
|
148
|
+
}
|
|
149
|
+
return firstString(item, ["toolName", "tool", "name", "title"]) ?? itemType;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toolInputFromItem(item: Record<string, unknown>): string | undefined {
|
|
153
|
+
const itemType = firstString(item, ["type"]);
|
|
154
|
+
if (itemType === "commandExecution") {
|
|
155
|
+
const command = firstString(item, ["command"]);
|
|
156
|
+
const cwd = firstString(item, ["cwd"]);
|
|
157
|
+
if (command && cwd) return `${command}\n\ncwd: ${cwd}`;
|
|
158
|
+
return command ?? cwd;
|
|
159
|
+
}
|
|
160
|
+
if (itemType === "fileChange") {
|
|
161
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
162
|
+
return summarizeFileChanges(changes);
|
|
163
|
+
}
|
|
164
|
+
return stringifyDefined(item.arguments ?? item.input ?? item.toolInput);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function summarizeFileChanges(changes: unknown[]): string | undefined {
|
|
168
|
+
const lines = changes
|
|
169
|
+
.map((change) => {
|
|
170
|
+
const raw = asRecord(change);
|
|
171
|
+
if (!raw) return undefined;
|
|
172
|
+
const path =
|
|
173
|
+
firstString(raw, ["path", "file", "filePath", "absolutePath", "relativePath"]) ??
|
|
174
|
+
firstString(asRecord(raw.update) ?? {}, ["path", "file", "filePath"]);
|
|
175
|
+
const kind = firstString(raw, ["kind", "type", "operation", "action"]);
|
|
176
|
+
return [kind, path].filter(Boolean).join(" ");
|
|
177
|
+
})
|
|
178
|
+
.filter((line): line is string => Boolean(line));
|
|
179
|
+
return lines.length > 0 ? lines.slice(0, 8).join("\n") : undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
64
182
|
export class AgentSessionProxy {
|
|
65
183
|
private client: AcpClient | undefined;
|
|
66
184
|
private agentSessionId: string | undefined;
|
|
@@ -70,6 +188,9 @@ export class AgentSessionProxy {
|
|
|
70
188
|
private currentTurnId: string | undefined;
|
|
71
189
|
private messages: AgentMessage[] = [];
|
|
72
190
|
private toolCalls = new Map<string, AgentToolCall>();
|
|
191
|
+
private toolOutputBuffers = new Map<string, string>();
|
|
192
|
+
private plan: AgentPlanStep[] = [];
|
|
193
|
+
private planDeltaBuffers = new Map<string, string>();
|
|
73
194
|
private pendingPermissions = new Map<string, AgentPermission>();
|
|
74
195
|
private permissionWaiters = new Map<string, PendingPermissionWaiter>();
|
|
75
196
|
private permissionSources = new Map<string, string>();
|
|
@@ -313,7 +434,12 @@ export class AgentSessionProxy {
|
|
|
313
434
|
if (
|
|
314
435
|
method === "initialized" ||
|
|
315
436
|
method.startsWith("account/") ||
|
|
316
|
-
method.startsWith("mcpServer/startupStatus/")
|
|
437
|
+
method.startsWith("mcpServer/startupStatus/") ||
|
|
438
|
+
method === "thread/status/changed" ||
|
|
439
|
+
method === "thread/tokenUsage/updated" ||
|
|
440
|
+
method === "turn/diff/updated" ||
|
|
441
|
+
method === "serverRequest/resolved" ||
|
|
442
|
+
method === "mcpServer/oauthLogin/completed"
|
|
317
443
|
) {
|
|
318
444
|
return;
|
|
319
445
|
}
|
|
@@ -339,11 +465,48 @@ export class AgentSessionProxy {
|
|
|
339
465
|
this.handlePermission(params, false, method);
|
|
340
466
|
return;
|
|
341
467
|
}
|
|
342
|
-
|
|
468
|
+
|
|
469
|
+
switch (method) {
|
|
470
|
+
case "item/agentMessage/delta":
|
|
471
|
+
this.handleAgentMessageDelta(params);
|
|
472
|
+
return;
|
|
473
|
+
case "turn/plan/updated":
|
|
474
|
+
this.handlePlanUpdated(params);
|
|
475
|
+
return;
|
|
476
|
+
case "item/plan/delta":
|
|
477
|
+
this.handlePlanDelta(params);
|
|
478
|
+
return;
|
|
479
|
+
case "item/started":
|
|
480
|
+
this.handleItemStarted(params);
|
|
481
|
+
return;
|
|
482
|
+
case "item/completed":
|
|
483
|
+
this.handleItemCompleted(params);
|
|
484
|
+
return;
|
|
485
|
+
case "item/commandExecution/outputDelta":
|
|
486
|
+
case "item/fileChange/outputDelta":
|
|
487
|
+
case "item/mcpToolCall/progress":
|
|
488
|
+
this.handleToolDelta(method, params);
|
|
489
|
+
return;
|
|
490
|
+
case "item/fileChange/patchUpdated":
|
|
491
|
+
this.handleFilePatchUpdated(params);
|
|
492
|
+
return;
|
|
493
|
+
case "command/exec/outputDelta":
|
|
494
|
+
this.handleCommandExecDelta(params);
|
|
495
|
+
return;
|
|
496
|
+
case "item/autoApprovalReview/started":
|
|
497
|
+
case "item/autoApprovalReview/completed":
|
|
498
|
+
case "item/commandExecution/terminalInteraction":
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (method === "session/update") {
|
|
343
503
|
this.handleUpdate(params);
|
|
344
504
|
return;
|
|
345
505
|
}
|
|
346
|
-
|
|
506
|
+
|
|
507
|
+
if (this.input.verbose) {
|
|
508
|
+
process.stderr.write(`[agent] ignored ${method}\n`);
|
|
509
|
+
}
|
|
347
510
|
}
|
|
348
511
|
|
|
349
512
|
private handlePermission(
|
|
@@ -386,13 +549,226 @@ export class AgentSessionProxy {
|
|
|
386
549
|
});
|
|
387
550
|
}
|
|
388
551
|
|
|
552
|
+
private handleAgentMessageDelta(params: unknown): void {
|
|
553
|
+
const raw = asRecord(params);
|
|
554
|
+
if (!raw) return;
|
|
555
|
+
const itemId = firstString(raw, ["itemId", "id", "messageId"]) ?? id("msg");
|
|
556
|
+
const delta = firstString(raw, ["delta", "text", "content"]);
|
|
557
|
+
if (!delta) return;
|
|
558
|
+
const current = this.messages.find((message) => message.id === itemId);
|
|
559
|
+
const message: AgentMessage = {
|
|
560
|
+
id: itemId,
|
|
561
|
+
role: "assistant",
|
|
562
|
+
content: `${current?.content ?? ""}${delta}`,
|
|
563
|
+
createdAt: current?.createdAt ?? Date.now(),
|
|
564
|
+
isStreaming: true,
|
|
565
|
+
};
|
|
566
|
+
this.upsertMessage(message);
|
|
567
|
+
this.status = "running";
|
|
568
|
+
this.sendUpdate({ kind: "message_delta", message, delta, status: "running" });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private handlePlanUpdated(params: unknown): void {
|
|
572
|
+
const raw = asRecord(params);
|
|
573
|
+
const plan = Array.isArray(raw?.plan) ? raw.plan : [];
|
|
574
|
+
this.plan = plan
|
|
575
|
+
.map((entry, index) => {
|
|
576
|
+
const step = asRecord(entry);
|
|
577
|
+
const text = firstString(step ?? {}, ["text", "title", "description", "message"]);
|
|
578
|
+
if (!text) return undefined;
|
|
579
|
+
return {
|
|
580
|
+
id: firstString(step ?? {}, ["id"]) ?? `plan-${index + 1}`,
|
|
581
|
+
text,
|
|
582
|
+
status: normalizePlanStatus(step?.status),
|
|
583
|
+
} satisfies AgentPlanStep;
|
|
584
|
+
})
|
|
585
|
+
.filter((step): step is AgentPlanStep => Boolean(step));
|
|
586
|
+
if (this.plan.length > 0) {
|
|
587
|
+
this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private handlePlanDelta(params: unknown): void {
|
|
592
|
+
const raw = asRecord(params);
|
|
593
|
+
if (!raw) return;
|
|
594
|
+
const itemId = firstString(raw, ["itemId", "id"]) ?? id("plan");
|
|
595
|
+
const delta = firstString(raw, ["delta", "text"]);
|
|
596
|
+
if (!delta) return;
|
|
597
|
+
const text = `${this.planDeltaBuffers.get(itemId) ?? ""}${delta}`;
|
|
598
|
+
this.planDeltaBuffers.set(itemId, text);
|
|
599
|
+
const existing = this.plan.findIndex((step) => step.id === itemId);
|
|
600
|
+
const step: AgentPlanStep = { id: itemId, text, status: "in_progress" };
|
|
601
|
+
if (existing >= 0) this.plan[existing] = step;
|
|
602
|
+
else this.plan.push(step);
|
|
603
|
+
this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private handleItemStarted(params: unknown): void {
|
|
607
|
+
const item = extractItem(params);
|
|
608
|
+
if (!item) return;
|
|
609
|
+
const itemType = firstString(item, ["type"]);
|
|
610
|
+
|
|
611
|
+
if (itemType === "agentMessage" || itemType === "assistantMessage") {
|
|
612
|
+
this.handleCompletedMessageItem(item, true);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (itemType === "plan") {
|
|
617
|
+
const planStep = planStepFromItem(item);
|
|
618
|
+
if (planStep) {
|
|
619
|
+
const existing = this.plan.findIndex((step) => step.id === planStep.id);
|
|
620
|
+
if (existing >= 0) this.plan[existing] = planStep;
|
|
621
|
+
else this.plan.push(planStep);
|
|
622
|
+
this.sendUpdate({ kind: "plan", plan: this.plan, status: "running" });
|
|
623
|
+
}
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const toolCall = this.toolCallFromItem(item, "running");
|
|
628
|
+
if (!toolCall) return;
|
|
629
|
+
this.toolCalls.set(toolCall.id, toolCall);
|
|
630
|
+
this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private handleItemCompleted(params: unknown): void {
|
|
634
|
+
const item = extractItem(params);
|
|
635
|
+
if (!item) return;
|
|
636
|
+
const itemType = firstString(item, ["type"]);
|
|
637
|
+
|
|
638
|
+
if (itemType === "agentMessage" || itemType === "assistantMessage") {
|
|
639
|
+
this.handleCompletedMessageItem(item, false);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (itemType === "plan") {
|
|
644
|
+
const planStep = planStepFromItem(item);
|
|
645
|
+
if (planStep) {
|
|
646
|
+
const existing = this.plan.findIndex((step) => step.id === planStep.id);
|
|
647
|
+
const completed = { ...planStep, status: "completed" as const };
|
|
648
|
+
if (existing >= 0) this.plan[existing] = completed;
|
|
649
|
+
else this.plan.push(completed);
|
|
650
|
+
this.sendUpdate({ kind: "plan", plan: this.plan, status: this.status === "running" ? "running" : "idle" });
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const toolCall = this.toolCallFromItem(item, normalizeToolStatus(item.status, true));
|
|
656
|
+
if (!toolCall) return;
|
|
657
|
+
const bufferedOutput = this.toolOutputBuffers.get(toolCall.id);
|
|
658
|
+
if (bufferedOutput && !toolCall.output) toolCall.output = bufferedOutput;
|
|
659
|
+
this.toolCalls.set(toolCall.id, toolCall);
|
|
660
|
+
this.sendUpdate({ kind: "tool_result", toolCall, status: this.status === "running" ? "running" : "idle" });
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private handleToolDelta(method: string, params: unknown): void {
|
|
664
|
+
const raw = asRecord(params);
|
|
665
|
+
if (!raw) return;
|
|
666
|
+
const itemId = firstString(raw, ["itemId", "id", "toolCallId"]) ?? id("tool");
|
|
667
|
+
const delta = firstString(raw, ["delta", "message", "text"]);
|
|
668
|
+
if (!delta) return;
|
|
669
|
+
const output = appendCapped(this.toolOutputBuffers.get(itemId), delta, 6000);
|
|
670
|
+
this.toolOutputBuffers.set(itemId, output);
|
|
671
|
+
const existing = this.toolCalls.get(itemId);
|
|
672
|
+
const toolCall: AgentToolCall = {
|
|
673
|
+
id: itemId,
|
|
674
|
+
name: existing?.name ?? nameFromToolMethod(method),
|
|
675
|
+
input: existing?.input,
|
|
676
|
+
output,
|
|
677
|
+
status: "running",
|
|
678
|
+
};
|
|
679
|
+
this.toolCalls.set(itemId, toolCall);
|
|
680
|
+
this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private handleFilePatchUpdated(params: unknown): void {
|
|
684
|
+
const raw = asRecord(params);
|
|
685
|
+
if (!raw) return;
|
|
686
|
+
const itemId = firstString(raw, ["itemId", "id"]) ?? id("file");
|
|
687
|
+
const changes = Array.isArray(raw.changes) ? raw.changes : [];
|
|
688
|
+
const output = summarizeFileChanges(changes);
|
|
689
|
+
const existing = this.toolCalls.get(itemId);
|
|
690
|
+
const toolCall: AgentToolCall = {
|
|
691
|
+
id: itemId,
|
|
692
|
+
name: existing?.name ?? "文件修改",
|
|
693
|
+
input: existing?.input,
|
|
694
|
+
output: output || existing?.output,
|
|
695
|
+
status: existing?.status ?? "running",
|
|
696
|
+
};
|
|
697
|
+
this.toolCalls.set(itemId, toolCall);
|
|
698
|
+
this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private handleCommandExecDelta(params: unknown): void {
|
|
702
|
+
const raw = asRecord(params);
|
|
703
|
+
if (!raw) return;
|
|
704
|
+
const processId = firstString(raw, ["processId", "id"]) ?? id("exec");
|
|
705
|
+
const delta =
|
|
706
|
+
firstString(raw, ["delta", "text"]) ??
|
|
707
|
+
decodeBase64(firstString(raw, ["deltaBase64"]));
|
|
708
|
+
if (!delta) return;
|
|
709
|
+
const output = appendCapped(this.toolOutputBuffers.get(processId), delta, 6000);
|
|
710
|
+
this.toolOutputBuffers.set(processId, output);
|
|
711
|
+
const existing = this.toolCalls.get(processId);
|
|
712
|
+
const toolCall: AgentToolCall = {
|
|
713
|
+
id: processId,
|
|
714
|
+
name: existing?.name ?? "命令输出",
|
|
715
|
+
input: existing?.input,
|
|
716
|
+
output,
|
|
717
|
+
status: "running",
|
|
718
|
+
};
|
|
719
|
+
this.toolCalls.set(processId, toolCall);
|
|
720
|
+
this.sendUpdate({ kind: "tool_call", toolCall, status: "running" });
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private handleCompletedMessageItem(item: Record<string, unknown>, streaming: boolean): void {
|
|
724
|
+
const itemId = firstString(item, ["id"]) ?? id("msg");
|
|
725
|
+
const existing = this.messages.find((message) => message.id === itemId);
|
|
726
|
+
const content = firstString(item, ["text", "content", "message"]) ?? existing?.content;
|
|
727
|
+
if (!content) return;
|
|
728
|
+
const message: AgentMessage = {
|
|
729
|
+
id: itemId,
|
|
730
|
+
role: "assistant",
|
|
731
|
+
content,
|
|
732
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
733
|
+
isStreaming: streaming,
|
|
734
|
+
};
|
|
735
|
+
this.upsertMessage(message);
|
|
736
|
+
this.sendUpdate({
|
|
737
|
+
kind: streaming ? "message_delta" : "message",
|
|
738
|
+
message,
|
|
739
|
+
status: this.status === "running" ? "running" : "idle",
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
private toolCallFromItem(
|
|
744
|
+
item: Record<string, unknown>,
|
|
745
|
+
fallbackStatus: AgentToolCall["status"],
|
|
746
|
+
): AgentToolCall | undefined {
|
|
747
|
+
const itemId = firstString(item, ["id", "itemId", "toolCallId"]);
|
|
748
|
+
if (!itemId) return undefined;
|
|
749
|
+
const itemType = firstString(item, ["type"]);
|
|
750
|
+
const name = toolNameFromItem(item);
|
|
751
|
+
const output =
|
|
752
|
+
firstString(item, ["aggregatedOutput", "output", "stdout", "stderr"]) ??
|
|
753
|
+
stringifyDefined(item.result ?? item.error ?? item.contentItems);
|
|
754
|
+
const bufferedOutput = this.toolOutputBuffers.get(itemId);
|
|
755
|
+
return {
|
|
756
|
+
id: itemId,
|
|
757
|
+
name: name ?? itemType ?? "tool",
|
|
758
|
+
input: toolInputFromItem(item),
|
|
759
|
+
output: output ?? bufferedOutput,
|
|
760
|
+
status: normalizeToolStatus(item.status, fallbackStatus === "completed"),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
389
764
|
private handleUpdate(params: unknown): void {
|
|
390
765
|
const raw = typeof params === "object" && params ? params as Record<string, unknown> : {};
|
|
391
766
|
const nested = typeof raw.params === "object" && raw.params ? raw.params as Record<string, unknown> : {};
|
|
392
767
|
const text =
|
|
393
768
|
firstString(raw, ["delta", "text", "content", "message"]) ??
|
|
394
769
|
firstString(nested, ["delta", "text", "content", "message"]) ??
|
|
395
|
-
|
|
770
|
+
undefined;
|
|
771
|
+
if (!text) return;
|
|
396
772
|
const role = raw.role === "user" || raw.role === "system" ? raw.role : "assistant";
|
|
397
773
|
|
|
398
774
|
if (firstString(raw, ["toolName", "tool", "name"])) {
|
|
@@ -417,8 +793,7 @@ export class AgentSessionProxy {
|
|
|
417
793
|
createdAt: Date.now(),
|
|
418
794
|
isStreaming: raw.done === false || raw.isStreaming === true,
|
|
419
795
|
};
|
|
420
|
-
this.
|
|
421
|
-
if (this.messages.length > 100) this.messages.shift();
|
|
796
|
+
this.upsertMessage(message);
|
|
422
797
|
this.status = raw.done === true ? "idle" : "running";
|
|
423
798
|
this.sendUpdate({
|
|
424
799
|
kind: "message",
|
|
@@ -449,6 +824,13 @@ export class AgentSessionProxy {
|
|
|
449
824
|
this.permissionWaiters.clear();
|
|
450
825
|
}
|
|
451
826
|
|
|
827
|
+
private upsertMessage(message: AgentMessage): void {
|
|
828
|
+
const existing = this.messages.findIndex((entry) => entry.id === message.id);
|
|
829
|
+
if (existing >= 0) this.messages[existing] = message;
|
|
830
|
+
else this.messages.push(message);
|
|
831
|
+
if (this.messages.length > 100) this.messages.shift();
|
|
832
|
+
}
|
|
833
|
+
|
|
452
834
|
private sendCapabilities(): void {
|
|
453
835
|
const enabled = Boolean(this.client && this.initialized && !this.error);
|
|
454
836
|
this.input.send(createEnvelope({
|
|
@@ -464,7 +846,7 @@ export class AgentSessionProxy {
|
|
|
464
846
|
supportsImages: false,
|
|
465
847
|
supportsAudio: false,
|
|
466
848
|
supportsPermission: enabled,
|
|
467
|
-
supportsPlan:
|
|
849
|
+
supportsPlan: enabled,
|
|
468
850
|
supportsCancel: enabled,
|
|
469
851
|
},
|
|
470
852
|
}));
|
|
@@ -490,6 +872,7 @@ export class AgentSessionProxy {
|
|
|
490
872
|
message?: AgentMessage;
|
|
491
873
|
delta?: string;
|
|
492
874
|
toolCall?: AgentToolCall;
|
|
875
|
+
plan?: AgentPlanStep[];
|
|
493
876
|
status?: "idle" | "running" | "waiting_permission" | "error";
|
|
494
877
|
error?: string;
|
|
495
878
|
}): void {
|