@nextclaw/ui 0.20.0 → 0.20.1
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 +18 -0
- package/dist/assets/{api-C2iYhdTe.js → api-BBFMpRGl.js} +1 -1
- package/dist/assets/{appearance-settings-page-BwQDQr0n.js → appearance-settings-page-C1Erayzl.js} +1 -1
- package/dist/assets/{book-open-C0IlT9zs.js → book-open-BhTIpBKg.js} +1 -1
- package/dist/assets/{channels-list-page-B0z4bh8e.js → channels-list-page-OTCkjMOW.js} +1 -1
- package/dist/assets/{config-split-page-VDTf3zZb.js → config-split-page-By7p-4X-.js} +1 -1
- package/dist/assets/{confirm-dialog-BI_Vt2s_.js → confirm-dialog-xpl1Yl7r.js} +1 -1
- package/dist/assets/{createLucideIcon-Bydi9Gc5.js → createLucideIcon-B_8khHn8.js} +1 -1
- package/dist/assets/{desktop-update-config-DVoD6NxG.js → desktop-update-config-CGFfa7-Z.js} +1 -1
- package/dist/assets/{dist-CmHtkFbs.js → dist-CXN62DSs.js} +1 -1
- package/dist/assets/{dist-A5btQciA.js → dist-DdE-kCbi.js} +1 -1
- package/dist/assets/{doc-browser-Cm_tMZ_C.js → doc-browser-5xrHcQ2z.js} +1 -1
- package/dist/assets/doc-browser-c2yO477-.js +1 -0
- package/dist/assets/{doc-browser-context-Bxw0ora-.js → doc-browser-context-B8NCuMv5.js} +1 -1
- package/dist/assets/{extensions-CG4voDCh.js → extensions-CMctxyTf.js} +1 -1
- package/dist/assets/{form-actions-lv47uGIB.js → form-actions-BFgTdxqI.js} +1 -1
- package/dist/assets/index-DUCA8m2w.js +109 -0
- package/dist/assets/loader-circle-CbdTrSp3.js +1 -0
- package/dist/assets/{mcp-marketplace-page-BSk0qUOX.js → mcp-marketplace-page-DGve0muV.js} +1 -1
- package/dist/assets/mcp-marketplace-page-_37VzG2q.js +1 -0
- package/dist/assets/{model-config-page-c8RnUGoH.js → model-config-page-DyC5H0bu.js} +1 -1
- package/dist/assets/{navigation-link-D0agTj5t.js → navigation-link-BwWrm4k1.js} +1 -1
- package/dist/assets/plus-DbeE4r-Q.js +1 -0
- package/dist/assets/{privacy-settings-page-CPkyTvEE.js → privacy-settings-page--MOnhAZ6.js} +1 -1
- package/dist/assets/{providers-config-page-CGUEb5Dx.js → providers-config-page-ChL3FUvK.js} +1 -1
- package/dist/assets/react-DVMmnPWz.js +8 -0
- package/dist/assets/{refresh-cw-DjgY2Eju.js → refresh-cw-B7NmPJ_Z.js} +1 -1
- package/dist/assets/remote-DFoxj4ls.js +1 -0
- package/dist/assets/{rotate-cw-Dot4PVh0.js → rotate-cw-B0ajMaHu.js} +1 -1
- package/dist/assets/{runtime-config-page-C6nzqTHg.js → runtime-config-page-BQdT3nmd.js} +1 -1
- package/dist/assets/{save-B-jdTe_c.js → save-DWnYYpgQ.js} +1 -1
- package/dist/assets/{search-CEp2AIiV.js → search-D6pX7FC-.js} +1 -1
- package/dist/assets/{search-config-page-Dp5guS5O.js → search-config-page-Cx3o_675.js} +1 -1
- package/dist/assets/{secrets-config-page-C7nRMjiv.js → secrets-config-page-tyFl7Rvt.js} +1 -1
- package/dist/assets/security-config-CUTUm6JT.js +1 -0
- package/dist/assets/{select-BNA3cma8.js → select-qEtFTWB5.js} +1 -1
- package/dist/assets/skeleton-7MYPzn8w.js +1 -0
- package/dist/assets/{tag-chip-DmCNi4-9.js → tag-chip-vcjAYdV_.js} +1 -1
- package/dist/assets/x-DThaS5ZL.js +1 -0
- package/dist/index.html +20 -20
- package/package.json +7 -7
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +33 -4
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +186 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +9 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +71 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-trigger-details.utils.test.ts +112 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +36 -0
- package/src/features/chat/features/message/utils/chat-message-trigger-details.utils.ts +118 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +90 -1
- package/src/features/chat/features/session/utils/ncp-session-context-metadata.utils.ts +5 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +5 -5
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -40
- package/src/features/chat/managers/__tests__/chat-completion-notification.manager.test.ts +70 -0
- package/src/features/chat/managers/chat-completion-notification.manager.ts +23 -6
- package/src/shared/lib/i18n/locales/en-US/chat.json +21 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +21 -0
- package/dist/assets/doc-browser-Dto54tCf.js +0 -1
- package/dist/assets/index-BTSwNDVi.js +0 -109
- package/dist/assets/loader-circle-CkaqeEne.js +0 -1
- package/dist/assets/mcp-marketplace-page-BgZGc-S_.js +0 -1
- package/dist/assets/plus-BiMjkv3R.js +0 -1
- package/dist/assets/react-BMYFkaGi.js +0 -8
- package/dist/assets/remote-1LxkpRED.js +0 -1
- package/dist/assets/security-config-DvHUDxSg.js +0 -1
- package/dist/assets/skeleton-CbroEO8Q.js +0 -1
- package/dist/assets/x-BTo5-A8h.js +0 -1
package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts
CHANGED
|
@@ -154,6 +154,12 @@ it("uses structured edit-file result line numbers after the tool finishes", () =
|
|
|
154
154
|
},
|
|
155
155
|
},
|
|
156
156
|
});
|
|
157
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
158
|
+
type: "tool-card",
|
|
159
|
+
card: {
|
|
160
|
+
outputData: expect.anything(),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
157
163
|
});
|
|
158
164
|
|
|
159
165
|
it("builds write-file previews from partial native args before the JSON is complete", () => {
|
|
@@ -261,6 +267,12 @@ it("keeps completed write-file cards in preview mode instead of falling back to
|
|
|
261
267
|
output: "Wrote 3906 bytes to games/snake.html",
|
|
262
268
|
},
|
|
263
269
|
});
|
|
270
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
271
|
+
type: "tool-card",
|
|
272
|
+
card: {
|
|
273
|
+
outputData: expect.anything(),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
264
276
|
});
|
|
265
277
|
|
|
266
278
|
it("renders codex file_change results as structured diff previews", () => {
|
|
@@ -337,4 +349,178 @@ it("renders codex file_change results as structured diff previews", () => {
|
|
|
337
349
|
},
|
|
338
350
|
},
|
|
339
351
|
});
|
|
352
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
353
|
+
type: "tool-card",
|
|
354
|
+
card: {
|
|
355
|
+
outputData: expect.anything(),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("uses a read-file preview as the single visible representation of file content", () => {
|
|
361
|
+
const adapted = adapt([
|
|
362
|
+
{
|
|
363
|
+
id: "assistant-read-result",
|
|
364
|
+
role: "assistant",
|
|
365
|
+
parts: [
|
|
366
|
+
{
|
|
367
|
+
type: "tool-invocation",
|
|
368
|
+
toolInvocation: {
|
|
369
|
+
status: ToolInvocationStatus.RESULT,
|
|
370
|
+
toolCallId: "read-result-1",
|
|
371
|
+
toolName: "read_file",
|
|
372
|
+
args: JSON.stringify({ path: "docs/readme.md" }),
|
|
373
|
+
parsedArgs: { path: "docs/readme.md" },
|
|
374
|
+
result: "1: first line\n2: second line\n</content>",
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
] as unknown as ChatMessageSource[]);
|
|
380
|
+
|
|
381
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
382
|
+
type: "tool-card",
|
|
383
|
+
card: {
|
|
384
|
+
toolName: "read_file",
|
|
385
|
+
summary: "docs/readme.md",
|
|
386
|
+
fileOperation: {
|
|
387
|
+
blocks: [
|
|
388
|
+
{
|
|
389
|
+
display: "preview",
|
|
390
|
+
path: "docs/readme.md",
|
|
391
|
+
lines: expect.arrayContaining([
|
|
392
|
+
expect.objectContaining({ text: "1: first line" }),
|
|
393
|
+
expect.objectContaining({ text: "2: second line" }),
|
|
394
|
+
]),
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
401
|
+
type: "tool-card",
|
|
402
|
+
card: {
|
|
403
|
+
outputData: expect.anything(),
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("uses an apply-patch preview as the single visible representation of the patch", () => {
|
|
409
|
+
const patch = [
|
|
410
|
+
"*** Begin Patch",
|
|
411
|
+
"*** Update File: src/app.ts",
|
|
412
|
+
"@@",
|
|
413
|
+
"-const color = 'red';",
|
|
414
|
+
"+const color = 'blue';",
|
|
415
|
+
"*** End Patch",
|
|
416
|
+
].join("\n");
|
|
417
|
+
const adapted = adapt([
|
|
418
|
+
{
|
|
419
|
+
id: "assistant-apply-patch-result",
|
|
420
|
+
role: "assistant",
|
|
421
|
+
parts: [
|
|
422
|
+
{
|
|
423
|
+
type: "tool-invocation",
|
|
424
|
+
toolInvocation: {
|
|
425
|
+
status: ToolInvocationStatus.RESULT,
|
|
426
|
+
toolCallId: "apply-patch-result-1",
|
|
427
|
+
toolName: "apply_patch",
|
|
428
|
+
args: patch,
|
|
429
|
+
result: "Done!",
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
},
|
|
434
|
+
] as unknown as ChatMessageSource[]);
|
|
435
|
+
|
|
436
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
437
|
+
type: "tool-card",
|
|
438
|
+
card: {
|
|
439
|
+
toolName: "apply_patch",
|
|
440
|
+
summary: "src/app.ts",
|
|
441
|
+
fileOperation: {
|
|
442
|
+
blocks: [
|
|
443
|
+
{
|
|
444
|
+
path: "src/app.ts",
|
|
445
|
+
lines: [
|
|
446
|
+
expect.objectContaining({ kind: "remove", text: "const color = 'red';" }),
|
|
447
|
+
expect.objectContaining({ kind: "add", text: "const color = 'blue';" }),
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
expect(adapted[0]?.parts[0]).not.toMatchObject({
|
|
455
|
+
type: "tool-card",
|
|
456
|
+
card: {
|
|
457
|
+
outputData: expect.anything(),
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("keeps raw file-tool results when no structured preview can be built", () => {
|
|
463
|
+
const adapted = adapt([
|
|
464
|
+
{
|
|
465
|
+
id: "assistant-read-result-without-path",
|
|
466
|
+
role: "assistant",
|
|
467
|
+
parts: [
|
|
468
|
+
{
|
|
469
|
+
type: "tool-invocation",
|
|
470
|
+
toolInvocation: {
|
|
471
|
+
status: ToolInvocationStatus.RESULT,
|
|
472
|
+
toolCallId: "read-result-without-path-1",
|
|
473
|
+
toolName: "read_file",
|
|
474
|
+
result: "raw file content",
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
] as unknown as ChatMessageSource[]);
|
|
480
|
+
|
|
481
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
482
|
+
type: "tool-card",
|
|
483
|
+
card: {
|
|
484
|
+
toolName: "read_file",
|
|
485
|
+
outputData: "raw file content",
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("keeps file-tool errors visible even when input can build a preview", () => {
|
|
491
|
+
const adapted = adapt([
|
|
492
|
+
{
|
|
493
|
+
id: "assistant-write-error",
|
|
494
|
+
role: "assistant",
|
|
495
|
+
parts: [
|
|
496
|
+
{
|
|
497
|
+
type: "tool-invocation",
|
|
498
|
+
toolInvocation: {
|
|
499
|
+
status: ToolInvocationStatus.RESULT,
|
|
500
|
+
toolCallId: "write-error-1",
|
|
501
|
+
toolName: "write_file",
|
|
502
|
+
parsedArgs: {
|
|
503
|
+
path: "src/app.ts",
|
|
504
|
+
content: "const color = 'blue';",
|
|
505
|
+
},
|
|
506
|
+
error: "Permission denied",
|
|
507
|
+
result: "write failed",
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
] as unknown as ChatMessageSource[]);
|
|
513
|
+
|
|
514
|
+
expect(adapted[0]?.parts[0]).toMatchObject({
|
|
515
|
+
type: "tool-card",
|
|
516
|
+
card: {
|
|
517
|
+
toolName: "write_file",
|
|
518
|
+
statusTone: "error",
|
|
519
|
+
output: "Permission denied",
|
|
520
|
+
outputData: "write failed",
|
|
521
|
+
fileOperation: {
|
|
522
|
+
blocks: [expect.objectContaining({ path: "src/app.ts" })],
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
});
|
|
340
526
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { expect, it, vi } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
buildChatMessageExecutionLabels,
|
|
4
|
+
buildChatMessageTriggerLabels,
|
|
4
5
|
buildChatMessageTexts,
|
|
5
6
|
} from "@/features/chat/features/message/utils/chat-message-texts.utils";
|
|
6
7
|
|
|
@@ -25,6 +26,14 @@ it("provides localized status keys for built-in tool categories", () => {
|
|
|
25
26
|
});
|
|
26
27
|
});
|
|
27
28
|
|
|
29
|
+
it("provides localized labels for run trigger details", () => {
|
|
30
|
+
expect(buildChatMessageTriggerLabels("zh")).toMatchObject({
|
|
31
|
+
viewTrigger: "chatRunTriggerView",
|
|
32
|
+
fields: { sourceModel: "chatRunTriggerSourceModel" },
|
|
33
|
+
actors: { agent: "chatRunTriggerActorAgent" },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
28
37
|
it("provides localized labels for the AI execution metadata action", () => {
|
|
29
38
|
expect(buildChatMessageExecutionLabels("zh")).toMatchObject({
|
|
30
39
|
moreActions: "chatMessageMoreActions",
|
package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts
CHANGED
|
@@ -125,7 +125,9 @@ describe("chat message timeline visibility", () => {
|
|
|
125
125
|
})).toBe(false);
|
|
126
126
|
expect(isVisibleChatMessage(visibleMessage)).toBe(true);
|
|
127
127
|
});
|
|
128
|
+
});
|
|
128
129
|
|
|
130
|
+
describe("chat message continuation projection", () => {
|
|
129
131
|
it("projects continued assistant output into the interrupted message", () => {
|
|
130
132
|
const messages = projectVisibleChatMessages([
|
|
131
133
|
{
|
|
@@ -253,3 +255,72 @@ describe("chat message timeline visibility", () => {
|
|
|
253
255
|
]).map((message) => message.id)).toEqual(["user-1", "assistant-1"]);
|
|
254
256
|
});
|
|
255
257
|
});
|
|
258
|
+
|
|
259
|
+
describe("child session timeline visibility", () => {
|
|
260
|
+
it("does not repeat inherited compaction markers", () => {
|
|
261
|
+
const childMessage = {
|
|
262
|
+
...visibleMessage,
|
|
263
|
+
id: "child-user",
|
|
264
|
+
role: "user" as const,
|
|
265
|
+
timestamp: "2026-08-24T15:50:23.415Z",
|
|
266
|
+
parts: [{ type: "text" as const, text: "child request" }],
|
|
267
|
+
};
|
|
268
|
+
const inheritedCompactions = Array.from({ length: 5 }, (_, index) => ({
|
|
269
|
+
...visibleMessage,
|
|
270
|
+
id: `child-session:inherited:${index + 1}`,
|
|
271
|
+
role: "service" as const,
|
|
272
|
+
timestamp: `2026-08-24T15:0${index}:00.000Z`,
|
|
273
|
+
parts: [{ type: "text" as const, text: "Earlier context was auto-compacted" }],
|
|
274
|
+
metadata: {
|
|
275
|
+
inherited_from_session_id: "parent-session",
|
|
276
|
+
inherited_from_message_id: `parent-compaction-${index + 1}`,
|
|
277
|
+
nextclaw_timeline_kind: "context_compaction",
|
|
278
|
+
checkpoint: {
|
|
279
|
+
id: "parent-checkpoint",
|
|
280
|
+
status: "compressed",
|
|
281
|
+
summary: "Compressed parent context",
|
|
282
|
+
coveredMessageCount: (index + 1) * 100,
|
|
283
|
+
coveredSessionMessageCount: (index + 1) * 100,
|
|
284
|
+
originalEstimatedTokens: 76_000,
|
|
285
|
+
projectedEstimatedTokens: 51_000,
|
|
286
|
+
createdAt: "2026-08-24T15:00:00.000Z",
|
|
287
|
+
updatedAt: `2026-08-24T15:0${index}:00.000Z`,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
} satisfies NcpMessage));
|
|
291
|
+
const items = buildChatMessageTimelineItems({
|
|
292
|
+
rawMessages: [...inheritedCompactions, childMessage],
|
|
293
|
+
messages: [{ id: childMessage.id } as never],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(items.map((item) => item.kind)).toEqual([
|
|
297
|
+
"context-inheritance",
|
|
298
|
+
"message",
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
const childCompaction = {
|
|
302
|
+
...inheritedCompactions[0]!,
|
|
303
|
+
id: "child-compaction",
|
|
304
|
+
metadata: {
|
|
305
|
+
...inheritedCompactions[0]!.metadata,
|
|
306
|
+
inherited_from_session_id: undefined,
|
|
307
|
+
inherited_from_message_id: undefined,
|
|
308
|
+
},
|
|
309
|
+
} satisfies NcpMessage;
|
|
310
|
+
const childAssistant = {
|
|
311
|
+
...visibleMessage,
|
|
312
|
+
id: "child-assistant",
|
|
313
|
+
timestamp: "2026-08-24T15:51:00.000Z",
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
expect(buildChatMessageTimelineItems({
|
|
317
|
+
rawMessages: [...inheritedCompactions, childMessage, childCompaction, childAssistant],
|
|
318
|
+
messages: [{ id: childMessage.id } as never, { id: childAssistant.id } as never],
|
|
319
|
+
}).map((item) => item.kind)).toEqual([
|
|
320
|
+
"context-inheritance",
|
|
321
|
+
"message",
|
|
322
|
+
"compaction",
|
|
323
|
+
"message",
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildChatMessageTriggerDetails,
|
|
4
|
+
mergeChatMessageMoreActions,
|
|
5
|
+
} from "@/features/chat/features/message/utils/chat-message-trigger-details.utils";
|
|
6
|
+
|
|
7
|
+
const labels = {
|
|
8
|
+
moreActions: "More actions",
|
|
9
|
+
viewTrigger: "View trigger details",
|
|
10
|
+
title: "Run trigger",
|
|
11
|
+
description: "Why this run started.",
|
|
12
|
+
close: "Close",
|
|
13
|
+
notAvailable: "Not available",
|
|
14
|
+
fields: {
|
|
15
|
+
actor: "Actor",
|
|
16
|
+
source: "Source",
|
|
17
|
+
triggeredAt: "Triggered at",
|
|
18
|
+
targetRunId: "Target run",
|
|
19
|
+
sourceSessionId: "Source session",
|
|
20
|
+
sourceMessageId: "Source message",
|
|
21
|
+
sourceRunId: "Source run",
|
|
22
|
+
sourceToolCallId: "Source tool call",
|
|
23
|
+
sourceRequestId: "Source request",
|
|
24
|
+
sourceModel: "Source model",
|
|
25
|
+
targetModel: "Run model",
|
|
26
|
+
sourceContext: "Source context",
|
|
27
|
+
raw: "Raw metadata",
|
|
28
|
+
},
|
|
29
|
+
actors: {
|
|
30
|
+
human: "Human",
|
|
31
|
+
agent: "Agent",
|
|
32
|
+
automation: "Automation",
|
|
33
|
+
system: "System",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe("buildChatMessageTriggerDetails", () => {
|
|
38
|
+
it("projects structured and raw provenance rows", () => {
|
|
39
|
+
const details = buildChatMessageTriggerDetails({
|
|
40
|
+
labels,
|
|
41
|
+
message: {
|
|
42
|
+
metadata: {
|
|
43
|
+
run_trigger: {
|
|
44
|
+
version: 1,
|
|
45
|
+
actor: "agent",
|
|
46
|
+
source: "sessions_spawn",
|
|
47
|
+
triggeredAt: "2026-08-25T00:00:00.000Z",
|
|
48
|
+
targetRunId: "target-run",
|
|
49
|
+
sourceSessionId: "source-session",
|
|
50
|
+
sourceMessageId: "source-message",
|
|
51
|
+
sourceRunId: "source-run",
|
|
52
|
+
sourceToolCallId: "source-tool-call",
|
|
53
|
+
sourceRequestId: "source-request",
|
|
54
|
+
sourceModel: "openai/gpt-5.6",
|
|
55
|
+
sourceContext: { channel: "telegram", chatId: "chat-1" },
|
|
56
|
+
},
|
|
57
|
+
run_spec: {
|
|
58
|
+
runId: "target-run",
|
|
59
|
+
model: "minimax/MiniMax-M3",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(details?.triggerLabel).toBe("More actions");
|
|
66
|
+
expect(details?.items[0]?.dialog.rows).toEqual(expect.arrayContaining([
|
|
67
|
+
{ label: "Actor", value: "Agent" },
|
|
68
|
+
{ label: "Source model", value: "openai/gpt-5.6" },
|
|
69
|
+
{ label: "Run model", value: "minimax/MiniMax-M3" },
|
|
70
|
+
{ label: "Source context", value: expect.stringContaining("telegram") },
|
|
71
|
+
expect.objectContaining({ label: "Raw metadata" }),
|
|
72
|
+
]));
|
|
73
|
+
expect(details?.items[0]?.dialog.rows.find(
|
|
74
|
+
(row) => row.label === "Raw metadata",
|
|
75
|
+
)?.value).toContain("run_spec");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does not create an empty action for legacy messages", () => {
|
|
79
|
+
expect(buildChatMessageTriggerDetails({ labels, message: {} })).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("keeps trigger-only messages actionable and merges assistant details in order", () => {
|
|
83
|
+
const triggerDetails = buildChatMessageTriggerDetails({
|
|
84
|
+
labels,
|
|
85
|
+
message: {
|
|
86
|
+
metadata: {
|
|
87
|
+
run_trigger: {
|
|
88
|
+
version: 1,
|
|
89
|
+
actor: "human",
|
|
90
|
+
source: "ui-http",
|
|
91
|
+
triggeredAt: "2026-08-25T00:00:00.000Z",
|
|
92
|
+
targetRunId: "run-1",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
expect(mergeChatMessageMoreActions(null, triggerDetails)?.items.map(
|
|
98
|
+
(item) => item.key,
|
|
99
|
+
)).toEqual(["run-trigger-metadata"]);
|
|
100
|
+
expect(mergeChatMessageMoreActions({
|
|
101
|
+
triggerLabel: "More actions",
|
|
102
|
+
items: [{
|
|
103
|
+
key: "ai-execution-metadata",
|
|
104
|
+
label: "View run metadata",
|
|
105
|
+
dialog: { title: "Run", closeLabel: "Close", rows: [] },
|
|
106
|
+
}],
|
|
107
|
+
}, triggerDetails)?.items.map((item) => item.key)).toEqual([
|
|
108
|
+
"ai-execution-metadata",
|
|
109
|
+
"run-trigger-metadata",
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -174,7 +174,7 @@ function buildToolInvocationPart(
|
|
|
174
174
|
detail,
|
|
175
175
|
inputData: fileOperationCardData ? undefined : invocation.parsedArgs ?? invocation.args,
|
|
176
176
|
text: shouldShowRawResult && rawResult ? rawResult : undefined,
|
|
177
|
-
outputData: invocation.result,
|
|
177
|
+
outputData: shouldShowRawResult ? invocation.result : undefined,
|
|
178
178
|
callId: invocation.toolCallId || undefined,
|
|
179
179
|
toolCallId: invocation.toolCallId || undefined,
|
|
180
180
|
...(invocation.execution ? { execution: invocation.execution } : {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChatMessageTexts } from "@nextclaw/agent-chat-ui";
|
|
2
2
|
import type { ChatMessageExecutionPresentationLabels } from "./chat-message-execution-summary.utils";
|
|
3
|
+
import type { ChatMessageTriggerDetailsLabels } from "./chat-message-trigger-details.utils";
|
|
3
4
|
import type { ChatMessageAdapterTexts } from "@/features/chat/features/message/utils/chat-message.utils";
|
|
4
5
|
import { t } from "@/shared/lib/i18n";
|
|
5
6
|
|
|
@@ -140,6 +141,41 @@ export function buildChatMessageExecutionLabels(
|
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
export function buildChatMessageTriggerLabels(
|
|
145
|
+
language: string,
|
|
146
|
+
): ChatMessageTriggerDetailsLabels {
|
|
147
|
+
void language;
|
|
148
|
+
return {
|
|
149
|
+
moreActions: t("chatMessageMoreActions"),
|
|
150
|
+
viewTrigger: t("chatRunTriggerView"),
|
|
151
|
+
title: t("chatRunTriggerTitle"),
|
|
152
|
+
description: t("chatRunTriggerDescription"),
|
|
153
|
+
close: t("chatRunTriggerClose"),
|
|
154
|
+
notAvailable: t("chatAiExecutionNotAvailable"),
|
|
155
|
+
fields: {
|
|
156
|
+
actor: t("chatRunTriggerActor"),
|
|
157
|
+
source: t("chatRunTriggerSource"),
|
|
158
|
+
triggeredAt: t("chatRunTriggerTime"),
|
|
159
|
+
targetRunId: t("chatRunTriggerTargetRun"),
|
|
160
|
+
sourceSessionId: t("chatRunTriggerSourceSession"),
|
|
161
|
+
sourceMessageId: t("chatRunTriggerSourceMessage"),
|
|
162
|
+
sourceRunId: t("chatRunTriggerSourceRun"),
|
|
163
|
+
sourceToolCallId: t("chatRunTriggerSourceToolCall"),
|
|
164
|
+
sourceRequestId: t("chatRunTriggerSourceRequest"),
|
|
165
|
+
sourceModel: t("chatRunTriggerSourceModel"),
|
|
166
|
+
targetModel: t("chatRunTriggerTargetModel"),
|
|
167
|
+
sourceContext: t("chatRunTriggerSourceContext"),
|
|
168
|
+
raw: t("chatRunTriggerRaw"),
|
|
169
|
+
},
|
|
170
|
+
actors: {
|
|
171
|
+
human: t("chatRunTriggerActorHuman"),
|
|
172
|
+
agent: t("chatRunTriggerActorAgent"),
|
|
173
|
+
automation: t("chatRunTriggerActorAutomation"),
|
|
174
|
+
system: t("chatRunTriggerActorSystem"),
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
143
179
|
export function buildChatMessageTexts(
|
|
144
180
|
language: string,
|
|
145
181
|
): ChatMessageTexts {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ChatMessageMoreActionsViewModel } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
import {
|
|
3
|
+
readNcpAiExecutionMetadata,
|
|
4
|
+
readNcpRunTriggerMetadata,
|
|
5
|
+
type NcpMessage,
|
|
6
|
+
type NcpRunTriggerActor,
|
|
7
|
+
} from "@nextclaw/ncp";
|
|
8
|
+
|
|
9
|
+
export type ChatMessageTriggerDetailsLabels = {
|
|
10
|
+
moreActions: string;
|
|
11
|
+
viewTrigger: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
close: string;
|
|
15
|
+
notAvailable: string;
|
|
16
|
+
fields: {
|
|
17
|
+
actor: string;
|
|
18
|
+
source: string;
|
|
19
|
+
triggeredAt: string;
|
|
20
|
+
targetRunId: string;
|
|
21
|
+
sourceSessionId: string;
|
|
22
|
+
sourceMessageId: string;
|
|
23
|
+
sourceRunId: string;
|
|
24
|
+
sourceToolCallId: string;
|
|
25
|
+
sourceRequestId: string;
|
|
26
|
+
sourceModel: string;
|
|
27
|
+
targetModel: string;
|
|
28
|
+
sourceContext: string;
|
|
29
|
+
raw: string;
|
|
30
|
+
};
|
|
31
|
+
actors: Record<NcpRunTriggerActor, string>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function mergeChatMessageMoreActions(
|
|
35
|
+
...candidates: Array<ChatMessageMoreActionsViewModel | null | undefined>
|
|
36
|
+
): ChatMessageMoreActionsViewModel | undefined {
|
|
37
|
+
const available = candidates.filter(
|
|
38
|
+
(candidate): candidate is ChatMessageMoreActionsViewModel =>
|
|
39
|
+
Boolean(candidate?.items.length),
|
|
40
|
+
);
|
|
41
|
+
if (available.length === 0) return undefined;
|
|
42
|
+
return {
|
|
43
|
+
triggerLabel: available[0]!.triggerLabel,
|
|
44
|
+
items: available.flatMap((candidate) => candidate.items),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readRunSpec(
|
|
49
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
50
|
+
): Record<string, unknown> | null {
|
|
51
|
+
const value = metadata?.run_spec;
|
|
52
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
53
|
+
? value as Record<string, unknown>
|
|
54
|
+
: null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readTargetModel(
|
|
58
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
59
|
+
): string | undefined {
|
|
60
|
+
const runSpecModel = readRunSpec(metadata)?.model;
|
|
61
|
+
if (typeof runSpecModel === "string" && runSpecModel.trim()) {
|
|
62
|
+
return runSpecModel.trim();
|
|
63
|
+
}
|
|
64
|
+
return readNcpAiExecutionMetadata(metadata)?.model;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildChatMessageTriggerDetails(params: {
|
|
68
|
+
labels: ChatMessageTriggerDetailsLabels;
|
|
69
|
+
message: Pick<NcpMessage, "metadata">;
|
|
70
|
+
}): ChatMessageMoreActionsViewModel | null {
|
|
71
|
+
const { labels, message } = params;
|
|
72
|
+
const trigger = readNcpRunTriggerMetadata(message.metadata);
|
|
73
|
+
if (!trigger) return null;
|
|
74
|
+
const { fields } = labels;
|
|
75
|
+
const runSpec = readRunSpec(message.metadata);
|
|
76
|
+
const rows = [
|
|
77
|
+
{ label: fields.actor, value: labels.actors[trigger.actor] },
|
|
78
|
+
{ label: fields.source, value: trigger.source },
|
|
79
|
+
{ label: fields.triggeredAt, value: trigger.triggeredAt },
|
|
80
|
+
{ label: fields.targetRunId, value: trigger.targetRunId },
|
|
81
|
+
{ label: fields.sourceSessionId, value: trigger.sourceSessionId },
|
|
82
|
+
{ label: fields.sourceMessageId, value: trigger.sourceMessageId },
|
|
83
|
+
{ label: fields.sourceRunId, value: trigger.sourceRunId },
|
|
84
|
+
{ label: fields.sourceToolCallId, value: trigger.sourceToolCallId },
|
|
85
|
+
{ label: fields.sourceRequestId, value: trigger.sourceRequestId },
|
|
86
|
+
{ label: fields.sourceModel, value: trigger.sourceModel },
|
|
87
|
+
{ label: fields.targetModel, value: readTargetModel(message.metadata) },
|
|
88
|
+
{
|
|
89
|
+
label: fields.sourceContext,
|
|
90
|
+
value: trigger.sourceContext
|
|
91
|
+
? JSON.stringify(trigger.sourceContext, null, 2)
|
|
92
|
+
: undefined,
|
|
93
|
+
},
|
|
94
|
+
].map((row) => ({
|
|
95
|
+
label: row.label,
|
|
96
|
+
value: row.value ?? labels.notAvailable,
|
|
97
|
+
}));
|
|
98
|
+
rows.push({
|
|
99
|
+
label: fields.raw,
|
|
100
|
+
value: JSON.stringify({
|
|
101
|
+
run_trigger: trigger,
|
|
102
|
+
...(runSpec ? { run_spec: runSpec } : {}),
|
|
103
|
+
}, null, 2),
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
triggerLabel: labels.moreActions,
|
|
107
|
+
items: [{
|
|
108
|
+
key: "run-trigger-metadata",
|
|
109
|
+
label: labels.viewTrigger,
|
|
110
|
+
dialog: {
|
|
111
|
+
title: labels.title,
|
|
112
|
+
description: labels.description,
|
|
113
|
+
closeLabel: labels.close,
|
|
114
|
+
rows,
|
|
115
|
+
},
|
|
116
|
+
}],
|
|
117
|
+
};
|
|
118
|
+
}
|