@nextclaw/ui 0.11.21 → 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.
Files changed (120) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-Zeys_w43.js} +6 -6
  3. package/dist/assets/ChatPage-DWOU_8P6.js +43 -0
  4. package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  5. package/dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
  7. package/dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
  8. package/dist/assets/MarketplacePage-BfaTTqN6.js +1 -0
  9. package/dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Cd4faegU.js} +2 -2
  10. package/dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-C09Ngs7O.js} +2 -2
  11. package/dist/assets/ModelConfig-DJgdcgvQ.js +1 -0
  12. package/dist/assets/ProvidersList-w0rVFIBf.js +1 -0
  13. package/dist/assets/RemoteAccessPage-BJ_ckkOV.js +1 -0
  14. package/dist/assets/RuntimeConfig-Cmn2xPQO.js +1 -0
  15. package/dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-BT13qpR_.js} +1 -1
  16. package/dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-CvqEVn0B.js} +2 -2
  17. package/dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-DHHcYznk.js} +2 -2
  18. package/dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
  19. package/dist/assets/chat-session-display-VW6ZMvZP.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  21. package/dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
  22. package/dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
  23. package/dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
  24. package/dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
  25. package/dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
  26. package/dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
  27. package/dist/assets/i18n-CDHMXlRZ.js +1 -0
  28. package/dist/assets/index-BlH4-cBw.css +1 -0
  29. package/dist/assets/{index-DvKS3L9j.js → index-C6d0xmtm.js} +3 -3
  30. package/dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
  31. package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  32. package/dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
  33. package/dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
  34. package/dist/assets/plus-PHf8q-Ct.js +1 -0
  35. package/dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
  36. package/dist/assets/provider-models-bz5y28rq.js +1 -0
  37. package/dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
  38. package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  39. package/dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
  40. package/dist/assets/search-C91yH_6y.js +1 -0
  41. package/dist/assets/{security-config-6t78Ph-I.js → security-config-T5zpg16O.js} +1 -1
  42. package/dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
  43. package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
  44. package/dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
  45. package/dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
  46. package/dist/assets/tabs-custom-DXv507_2.js +1 -0
  47. package/dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
  48. package/dist/assets/useConfirmDialog-Bs5Ll17m.js +1 -0
  49. package/dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
  50. package/dist/assets/x-D7Q1yqSF.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +3 -3
  53. package/src/api/ncp-session.test.ts +37 -0
  54. package/src/api/ncp-session.ts +29 -1
  55. package/src/api/server-path.ts +23 -0
  56. package/src/api/types.ts +41 -0
  57. package/src/components/chat/ChatConversationPanel.test.tsx +43 -7
  58. package/src/components/chat/ChatConversationPanel.tsx +23 -17
  59. package/src/components/chat/ChatSidebar.test.tsx +2 -2
  60. package/src/components/chat/ChatSidebar.tsx +2 -2
  61. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
  62. package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
  63. package/src/components/chat/adapters/chat-message-part.adapter.ts +13 -9
  64. package/src/components/chat/adapters/chat-message.adapter.test.ts +76 -4
  65. package/src/components/chat/adapters/{chat-message.file-operation-card.ts → file-operation/card.ts} +74 -181
  66. package/src/components/chat/adapters/{chat-message.file-operation-diff.ts → file-operation/diff.ts} +178 -188
  67. package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
  68. package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
  69. package/src/components/chat/chat-composer-state.ts +3 -3
  70. package/src/components/chat/chat-session-display.test.ts +21 -0
  71. package/src/components/chat/chat-session-display.ts +6 -1
  72. package/src/components/chat/containers/chat-input-bar.container.tsx +21 -24
  73. package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
  74. package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
  75. package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
  76. package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
  77. package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
  78. package/src/components/chat/ncp/NcpChatPage.tsx +55 -17
  79. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
  80. package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
  81. package/src/components/chat/ncp/ncp-session-adapter.test.ts +3 -0
  82. package/src/components/chat/ncp/ncp-session-adapter.ts +16 -0
  83. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
  84. package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
  85. package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
  86. package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
  87. package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
  88. package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
  89. package/src/components/chat/stores/chat-input.store.ts +6 -3
  90. package/src/components/chat/stores/chat-thread.store.ts +6 -2
  91. package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
  92. package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
  93. package/src/hooks/server-path/use-server-path-browse.ts +19 -0
  94. package/src/hooks/useConfig.ts +26 -1
  95. package/src/lib/i18n/i18n-language-owner.ts +94 -0
  96. package/src/lib/i18n/i18n.path-picker.ts +12 -0
  97. package/src/lib/i18n.chat.ts +23 -0
  98. package/src/lib/i18n.ts +21 -84
  99. package/src/lib/session-project/session-project.utils.ts +30 -0
  100. package/dist/assets/ChatPage-FdT3pDnw.js +0 -42
  101. package/dist/assets/DocBrowser-CMdPdbZj.js +0 -1
  102. package/dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
  103. package/dist/assets/ModelConfig-DmCY6jWM.js +0 -1
  104. package/dist/assets/ProvidersList-ClT-34aX.js +0 -1
  105. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
  106. package/dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
  107. package/dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
  108. package/dist/assets/i18n-CSytxMFI.js +0 -1
  109. package/dist/assets/index-CUy6doWo.css +0 -1
  110. package/dist/assets/loader-circle-B2J777gj.js +0 -1
  111. package/dist/assets/plus-CM9XJ0Tf.js +0 -1
  112. package/dist/assets/provider-models-C8JQUd1E.js +0 -1
  113. package/dist/assets/search-Ctaw34Kp.js +0 -1
  114. package/dist/assets/skeleton-Bycyb0zU.js +0 -1
  115. package/dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
  116. package/dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
  117. package/dist/assets/x-CHOBE-63.js +0 -1
  118. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  119. /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  120. /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: [`${texts.slashSkillSpecLabel}: ${record.key}`],
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,14 +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";
13
- import { buildFileOperationCardData } from "@/components/chat/adapters/chat-message.file-operation-card";
11
+ import { buildFileOperationCardData } from "@/components/chat/adapters/file-operation/card";
14
12
  import { buildSubagentToolCard } from "@/components/chat/adapters/chat-message.subagent-tool-card";
