@deepseek-ai/dsh-client-ui-tool 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/LICENSE +28 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +49 -0
  4. package/README.zh.md +49 -0
  5. package/lib/client.js +1614 -0
  6. package/lib/index.js +6 -0
  7. package/lib/invariant.js +23 -0
  8. package/lib/types/client/apply.d.ts +10 -0
  9. package/lib/types/client/contract/slots.d.ts +35 -0
  10. package/lib/types/client/index.d.ts +4 -0
  11. package/lib/types/client/locale.d.ts +3 -0
  12. package/lib/types/client/tool/ToolCallTree.d.ts +9 -0
  13. package/lib/types/client/tool/ToolDetails.d.ts +16 -0
  14. package/lib/types/client/tool/components/ToolRow.d.ts +79 -0
  15. package/lib/types/client/tool/models/diff-card-model.d.ts +58 -0
  16. package/lib/types/client/tool/models/read-card-model.d.ts +60 -0
  17. package/lib/types/client/tool/models/search-card-model.d.ts +89 -0
  18. package/lib/types/client/tool/models/terminal-card-model.d.ts +71 -0
  19. package/lib/types/client/tool/models/tool-call-model.d.ts +65 -0
  20. package/lib/types/client/tool/models/web-card-model.d.ts +39 -0
  21. package/lib/types/client/tool/toolviews/GenericToolCard.d.ts +7 -0
  22. package/lib/types/client/tool/toolviews/ask-question-row.d.ts +23 -0
  23. package/lib/types/client/tool/toolviews/bash-sample.d.ts +26 -0
  24. package/lib/types/client/tool/toolviews/file-mutation-row.d.ts +31 -0
  25. package/lib/types/client/tool/toolviews/plan-summary.d.ts +48 -0
  26. package/lib/types/client/tool/toolviews/read-row.d.ts +26 -0
  27. package/lib/types/client/tool/toolviews/search-row.d.ts +31 -0
  28. package/lib/types/client/tool/toolviews/todo-row.d.ts +25 -0
  29. package/lib/types/client/tool/toolviews/web-row.d.ts +26 -0
  30. package/lib/types/index.d.ts +4 -0
  31. package/lib/types/invariant.d.ts +16 -0
  32. package/package.json +82 -0
