dsh-ptc-diff-card 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GrounzerLiu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,116 @@
1
+ # dsh-ptc-diff-card
2
+
3
+ [English](README.en.md) · [简体中文](README.md)
4
+
5
+ DSH Web GUI plugin: shows the standard diff card for `edit`/`write` tool calls made
6
+ inside PTC programs.
7
+
8
+ ## The problem
9
+
10
+ In PTC (Programmatic Tool Calling) mode the agent calls `edit`/`write` from inside a
11
+ `run_code` program. Those calls are recorded as nested `tool/ptc-dispatch` events and
12
+ rendered as generic rows under the `run_code` card: expanding one shows the result text
13
+ (`The file ... has been updated successfully`) instead of a diff card.
14
+
15
+ The shipped Tool layer derives its diff card in `packages/client/ui-tool`
16
+ (`diffCardModel`) and stops at root calls on its first line:
17
+
18
+ ```ts
19
+ if (block.parentCallId !== undefined) return null
20
+ ```
21
+
22
+ Everything the card needs is already on the nested block: the call arguments
23
+ (`file_path`/`old_string`/`new_string`/`content`) and the persisted `meta.diffs`.
24
+
25
+ ## What you get
26
+
27
+ - Nested `edit`/`write` rows become standard file cards: title, workspace-relative path,
28
+ decoded input size, `+added -removed`;
29
+ - expanding renders the shipped `DiffBlock`: language label, wrap/copy actions, path line,
30
+ colored additions and deletions, `⋯ N more lines` folding;
31
+ - failed calls render no diff: the error's first line in the summary, raw output expanded
32
+ (same as the stock row);
33
+ - root calls look byte-for-byte identical to the shipped row, because the whole row is a
34
+ faithful re-implementation.
35
+
36
+ ## How it works
37
+
38
+ The plugin registers its own `edit`/`write` views on the `tool.call.toolview` keyed slot at
39
+ `priority: -1`, which shadows the shipped rows while mounted and restores them on unload.
40
+ The row re-implements the stock file-mutation row chrome (same `DisclosureRow`/`DiffBlock`
41
+ primitives plus a vendored copy of the stock stylesheet) and reuses the stock diff
42
+ derivation minus the root-only guard:
43
+
44
+ | Call state | Diff source |
45
+ |---|---|
46
+ | settled success with non-empty `meta.diffs` | the recorded applied hunks (hunk paths rewritten to the caller's spelling) |
47
+ | `write` create / call still running | whole-file diff derived from the arguments |
48
+ | failed / interrupted | no diff |
49
+
50
+ ## Install
51
+
52
+ Mount it as a normal DSH bundle:
53
+
54
+ ```sh
55
+ # local directory (development)
56
+ dsh plugin --profile <name> add link:D:/dsh-plugins/dsh-ptc-diff-card
57
+
58
+ # from GitHub
59
+ dsh plugin --profile <name> add "github:GrounzerLiu/dsh-ptc-diff-card"
60
+
61
+ # from npm (once published)
62
+ dsh plugin --profile <name> add dsh-ptc-diff-card
63
+ ```
64
+
65
+ Then add `dsh-ptc-diff-card` to that profile's `package.json` `dsh.profile.bundles` and
66
+ restart the app.
67
+
68
+ ## Compatibility and limits
69
+
70
+ - Targets DSH `>= 0.2.1-rc.2` (`engines.dsh`) on the Web GUI (`platform: web`).
71
+ - Takes over `edit` and `write` only; `str_replace_editor`/`apply_patch` are not covered.
72
+ - The row stylesheet is a vendored snapshot of ui-tool 0.2.1-rc.3 chrome; re-vendor after a
73
+ major restyle.
74
+ - Cannot coexist with another plugin shadowing the same `edit`/`write` keys at
75
+ `priority: -1` (e.g. `dsh-diff-viewer`): the slot rejects equal key + priority. This
76
+ plugin catches that per key and logs, leaving the shipped row in charge instead of
77
+ failing the whole plugin load.
78
+ - No auto-expand; behavior matches standard mode.
79
+
80
+ ## Development
81
+
82
+ ```sh
83
+ pnpm install
84
+ pnpm run check # typecheck + pure derivation assertions + build
85
+ pnpm run build # lib/index.js and lib/client.js only
86
+ ```
87
+
88
+ ```
89
+ src/index.ts host half: inert Loader entry (mount point for the browser half)
90
+ src/client/index.tsx slot registration (edit/write, priority -1)
91
+ src/client/row.tsx the row component (preparing + dispatched/settled stages)
92
+ src/client/model.ts row state, paths, and diff derivation
93
+ src/client/tool-row.module.css vendored stock row stylesheet
94
+ scripts/model-check.ts pure derivation assertions (Node type stripping, no extra deps)
95
+ ```
96
+
97
+ The browser half is bundled with the DSH client-plugin wrapper
98
+ (`window.__ModuleLoader__.load({ id, factory })`): the platform module table stays external,
99
+ everything else is inlined, and CSS Modules compile into a hashed class map plus one
100
+ `<style data-plugin>` tag.
101
+
102
+ ## Checks
103
+
104
+ `scripts/model-check.ts` pins the derivation's behavior without a browser: settled write
105
+ create, applied edit hunks with path rewriting, nested PTC dispatch (the case this plugin
106
+ exists for), failed edit, no-op edit, running call, preparing call, interrupted result,
107
+ windowed result with a null call head, and the path helpers.
108
+
109
+ ## Verification
110
+
111
+ See [VERIFY.md](VERIFY.md) (Chinese): how to boot a test profile, which row to look at in a
112
+ PTC session, and how to compare standard mode and failed calls.
113
+
114
+ ## License
115
+
116
+ MIT
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # dsh-ptc-diff-card
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [简体中文](README.md) · [English](README.en.md)
4
+
5
+ DSH Web GUI 插件:让 **PTC 模式**下的 `edit`/`write` 显示和标准模式一样的 diff 卡片。
6
+
7
+ ## 解决什么问题
8
+
9
+ PTC(Programmatic Tool Calling)模式下,agent 在 `run_code` 程序里调用 `edit`/`write`。
10
+ 这些调用被记录成嵌套的 `tool/ptc-dispatch` 事件,在 `run_code` 卡片下面渲染成通用行:
11
+ 展开只有结果文本 `The file ... has been updated successfully`,没有 diff 卡片。
12
+
13
+ 官方 Tool 层的 diff 推导在 `packages/client/ui-tool` 的 `diffCardModel` 里,第一行就把嵌套调用排除了:
14
+
15
+ ```ts
16
+ if (block.parentCallId !== undefined) return null
17
+ ```
18
+
19
+ 而卡片需要的数据其实都在——嵌套块上有调用参数(`file_path`/`old_string`/`new_string`/`content`)
20
+ 和落盘的 `meta.diffs`(已应用 hunks)。
21
+
22
+ ## 效果
23
+
24
+ - 嵌套的 `edit`/`write` 行变成标准文件卡片:标题、工作区相对路径、输入大小、`+新增 -删除`;
25
+ - 展开就是官方 `DiffBlock`:语言标签、换行/复制、路径行、增删着色、`⋯ 其余 N 行` 折叠;
26
+ - 失败的调用不画 diff,标题显示错误首行、展开是原始输出(与官方一致);
27
+ - 根调用(标准模式)外观与官方逐字一致,因为整行是照官方实现的复刻。
28
+
29
+ ## 原理
30
+
31
+ 在 `tool.call.toolview` 这个 keyed slot 上注册自己的 `edit`/`write` 视图,`priority: -1`
32
+ 覆盖官方行(低优先级渲染),插件卸载自动还原。行本身复刻官方文件行(同一套
33
+ `DisclosureRow`/`DiffBlock` 原语 + 一份 vendor 的官方样式表),diff 推导沿用官方逻辑,
34
+ 只去掉「根调用限定」:
35
+
36
+ | 调用状态 | diff 来源 |
37
+ |---|---|
38
+ | 成功 settle 且 `meta.diffs` 非空 | 记录的实际 hunks(路径回写成入参拼写) |
39
+ | `write` 新建 / 调用进行中 | 由入参推导的整文件 diff |
40
+ | 失败 / 中断 | 不画 diff |
41
+
42
+ ## 安装
43
+
44
+ 作为普通 DSH bundle 挂到某个 profile:
45
+
46
+ ```sh
47
+ # 本地目录(开发)
48
+ dsh plugin --profile <name> add link:D:/dsh-plugins/dsh-ptc-diff-card
49
+
50
+ # 从 GitHub
51
+ dsh plugin --profile <name> add "github:GrounzerLiu/dsh-ptc-diff-card"
52
+
53
+ # 从 npm(已发布后)
54
+ dsh plugin --profile <name> add dsh-ptc-diff-card
55
+ ```
56
+
57
+ 然后把 `dsh-ptc-diff-card` 加进该 profile `package.json` 的 `dsh.profile.bundles`,
58
+ 重启应用生效。
59
+
60
+ ## 兼容性与边界
61
+
62
+ - 目标:DSH `>= 0.2.1-rc.2`(`engines.dsh`),Web GUI(`platform: web`)。
63
+ - 只接管 `edit` 和 `write`;`str_replace_editor`/`apply_patch` 等未覆盖。
64
+ - 行样式是从 ui-tool 0.2.1-rc.3 vendor 的一份拷贝,应用大改版后如需同步外观要重新 vendor。
65
+ - 与同样以 `priority: -1` 抢占 `edit`/`write` 的插件(如 `dsh-diff-viewer`)不能共存:
66
+ 同 key 同优先级会被 DSH 拒。本插件会捕获并打日志,让官方行继续接管,不会让整个插件加载失败。
67
+ - 不自动展开 diff,行为与标准模式一致。
68
+
69
+ ## 开发
70
+
71
+ ```sh
72
+ pnpm install
73
+ pnpm run check # typecheck + 纯逻辑断言 + 构建
74
+ pnpm run build # 只构建 lib/index.js 与 lib/client.js
75
+ ```
76
+
77
+ `source` 结构:
78
+
79
+ ```
80
+ src/index.ts 宿主半身:inert 的 Loader 入口(浏览器半身的挂载点)
81
+ src/client/index.tsx slot 注册(edit/write,priority -1)
82
+ src/client/row.tsx 行组件(preparing / dispatched-settled 两态)
83
+ src/client/model.ts 行状态、路径、diff 推导
84
+ src/client/tool-row.module.css vendor 的官方行样式
85
+ scripts/model-check.ts 纯逻辑断言(Node 原生 type stripping,无额外依赖)
86
+ ```
87
+
88
+ 浏览器半身按 DSH 客户端插件格式打包:platform 模块表(`react`、`@deepseek-ai/cordis`、
89
+ `ui-slots`、`ui-primitives`…)保持 external,其余内联;CSS Modules 编译成哈希类名 +
90
+ 一个 `<style data-plugin>`。
91
+
92
+ ## 验收
93
+
94
+ 见 [VERIFY.md](VERIFY.md):怎么起测试 profile、在 PTC 会话里看哪一行、怎么对照标准模式与失败调用。
95
+
96
+ ## 许可
97
+
98
+ MIT
package/VERIFY.md ADDED
@@ -0,0 +1,61 @@
1
+ # 验收指南(PTC diff 卡片)
2
+
3
+ ## 已经装好的测试环境
4
+
5
+ - profile:`ptcdiff`(`%USERPROFILE%\.dsh\profiles\ptcdiff`),bundles =
6
+ `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app` + `dsh-ptc-diff-card`
7
+ - 服务:`dsh --profile ptcdiff --port 19399 --no-open`
8
+ - 浏览器入口:启动日志里打印的 `http://127.0.0.1:19399/?token=...`(每次启动 token 会变)
9
+
10
+ 重启服务:
11
+
12
+ ```sh
13
+ dsh --profile ptcdiff --port 19399 --no-open
14
+ ```
15
+
16
+ ## 看什么
17
+
18
+ ### 1. PTC 模式(核心诉求)
19
+
20
+ 打开侧栏 `dsh-plugins` → `PTC 模式 diff 卡片插件开发`(或 `winia` 的任意 PTC 会话)。
21
+
22
+ - 每个由 PTC 程序调用的 `edit`/`write` 现在渲染成标准文件卡片:
23
+ `编辑 winia/src/xxx.rs 1KB +5 -2` / `写入 .refs/foo.mjs 2KB +30 -0`
24
+ (标题 + 相对路径 + 输入大小 + 增删行数)
25
+ - 点一下这行 → 展开的就是标准 `DiffBlock` diff 卡片(语言标签、换行/复制按钮、路径行、
26
+ 绿色新增 / 红色删除、`⋯ 其余 N 行` 折叠)。
27
+
28
+ 旧行为(对照):同一行展开后只有结果文本
29
+ `The file ... has been updated successfully`,没有增删统计。
30
+
31
+ > 历史轮次的工具行收在「运行了代码,…,已写入文件等」这类进程分组里:先点分组标题展开,
32
+ > 再点卡片行展开 diff。最新一轮默认是展开的。
33
+
34
+ ### 2. 标准模式(回归)
35
+
36
+ 打开 `any` 工作区的 `你好` 会话(真正的根级 `write`/`edit`)。
37
+
38
+ - 折叠行文本与官方渲染逐字一致:`写入 write-tool-test.txt 1KB +16 -0`
39
+ (已在 desktop 官方 profile 用同一会话对照验证)
40
+ - 展开后同样是 diff 卡片。
41
+
42
+ ### 3. 失败调用
43
+
44
+ `winia` 会话里有失败的 `edit`(`old_string matched 4 times`):行首显示「失败」+ 错误首行,
45
+ 不渲染 diff,展开是原始错误输出 —— 与标准模式一致。
46
+
47
+ ## 已知边界
48
+
49
+ - 只接管 `edit` 和 `write`;`str_replace_editor` / `apply_patch` 这类构建里没有的工具不覆盖。
50
+ - 样式表是从 ui-tool 0.2.1-rc.3 vendor 的一份拷贝;应用大改版后如需同步外观需重新同步该文件。
51
+ - 插件通过 `tool.call.toolview` keyed slot 以 `priority: -1` 覆盖官方行;卸载或停用后自动回到官方渲染。
52
+
53
+ ## 装到 desktop profile(验收通过后)
54
+
55
+ ```sh
56
+ # 1) 依赖
57
+ dsh plugin --profile desktop add link:D:/dsh-plugins/dsh-ptc-diff-card
58
+ # 2) 确认 %USERPROFILE%\.dsh\profiles\desktop\package.json 的 dsh.profile.bundles 末尾有
59
+ # "dsh-ptc-diff-card"
60
+ # 3) 重启应用
61
+ ```
@@ -0,0 +1,5 @@
1
+ # dsh-ptc-diff-card bundle patch: mounts this plugin as a host row so the
2
+ # client module system serves its browser half (lib/client.js).
3
+ - insert:
4
+ - id: ptc-diff-card
5
+ name: 'dsh-ptc-diff-card'
package/lib/client.js ADDED
@@ -0,0 +1,575 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-ptc-diff-card",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ // node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
11
+ function r(e) {
12
+ var t, f, n = "";
13
+ if ("string" == typeof e || "number" == typeof e) n += e;
14
+ else if ("object" == typeof e) if (Array.isArray(e)) {
15
+ var o = e.length;
16
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
17
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
18
+ return n;
19
+ }
20
+ function clsx() {
21
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
22
+ return n;
23
+ }
24
+ // src/client/model.ts
25
+ const KILOBYTE = 1024;
26
+ const CONTENT_FIELDS = [
27
+ "content",
28
+ "old_string",
29
+ "new_string"
30
+ ];
31
+ const PATH_FIELDS = ["file_path", "path"];
32
+ /** Narrow a block to the preparing stage. */
33
+ function isPreparing(block) {
34
+ return !("kind" in block) && block.phase === "preparing";
35
+ }
36
+ /** Narrow a block to the settled stage. */
37
+ function isSettled(block) {
38
+ return "kind" in block;
39
+ }
40
+ /** The wire name and raw argument JSON paired with a dispatched block. */
41
+ function callHead(block) {
42
+ return "kind" in block ? block.call : {
43
+ name: block.name,
44
+ argsRaw: block.argsRaw
45
+ };
46
+ }
47
+ function parseArgs(raw) {
48
+ if (raw === "") return null;
49
+ try {
50
+ const value = JSON.parse(raw);
51
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
52
+ return value;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** The parsed top-level argument object, or null when the call carries none. */
58
+ function argObject(block) {
59
+ const head = callHead(block);
60
+ const parsed = head === null ? null : parseArgs(head.argsRaw);
61
+ if (parsed !== null) return parsed;
62
+ const fallback = {};
63
+ for (const key of [...PATH_FIELDS, ...CONTENT_FIELDS]) {
64
+ const value = completedText(block.args, key);
65
+ if (value !== void 0) fallback[key] = value;
66
+ }
67
+ return Object.keys(fallback).length === 0 ? null : fallback;
68
+ }
69
+ function pickString(args, keys) {
70
+ if (args === null) return void 0;
71
+ for (const key of keys) {
72
+ const value = args[key];
73
+ if (typeof value === "string" && value.trim() !== "") return value;
74
+ }
75
+ }
76
+ function stringOf(args, key) {
77
+ const value = args[key];
78
+ return typeof value === "string" ? value : void 0;
79
+ }
80
+ /** Whether a path is absolute in either spelling the Host accepts (POSIX, Windows drive, UNC). */
81
+ function isAbsolutePath(path) {
82
+ return path.startsWith("/") || /^[A-Za-z]:[/\\]/.test(path) || path.startsWith("\\\\");
83
+ }
84
+ /** Whether a path uses a Windows drive or UNC prefix. */
85
+ function isWindowsStylePath(value) {
86
+ return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
87
+ }
88
+ /**
89
+ * Abbreviate a POSIX home directory for display.
90
+ * @param path - absolute or already-short display path.
91
+ * @param home - host account home; absent, empty, or Windows-spelled paths skip abbreviation.
92
+ * @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
93
+ */
94
+ function abbreviateHomePath(path, home) {
95
+ if (home === void 0 || home === "") return path;
96
+ if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
97
+ const root = home.replace(/\/+$/, "");
98
+ if (root === "" || root === "/") return path;
99
+ if (path.replace(/\/+$/, "") === root) return "~";
100
+ return path.startsWith(`${root}/`) ? `~${path.slice(root.length)}` : path;
101
+ }
102
+ /** A completed string field of a streaming argument view. */
103
+ function completedText(args, key) {
104
+ return args.complete(key) ? args.text(key) : void 0;
105
+ }
106
+ /** The call's target path at every stage. */
107
+ function blockPath(block) {
108
+ if (isPreparing(block)) {
109
+ const path = completedText(block.args, "file_path");
110
+ return path === void 0 || path.trim() === "" ? void 0 : path;
111
+ }
112
+ return pickString(argObject(block), PATH_FIELDS);
113
+ }
114
+ /** Kilobytes of decoded input at any stage; null before a content field arrives. */
115
+ function contentKilobytes(args) {
116
+ const open = CONTENT_FIELDS.find((key) => args.has(key) && !args.complete(key));
117
+ const completed = CONTENT_FIELDS.reduce((total, key) => key !== open && args.complete(key) ? total + (args.stringLength(key, { step: KILOBYTE }) ?? 0) : total, 0);
118
+ if (open === void 0) return CONTENT_FIELDS.some((key) => args.has(key)) ? Math.ceil(completed / KILOBYTE) : null;
119
+ const chars = completed + (args.stringLength(open, {
120
+ step: KILOBYTE,
121
+ offset: completed
122
+ }) ?? 0);
123
+ return Math.ceil(chars / KILOBYTE);
124
+ }
125
+ /** The whole-file diff derived from the call's own arguments. */
126
+ function intendedDiff(toolName, args) {
127
+ if (args === null) return null;
128
+ const path = pickString(args, PATH_FIELDS);
129
+ if (path === void 0) return null;
130
+ if (toolName === "write") {
131
+ const content = stringOf(args, "content");
132
+ return content === void 0 ? null : {
133
+ path,
134
+ oldText: null,
135
+ newText: content
136
+ };
137
+ }
138
+ if (toolName === "edit") {
139
+ const oldText = stringOf(args, "old_string");
140
+ const newText = stringOf(args, "new_string");
141
+ if (newText === void 0) return null;
142
+ return {
143
+ path,
144
+ oldText: oldText === void 0 || oldText === "" ? null : oldText,
145
+ newText
146
+ };
147
+ }
148
+ return null;
149
+ }
150
+ /** Narrow the persisted `meta.diffs` payload to well-formed hunks. */
151
+ function narrowDiffs(diffs) {
152
+ if (!Array.isArray(diffs)) return null;
153
+ const out = [];
154
+ for (const hunk of diffs) {
155
+ if (typeof hunk !== "object" || hunk === null) return null;
156
+ const { path, oldText, newText } = hunk;
157
+ if (typeof path !== "string") return null;
158
+ if (oldText !== null && typeof oldText !== "string") return null;
159
+ if (typeof newText !== "string") return null;
160
+ out.push({
161
+ path,
162
+ oldText,
163
+ newText
164
+ });
165
+ }
166
+ return out;
167
+ }
168
+ /**
169
+ * The persisted applied hunks of a settled call.
170
+ * @returns the hunks, 'empty' for a settled call that recorded an empty list,
171
+ * or null when the result carries no usable diff metadata.
172
+ */
173
+ function appliedDiffs(meta) {
174
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return null;
175
+ const diffs = meta.diffs;
176
+ if (!Array.isArray(diffs)) return null;
177
+ if (diffs.length === 0) return "empty";
178
+ return narrowDiffs(diffs);
179
+ }
180
+ /** The recorded absolute path the settled metadata belongs to (stock narrowing). */
181
+ function recordedPath(meta) {
182
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta)) return void 0;
183
+ const path = meta.path;
184
+ return typeof path === "string" && isAbsolutePath(path) ? path : void 0;
185
+ }
186
+ /**
187
+ * Diff hunks to render for this mutation call.
188
+ *
189
+ * Root and nested dispatches follow the same rule: a settled success shows
190
+ * the applied hunks the tool recorded; a create (`diffs: []`) and a running
191
+ * call show the argument-derived whole-file diff. A nested dispatch whose
192
+ * metadata is absent falls back to the argument-derived diff, which the tool
193
+ * arguments already describe exactly.
194
+ * @param toolName - wire Tool name.
195
+ * @param block - the call block at its current stage.
196
+ * @returns hunks for the diff card, or null when the call shows no card.
197
+ */
198
+ function mutationDiffs(toolName, block) {
199
+ if (isPreparing(block)) return null;
200
+ const intended = intendedDiff(toolName, argObject(block));
201
+ if (!isSettled(block)) return intended === null ? null : [intended];
202
+ if (block.isError) return null;
203
+ const applied = appliedDiffs(block.meta);
204
+ if (applied !== null && applied !== "empty" && applied.length > 0) {
205
+ const display = intended === null ? void 0 : intended.path;
206
+ const recorded = recordedPath(block.meta);
207
+ return applied.map((hunk) => display === void 0 || hunk.path !== recorded ? hunk : {
208
+ ...hunk,
209
+ path: display
210
+ });
211
+ }
212
+ if (toolName === "write") return intended === null ? null : [intended];
213
+ return applied === null && intended !== null ? [intended] : null;
214
+ }
215
+ function firstLine(text) {
216
+ const nl = text.indexOf("\n");
217
+ return nl === -1 ? text : text.slice(0, nl);
218
+ }
219
+ /** Flatten a settled result's content blocks to display text. */
220
+ function resultText(node) {
221
+ const parts = [];
222
+ for (const block of node.content) if (block.type === "text") parts.push(block.text);
223
+ else parts.push(JSON.stringify(block, null, 2));
224
+ if (parts.length === 0 && node.error !== void 0) parts.push(`${node.error.name}: ${node.error.code}`);
225
+ return parts.join("\n");
226
+ }
227
+ /** The displayed form of a path: workspace-rooted paths shed the session cwd. */
228
+ function relativize(path, cwd) {
229
+ if (cwd === void 0 || cwd === "") return path;
230
+ const root = cwd.replace(/[/\\]+$/, "");
231
+ if (!path.startsWith(root)) return path;
232
+ const rest = path.slice(root.length);
233
+ return rest.startsWith("/") || rest.startsWith("\\") ? rest.slice(1) : path;
234
+ }
235
+ /** Row state for the block's current stage. */
236
+ function rowState(block) {
237
+ if (isPreparing(block)) return "preparing";
238
+ if (!isSettled(block)) return "running";
239
+ if (block.error?.code === "interrupted") return "stopped";
240
+ return block.isError ? "error" : "ok";
241
+ }
242
+ /**
243
+ * Derive the whole row model.
244
+ *
245
+ * The summary keeps the call's own spelling (relative to the session
246
+ * workspace), while the link prefers the absolute path the settled result
247
+ * recorded — the same split the stock model makes, so a relative argument
248
+ * still opens the right file after the session's working directory moves.
249
+ * @param toolName - wire Tool name (edit or write).
250
+ * @param block - the call block at its current stage.
251
+ * @param cwd - session workspace root, used to shorten the summary path.
252
+ * @param home - host account home, used to abbreviate a POSIX home path.
253
+ * @returns the row model.
254
+ */
255
+ function mutationModel(toolName, block, cwd, home) {
256
+ const path = blockPath(block);
257
+ const settled = isSettled(block);
258
+ const recorded = settled ? recordedPath(block.meta) : void 0;
259
+ const usableArgumentPath = path !== void 0 && (isAbsolutePath(path) || settled && !block.isError && block.parentCallId === void 0);
260
+ const linkPath = recorded ?? (usableArgumentPath ? path : void 0);
261
+ const output = settled ? resultText(block) || null : null;
262
+ return {
263
+ path,
264
+ linkPath,
265
+ summary: path === void 0 ? "" : abbreviateHomePath(relativize(path, cwd), home),
266
+ output,
267
+ state: rowState(block),
268
+ diffs: mutationDiffs(toolName, block)
269
+ };
270
+ }
271
+ /** First line of the settled output, for the failed-row summary. */
272
+ function errorSummary(model) {
273
+ return model.state === "error" && model.output !== null ? firstLine(model.output) : null;
274
+ }
275
+ // \0dsh-css:src/client/tool-row.module.css.mjs
276
+ const css = ".tool-row-module_root{flex-direction:column;display:flex}.tool-row-module_leading{flex-shrink:0}.tool-row-module_root[data-tool^=cordis_] .tool-row-module_title{font-weight:500}.tool-row-module_title{font-weight:400}.tool-row-module_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.tool-row-module_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));flex:auto;overflow:hidden}.tool-row-module_summarySuffix{white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));flex:none;margin-left:4px}.tool-row-module_diffStat{font-family:var(--ds-font-family-code);font-size:calc(var(--dsh-content-font-size-secondary,13px) - 2px);margin-left:10px;transform:translateY(.5px)}.tool-row-module_row[aria-expanded=true] .tool-row-module_diffAdded{color:var(--dsw-alias-state-success-primary)}@media (hover:hover){.tool-row-module_row:hover .tool-row-module_diffAdded{color:var(--dsw-alias-state-success-primary)}}.tool-row-module_row[aria-expanded=true] .tool-row-module_diffRemoved{color:var(--dsw-alias-state-error-primary)}@media (hover:hover){.tool-row-module_row:hover .tool-row-module_diffRemoved{color:var(--dsw-alias-state-error-primary)}}.tool-row-module_fileLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:inherit;text-align:left;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:inherit;text-underline-offset:3px;cursor:pointer;background:0 0;border:none;flex:0 auto;margin:0;padding:0;text-decoration:underline 1px dotted;overflow:hidden}.tool-row-module_errorSummary{color:var(--dsw-alias-state-error-primary)}.tool-row-module_stoppedSummary{color:var(--dsw-alias-state-warn-label)}.tool-row-module_bodyWrap{flex-direction:column;display:flex}.tool-row-module_inspectButton{border:.5px solid var(--dsw-alias-border-l3);corner-shape:round;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-secondary);cursor:pointer;opacity:0;border-radius:999px;align-self:flex-start;align-items:center;gap:4px;margin:4px 0 2px 4px;padding:2px 8px;font-size:11px;line-height:16px;transition:opacity .1s;display:inline-flex}.tool-row-module_inspectButton:focus-visible{opacity:1}@media (hover:hover){.tool-row-module_root:hover .tool-row-module_inspectButton{opacity:1}}.tool-row-module_detailsBodyWrap{position:relative}.tool-row-module_detailsBodyWrap .tool-row-module_inspectButton{margin:0;position:absolute;top:12px;right:12px}@media (hover:hover){.tool-row-module_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}}.tool-row-module_bodyScroll{max-height:260px;overflow-y:auto}.tool-row-module_ioCard{border:.5px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);flex-direction:column;margin:4px 0 4px 4px;display:flex}.tool-row-module_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.tool-row-module_ioSection::-webkit-scrollbar-thumb{border-radius:var(--dsw-radius-sm);background-clip:padding-box;border:2px solid #0000}.tool-row-module_ioSection::-webkit-scrollbar-track{margin:6px 0}.tool-row-module_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.tool-row-module_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:.5px}.tool-row-module_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.tool-row-module_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.tool-row-module_codeBody,.tool-row-module_terminalBody,.tool-row-module_diffBody,.tool-row-module_readBody,.tool-row-module_imageBody,.tool-row-module_searchBody,.tool-row-module_webBody{margin:4px 0 4px 4px}.tool-row-module_searchRecovery{white-space:pre-wrap;overflow-wrap:anywhere;font:var(--dsw-font-xs-13);color:var(--dsw-alias-label-tertiary);margin:4px 0 4px 4px}.tool-row-module_imageMeta{white-space:pre-wrap;overflow-wrap:anywhere;font:var(--dsw-font-markdown-code-block-small);color:var(--dsw-alias-label-tertiary);margin-top:4px}.tool-row-module_imageExtra{white-space:pre-wrap;overflow-wrap:anywhere;font:var(--dsw-font-xs-13);color:var(--dsw-alias-label-tertiary);margin-top:4px}.tool-row-module_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;--dsl-terminal-command-whitespace:pre-wrap;border:.5px solid var(--dsw-alias-border-l1)}.tool-row-module_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (hover:none){.tool-row-module_inspectButton{opacity:1}}";
277
+ const tagId = "dsh-ptc-diff-card/tool-row.module.css";
278
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
279
+ const tag = document.createElement("style");
280
+ tag.dataset.plugin = "dsh-ptc-diff-card";
281
+ tag.dataset.pluginCss = tagId;
282
+ tag.textContent = css;
283
+ document.head.appendChild(tag);
284
+ }
285
+ var tool_row_module_css_default = {
286
+ "bodyScroll": "tool-row-module_bodyScroll",
287
+ "bodyWrap": "tool-row-module_bodyWrap",
288
+ "codeBody": "tool-row-module_codeBody",
289
+ "detailsBodyWrap": "tool-row-module_detailsBodyWrap",
290
+ "diffAdded": "tool-row-module_diffAdded",
291
+ "diffBody": "tool-row-module_diffBody",
292
+ "diffRemoved": "tool-row-module_diffRemoved",
293
+ "diffStat": "tool-row-module_diffStat",
294
+ "errorSummary": "tool-row-module_errorSummary",
295
+ "fileLink": "tool-row-module_fileLink",
296
+ "imageBody": "tool-row-module_imageBody",
297
+ "imageExtra": "tool-row-module_imageExtra",
298
+ "imageMeta": "tool-row-module_imageMeta",
299
+ "inspectButton": "tool-row-module_inspectButton",
300
+ "ioCard": "tool-row-module_ioCard",
301
+ "ioDivider": "tool-row-module_ioDivider",
302
+ "ioLabel": "tool-row-module_ioLabel",
303
+ "ioSection": "tool-row-module_ioSection",
304
+ "ioText": "tool-row-module_ioText",
305
+ "leading": "tool-row-module_leading",
306
+ "readBody": "tool-row-module_readBody",
307
+ "root": "tool-row-module_root",
308
+ "row": "tool-row-module_row",
309
+ "searchBody": "tool-row-module_searchBody",
310
+ "searchRecovery": "tool-row-module_searchRecovery",
311
+ "sep": "tool-row-module_sep",
312
+ "stoppedSummary": "tool-row-module_stoppedSummary",
313
+ "summary": "tool-row-module_summary",
314
+ "summarySuffix": "tool-row-module_summarySuffix",
315
+ "terminalBody": "tool-row-module_terminalBody",
316
+ "title": "tool-row-module_title",
317
+ "visuallyHidden": "tool-row-module_visuallyHidden",
318
+ "webBody": "tool-row-module_webBody"
319
+ };
320
+ // src/client/row.tsx
321
+ /**
322
+ * The `edit`/`write` Tool view this plugin mounts over the shipped
323
+ * file-mutation row.
324
+ *
325
+ * It re-implements the stock row's chrome (the same DisclosureRow/DiffBlock
326
+ * primitives and the vendored stock stylesheet) and swaps in one behavior:
327
+ * the expanded body is the diff card at every call site, including the
328
+ * nested `tool/ptc-dispatch` blocks that the stock row sends to the generic
329
+ * fallback. A root call therefore looks and behaves exactly as before, and a
330
+ * call dispatched from inside a PTC program finally shows the same card.
331
+ */
332
+ /** The stock chat diff cap (ui-tool's CHAT_DIFF_MAX_LINES). */
333
+ const CHAT_DIFF_MAX_LINES = 9;
334
+ /** The row's leading glyph, sized like the stock file-mutation row. */
335
+ const MUTATION_ICON = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, { size: 14 });
336
+ /** Row variant per wire Tool name (the two keys this plugin takes over). */
337
+ function variantOf(toolName) {
338
+ return toolName === "edit" ? "edit" : "write";
339
+ }
340
+ /** The stock title key per wire Tool name. */
341
+ function titleKeyOf(toolName) {
342
+ return toolName === "edit" ? "tool.title.edit" : "tool.title.write";
343
+ }
344
+ /** Read one shared chrome string through the conversation seat. */
345
+ function chromeLabel(t, key) {
346
+ return t(key);
347
+ }
348
+ /** Localized chrome for the diff card, from the same conversation dictionary. */
349
+ function diffLabels(t) {
350
+ return {
351
+ codeLabel: chromeLabel(t, "codeBlock.title"),
352
+ wrapLabel: chromeLabel(t, "codeBlock.wrap"),
353
+ unwrapLabel: chromeLabel(t, "codeBlock.unwrap"),
354
+ copy: chromeLabel(t, "copy"),
355
+ copied: chromeLabel(t, "copied"),
356
+ collapseAria: t("diff.collapseAria"),
357
+ expandAria: (count) => t("diff.expandAria", { count }),
358
+ collapse: chromeLabel(t, "collapse"),
359
+ expand: (count) => t("diff.expandRest", { count })
360
+ };
361
+ }
362
+ /** Visually hidden run-state label (the stock row's AT requirement). */
363
+ function stateStatus(state, t) {
364
+ switch (state) {
365
+ case "preparing": return t("row.preparing");
366
+ case "running": return t("row.running");
367
+ case "error": return t("row.failed");
368
+ case "stopped": return t("row.stopped");
369
+ default: return null;
370
+ }
371
+ }
372
+ /** The preparing prefix: named call, path once its string closes, input size. */
373
+ function PreparingRow({ toolName, block, cwd, home, useDisclosure, t }) {
374
+ const { expanded, toggle } = useDisclosure();
375
+ const path = block.args.complete("file_path") ? block.args.text("file_path") : void 0;
376
+ const summary = path === void 0 || path === "" ? "" : abbreviateHomePath(relativize(path, cwd), home);
377
+ const kilobytes = contentKilobytes(block.args);
378
+ const size = kilobytes === null ? null : t("tool.preparing.content", { kilobytes });
379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
380
+ className: tool_row_module_css_default.root,
381
+ "data-variant": variantOf(toolName),
382
+ "data-tool": toolName,
383
+ "data-state": "preparing",
384
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
385
+ className: tool_row_module_css_default.visuallyHidden,
386
+ children: t("row.preparing")
387
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
388
+ rowClassName: tool_row_module_css_default.row,
389
+ leadingClassName: tool_row_module_css_default.leading,
390
+ titleClassName: tool_row_module_css_default.title,
391
+ icon: MUTATION_ICON,
392
+ title: t(titleKeyOf(toolName)),
393
+ running: true,
394
+ open: expanded,
395
+ expandable: false,
396
+ onToggle: toggle,
397
+ collapsedContent: summary !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
399
+ className: tool_row_module_css_default.sep,
400
+ "aria-hidden": true
401
+ }),
402
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
403
+ className: tool_row_module_css_default.summary,
404
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, { children: summary })
405
+ }),
406
+ size !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
407
+ className: tool_row_module_css_default.summarySuffix,
408
+ children: size
409
+ })
410
+ ] })
411
+ })]
412
+ });
413
+ }
414
+ /** The dispatched and settled row: path link, diff totals, diff card body. */
415
+ function DispatchedRow({ toolName, block, cwd, home, openFile, inspect, useDisclosure, t }) {
416
+ const { expanded, toggle } = useDisclosure();
417
+ const model = mutationModel(toolName, block, cwd, home);
418
+ const kilobytes = contentKilobytes(block.args);
419
+ const size = kilobytes === null ? null : t("tool.preparing.content", { kilobytes });
420
+ const totals = model.diffs === null ? null : (0, _deepseek_ai_dsh_client_ui_primitives.diffTotals)(model.diffs);
421
+ const failure = errorSummary(model);
422
+ const summaryText = failure ?? model.summary;
423
+ const expandable = model.diffs !== null || model.output !== null;
424
+ const open = expanded && expandable;
425
+ const settledWithCue = model.state === "error" || model.state === "stopped";
426
+ const suffix = settledWithCue ? null : size;
427
+ const fileLink = model.linkPath !== void 0 && failure === null && !settledWithCue ? model.linkPath : null;
428
+ const labels = (0, react.useMemo)(() => diffLabels(t), [t]);
429
+ const openPath = (0, react.useMemo)(() => fileLink === null ? void 0 : () => {
430
+ openFile(fileLink);
431
+ }, [fileLink, openFile]);
432
+ const status = stateStatus(model.state, t);
433
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
434
+ className: tool_row_module_css_default.root,
435
+ "data-variant": variantOf(toolName),
436
+ "data-tool": toolName,
437
+ "data-state": model.state,
438
+ children: [status !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
439
+ className: tool_row_module_css_default.visuallyHidden,
440
+ children: status
441
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
442
+ rowClassName: tool_row_module_css_default.row,
443
+ leadingClassName: tool_row_module_css_default.leading,
444
+ titleClassName: tool_row_module_css_default.title,
445
+ icon: MUTATION_ICON,
446
+ title: t(titleKeyOf(toolName)),
447
+ running: model.state === "running",
448
+ open,
449
+ expandable,
450
+ expandOnRowClick: true,
451
+ keepContentWhenOpen: true,
452
+ onToggle: toggle,
453
+ collapsedContent: summaryText !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
454
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
455
+ className: tool_row_module_css_default.sep,
456
+ "aria-hidden": true
457
+ }),
458
+ fileLink !== null && openPath !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
459
+ type: "button",
460
+ className: tool_row_module_css_default.fileLink,
461
+ onClick: (event) => {
462
+ event.stopPropagation();
463
+ openPath();
464
+ },
465
+ onKeyDown: (event) => {
466
+ if (event.key === "Enter" || event.key === " ") event.stopPropagation();
467
+ },
468
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, { children: summaryText })
469
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
470
+ className: clsx(tool_row_module_css_default.summary, failure !== null && tool_row_module_css_default.errorSummary, failure === null && model.state === "stopped" && tool_row_module_css_default.stoppedSummary),
471
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, { children: summaryText })
472
+ }),
473
+ suffix !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
474
+ className: tool_row_module_css_default.summarySuffix,
475
+ children: suffix
476
+ }),
477
+ totals !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
478
+ className: clsx(tool_row_module_css_default.summarySuffix, tool_row_module_css_default.diffStat),
479
+ children: [
480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
481
+ className: tool_row_module_css_default.diffAdded,
482
+ children: `+${totals.added}`
483
+ }),
484
+ " ",
485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
486
+ className: tool_row_module_css_default.diffRemoved,
487
+ children: `-${totals.removed}`
488
+ })
489
+ ]
490
+ })
491
+ ] }),
492
+ children: open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
493
+ className: tool_row_module_css_default.bodyWrap,
494
+ children: [model.diffs !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DiffBlock, {
495
+ diffs: model.diffs,
496
+ labels,
497
+ maxLines: CHAT_DIFF_MAX_LINES,
498
+ className: tool_row_module_css_default.diffBody
499
+ }) : model.output !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
500
+ className: tool_row_module_css_default.ioCard,
501
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
502
+ className: tool_row_module_css_default.ioSection,
503
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
504
+ className: tool_row_module_css_default.ioLabel,
505
+ children: t("row.output")
506
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
507
+ className: tool_row_module_css_default.ioText,
508
+ "data-error": model.state === "error" || void 0,
509
+ children: model.output
510
+ })]
511
+ })
512
+ }), inspect !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
513
+ type: "button",
514
+ className: tool_row_module_css_default.inspectButton,
515
+ onClick: inspect,
516
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconInspectOutlineRegular, {}), t("row.inspect")]
517
+ })]
518
+ })
519
+ })]
520
+ });
521
+ }
522
+ /**
523
+ * The registered `edit`/`write` Tool view.
524
+ * @param props - keyed toolview owner currency plus the locale seat.
525
+ * @returns the preparing or dispatched row for the call's current stage.
526
+ */
527
+ function PtcDiffRow(props) {
528
+ return props.phase === "preparing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreparingRow, {
529
+ toolName: props.toolName,
530
+ block: props.block,
531
+ cwd: props.cwd,
532
+ home: props.home,
533
+ useDisclosure: props.useDisclosure,
534
+ t: props.t
535
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DispatchedRow, {
536
+ toolName: props.toolName,
537
+ block: props.block,
538
+ cwd: props.cwd,
539
+ home: props.home,
540
+ openFile: props.openFile,
541
+ inspect: props.inspect,
542
+ useDisclosure: props.useDisclosure,
543
+ t: props.t
544
+ });
545
+ }
546
+ // src/client/index.tsx
547
+ /** Required service: the slot registry. */
548
+ const inject = ["slots"];
549
+ /** The wire Tools whose cards this plugin renders. */
550
+ const MUTATION_TOOLS = ["edit", "write"];
551
+ /** The dictionary namespace the row's copy is translated through. */
552
+ const NS = "conversation";
553
+ /**
554
+ * Mount the diff rows over the shipped file-mutation rows.
555
+ * @param ctx - client root context.
556
+ */
557
+ function apply(ctx) {
558
+ ctx.slots.inject("tool.call.toolview", function* () {
559
+ for (const key of MUTATION_TOOLS) try {
560
+ yield ctx.slots.register({
561
+ name: "tool.call.toolview",
562
+ key,
563
+ locale: NS,
564
+ priority: -1
565
+ }, PtcDiffRow);
566
+ } catch (error) {
567
+ console.error(`[dsh-ptc-diff-card] cannot take over the "${key}" tool view; the shipped row stays in charge`, error);
568
+ }
569
+ });
570
+ }
571
+ exports.apply = apply;
572
+ exports.inject = inject;
573
+ return module.exports;
574
+ }
575
+ });
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // src/index.ts
2
+ /** Loader entry id; must match the id the bundle patch inserts. */
3
+ const name = "ptc-diff-card";
4
+ /** No host-side behaviour. */
5
+ function apply() {}
6
+ export { apply, name };
package/package.json CHANGED
@@ -1,6 +1,78 @@
1
1
  {
2
2
  "name": "dsh-ptc-diff-card",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "author": "GrounzerLiu",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/GrounzerLiu/dsh-ptc-diff-card.git"
8
+ },
9
+ "homepage": "https://github.com/GrounzerLiu/dsh-ptc-diff-card#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/GrounzerLiu/dsh-ptc-diff-card/issues"
12
+ },
13
+ "description": "DSH Web 插件:PTC 模式(run_code 内的嵌套调用)里的 edit/write 显示标准 diff 卡片 | DSH Web GUI plugin: standard diff cards for edit/write calls made inside PTC programs",
14
+ "type": "module",
15
+ "main": "lib/index.js",
16
+ "exports": {
17
+ ".": "./lib/index.js",
18
+ "./client": "./lib/client.js",
19
+ "./cordis.patch.yml": "./cordis.patch.yml",
20
+ "./package.json": "./package.json"
21
+ },
22
+ "dsh": {
23
+ "bundle": {
24
+ "patch": "./cordis.patch.yml"
25
+ },
26
+ "client": {
27
+ "inject": [],
28
+ "platform": "web"
29
+ }
30
+ },
31
+ "engines": {
32
+ "dsh": ">=0.2.1-rc.2"
33
+ },
34
+ "files": [
35
+ "lib",
36
+ "cordis.patch.yml",
37
+ "README.md",
38
+ "README.en.md",
39
+ "VERIFY.md",
40
+ "LICENSE"
41
+ ],
42
+ "scripts": {
43
+ "prepublishOnly": "pnpm run check",
44
+ "pack:check": "npm pack --dry-run",
45
+ "build": "tsdown",
46
+ "typecheck": "tsc -p tsconfig.json --noUnusedLocals --noUnusedParameters",
47
+ "check:model": "node --experimental-strip-types scripts/model-check.ts",
48
+ "check": "pnpm run typecheck && pnpm run check:model && pnpm run build"
49
+ },
50
+ "keywords": [
51
+ "deepseek",
52
+ "harness",
53
+ "dsh",
54
+ "dsh-plugin",
55
+ "ptc",
56
+ "diff",
57
+ "ui"
58
+ ],
59
+ "license": "MIT",
60
+ "dependencies": {
61
+ "clsx": "^2.1.1"
62
+ },
63
+ "devDependencies": {
64
+ "@deepseek-ai/cordis": "4.0.5",
65
+ "@deepseek-ai/dsh-client-ui-chat": "0.2.1-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
68
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
70
+ "@deepseek-ai/dsh-client-ui-tool": "0.2.1-rc.2",
71
+ "@deepseek-ai/dsh-util-values": "0.2.1-rc.2",
72
+ "@types/react": "~18.3.1",
73
+ "lightningcss": "^1.30.0",
74
+ "react": "^18.2.0",
75
+ "tsdown": "^0.22.2",
76
+ "typescript": "^5.9.3"
77
+ }
78
+ }