wave-code 0.17.5 → 0.17.7
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/acp/agent.d.ts +3 -0
- package/dist/acp/agent.d.ts.map +1 -1
- package/dist/acp/agent.js +307 -172
- package/dist/components/BtwDisplay.d.ts.map +1 -1
- package/dist/components/BtwDisplay.js +2 -2
- package/dist/components/InputBox.js +14 -9
- package/dist/components/StatusLine.d.ts +0 -1
- package/dist/components/StatusLine.d.ts.map +1 -1
- package/dist/components/StatusLine.js +2 -2
- package/dist/constants/commands.d.ts.map +1 -1
- package/dist/constants/commands.js +18 -0
- package/dist/contexts/useChat.d.ts +3 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +21 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +13 -38
- package/dist/managers/inputHandlers.d.ts.map +1 -1
- package/dist/managers/inputHandlers.js +22 -40
- package/dist/managers/inputReducer.d.ts +4 -1
- package/dist/managers/inputReducer.d.ts.map +1 -1
- package/dist/managers/inputReducer.js +21 -34
- package/package.json +2 -2
- package/src/acp/agent.ts +418 -201
- package/src/components/BtwDisplay.tsx +4 -1
- package/src/components/InputBox.tsx +46 -46
- package/src/components/StatusLine.tsx +1 -7
- package/src/constants/commands.ts +18 -0
- package/src/contexts/useChat.tsx +27 -0
- package/src/hooks/useInputManager.ts +13 -38
- package/src/managers/inputHandlers.ts +20 -49
- package/src/managers/inputReducer.ts +27 -40
package/src/acp/agent.ts
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
ASK_USER_QUESTION_TOOL_NAME,
|
|
18
18
|
AskUserQuestion,
|
|
19
19
|
AskUserQuestionOption,
|
|
20
|
-
type Message,
|
|
21
20
|
type TextBlock,
|
|
22
21
|
type ReasoningBlock,
|
|
23
22
|
type ToolBlock,
|
|
23
|
+
type CompactBlock,
|
|
24
24
|
} from "wave-agent-sdk";
|
|
25
25
|
import { logger } from "../utils/logger.js";
|
|
26
26
|
import {
|
|
@@ -58,9 +58,31 @@ import {
|
|
|
58
58
|
} from "@agentclientprotocol/sdk";
|
|
59
59
|
import type { McpServerConfig, McpServerStatus } from "wave-agent-sdk";
|
|
60
60
|
|
|
61
|
+
interface WaveAskQuestionRequest {
|
|
62
|
+
toolCallId: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
questions: Array<{
|
|
65
|
+
id: string;
|
|
66
|
+
prompt: string;
|
|
67
|
+
options: Array<{ id: string; label: string; description?: string }>;
|
|
68
|
+
allowMultiple?: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface WaveCreatePlanRequest {
|
|
73
|
+
toolCallId: string;
|
|
74
|
+
plan: string;
|
|
75
|
+
todos?: Array<{
|
|
76
|
+
id: string;
|
|
77
|
+
content: string;
|
|
78
|
+
status: "pending" | "in_progress" | "completed";
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
export class WaveAcpAgent implements AcpAgent {
|
|
62
83
|
private agents: Map<string, WaveAgent> = new Map();
|
|
63
84
|
private connection: AgentSideConnection;
|
|
85
|
+
private taskCache = new Map<string, Task[]>();
|
|
64
86
|
|
|
65
87
|
constructor(connection: AgentSideConnection) {
|
|
66
88
|
this.connection = connection;
|
|
@@ -217,6 +239,8 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
217
239
|
onUserMessageAdded: (params) => callbacks.onUserMessageAdded?.(params),
|
|
218
240
|
onMcpServersChange: (servers) =>
|
|
219
241
|
callbacks.onMcpServersChange?.(servers),
|
|
242
|
+
onSessionIdChange: (newSessionId: string) =>
|
|
243
|
+
callbacks.onSessionIdChange?.(newSessionId),
|
|
220
244
|
},
|
|
221
245
|
});
|
|
222
246
|
|
|
@@ -225,7 +249,8 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
225
249
|
this.agents.set(actualSessionId, agent);
|
|
226
250
|
|
|
227
251
|
// Update the callbacks object with the correct sessionId
|
|
228
|
-
|
|
252
|
+
const { callbacks: cb } = this.createCallbacks(actualSessionId);
|
|
253
|
+
Object.assign(callbacks, cb);
|
|
229
254
|
|
|
230
255
|
// Send initial available commands after agent creation
|
|
231
256
|
// Use setImmediate to ensure the client receives the session response before the update
|
|
@@ -458,6 +483,133 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
458
483
|
return "Allow Always";
|
|
459
484
|
}
|
|
460
485
|
|
|
486
|
+
private async handleAskQuestion(
|
|
487
|
+
sessionId: string,
|
|
488
|
+
toolCallId: string,
|
|
489
|
+
context: ToolPermissionContext,
|
|
490
|
+
): Promise<PermissionDecision | null> {
|
|
491
|
+
try {
|
|
492
|
+
const questions =
|
|
493
|
+
(context.toolInput?.questions as Array<{
|
|
494
|
+
question: string;
|
|
495
|
+
header?: string;
|
|
496
|
+
options: Array<{ label: string; description?: string }>;
|
|
497
|
+
multiSelect?: boolean;
|
|
498
|
+
}>) || [];
|
|
499
|
+
|
|
500
|
+
const request: WaveAskQuestionRequest = {
|
|
501
|
+
toolCallId,
|
|
502
|
+
title: questions.length === 1 ? questions[0].header : undefined,
|
|
503
|
+
questions: questions.map((q, qi) => ({
|
|
504
|
+
id: `q${qi}`,
|
|
505
|
+
prompt: q.question,
|
|
506
|
+
options: q.options.map((opt, oi) => ({
|
|
507
|
+
id: String(oi),
|
|
508
|
+
label: opt.label,
|
|
509
|
+
description: opt.description,
|
|
510
|
+
})),
|
|
511
|
+
allowMultiple: q.multiSelect,
|
|
512
|
+
})),
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const response = await this.connection.extMethod(
|
|
516
|
+
"wave/ask_question",
|
|
517
|
+
request as unknown as Record<string, unknown>,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const outcome = (response as { outcome?: string }).outcome;
|
|
521
|
+
if (outcome === "cancelled") {
|
|
522
|
+
return { behavior: "deny", message: "Cancelled by user" };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// outcome === "answered"
|
|
526
|
+
const answers =
|
|
527
|
+
(
|
|
528
|
+
response as {
|
|
529
|
+
answers?: Array<{
|
|
530
|
+
questionId: string;
|
|
531
|
+
selectedOptionIds: string[];
|
|
532
|
+
}>;
|
|
533
|
+
}
|
|
534
|
+
).answers || [];
|
|
535
|
+
const answerMap: Record<string, string> = {};
|
|
536
|
+
for (const answer of answers) {
|
|
537
|
+
const qIndex = parseInt(answer.questionId.replace("q", ""), 10);
|
|
538
|
+
const question = questions[qIndex];
|
|
539
|
+
if (!question) continue;
|
|
540
|
+
const selectedLabels = answer.selectedOptionIds
|
|
541
|
+
.map((id) => question.options[parseInt(id, 10)]?.label)
|
|
542
|
+
.filter(Boolean);
|
|
543
|
+
answerMap[question.question] = selectedLabels.join(", ");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return { behavior: "allow", message: JSON.stringify(answerMap) };
|
|
547
|
+
} catch (error) {
|
|
548
|
+
logger.warn(
|
|
549
|
+
"wave/ask_question extMethod failed, falling back to requestPermission",
|
|
550
|
+
{ error },
|
|
551
|
+
);
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private async handleCreatePlan(
|
|
557
|
+
sessionId: string,
|
|
558
|
+
toolCallId: string,
|
|
559
|
+
context: ToolPermissionContext,
|
|
560
|
+
): Promise<PermissionDecision | null> {
|
|
561
|
+
try {
|
|
562
|
+
const cachedTasks = this.taskCache.get(sessionId) || [];
|
|
563
|
+
const request: WaveCreatePlanRequest = {
|
|
564
|
+
toolCallId,
|
|
565
|
+
plan: context.planContent || "",
|
|
566
|
+
todos: cachedTasks
|
|
567
|
+
.filter((t) => t.status !== "deleted")
|
|
568
|
+
.map((t) => ({
|
|
569
|
+
id: t.id,
|
|
570
|
+
content: t.subject,
|
|
571
|
+
status:
|
|
572
|
+
t.status === "completed"
|
|
573
|
+
? ("completed" as const)
|
|
574
|
+
: t.status === "in_progress"
|
|
575
|
+
? ("in_progress" as const)
|
|
576
|
+
: ("pending" as const),
|
|
577
|
+
})),
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const response = await this.connection.extMethod(
|
|
581
|
+
"wave/create_plan",
|
|
582
|
+
request as unknown as Record<string, unknown>,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
const outcome = (response as { outcome?: string }).outcome;
|
|
586
|
+
if (outcome === "accepted") {
|
|
587
|
+
const mode = (response as { mode?: string }).mode;
|
|
588
|
+
return {
|
|
589
|
+
behavior: "allow",
|
|
590
|
+
newPermissionMode: (mode || "default") as
|
|
591
|
+
| "default"
|
|
592
|
+
| "acceptEdits"
|
|
593
|
+
| "plan"
|
|
594
|
+
| "bypassPermissions"
|
|
595
|
+
| "dontAsk",
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (outcome === "rejected") {
|
|
599
|
+
const reason = (response as { reason?: string }).reason;
|
|
600
|
+
return { behavior: "deny", message: reason || "Plan rejected" };
|
|
601
|
+
}
|
|
602
|
+
// cancelled or unknown outcome
|
|
603
|
+
return { behavior: "deny", message: "Cancelled by user" };
|
|
604
|
+
} catch (error) {
|
|
605
|
+
logger.warn(
|
|
606
|
+
"wave/create_plan extMethod failed, falling back to requestPermission",
|
|
607
|
+
{ error },
|
|
608
|
+
);
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
461
613
|
private async handlePermissionRequest(
|
|
462
614
|
sessionId: string,
|
|
463
615
|
context: ToolPermissionContext,
|
|
@@ -573,6 +725,27 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
573
725
|
? this.getToolKind(context.toolName)
|
|
574
726
|
: undefined;
|
|
575
727
|
|
|
728
|
+
// Try extension methods first, fall back to requestPermission
|
|
729
|
+
if (context.toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
730
|
+
const extResult = await this.handleAskQuestion(
|
|
731
|
+
sessionId,
|
|
732
|
+
toolCallId,
|
|
733
|
+
context,
|
|
734
|
+
);
|
|
735
|
+
if (extResult !== null) return extResult;
|
|
736
|
+
// Fall through to existing requestPermission logic
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
|
|
740
|
+
const extResult = await this.handleCreatePlan(
|
|
741
|
+
sessionId,
|
|
742
|
+
toolCallId,
|
|
743
|
+
context,
|
|
744
|
+
);
|
|
745
|
+
if (extResult !== null) return extResult;
|
|
746
|
+
// Fall through to existing requestPermission logic
|
|
747
|
+
}
|
|
748
|
+
|
|
576
749
|
try {
|
|
577
750
|
const response = await this.connection.requestPermission({
|
|
578
751
|
sessionId: sessionId as AcpSessionId,
|
|
@@ -798,14 +971,7 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
798
971
|
private async replayConversationHistory(agent: WaveAgent): Promise<void> {
|
|
799
972
|
const sessionId = agent.sessionId as AcpSessionId;
|
|
800
973
|
|
|
801
|
-
|
|
802
|
-
try {
|
|
803
|
-
const thread = await agent.getFullMessageThread();
|
|
804
|
-
history = thread.messages;
|
|
805
|
-
} catch {
|
|
806
|
-
// Fallback to in-memory messages if full thread fails
|
|
807
|
-
history = agent.messages;
|
|
808
|
-
}
|
|
974
|
+
const history = agent.messages;
|
|
809
975
|
|
|
810
976
|
for (const message of history) {
|
|
811
977
|
if (message.isMeta) continue;
|
|
@@ -918,14 +1084,28 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
918
1084
|
rawInput: parsedParameters,
|
|
919
1085
|
},
|
|
920
1086
|
});
|
|
1087
|
+
} else if (block.type === "compact") {
|
|
1088
|
+
const compactBlock = block as CompactBlock;
|
|
1089
|
+
this.connection.sessionUpdate({
|
|
1090
|
+
sessionId,
|
|
1091
|
+
update: {
|
|
1092
|
+
sessionUpdate: "agent_message_chunk",
|
|
1093
|
+
content: { type: "text", text: compactBlock.content },
|
|
1094
|
+
messageId,
|
|
1095
|
+
},
|
|
1096
|
+
});
|
|
921
1097
|
}
|
|
922
|
-
// Skip: image, bang,
|
|
1098
|
+
// Skip: image, bang, error, file_history, task_notification blocks
|
|
923
1099
|
}
|
|
924
1100
|
}
|
|
925
1101
|
}
|
|
926
1102
|
|
|
927
|
-
private createCallbacks(sessionId: string):
|
|
928
|
-
|
|
1103
|
+
private createCallbacks(sessionId: string): {
|
|
1104
|
+
callbacks: AgentOptions["callbacks"];
|
|
1105
|
+
sessionRef: { id: string };
|
|
1106
|
+
} {
|
|
1107
|
+
const sessionRef = { id: sessionId };
|
|
1108
|
+
const getAgent = () => this.agents.get(sessionRef.id);
|
|
929
1109
|
const toolStates = new Map<
|
|
930
1110
|
string,
|
|
931
1111
|
{
|
|
@@ -936,225 +1116,262 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
936
1116
|
}
|
|
937
1117
|
>();
|
|
938
1118
|
return {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1119
|
+
callbacks: {
|
|
1120
|
+
onAssistantContentUpdated: (chunk: string) => {
|
|
1121
|
+
this.connection.sessionUpdate({
|
|
1122
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1123
|
+
update: {
|
|
1124
|
+
sessionUpdate: "agent_message_chunk",
|
|
1125
|
+
content: {
|
|
1126
|
+
type: "text",
|
|
1127
|
+
text: chunk,
|
|
1128
|
+
},
|
|
947
1129
|
},
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1130
|
+
});
|
|
1131
|
+
},
|
|
1132
|
+
onAssistantReasoningUpdated: (chunk: string) => {
|
|
1133
|
+
this.connection.sessionUpdate({
|
|
1134
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1135
|
+
update: {
|
|
1136
|
+
sessionUpdate: "agent_thought_chunk",
|
|
1137
|
+
content: {
|
|
1138
|
+
type: "text",
|
|
1139
|
+
text: chunk,
|
|
1140
|
+
},
|
|
959
1141
|
},
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
toolStates.set(id, state);
|
|
981
|
-
}
|
|
982
|
-
if (name) state.name = name;
|
|
983
|
-
if (compactParams) state.compactParams = compactParams;
|
|
984
|
-
if (shortResult) state.shortResult = shortResult;
|
|
985
|
-
if (startLineNumber !== undefined)
|
|
986
|
-
state.startLineNumber = startLineNumber;
|
|
987
|
-
|
|
988
|
-
const effectiveName = state.name || name;
|
|
989
|
-
const effectiveCompactParams = state.compactParams || compactParams;
|
|
990
|
-
const effectiveShortResult = state.shortResult || shortResult;
|
|
991
|
-
const effectiveStartLineNumber =
|
|
992
|
-
state.startLineNumber !== undefined
|
|
993
|
-
? state.startLineNumber
|
|
994
|
-
: startLineNumber;
|
|
995
|
-
|
|
996
|
-
const displayTitle =
|
|
997
|
-
effectiveName && effectiveCompactParams
|
|
998
|
-
? `${effectiveName}: ${effectiveCompactParams}`
|
|
999
|
-
: effectiveName || "Tool Call";
|
|
1000
|
-
|
|
1001
|
-
let parsedParameters: Record<string, unknown> | undefined = undefined;
|
|
1002
|
-
if (parameters) {
|
|
1003
|
-
try {
|
|
1004
|
-
const parsed = JSON.parse(parameters);
|
|
1005
|
-
parsedParameters = Array.isArray(parsed)
|
|
1006
|
-
? { args: parsed }
|
|
1007
|
-
: parsed;
|
|
1008
|
-
} catch {
|
|
1009
|
-
// Ignore parse errors during streaming
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1144
|
+
onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
|
|
1145
|
+
const {
|
|
1146
|
+
id,
|
|
1147
|
+
name,
|
|
1148
|
+
stage,
|
|
1149
|
+
success,
|
|
1150
|
+
error,
|
|
1151
|
+
result,
|
|
1152
|
+
parameters,
|
|
1153
|
+
compactParams,
|
|
1154
|
+
shortResult,
|
|
1155
|
+
startLineNumber,
|
|
1156
|
+
} = params;
|
|
1157
|
+
|
|
1158
|
+
let state = toolStates.get(id);
|
|
1159
|
+
if (!state) {
|
|
1160
|
+
state = {};
|
|
1161
|
+
toolStates.set(id, state);
|
|
1010
1162
|
}
|
|
1011
|
-
|
|
1163
|
+
if (name) state.name = name;
|
|
1164
|
+
if (compactParams) state.compactParams = compactParams;
|
|
1165
|
+
if (shortResult) state.shortResult = shortResult;
|
|
1166
|
+
if (startLineNumber !== undefined)
|
|
1167
|
+
state.startLineNumber = startLineNumber;
|
|
1168
|
+
|
|
1169
|
+
const effectiveName = state.name || name;
|
|
1170
|
+
const effectiveCompactParams = state.compactParams || compactParams;
|
|
1171
|
+
const effectiveShortResult = state.shortResult || shortResult;
|
|
1172
|
+
const effectiveStartLineNumber =
|
|
1173
|
+
state.startLineNumber !== undefined
|
|
1174
|
+
? state.startLineNumber
|
|
1175
|
+
: startLineNumber;
|
|
1012
1176
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1177
|
+
const displayTitle =
|
|
1178
|
+
effectiveName && effectiveCompactParams
|
|
1179
|
+
? `${effectiveName}: ${effectiveCompactParams}`
|
|
1180
|
+
: effectiveName || "Tool Call";
|
|
1181
|
+
|
|
1182
|
+
let parsedParameters: Record<string, unknown> | undefined = undefined;
|
|
1183
|
+
if (parameters) {
|
|
1184
|
+
try {
|
|
1185
|
+
const parsed = JSON.parse(parameters);
|
|
1186
|
+
parsedParameters = Array.isArray(parsed)
|
|
1187
|
+
? { args: parsed }
|
|
1188
|
+
: parsed;
|
|
1189
|
+
} catch {
|
|
1190
|
+
// Ignore parse errors during streaming
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const content =
|
|
1195
|
+
effectiveName && (parsedParameters || effectiveShortResult)
|
|
1196
|
+
? this.getToolContent(
|
|
1197
|
+
effectiveName,
|
|
1198
|
+
parsedParameters,
|
|
1199
|
+
effectiveShortResult,
|
|
1200
|
+
)
|
|
1201
|
+
: undefined;
|
|
1202
|
+
const locations =
|
|
1203
|
+
effectiveName && parsedParameters
|
|
1204
|
+
? this.getToolLocations(
|
|
1205
|
+
effectiveName,
|
|
1206
|
+
parsedParameters,
|
|
1207
|
+
effectiveStartLineNumber,
|
|
1208
|
+
)
|
|
1209
|
+
: undefined;
|
|
1210
|
+
const kind = effectiveName
|
|
1211
|
+
? this.getToolKind(effectiveName)
|
|
1028
1212
|
: undefined;
|
|
1029
|
-
const kind = effectiveName
|
|
1030
|
-
? this.getToolKind(effectiveName)
|
|
1031
|
-
: undefined;
|
|
1032
1213
|
|
|
1033
|
-
|
|
1214
|
+
if (stage === "start") {
|
|
1215
|
+
this.connection.sessionUpdate({
|
|
1216
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1217
|
+
update: {
|
|
1218
|
+
sessionUpdate: "tool_call",
|
|
1219
|
+
toolCallId: id,
|
|
1220
|
+
title: displayTitle,
|
|
1221
|
+
status: "pending",
|
|
1222
|
+
content,
|
|
1223
|
+
locations,
|
|
1224
|
+
kind,
|
|
1225
|
+
rawInput: parsedParameters,
|
|
1226
|
+
},
|
|
1227
|
+
});
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (stage === "streaming") {
|
|
1232
|
+
// We don't support streaming tool arguments in ACP yet
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const status: ToolCallStatus =
|
|
1237
|
+
stage === "end"
|
|
1238
|
+
? success
|
|
1239
|
+
? "completed"
|
|
1240
|
+
: "failed"
|
|
1241
|
+
: stage === "running"
|
|
1242
|
+
? "in_progress"
|
|
1243
|
+
: "pending";
|
|
1244
|
+
|
|
1034
1245
|
this.connection.sessionUpdate({
|
|
1035
|
-
sessionId:
|
|
1246
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1036
1247
|
update: {
|
|
1037
|
-
sessionUpdate: "
|
|
1248
|
+
sessionUpdate: "tool_call_update",
|
|
1038
1249
|
toolCallId: id,
|
|
1250
|
+
status,
|
|
1039
1251
|
title: displayTitle,
|
|
1040
|
-
|
|
1252
|
+
rawOutput: result || error,
|
|
1041
1253
|
content,
|
|
1042
1254
|
locations,
|
|
1043
1255
|
kind,
|
|
1044
1256
|
rawInput: parsedParameters,
|
|
1045
1257
|
},
|
|
1046
1258
|
});
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
if (stage === "streaming") {
|
|
1051
|
-
// We don't support streaming tool arguments in ACP yet
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const status: ToolCallStatus =
|
|
1056
|
-
stage === "end"
|
|
1057
|
-
? success
|
|
1058
|
-
? "completed"
|
|
1059
|
-
: "failed"
|
|
1060
|
-
: stage === "running"
|
|
1061
|
-
? "in_progress"
|
|
1062
|
-
: "pending";
|
|
1063
|
-
|
|
1064
|
-
this.connection.sessionUpdate({
|
|
1065
|
-
sessionId: sessionId as AcpSessionId,
|
|
1066
|
-
update: {
|
|
1067
|
-
sessionUpdate: "tool_call_update",
|
|
1068
|
-
toolCallId: id,
|
|
1069
|
-
status,
|
|
1070
|
-
title: displayTitle,
|
|
1071
|
-
rawOutput: result || error,
|
|
1072
|
-
content,
|
|
1073
|
-
locations,
|
|
1074
|
-
kind,
|
|
1075
|
-
rawInput: parsedParameters,
|
|
1076
|
-
},
|
|
1077
|
-
});
|
|
1078
1259
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
sessionId: sessionId as AcpSessionId,
|
|
1086
|
-
update: {
|
|
1087
|
-
sessionUpdate: "plan",
|
|
1088
|
-
entries: tasks.map((task) => ({
|
|
1089
|
-
content: task.subject,
|
|
1090
|
-
status:
|
|
1091
|
-
task.status === "completed"
|
|
1092
|
-
? "completed"
|
|
1093
|
-
: task.status === "in_progress"
|
|
1094
|
-
? "in_progress"
|
|
1095
|
-
: "pending",
|
|
1096
|
-
priority: "medium",
|
|
1097
|
-
})),
|
|
1098
|
-
},
|
|
1099
|
-
});
|
|
1100
|
-
},
|
|
1101
|
-
onPermissionModeChange: (mode) => {
|
|
1102
|
-
this.connection.sessionUpdate({
|
|
1103
|
-
sessionId: sessionId as AcpSessionId,
|
|
1104
|
-
update: {
|
|
1105
|
-
sessionUpdate: "current_mode_update",
|
|
1106
|
-
currentModeId: mode,
|
|
1107
|
-
},
|
|
1108
|
-
});
|
|
1109
|
-
const agent = getAgent();
|
|
1110
|
-
if (agent) {
|
|
1260
|
+
if (stage === "end") {
|
|
1261
|
+
toolStates.delete(id);
|
|
1262
|
+
}
|
|
1263
|
+
},
|
|
1264
|
+
onTasksChange: (tasks) => {
|
|
1265
|
+
this.taskCache.set(sessionRef.id, tasks);
|
|
1111
1266
|
this.connection.sessionUpdate({
|
|
1112
|
-
sessionId:
|
|
1267
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1113
1268
|
update: {
|
|
1114
|
-
sessionUpdate: "
|
|
1115
|
-
|
|
1269
|
+
sessionUpdate: "plan",
|
|
1270
|
+
entries: tasks
|
|
1271
|
+
.filter((t) => t.status !== "deleted")
|
|
1272
|
+
.map((task) => ({
|
|
1273
|
+
content: task.subject,
|
|
1274
|
+
status:
|
|
1275
|
+
task.status === "completed"
|
|
1276
|
+
? "completed"
|
|
1277
|
+
: task.status === "in_progress"
|
|
1278
|
+
? "in_progress"
|
|
1279
|
+
: "pending",
|
|
1280
|
+
priority: "medium" as const,
|
|
1281
|
+
})),
|
|
1116
1282
|
},
|
|
1117
1283
|
});
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
onModelChange: () => {
|
|
1121
|
-
const agent = getAgent();
|
|
1122
|
-
if (agent) {
|
|
1284
|
+
},
|
|
1285
|
+
onPermissionModeChange: (mode) => {
|
|
1123
1286
|
this.connection.sessionUpdate({
|
|
1124
|
-
sessionId:
|
|
1287
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1125
1288
|
update: {
|
|
1126
|
-
sessionUpdate: "
|
|
1127
|
-
|
|
1289
|
+
sessionUpdate: "current_mode_update",
|
|
1290
|
+
currentModeId: mode,
|
|
1128
1291
|
},
|
|
1129
1292
|
});
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1293
|
+
const agent = getAgent();
|
|
1294
|
+
if (agent) {
|
|
1295
|
+
this.connection.sessionUpdate({
|
|
1296
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1297
|
+
update: {
|
|
1298
|
+
sessionUpdate: "config_option_update",
|
|
1299
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
},
|
|
1304
|
+
onModelChange: () => {
|
|
1305
|
+
const agent = getAgent();
|
|
1306
|
+
if (agent) {
|
|
1307
|
+
this.connection.sessionUpdate({
|
|
1308
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1309
|
+
update: {
|
|
1310
|
+
sessionUpdate: "config_option_update",
|
|
1311
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
onUserMessageAdded: (params) => {
|
|
1143
1317
|
this.connection.sessionUpdate({
|
|
1144
|
-
sessionId:
|
|
1318
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1145
1319
|
update: {
|
|
1146
|
-
sessionUpdate: "
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1320
|
+
sessionUpdate: "user_message_chunk",
|
|
1321
|
+
content: { type: "text", text: params.content },
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
},
|
|
1325
|
+
onMcpServersChange: (servers: McpServerStatus[]) => {
|
|
1326
|
+
for (const server of servers) {
|
|
1327
|
+
this.connection.sessionUpdate({
|
|
1328
|
+
sessionId: sessionRef.id as AcpSessionId,
|
|
1329
|
+
update: {
|
|
1330
|
+
sessionUpdate: "ext_notification" as const,
|
|
1331
|
+
method: "mcp_server_status",
|
|
1332
|
+
params: {
|
|
1333
|
+
name: server.name,
|
|
1334
|
+
status: server.status,
|
|
1335
|
+
toolCount: server.toolCount,
|
|
1336
|
+
error: server.error,
|
|
1337
|
+
},
|
|
1153
1338
|
},
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
onSessionIdChange: (newSessionId: string) => {
|
|
1343
|
+
const oldSessionId = sessionRef.id;
|
|
1344
|
+
if (oldSessionId === newSessionId) return;
|
|
1345
|
+
|
|
1346
|
+
// Update agents Map key
|
|
1347
|
+
const agent = this.agents.get(oldSessionId);
|
|
1348
|
+
if (agent) {
|
|
1349
|
+
this.agents.delete(oldSessionId);
|
|
1350
|
+
this.agents.set(newSessionId, agent);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Update taskCache key
|
|
1354
|
+
const tasks = this.taskCache.get(oldSessionId);
|
|
1355
|
+
if (tasks) {
|
|
1356
|
+
this.taskCache.delete(oldSessionId);
|
|
1357
|
+
this.taskCache.set(newSessionId, tasks);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Update ref so all subsequent callbacks use new ID
|
|
1361
|
+
sessionRef.id = newSessionId;
|
|
1362
|
+
|
|
1363
|
+
// Notify ACP client
|
|
1364
|
+
this.connection.sessionUpdate({
|
|
1365
|
+
sessionId: oldSessionId as AcpSessionId,
|
|
1366
|
+
update: {
|
|
1367
|
+
sessionUpdate: "ext_notification" as const,
|
|
1368
|
+
method: "session_id_change",
|
|
1369
|
+
params: { oldSessionId, newSessionId },
|
|
1154
1370
|
},
|
|
1155
1371
|
});
|
|
1156
|
-
}
|
|
1372
|
+
},
|
|
1157
1373
|
},
|
|
1374
|
+
sessionRef,
|
|
1158
1375
|
};
|
|
1159
1376
|
}
|
|
1160
1377
|
}
|