package/lib/client.js ADDED
@@ -0,0 +1,1614 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@deepseek-ai/dsh-client-ui-tool",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
+ //#region lib/types/client/tool/models/tool-call-model.js
12
+ /** Figma row titles per variant (design literals, not translatable copy). */
13
+ const VARIANT_TITLES = {
14
+ search: "Search",
15
+ read: "Read",
16
+ bash: "Bash",
17
+ write: "Write",
18
+ edit: "Edit",
19
+ code: "Code",
20
+ others: "Tool call"
21
+ };
22
+ /** Known tool name -> variant. */
23
+ const TOOL_VARIANTS = {
24
+ bash: "bash",
25
+ pwsh: "bash",
26
+ read: "read",
27
+ web_fetch: "read",
28
+ web_search: "search",
29
+ grep: "search",
30
+ glob: "search",
31
+ write: "write",
32
+ edit: "edit",
33
+ run_code: "code",
34
+ cordis_inspect: "read",
35
+ cordis_mount: "code",
36
+ cordis_unmount: "others"
37
+ };
38
+ /** Tool-owned titles that refine a generic row variant without replacing it. */
39
+ const TOOL_TITLES = {
40
+ cordis_inspect: "Inspect",
41
+ cordis_mount: "Mount temporary Plugin",
42
+ cordis_unmount: "Unmount temporary Plugin",
43
+ pwsh: "Pwsh"
44
+ };
45
+ /**
46
+ * Classify a tool name into its row variant.
47
+ * @param toolName - wire tool name.
48
+ * @returns matching variant, others when unknown.
49
+ */
50
+ function classifyTool(toolName) {
51
+ return TOOL_VARIANTS[toolName] ?? "others";
52
+ }
53
+ /**
54
+ * Flatten a settled result's content blocks to display text: text blocks
55
+ * verbatim, other block shapes as pretty JSON. Empty content on a failed call
56
+ * falls back to the structured error's `name: code` line.
57
+ * @param node - the settled result node.
58
+ * @returns the flattened result text (may be empty).
59
+ */
60
+ function resultText(node) {
61
+ const parts = [];
62
+ for (const block of node.content) if (block.type === "text") parts.push(block.text);
63
+ else parts.push(JSON.stringify(block, null, 2));
64
+ if (parts.length === 0 && node.error !== void 0) parts.push(`${node.error.name}: ${node.error.code}`);
65
+ return parts.join("\n");
66
+ }
67
+ function parseArgs(argsRaw) {
68
+ try {
69
+ return JSON.parse(argsRaw);
70
+ } catch {
71
+ return;
72
+ }
73
+ }
74
+ function firstLine(text) {
75
+ const nl = text.indexOf("\n");
76
+ return nl === -1 ? text : text.slice(0, nl);
77
+ }
78
+ function pickString(args, keys) {
79
+ for (const key of keys) {
80
+ const v = args[key];
81
+ if (typeof v === "string" && v !== "") return v;
82
+ }
83
+ }
84
+ /** Summary key preference per variant (args-derived; result-derived summaries are a ledger item). */
85
+ const SUMMARY_KEYS = {
86
+ bash: ["description", "command"],
87
+ read: [
88
+ "path",
89
+ "file_path",
90
+ "url"
91
+ ],
92
+ search: [
93
+ "query",
94
+ "pattern",
95
+ "url"
96
+ ],
97
+ write: ["path", "file_path"],
98
+ edit: ["path", "file_path"],
99
+ code: ["description"],
100
+ others: []
101
+ };
102
+ /**
103
+ * Strip the workspace root from a workspace-rooted absolute path (display only).
104
+ * @param text - the path to shorten.
105
+ * @param cwd - session workspace root; absent or empty leaves the path unchanged.
106
+ * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
107
+ */
108
+ function relativizeToCwd(text, cwd) {
109
+ if (cwd === void 0 || cwd === "") return text;
110
+ const root = cwd.replace(/[/\\]+$/, "");
111
+ if (text.startsWith(`${root}/`) || text.startsWith(`${root}\\`)) return text.slice(root.length + 1);
112
+ return text;
113
+ }
114
+ function deriveSummary(variant, argsRaw) {
115
+ const parsed = parseArgs(argsRaw);
116
+ if (typeof parsed !== "object" || parsed === null) return firstLine(argsRaw);
117
+ const args = parsed;
118
+ const picked = pickString(args, SUMMARY_KEYS[variant]);
119
+ if (picked !== void 0) return firstLine(picked);
120
+ for (const v of Object.values(args)) if (typeof v === "string" && v !== "") return firstLine(v);
121
+ return firstLine(argsRaw);
122
+ }
123
+ /** Path keys only — never `url` (web_fetch lands on the read variant). */
124
+ const FILE_PATH_KEYS = ["path", "file_path"];
125
+ /** File-tool variants whose summary may be an openable workspace path. */
126
+ const FILE_PATH_VARIANTS = new Set([
127
+ "read",
128
+ "write",
129
+ "edit"
130
+ ]);
131
+ function deriveFilePath(variant, argsRaw) {
132
+ if (!FILE_PATH_VARIANTS.has(variant)) return void 0;
133
+ const parsed = parseArgs(argsRaw);
134
+ if (typeof parsed !== "object" || parsed === null) return void 0;
135
+ const picked = pickString(parsed, FILE_PATH_KEYS);
136
+ return picked === void 0 ? void 0 : firstLine(picked);
137
+ }
138
+ function deriveBody(variant, argsRaw) {
139
+ if (argsRaw === "") return null;
140
+ const parsed = parseArgs(argsRaw);
141
+ if (parsed === void 0) return argsRaw;
142
+ if (variant === "code" && typeof parsed === "object" && parsed !== null) {
143
+ const code = parsed.code;
144
+ if (typeof code === "string" && code !== "") return code;
145
+ }
146
+ return JSON.stringify(parsed, null, 2);
147
+ }
148
+ /**
149
+ * Derive the full row model from a frozen call slice.
150
+ * @param toolName - wire tool name (dispatch-supplied; survives windowless results).
151
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
152
+ * @param cwd - session workspace root; workspace-rooted path summaries display relative to it.
153
+ * @returns the row model.
154
+ */
155
+ function toolRowModel(toolName, block, cwd) {
156
+ const variant = classifyTool(toolName);
157
+ const done = "kind" in block;
158
+ const argsRaw = (done ? block.call?.argsRaw : block.argsRaw) ?? "";
159
+ const state = !done ? "running" : block.error?.code === "interrupted" ? "stopped" : block.isError ? "error" : "ok";
160
+ const base = argsRaw === "" ? block.callId : relativizeToCwd(deriveSummary(variant, argsRaw), cwd);
161
+ const toolTitle = TOOL_TITLES[toolName];
162
+ const summary = variant === "others" && toolName !== "" && toolTitle === void 0 ? `${toolName} · ${base}` : base;
163
+ const output = done ? resultText(block) || null : null;
164
+ const errorSummary = state === "error" && output !== null ? firstLine(output) : null;
165
+ return {
166
+ variant,
167
+ title: toolTitle ?? VARIANT_TITLES[variant],
168
+ summary,
169
+ filePath: deriveFilePath(variant, argsRaw),
170
+ body: deriveBody(variant, argsRaw),
171
+ output,
172
+ errorSummary,
173
+ state
174
+ };
175
+ }
176
+ //#endregion
177
+ //#region lib/types/client/tool/models/read-card-model.js
178
+ /**
179
+ * Derive the read-card props for a tool call, or null when this call is not a
180
+ * read card and belongs on the generic path.
181
+ *
182
+ * The read card is result-side only, so only a settled call whose result view
183
+ * declares `card:'read'` produces one. Every other case is null — the
184
+ * documented generic-card default:
185
+ *
186
+ * - A running call: it has no result view yet, and a read carries no content at
187
+ * call time.
188
+ * - A settled call whose result view is not a read card — including a `card`
189
+ * value this UI version does not know, which arrives over the wire and cannot
190
+ * be trusted to be one of the compiled variants, and the read tool's own
191
+ * generic fallback for an error result or a non-envelope body.
192
+ *
193
+ * The label is the read view's `title` when the tool supplied one (the
194
+ * presentation contract's replacement-title rule), otherwise the file path
195
+ * relativized to the session workspace so a workspace-rooted absolute path
196
+ * displays the same short form the row summary shows.
197
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
198
+ * @param sessionCwd - the session workspace root; a workspace-rooted absolute
199
+ * path label displays relative to it. Absent leaves the path as authored.
200
+ * @returns the read-card props, or null for the generic path.
201
+ */
202
+ function readCardModel(block, sessionCwd) {
203
+ if (!("kind" in block)) return null;
204
+ const result = block.resultView?.card === "read" ? block.resultView : null;
205
+ if (result === null) return null;
206
+ const lines = result.lines.map((line) => ({
207
+ number: line.number,
208
+ text: line.text
209
+ }));
210
+ return {
211
+ label: result.title ?? relativizeToCwd(result.path, sessionCwd),
212
+ lines,
213
+ totalLines: result.totalLines,
214
+ lang: result.lang
215
+ };
216
+ }
217
+ //#endregion
218
+ //#region lib/types/client/tool/models/diff-card-model.js
219
+ /**
220
+ * Narrow a wire `card:'diff'` view's `diffs` to well-formed hunks. The event
221
+ * view crosses the wire and `toolEventViewSchema` validates only the `card`
222
+ * string, so a version mismatch or an anomalous plugin can deliver a `diff` card
223
+ * whose `diffs` is absent, not an array, or carries malformed hunks. Returning
224
+ * null for any of those routes the block to the generic path instead of letting
225
+ * DiffBlock's `for...of`/`split` throw and crash the row or the details panel.
226
+ * @param diffs - the view's `diffs` field, unverified.
227
+ * @returns the validated hunks, or null when the payload is not usable.
228
+ */
229
+ function narrowDiffs(diffs) {
230
+ if (!Array.isArray(diffs) || diffs.length === 0) return null;
231
+ const out = [];
232
+ for (const hunk of diffs) {
233
+ if (typeof hunk !== "object" || hunk === null) return null;
234
+ const { path, oldText, newText } = hunk;
235
+ if (typeof path !== "string") return null;
236
+ if (oldText !== null && typeof oldText !== "string") return null;
237
+ if (typeof newText !== "string") return null;
238
+ out.push({
239
+ path,
240
+ oldText,
241
+ newText
242
+ });
243
+ }
244
+ return out;
245
+ }
246
+ /**
247
+ * Derive the diff-card props for a tool call, or null when this call is not a
248
+ * diff card and belongs on the generic path.
249
+ *
250
+ * The result side is authoritative once the call settles: the write/edit tools
251
+ * return the applied contextual hunks there (an edit's real before/after, a
252
+ * create's whole-file diff), which replace the call-time diff derived from the
253
+ * arguments alone. While the call is still running only the call side exists,
254
+ * so a running write/edit shows its intended change. Null is the documented
255
+ * generic-card default and covers every non-diff card — including a `card`
256
+ * value this UI version does not know, which arrives over the wire and cannot
257
+ * be trusted to be one of the compiled variants — and a settled call whose
258
+ * result view is generic (how write/edit keep their execution errors on the
259
+ * generic path).
260
+ *
261
+ * This derivation consumes only `diffs`; the render intent's `title` field is
262
+ * deliberately dropped. The row supplies its own title (`Edit`/`Write · path`
263
+ * from the args), which outranks the view's `title`. A tool that names its own
264
+ * diff header therefore does not surface that text on the Web row.
265
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
266
+ * @returns the diff-card props, or null for the generic path.
267
+ */
268
+ function diffCardModel(block) {
269
+ if (!("kind" in block)) {
270
+ const call = block.callView?.card === "diff" ? block.callView : null;
271
+ const diffs = call === null ? null : narrowDiffs(call.diffs);
272
+ return diffs === null ? null : { card: { diffs } };
273
+ }
274
+ const result = block.resultView?.card === "diff" ? block.resultView : null;
275
+ const diffs = result === null ? null : narrowDiffs(result.diffs);
276
+ return diffs === null ? null : { card: { diffs } };
277
+ }
278
+ //#endregion
279
+ //#region lib/types/client/tool/models/search-card-model.js
280
+ /**
281
+ * Whether every file group in a matches view is structurally valid: the wire
282
+ * frame carries `shape` and `card` as strings the host schema checks, but not the
283
+ * grouped `files` fields, so a version mismatch or loose producer could deliver
284
+ * `shape: 'matches'` with a missing or malformed `files`. Rendering that would
285
+ * crash {@link SearchBlock} at `.reduce`/`.map`; invalid fields select the
286
+ * generic path instead.
287
+ * @param files - the candidate `files` field off the untrusted result view.
288
+ * @returns whether `files` is a valid {@link SearchFileGroup} array.
289
+ */
290
+ function isValidFiles(files) {
291
+ return Array.isArray(files) && files.every((file) => typeof file === "object" && file !== null && typeof file.path === "string" && Array.isArray(file.matches) && file.matches.every((match) => typeof match === "object" && match !== null && typeof match.lineNumber === "number" && typeof match.line === "string"));
292
+ }
293
+ /**
294
+ * Flatten a settled tool result's content blocks to their text, joined by
295
+ * newlines. The search view carries no result text — a UI without a card falls
296
+ * back to the raw `tool/result` content — so the truncation recovery footer is
297
+ * read from the block's own content here. Non-text blocks (a search result
298
+ * carries none) are skipped.
299
+ * @param content - the result node's content blocks.
300
+ * @returns the joined text, or undefined when empty.
301
+ */
302
+ function flattenContent(content) {
303
+ const text = content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
304
+ return text === "" ? void 0 : text;
305
+ }
306
+ /**
307
+ * Derive the search-card props for a tool call, or null when this call is not a
308
+ * search card and belongs on the generic path.
309
+ *
310
+ * Only the result side matters: the search card carries no call-time state, so
311
+ * a still-running call (no result view) is null, as is a settled call whose
312
+ * result view is not a search card — including a `card` value this UI version
313
+ * does not know, which arrives over the wire and cannot be trusted to be one of
314
+ * the compiled variants, a `card: 'search'` view whose `shape` is neither
315
+ * `matches` nor `paths` (equally untrusted wire data), and a generic result a
316
+ * `grep`/`glob` failure or nested `run_code` dispatch produces (its text keeps
317
+ * the generic path).
318
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
319
+ * @returns the search-card props, or null for the generic path.
320
+ */
321
+ function searchCardModel(block) {
322
+ if (!("kind" in block)) return null;
323
+ const result = block.resultView?.card === "search" ? block.resultView : null;
324
+ if (result === null) return null;
325
+ const common = {
326
+ truncated: result.truncated,
327
+ total: result.total
328
+ };
329
+ const recovery = result.truncated ? flattenContent(block.content) : void 0;
330
+ if (result.shape === "matches") {
331
+ if (!isValidFiles(result.files)) return null;
332
+ return {
333
+ title: result.title,
334
+ recovery,
335
+ card: {
336
+ kind: "matches",
337
+ files: result.files,
338
+ ...common
339
+ }
340
+ };
341
+ }
342
+ if (result.shape !== "paths") return null;
343
+ if (!Array.isArray(result.paths) || !result.paths.every((path) => typeof path === "string")) return null;
344
+ return {
345
+ title: result.title,
346
+ recovery,
347
+ card: {
348
+ kind: "paths",
349
+ paths: result.paths,
350
+ ...common
351
+ }
352
+ };
353
+ }
354
+ //#endregion
355
+ //#region lib/types/client/tool/models/terminal-card-model.js
356
+ /**
357
+ * Pure derivation of the terminal-card props from a frozen call slice: the
358
+ * `card:'terminal'` render intent the shell tools declare arrives on the
359
+ * snapshot as `callView`/`resultView`, and this is the one place that turns
360
+ * that pair into what {@link TerminalBlock} draws. Both conversation render
361
+ * sites (the chat tool row's expanded body and the details panel's Output
362
+ * section) call this, so the command, cwd, output and exit status they show
363
+ * are derived once.
364
+ * @module
365
+ */
366
+ /**
367
+ * Build the TerminalBlock display copy from the conversation locale seat —
368
+ * the one place the primitive's label surface pairs with this package's
369
+ * dictionary, shared by every terminal render site (chat row, bash row,
370
+ * details panel).
371
+ * @param t - the render site's conversation locale seat.
372
+ * @returns the full label set for {@link TerminalBlockProps}'s `labels`.
373
+ */
374
+ function terminalBlockLabels(t) {
375
+ return {
376
+ signal: (signal) => t("terminal.signal", { signal }),
377
+ exitCode: (code) => t("terminal.exitCode", { code }),
378
+ running: t("terminal.running"),
379
+ failed: t("terminal.failed"),
380
+ done: t("terminal.done"),
381
+ copy: t("copy"),
382
+ copied: t("copied"),
383
+ noOutput: t("terminal.noOutput"),
384
+ collapseAria: t("terminal.collapseAria"),
385
+ collapse: t("collapse"),
386
+ expandAria: (hidden) => t("terminal.expandAria", { n: hidden }),
387
+ expand: (hidden) => t("terminal.expandRest", { n: hidden })
388
+ };
389
+ }
390
+ /**
391
+ * True when a settled terminal card reports a failing exit — a non-zero code
392
+ * or a terminating signal. The bash tool settles a failing command as a
393
+ * completed call (`isError` stays false: the exit status is result data), so
394
+ * this is the collapsed row's only failure signal; without it the red exit
395
+ * pill would be visible only after expanding the card.
396
+ * @param model - a derived terminal card.
397
+ * @returns whether the card's exit status is a failure.
398
+ */
399
+ function terminalFailed(model) {
400
+ const { exitCode, signal, running } = model.card;
401
+ return running !== true && (exitCode !== void 0 && exitCode !== 0 || signal !== void 0);
402
+ }
403
+ /**
404
+ * Resolve a terminal view's working directory the way the render-intent
405
+ * contract assigns to the UI bridge: an absolute path is used as-is, a relative
406
+ * one joins under the session workspace, and an omitted one IS the session
407
+ * workspace. A pure presenter cannot see the session cwd, which is why this
408
+ * resolution belongs here rather than in the tool. Without a session cwd there
409
+ * is nothing to resolve against, so a relative path stays as authored and an
410
+ * omitted one stays absent (the prompt row then draws a bare `$`).
411
+ * @param viewCwd - the cwd the terminal call view carries, if any.
412
+ * @param sessionCwd - the session workspace root, if the caller knows it.
413
+ * @returns the working directory for the prompt label, or undefined.
414
+ */
415
+ function resolveTerminalCwd(viewCwd, sessionCwd) {
416
+ if (viewCwd === void 0 || viewCwd === "") return sessionCwd;
417
+ if (sessionCwd === void 0 || sessionCwd === "") return normalizeSegments(viewCwd);
418
+ return normalizeSegments((0, _deepseek_ai_dsh_client_runtime_client.resolveWorkspacePath)(sessionCwd, viewCwd));
419
+ }
420
+ /**
421
+ * Collapse `.` and `..` segments so the prompt label names the directory the
422
+ * command actually ran in. The bash executor resolves the workdir before
423
+ * running, so a joined `/w/app/..` must display as `w`, not as `..`. Separators
424
+ * are preserved as authored (a Windows path keeps its backslashes) because this
425
+ * value is only ever displayed; a `..` that would climb past the root is
426
+ * dropped, which is what a filesystem does with it. A UNC path's `server` and
427
+ * `share` are part of its root, not poppable segments: Windows cannot climb
428
+ * above a share, so `\\\\server\\share` with a `..` stays there.
429
+ * @param path - a joined or absolute path, possibly carrying `.`/`..` segments.
430
+ * @returns the same path with those segments resolved.
431
+ */
432
+ function normalizeSegments(path) {
433
+ if (!/(?:^|[/\\])\.\.?(?:[/\\]|$)/.test(path)) return path;
434
+ const unc = /^[/\\]{2}([^/\\]+)[/\\]+([^/\\]+)/.exec(path);
435
+ if (unc !== null) {
436
+ const [matched, server, share] = unc;
437
+ const root = `\\\\${String(server)}\\${String(share)}`;
438
+ const rest = collapse(path.slice(matched.length), true);
439
+ return rest === "" ? root : `${root}\\${rest}`;
440
+ }
441
+ const separator = path.includes("\\") && !path.includes("/") ? "\\" : "/";
442
+ const rooted = /^[/\\]/.test(path);
443
+ const drive = /^[A-Za-z]:/.exec(path)?.[0] ?? "";
444
+ const body = collapse(path.slice(drive.length), rooted || drive !== "", separator);
445
+ const leading = rooted ? separator : "";
446
+ return drive === "" ? `${leading}${body}` : `${drive}${rooted ? leading : separator}${body}`;
447
+ }
448
+ /**
449
+ * Collapse the `.`/`..` segments of a path body against a known root state.
450
+ * @param body - the path after any drive letter or UNC root.
451
+ * @param rooted - the body hangs off a root, so a `..` at its top is dropped
452
+ * the way a filesystem drops one; without a root the `..` is kept, since it
453
+ * stays meaningful against a cwd this function cannot see.
454
+ * @param separator - separator to rejoin with (default `/`).
455
+ * @returns the collapsed body, without leading or trailing separators.
456
+ */
457
+ function collapse(body, rooted, separator = "/") {
458
+ const kept = [];
459
+ for (const segment of body.split(/[/\\]/)) {
460
+ if (segment === "" || segment === ".") continue;
461
+ if (segment === "..") {
462
+ if (kept.length > 0 && kept[kept.length - 1] !== "..") kept.pop();
463
+ else if (!rooted) kept.push(segment);
464
+ continue;
465
+ }
466
+ kept.push(segment);
467
+ }
468
+ return kept.join(separator);
469
+ }
470
+ /**
471
+ * Derive the terminal-card props for a tool call, or null when this call is
472
+ * not a terminal card and belongs on the generic path.
473
+ *
474
+ * The call side supplies the command and its working directory; the result
475
+ * side supplies the captured output and exit status. Three cases produce
476
+ * null, all of them the documented generic-card default:
477
+ *
478
+ * - Neither side declares `card:'terminal'` — including a `card` value this
479
+ * UI version does not know, which arrives over the wire and therefore
480
+ * cannot be trusted to be one of the compiled variants.
481
+ * - A settled call whose result view is not a terminal card: the result
482
+ * presentation decides how the settled call renders, and the bash tool
483
+ * returns a generic fenced card for an execution error or a background
484
+ * start, whose text and error styling the generic path preserves.
485
+ *
486
+ * Window truncation can drop the call head from a settled result (see
487
+ * `ToolResultNode.call`/`callView` in dsh-client-runtime), leaving a terminal
488
+ * result with no call side. That still renders: the command falls back to the
489
+ * result view's replacement title, then to an empty command (the prompt line
490
+ * draws bare), and the prompt shows no cwd.
491
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
492
+ * @param sessionCwd - the session workspace root, which resolves an omitted or
493
+ * relative view cwd (see {@link resolveTerminalCwd}); absent leaves both unresolved.
494
+ * @returns the terminal-card props, or null for the generic path.
495
+ */
496
+ function terminalCardModel(block, sessionCwd) {
497
+ const call = block.callView?.card === "terminal" ? block.callView : null;
498
+ if (!("kind" in block)) return call === null ? null : {
499
+ description: call.description,
500
+ card: {
501
+ command: call.title,
502
+ cwd: resolveTerminalCwd(call.cwd, sessionCwd),
503
+ output: void 0,
504
+ exitCode: void 0,
505
+ signal: void 0,
506
+ running: true
507
+ }
508
+ };
509
+ const result = block.resultView?.card === "terminal" ? block.resultView : null;
510
+ if (result === null) return null;
511
+ return {
512
+ description: call?.description,
513
+ card: {
514
+ command: result.title ?? call?.title ?? "",
515
+ cwd: call === null ? void 0 : resolveTerminalCwd(call.cwd, sessionCwd),
516
+ output: result.output,
517
+ exitCode: result.exitCode,
518
+ signal: result.signal,
519
+ running: false
520
+ }
521
+ };
522
+ }
523
+ //#endregion
524
+ //#region lib/types/client/tool/models/web-card-model.js
525
+ /**
526
+ * Derive the web-card props for a tool call, or null when this call is not a
527
+ * web card and belongs on the generic path.
528
+ *
529
+ * The result side supplies the whole card: the sources and answer for a
530
+ * `search`, the URL and status for a `fetch`. Cases producing null, all of
531
+ * them the documented generic-card default:
532
+ *
533
+ * - A running call (no `resultView` yet): the web tools keep a generic pending
534
+ * card, so nothing web-shaped exists until the call settles.
535
+ * - A settled call whose result view is not a web card — including a `card`
536
+ * value this UI version does not know, which arrives over the wire and so
537
+ * cannot be trusted to be one of the compiled variants, and a generic result
538
+ * view (a web tool's error path returns the generic card, whose text the
539
+ * generic path preserves).
540
+ * - A web card whose `kind` this UI version does not know (a newer host's
541
+ * value): the wire cannot be trusted to be `search` or `fetch`, so it takes
542
+ * the generic path rather than rendering as a malformed fetch.
543
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
544
+ * @returns the web-card props, or null for the generic path.
545
+ */
546
+ function webCardModel(block) {
547
+ if (!("kind" in block)) return null;
548
+ const result = block.resultView;
549
+ if (result?.card !== "web") return null;
550
+ if (result.kind === "search") return {
551
+ kind: "search",
552
+ answer: result.answer,
553
+ sources: result.sources.map((source) => ({
554
+ url: source.url,
555
+ title: source.title,
556
+ snippet: source.snippet,
557
+ publishedAt: source.publishedAt
558
+ })),
559
+ truncated: result.truncated
560
+ };
561
+ if (result.kind === "fetch") return {
562
+ kind: "fetch",
563
+ url: result.url,
564
+ statusCode: result.statusCode,
565
+ truncated: result.truncated
566
+ };
567
+ return null;
568
+ }
569
+ //#endregion
570
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
571
+ function r(e) {
572
+ var t, f, n = "";
573
+ if ("string" == typeof e || "number" == typeof e) n += e;
574
+ else if ("object" == typeof e) if (Array.isArray(e)) {
575
+ var o = e.length;
576
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
577
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
578
+ return n;
579
+ }
580
+ function clsx() {
581
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
582
+ return n;
583
+ }
584
+ //#endregion
585
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css.mjs
586
+ const css$3 = ".o3BgMG_root{flex-direction:column;display:flex}.o3BgMG_row{position:relative;overflow:hidden}.o3BgMG_root[data-state=running] .o3BgMG_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite o3BgMG_dsh-tool-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes o3BgMG_dsh-tool-row-sweep{0%{left:-300px}90%,to{left:100%}}.o3BgMG_leading{flex-shrink:0}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_leading,.o3BgMG_root[data-tool^=cordis_] .o3BgMG_title{color:var(--dsw-alias-state-business-primary)}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_title{font-weight:500}.o3BgMG_root[data-tool^=cordis_] .o3BgMG_sep{background:var(--dsw-alias-state-business-primary)}.o3BgMG_chevron{color:var(--dsw-alias-label-secondary)}.o3BgMG_title{font-weight:400}.o3BgMG_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.o3BgMG_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-tertiary);flex:auto;font-size:14px;line-height:24px;overflow:hidden}.o3BgMG_summarySuffix{white-space:nowrap;color:var(--dsw-alias-label-tertiary);flex:none;margin-left:4px;font-size:14px;line-height:24px}.o3BgMG_fileLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:inherit;text-align:left;color:var(--dsw-alias-label-secondary);text-decoration:underline;text-decoration-color:var(--dsw-alias-label-quaternary);text-underline-offset:3px;cursor:pointer;background:0 0;border:none;flex:auto;margin:0;padding:0;font-size:14px;line-height:24px;overflow:hidden}.o3BgMG_fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}.o3BgMG_errorSummary{color:var(--dsw-alias-state-error-primary)}.o3BgMG_bodyWrap{flex-direction:column;display:flex}.o3BgMG_inspectButton{border:1px solid var(--dsw-alias-border-l2);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}.o3BgMG_root:hover .o3BgMG_inspectButton,.o3BgMG_inspectButton:focus-visible{opacity:1}.o3BgMG_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.o3BgMG_bodyScroll{max-height:260px;overflow-y:auto}.o3BgMG_ioCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);border-radius:12px;flex-direction:column;margin:4px 0 4px 4px;display:flex}.o3BgMG_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.o3BgMG_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.o3BgMG_ioSection::-webkit-scrollbar-track{margin:6px 0}.o3BgMG_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.o3BgMG_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.o3BgMG_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.o3BgMG_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.o3BgMG_codeBody,.o3BgMG_terminalBody,.o3BgMG_diffBody,.o3BgMG_readBody,.o3BgMG_searchBody,.o3BgMG_webBody{margin:4px 0 4px 4px}.o3BgMG_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}.o3BgMG_codeBody{--dsl-code-block-content-font:var(--dsw-font-markdown-code-block-small)}.o3BgMG_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:1px solid var(--dsw-alias-border-l1)}.o3BgMG_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
587
+ const tagId$3 = "@deepseek-ai/dsh-client-ui-tool/ToolRow.module.css";
588
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
589
+ const tag = document.createElement("style");
590
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-tool";
591
+ tag.dataset.pluginCss = tagId$3;
592
+ tag.textContent = css$3;
593
+ document.head.appendChild(tag);
594
+ }
595
+ var ToolRow_module_css_default = {
596
+ "summary": "o3BgMG_summary",
597
+ "row": "o3BgMG_row",
598
+ "visuallyHidden": "o3BgMG_visuallyHidden",
599
+ "summarySuffix": "o3BgMG_summarySuffix",
600
+ "sep": "o3BgMG_sep",
601
+ "chevron": "o3BgMG_chevron",
602
+ "inspectButton": "o3BgMG_inspectButton",
603
+ "codeBody": "o3BgMG_codeBody",
604
+ "dsh-tool-row-sweep": "o3BgMG_dsh-tool-row-sweep",
605
+ "ioText": "o3BgMG_ioText",
606
+ "bodyWrap": "o3BgMG_bodyWrap",
607
+ "diffBody": "o3BgMG_diffBody",
608
+ "readBody": "o3BgMG_readBody",
609
+ "searchBody": "o3BgMG_searchBody",
610
+ "fileLink": "o3BgMG_fileLink",
611
+ "root": "o3BgMG_root",
612
+ "errorSummary": "o3BgMG_errorSummary",
613
+ "webBody": "o3BgMG_webBody",
614
+ "title": "o3BgMG_title",
615
+ "ioCard": "o3BgMG_ioCard",
616
+ "terminalBody": "o3BgMG_terminalBody",
617
+ "leading": "o3BgMG_leading",
618
+ "bodyScroll": "o3BgMG_bodyScroll",
619
+ "ioLabel": "o3BgMG_ioLabel",
620
+ "ioSection": "o3BgMG_ioSection",
621
+ "ioDivider": "o3BgMG_ioDivider",
622
+ "searchRecovery": "o3BgMG_searchRecovery"
623
+ };
624
+ //#endregion
625
+ //#region lib/types/client/tool/components/ToolRow.js
626
+ /** Leading-slot state substitution: the tool icon yields to the terminal state
627
+ * semantic (error = red, interrupted = amber halo). Running keeps the icon —
628
+ * the row sweep (CSS on data-state) carries the in-flight signal. */
629
+ function leadingFor$1(state, icon) {
630
+ switch (state) {
631
+ case "error": return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" });
632
+ case "stopped": return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" });
633
+ default: return icon;
634
+ }
635
+ }
636
+ /** Visually hidden run-state label: the StateDot and the CSS sweep are both
637
+ * aria-hidden / colour-only, so assistive technology needs this text to know a
638
+ * row is running, failed, or interrupted. null in the ok state (the icon and
639
+ * summary already describe a settled row). */
640
+ function stateStatus$1(state, t) {
641
+ switch (state) {
642
+ case "running": return t("row.running");
643
+ case "error": return t("row.failed");
644
+ case "stopped": return t("row.stopped");
645
+ default: return null;
646
+ }
647
+ }
648
+ function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, body, output, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect }) {
649
+ const [expanded, setExpanded] = (0, react.useState)(false);
650
+ const terminalBody = terminal ?? null;
651
+ const diffBody = diff ?? null;
652
+ const readBody = read ?? null;
653
+ const searchBody = search ?? null;
654
+ const webBody = web ?? null;
655
+ const outputText = output ?? null;
656
+ const expandable = body !== null || outputText !== null || (terminalBody ?? diffBody ?? readBody ?? searchBody ?? webBody) !== null;
657
+ const open = expanded && expandable;
658
+ const status = stateStatus$1(state, t);
659
+ const failureLine = state === "error" ? errorSummary ?? null : null;
660
+ const summaryText = failureLine ?? summary;
661
+ const suffix = failureLine === null ? summarySuffix ?? null : null;
662
+ const fileLink = filePath !== void 0 && onOpenFile !== void 0 && failureLine === null;
663
+ const toggleExpand = () => {
664
+ setExpanded((v) => !v);
665
+ };
666
+ const openFile = (event) => {
667
+ event.stopPropagation();
668
+ if (filePath !== void 0) onOpenFile?.(filePath);
669
+ };
670
+ const fileLinkKeyDown = (event) => {
671
+ if (event.key === "Enter" || event.key === " ") event.stopPropagation();
672
+ };
673
+ const cardBody = variant === "code" ? null : body;
674
+ return (0, react_jsx_runtime.jsxs)("div", {
675
+ className: ToolRow_module_css_default.root,
676
+ "data-variant": variant,
677
+ "data-tool": toolName,
678
+ "data-state": state,
679
+ children: [status !== null && (0, react_jsx_runtime.jsx)("span", {
680
+ className: ToolRow_module_css_default.visuallyHidden,
681
+ children: status
682
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
683
+ rowClassName: ToolRow_module_css_default.row,
684
+ leadingClassName: ToolRow_module_css_default.leading,
685
+ titleClassName: ToolRow_module_css_default.title,
686
+ chevronClassName: ToolRow_module_css_default.chevron,
687
+ icon: leadingFor$1(state, icon),
688
+ title,
689
+ open,
690
+ expandable,
691
+ expandOnRowClick: true,
692
+ keepContentWhenOpen: true,
693
+ onToggle: toggleExpand,
694
+ collapsedContent: summaryText !== "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
695
+ (0, react_jsx_runtime.jsx)("span", {
696
+ className: ToolRow_module_css_default.sep,
697
+ "aria-hidden": true
698
+ }),
699
+ fileLink ? (0, react_jsx_runtime.jsx)("button", {
700
+ type: "button",
701
+ className: ToolRow_module_css_default.fileLink,
702
+ onClick: openFile,
703
+ onKeyDown: fileLinkKeyDown,
704
+ children: summaryText
705
+ }) : (0, react_jsx_runtime.jsx)("span", {
706
+ className: clsx(ToolRow_module_css_default.summary, failureLine !== null && ToolRow_module_css_default.errorSummary),
707
+ children: summaryText
708
+ }),
709
+ suffix !== null && (0, react_jsx_runtime.jsx)("span", {
710
+ className: ToolRow_module_css_default.summarySuffix,
711
+ children: suffix
712
+ })
713
+ ] }),
714
+ children: (0, react_jsx_runtime.jsxs)("div", {
715
+ className: ToolRow_module_css_default.bodyWrap,
716
+ children: [terminalBody !== null ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TerminalBlock, {
717
+ ...terminalBody.card,
718
+ maxLines: Infinity,
719
+ labels: terminalBlockLabels(t),
720
+ className: ToolRow_module_css_default.terminalBody
721
+ }) : diffBody !== null ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DiffBlock, {
722
+ ...diffBody.card,
723
+ maxLines: 8,
724
+ className: ToolRow_module_css_default.diffBody
725
+ }) : readBody !== null ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ReadBlock, {
726
+ ...readBody,
727
+ maxLines: 8,
728
+ className: ToolRow_module_css_default.readBody
729
+ }) : searchBody !== null ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SearchBlock, {
730
+ ...searchBody.card,
731
+ maxLines: 8,
732
+ className: ToolRow_module_css_default.searchBody
733
+ }), searchBody.recovery !== void 0 && (0, react_jsx_runtime.jsx)("div", {
734
+ className: ToolRow_module_css_default.searchRecovery,
735
+ children: searchBody.recovery
736
+ })] }) : webBody !== null ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.WebBlock, {
737
+ ...webBody,
738
+ className: ToolRow_module_css_default.webBody
739
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [variant === "code" && body !== null && (0, react_jsx_runtime.jsx)("div", {
740
+ className: ToolRow_module_css_default.bodyScroll,
741
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.CodeBlock, {
742
+ code: body,
743
+ lang: "typescript",
744
+ copyLabel: t("copy"),
745
+ copiedLabel: t("copied"),
746
+ className: ToolRow_module_css_default.codeBody
747
+ })
748
+ }), (cardBody !== null || outputText !== null) && (0, react_jsx_runtime.jsxs)("div", {
749
+ className: ToolRow_module_css_default.ioCard,
750
+ children: [
751
+ cardBody !== null && (0, react_jsx_runtime.jsxs)("div", {
752
+ className: ToolRow_module_css_default.ioSection,
753
+ children: [(0, react_jsx_runtime.jsx)("span", {
754
+ className: ToolRow_module_css_default.ioLabel,
755
+ children: "IN"
756
+ }), (0, react_jsx_runtime.jsx)("span", {
757
+ className: ToolRow_module_css_default.ioText,
758
+ children: cardBody
759
+ })]
760
+ }),
761
+ cardBody !== null && outputText !== null && (0, react_jsx_runtime.jsx)("span", {
762
+ className: ToolRow_module_css_default.ioDivider,
763
+ "aria-hidden": true
764
+ }),
765
+ outputText !== null && (0, react_jsx_runtime.jsxs)("div", {
766
+ className: ToolRow_module_css_default.ioSection,
767
+ children: [(0, react_jsx_runtime.jsx)("span", {
768
+ className: ToolRow_module_css_default.ioLabel,
769
+ children: "OUT"
770
+ }), (0, react_jsx_runtime.jsx)("span", {
771
+ className: ToolRow_module_css_default.ioText,
772
+ "data-error": state === "error" || void 0,
773
+ children: outputText
774
+ })]
775
+ })
776
+ ]
777
+ })] }), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
778
+ type: "button",
779
+ className: ToolRow_module_css_default.inspectButton,
780
+ onClick: inspect,
781
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconInspectOutline12, {}), "Inspect"]
782
+ })]
783
+ })
784
+ })]
785
+ });
786
+ }
787
+ //#endregion
788
+ //#region lib/types/client/tool/toolviews/GenericToolCard.js
789
+ /** Variant leading icons (figma table); all glyphs render at 14 inside the 16px leading box. */
790
+ const VARIANT_ICONS = {
791
+ search: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
792
+ read: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
793
+ bash: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, { size: 14 }),
794
+ write: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
795
+ edit: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
796
+ code: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCodeOutline16, { size: 14 }),
797
+ others: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 14 })
798
+ };
799
+ function GenericToolCard({ toolName, block, cwd, openFile, inspect, t }) {
800
+ const model = toolRowModel(toolName, block, cwd);
801
+ const terminal = terminalCardModel(block, cwd);
802
+ const read = readCardModel(block, cwd);
803
+ const diff = diffCardModel(block);
804
+ const search = searchCardModel(block);
805
+ const web = webCardModel(block);
806
+ const state = model.state === "ok" && terminal !== null && terminalFailed(terminal) ? "error" : model.state;
807
+ const singleFile = model.filePath !== void 0;
808
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
809
+ t,
810
+ variant: model.variant,
811
+ toolName,
812
+ icon: VARIANT_ICONS[model.variant],
813
+ title: model.title,
814
+ summary: terminal?.description ?? search?.title ?? model.summary,
815
+ body: singleFile ? null : model.body,
816
+ output: model.output,
817
+ errorSummary: model.errorSummary,
818
+ terminal,
819
+ diff,
820
+ read,
821
+ search,
822
+ web,
823
+ state,
824
+ filePath: model.filePath,
825
+ onOpenFile: singleFile ? openFile : void 0,
826
+ inspect
827
+ });
828
+ }
829
+ //#endregion
830
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/ToolCallTree.module.css.mjs
831
+ const css$2 = ".ztWv_q_callRow{border-radius:6px}.ztWv_q_subCalls{border-left:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:4px;margin:4px 0 2px 22px;padding-left:8px;display:flex}";
832
+ const tagId$2 = "@deepseek-ai/dsh-client-ui-tool/ToolCallTree.module.css";
833
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
834
+ const tag = document.createElement("style");
835
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-tool";
836
+ tag.dataset.pluginCss = tagId$2;
837
+ tag.textContent = css$2;
838
+ document.head.appendChild(tag);
839
+ }
840
+ var ToolCallTree_module_css_default = {
841
+ "subCalls": "ztWv_q_subCalls",
842
+ "callRow": "ztWv_q_callRow"
843
+ };
844
+ //#endregion
845
+ //#region lib/types/client/tool/ToolCallTree.js
846
+ /** Root/subcall Tool composition with one keyed atomic dispatch path. */
847
+ /** Resolve a Tool call's wire name from either lifecycle form. */
848
+ function callName(node) {
849
+ return "kind" in node ? node.call?.name ?? "" : node.name;
850
+ }
851
+ /** One atomic call dispatched through the Tool-owned keyed slot. */
852
+ const ToolCall = (0, react.memo)(function ToolCall({ renderSlot, callId, toolName, block, openFile, selected, cwd, inspectCall, t, children }) {
853
+ const owner = (0, react.useMemo)(() => ({
854
+ callId,
855
+ toolName,
856
+ block,
857
+ openFile,
858
+ cwd,
859
+ inspect: () => {
860
+ inspectCall(callId);
861
+ }
862
+ }), [
863
+ callId,
864
+ toolName,
865
+ block,
866
+ openFile,
867
+ cwd,
868
+ inspectCall
869
+ ]);
870
+ return (0, react_jsx_runtime.jsxs)("div", {
871
+ className: ToolCallTree_module_css_default.callRow,
872
+ "data-chat-anchor-key": `call:${callId}`,
873
+ "data-chat-call-id": callId,
874
+ "data-selected": selected || void 0,
875
+ children: [renderSlot("tool.call.toolview", owner, {
876
+ entryKey: toolName,
877
+ fallback: (0, react_jsx_runtime.jsx)(GenericToolCard, {
878
+ ...owner,
879
+ t
880
+ })
881
+ }), children]
882
+ });
883
+ });
884
+ const ToolCallBranch = (0, react.memo)(function ToolCallBranch({ renderSlot, block, selectedCallId, cwd, openFile, inspectCall, t }) {
885
+ return (0, react_jsx_runtime.jsx)(ToolCall, {
886
+ renderSlot,
887
+ callId: block.callId,
888
+ toolName: callName(block),
889
+ block,
890
+ openFile,
891
+ selected: block.callId === selectedCallId,
892
+ cwd,
893
+ inspectCall,
894
+ t,
895
+ children: block.subCalls.length > 0 ? (0, react_jsx_runtime.jsx)("div", {
896
+ className: ToolCallTree_module_css_default.subCalls,
897
+ "data-subcalls": true,
898
+ children: block.subCalls.map((child) => (0, react_jsx_runtime.jsx)(ToolCallBranch, {
899
+ renderSlot,
900
+ block: child,
901
+ selectedCallId,
902
+ cwd,
903
+ openFile,
904
+ inspectCall,
905
+ t
906
+ }, child.callId))
907
+ }) : null
908
+ });
909
+ });
910
+ /**
911
+ * Render one root Tool call and its recursive children through the same
912
+ * atomic keyed dispatch.
913
+ * @param props - whole-Tool owner data and the Tool-owned child-slot share.
914
+ * @returns the Tool call tree.
915
+ */
916
+ function ToolCallTree({ renderSlot, node, selectedCallId, cwd, openFile, inspectCall, t }) {
917
+ const block = node.data.root;
918
+ return (0, react_jsx_runtime.jsx)(ToolCallBranch, {
919
+ renderSlot,
920
+ block,
921
+ selectedCallId,
922
+ cwd,
923
+ openFile,
924
+ inspectCall,
925
+ t
926
+ });
927
+ }
928
+ //#endregion
929
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/ToolDetails.module.css.mjs
930
+ const css$1 = ".xDAfVq_description{color:var(--dsw-alias-label-secondary);font:var(--dsw-font-xs-13);margin:0 0 6px}.xDAfVq_cardBody{margin:0}.xDAfVq_recovery{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xs-13);margin:6px 0 0}.xDAfVq_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}.xDAfVq_code[data-error]{color:var(--dsw-alias-state-error-primary)}.xDAfVq_read,.xDAfVq_web{margin:0}.xDAfVq_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}";
931
+ const tagId$1 = "@deepseek-ai/dsh-client-ui-tool/ToolDetails.module.css";
932
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
933
+ const tag = document.createElement("style");
934
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-tool";
935
+ tag.dataset.pluginCss = tagId$1;
936
+ tag.textContent = css$1;
937
+ document.head.appendChild(tag);
938
+ }
939
+ var ToolDetails_module_css_default = {
940
+ "recovery": "xDAfVq_recovery",
941
+ "cardBody": "xDAfVq_cardBody",
942
+ "code": "xDAfVq_code",
943
+ "description": "xDAfVq_description",
944
+ "read": "xDAfVq_read",
945
+ "web": "xDAfVq_web",
946
+ "empty": "xDAfVq_empty"
947
+ };
948
+ //#endregion
949
+ //#region lib/types/client/tool/ToolDetails.js
950
+ /** Card-aware output body for the selected Tool call in details. */
951
+ /**
952
+ * Render the selected Tool call's structured output when its presentation
953
+ * intent is known, otherwise preserve the flattened result text.
954
+ * @param props - selected call slice, workspace root, and locale seat.
955
+ * @returns the details output body.
956
+ */
957
+ function ToolDetails({ block, cwd, t }) {
958
+ const terminal = terminalCardModel(block, cwd);
959
+ if (terminal !== null) return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [terminal.description !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
960
+ className: ToolDetails_module_css_default.description,
961
+ children: terminal.description
962
+ }) : null, (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TerminalBlock, {
963
+ ...terminal.card,
964
+ labels: terminalBlockLabels(t),
965
+ className: ToolDetails_module_css_default.cardBody
966
+ })] });
967
+ const read = readCardModel(block, cwd);
968
+ if (read !== null) return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.ReadBlock, {
969
+ ...read,
970
+ className: ToolDetails_module_css_default.read
971
+ });
972
+ const diff = diffCardModel(block);
973
+ if (diff !== null) return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DiffBlock, {
974
+ ...diff.card,
975
+ className: ToolDetails_module_css_default.cardBody
976
+ });
977
+ const search = searchCardModel(block);
978
+ if (search !== null) return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SearchBlock, {
979
+ ...search.card,
980
+ className: ToolDetails_module_css_default.cardBody
981
+ }), search.recovery !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
982
+ className: ToolDetails_module_css_default.recovery,
983
+ children: search.recovery
984
+ }) : null] });
985
+ const web = webCardModel(block);
986
+ if (web !== null) {
987
+ const body = "kind" in block ? resultText(block) : "";
988
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.WebBlock, {
989
+ ...web,
990
+ className: ToolDetails_module_css_default.web
991
+ }), body !== "" ? (0, react_jsx_runtime.jsx)("pre", {
992
+ className: ToolDetails_module_css_default.code,
993
+ children: body
994
+ }) : null] });
995
+ }
996
+ if (!("kind" in block)) return (0, react_jsx_runtime.jsx)("div", {
997
+ className: ToolDetails_module_css_default.empty,
998
+ children: t("details.running")
999
+ });
1000
+ return (0, react_jsx_runtime.jsx)("pre", {
1001
+ className: ToolDetails_module_css_default.code,
1002
+ "data-error": block.isError || void 0,
1003
+ children: resultText(block)
1004
+ });
1005
+ }
1006
+ //#endregion
1007
+ //#region lib/types/client/locale.js
1008
+ /** Locale namespace supplied by the conversation owner to Tool renderers. */
1009
+ const CONVERSATION_NS = "conversation";
1010
+ //#endregion
1011
+ //#region lib/types/client/tool/toolviews/ask-question-row.js
1012
+ function isAnswer(value) {
1013
+ return typeof value === "object" && value !== null;
1014
+ }
1015
+ /** Answered-count summary from the result JSON (a skipped question has
1016
+ * empty `selected` and no `custom`); null when answer fields are invalid. */
1017
+ function answeredSummary(text, t) {
1018
+ let parsed;
1019
+ try {
1020
+ parsed = JSON.parse(text);
1021
+ } catch {
1022
+ return null;
1023
+ }
1024
+ if (typeof parsed !== "object" || parsed === null) return null;
1025
+ const answers = parsed.answers;
1026
+ if (!Array.isArray(answers) || !answers.every(isAnswer)) return null;
1027
+ const answered = answers.filter((a) => Array.isArray(a.selected) && a.selected.length > 0 || typeof a.custom === "string" && a.custom !== "").length;
1028
+ return t("ask.answered", {
1029
+ answered,
1030
+ total: answers.length
1031
+ });
1032
+ }
1033
+ /** One-line question-interaction row (the whole row toggles the call's
1034
+ * Input/Output sections, ToolRow's unified expand). */
1035
+ function AskQuestionRow({ toolName, block, inspect, t }) {
1036
+ const model = toolRowModel(toolName, block);
1037
+ const code = "kind" in block ? block.error?.code : void 0;
1038
+ let summary = model.summary;
1039
+ let state = model.state;
1040
+ if (code === "ASK_CANCELLED") summary = t("ask.cancelled");
1041
+ else if (code === "ASK_ABORTED") {
1042
+ summary = t("ask.interrupted");
1043
+ state = "stopped";
1044
+ } else if (model.state === "running") summary = t("ask.waiting");
1045
+ else if ("kind" in block && model.state === "ok") summary = answeredSummary(block.content.filter((b) => b.type === "text").map((b) => b.text).join(""), t) ?? model.summary;
1046
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1047
+ t,
1048
+ variant: model.variant,
1049
+ toolName,
1050
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQuestionOutline14, {}),
1051
+ title: t("ask.rowTitle"),
1052
+ summary,
1053
+ body: model.body,
1054
+ output: model.output,
1055
+ state,
1056
+ inspect
1057
+ });
1058
+ }
1059
+ /**
1060
+ * The ask-question row as a plain registrant plugin following the chat
1061
+ * toolview declaration across independent activation and reload lifetimes.
1062
+ */
1063
+ const askQuestionToolview = {
1064
+ name: "ask-question-toolview",
1065
+ inject: ["slots"],
1066
+ /**
1067
+ * Register the ask-question row into the Tool-owned keyed view slot.
1068
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1069
+ */
1070
+ apply(ctx) {
1071
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1072
+ name: "tool.call.toolview",
1073
+ key: "ask_user_question",
1074
+ locale: CONVERSATION_NS
1075
+ }, AskQuestionRow));
1076
+ }
1077
+ };
1078
+ //#endregion
1079
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css.mjs
1080
+ const css = ".CY-8Ka_card{flex-direction:column;display:flex}.CY-8Ka_terminal{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:1px solid var(--dsw-alias-border-l1);margin:4px 0 4px 4px}.CY-8Ka_ioCard{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small);border-radius:12px;flex-direction:column;margin:4px 0 4px 4px;display:flex}.CY-8Ka_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.CY-8Ka_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.CY-8Ka_ioSection::-webkit-scrollbar-track{margin:6px 0}.CY-8Ka_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.CY-8Ka_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.CY-8Ka_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.CY-8Ka_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.CY-8Ka_root[data-expandable]{cursor:pointer}.CY-8Ka_root{align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.CY-8Ka_root[data-state=running]:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite CY-8Ka_dsh-bash-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes CY-8Ka_dsh-bash-row-sweep{0%{left:-300px}90%,to{left:100%}}.CY-8Ka_leading{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex;position:relative}.CY-8Ka_chevron{color:var(--dsw-alias-label-secondary)}.CY-8Ka_iconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.CY-8Ka_chevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.CY-8Ka_root:hover .CY-8Ka_iconIdle{opacity:0}.CY-8Ka_root:hover .CY-8Ka_chevronHover{opacity:1}.CY-8Ka_title{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.CY-8Ka_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.CY-8Ka_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-tertiary);flex:auto;font-size:14px;line-height:24px;overflow:hidden}.CY-8Ka_errorSummary{color:var(--dsw-alias-state-error-primary)}.CY-8Ka_bodyWrap{flex-direction:column;display:flex}.CY-8Ka_inspectButton{border:1px solid var(--dsw-alias-border-l2);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}.CY-8Ka_card:hover .CY-8Ka_inspectButton,.CY-8Ka_inspectButton:focus-visible{opacity:1}.CY-8Ka_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.CY-8Ka_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
1081
+ const tagId = "@deepseek-ai/dsh-client-ui-tool/bash-sample.module.css";
1082
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1083
+ const tag = document.createElement("style");
1084
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-tool";
1085
+ tag.dataset.pluginCss = tagId;
1086
+ tag.textContent = css;
1087
+ document.head.appendChild(tag);
1088
+ }
1089
+ var bash_sample_module_css_default = {
1090
+ "bodyWrap": "CY-8Ka_bodyWrap",
1091
+ "inspectButton": "CY-8Ka_inspectButton",
1092
+ "terminal": "CY-8Ka_terminal",
1093
+ "visuallyHidden": "CY-8Ka_visuallyHidden",
1094
+ "chevronHover": "CY-8Ka_chevronHover",
1095
+ "dsh-bash-row-sweep": "CY-8Ka_dsh-bash-row-sweep",
1096
+ "root": "CY-8Ka_root",
1097
+ "leading": "CY-8Ka_leading",
1098
+ "iconIdle": "CY-8Ka_iconIdle",
1099
+ "title": "CY-8Ka_title",
1100
+ "sep": "CY-8Ka_sep",
1101
+ "card": "CY-8Ka_card",
1102
+ "ioSection": "CY-8Ka_ioSection",
1103
+ "ioLabel": "CY-8Ka_ioLabel",
1104
+ "ioDivider": "CY-8Ka_ioDivider",
1105
+ "ioText": "CY-8Ka_ioText",
1106
+ "ioCard": "CY-8Ka_ioCard",
1107
+ "chevron": "CY-8Ka_chevron",
1108
+ "summary": "CY-8Ka_summary",
1109
+ "errorSummary": "CY-8Ka_errorSummary"
1110
+ };
1111
+ //#endregion
1112
+ //#region lib/types/client/tool/toolviews/bash-sample.js
1113
+ function leadingFor(state) {
1114
+ switch (state) {
1115
+ case "error": return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" });
1116
+ case "stopped": return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "warning" });
1117
+ default: return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, { size: 14 });
1118
+ }
1119
+ }
1120
+ /** Visually hidden status — StateDot is aria-hidden; AT needs a text label. */
1121
+ function stateStatus(state, t) {
1122
+ switch (state) {
1123
+ case "running": return t("bash.running");
1124
+ case "error": return t("bash.failed");
1125
+ case "stopped": return t("bash.stopped");
1126
+ default: return null;
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Bash row: icon + Bash · {description} in the shared ToolRow chrome, the
1131
+ * whole row toggling the command's terminal or generic error card (ToolRow's unified
1132
+ * expand interaction, replicated locally per the registrant posture).
1133
+ */
1134
+ function BashRow({ toolName, block, sessionId, useSessions, inspect, t }) {
1135
+ const model = toolRowModel(toolName, block);
1136
+ const terminal = terminalCardModel(block, useSessions((list) => list.byId[sessionId]?.cwd));
1137
+ const state = model.state === "ok" && terminal !== null && terminalFailed(terminal) ? "error" : model.state;
1138
+ const status = stateStatus(state, t);
1139
+ const [expanded, setExpanded] = (0, react.useState)(false);
1140
+ const genericError = terminal === null && model.state === "error" && (model.body !== null || model.output !== null);
1141
+ const expandable = terminal !== null || genericError;
1142
+ const open = expanded && expandable;
1143
+ const failureLine = model.state === "error" ? model.errorSummary : null;
1144
+ const toggleExpand = () => {
1145
+ setExpanded((v) => !v);
1146
+ };
1147
+ const toggleFromKeyboard = (event) => {
1148
+ if (!expandable || event.key !== "Enter" && event.key !== " ") return;
1149
+ event.preventDefault();
1150
+ toggleExpand();
1151
+ };
1152
+ const leading = open ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: bash_sample_module_css_default.chevron }) : expandable ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
1153
+ className: bash_sample_module_css_default.iconIdle,
1154
+ children: leadingFor(state)
1155
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: clsx(bash_sample_module_css_default.chevron, bash_sample_module_css_default.chevronHover) })] }) : leadingFor(state);
1156
+ return (0, react_jsx_runtime.jsxs)("div", {
1157
+ className: bash_sample_module_css_default.card,
1158
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1159
+ className: bash_sample_module_css_default.root,
1160
+ "data-sample": "bash",
1161
+ "data-variant": "bash",
1162
+ "data-state": state,
1163
+ "data-expandable": expandable || void 0,
1164
+ role: expandable ? "button" : void 0,
1165
+ tabIndex: expandable ? 0 : void 0,
1166
+ "aria-expanded": expandable ? open : void 0,
1167
+ onClick: expandable ? toggleExpand : void 0,
1168
+ onKeyDown: expandable ? toggleFromKeyboard : void 0,
1169
+ children: [
1170
+ (0, react_jsx_runtime.jsx)("span", {
1171
+ className: bash_sample_module_css_default.leading,
1172
+ children: leading
1173
+ }),
1174
+ status !== null && (0, react_jsx_runtime.jsx)("span", {
1175
+ className: bash_sample_module_css_default.visuallyHidden,
1176
+ children: status
1177
+ }),
1178
+ (0, react_jsx_runtime.jsx)("span", {
1179
+ className: bash_sample_module_css_default.title,
1180
+ children: model.title
1181
+ }),
1182
+ (0, react_jsx_runtime.jsx)("span", {
1183
+ className: bash_sample_module_css_default.sep,
1184
+ "aria-hidden": true
1185
+ }),
1186
+ (0, react_jsx_runtime.jsx)("span", {
1187
+ className: clsx(bash_sample_module_css_default.summary, failureLine !== null && bash_sample_module_css_default.errorSummary),
1188
+ children: failureLine ?? terminal?.description ?? model.summary
1189
+ })
1190
+ ]
1191
+ }), open && (0, react_jsx_runtime.jsxs)("div", {
1192
+ className: bash_sample_module_css_default.bodyWrap,
1193
+ children: [terminal !== null ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TerminalBlock, {
1194
+ ...terminal.card,
1195
+ maxLines: Infinity,
1196
+ labels: terminalBlockLabels(t),
1197
+ className: bash_sample_module_css_default.terminal
1198
+ }) : (0, react_jsx_runtime.jsxs)("div", {
1199
+ className: bash_sample_module_css_default.ioCard,
1200
+ children: [
1201
+ model.body !== null && (0, react_jsx_runtime.jsxs)("div", {
1202
+ className: bash_sample_module_css_default.ioSection,
1203
+ children: [(0, react_jsx_runtime.jsx)("span", {
1204
+ className: bash_sample_module_css_default.ioLabel,
1205
+ children: "IN"
1206
+ }), (0, react_jsx_runtime.jsx)("span", {
1207
+ className: bash_sample_module_css_default.ioText,
1208
+ children: model.body
1209
+ })]
1210
+ }),
1211
+ model.body !== null && model.output !== null && (0, react_jsx_runtime.jsx)("span", {
1212
+ className: bash_sample_module_css_default.ioDivider,
1213
+ "aria-hidden": true
1214
+ }),
1215
+ model.output !== null && (0, react_jsx_runtime.jsxs)("div", {
1216
+ className: bash_sample_module_css_default.ioSection,
1217
+ children: [(0, react_jsx_runtime.jsx)("span", {
1218
+ className: bash_sample_module_css_default.ioLabel,
1219
+ children: "OUT"
1220
+ }), (0, react_jsx_runtime.jsx)("span", {
1221
+ className: bash_sample_module_css_default.ioText,
1222
+ "data-error": true,
1223
+ children: model.output
1224
+ })]
1225
+ })
1226
+ ]
1227
+ }), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
1228
+ type: "button",
1229
+ className: bash_sample_module_css_default.inspectButton,
1230
+ onClick: inspect,
1231
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconInspectOutline12, {}), "Inspect"]
1232
+ })]
1233
+ })]
1234
+ });
1235
+ }
1236
+ /**
1237
+ * The sample as a plain registrant plugin. Slot injection follows the chat
1238
+ * toolview declaration across independent activation and reload lifetimes.
1239
+ */
1240
+ const bashToolviewSample = {
1241
+ name: "bash-toolview-sample",
1242
+ inject: ["slots"],
1243
+ /**
1244
+ * Register the bash row into the Tool-owned keyed view slot.
1245
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1246
+ */
1247
+ apply(ctx) {
1248
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1249
+ name: "tool.call.toolview",
1250
+ key: "bash",
1251
+ locale: CONVERSATION_NS
1252
+ }, BashRow));
1253
+ }
1254
+ };
1255
+ //#endregion
1256
+ //#region lib/types/client/tool/toolviews/file-mutation-row.js
1257
+ /**
1258
+ * File-mutation row: icon + {Edit,Write} · {path} in the shared ToolRow chrome,
1259
+ * with the applied diff as the row's collapsed-by-default card body. The
1260
+ * summary is a path link (a file tool's interaction); the host's `openFile`
1261
+ * resolves it against the session cwd, so this passes the tool's own path
1262
+ * verbatim. An errored mutation has no diff card, so ToolRow surfaces the
1263
+ * model-facing error text through its Output section and its first line in the
1264
+ * collapsed summary instead.
1265
+ */
1266
+ function FileMutationRow({ toolName, block, cwd, openFile, inspect, t }) {
1267
+ const model = toolRowModel(toolName, block, cwd);
1268
+ const diff = diffCardModel(block);
1269
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1270
+ t,
1271
+ variant: model.variant,
1272
+ toolName,
1273
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
1274
+ title: model.title,
1275
+ summary: model.summary,
1276
+ body: null,
1277
+ output: model.output,
1278
+ errorSummary: model.errorSummary,
1279
+ diff,
1280
+ state: model.state,
1281
+ filePath: model.filePath,
1282
+ onOpenFile: openFile,
1283
+ inspect
1284
+ });
1285
+ }
1286
+ /**
1287
+ * The file-mutation rows as a plain registrant plugin following the chat
1288
+ * toolview declaration across independent activation and reload lifetimes.
1289
+ */
1290
+ const fileMutationToolview = {
1291
+ name: "file-mutation-toolview",
1292
+ inject: ["slots"],
1293
+ /**
1294
+ * Register the file-mutation row into the Tool-owned keyed view slot
1295
+ * under both mutation tool names.
1296
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1297
+ */
1298
+ apply(ctx) {
1299
+ ctx.slots.inject("tool.call.toolview", function* () {
1300
+ yield ctx.slots.register({
1301
+ name: "tool.call.toolview",
1302
+ key: "edit",
1303
+ locale: CONVERSATION_NS
1304
+ }, FileMutationRow);
1305
+ yield ctx.slots.register({
1306
+ name: "tool.call.toolview",
1307
+ key: "write",
1308
+ locale: CONVERSATION_NS
1309
+ }, FileMutationRow);
1310
+ });
1311
+ }
1312
+ };
1313
+ //#endregion
1314
+ //#region lib/types/client/tool/toolviews/read-row.js
1315
+ /**
1316
+ * Read row: icon + Read · {path} in the shared ToolRow chrome, with the file's
1317
+ * read card as the row's collapsed-by-default card body. The summary path is an
1318
+ * openable host link when the row names a single file.
1319
+ */
1320
+ function ReadRow({ toolName, block, cwd, openFile, inspect, t }) {
1321
+ const model = toolRowModel(toolName, block, cwd);
1322
+ const read = readCardModel(block, cwd);
1323
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1324
+ t,
1325
+ variant: model.variant,
1326
+ toolName,
1327
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
1328
+ title: model.title,
1329
+ summary: model.summary,
1330
+ body: null,
1331
+ output: model.output,
1332
+ errorSummary: model.errorSummary,
1333
+ read,
1334
+ state: model.state,
1335
+ filePath: model.filePath,
1336
+ onOpenFile: openFile,
1337
+ inspect
1338
+ });
1339
+ }
1340
+ /**
1341
+ * The read row as a plain registrant plugin following the atomic Tool-view
1342
+ * declaration across independent activation and reload lifetimes.
1343
+ */
1344
+ const readToolview = {
1345
+ name: "read-toolview",
1346
+ inject: ["slots"],
1347
+ /**
1348
+ * Register the read row into the Tool-owned keyed view slot.
1349
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1350
+ */
1351
+ apply(ctx) {
1352
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1353
+ name: "tool.call.toolview",
1354
+ key: "read",
1355
+ locale: CONVERSATION_NS
1356
+ }, ReadRow));
1357
+ }
1358
+ };
1359
+ //#endregion
1360
+ //#region lib/types/client/tool/toolviews/search-row.js
1361
+ /**
1362
+ * Search row: icon + Search · {summary} in the shared ToolRow chrome, with the
1363
+ * completed search's card as the row's collapsed-by-default card body (a capped
1364
+ * search's recovery footer rides below it, inside ToolRow). Registered under
1365
+ * both `grep` and `glob`; the derived model's `kind` decides the card shape. A
1366
+ * settled call with no search card surfaces its model-facing text through
1367
+ * ToolRow's Output section, since the keyed SearchRow owns this render slot.
1368
+ */
1369
+ function SearchRow({ toolName, block, inspect, t }) {
1370
+ const model = toolRowModel(toolName, block);
1371
+ const search = searchCardModel(block);
1372
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1373
+ t,
1374
+ variant: model.variant,
1375
+ toolName,
1376
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
1377
+ title: model.title,
1378
+ summary: search?.title ?? model.summary,
1379
+ body: null,
1380
+ output: model.output,
1381
+ errorSummary: model.errorSummary,
1382
+ search,
1383
+ state: model.state,
1384
+ inspect
1385
+ });
1386
+ }
1387
+ /**
1388
+ * The search view follows the atomic Tool-view declaration across activation
1389
+ * and reload. One component registers under both keys because `grep` and
1390
+ * `glob` are the same visual object discriminated by the result view's `kind`.
1391
+ */
1392
+ const searchToolview = {
1393
+ name: "search-toolview",
1394
+ inject: ["slots"],
1395
+ /**
1396
+ * Register the search row into the Tool-owned keyed view slot under both
1397
+ * the `grep` and `glob` tool names.
1398
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1399
+ */
1400
+ apply(ctx) {
1401
+ ctx.slots.inject("tool.call.toolview", function* () {
1402
+ yield ctx.slots.register({
1403
+ name: "tool.call.toolview",
1404
+ key: "grep",
1405
+ locale: CONVERSATION_NS
1406
+ }, SearchRow);
1407
+ yield ctx.slots.register({
1408
+ name: "tool.call.toolview",
1409
+ key: "glob",
1410
+ locale: CONVERSATION_NS
1411
+ }, SearchRow);
1412
+ });
1413
+ }
1414
+ };
1415
+ //#endregion
1416
+ //#region lib/types/client/tool/toolviews/plan-summary.js
1417
+ /**
1418
+ * Pure plan derivation for the todo_write row's one-line summary. Several items
1419
+ * may be `in_progress` at once — parallel work runs concurrent tasks, so a
1420
+ * summary built from one active item would silently drop the rest. The plan
1421
+ * strip header derives its own counts inline and shares nothing with this, so
1422
+ * this stays inside the toolviews domain rather than in `contract/` (the
1423
+ * inter-domain face).
1424
+ * @module
1425
+ */
1426
+ /**
1427
+ * Derive the counts and the active summary from a whole-list snapshot. It names
1428
+ * the first `in_progress` item and counts the remaining active ones, so a
1429
+ * parallel plan reports how many tasks are running rather than naming one and
1430
+ * hiding the others. `activeContent` is null when nothing is in progress, or
1431
+ * when the first active item's content is missing, mistyped, or blank once
1432
+ * trimmed — the tool's own rule for usable content, applied here because a
1433
+ * rejected call keeps its args verbatim. The row then renders the counts alone
1434
+ * rather than falling back to the generic tool summary: the counts are already
1435
+ * known to be good, and the active-item clause is the only part an unusable
1436
+ * name costs.
1437
+ * @param todos - the whole list, in model order.
1438
+ * @returns the done/total counts and the two summary halves.
1439
+ */
1440
+ function planSummary(todos) {
1441
+ const active = todos.filter((t) => t.status === "in_progress");
1442
+ const first = active[0]?.content;
1443
+ const named = typeof first === "string" && first.trim() !== "";
1444
+ return {
1445
+ done: todos.filter((t) => t.status === "completed").length,
1446
+ total: todos.length,
1447
+ activeContent: named ? first : null,
1448
+ activeExtra: named ? active.length - 1 : 0
1449
+ };
1450
+ }
1451
+ //#endregion
1452
+ //#region lib/types/client/tool/toolviews/todo-row.js
1453
+ function isItem(value) {
1454
+ return typeof value === "object" && value !== null;
1455
+ }
1456
+ function summarize(argsRaw, t) {
1457
+ let parsed;
1458
+ try {
1459
+ parsed = JSON.parse(argsRaw);
1460
+ } catch {
1461
+ return null;
1462
+ }
1463
+ if (typeof parsed !== "object" || parsed === null) return null;
1464
+ const todos = parsed.todos;
1465
+ if (!Array.isArray(todos) || !todos.every(isItem)) return null;
1466
+ const { done, total, activeContent, activeExtra } = planSummary(todos);
1467
+ const head = t("todo.completed", {
1468
+ done,
1469
+ total
1470
+ });
1471
+ return {
1472
+ text: activeContent === null ? head : `${head} · ${activeContent}`,
1473
+ extra: activeExtra
1474
+ };
1475
+ }
1476
+ /** One-line plan update row (the whole row toggles the call's Input/Output
1477
+ * sections, ToolRow's unified expand). Non-ok execution states keep the
1478
+ * shared row's dot semantics — a cancelled call wrote no todo/write, so it
1479
+ * must not read as a completed update. */
1480
+ function TodoRow({ toolName, block, inspect, t }) {
1481
+ const model = toolRowModel(toolName, block);
1482
+ const summary = summarize(("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "", t) ?? {
1483
+ text: model.summary,
1484
+ extra: 0
1485
+ };
1486
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1487
+ t,
1488
+ variant: model.variant,
1489
+ toolName,
1490
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChecklistOutline14, {}),
1491
+ title: t("todo.rowTitle"),
1492
+ summary: summary.text,
1493
+ summarySuffix: summary.extra > 0 ? `+${summary.extra}` : null,
1494
+ body: model.body,
1495
+ output: model.output,
1496
+ errorSummary: model.errorSummary,
1497
+ state: model.state,
1498
+ inspect
1499
+ });
1500
+ }
1501
+ /**
1502
+ * The todo row as a plain registrant plugin following the atomic Tool-view
1503
+ * declaration across independent activation and reload lifetimes.
1504
+ */
1505
+ const todoToolview = {
1506
+ name: "todo-toolview",
1507
+ inject: ["slots"],
1508
+ /**
1509
+ * Register the todo row into the Tool-owned keyed view slot.
1510
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1511
+ */
1512
+ apply(ctx) {
1513
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1514
+ name: "tool.call.toolview",
1515
+ key: "todo_write",
1516
+ locale: CONVERSATION_NS
1517
+ }, TodoRow));
1518
+ }
1519
+ };
1520
+ //#endregion
1521
+ //#region lib/types/client/tool/toolviews/web-row.js
1522
+ /** web_fetch reads one URL; web_search queries. Titles are figma literals. */
1523
+ const WEB_TITLES = {
1524
+ web_search: "Search",
1525
+ web_fetch: "Fetch"
1526
+ };
1527
+ /**
1528
+ * Web row: icon + Search/Fetch · {summary} in the shared ToolRow chrome, with
1529
+ * the completed retrieval's web card as the row's collapsed-by-default card
1530
+ * body. The row discriminates on `toolName` only to pick its icon and title.
1531
+ */
1532
+ function WebRow({ toolName, block, inspect, t }) {
1533
+ const model = toolRowModel(toolName, block);
1534
+ const web = webCardModel(block);
1535
+ const icon = toolName === "web_fetch" ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 });
1536
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1537
+ t,
1538
+ variant: model.variant,
1539
+ toolName,
1540
+ icon,
1541
+ title: WEB_TITLES[toolName] ?? model.title,
1542
+ summary: model.summary,
1543
+ body: null,
1544
+ output: model.output,
1545
+ errorSummary: model.errorSummary,
1546
+ web,
1547
+ state: model.state,
1548
+ inspect
1549
+ });
1550
+ }
1551
+ /**
1552
+ * The web rows follow the atomic Tool-view declaration across activation and
1553
+ * reload. One WebRow component registers under both web tool names.
1554
+ */
1555
+ const webToolview = {
1556
+ name: "web-toolview",
1557
+ inject: ["slots"],
1558
+ /**
1559
+ * Register the web row under both web tool names' keyed toolview holes.
1560
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1561
+ */
1562
+ apply(ctx) {
1563
+ ctx.slots.inject("tool.call.toolview", function* () {
1564
+ yield ctx.slots.register({
1565
+ name: "tool.call.toolview",
1566
+ key: "web_search",
1567
+ locale: CONVERSATION_NS
1568
+ }, WebRow);
1569
+ yield ctx.slots.register({
1570
+ name: "tool.call.toolview",
1571
+ key: "web_fetch",
1572
+ locale: CONVERSATION_NS
1573
+ }, WebRow);
1574
+ });
1575
+ }
1576
+ };
1577
+ //#endregion
1578
+ //#region lib/types/client/apply.js
1579
+ /** Required service: the slot registry that owns both Tool render seats. */
1580
+ const inject = ["slots"];
1581
+ /**
1582
+ * Mount the whole-Tool renderers and built-in atomic Tool registrations.
1583
+ * @param ctx - Client root context.
1584
+ */
1585
+ function apply(ctx) {
1586
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1587
+ name: "conversation.chat.node",
1588
+ key: "tool-call",
1589
+ locale: CONVERSATION_NS,
1590
+ children: { "tool.call.toolview": {
1591
+ kind: "keyed",
1592
+ scope: "session"
1593
+ } }
1594
+ }, ToolCallTree));
1595
+ ctx.slots.inject("conversation.details.tool", () => ctx.slots.register({
1596
+ name: "conversation.details.tool",
1597
+ locale: CONVERSATION_NS
1598
+ }, ToolDetails));
1599
+ ctx.plugin(bashToolviewSample);
1600
+ ctx.plugin(readToolview);
1601
+ ctx.plugin(fileMutationToolview);
1602
+ ctx.plugin(searchToolview);
1603
+ ctx.plugin(webToolview);
1604
+ ctx.plugin(todoToolview);
1605
+ ctx.plugin(askQuestionToolview);
1606
+ }
1607
+ //#endregion
1608
+ exports.apply = apply;
1609
+ exports.inject = inject;
1610
+ return module.exports;
1611
+ }
1612
+ });
1613
+
1614
+ //# sourceMappingURL=client.js.map