@agimon-ai/doompi-edit 0.0.1-alpha.3 → 0.0.1-alpha.36

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/README.md CHANGED
@@ -13,7 +13,7 @@ compatible hashline producer. Stale or ambiguous mutations fail before any file
13
13
  ## Requirements
14
14
 
15
15
  - Node.js 22.19.0 or newer
16
- - Pi 0.84.2 and Pi TUI 0.84.2
16
+ - Pi 0.85.0 and Pi TUI 0.85.0
17
17
 
18
18
  ## Install
19
19
 
@@ -1,8 +1,8 @@
1
1
  import { EditParams } from "../../schemas/editTool.cjs";
2
2
  import { EditToolDetails, ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/editTool.d.ts
4
- declare function registerHashlineEditTool(pi: Pick<ExtensionAPI, 'registerTool'>): void;
5
- declare function executeHashlineEdit(params: EditParams, cwd: string, signal: AbortSignal | undefined): Promise<{
4
+ export declare function registerHashlineEditTool(pi: Pick<ExtensionAPI, 'registerTool'>): void;
5
+ export declare function executeHashlineEdit(params: EditParams, cwd: string, signal: AbortSignal | undefined): Promise<{
6
6
  content: [{
7
7
  type: 'text';
8
8
  text: string;
@@ -10,5 +10,4 @@ declare function executeHashlineEdit(params: EditParams, cwd: string, signal: Ab
10
10
  details: EditToolDetails;
11
11
  }>;
12
12
  //#endregion
13
- export { executeHashlineEdit, registerHashlineEditTool };
14
13
  //# sourceMappingURL=editTool.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editTool.d.cts","names":[],"sources":["../../../src/adapters/pi/editTool.ts"],"mappings":";;;iBAsBgB,yBAAyB,IAAI,KAAK;iBA+B5B,oBACpB,QAAQ,YACR,aACA,QAAQ,0BACP;EAAU;IAAY;IAAc;;EAAiB,SAAS"}
1
+ {"version":3,"file":"editTool.d.cts","names":[],"sources":["../../../src/adapters/pi/editTool.ts"],"mappings":";;;wBAsBgB,yBAAyB,IAAI,KAAK;wBA+B5B,oBACpB,QAAQ,YACR,aACA,QAAQ,0BACP;EAAU;IAAY;IAAc;;EAAiB,SAAS"}
@@ -1,8 +1,8 @@
1
1
  import { EditParams } from "../../schemas/editTool.mjs";
2
2
  import { EditToolDetails, ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/editTool.d.ts
4
- declare function registerHashlineEditTool(pi: Pick<ExtensionAPI, 'registerTool'>): void;
5
- declare function executeHashlineEdit(params: EditParams, cwd: string, signal: AbortSignal | undefined): Promise<{
4
+ export declare function registerHashlineEditTool(pi: Pick<ExtensionAPI, 'registerTool'>): void;
5
+ export declare function executeHashlineEdit(params: EditParams, cwd: string, signal: AbortSignal | undefined): Promise<{
6
6
  content: [{
7
7
  type: 'text';
8
8
  text: string;
@@ -10,5 +10,4 @@ declare function executeHashlineEdit(params: EditParams, cwd: string, signal: Ab
10
10
  details: EditToolDetails;
11
11
  }>;
12
12
  //#endregion
13
- export { executeHashlineEdit, registerHashlineEditTool };
14
13
  //# sourceMappingURL=editTool.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editTool.d.mts","names":[],"sources":["../../../src/adapters/pi/editTool.ts"],"mappings":";;;iBAsBgB,yBAAyB,IAAI,KAAK;iBA+B5B,oBACpB,QAAQ,YACR,aACA,QAAQ,0BACP;EAAU;IAAY;IAAc;;EAAiB,SAAS"}
1
+ {"version":3,"file":"editTool.d.mts","names":[],"sources":["../../../src/adapters/pi/editTool.ts"],"mappings":";;;wBAsBgB,yBAAyB,IAAI,KAAK;wBA+B5B,oBACpB,QAAQ,YACR,aACA,QAAQ,0BACP;EAAU;IAAY;IAAc;;EAAiB,SAAS"}
@@ -2,9 +2,9 @@ import { Context } from "@deepseek-ai/cordis";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/extension.d.ts
4
4
  /** Install the hashline edit tool after claiming its shared Pi name. */
5
- declare function installDoomPiEditRuntime(cordis: Context, pi: ExtensionAPI): void;
5
+ export declare function installDoomPiEditRuntime(cordis: Context, pi: ExtensionAPI): void;
6
6
  /** Connect the package's standard Pi entry to DoomPi's shared Cordis host. */
7
- declare function activateDoomPiEditExtension(pi: ExtensionAPI): Promise<void>;
7
+ export declare function activateDoomPiEditExtension(pi: ExtensionAPI): Promise<void>;
8
8
  //#endregion
9
- export { activateDoomPiEditExtension, activateDoomPiEditExtension as default, installDoomPiEditRuntime };
9
+ export { activateDoomPiEditExtension as default };
10
10
  //# sourceMappingURL=extension.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAYgB,yBAAyB,QAAQ,SAAS,IAAI;;iBAkBxC,4BAA4B,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAYgB,yBAAyB,QAAQ,SAAS,IAAI;;wBAkBxC,4BAA4B,IAAI,eAAe"}
@@ -2,9 +2,9 @@ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/adapters/pi/extension.d.ts
4
4
  /** Install the hashline edit tool after claiming its shared Pi name. */
5
- declare function installDoomPiEditRuntime(cordis: Context, pi: ExtensionAPI): void;
5
+ export declare function installDoomPiEditRuntime(cordis: Context, pi: ExtensionAPI): void;
6
6
  /** Connect the package's standard Pi entry to DoomPi's shared Cordis host. */
7
- declare function activateDoomPiEditExtension(pi: ExtensionAPI): Promise<void>;
7
+ export declare function activateDoomPiEditExtension(pi: ExtensionAPI): Promise<void>;
8
8
  //#endregion
9
- export { activateDoomPiEditExtension, activateDoomPiEditExtension as default, installDoomPiEditRuntime };
9
+ export { activateDoomPiEditExtension as default };
10
10
  //# sourceMappingURL=extension.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAYgB,yBAAyB,QAAQ,SAAS,IAAI;;iBAkBxC,4BAA4B,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAYgB,yBAAyB,QAAQ,SAAS,IAAI;;wBAkBxC,4BAA4B,IAAI,eAAe"}
@@ -1,11 +1,11 @@
1
1
  import { Static, Type } from "typebox";
2
2
  //#region src/schemas/editTool.d.ts
3
- declare const HashlineRangeSchema: Type.TObject<{
3
+ export declare const HashlineRangeSchema: Type.TObject<{
4
4
  from: Type.TString;
5
5
  to: Type.TString;
6
6
  content: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
7
7
  }>;
8
- declare const EditParamsSchema: Type.TObject<{
8
+ export declare const EditParamsSchema: Type.TObject<{
9
9
  path: Type.TString;
10
10
  hash: Type.TString;
11
11
  edits: Type.TArray<Type.TObject<{
@@ -14,8 +14,7 @@ declare const EditParamsSchema: Type.TObject<{
14
14
  content: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
15
15
  }>>;
16
16
  }>;
17
- type HashlineRange = Static<typeof HashlineRangeSchema>;
18
- type EditParams = Static<typeof EditParamsSchema>;
17
+ export type HashlineRange = Static<typeof HashlineRangeSchema>;
18
+ export type EditParams = Static<typeof EditParamsSchema>;
19
19
  //#endregion
20
- export { EditParams, EditParamsSchema, HashlineRange, HashlineRangeSchema };
21
20
  //# sourceMappingURL=editTool.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editTool.d.cts","names":[],"sources":["../../src/schemas/editTool.ts"],"mappings":";;cAEa,qBAAmB,KAAA;;;;;cAWnB,kBAAgB,KAAA;;;;;;;;;KASjB,gBAAgB,cAAc;KAC9B,aAAa,cAAc"}
1
+ {"version":3,"file":"editTool.d.cts","names":[],"sources":["../../src/schemas/editTool.ts"],"mappings":";;qBAEa,qBAAmB,KAAA;;;;;qBAWnB,kBAAgB,KAAA;;;;;;;;;YASjB,gBAAgB,cAAc;YAC9B,aAAa,cAAc"}
@@ -1,11 +1,11 @@
1
1
  import { Static, Type } from "typebox";
2
2
  //#region src/schemas/editTool.d.ts
3
- declare const HashlineRangeSchema: Type.TObject<{
3
+ export declare const HashlineRangeSchema: Type.TObject<{
4
4
  from: Type.TString;
5
5
  to: Type.TString;
6
6
  content: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
7
7
  }>;
8
- declare const EditParamsSchema: Type.TObject<{
8
+ export declare const EditParamsSchema: Type.TObject<{
9
9
  path: Type.TString;
10
10
  hash: Type.TString;
11
11
  edits: Type.TArray<Type.TObject<{
@@ -14,8 +14,7 @@ declare const EditParamsSchema: Type.TObject<{
14
14
  content: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
15
15
  }>>;
16
16
  }>;
17
- type HashlineRange = Static<typeof HashlineRangeSchema>;
18
- type EditParams = Static<typeof EditParamsSchema>;
17
+ export type HashlineRange = Static<typeof HashlineRangeSchema>;
18
+ export type EditParams = Static<typeof EditParamsSchema>;
19
19
  //#endregion
20
- export { EditParams, EditParamsSchema, HashlineRange, HashlineRangeSchema };
21
20
  //# sourceMappingURL=editTool.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editTool.d.mts","names":[],"sources":["../../src/schemas/editTool.ts"],"mappings":";;cAEa,qBAAmB,KAAA;;;;;cAWnB,kBAAgB,KAAA;;;;;;;;;KASjB,gBAAgB,cAAc;KAC9B,aAAa,cAAc"}
1
+ {"version":3,"file":"editTool.d.mts","names":[],"sources":["../../src/schemas/editTool.ts"],"mappings":";;qBAEa,qBAAmB,KAAA;;;;;qBAWnB,kBAAgB,KAAA;;;;;;;;;YASjB,gBAAgB,cAAc;YAC9B,aAAa,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-edit",
3
- "version": "0.0.1-alpha.3",
3
+ "version": "0.0.1-alpha.36",
4
4
  "description": "Snapshot-bound hashline edit tool for Pi and DoomPi.",
5
5
  "keywords": [
6
6
  "ai",
@@ -13,10 +13,20 @@
13
13
  "pi-package"
14
14
  ],
15
15
  "homepage": "https://agimon.ai",
16
+ "bugs": {
17
+ "url": "https://github.com/AgiFlow/doompi/issues"
18
+ },
16
19
  "license": "MIT",
17
20
  "author": "Vuong Ngo",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/AgiFlow/doompi.git",
24
+ "directory": "packages/default/doompi-edit"
25
+ },
18
26
  "files": [
19
- "dist"
27
+ "dist",
28
+ "src/web",
29
+ "src/exports/webClient.ts"
20
30
  ],
21
31
  "type": "module",
22
32
  "main": "./dist/index.cjs",
@@ -39,24 +49,30 @@
39
49
  "access": "public"
40
50
  },
41
51
  "dependencies": {
42
- "@deepseek-ai/cordis": "4.0.1",
43
- "typebox": "1.3.16",
44
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.31",
45
- "@agimon-ai/doompi-hashline": "0.0.1-alpha.3",
46
- "@agimon-ai/doompi-ui": "0.0.1-alpha.31"
52
+ "@deepseek-ai/cordis": "4.0.2",
53
+ "typebox": "1.3.25",
54
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.63",
55
+ "@agimon-ai/doompi-hashline": "0.0.1-alpha.34",
56
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.23",
57
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.26",
58
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.64"
47
59
  },
48
60
  "devDependencies": {
49
- "@earendil-works/pi-coding-agent": "0.84.2",
50
- "@earendil-works/pi-tui": "0.84.2",
51
- "@types/node": "26.2.0",
52
- "@vitest/coverage-v8": "4.1.11",
53
- "tsdown": "0.22.14",
61
+ "@earendil-works/pi-coding-agent": "0.85.1",
62
+ "@earendil-works/pi-tui": "0.85.1",
63
+ "@tanstack/react-store": "0.11.1",
64
+ "@tanstack/store": "0.11.1",
65
+ "@types/node": "26.4.1",
66
+ "@types/react": "19.2.18",
67
+ "@vitest/coverage-v8": "5.0.0",
68
+ "react": "19.2.8",
69
+ "tsdown": "0.23.0",
54
70
  "typescript": "7.0.2",
55
- "vitest": "4.1.11"
71
+ "vitest": "5.0.0"
56
72
  },
57
73
  "peerDependencies": {
58
- "@earendil-works/pi-coding-agent": "0.84.2",
59
- "@earendil-works/pi-tui": "0.84.2"
74
+ "@earendil-works/pi-coding-agent": "0.85.1",
75
+ "@earendil-works/pi-tui": "0.85.1"
60
76
  },
61
77
  "peerDependenciesMeta": {
62
78
  "@earendil-works/pi-coding-agent": {
@@ -69,6 +85,11 @@
69
85
  "engines": {
70
86
  "node": ">=22.19.0"
71
87
  },
88
+ "doompiWeb": {
89
+ "pluginId": "edit",
90
+ "channels": [],
91
+ "client": "./src/exports/webClient.ts"
92
+ },
72
93
  "pi": {
73
94
  "extensions": [
74
95
  "./dist/extensions/pi.mjs"
@@ -77,7 +98,7 @@
77
98
  "scripts": {
78
99
  "build": "tsdown",
79
100
  "test": "vitest --run",
80
- "typecheck": "tsc --noEmit",
101
+ "typecheck": "tsc --noEmit && tsc --noEmit -p src/web/tsconfig.json",
81
102
  "lint": "oxlint . && oxfmt . --check",
82
103
  "fixcode": "oxlint . --fix && oxfmt ."
83
104
  }
@@ -0,0 +1 @@
1
+ export { webPlugin } from '../web/index.ts';
@@ -0,0 +1,120 @@
1
+ import {
2
+ collapseLines,
3
+ MessageItem,
4
+ MessageItemBody,
5
+ MessageItemHeader,
6
+ MessageItemStatus,
7
+ SyntaxLine,
8
+ ToolPathLink,
9
+ toolTone,
10
+ useSyntaxLines,
11
+ } from '@agimon-ai/doompi-web-components';
12
+ import type { ToolMessageRenderProps } from '@agimon-ai/doompi-web-contracts';
13
+ import { type DiffRow, editCallView, editResultView, resultTextLines } from '../lib/editToolView.ts';
14
+
15
+ /** Diff rows shown until the item expands; about what the old scroll box held. */
16
+ const COLLAPSED_ROWS = 24;
17
+
18
+ // Doom carries a diff on the background the way Magit does: a green wash on
19
+ // added rows, a red wash on removed ones, and the foreground left to the text.
20
+ const ROW_TONE: Readonly<Record<DiffRow['marker'], string>> = {
21
+ '+': 'bg-doom-tint-green text-doom-green',
22
+ '-': 'bg-doom-tint-red text-doom-red',
23
+ ' ': 'text-doom-dim',
24
+ };
25
+
26
+ /**
27
+ * The banded rows, and the grammar over them.
28
+ *
29
+ * Its own component because the colours come from a hook, and the body that
30
+ * renders these rows is chosen inside a render prop. The rows are parsed as
31
+ * one document: a diff is not a file, so a replaced line is seen twice and a
32
+ * split string can colour oddly, which costs a token here and there and never
33
+ * costs the reader the text. The `+` and `-` wash stays underneath, so what
34
+ * changed is still legible when the grammar has coloured the foreground.
35
+ */
36
+ function DiffRows({ rows, gutter, path }: { rows: readonly DiffRow[]; gutter: number; path: string }) {
37
+ const spans = useSyntaxLines(rows.map((row) => row.content).join('\n'), { path });
38
+ return (
39
+ <>
40
+ {rows.map((row, index) => (
41
+ <span key={`${String(index)}-${row.lineNumber}`} className={`flex gap-2 ${ROW_TONE[row.marker]}`}>
42
+ <span className="shrink-0 text-right text-doom-faint" style={{ width: `${String(gutter)}ch` }}>
43
+ {row.lineNumber}
44
+ </span>
45
+ <span className="w-2 shrink-0">{row.marker === ' ' ? '' : row.marker}</span>
46
+ <SyntaxLine spans={spans?.[index]} text={row.content} className="min-w-0 whitespace-pre-wrap break-words" />
47
+ </span>
48
+ ))}
49
+ </>
50
+ );
51
+ }
52
+
53
+ /**
54
+ * The edit tool's timeline item: `path · N ranges` in the header; the display
55
+ * diff from the result's details, banded by row, in the body. A successful
56
+ * edit whose details never arrived shows nothing, and a failed one shows the
57
+ * tool's message in red.
58
+ */
59
+ export function EditToolMessage({
60
+ args,
61
+ result,
62
+ output,
63
+ running,
64
+ isError,
65
+ fileTabFor,
66
+ openTransientTab,
67
+ }: ToolMessageRenderProps) {
68
+ const call = editCallView(args);
69
+ const view = result === null || isError ? undefined : editResultView(result);
70
+ // The file this edit lands in, opened from the header rather than found
71
+ // again in a file list; plain text when nothing installed can show it.
72
+ const tab = fileTabFor(call.path);
73
+ return (
74
+ <MessageItem
75
+ tone={toolTone({ running, isError })}
76
+ expandable={view !== undefined && view.rows.length > COLLAPSED_ROWS}
77
+ >
78
+ {({ expanded }) => (
79
+ <>
80
+ <MessageItemHeader title="edit">
81
+ <span data-testid="tool-call-edit" className="flex min-w-0 flex-1 items-center gap-2">
82
+ <ToolPathLink path={call.path} {...(tab === undefined ? {} : { onOpen: () => openTransientTab(tab) })} />
83
+ <span className="shrink-0 text-doom-faint">· {call.ranges}</span>
84
+ </span>
85
+ </MessageItemHeader>
86
+ {isError ? (
87
+ <MessageItemBody data-testid="tool-result-edit" className="flex flex-col">
88
+ <MessageItemStatus tone="error">failed</MessageItemStatus>
89
+ {resultTextLines(result, output).map((line, index) => (
90
+ <span key={`${String(index)}-${line}`} className="whitespace-pre-wrap break-words text-doom-red">
91
+ {line}
92
+ </span>
93
+ ))}
94
+ </MessageItemBody>
95
+ ) : view === undefined ? (
96
+ running ? (
97
+ <MessageItemBody data-testid="tool-result-edit">
98
+ <MessageItemStatus tone="running">running</MessageItemStatus>
99
+ </MessageItemBody>
100
+ ) : null
101
+ ) : (
102
+ (() => {
103
+ const { shown, hidden } = collapseLines(view.rows, COLLAPSED_ROWS, expanded);
104
+ return (
105
+ <MessageItemBody data-testid="tool-result-edit" className="flex flex-col font-mono">
106
+ <DiffRows rows={shown} gutter={view.gutter} path={call.path} />
107
+ {hidden > 0 ? (
108
+ <MessageItemStatus expands className="pt-1">
109
+ {hidden} more rows
110
+ </MessageItemStatus>
111
+ ) : null}
112
+ </MessageItemBody>
113
+ );
114
+ })()
115
+ )}
116
+ </>
117
+ )}
118
+ </MessageItem>
119
+ );
120
+ }
@@ -0,0 +1,11 @@
1
+ import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
2
+ import { EditToolMessage } from './components/EditToolMessage.tsx';
3
+
4
+ /**
5
+ * This package's cockpit presence: the edit tool's timeline card, the web
6
+ * half of the TUI's renderCall and renderResult for hashline edits.
7
+ */
8
+ export const webPlugin = defineWebPlugin({
9
+ id: 'edit',
10
+ toolRenderers: [{ tools: ['edit'], message: EditToolMessage }],
11
+ });
@@ -0,0 +1,75 @@
1
+ import type { ToolResultView } from '@agimon-ai/doompi-web-contracts';
2
+
3
+ /** Pi emits `+12 text`, `-12 text`, ` 12 text`, and a blank-numbered ` ... ` elision between hunks. */
4
+ const DIFF_ROW_PATTERN = /^([+\- ])( *\d*) (.*)$/u;
5
+ const CONTEXT_MARKER = ' ';
6
+
7
+ export type DiffMarker = '+' | '-' | typeof CONTEXT_MARKER;
8
+
9
+ export interface DiffRow {
10
+ readonly marker: DiffMarker;
11
+ readonly lineNumber: string;
12
+ readonly content: string;
13
+ }
14
+
15
+ export interface EditCallView {
16
+ readonly path: string;
17
+ /** `N range` or `N ranges`, the count the TUI's call heading shows. */
18
+ readonly ranges: string;
19
+ }
20
+
21
+ export interface EditResultView {
22
+ readonly rows: readonly DiffRow[];
23
+ /** Widest line number among the rows, in characters, sizing the gutter. */
24
+ readonly gutter: number;
25
+ }
26
+
27
+ /** What the edit call heading shows: the path and how many ranges the call touches. */
28
+ export function editCallView(args: Readonly<Record<string, unknown>>): EditCallView {
29
+ const count = Array.isArray(args.edits) ? args.edits.length : 0;
30
+ return {
31
+ path: typeof args.path === 'string' ? args.path : '',
32
+ ranges: `${count} ${count === 1 ? 'range' : 'ranges'}`,
33
+ };
34
+ }
35
+
36
+ function toDiffMarker(value: string | undefined): DiffMarker {
37
+ return value === '+' || value === '-' ? value : CONTEXT_MARKER;
38
+ }
39
+
40
+ /** Pi's display diff as rows; a line that is not a diff row reads as unnumbered context. */
41
+ export function parseDiffRows(diff: string): DiffRow[] {
42
+ return diff.split('\n').map((line) => {
43
+ const expanded = line.replaceAll('\t', ' ');
44
+ const match = DIFF_ROW_PATTERN.exec(expanded);
45
+ if (!match) return { marker: CONTEXT_MARKER, lineNumber: '', content: expanded };
46
+ return { marker: toDiffMarker(match[1]), lineNumber: (match[2] ?? '').trim(), content: match[3] ?? '' };
47
+ });
48
+ }
49
+
50
+ /** The diff a successful edit carries in its details; undefined when the details never arrived. */
51
+ export function editResultView(result: ToolResultView | null): EditResultView | undefined {
52
+ const details = result?.details;
53
+ if (typeof details !== 'object' || details === null) return undefined;
54
+ const diff = (details as { diff?: unknown }).diff;
55
+ if (typeof diff !== 'string' || diff.length === 0) return undefined;
56
+ const rows = parseDiffRows(diff);
57
+ return { rows, gutter: rows.reduce((widest, row) => Math.max(widest, row.lineNumber.length), 1) };
58
+ }
59
+
60
+ /** The result's text lines, for the error case where the diff never happened. */
61
+ export function resultTextLines(result: ToolResultView | null, output: string): string[] {
62
+ const text =
63
+ result === null
64
+ ? output
65
+ : result.content
66
+ .flatMap((block) => {
67
+ const record =
68
+ typeof block === 'object' && block !== null ? (block as { type?: unknown; text?: unknown }) : {};
69
+ return record.type === 'text' && typeof record.text === 'string' ? [record.text] : [];
70
+ })
71
+ .join('\n');
72
+ const lines = text.replaceAll('\t', ' ').replaceAll('\r\n', '\n').split('\n');
73
+ while (lines.at(-1) === '') lines.pop();
74
+ return lines;
75
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noImplicitAny": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noImplicitReturns": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noEmit": true,
15
+ "resolveJsonModule": true,
16
+ "esModuleInterop": true,
17
+ "skipLibCheck": true,
18
+ "allowImportingTsExtensions": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "types": ["node"]
21
+ },
22
+ "include": [".", "../types", "../exports/webClient.ts"]
23
+ }