15
13
  import type {
16
14
  ChatMessagePartViewModel,
@@ -116,7 +114,9 @@ function readOptionalNumber(value: unknown): number | null {
116
114
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
117
115
  }
118
116
 
119
- function isTerminalResultRecord(value: unknown): value is Record<string, unknown> {
117
+ function isTerminalResultRecord(
118
+ value: unknown,
119
+ ): value is Record<string, unknown> {
120
120
  if (!isRecord(value)) {
121
121
  return false;
122
122
  }
@@ -181,9 +181,10 @@ function buildToolCard(
181
181
  toolName: toolCard.name,
182
182
  summary: toolCard.detail,
183
183
  inputLabel: texts.toolInputLabel,
184
- input: "input" in toolCard && typeof toolCard.input === "string"
185
- ? toolCard.input
186
- : undefined,
184
+ input:
185
+ "input" in toolCard && typeof toolCard.input === "string"
186
+ ? toolCard.input
187
+ : undefined,
187
188
  output: toolCard.text,
188
189
  outputData: toolCard.outputData,
189
190
  hasResult: Boolean(toolCard.hasResult),
@@ -269,7 +270,10 @@ function parseStructuredValue(value: unknown): unknown {
269
270
  }
270
271
  }
271
272
 
272
- function buildToolInvocationInput(args?: unknown, parsedArgs?: unknown): string | undefined {
273
+ function buildToolInvocationInput(
274
+ args?: unknown,
275
+ parsedArgs?: unknown,
276
+ ): string | undefined {
273
277
  const source = parsedArgs ?? parseStructuredValue(args);
274
278
  const text = stringifyUnknown(source).trim();
275
279
  return text || undefined;
@@ -488,6 +488,11 @@ it("builds edit-file previews from structured args before the tool finishes", ()
488
488
  },
489
489
  ] as unknown as ChatMessageSource[]);
490
490
 
491
+ const editLines =
492
+ adapted[0]?.parts[0]?.type === "tool-card"
493
+ ? (adapted[0].parts[0].card.fileOperation?.blocks[0]?.lines ?? [])
494
+ : [];
495
+
491
496
  expect(adapted[0]?.parts[0]).toMatchObject({
492
497
  type: "tool-card",
493
498
  card: {
@@ -513,6 +518,71 @@ it("builds edit-file previews from structured args before the tool finishes", ()
513
518
  },
514
519
  },
515
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
+ });
516
586
  });
517
587
 
518
588
  it("builds write-file previews from partial native args before the JSON is complete", () => {
@@ -577,7 +647,7 @@ it("keeps completed write-file cards in preview mode instead of falling back to
577
647
  toolName: "write_file",
578
648
  args: JSON.stringify({
579
649
  path: "games/snake.html",
580
- content: "<!DOCTYPE html>\n<canvas id=\"game\"></canvas>",
650
+ content: '<!DOCTYPE html>\n<canvas id="game"></canvas>',
581
651
  }),
582
652
  result: "Wrote 3906 bytes to games/snake.html",
583
653
  },
@@ -605,7 +675,7 @@ it("keeps completed write-file cards in preview mode instead of falling back to
605
675
  },
606
676
  {
607
677
  kind: "add",
608
- text: "<canvas id=\"game\"></canvas>",
678
+ text: '<canvas id="game"></canvas>',
609
679
  newLineNumber: 2,
610
680
  },
611
681
  ],
@@ -641,7 +711,7 @@ it("renders codex file_change results as structured diff previews", () => {
641
711
  diff: [
642
712
  "--- a/src/main.ts",
643
713
  "+++ b/src/main.ts",
644
- "@@",
714
+ "@@ -109,1 +109,1 @@",
645
715
  "-console.log('old');",
646
716
  "+console.log('new');",
647
717
  ].join("\n"),
@@ -656,7 +726,7 @@ it("renders codex file_change results as structured diff previews", () => {
656
726
  diff: [
657
727
  "--- a/src/main.ts",
658
728
  "+++ b/src/main.ts",
659
- "@@",
729
+ "@@ -109,1 +109,1 @@",
660
730
  "-console.log('old');",
661
731
  "+console.log('new');",
662
732
  ].join("\n"),
@@ -683,10 +753,12 @@ it("renders codex file_change results as structured diff previews", () => {
683
753
  {
684
754
  kind: "remove",
685
755
  text: "console.log('old');",
756
+ oldLineNumber: 109,
686
757
  },
687
758
  {
688
759
  kind: "add",
689
760
  text: "console.log('new');",
761
+ newLineNumber: 109,
690
762
  },
691
763
  ],
692
764
  },
@@ -1,12 +1,23 @@
1
- import type {
2
- ChatFileOperationBlockViewModel,
3
- } from "@nextclaw/agent-chat-ui";
1
+ import type { ChatFileOperationBlockViewModel } from "@nextclaw/agent-chat-ui";
4
2
  import {
5
3
  buildFullReplaceBlock,
6
4
  buildRawPreviewBlock,
7
5
  parsePatchBlocks,
8
6
  type ParsedBlock,
9
- } from "@/components/chat/adapters/chat-message.file-operation-diff";
7
+ } from "@/components/chat/adapters/file-operation/diff";
8
+ import {
9
+ isRecord,
10
+ readAfterText,
11
+ readBeforeText,
12
+ readNewStartLine,
13
+ readNonEmptyString,
14
+ readOldStartLine,
15
+ readOperation,
16
+ readPartialRecordPayload,
17
+ readPatchText,
18
+ readPath,
19
+ readRecordPayload,
20
+ } from "@/components/chat/adapters/file-operation/record-readers";
10
21
  import { readPartialJsonStringField } from "@/components/chat/adapters/chat-message.partial-json";
11
22
 
12
23
  type ToolInvocationSource = {
@@ -33,167 +44,9 @@ const FILE_TOOL_NAMES = new Set([
33
44
  "apply_patch",
34
45
  ]);
35
46
 
36
- function isRecord(value: unknown): value is Record<string, unknown> {
37
- return typeof value === "object" && value !== null && !Array.isArray(value);
38
- }
39
-
40
- function readNonEmptyString(value: unknown): string | null {
41
- if (typeof value !== "string") {
42
- return null;
43
- }
44
- const trimmed = value.trim();
45
- return trimmed.length > 0 ? trimmed : null;
46
- }
47
-
48
- function normalizePath(value: unknown): string | null {
49
- if (typeof value === "string" && value.trim()) {
50
- return value.trim();
51
- }
52
- return null;
53
- }
54
-
55
- function readRecordPayload(value: unknown): Record<string, unknown> | null {
56
- if (isRecord(value)) {
57
- return value;
58
- }
59
- if (typeof value !== "string") {
60
- return null;
61
- }
62
- const trimmed = value.trim();
63
- if (!trimmed.startsWith("{")) {
64
- return null;
65
- }
66
- try {
67
- const parsed = JSON.parse(trimmed) as unknown;
68
- return isRecord(parsed) ? parsed : null;
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
- function readPartialRecordPayload(value: unknown): Record<string, unknown> | null {
75
- if (isRecord(value)) {
76
- return value;
77
- }
78
- if (typeof value !== "string") {
79
- return null;
80
- }
81
- const trimmed = value.trim();
82
- if (!trimmed.startsWith("{")) {
83
- return null;
84
- }
85
- const path =
86
- readPartialJsonStringField(trimmed, [
87
- "path",
88
- "filePath",
89
- "file_path",
90
- "targetPath",
91
- "target_path",
92
- "filename",
93
- "name",
94
- ])?.value ?? null;
95
- const content =
96
- readPartialJsonStringField(
97
- trimmed,
98
- ["content", "text", "afterText", "after_text"],
99
- )?.value ?? null;
100
- const oldText =
101
- readPartialJsonStringField(
102
- trimmed,
103
- ["oldText", "beforeText", "before_text"],
104
- )?.value ?? null;
105
- const newText =
106
- readPartialJsonStringField(
107
- trimmed,
108
- ["newText", "afterText", "after_text"],
109
- )?.value ?? null;
110
- const patch =
111
- readPartialJsonStringField(
112
- trimmed,
113
- ["patch", "diff", "unifiedDiff", "unified_diff"],
114
- )?.value ?? null;
115
-
116
- const partialRecord: Record<string, unknown> = {};
117
- if (path) {
118
- partialRecord.path = path;
119
- }
120
- if (content) {
121
- partialRecord.content = content;
122
- }
123
- if (oldText) {
124
- partialRecord.oldText = oldText;
125
- }
126
- if (newText) {
127
- partialRecord.newText = newText;
128
- }
129
- if (patch) {
130
- partialRecord.patch = patch;
131
- }
132
-
133
- return Object.keys(partialRecord).length > 0 ? partialRecord : null;
134
- }
135
-
136
- function readPath(record: Record<string, unknown>): string | null {
137
- return (
138
- normalizePath(record.path) ??
139
- normalizePath(record.filePath) ??
140
- normalizePath(record.file_path) ??
141
- normalizePath(record.targetPath) ??
142
- normalizePath(record.target_path) ??
143
- normalizePath(record.filename) ??
144
- normalizePath(record.name)
145
- );
146
- }
147
-
148
- function readOperation(record: Record<string, unknown>): string | null {
149
- return (
150
- readNonEmptyString(record.operation) ??
151
- readNonEmptyString(record.op) ??
152
- readNonEmptyString(record.action) ??
153
- readNonEmptyString(record.kind) ??
154
- readNonEmptyString(record.type) ??
155
- readNonEmptyString(record.status)
156
- );
157
- }
158
-
159
- function readPatchText(record: Record<string, unknown>): string | null {
160
- return (
161
- readNonEmptyString(record.patch) ??
162
- readNonEmptyString(record.diff) ??
163
- readNonEmptyString(record.unifiedDiff) ??
164
- readNonEmptyString(record.unified_diff)
165
- );
166
- }
167
-
168
- function readBeforeText(record: Record<string, unknown>): string | null {
169
- return (
170
- readNonEmptyString(record.beforeText) ??
171
- readNonEmptyString(record.before_text) ??
172
- readNonEmptyString(record.oldText) ??
173
- readNonEmptyString(record.old_text) ??
174
- readNonEmptyString(record.oldContent) ??
175
- readNonEmptyString(record.old_content) ??
176
- readNonEmptyString(record.before) ??
177
- readNonEmptyString(record.previous)
178
- );
179
- }
180
-
181
- function readAfterText(record: Record<string, unknown>): string | null {
182
- return (
183
- readNonEmptyString(record.afterText) ??
184
- readNonEmptyString(record.after_text) ??
185
- readNonEmptyString(record.newText) ??
186
- readNonEmptyString(record.new_text) ??
187
- readNonEmptyString(record.newContent) ??
188
- readNonEmptyString(record.new_content) ??
189
- readNonEmptyString(record.content) ??
190
- readNonEmptyString(record.text) ??
191
- readNonEmptyString(record.after) ??
192
- readNonEmptyString(record.updated)
193
- );
194
- }
195
-
196
- function finalizeParsedBlocks(blocks: ParsedBlock[]): FileOperationCardData | null {
47
+ function finalizeParsedBlocks(
48
+ blocks: ParsedBlock[],
49
+ ): FileOperationCardData | null {
197
50
  const normalizedBlocks = blocks
198
51
  .map((block, index) => ({
199
52
  key: `${block.path}-${index + 1}`,
@@ -224,9 +77,14 @@ function finalizeParsedBlocks(blocks: ParsedBlock[]): FileOperationCardData | nu
224
77
  };
225
78
  }
226
79
 
227
- function buildBlockFromChangeRecord(record: Record<string, unknown>, fallbackPath: string): ParsedBlock | null {
80
+ function buildBlockFromChangeRecord(
81
+ record: Record<string, unknown>,
82
+ fallbackPath: string,
83
+ ): ParsedBlock | null {
228
84
  const path = readPath(record) ?? fallbackPath;
229
85
  const operation = readOperation(record);
86
+ const oldStartLine = readOldStartLine(record);
87
+ const newStartLine = readNewStartLine(record);
230
88
  const patchText = readPatchText(record);
231
89
  if (patchText) {
232
90
  const parsedBlocks = parsePatchBlocks(patchText);
@@ -243,6 +101,8 @@ function buildBlockFromChangeRecord(record: Record<string, unknown>, fallbackPat
243
101
  beforeText,
244
102
  afterText,
245
103
  operation,
104
+ oldStartLine,
105
+ newStartLine,
246
106
  });
247
107
  }
248
108
 
@@ -252,6 +112,8 @@ function buildBlockFromChangeRecord(record: Record<string, unknown>, fallbackPat
252
112
  path,
253
113
  text: previewText,
254
114
  operation,
115
+ oldStartLine,
116
+ newStartLine,
255
117
  });
256
118
  }
257
119
 
@@ -281,7 +143,9 @@ function buildBlocksFromChanges(changes: unknown): ParsedBlock[] {
281
143
  blocks.push(block);
282
144
  return;
283
145
  }
284
- const nestedChanges = Array.isArray(entry.changes) ? buildBlocksFromChanges(entry.changes) : [];
146
+ const nestedChanges = Array.isArray(entry.changes)
147
+ ? buildBlocksFromChanges(entry.changes)
148
+ : [];
285
149
  if (nestedChanges.length > 0) {
286
150
  blocks.push(...nestedChanges);
287
151
  }
@@ -289,7 +153,9 @@ function buildBlocksFromChanges(changes: unknown): ParsedBlock[] {
289
153
  return blocks;
290
154
  }
291
155
 
292
- function buildFileChangeCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
156
+ function buildFileChangeCardData(
157
+ invocation: ToolInvocationSource,
158
+ ): FileOperationCardData | null {
293
159
  const sourceRecord =
294
160
  readRecordPayload(invocation.result) ??
295
161
  readRecordPayload(invocation.parsedArgs) ??
@@ -301,7 +167,9 @@ function buildFileChangeCardData(invocation: ToolInvocationSource): FileOperatio
301
167
  return finalizeParsedBlocks(blocks);
302
168
  }
303
169
 
304
- function buildReadFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
170
+ function buildReadFileCardData(
171
+ invocation: ToolInvocationSource,
172
+ ): FileOperationCardData | null {
305
173
  const argsRecord =
306
174
  readRecordPayload(invocation.parsedArgs) ??
307
175
  readRecordPayload(invocation.args) ??
@@ -322,7 +190,9 @@ function buildReadFileCardData(invocation: ToolInvocationSource): FileOperationC
322
190
  );
323
191
  }
324
192
 
325
- function buildWriteFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
193
+ function buildWriteFileCardData(
194
+ invocation: ToolInvocationSource,
195
+ ): FileOperationCardData | null {
326
196
  const isStreamingPartialCall = invocation.status === "partial-call";
327
197
  if (isStreamingPartialCall && typeof invocation.args === "string") {
328
198
  const pathField = readPartialJsonStringField(invocation.args, [
@@ -334,10 +204,12 @@ function buildWriteFileCardData(invocation: ToolInvocationSource): FileOperation
334
204
  "filename",
335
205
  "name",
336
206
  ]);
337
- const contentField = readPartialJsonStringField(
338
- invocation.args,
339
- ["content", "text", "afterText", "after_text"],
340
- );
207
+ const contentField = readPartialJsonStringField(invocation.args, [
208
+ "content",
209
+ "text",
210
+ "afterText",
211
+ "after_text",
212
+ ]);
341
213
  if (pathField?.value && contentField?.value) {
342
214
  const previewBlock = buildRawPreviewBlock({
343
215
  path: pathField.value,
@@ -373,17 +245,32 @@ function buildWriteFileCardData(invocation: ToolInvocationSource): FileOperation
373
245
  );
374
246
  }
375
247
 
376
- function buildEditFileCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
248
+ function buildEditFileCardData(
249
+ invocation: ToolInvocationSource,
250
+ ): FileOperationCardData | null {
251
+ const resultRecord = readRecordPayload(invocation.result);
377
252
  const argsRecord =
378
253
  readRecordPayload(invocation.parsedArgs) ??
379
254
  readRecordPayload(invocation.args) ??
380
255
  readPartialRecordPayload(invocation.args);
381
- if (!argsRecord) {
256
+ if (!resultRecord && !argsRecord) {
382
257
  return null;
383
258
  }
384
- const path = readPath(argsRecord);
385
- const beforeText = readNonEmptyString(argsRecord.oldText) ?? readNonEmptyString(argsRecord.beforeText);
386
- const afterText = readNonEmptyString(argsRecord.newText) ?? readNonEmptyString(argsRecord.afterText);
259
+ const path =
260
+ (resultRecord ? readPath(resultRecord) : null) ??
261
+ (argsRecord ? readPath(argsRecord) : null);
262
+ const beforeText =
263
+ (resultRecord ? readBeforeText(resultRecord) : null) ??
264
+ (argsRecord ? readBeforeText(argsRecord) : null);
265
+ const afterText =
266
+ (resultRecord ? readAfterText(resultRecord) : null) ??
267
+ (argsRecord ? readAfterText(argsRecord) : null);
268
+ const oldStartLine =
269
+ (resultRecord ? readOldStartLine(resultRecord) : null) ??
270
+ (argsRecord ? readOldStartLine(argsRecord) : null);
271
+ const newStartLine =
272
+ (resultRecord ? readNewStartLine(resultRecord) : null) ??
273
+ (argsRecord ? readNewStartLine(argsRecord) : null);
387
274
  if (!path || (beforeText == null && afterText == null)) {
388
275
  return null;
389
276
  }
@@ -394,14 +281,20 @@ function buildEditFileCardData(invocation: ToolInvocationSource): FileOperationC
394
281
  beforeText,
395
282
  afterText,
396
283
  operation: "edit",
284
+ oldStartLine,
285
+ newStartLine,
397
286
  }),
398
287
  ].filter((block): block is ParsedBlock => Boolean(block)),
399
288
  );
400
289
  }
401
290
 
402
- function buildApplyPatchCardData(invocation: ToolInvocationSource): FileOperationCardData | null {
291
+ function buildApplyPatchCardData(
292
+ invocation: ToolInvocationSource,
293
+ ): FileOperationCardData | null {
403
294
  const parsedArgsRecord = readRecordPayload(invocation.parsedArgs);
404
- const argsRecord = readRecordPayload(invocation.args) ?? readPartialRecordPayload(invocation.args);
295
+ const argsRecord =
296
+ readRecordPayload(invocation.args) ??
297
+ readPartialRecordPayload(invocation.args);
405
298
  const patchText =
406
299
  readNonEmptyString(invocation.args) ??
407
300
  (parsedArgsRecord ? readNonEmptyString(parsedArgsRecord.patch) : null) ??