@nextclaw/ui 0.11.20 → 0.11.22
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/CHANGELOG.md +25 -0
- package/dist/assets/{ChannelsList-DAx7wv0_.js → ChannelsList-Zeys_w43.js} +6 -6
- package/dist/assets/ChatPage-DWOU_8P6.js +43 -0
- package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
- package/dist/assets/{DocBrowser-DKkE3Y4I.js → DocBrowser-BmtBLFU0.js} +1 -1
- package/dist/assets/{DocBrowserContext-BcZRBsCg.js → DocBrowserContext-YIKkPb76.js} +1 -1
- package/dist/assets/{LogoBadge-BIPDLEwK.js → LogoBadge-F7ZWdxLT.js} +1 -1
- package/dist/assets/MarketplacePage-BfaTTqN6.js +1 -0
- package/dist/assets/{MarketplacePage-Dlp5BgCh.js → MarketplacePage-Cd4faegU.js} +2 -2
- package/dist/assets/{McpMarketplacePage-CwKtAil8.js → McpMarketplacePage-C09Ngs7O.js} +2 -2
- package/dist/assets/ModelConfig-DJgdcgvQ.js +1 -0
- package/dist/assets/ProvidersList-w0rVFIBf.js +1 -0
- package/dist/assets/RemoteAccessPage-BJ_ckkOV.js +1 -0
- package/dist/assets/RuntimeConfig-Cmn2xPQO.js +1 -0
- package/dist/assets/{SearchConfig-v46R5a2U.js → SearchConfig-BT13qpR_.js} +1 -1
- package/dist/assets/{SecretsConfig-CXvUpbB_.js → SecretsConfig-CvqEVn0B.js} +2 -2
- package/dist/assets/{SessionsConfig-7vUHMtOh.js → SessionsConfig-DHHcYznk.js} +2 -2
- package/dist/assets/{book-open-DzSduAaw.js → book-open-CXoF5nQC.js} +1 -1
- package/dist/assets/chat-session-display-VW6ZMvZP.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-C1vpvW4r.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
- package/dist/assets/{config-Df97LeLR.js → config-DJswxxE8.js} +1 -1
- package/dist/assets/{createLucideIcon-CcR5wVoU.js → createLucideIcon-CjGHOWb6.js} +1 -1
- package/dist/assets/{dist-Dii9v3X9.js → dist-Cl2QB-2y.js} +1 -1
- package/dist/assets/{dist-BMlnBah3.js → dist-nqTTbVdA.js} +1 -1
- package/dist/assets/{external-link-CnSDrvJE.js → external-link-tIO7zING.js} +1 -1
- package/dist/assets/{hash-CAnX6PNt.js → hash-JWUyl1pT.js} +1 -1
- package/dist/assets/i18n-CDHMXlRZ.js +1 -0
- package/dist/assets/index-BlH4-cBw.css +1 -0
- package/dist/assets/{index-B0DzQqwv.js → index-C6d0xmtm.js} +3 -3
- package/dist/assets/{label-CtIFj7_6.js → label-BIpeNu4r.js} +1 -1
- package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
- package/dist/assets/{logos-3KFNiOej.js → logos-DThdM9lk.js} +1 -1
- package/dist/assets/{page-layout-BMwpn87D.js → page-layout-D3Xo605Z.js} +1 -1
- package/dist/assets/plus-PHf8q-Ct.js +1 -0
- package/dist/assets/{popover-BIzq25oH.js → popover-BJRUGA_H.js} +1 -1
- package/dist/assets/provider-models-bz5y28rq.js +1 -0
- package/dist/assets/{react-ji6GGP_j.js → react-7ZHqQtEV.js} +1 -1
- package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
- package/dist/assets/{save-CMgYkJ-y.js → save-DJM5RRWW.js} +1 -1
- package/dist/assets/search-C91yH_6y.js +1 -0
- package/dist/assets/{security-config-Xi5DYW7j.js → security-config-T5zpg16O.js} +1 -1
- package/dist/assets/{select-Cz82gl01.js → select-DSkTc61S.js} +1 -1
- package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
- package/dist/assets/{status-dot-C7q1HvLH.js → status-dot-LNBlDu3q.js} +1 -1
- package/dist/assets/{switch-DYswvkYj.js → switch-Bo-Y46HZ.js} +1 -1
- package/dist/assets/tabs-custom-DXv507_2.js +1 -0
- package/dist/assets/{trash-2-DfXI7-ap.js → trash-2-DFZmW6Gg.js} +1 -1
- package/dist/assets/useConfirmDialog-Bs5Ll17m.js +1 -0
- package/dist/assets/{useMutation-s2sn2yzh.js → useMutation-DrZrOgVL.js} +1 -1
- package/dist/assets/x-D7Q1yqSF.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +6 -6
- package/src/api/ncp-session.test.ts +37 -0
- package/src/api/ncp-session.ts +29 -1
- package/src/api/server-path.ts +23 -0
- package/src/api/types.ts +41 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +43 -7
- package/src/components/chat/ChatConversationPanel.tsx +23 -17
- package/src/components/chat/ChatSidebar.test.tsx +2 -2
- package/src/components/chat/ChatSidebar.tsx +2 -2
- package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
- package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
- package/src/components/chat/adapters/chat-message-part.adapter.ts +81 -6
- package/src/components/chat/adapters/chat-message.adapter.test.ts +393 -3
- package/src/components/chat/adapters/chat-message.partial-json.ts +89 -0
- package/src/components/chat/adapters/file-operation/card.ts +330 -0
- package/src/components/chat/adapters/file-operation/diff.ts +398 -0
- package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
- package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
- package/src/components/chat/chat-composer-state.ts +3 -3
- package/src/components/chat/chat-session-display.test.ts +21 -0
- package/src/components/chat/chat-session-display.ts +6 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +29 -32
- package/src/components/chat/containers/chat-message-list.container.tsx +1 -0
- package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
- package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
- package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
- package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
- package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
- package/src/components/chat/ncp/NcpChatPage.tsx +55 -17
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
- package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +176 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +16 -0
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
- package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
- package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
- package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
- package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
- package/src/components/chat/stores/chat-input.store.ts +6 -3
- package/src/components/chat/stores/chat-thread.store.ts +6 -2
- package/src/components/chat/useNcpAgentRuntime.test.tsx +90 -0
- package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
- package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
- package/src/hooks/server-path/use-server-path-browse.ts +19 -0
- package/src/hooks/useConfig.ts +26 -1
- package/src/lib/i18n/i18n-language-owner.ts +94 -0
- package/src/lib/i18n/i18n.path-picker.ts +12 -0
- package/src/lib/i18n.chat.ts +25 -1
- package/src/lib/i18n.ts +21 -84
- package/src/lib/session-project/session-project.utils.ts +30 -0
- package/src/remote/remote-access-feedback.service.test.ts +18 -0
- package/src/remote/remote-access-feedback.service.ts +10 -1
- package/dist/assets/ChatPage-l2PYwCeB.js +0 -38
- package/dist/assets/DocBrowser-CIHLqoIm.js +0 -1
- package/dist/assets/MarketplacePage-TVeyVOuO.js +0 -1
- package/dist/assets/ModelConfig-Dg6F3Ldb.js +0 -1
- package/dist/assets/ProvidersList-f7bQdRxA.js +0 -1
- package/dist/assets/RemoteAccessPage-w_dY7P4T.js +0 -1
- package/dist/assets/RuntimeConfig-M4OKjmgU.js +0 -1
- package/dist/assets/chat-session-display-CGfXhJoT.js +0 -1
- package/dist/assets/i18n-CXBpwAwA.js +0 -1
- package/dist/assets/index-BahpXJg8.css +0 -1
- package/dist/assets/loader-circle-qgU4zQDw.js +0 -1
- package/dist/assets/plus-C9cYVbL-.js +0 -1
- package/dist/assets/provider-models-C8JQUd1E.js +0 -1
- package/dist/assets/search-sl1OeJFl.js +0 -1
- package/dist/assets/skeleton-rgIt7a5q.js +0 -1
- package/dist/assets/tabs-custom-DKYQxrx1.js +0 -1
- package/dist/assets/useConfirmDialog-CXDAxtRL.js +0 -1
- package/dist/assets/x-MIimOGs6.js +0 -1
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
- /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
- /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
|
@@ -12,6 +12,7 @@ export type ChatThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' |
|
|
|
12
12
|
export type ChatSkillRecord = {
|
|
13
13
|
key: string;
|
|
14
14
|
label: string;
|
|
15
|
+
scopeLabel?: string;
|
|
15
16
|
description?: string;
|
|
16
17
|
descriptionZh?: string;
|
|
17
18
|
badgeLabel?: string;
|
|
@@ -43,6 +44,7 @@ const SLASH_ITEM_MATCH_SCORE = {
|
|
|
43
44
|
export type ChatInputBarAdapterTexts = {
|
|
44
45
|
slashSkillSubtitle: string;
|
|
45
46
|
slashSkillSpecLabel: string;
|
|
47
|
+
slashSkillScopeLabel: string;
|
|
46
48
|
noSkillDescription: string;
|
|
47
49
|
recentSkillsLabel: string;
|
|
48
50
|
allSkillsLabel: string;
|
|
@@ -171,7 +173,7 @@ function prioritizeSkillRecords(skillRecords: ChatSkillRecord[], recentSkillValu
|
|
|
171
173
|
export function buildChatSlashItems(
|
|
172
174
|
skillRecords: ChatSkillRecord[],
|
|
173
175
|
normalizedSlashQuery: string,
|
|
174
|
-
texts: Pick<ChatInputBarAdapterTexts, 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'noSkillDescription'>,
|
|
176
|
+
texts: Pick<ChatInputBarAdapterTexts, 'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'>,
|
|
175
177
|
recentSkillValues: string[] = []
|
|
176
178
|
): ChatSlashItem[] {
|
|
177
179
|
const skillSortCollator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
|
|
@@ -211,7 +213,10 @@ export function buildChatSlashItems(
|
|
|
211
213
|
title: record.label || record.key,
|
|
212
214
|
subtitle: texts.slashSkillSubtitle,
|
|
213
215
|
description: (record.descriptionZh ?? record.description ?? '').trim() || texts.noSkillDescription,
|
|
214
|
-
detailLines: [
|
|
216
|
+
detailLines: [
|
|
217
|
+
`${texts.slashSkillSpecLabel}: ${record.key}`,
|
|
218
|
+
...(record.scopeLabel ? [`${texts.slashSkillScopeLabel}: ${record.scopeLabel}`] : [])
|
|
219
|
+
],
|
|
215
220
|
value: record.key
|
|
216
221
|
}));
|
|
217
222
|
}
|
|
@@ -3,13 +3,12 @@ import {
|
|
|
3
3
|
summarizeToolArgs,
|
|
4
4
|
type ToolCard,
|
|
5
5
|
} from "@/lib/chat-message";
|
|
6
|
-
import {
|
|
7
|
-
type ChatInlineTokenSource,
|
|
8
|
-
} from "@/components/chat/chat-inline-token.utils";
|
|
6
|
+
import { type ChatInlineTokenSource } from "@/components/chat/chat-inline-token.utils";
|
|
9
7
|
import {
|
|
10
8
|
buildRenderableText,
|
|
11
9
|
buildTextPart,
|
|
12
10
|
} from "@/components/chat/adapters/chat-message-inline-content.adapter";
|
|
11
|
+
import { buildFileOperationCardData } from "@/components/chat/adapters/file-operation/card";
|
|
13
12
|
import { buildSubagentToolCard } from "@/components/chat/adapters/chat-message.subagent-tool-card";
|
|
14
13
|
import type {
|
|
15
14
|
ChatMessagePartViewModel,
|
|
@@ -27,6 +26,7 @@ export type ChatMessageAdapterTexts = {
|
|
|
27
26
|
reasoningLabel: string;
|
|
28
27
|
toolCallLabel: string;
|
|
29
28
|
toolResultLabel: string;
|
|
29
|
+
toolInputLabel: string;
|
|
30
30
|
toolNoOutputLabel: string;
|
|
31
31
|
toolOutputLabel: string;
|
|
32
32
|
toolStatusPreparingLabel: string;
|
|
@@ -77,6 +77,8 @@ export type ChatMessagePartSource =
|
|
|
77
77
|
type ToolCardViewSource = ToolCard & {
|
|
78
78
|
statusTone: ChatToolPartViewModel["statusTone"];
|
|
79
79
|
statusLabel: string;
|
|
80
|
+
fileOperation?: ChatToolPartViewModel["fileOperation"];
|
|
81
|
+
outputData?: unknown;
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
type ChatMessagePartAdapterParams = {
|
|
@@ -112,6 +114,23 @@ function readOptionalNumber(value: unknown): number | null {
|
|
|
112
114
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
function isTerminalResultRecord(
|
|
118
|
+
value: unknown,
|
|
119
|
+
): value is Record<string, unknown> {
|
|
120
|
+
if (!isRecord(value)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return (
|
|
124
|
+
"command" in value ||
|
|
125
|
+
"workingDir" in value ||
|
|
126
|
+
"exitCode" in value ||
|
|
127
|
+
"stdout" in value ||
|
|
128
|
+
"stderr" in value ||
|
|
129
|
+
"aggregated_output" in value ||
|
|
130
|
+
"combinedOutput" in value
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
115
134
|
function extractAssetFileView(
|
|
116
135
|
value: unknown,
|
|
117
136
|
texts: ChatMessageAdapterTexts,
|
|
@@ -161,7 +180,13 @@ function buildToolCard(
|
|
|
161
180
|
kind: toolCard.kind,
|
|
162
181
|
toolName: toolCard.name,
|
|
163
182
|
summary: toolCard.detail,
|
|
183
|
+
inputLabel: texts.toolInputLabel,
|
|
184
|
+
input:
|
|
185
|
+
"input" in toolCard && typeof toolCard.input === "string"
|
|
186
|
+
? toolCard.input
|
|
187
|
+
: undefined,
|
|
164
188
|
output: toolCard.text,
|
|
189
|
+
outputData: toolCard.outputData,
|
|
165
190
|
hasResult: Boolean(toolCard.hasResult),
|
|
166
191
|
statusTone: toolCard.statusTone,
|
|
167
192
|
statusLabel: toolCard.statusLabel,
|
|
@@ -169,6 +194,9 @@ function buildToolCard(
|
|
|
169
194
|
toolCard.kind === "call" ? texts.toolCallLabel : texts.toolResultLabel,
|
|
170
195
|
outputLabel: texts.toolOutputLabel,
|
|
171
196
|
emptyLabel: texts.toolNoOutputLabel,
|
|
197
|
+
...("fileOperation" in toolCard && toolCard.fileOperation
|
|
198
|
+
? { fileOperation: toolCard.fileOperation }
|
|
199
|
+
: {}),
|
|
172
200
|
};
|
|
173
201
|
}
|
|
174
202
|
|
|
@@ -216,7 +244,7 @@ function resolveToolCardStatus(params: {
|
|
|
216
244
|
kind: "call",
|
|
217
245
|
hasResult: false,
|
|
218
246
|
statusTone: "running",
|
|
219
|
-
statusLabel: params.texts.
|
|
247
|
+
statusLabel: params.texts.toolStatusRunningLabel,
|
|
220
248
|
};
|
|
221
249
|
}
|
|
222
250
|
return {
|
|
@@ -227,6 +255,30 @@ function resolveToolCardStatus(params: {
|
|
|
227
255
|
};
|
|
228
256
|
}
|
|
229
257
|
|
|
258
|
+
function parseStructuredValue(value: unknown): unknown {
|
|
259
|
+
if (typeof value !== "string") {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
const trimmed = value.trim();
|
|
263
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(trimmed) as unknown;
|
|
268
|
+
} catch {
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function buildToolInvocationInput(
|
|
274
|
+
args?: unknown,
|
|
275
|
+
parsedArgs?: unknown,
|
|
276
|
+
): string | undefined {
|
|
277
|
+
const source = parsedArgs ?? parseStructuredValue(args);
|
|
278
|
+
const text = stringifyUnknown(source).trim();
|
|
279
|
+
return text || undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
230
282
|
function buildReasoningPart(
|
|
231
283
|
part: Extract<ChatMessagePartSource, { type: "reasoning" }>,
|
|
232
284
|
texts: ChatMessageAdapterTexts,
|
|
@@ -296,22 +348,45 @@ function buildToolInvocationPart(
|
|
|
296
348
|
result: invocation.result,
|
|
297
349
|
texts,
|
|
298
350
|
});
|
|
299
|
-
const
|
|
351
|
+
const fileOperationCardData = buildFileOperationCardData({
|
|
352
|
+
toolName: invocation.toolName,
|
|
353
|
+
status: invocation.status,
|
|
354
|
+
toolCallId: invocation.toolCallId,
|
|
355
|
+
args: invocation.args,
|
|
356
|
+
parsedArgs: invocation.parsedArgs,
|
|
357
|
+
result: invocation.result,
|
|
358
|
+
});
|
|
359
|
+
const detail =
|
|
360
|
+
fileOperationCardData?.summary ??
|
|
361
|
+
summarizeToolArgs(invocation.parsedArgs ?? invocation.args);
|
|
362
|
+
const input = fileOperationCardData
|
|
363
|
+
? undefined
|
|
364
|
+
: buildToolInvocationInput(invocation.args, invocation.parsedArgs);
|
|
300
365
|
const rawResult =
|
|
301
366
|
typeof invocation.error === "string" && invocation.error.trim()
|
|
302
367
|
? invocation.error.trim()
|
|
303
368
|
: invocation.result != null
|
|
304
369
|
? stringifyUnknown(invocation.result).trim()
|
|
305
370
|
: "";
|
|
371
|
+
const shouldHideStructuredTerminalJson =
|
|
372
|
+
!invocation.error && isTerminalResultRecord(invocation.result);
|
|
373
|
+
const shouldShowRawResult =
|
|
374
|
+
(!fileOperationCardData?.fileOperation || Boolean(invocation.error)) &&
|
|
375
|
+
!shouldHideStructuredTerminalJson;
|
|
306
376
|
const card: ToolCardViewSource = {
|
|
307
377
|
kind: statusView.kind,
|
|
308
378
|
name: invocation.toolName,
|
|
309
379
|
detail,
|
|
310
|
-
|
|
380
|
+
...(input ? { input } : {}),
|
|
381
|
+
text: shouldShowRawResult && rawResult ? rawResult : undefined,
|
|
382
|
+
outputData: invocation.result,
|
|
311
383
|
callId: invocation.toolCallId || undefined,
|
|
312
384
|
hasResult: statusView.hasResult,
|
|
313
385
|
statusTone: statusView.statusTone,
|
|
314
386
|
statusLabel: statusView.statusLabel,
|
|
387
|
+
...(fileOperationCardData?.fileOperation
|
|
388
|
+
? { fileOperation: fileOperationCardData.fileOperation }
|
|
389
|
+
: {}),
|
|
315
390
|
};
|
|
316
391
|
return {
|
|
317
392
|
type: "tool-card",
|
|
@@ -17,8 +17,9 @@ const defaultTexts = {
|
|
|
17
17
|
reasoningLabel: "Reasoning",
|
|
18
18
|
toolCallLabel: "Tool Call",
|
|
19
19
|
toolResultLabel: "Tool Result",
|
|
20
|
+
toolInputLabel: "Input",
|
|
20
21
|
toolNoOutputLabel: "No output",
|
|
21
|
-
toolOutputLabel: "
|
|
22
|
+
toolOutputLabel: "Output",
|
|
22
23
|
toolStatusPreparingLabel: "Preparing",
|
|
23
24
|
toolStatusRunningLabel: "Running",
|
|
24
25
|
toolStatusCompletedLabel: "Completed",
|
|
@@ -88,7 +89,7 @@ it("maps markdown, reasoning, and tool parts into UI view models", () => {
|
|
|
88
89
|
statusLabel: "Completed",
|
|
89
90
|
statusTone: "success",
|
|
90
91
|
titleLabel: "Tool Result",
|
|
91
|
-
outputLabel: "
|
|
92
|
+
outputLabel: "Output",
|
|
92
93
|
},
|
|
93
94
|
});
|
|
94
95
|
});
|
|
@@ -126,7 +127,7 @@ it("maps tool lifecycle statuses into visible card state feedback", () => {
|
|
|
126
127
|
type: "tool-card",
|
|
127
128
|
card: {
|
|
128
129
|
statusTone: "running",
|
|
129
|
-
statusLabel: "
|
|
130
|
+
statusLabel: "Running",
|
|
130
131
|
titleLabel: "Tool Call",
|
|
131
132
|
},
|
|
132
133
|
});
|
|
@@ -141,6 +142,87 @@ it("maps tool lifecycle statuses into visible card state feedback", () => {
|
|
|
141
142
|
});
|
|
142
143
|
});
|
|
143
144
|
|
|
145
|
+
it("preserves full generic tool args for the expanded body while keeping the header summary short", () => {
|
|
146
|
+
const adapted = adapt([
|
|
147
|
+
{
|
|
148
|
+
id: "assistant-generic-tool-input",
|
|
149
|
+
role: "assistant",
|
|
150
|
+
parts: [
|
|
151
|
+
{
|
|
152
|
+
type: "tool-invocation",
|
|
153
|
+
toolInvocation: {
|
|
154
|
+
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
155
|
+
toolCallId: "call-generic-input",
|
|
156
|
+
toolName: "open_url",
|
|
157
|
+
args: JSON.stringify({
|
|
158
|
+
url: "https://example.com/really/long/path",
|
|
159
|
+
headers: {
|
|
160
|
+
authorization: "Bearer secret-token",
|
|
161
|
+
},
|
|
162
|
+
mode: "reader",
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
] as unknown as ChatMessageSource[]);
|
|
169
|
+
|
|
170
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
171
|
+
type: "tool-card",
|
|
172
|
+
card: {
|
|
173
|
+
toolName: "open_url",
|
|
174
|
+
statusTone: "running",
|
|
175
|
+
summary: "url: https://example.com/really/long/path",
|
|
176
|
+
input: `{
|
|
177
|
+
"url": "https://example.com/really/long/path",
|
|
178
|
+
"headers": {
|
|
179
|
+
"authorization": "Bearer secret-token"
|
|
180
|
+
},
|
|
181
|
+
"mode": "reader"
|
|
182
|
+
}`,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("keeps structured terminal results as structured data instead of raw json output", () => {
|
|
188
|
+
const terminalResult = {
|
|
189
|
+
status: "completed",
|
|
190
|
+
command: "python3 -m http.server 8765",
|
|
191
|
+
aggregated_output: "",
|
|
192
|
+
exit_code: 0,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const adapted = adapt([
|
|
196
|
+
{
|
|
197
|
+
id: "assistant-terminal-result",
|
|
198
|
+
role: "assistant",
|
|
199
|
+
parts: [
|
|
200
|
+
{
|
|
201
|
+
type: "tool-invocation",
|
|
202
|
+
toolInvocation: {
|
|
203
|
+
status: ToolInvocationStatus.RESULT,
|
|
204
|
+
toolCallId: "call-terminal-result",
|
|
205
|
+
toolName: "command_execution",
|
|
206
|
+
args: '{"command":"python3 -m http.server 8765"}',
|
|
207
|
+
result: terminalResult,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
] as unknown as ChatMessageSource[]);
|
|
213
|
+
|
|
214
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
215
|
+
type: "tool-card",
|
|
216
|
+
card: {
|
|
217
|
+
toolName: "command_execution",
|
|
218
|
+
summary: "command: python3 -m http.server 8765",
|
|
219
|
+
output: undefined,
|
|
220
|
+
outputData: terminalResult,
|
|
221
|
+
statusTone: "success",
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
144
226
|
it("renders spawn tool cards from structured subagent status updates", () => {
|
|
145
227
|
const adapted = adapt([
|
|
146
228
|
{
|
|
@@ -377,3 +459,311 @@ it("renders asset tool results as previewable files", () => {
|
|
|
377
459
|
},
|
|
378
460
|
});
|
|
379
461
|
});
|
|
462
|
+
|
|
463
|
+
it("builds edit-file previews from structured args before the tool finishes", () => {
|
|
464
|
+
const adapted = adapt([
|
|
465
|
+
{
|
|
466
|
+
id: "assistant-edit-preview",
|
|
467
|
+
role: "assistant",
|
|
468
|
+
parts: [
|
|
469
|
+
{
|
|
470
|
+
type: "tool-invocation",
|
|
471
|
+
toolInvocation: {
|
|
472
|
+
status: ToolInvocationStatus.CALL,
|
|
473
|
+
toolCallId: "edit-call-1",
|
|
474
|
+
toolName: "edit_file",
|
|
475
|
+
args: JSON.stringify({
|
|
476
|
+
path: "src/app.ts",
|
|
477
|
+
oldText: "const color = 'red';",
|
|
478
|
+
newText: "const color = 'blue';",
|
|
479
|
+
}),
|
|
480
|
+
parsedArgs: {
|
|
481
|
+
path: "src/app.ts",
|
|
482
|
+
oldText: "const color = 'red';",
|
|
483
|
+
newText: "const color = 'blue';",
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
] as unknown as ChatMessageSource[]);
|
|
490
|
+
|
|
491
|
+
const editLines =
|
|
492
|
+
adapted[0]?.parts[0]?.type === "tool-card"
|
|
493
|
+
? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
|
|
494
|
+
: [];
|
|
495
|
+
|
|
496
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
497
|
+
type: "tool-card",
|
|
498
|
+
card: {
|
|
499
|
+
toolName: "edit_file",
|
|
500
|
+
summary: "src/app.ts",
|
|
501
|
+
statusTone: "running",
|
|
502
|
+
fileOperation: {
|
|
503
|
+
blocks: [
|
|
504
|
+
{
|
|
505
|
+
path: "src/app.ts",
|
|
506
|
+
lines: [
|
|
507
|
+
{
|
|
508
|
+
kind: "remove",
|
|
509
|
+
text: "const color = 'red';",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
kind: "add",
|
|
513
|
+
text: "const color = 'blue';",
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
expect(editLines[0]).not.toHaveProperty("oldLineNumber");
|
|
522
|
+
expect(editLines[1]).not.toHaveProperty("newLineNumber");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("uses structured edit-file result line numbers after the tool finishes", () => {
|
|
526
|
+
const adapted = adapt([
|
|
527
|
+
{
|
|
528
|
+
id: "assistant-edit-result",
|
|
529
|
+
role: "assistant",
|
|
530
|
+
parts: [
|
|
531
|
+
{
|
|
532
|
+
type: "tool-invocation",
|
|
533
|
+
toolInvocation: {
|
|
534
|
+
status: ToolInvocationStatus.RESULT,
|
|
535
|
+
toolCallId: "edit-result-1",
|
|
536
|
+
toolName: "edit_file",
|
|
537
|
+
args: JSON.stringify({
|
|
538
|
+
path: "src/app.ts",
|
|
539
|
+
oldText: "const color = 'red';",
|
|
540
|
+
newText: "const color = 'blue';",
|
|
541
|
+
}),
|
|
542
|
+
parsedArgs: {
|
|
543
|
+
path: "src/app.ts",
|
|
544
|
+
oldText: "const color = 'red';",
|
|
545
|
+
newText: "const color = 'blue';",
|
|
546
|
+
},
|
|
547
|
+
result: {
|
|
548
|
+
path: "src/app.ts",
|
|
549
|
+
oldStartLine: 27,
|
|
550
|
+
newStartLine: 27,
|
|
551
|
+
message: "Edited src/app.ts",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
] as unknown as ChatMessageSource[]);
|
|
558
|
+
|
|
559
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
560
|
+
type: "tool-card",
|
|
561
|
+
card: {
|
|
562
|
+
toolName: "edit_file",
|
|
563
|
+
summary: "src/app.ts",
|
|
564
|
+
statusTone: "success",
|
|
565
|
+
fileOperation: {
|
|
566
|
+
blocks: [
|
|
567
|
+
{
|
|
568
|
+
path: "src/app.ts",
|
|
569
|
+
lines: [
|
|
570
|
+
{
|
|
571
|
+
kind: "remove",
|
|
572
|
+
text: "const color = 'red';",
|
|
573
|
+
oldLineNumber: 27,
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
kind: "add",
|
|
577
|
+
text: "const color = 'blue';",
|
|
578
|
+
newLineNumber: 27,
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
},
|
|
582
|
+
],
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("builds write-file previews from partial native args before the JSON is complete", () => {
|
|
589
|
+
const adapted = adapt([
|
|
590
|
+
{
|
|
591
|
+
id: "assistant-write-preview",
|
|
592
|
+
role: "assistant",
|
|
593
|
+
parts: [
|
|
594
|
+
{
|
|
595
|
+
type: "tool-invocation",
|
|
596
|
+
toolInvocation: {
|
|
597
|
+
status: ToolInvocationStatus.PARTIAL_CALL,
|
|
598
|
+
toolCallId: "write-call-1",
|
|
599
|
+
toolName: "write_file",
|
|
600
|
+
args: '{"path":"games/snake.html","content":"<!DOCTYPE html>\\n<canvas id=\\"game\\"></canvas>\\n<script>const score = 1;',
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
},
|
|
605
|
+
] as unknown as ChatMessageSource[]);
|
|
606
|
+
|
|
607
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
608
|
+
type: "tool-card",
|
|
609
|
+
card: {
|
|
610
|
+
toolName: "write_file",
|
|
611
|
+
summary: "games/snake.html",
|
|
612
|
+
statusTone: "running",
|
|
613
|
+
statusLabel: "Running",
|
|
614
|
+
fileOperation: {
|
|
615
|
+
blocks: [
|
|
616
|
+
{
|
|
617
|
+
display: "preview",
|
|
618
|
+
path: "games/snake.html",
|
|
619
|
+
lines: expect.arrayContaining([
|
|
620
|
+
expect.objectContaining({
|
|
621
|
+
kind: "add",
|
|
622
|
+
text: "<!DOCTYPE html>",
|
|
623
|
+
}),
|
|
624
|
+
expect.objectContaining({
|
|
625
|
+
kind: "add",
|
|
626
|
+
text: '<canvas id="game"></canvas>',
|
|
627
|
+
}),
|
|
628
|
+
]),
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("keeps completed write-file cards in preview mode instead of falling back to raw byte summaries", () => {
|
|
637
|
+
const adapted = adapt([
|
|
638
|
+
{
|
|
639
|
+
id: "assistant-write-result",
|
|
640
|
+
role: "assistant",
|
|
641
|
+
parts: [
|
|
642
|
+
{
|
|
643
|
+
type: "tool-invocation",
|
|
644
|
+
toolInvocation: {
|
|
645
|
+
status: ToolInvocationStatus.RESULT,
|
|
646
|
+
toolCallId: "write-result-1",
|
|
647
|
+
toolName: "write_file",
|
|
648
|
+
args: JSON.stringify({
|
|
649
|
+
path: "games/snake.html",
|
|
650
|
+
content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
|
|
651
|
+
}),
|
|
652
|
+
result: "Wrote 3906 bytes to games/snake.html",
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
},
|
|
657
|
+
] as unknown as ChatMessageSource[]);
|
|
658
|
+
|
|
659
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
660
|
+
type: "tool-card",
|
|
661
|
+
card: {
|
|
662
|
+
toolName: "write_file",
|
|
663
|
+
summary: "games/snake.html",
|
|
664
|
+
statusTone: "success",
|
|
665
|
+
fileOperation: {
|
|
666
|
+
blocks: [
|
|
667
|
+
{
|
|
668
|
+
display: "preview",
|
|
669
|
+
path: "games/snake.html",
|
|
670
|
+
lines: [
|
|
671
|
+
{
|
|
672
|
+
kind: "add",
|
|
673
|
+
text: "<!DOCTYPE html>",
|
|
674
|
+
newLineNumber: 1,
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
kind: "add",
|
|
678
|
+
text: '<canvas id="game"></canvas>',
|
|
679
|
+
newLineNumber: 2,
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
688
|
+
type: "tool-card",
|
|
689
|
+
card: {
|
|
690
|
+
output: "Wrote 3906 bytes to games/snake.html",
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("renders codex file_change results as structured diff previews", () => {
|
|
696
|
+
const adapted = adapt([
|
|
697
|
+
{
|
|
698
|
+
id: "assistant-file-change",
|
|
699
|
+
role: "assistant",
|
|
700
|
+
parts: [
|
|
701
|
+
{
|
|
702
|
+
type: "tool-invocation",
|
|
703
|
+
toolInvocation: {
|
|
704
|
+
status: ToolInvocationStatus.RESULT,
|
|
705
|
+
toolCallId: "file-change-1",
|
|
706
|
+
toolName: "file_change",
|
|
707
|
+
args: JSON.stringify({
|
|
708
|
+
changes: [
|
|
709
|
+
{
|
|
710
|
+
path: "src/main.ts",
|
|
711
|
+
diff: [
|
|
712
|
+
"--- a/src/main.ts",
|
|
713
|
+
"+++ b/src/main.ts",
|
|
714
|
+
"@@ -109,1 +109,1 @@",
|
|
715
|
+
"-console.log('old');",
|
|
716
|
+
"+console.log('new');",
|
|
717
|
+
].join("\n"),
|
|
718
|
+
},
|
|
719
|
+
],
|
|
720
|
+
}),
|
|
721
|
+
result: {
|
|
722
|
+
status: "completed",
|
|
723
|
+
changes: [
|
|
724
|
+
{
|
|
725
|
+
path: "src/main.ts",
|
|
726
|
+
diff: [
|
|
727
|
+
"--- a/src/main.ts",
|
|
728
|
+
"+++ b/src/main.ts",
|
|
729
|
+
"@@ -109,1 +109,1 @@",
|
|
730
|
+
"-console.log('old');",
|
|
731
|
+
"+console.log('new');",
|
|
732
|
+
].join("\n"),
|
|
733
|
+
},
|
|
734
|
+
],
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
],
|
|
739
|
+
},
|
|
740
|
+
] as unknown as ChatMessageSource[]);
|
|
741
|
+
|
|
742
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
743
|
+
type: "tool-card",
|
|
744
|
+
card: {
|
|
745
|
+
toolName: "file_change",
|
|
746
|
+
summary: "src/main.ts",
|
|
747
|
+
statusTone: "success",
|
|
748
|
+
fileOperation: {
|
|
749
|
+
blocks: [
|
|
750
|
+
{
|
|
751
|
+
path: "src/main.ts",
|
|
752
|
+
lines: [
|
|
753
|
+
{
|
|
754
|
+
kind: "remove",
|
|
755
|
+
text: "console.log('old');",
|
|
756
|
+
oldLineNumber: 109,
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
kind: "add",
|
|
760
|
+
text: "console.log('new');",
|
|
761
|
+
newLineNumber: 109,
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
});
|