@hydraharness/harness-client-ui-tool 0.1.1-rc.6

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 +21 -0
  2. package/README.md +56 -0
  3. package/lib/client.js +1999 -0
  4. package/lib/index.js +6 -0
  5. package/lib/invariant.js +23 -0
  6. package/lib/types/client/apply.d.ts +9 -0
  7. package/lib/types/client/contract/slots.d.ts +71 -0
  8. package/lib/types/client/index.d.ts +4 -0
  9. package/lib/types/client/locale.d.ts +3 -0
  10. package/lib/types/client/tool/MediaGeneration.d.ts +8 -0
  11. package/lib/types/client/tool/ToolCallTree.d.ts +9 -0
  12. package/lib/types/client/tool/ToolDetails.d.ts +9 -0
  13. package/lib/types/client/tool/components/ToolRow.d.ts +79 -0
  14. package/lib/types/client/tool/models/diff-card-model.d.ts +58 -0
  15. package/lib/types/client/tool/models/read-card-model.d.ts +46 -0
  16. package/lib/types/client/tool/models/search-card-model.d.ts +89 -0
  17. package/lib/types/client/tool/models/terminal-card-model.d.ts +71 -0
  18. package/lib/types/client/tool/models/tool-call-model.d.ts +58 -0
  19. package/lib/types/client/tool/models/web-card-model.d.ts +39 -0
  20. package/lib/types/client/tool/toolviews/GenericToolCard.d.ts +7 -0
  21. package/lib/types/client/tool/toolviews/ask-question-row.d.ts +23 -0
  22. package/lib/types/client/tool/toolviews/bash-sample.d.ts +26 -0
  23. package/lib/types/client/tool/toolviews/file-mutation-row.d.ts +31 -0
  24. package/lib/types/client/tool/toolviews/plan-summary.d.ts +48 -0
  25. package/lib/types/client/tool/toolviews/read-row.d.ts +26 -0
  26. package/lib/types/client/tool/toolviews/search-row.d.ts +31 -0
  27. package/lib/types/client/tool/toolviews/subagent-row.d.ts +26 -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 +87 -0
package/lib/client.js ADDED
@@ -0,0 +1,1999 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@hydraharness/harness-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 _hydraharness_harness_client_ui_primitives = require("@hydraharness/harness-client-ui-primitives");
10
+ let _hydraharness_harness_client_runtime_client = require("@hydraharness/harness-client-runtime/client");
11
+ //#region lib/types/client/tool/models/tool-call-model.js
12
+ /**
13
+ * Pure row-model derivation for tool summary rows: variant classification,
14
+ * one-line summary, expanded-body text, and flattened result output from the
15
+ * frozen call slice. Input material comes from the call ARGUMENTS; output and
16
+ * error material from the settled result node. A call whose render intent is
17
+ * a terminal card gets its expanded body from the views instead, through
18
+ * `terminalCardModel` in terminal-card-model.ts.
19
+ */
20
+ /** Figma row titles per variant (design literals, not translatable copy). */
21
+ const VARIANT_TITLES = {
22
+ search: "Search",
23
+ read: "Read",
24
+ bash: "Bash",
25
+ write: "Write",
26
+ edit: "Edit",
27
+ code: "Code",
28
+ others: "Tool call"
29
+ };
30
+ /**
31
+ * Known tool name -> variant.
32
+ *
33
+ * `cordis_define` is deliberately absent: ui-cordis registers a keyed
34
+ * `tool.call.toolview` entry for it, and a keyed hit REPLACES the generic row
35
+ * (this table is only reached through GenericToolCard, the dispatch fallback in
36
+ * ToolCallTree). An entry here would be unreachable, and a second title for the
37
+ * same call would be a second answer to a question the card already owns.
38
+ */
39
+ const TOOL_VARIANTS = {
40
+ bash: "bash",
41
+ pwsh: "bash",
42
+ read: "read",
43
+ web_fetch: "read",
44
+ web_search: "search",
45
+ grep: "search",
46
+ glob: "search",
47
+ write: "write",
48
+ edit: "edit",
49
+ run_code: "code",
50
+ subagent: "code",
51
+ cordis_package_inspect: "read",
52
+ cordis_runtime_inspect: "read",
53
+ cordis_run: "others",
54
+ cordis_stop: "others",
55
+ cordis_undefine: "others"
56
+ };
57
+ /** Tool-owned titles that refine a generic row variant without replacing it. */
58
+ const TOOL_TITLES = {
59
+ cordis_package_inspect: "Inspect",
60
+ cordis_runtime_inspect: "Inspect",
61
+ cordis_run: "Run Cordis Plugin",
62
+ cordis_stop: "Stop Cordis Plugin",
63
+ cordis_undefine: "Remove Cordis Plugin",
64
+ pwsh: "Pwsh",
65
+ subagent: "Subagent"
66
+ };
67
+ /**
68
+ * Classify a tool name into its row variant.
69
+ * @param toolName - wire tool name.
70
+ * @returns matching variant, others when unknown.
71
+ */
72
+ function classifyTool(toolName) {
73
+ return TOOL_VARIANTS[toolName] ?? "others";
74
+ }
75
+ /**
76
+ * Flatten a settled result's content blocks to display text: text blocks
77
+ * verbatim, other block shapes as pretty JSON. Empty content on a failed call
78
+ * falls back to the structured error's `name: code` line.
79
+ * @param node - the settled result node.
80
+ * @returns the flattened result text (may be empty).
81
+ */
82
+ function resultText(node) {
83
+ const parts = [];
84
+ for (const block of node.content) if (block.type === "text") parts.push(block.text);
85
+ else parts.push(JSON.stringify(block, null, 2));
86
+ if (parts.length === 0 && node.error !== void 0) parts.push(`${node.error.name}: ${node.error.code}`);
87
+ return parts.join("\n");
88
+ }
89
+ function parseArgs(argsRaw) {
90
+ try {
91
+ return JSON.parse(argsRaw);
92
+ } catch {
93
+ return;
94
+ }
95
+ }
96
+ function firstLine(text) {
97
+ const nl = text.indexOf("\n");
98
+ return nl === -1 ? text : text.slice(0, nl);
99
+ }
100
+ function pickString(args, keys) {
101
+ for (const key of keys) {
102
+ const v = args[key];
103
+ if (typeof v === "string" && v !== "") return v;
104
+ }
105
+ }
106
+ /** Summary key preference per variant (args-derived; result-derived summaries are a ledger item). */
107
+ const SUMMARY_KEYS = {
108
+ bash: ["description", "command"],
109
+ read: [
110
+ "path",
111
+ "file_path",
112
+ "url"
113
+ ],
114
+ search: [
115
+ "query",
116
+ "pattern",
117
+ "url"
118
+ ],
119
+ write: ["path", "file_path"],
120
+ edit: ["path", "file_path"],
121
+ code: [
122
+ "description",
123
+ "task",
124
+ "prompt"
125
+ ],
126
+ others: [
127
+ "description",
128
+ "task",
129
+ "prompt",
130
+ "message",
131
+ "query",
132
+ "command",
133
+ "title",
134
+ "name",
135
+ "action"
136
+ ]
137
+ };
138
+ /**
139
+ * Strip the workspace root from a workspace-rooted absolute path (display only).
140
+ * @param text - the path to shorten.
141
+ * @param cwd - session workspace root; absent or empty leaves the path unchanged.
142
+ * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
143
+ */
144
+ function relativizeToCwd(text, cwd) {
145
+ if (cwd === void 0 || cwd === "") return text;
146
+ const root = cwd.replace(/[/\\]+$/, "");
147
+ if (text.startsWith(`${root}/`) || text.startsWith(`${root}\\`)) return text.slice(root.length + 1);
148
+ return text;
149
+ }
150
+ function deriveSummary(variant, argsRaw) {
151
+ const parsed = parseArgs(argsRaw);
152
+ if (typeof parsed !== "object" || parsed === null) return firstLine(argsRaw);
153
+ const args = parsed;
154
+ if (variant === "search" && Array.isArray(args.queries)) {
155
+ const queries = args.queries.filter((query) => typeof query === "string" && query !== "");
156
+ if (queries.length > 0) return queries.map(firstLine).join(", ");
157
+ }
158
+ const picked = pickString(args, SUMMARY_KEYS[variant]);
159
+ if (picked !== void 0) return firstLine(picked);
160
+ for (const v of Object.values(args)) if (typeof v === "string" && v !== "") return firstLine(v);
161
+ return firstLine(argsRaw);
162
+ }
163
+ /** Path keys only — never `url` (web_fetch lands on the read variant). */
164
+ const FILE_PATH_KEYS = ["path", "file_path"];
165
+ /** File-tool variants whose summary may be an openable workspace path. */
166
+ const FILE_PATH_VARIANTS = new Set([
167
+ "read",
168
+ "write",
169
+ "edit"
170
+ ]);
171
+ function deriveFilePath(variant, argsRaw) {
172
+ if (!FILE_PATH_VARIANTS.has(variant)) return void 0;
173
+ const parsed = parseArgs(argsRaw);
174
+ if (typeof parsed !== "object" || parsed === null) return void 0;
175
+ const picked = pickString(parsed, FILE_PATH_KEYS);
176
+ return picked === void 0 ? void 0 : firstLine(picked);
177
+ }
178
+ function deriveBody(variant, argsRaw) {
179
+ if (argsRaw === "") return null;
180
+ const parsed = parseArgs(argsRaw);
181
+ if (parsed === void 0) return argsRaw;
182
+ if (variant === "code" && typeof parsed === "object" && parsed !== null) {
183
+ const code = parsed.code;
184
+ if (typeof code === "string" && code !== "") return code;
185
+ }
186
+ return JSON.stringify(parsed, null, 2);
187
+ }
188
+ /**
189
+ * Derive the full row model from a frozen call slice.
190
+ * @param toolName - wire tool name (dispatch-supplied; survives windowless results).
191
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
192
+ * @param cwd - session workspace root; workspace-rooted path summaries display relative to it.
193
+ * @param home - host account home; a leftover POSIX home path displays as `~`.
194
+ * @returns the row model.
195
+ */
196
+ function toolRowModel(toolName, block, cwd, home) {
197
+ const variant = classifyTool(toolName);
198
+ const done = "kind" in block;
199
+ const argsRaw = (done ? block.call?.argsRaw : block.argsRaw) ?? "";
200
+ const state = !done ? "running" : block.error?.code === "interrupted" ? "stopped" : block.isError ? "error" : "ok";
201
+ const base = argsRaw === "" ? block.callId : (0, _hydraharness_harness_client_runtime_client.abbreviateHomePath)(relativizeToCwd(deriveSummary(variant, argsRaw), cwd), home);
202
+ const toolTitle = TOOL_TITLES[toolName];
203
+ const summary = variant === "others" && toolName !== "" && toolTitle === void 0 ? `${toolName} · ${base}` : base;
204
+ const output = done ? resultText(block) || null : null;
205
+ const errorSummary = state === "error" && output !== null ? firstLine(output) : null;
206
+ return {
207
+ variant,
208
+ title: toolTitle ?? VARIANT_TITLES[variant],
209
+ summary,
210
+ filePath: deriveFilePath(variant, argsRaw),
211
+ body: deriveBody(variant, argsRaw),
212
+ output,
213
+ errorSummary,
214
+ state
215
+ };
216
+ }
217
+ //#endregion
218
+ //#region lib/types/client/tool/models/read-card-model.js
219
+ /**
220
+ * Pure derivation of the read-card props from a frozen call slice: the
221
+ * `card:'read'` render intent the read tool declares arrives on the snapshot as
222
+ * the settled result node's `resultView`, and this is the one place that turns
223
+ * it into what {@link ReadBlock} draws. Both conversation render sites (the chat
224
+ * tool row's resident body and the details panel's Output section) call this, so
225
+ * the path, lines, total, and language they show are derived once.
226
+ *
227
+ * The read card is result-side only ([read card note](../../../../../../.agents/notes/implemented/feature/2026-07-30-web-read-card.md)):
228
+ * a call carries no file content until `execute` returns, so the pending call
229
+ * stays a generic card (`kind: 'read'`). A running read therefore has no read
230
+ * card, and this returns null for it — the row keeps its args-derived summary
231
+ * until the result arrives.
232
+ * @module
233
+ */
234
+ /**
235
+ * Derive the read-card props for a tool call, or null when this call is not a
236
+ * read card and belongs on the generic path.
237
+ *
238
+ * The read card is result-side only, so only a settled call whose result view
239
+ * declares `card:'read'` produces one. Every other case is null — the
240
+ * documented generic-card default:
241
+ *
242
+ * - A running call: it has no result view yet, and a read carries no content at
243
+ * call time.
244
+ * - A settled call whose result view is not a read card — including a `card`
245
+ * value this UI version does not know, which arrives over the wire and cannot
246
+ * be trusted to be one of the compiled variants, and the read tool's own
247
+ * generic fallback for an error result or a non-envelope body.
248
+ *
249
+ * The label is the read view's `title` when the tool supplied one (the
250
+ * presentation contract's replacement-title rule), otherwise the file path
251
+ * shortened the same way the row summary is: workspace-relative first, then
252
+ * POSIX `~` for a leftover host-home path.
253
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
254
+ * @param sessionCwd - the session workspace root; a workspace-rooted absolute
255
+ * path label displays relative to it. Absent leaves the path as authored.
256
+ * @param home - host account home; a leftover POSIX home path displays as `~`.
257
+ * @returns the read-card props, or null for the generic path.
258
+ */
259
+ function readCardModel(block, sessionCwd, home) {
260
+ if (!("kind" in block)) return null;
261
+ const result = block.resultView?.card === "read" ? block.resultView : null;
262
+ if (result === null) return null;
263
+ const lines = result.lines.map((line) => ({
264
+ number: line.number,
265
+ text: line.text
266
+ }));
267
+ return {
268
+ label: result.title ?? (0, _hydraharness_harness_client_runtime_client.abbreviateHomePath)(relativizeToCwd(result.path, sessionCwd), home),
269
+ lines,
270
+ totalLines: result.totalLines,
271
+ lang: result.lang
272
+ };
273
+ }
274
+ //#endregion
275
+ //#region lib/types/client/tool/models/diff-card-model.js
276
+ /**
277
+ * Narrow a wire `card:'diff'` view's `diffs` to well-formed hunks. The event
278
+ * view crosses the wire and `toolEventViewSchema` validates only the `card`
279
+ * string, so a version mismatch or an anomalous plugin can deliver a `diff` card
280
+ * whose `diffs` is absent, not an array, or carries malformed hunks. Returning
281
+ * null for any of those routes the block to the generic path instead of letting
282
+ * DiffBlock's `for...of`/`split` throw and crash the row or the details panel.
283
+ * @param diffs - the view's `diffs` field, unverified.
284
+ * @returns the validated hunks, or null when the payload is not usable.
285
+ */
286
+ function narrowDiffs(diffs) {
287
+ if (!Array.isArray(diffs) || diffs.length === 0) return null;
288
+ const out = [];
289
+ for (const hunk of diffs) {
290
+ if (typeof hunk !== "object" || hunk === null) return null;
291
+ const { path, oldText, newText } = hunk;
292
+ if (typeof path !== "string") return null;
293
+ if (oldText !== null && typeof oldText !== "string") return null;
294
+ if (typeof newText !== "string") return null;
295
+ out.push({
296
+ path,
297
+ oldText,
298
+ newText
299
+ });
300
+ }
301
+ return out;
302
+ }
303
+ /**
304
+ * Derive the diff-card props for a tool call, or null when this call is not a
305
+ * diff card and belongs on the generic path.
306
+ *
307
+ * The result side is authoritative once the call settles: the write/edit tools
308
+ * return the applied contextual hunks there (an edit's real before/after, a
309
+ * create's whole-file diff), which replace the call-time diff derived from the
310
+ * arguments alone. While the call is still running only the call side exists,
311
+ * so a running write/edit shows its intended change. Null is the documented
312
+ * generic-card default and covers every non-diff card — including a `card`
313
+ * value this UI version does not know, which arrives over the wire and cannot
314
+ * be trusted to be one of the compiled variants — and a settled call whose
315
+ * result view is generic (how write/edit keep their execution errors on the
316
+ * generic path).
317
+ *
318
+ * This derivation consumes only `diffs`; the render intent's `title` field is
319
+ * deliberately dropped. The row supplies its own title (`Edit`/`Write · path`
320
+ * from the args), which outranks the view's `title`. A tool that names its own
321
+ * diff header therefore does not surface that text on the Web row.
322
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
323
+ * @returns the diff-card props, or null for the generic path.
324
+ */
325
+ function diffCardModel(block) {
326
+ if (!("kind" in block)) {
327
+ const call = block.callView?.card === "diff" ? block.callView : null;
328
+ const diffs = call === null ? null : narrowDiffs(call.diffs);
329
+ return diffs === null ? null : { card: { diffs } };
330
+ }
331
+ const result = block.resultView?.card === "diff" ? block.resultView : null;
332
+ const diffs = result === null ? null : narrowDiffs(result.diffs);
333
+ return diffs === null ? null : { card: { diffs } };
334
+ }
335
+ //#endregion
336
+ //#region lib/types/client/tool/models/search-card-model.js
337
+ /**
338
+ * Whether every file group in a matches view is structurally valid: the wire
339
+ * frame carries `shape` and `card` as strings the host schema checks, but not the
340
+ * grouped `files` fields, so a version mismatch or loose producer could deliver
341
+ * `shape: 'matches'` with a missing or malformed `files`. Rendering that would
342
+ * crash {@link SearchBlock} at `.reduce`/`.map`; invalid fields select the
343
+ * generic path instead.
344
+ * @param files - the candidate `files` field off the untrusted result view.
345
+ * @returns whether `files` is a valid {@link SearchFileGroup} array.
346
+ */
347
+ function isValidFiles(files) {
348
+ 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"));
349
+ }
350
+ /**
351
+ * Flatten a settled tool result's content blocks to their text, joined by
352
+ * newlines. The search view carries no result text — a UI without a card falls
353
+ * back to the raw `tool/result` content — so the truncation recovery footer is
354
+ * read from the block's own content here. Non-text blocks (a search result
355
+ * carries none) are skipped.
356
+ * @param content - the result node's content blocks.
357
+ * @returns the joined text, or undefined when empty.
358
+ */
359
+ function flattenContent(content) {
360
+ const text = content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
361
+ return text === "" ? void 0 : text;
362
+ }
363
+ /**
364
+ * Derive the search-card props for a tool call, or null when this call is not a
365
+ * search card and belongs on the generic path.
366
+ *
367
+ * Only the result side matters: the search card carries no call-time state, so
368
+ * a still-running call (no result view) is null, as is a settled call whose
369
+ * result view is not a search card — including a `card` value this UI version
370
+ * does not know, which arrives over the wire and cannot be trusted to be one of
371
+ * the compiled variants, a `card: 'search'` view whose `shape` is neither
372
+ * `matches` nor `paths` (equally untrusted wire data), and a generic result a
373
+ * `grep`/`glob` failure or nested `run_code` dispatch produces (its text keeps
374
+ * the generic path).
375
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
376
+ * @returns the search-card props, or null for the generic path.
377
+ */
378
+ function searchCardModel(block) {
379
+ if (!("kind" in block)) return null;
380
+ const result = block.resultView?.card === "search" ? block.resultView : null;
381
+ if (result === null) return null;
382
+ const common = {
383
+ truncated: result.truncated,
384
+ total: result.total
385
+ };
386
+ const recovery = result.truncated ? flattenContent(block.content) : void 0;
387
+ if (result.shape === "matches") {
388
+ if (!isValidFiles(result.files)) return null;
389
+ return {
390
+ title: result.title,
391
+ recovery,
392
+ card: {
393
+ kind: "matches",
394
+ files: result.files,
395
+ ...common
396
+ }
397
+ };
398
+ }
399
+ if (result.shape !== "paths") return null;
400
+ if (!Array.isArray(result.paths) || !result.paths.every((path) => typeof path === "string")) return null;
401
+ return {
402
+ title: result.title,
403
+ recovery,
404
+ card: {
405
+ kind: "paths",
406
+ paths: result.paths,
407
+ ...common
408
+ }
409
+ };
410
+ }
411
+ //#endregion
412
+ //#region lib/types/client/tool/models/terminal-card-model.js
413
+ /**
414
+ * Pure derivation of the terminal-card props from a frozen call slice: the
415
+ * `card:'terminal'` render intent the shell tools declare arrives on the
416
+ * snapshot as `callView`/`resultView`, and this is the one place that turns
417
+ * that pair into what {@link TerminalBlock} draws. Both conversation render
418
+ * sites (the chat tool row's expanded body and the details panel's Output
419
+ * section) call this, so the command, cwd, output and exit status they show
420
+ * are derived once.
421
+ * @module
422
+ */
423
+ /**
424
+ * Build the TerminalBlock display copy from the conversation locale seat —
425
+ * the one place the primitive's label surface pairs with this package's
426
+ * dictionary, shared by every terminal render site (chat row, bash row,
427
+ * details panel).
428
+ * @param t - the render site's conversation locale seat.
429
+ * @returns the full label set for {@link TerminalBlockProps}'s `labels`.
430
+ */
431
+ function terminalBlockLabels(t) {
432
+ return {
433
+ signal: (signal) => t("terminal.signal", { signal }),
434
+ exitCode: (code) => t("terminal.exitCode", { code }),
435
+ running: t("terminal.running"),
436
+ failed: t("terminal.failed"),
437
+ done: t("terminal.done"),
438
+ copy: t("copy"),
439
+ copied: t("copied"),
440
+ noOutput: t("terminal.noOutput"),
441
+ collapseAria: t("terminal.collapseAria"),
442
+ collapse: t("collapse"),
443
+ expandAria: (hidden) => t("terminal.expandAria", { n: hidden }),
444
+ expand: (hidden) => t("terminal.expandRest", { n: hidden })
445
+ };
446
+ }
447
+ /**
448
+ * True when a settled terminal card reports a failing exit — a non-zero code
449
+ * or a terminating signal. The bash tool settles a failing command as a
450
+ * completed call (`isError` stays false: the exit status is result data), so
451
+ * this is the collapsed row's only failure signal; without it the red exit
452
+ * pill would be visible only after expanding the card.
453
+ * @param model - a derived terminal card.
454
+ * @returns whether the card's exit status is a failure.
455
+ */
456
+ function terminalFailed(model) {
457
+ const { exitCode, signal, running } = model.card;
458
+ return running !== true && (exitCode !== void 0 && exitCode !== 0 || signal !== void 0);
459
+ }
460
+ /**
461
+ * Resolve a terminal view's working directory the way the render-intent
462
+ * contract assigns to the UI bridge: an absolute path is used as-is, a relative
463
+ * one joins under the session workspace, and an omitted one IS the session
464
+ * workspace. A pure presenter cannot see the session cwd, which is why this
465
+ * resolution belongs here rather than in the tool. Without a session cwd there
466
+ * is nothing to resolve against, so a relative path stays as authored and an
467
+ * omitted one stays absent (the prompt row then draws a bare `$`).
468
+ * @param viewCwd - the cwd the terminal call view carries, if any.
469
+ * @param sessionCwd - the session workspace root, if the caller knows it.
470
+ * @returns the working directory for the prompt label, or undefined.
471
+ */
472
+ function resolveTerminalCwd(viewCwd, sessionCwd) {
473
+ if (viewCwd === void 0 || viewCwd === "") return sessionCwd;
474
+ if (sessionCwd === void 0 || sessionCwd === "") return normalizeSegments(viewCwd);
475
+ return normalizeSegments((0, _hydraharness_harness_client_runtime_client.resolveWorkspacePath)(sessionCwd, viewCwd));
476
+ }
477
+ /**
478
+ * Collapse `.` and `..` segments so the prompt label names the directory the
479
+ * command actually ran in. The bash executor resolves the workdir before
480
+ * running, so a joined `/w/app/..` must display as `w`, not as `..`. Separators
481
+ * are preserved as authored (a Windows path keeps its backslashes) because this
482
+ * value is only ever displayed; a `..` that would climb past the root is
483
+ * dropped, which is what a filesystem does with it. A UNC path's `server` and
484
+ * `share` are part of its root, not poppable segments: Windows cannot climb
485
+ * above a share, so `\\\\server\\share` with a `..` stays there.
486
+ * @param path - a joined or absolute path, possibly carrying `.`/`..` segments.
487
+ * @returns the same path with those segments resolved.
488
+ */
489
+ function normalizeSegments(path) {
490
+ if (!/(?:^|[/\\])\.\.?(?:[/\\]|$)/.test(path)) return path;
491
+ const unc = /^[/\\]{2}([^/\\]+)[/\\]+([^/\\]+)/.exec(path);
492
+ if (unc !== null) {
493
+ const [matched, server, share] = unc;
494
+ const root = `\\\\${String(server)}\\${String(share)}`;
495
+ const rest = collapse(path.slice(matched.length), true);
496
+ return rest === "" ? root : `${root}\\${rest}`;
497
+ }
498
+ const separator = path.includes("\\") && !path.includes("/") ? "\\" : "/";
499
+ const rooted = /^[/\\]/.test(path);
500
+ const drive = /^[A-Za-z]:/.exec(path)?.[0] ?? "";
501
+ const body = collapse(path.slice(drive.length), rooted || drive !== "", separator);
502
+ const leading = rooted ? separator : "";
503
+ return drive === "" ? `${leading}${body}` : `${drive}${rooted ? leading : separator}${body}`;
504
+ }
505
+ /**
506
+ * Collapse the `.`/`..` segments of a path body against a known root state.
507
+ * @param body - the path after any drive letter or UNC root.
508
+ * @param rooted - the body hangs off a root, so a `..` at its top is dropped
509
+ * the way a filesystem drops one; without a root the `..` is kept, since it
510
+ * stays meaningful against a cwd this function cannot see.
511
+ * @param separator - separator to rejoin with (default `/`).
512
+ * @returns the collapsed body, without leading or trailing separators.
513
+ */
514
+ function collapse(body, rooted, separator = "/") {
515
+ const kept = [];
516
+ for (const segment of body.split(/[/\\]/)) {
517
+ if (segment === "" || segment === ".") continue;
518
+ if (segment === "..") {
519
+ if (kept.length > 0 && kept[kept.length - 1] !== "..") kept.pop();
520
+ else if (!rooted) kept.push(segment);
521
+ continue;
522
+ }
523
+ kept.push(segment);
524
+ }
525
+ return kept.join(separator);
526
+ }
527
+ /**
528
+ * Derive the terminal-card props for a tool call, or null when this call is
529
+ * not a terminal card and belongs on the generic path.
530
+ *
531
+ * The call side supplies the command and its working directory; the result
532
+ * side supplies the captured output and exit status. Three cases produce
533
+ * null, all of them the documented generic-card default:
534
+ *
535
+ * - Neither side declares `card:'terminal'` — including a `card` value this
536
+ * UI version does not know, which arrives over the wire and therefore
537
+ * cannot be trusted to be one of the compiled variants.
538
+ * - A settled call whose result view is not a terminal card: the result
539
+ * presentation decides how the settled call renders, and the bash tool
540
+ * returns a generic fenced card for an execution error or a background
541
+ * start, whose text and error styling the generic path preserves.
542
+ *
543
+ * Window truncation can drop the call head from a settled result (see
544
+ * `ToolResultNode.call`/`callView` in @hydraharness/harness-client-runtime), leaving a terminal
545
+ * result with no call side. That still renders: the command falls back to the
546
+ * result view's replacement title, then to an empty command (the prompt line
547
+ * draws bare), and the prompt shows no cwd.
548
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
549
+ * @param sessionCwd - the session workspace root, which resolves an omitted or
550
+ * relative view cwd (see {@link resolveTerminalCwd}); absent leaves both unresolved.
551
+ * @returns the terminal-card props, or null for the generic path.
552
+ */
553
+ function terminalCardModel(block, sessionCwd) {
554
+ const call = block.callView?.card === "terminal" ? block.callView : null;
555
+ if (!("kind" in block)) return call === null ? null : {
556
+ description: call.description,
557
+ card: {
558
+ command: call.title,
559
+ cwd: resolveTerminalCwd(call.cwd, sessionCwd),
560
+ output: void 0,
561
+ exitCode: void 0,
562
+ signal: void 0,
563
+ running: true
564
+ }
565
+ };
566
+ const result = block.resultView?.card === "terminal" ? block.resultView : null;
567
+ if (result === null) return null;
568
+ return {
569
+ description: call?.description,
570
+ card: {
571
+ command: result.title ?? call?.title ?? "",
572
+ cwd: call === null ? void 0 : resolveTerminalCwd(call.cwd, sessionCwd),
573
+ output: result.output,
574
+ exitCode: result.exitCode,
575
+ signal: result.signal,
576
+ running: false
577
+ }
578
+ };
579
+ }
580
+ //#endregion
581
+ //#region lib/types/client/tool/models/web-card-model.js
582
+ /**
583
+ * Derive the web-card props for a tool call, or null when this call is not a
584
+ * web card and belongs on the generic path.
585
+ *
586
+ * The result side supplies the whole card: the sources and answer for a
587
+ * `search`, the URL and status for a `fetch`. Cases producing null, all of
588
+ * them the documented generic-card default:
589
+ *
590
+ * - A running call (no `resultView` yet): the web tools keep a generic pending
591
+ * card, so nothing web-shaped exists until the call settles.
592
+ * - A settled call whose result view is not a web card — including a `card`
593
+ * value this UI version does not know, which arrives over the wire and so
594
+ * cannot be trusted to be one of the compiled variants, and a generic result
595
+ * view (a web tool's error path returns the generic card, whose text the
596
+ * generic path preserves).
597
+ * - A web card whose `kind` this UI version does not know (a newer host's
598
+ * value): the wire cannot be trusted to be `search` or `fetch`, so it takes
599
+ * the generic path rather than rendering as a malformed fetch.
600
+ * @param block - RunningToolCall or ToolResultNode off the snapshot caches.
601
+ * @returns the web-card props, or null for the generic path.
602
+ */
603
+ function webCardModel(block) {
604
+ if (!("kind" in block)) return null;
605
+ const result = block.resultView;
606
+ if (result?.card !== "web") return null;
607
+ if (result.kind === "search") return {
608
+ kind: "search",
609
+ answer: result.answer,
610
+ sources: result.sources.map((source) => ({
611
+ url: source.url,
612
+ title: source.title,
613
+ snippet: source.snippet,
614
+ publishedAt: source.publishedAt
615
+ })),
616
+ truncated: result.truncated
617
+ };
618
+ if (result.kind === "fetch") return {
619
+ kind: "fetch",
620
+ url: result.url,
621
+ statusCode: result.statusCode,
622
+ truncated: result.truncated
623
+ };
624
+ return null;
625
+ }
626
+ //#endregion
627
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
628
+ function r(e) {
629
+ var t, f, n = "";
630
+ if ("string" == typeof e || "number" == typeof e) n += e;
631
+ else if ("object" == typeof e) if (Array.isArray(e)) {
632
+ var o = e.length;
633
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
634
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
635
+ return n;
636
+ }
637
+ function clsx() {
638
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
639
+ return n;
640
+ }
641
+ //#endregion
642
+ //#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css.mjs
643
+ const css$4 = ".cHi0Pa_root{border-radius:var(--dsw-radius-panel);transition:background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);flex-direction:column;display:flex}.cHi0Pa_row{box-sizing:border-box;border-radius:var(--dsw-radius-control);transition:background-color var(--ds-transition-duration-fast) var(--ds-ease-in-out);padding:0 4px;position:relative;overflow:hidden}.cHi0Pa_root:hover .cHi0Pa_row,.cHi0Pa_root:focus-within .cHi0Pa_row{background:var(--dsw-alias-interactive-bg-hover)}.cHi0Pa_root:has([data-open]){border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);padding:4px 6px}.cHi0Pa_root[data-state=running] .cHi0Pa_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);width:42%;animation:cHi0Pa_hydra-tool-row-sweep 2.6s var(--ds-ease-in-out) infinite;pointer-events:none;position:absolute;top:0;bottom:0;left:0;transform:translate(-125%)}@keyframes cHi0Pa_hydra-tool-row-sweep{0%{transform:translate(-125%)}90%,to{transform:translate(350%)}}.cHi0Pa_leading{flex-shrink:0}.cHi0Pa_root[data-tool^=cordis_] .cHi0Pa_leading,.cHi0Pa_root[data-tool^=cordis_] .cHi0Pa_title{color:var(--dsw-alias-state-business-primary)}.cHi0Pa_root[data-tool^=cordis_] .cHi0Pa_title{font-weight:500}.cHi0Pa_root[data-tool^=cordis_] .cHi0Pa_sep{background:var(--dsw-alias-state-business-primary)}.cHi0Pa_chevron{color:var(--dsw-alias-label-secondary)}.cHi0Pa_title{font-weight:400}.cHi0Pa_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.cHi0Pa_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}.cHi0Pa_summarySuffix{white-space:nowrap;color:var(--dsw-alias-label-tertiary);flex:none;margin-left:4px;font-size:14px;line-height:24px}.cHi0Pa_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}.cHi0Pa_fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}.cHi0Pa_errorSummary{color:var(--dsw-alias-state-error-primary)}.cHi0Pa_bodyWrap{flex-direction:column;display:flex}.cHi0Pa_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}.cHi0Pa_root:hover .cHi0Pa_inspectButton,.cHi0Pa_inspectButton:focus-visible{opacity:1}.cHi0Pa_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.cHi0Pa_bodyScroll{max-height:260px;overflow-y:auto}.cHi0Pa_ioCard{border:1px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-panel);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}.cHi0Pa_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.cHi0Pa_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.cHi0Pa_ioSection::-webkit-scrollbar-track{margin:6px 0}.cHi0Pa_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.cHi0Pa_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.cHi0Pa_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.cHi0Pa_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.cHi0Pa_codeBody,.cHi0Pa_terminalBody,.cHi0Pa_diffBody,.cHi0Pa_readBody,.cHi0Pa_searchBody,.cHi0Pa_webBody{margin:4px 0 4px 4px}.cHi0Pa_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}.cHi0Pa_codeBody{--dsl-code-block-content-font:var(--dsw-font-markdown-code-block-small)}.cHi0Pa_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border-color:var(--dsw-alias-border-l1)}.cHi0Pa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.cHi0Pa_root,.cHi0Pa_row,.cHi0Pa_root[data-state=running] .cHi0Pa_row:after{transition:none;animation:none}}";
644
+ const tagId$4 = "@hydraharness/harness-client-ui-tool/ToolRow.module.css";
645
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
646
+ const tag = document.createElement("style");
647
+ tag.dataset.plugin = "@hydraharness/harness-client-ui-tool";
648
+ tag.dataset.pluginCss = tagId$4;
649
+ tag.textContent = css$4;
650
+ document.head.appendChild(tag);
651
+ }
652
+ var ToolRow_module_css_default = {
653
+ "bodyScroll": "cHi0Pa_bodyScroll",
654
+ "bodyWrap": "cHi0Pa_bodyWrap",
655
+ "chevron": "cHi0Pa_chevron",
656
+ "codeBody": "cHi0Pa_codeBody",
657
+ "diffBody": "cHi0Pa_diffBody",
658
+ "errorSummary": "cHi0Pa_errorSummary",
659
+ "fileLink": "cHi0Pa_fileLink",
660
+ "hydra-tool-row-sweep": "cHi0Pa_hydra-tool-row-sweep",
661
+ "inspectButton": "cHi0Pa_inspectButton",
662
+ "ioCard": "cHi0Pa_ioCard",
663
+ "ioDivider": "cHi0Pa_ioDivider",
664
+ "ioLabel": "cHi0Pa_ioLabel",
665
+ "ioSection": "cHi0Pa_ioSection",
666
+ "ioText": "cHi0Pa_ioText",
667
+ "leading": "cHi0Pa_leading",
668
+ "readBody": "cHi0Pa_readBody",
669
+ "root": "cHi0Pa_root",
670
+ "row": "cHi0Pa_row",
671
+ "searchBody": "cHi0Pa_searchBody",
672
+ "searchRecovery": "cHi0Pa_searchRecovery",
673
+ "sep": "cHi0Pa_sep",
674
+ "summary": "cHi0Pa_summary",
675
+ "summarySuffix": "cHi0Pa_summarySuffix",
676
+ "terminalBody": "cHi0Pa_terminalBody",
677
+ "title": "cHi0Pa_title",
678
+ "visuallyHidden": "cHi0Pa_visuallyHidden",
679
+ "webBody": "cHi0Pa_webBody"
680
+ };
681
+ //#endregion
682
+ //#region lib/types/client/tool/components/ToolRow.js
683
+ /** Leading-slot state substitution: the tool icon yields to the terminal state
684
+ * semantic (error = red, interrupted = amber halo). Running keeps the icon —
685
+ * the row sweep (CSS on data-state) carries the in-flight signal. */
686
+ function leadingFor$1(state, icon) {
687
+ switch (state) {
688
+ case "error": return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.StateDot, { state: "error" });
689
+ case "stopped": return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.StateDot, { state: "warning" });
690
+ default: return icon;
691
+ }
692
+ }
693
+ /** Visually hidden run-state label: the StateDot and the CSS sweep are both
694
+ * aria-hidden / colour-only, so assistive technology needs this text to know a
695
+ * row is running, failed, or interrupted. null in the ok state (the icon and
696
+ * summary already describe a settled row). */
697
+ function stateStatus$1(state, t) {
698
+ switch (state) {
699
+ case "running": return t("row.running");
700
+ case "error": return t("row.failed");
701
+ case "stopped": return t("row.stopped");
702
+ default: return null;
703
+ }
704
+ }
705
+ function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, body, output, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect }) {
706
+ const [expanded, setExpanded] = (0, react.useState)(false);
707
+ const terminalBody = terminal ?? null;
708
+ const diffBody = diff ?? null;
709
+ const readBody = read ?? null;
710
+ const searchBody = search ?? null;
711
+ const webBody = web ?? null;
712
+ const outputText = output ?? null;
713
+ const expandable = body !== null || outputText !== null || (terminalBody ?? diffBody ?? readBody ?? searchBody ?? webBody) !== null;
714
+ const open = expanded && expandable;
715
+ const status = stateStatus$1(state, t);
716
+ const failureLine = state === "error" ? errorSummary ?? null : null;
717
+ const summaryText = failureLine ?? summary;
718
+ const suffix = failureLine === null ? summarySuffix ?? null : null;
719
+ const fileLink = filePath !== void 0 && onOpenFile !== void 0 && failureLine === null;
720
+ const toggleExpand = () => {
721
+ setExpanded((v) => !v);
722
+ };
723
+ const openFile = (event) => {
724
+ event.stopPropagation();
725
+ if (filePath !== void 0) onOpenFile?.(filePath);
726
+ };
727
+ const fileLinkKeyDown = (event) => {
728
+ if (event.key === "Enter" || event.key === " ") event.stopPropagation();
729
+ };
730
+ const cardBody = variant === "code" ? null : body;
731
+ return (0, react_jsx_runtime.jsxs)("div", {
732
+ className: ToolRow_module_css_default.root,
733
+ "data-variant": variant,
734
+ "data-tool": toolName,
735
+ "data-state": state,
736
+ children: [status !== null && (0, react_jsx_runtime.jsx)("span", {
737
+ className: ToolRow_module_css_default.visuallyHidden,
738
+ children: status
739
+ }), (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.DisclosureRow, {
740
+ rowClassName: ToolRow_module_css_default.row,
741
+ leadingClassName: ToolRow_module_css_default.leading,
742
+ titleClassName: ToolRow_module_css_default.title,
743
+ chevronClassName: ToolRow_module_css_default.chevron,
744
+ icon: leadingFor$1(state, icon),
745
+ title,
746
+ open,
747
+ expandable,
748
+ expandOnRowClick: true,
749
+ keepContentWhenOpen: true,
750
+ onToggle: toggleExpand,
751
+ collapsedContent: summaryText !== "" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
752
+ (0, react_jsx_runtime.jsx)("span", {
753
+ className: ToolRow_module_css_default.sep,
754
+ "aria-hidden": true
755
+ }),
756
+ fileLink ? (0, react_jsx_runtime.jsx)("button", {
757
+ type: "button",
758
+ className: ToolRow_module_css_default.fileLink,
759
+ onClick: openFile,
760
+ onKeyDown: fileLinkKeyDown,
761
+ children: summaryText
762
+ }) : (0, react_jsx_runtime.jsx)("span", {
763
+ className: clsx(ToolRow_module_css_default.summary, failureLine !== null && ToolRow_module_css_default.errorSummary),
764
+ children: summaryText
765
+ }),
766
+ suffix !== null && (0, react_jsx_runtime.jsx)("span", {
767
+ className: ToolRow_module_css_default.summarySuffix,
768
+ children: suffix
769
+ })
770
+ ] }),
771
+ children: (0, react_jsx_runtime.jsxs)("div", {
772
+ className: ToolRow_module_css_default.bodyWrap,
773
+ children: [terminalBody !== null ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.TerminalBlock, {
774
+ ...terminalBody.card,
775
+ maxLines: Infinity,
776
+ labels: terminalBlockLabels(t),
777
+ className: ToolRow_module_css_default.terminalBody
778
+ }) : diffBody !== null ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.DiffBlock, {
779
+ ...diffBody.card,
780
+ maxLines: 8,
781
+ className: ToolRow_module_css_default.diffBody
782
+ }) : readBody !== null ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.ReadBlock, {
783
+ ...readBody,
784
+ maxLines: 8,
785
+ className: ToolRow_module_css_default.readBody
786
+ }) : searchBody !== null ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.SearchBlock, {
787
+ ...searchBody.card,
788
+ maxLines: 8,
789
+ className: ToolRow_module_css_default.searchBody
790
+ }), searchBody.recovery !== void 0 && (0, react_jsx_runtime.jsx)("div", {
791
+ className: ToolRow_module_css_default.searchRecovery,
792
+ children: searchBody.recovery
793
+ })] }) : webBody !== null ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.WebBlock, {
794
+ ...webBody,
795
+ className: ToolRow_module_css_default.webBody
796
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [variant === "code" && body !== null && (0, react_jsx_runtime.jsx)("div", {
797
+ className: ToolRow_module_css_default.bodyScroll,
798
+ children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.CodeBlock, {
799
+ code: body,
800
+ lang: "typescript",
801
+ copyLabel: t("copy"),
802
+ copiedLabel: t("copied"),
803
+ className: ToolRow_module_css_default.codeBody
804
+ })
805
+ }), (cardBody !== null || outputText !== null) && (0, react_jsx_runtime.jsxs)("div", {
806
+ className: ToolRow_module_css_default.ioCard,
807
+ children: [
808
+ cardBody !== null && (0, react_jsx_runtime.jsxs)("div", {
809
+ className: ToolRow_module_css_default.ioSection,
810
+ children: [(0, react_jsx_runtime.jsx)("span", {
811
+ className: ToolRow_module_css_default.ioLabel,
812
+ children: "IN"
813
+ }), (0, react_jsx_runtime.jsx)("span", {
814
+ className: ToolRow_module_css_default.ioText,
815
+ children: cardBody
816
+ })]
817
+ }),
818
+ cardBody !== null && outputText !== null && (0, react_jsx_runtime.jsx)("span", {
819
+ className: ToolRow_module_css_default.ioDivider,
820
+ "aria-hidden": true
821
+ }),
822
+ outputText !== null && (0, react_jsx_runtime.jsxs)("div", {
823
+ className: ToolRow_module_css_default.ioSection,
824
+ children: [(0, react_jsx_runtime.jsx)("span", {
825
+ className: ToolRow_module_css_default.ioLabel,
826
+ children: "OUT"
827
+ }), (0, react_jsx_runtime.jsx)("span", {
828
+ className: ToolRow_module_css_default.ioText,
829
+ "data-error": state === "error" || void 0,
830
+ children: outputText
831
+ })]
832
+ })
833
+ ]
834
+ })] }), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
835
+ type: "button",
836
+ className: ToolRow_module_css_default.inspectButton,
837
+ onClick: inspect,
838
+ children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconInspectOutline12, {}), "Inspect"]
839
+ })]
840
+ })
841
+ })]
842
+ });
843
+ }
844
+ //#endregion
845
+ //#region lib/types/client/tool/toolviews/GenericToolCard.js
846
+ /** Variant leading icons (figma table); all glyphs render at 14 inside the 16px leading box. */
847
+ const VARIANT_ICONS = {
848
+ search: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconSearchOutline16, { size: 14 }),
849
+ read: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
850
+ bash: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconApiOutline14, { size: 14 }),
851
+ write: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconEditOutline16, { size: 14 }),
852
+ edit: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconEditOutline16, { size: 14 }),
853
+ code: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconCodeOutline16, { size: 14 }),
854
+ others: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconSparkle16, { size: 14 })
855
+ };
856
+ function variantForKind(kind) {
857
+ switch (kind) {
858
+ case "read": return "read";
859
+ case "write": return "write";
860
+ case "execute": return "bash";
861
+ case "search": return "search";
862
+ default: return;
863
+ }
864
+ }
865
+ function GenericToolCard({ toolName, block, cwd, home, openFile, inspect, t }) {
866
+ const model = toolRowModel(toolName, block, cwd, home);
867
+ const terminal = terminalCardModel(block, cwd);
868
+ const read = readCardModel(block, cwd, home);
869
+ const diff = diffCardModel(block);
870
+ const search = searchCardModel(block);
871
+ const web = webCardModel(block);
872
+ const state = model.state === "ok" && terminal !== null && terminalFailed(terminal) ? "error" : model.state;
873
+ const singleFile = model.filePath !== void 0;
874
+ const genericCall = block.callView !== null && block.callView.card === "generic" ? block.callView : null;
875
+ const variant = (model.variant === "others" && genericCall?.kind ? variantForKind(genericCall.kind) : void 0) ?? model.variant;
876
+ const summary = terminal?.description ?? search?.title ?? genericCall?.title ?? model.summary;
877
+ const body = singleFile ? null : genericCall !== null && genericCall.rawInput !== void 0 ? typeof genericCall.rawInput === "string" ? genericCall.rawInput : JSON.stringify(genericCall.rawInput, null, 2) : model.body;
878
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
879
+ t,
880
+ variant,
881
+ toolName,
882
+ icon: VARIANT_ICONS[variant],
883
+ title: model.title,
884
+ summary,
885
+ body,
886
+ output: model.output,
887
+ errorSummary: model.errorSummary,
888
+ terminal,
889
+ diff,
890
+ read,
891
+ search,
892
+ web,
893
+ state,
894
+ filePath: model.filePath,
895
+ onOpenFile: singleFile ? openFile : void 0,
896
+ inspect
897
+ });
898
+ }
899
+ //#endregion
900
+ //#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-tool/src/client/tool/ToolCallTree.module.css.mjs
901
+ const css$3 = ".sJNTyW_callRow{border-radius:var(--dsw-radius-panel)}.sJNTyW_callRow[data-selected]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 7%, transparent)}.sJNTyW_subCalls{border-left:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:4px;margin:4px 0 2px 22px;padding-left:8px;display:flex}";
902
+ const tagId$3 = "@hydraharness/harness-client-ui-tool/ToolCallTree.module.css";
903
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
904
+ const tag = document.createElement("style");
905
+ tag.dataset.plugin = "@hydraharness/harness-client-ui-tool";
906
+ tag.dataset.pluginCss = tagId$3;
907
+ tag.textContent = css$3;
908
+ document.head.appendChild(tag);
909
+ }
910
+ var ToolCallTree_module_css_default = {
911
+ "callRow": "sJNTyW_callRow",
912
+ "subCalls": "sJNTyW_subCalls"
913
+ };
914
+ //#endregion
915
+ //#region lib/types/client/tool/ToolCallTree.js
916
+ /** Root/subcall Tool composition with one keyed atomic dispatch path. */
917
+ /** Resolve a Tool call's wire name from either lifecycle form. */
918
+ function callName(node) {
919
+ return "kind" in node ? node.call?.name ?? "" : node.name;
920
+ }
921
+ /** One atomic call dispatched through the Tool-owned keyed slot. */
922
+ const ToolCall = (0, react.memo)(function ToolCall({ renderSlot, renderMessageImages, callId, toolName, block, openFile, selected, cwd, home, inspectCall, t, children }) {
923
+ const owner = (0, react.useMemo)(() => ({
924
+ callId,
925
+ toolName,
926
+ block,
927
+ openFile,
928
+ cwd,
929
+ home,
930
+ inspect: () => {
931
+ inspectCall(callId);
932
+ }
933
+ }), [
934
+ callId,
935
+ toolName,
936
+ block,
937
+ openFile,
938
+ cwd,
939
+ home,
940
+ inspectCall
941
+ ]);
942
+ const images = "kind" in block && !block.isError && block.resultView?.card === "generic" ? block.resultView.content?.flatMap((content) => content.type === "image" ? [{ attachment: content.attachment }] : []) ?? [] : [];
943
+ return (0, react_jsx_runtime.jsxs)("div", {
944
+ className: ToolCallTree_module_css_default.callRow,
945
+ "data-chat-anchor-key": `call:${callId}`,
946
+ "data-chat-call-id": callId,
947
+ "data-selected": selected || void 0,
948
+ children: [
949
+ renderSlot("tool.call.toolview", owner, {
950
+ entryKey: toolName,
951
+ fallback: (0, react_jsx_runtime.jsx)(GenericToolCard, {
952
+ ...owner,
953
+ t
954
+ })
955
+ }),
956
+ renderSlot("tool.call.review", { callId }),
957
+ images.length > 0 && renderMessageImages({
958
+ images,
959
+ align: "start"
960
+ }),
961
+ children
962
+ ]
963
+ });
964
+ });
965
+ const ToolCallBranch = (0, react.memo)(function ToolCallBranch({ renderSlot, renderMessageImages, block, selectedCallId, cwd, home, openFile, inspectCall, t }) {
966
+ return (0, react_jsx_runtime.jsx)(ToolCall, {
967
+ renderSlot,
968
+ renderMessageImages,
969
+ callId: block.callId,
970
+ toolName: callName(block),
971
+ block,
972
+ openFile,
973
+ selected: block.callId === selectedCallId,
974
+ cwd,
975
+ home,
976
+ inspectCall,
977
+ t,
978
+ children: block.subCalls.length > 0 ? (0, react_jsx_runtime.jsx)("div", {
979
+ className: ToolCallTree_module_css_default.subCalls,
980
+ "data-subcalls": true,
981
+ children: block.subCalls.map((child) => (0, react_jsx_runtime.jsx)(ToolCallBranch, {
982
+ renderSlot,
983
+ renderMessageImages,
984
+ block: child,
985
+ selectedCallId,
986
+ cwd,
987
+ home,
988
+ openFile,
989
+ inspectCall,
990
+ t
991
+ }, child.callId))
992
+ }) : null
993
+ });
994
+ });
995
+ /**
996
+ * Render one root Tool call and its recursive children through the same
997
+ * atomic keyed dispatch.
998
+ * @param props - whole-Tool owner data and the Tool-owned child-slot share.
999
+ * @returns the Tool call tree.
1000
+ */
1001
+ function ToolCallTree({ renderSlot, renderMessageImages, node, selectedCallId, cwd, openFile, inspectCall, useHostDescription, t }) {
1002
+ const home = useHostDescription((description) => description?.home);
1003
+ const block = node.data.root;
1004
+ return (0, react_jsx_runtime.jsx)(ToolCallBranch, {
1005
+ renderSlot,
1006
+ renderMessageImages,
1007
+ block,
1008
+ selectedCallId,
1009
+ cwd,
1010
+ home,
1011
+ openFile,
1012
+ inspectCall,
1013
+ t
1014
+ });
1015
+ }
1016
+ //#endregion
1017
+ //#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-tool/src/client/tool/MediaGeneration.module.css.mjs
1018
+ const css$2 = ".Ez8rOG_root{border:1px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-panel);background:var(--dsw-alias-bg-layer-1);min-width:0;padding:16px;overflow:hidden}.Ez8rOG_root[data-selected]{border-color:var(--dsw-alias-state-business-primary)}.Ez8rOG_header{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;display:flex}.Ez8rOG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.Ez8rOG_title{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:600;line-height:22px;display:flex}.Ez8rOG_details,.Ez8rOG_download{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.Ez8rOG_details{cursor:pointer;background:0 0;border:0;flex-shrink:0;padding:4px}.Ez8rOG_details:focus-visible,.Ez8rOG_download:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:3px}.Ez8rOG_prompt{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;user-select:text;margin:8px 0 12px;font-size:13px;line-height:20px}.Ez8rOG_placeholder{border-radius:var(--dsw-radius-panel);background:var(--dsw-alias-bg-layer-2);place-items:center;min-height:180px;display:grid;position:relative;overflow:hidden}.Ez8rOG_placeholder[data-aspect-ratio]{width:100%;min-height:0}.Ez8rOG_placeholder:before{content:\"\";background:linear-gradient(110deg, transparent 25%, color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent) 50%, transparent 75%);background-size:250% 100%;animation:2.4s ease-in-out infinite Ez8rOG_media-shimmer;position:absolute;inset:0}.Ez8rOG_orbit{border:1px solid var(--dsw-alias-border-l2);width:64px;height:64px;color:var(--dsw-alias-label-secondary);border-radius:50%;place-items:center;animation:2.4s ease-in-out infinite Ez8rOG_media-breathe;display:grid}.Ez8rOG_output,.Ez8rOG_video{margin-top:12px}.Ez8rOG_video video{border-radius:var(--dsw-radius-panel);background:var(--dsw-alias-bg-layer-2);width:auto;max-width:100%;height:auto;max-height:480px;display:block}.Ez8rOG_download{margin-top:8px;display:inline-block}.Ez8rOG_caption{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin-top:12px;font-size:12px;line-height:18px}.Ez8rOG_error{color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:20px}@keyframes Ez8rOG_media-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@keyframes Ez8rOG_media-breathe{50%{opacity:.6;transform:scale(1.08)}}@media (prefers-reduced-motion:reduce){.Ez8rOG_placeholder:before,.Ez8rOG_orbit{animation:none}}";
1019
+ const tagId$2 = "@hydraharness/harness-client-ui-tool/MediaGeneration.module.css";
1020
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1021
+ const tag = document.createElement("style");
1022
+ tag.dataset.plugin = "@hydraharness/harness-client-ui-tool";
1023
+ tag.dataset.pluginCss = tagId$2;
1024
+ tag.textContent = css$2;
1025
+ document.head.appendChild(tag);
1026
+ }
1027
+ var MediaGeneration_module_css_default = {
1028
+ "actions": "Ez8rOG_actions",
1029
+ "caption": "Ez8rOG_caption",
1030
+ "details": "Ez8rOG_details",
1031
+ "download": "Ez8rOG_download",
1032
+ "error": "Ez8rOG_error",
1033
+ "header": "Ez8rOG_header",
1034
+ "media-breathe": "Ez8rOG_media-breathe",
1035
+ "media-shimmer": "Ez8rOG_media-shimmer",
1036
+ "orbit": "Ez8rOG_orbit",
1037
+ "output": "Ez8rOG_output",
1038
+ "placeholder": "Ez8rOG_placeholder",
1039
+ "prompt": "Ez8rOG_prompt",
1040
+ "root": "Ez8rOG_root",
1041
+ "title": "Ez8rOG_title",
1042
+ "video": "Ez8rOG_video"
1043
+ };
1044
+ //#endregion
1045
+ //#region lib/types/client/tool/MediaGeneration.js
1046
+ /** Dedicated media generation output, independent of tool activity disclosure. */
1047
+ function GeneratedVideo({ attachment, loadMedia, t }) {
1048
+ const [url, setUrl] = (0, react.useState)();
1049
+ const [failed, setFailed] = (0, react.useState)(false);
1050
+ const [attempt, setAttempt] = (0, react.useState)(0);
1051
+ (0, react.useEffect)(() => {
1052
+ let active = true;
1053
+ setFailed(false);
1054
+ loadMedia(attachment).then((value) => {
1055
+ if (active) setUrl(value);
1056
+ }, () => {
1057
+ if (active) setFailed(true);
1058
+ });
1059
+ return () => {
1060
+ active = false;
1061
+ };
1062
+ }, [
1063
+ attachment,
1064
+ loadMedia,
1065
+ attempt
1066
+ ]);
1067
+ return (0, react_jsx_runtime.jsx)("div", {
1068
+ className: MediaGeneration_module_css_default.video,
1069
+ children: url !== void 0 && !failed ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("video", {
1070
+ src: url,
1071
+ controls: true,
1072
+ playsInline: true,
1073
+ preload: "metadata",
1074
+ "aria-label": attachment.name,
1075
+ onError: () => {
1076
+ setFailed(true);
1077
+ }
1078
+ }), (0, react_jsx_runtime.jsx)("a", {
1079
+ className: MediaGeneration_module_css_default.download,
1080
+ href: url,
1081
+ download: attachment.name,
1082
+ children: t("media.downloadVideo")
1083
+ })] }) : failed ? (0, react_jsx_runtime.jsxs)("div", {
1084
+ role: "alert",
1085
+ children: [t("media.videoLoadFailed"), (0, react_jsx_runtime.jsx)("button", {
1086
+ type: "button",
1087
+ onClick: () => {
1088
+ setUrl(void 0);
1089
+ setAttempt((value) => value + 1);
1090
+ },
1091
+ children: t("retry")
1092
+ })]
1093
+ }) : (0, react_jsx_runtime.jsx)("div", {
1094
+ role: "status",
1095
+ children: t("media.loadingVideo")
1096
+ })
1097
+ });
1098
+ }
1099
+ /**
1100
+ * Render generation progress, durable output, or its settled error as one chat card.
1101
+ * @param props - logged call/result and the conversation's media callbacks.
1102
+ * @returns an always-visible media output.
1103
+ */
1104
+ function MediaGeneration({ node, renderMessageImages, loadMedia, loadModelName, inspectCall, selectedCallId, t }) {
1105
+ const block = node.data.root;
1106
+ const call = block.callView?.card === "media" ? block.callView : null;
1107
+ const result = "kind" in block && block.resultView?.card === "media" ? block.resultView : null;
1108
+ const kind = result?.kind ?? call?.kind ?? "image";
1109
+ const state = !("kind" in block) ? "running" : block.isError ? block.error?.code === "interrupted" ? "interrupted" : "error" : "complete";
1110
+ const images = state === "complete" ? result?.content.flatMap((part) => part.type === "image" ? [{ attachment: part.attachment }] : []) ?? [] : [];
1111
+ const videos = state === "complete" ? result?.content.flatMap((part) => part.type === "video" ? [part.attachment] : []) ?? [] : [];
1112
+ const label = t(`media.${kind}.${state}`);
1113
+ const text = "kind" in block ? resultText(block) : "";
1114
+ const provider = result?.provider;
1115
+ const model = result?.model;
1116
+ const [modelName, setModelName] = (0, react.useState)();
1117
+ const [copied, setCopied] = (0, react.useState)(false);
1118
+ (0, react.useEffect)(() => {
1119
+ let active = true;
1120
+ setModelName(void 0);
1121
+ if (model !== void 0) loadModelName(provider, model).then((name) => {
1122
+ if (active) setModelName(name);
1123
+ }, () => {
1124
+ if (active) setModelName(void 0);
1125
+ });
1126
+ return () => {
1127
+ active = false;
1128
+ };
1129
+ }, [
1130
+ loadModelName,
1131
+ provider,
1132
+ model
1133
+ ]);
1134
+ (0, react.useEffect)(() => {
1135
+ setCopied(false);
1136
+ }, [block.callId, call?.prompt]);
1137
+ (0, react.useEffect)(() => {
1138
+ if (!copied) return;
1139
+ const timer = window.setTimeout(() => {
1140
+ setCopied(false);
1141
+ }, 1e3);
1142
+ return () => {
1143
+ window.clearTimeout(timer);
1144
+ };
1145
+ }, [copied]);
1146
+ return (0, react_jsx_runtime.jsxs)("section", {
1147
+ className: MediaGeneration_module_css_default.root,
1148
+ "aria-label": label,
1149
+ "aria-busy": state === "running",
1150
+ "data-media-generation": kind,
1151
+ "data-state": state,
1152
+ "data-chat-call-id": block.callId,
1153
+ "data-chat-anchor-key": `call:${block.callId}`,
1154
+ "data-selected": block.callId === selectedCallId || void 0,
1155
+ children: [
1156
+ (0, react_jsx_runtime.jsxs)("header", {
1157
+ className: MediaGeneration_module_css_default.header,
1158
+ children: [(0, react_jsx_runtime.jsxs)("span", {
1159
+ className: MediaGeneration_module_css_default.title,
1160
+ children: [(0, react_jsx_runtime.jsx)("span", {
1161
+ "aria-hidden": true,
1162
+ children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconSparkle16, {})
1163
+ }), (0, react_jsx_runtime.jsx)("span", {
1164
+ role: "status",
1165
+ "aria-live": "polite",
1166
+ children: label
1167
+ })]
1168
+ }), (0, react_jsx_runtime.jsxs)("div", {
1169
+ className: MediaGeneration_module_css_default.actions,
1170
+ children: [call?.prompt !== void 0 && (0, react_jsx_runtime.jsx)("button", {
1171
+ type: "button",
1172
+ className: MediaGeneration_module_css_default.details,
1173
+ onClick: () => {
1174
+ (0, _hydraharness_harness_client_ui_primitives.writeClipboard)(call.prompt).then((ok) => {
1175
+ if (ok) setCopied(true);
1176
+ });
1177
+ },
1178
+ children: copied ? t("copied") : t("media.copyPrompt")
1179
+ }), (0, react_jsx_runtime.jsx)("button", {
1180
+ type: "button",
1181
+ className: MediaGeneration_module_css_default.details,
1182
+ onClick: () => {
1183
+ inspectCall(block.callId);
1184
+ },
1185
+ children: t("media.details")
1186
+ })]
1187
+ })]
1188
+ }),
1189
+ call?.prompt !== void 0 && (0, react_jsx_runtime.jsx)("p", {
1190
+ className: MediaGeneration_module_css_default.prompt,
1191
+ children: call.prompt
1192
+ }),
1193
+ state === "running" && (0, react_jsx_runtime.jsx)("div", {
1194
+ className: MediaGeneration_module_css_default.placeholder,
1195
+ "data-media-placeholder": true,
1196
+ "data-aspect-ratio": call?.aspectRatio,
1197
+ "aria-hidden": true,
1198
+ style: call?.aspectRatio === void 0 ? void 0 : { aspectRatio: `${call.aspectRatio} / 1` },
1199
+ children: (0, react_jsx_runtime.jsx)("span", {
1200
+ className: MediaGeneration_module_css_default.orbit,
1201
+ children: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconSparkle16, { size: 24 })
1202
+ })
1203
+ }),
1204
+ (state === "error" || state === "interrupted") && (0, react_jsx_runtime.jsx)("p", {
1205
+ className: MediaGeneration_module_css_default.error,
1206
+ role: "alert",
1207
+ children: text || label
1208
+ }),
1209
+ images.length > 0 && (0, react_jsx_runtime.jsx)("div", {
1210
+ className: MediaGeneration_module_css_default.output,
1211
+ children: renderMessageImages({
1212
+ images,
1213
+ align: "start",
1214
+ presentation: "media"
1215
+ })
1216
+ }),
1217
+ videos.map((attachment) => (0, react_jsx_runtime.jsx)(GeneratedVideo, {
1218
+ attachment,
1219
+ loadMedia,
1220
+ t
1221
+ }, attachment.attachmentId)),
1222
+ state === "complete" && (0, react_jsx_runtime.jsx)("footer", {
1223
+ className: MediaGeneration_module_css_default.caption,
1224
+ children: modelName ?? model ?? text
1225
+ })
1226
+ ]
1227
+ });
1228
+ }
1229
+ //#endregion
1230
+ //#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-tool/src/client/tool/ToolDetails.module.css.mjs
1231
+ const css$1 = "._1KR71q_description{color:var(--dsw-alias-label-secondary);font:var(--dsw-font-xs-13);margin:0 0 6px}._1KR71q_cardBody{margin:0}._1KR71q_recovery{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-xs-13);margin:6px 0 0}._1KR71q_code{border:1px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-panel);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;margin:0;padding:16px;font-size:13px;line-height:22px}._1KR71q_code[data-error]{color:var(--dsw-alias-state-error-primary)}._1KR71q_read,._1KR71q_web{margin:0}._1KR71q_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}";
1232
+ const tagId$1 = "@hydraharness/harness-client-ui-tool/ToolDetails.module.css";
1233
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1234
+ const tag = document.createElement("style");
1235
+ tag.dataset.plugin = "@hydraharness/harness-client-ui-tool";
1236
+ tag.dataset.pluginCss = tagId$1;
1237
+ tag.textContent = css$1;
1238
+ document.head.appendChild(tag);
1239
+ }
1240
+ var ToolDetails_module_css_default = {
1241
+ "cardBody": "_1KR71q_cardBody",
1242
+ "code": "_1KR71q_code",
1243
+ "description": "_1KR71q_description",
1244
+ "empty": "_1KR71q_empty",
1245
+ "read": "_1KR71q_read",
1246
+ "recovery": "_1KR71q_recovery",
1247
+ "web": "_1KR71q_web"
1248
+ };
1249
+ //#endregion
1250
+ //#region lib/types/client/tool/ToolDetails.js
1251
+ /** Card-aware output body for the selected Tool call in details. */
1252
+ /**
1253
+ * Render the selected Tool call's structured output when its presentation
1254
+ * intent is known, otherwise preserve the flattened result text.
1255
+ * @param props - selected call slice, workspace root, host home, and locale seat.
1256
+ * @returns the details output body.
1257
+ */
1258
+ function ToolDetails({ block, cwd, useHostDescription, t }) {
1259
+ const home = useHostDescription((description) => description?.home);
1260
+ const terminal = terminalCardModel(block, cwd);
1261
+ if (terminal !== null) return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [terminal.description !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
1262
+ className: ToolDetails_module_css_default.description,
1263
+ children: terminal.description
1264
+ }) : null, (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.TerminalBlock, {
1265
+ ...terminal.card,
1266
+ labels: terminalBlockLabels(t),
1267
+ className: ToolDetails_module_css_default.cardBody
1268
+ })] });
1269
+ const read = readCardModel(block, cwd, home);
1270
+ if (read !== null) return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.ReadBlock, {
1271
+ ...read,
1272
+ className: ToolDetails_module_css_default.read
1273
+ });
1274
+ const diff = diffCardModel(block);
1275
+ if (diff !== null) return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.DiffBlock, {
1276
+ ...diff.card,
1277
+ className: ToolDetails_module_css_default.cardBody
1278
+ });
1279
+ const search = searchCardModel(block);
1280
+ if (search !== null) return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.SearchBlock, {
1281
+ ...search.card,
1282
+ className: ToolDetails_module_css_default.cardBody
1283
+ }), search.recovery !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
1284
+ className: ToolDetails_module_css_default.recovery,
1285
+ children: search.recovery
1286
+ }) : null] });
1287
+ const web = webCardModel(block);
1288
+ if (web !== null) {
1289
+ const body = "kind" in block ? resultText(block) : "";
1290
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.WebBlock, {
1291
+ ...web,
1292
+ className: ToolDetails_module_css_default.web
1293
+ }), body !== "" ? (0, react_jsx_runtime.jsx)("pre", {
1294
+ className: ToolDetails_module_css_default.code,
1295
+ children: body
1296
+ }) : null] });
1297
+ }
1298
+ if (!("kind" in block)) return (0, react_jsx_runtime.jsx)("div", {
1299
+ className: ToolDetails_module_css_default.empty,
1300
+ children: t("details.running")
1301
+ });
1302
+ return (0, react_jsx_runtime.jsx)("pre", {
1303
+ className: ToolDetails_module_css_default.code,
1304
+ "data-error": block.isError || void 0,
1305
+ children: resultText(block)
1306
+ });
1307
+ }
1308
+ //#endregion
1309
+ //#region lib/types/client/locale.js
1310
+ /** Locale namespace supplied by the conversation owner to Tool renderers. */
1311
+ const CONVERSATION_NS = "conversation";
1312
+ //#endregion
1313
+ //#region lib/types/client/tool/toolviews/ask-question-row.js
1314
+ function isAnswer(value) {
1315
+ return typeof value === "object" && value !== null;
1316
+ }
1317
+ /** Answered-count summary from the result JSON (a skipped question has
1318
+ * empty `selected` and no `custom`); null when answer fields are invalid. */
1319
+ function answeredSummary(text, t) {
1320
+ let parsed;
1321
+ try {
1322
+ parsed = JSON.parse(text);
1323
+ } catch {
1324
+ return null;
1325
+ }
1326
+ if (typeof parsed !== "object" || parsed === null) return null;
1327
+ const answers = parsed.answers;
1328
+ if (!Array.isArray(answers) || !answers.every(isAnswer)) return null;
1329
+ const answered = answers.filter((a) => Array.isArray(a.selected) && a.selected.length > 0 || typeof a.custom === "string" && a.custom !== "").length;
1330
+ return t("ask.answered", {
1331
+ answered,
1332
+ total: answers.length
1333
+ });
1334
+ }
1335
+ /** One-line question-interaction row (the whole row toggles the call's
1336
+ * Input/Output sections, ToolRow's unified expand). */
1337
+ function AskQuestionRow({ toolName, block, inspect, t }) {
1338
+ const model = toolRowModel(toolName, block);
1339
+ const code = "kind" in block ? block.error?.code : void 0;
1340
+ let summary = model.summary;
1341
+ let state = model.state;
1342
+ if (code === "ASK_CANCELLED") summary = t("ask.cancelled");
1343
+ else if (code === "ASK_ABORTED") {
1344
+ summary = t("ask.interrupted");
1345
+ state = "stopped";
1346
+ } else if (model.state === "running") summary = t("ask.waiting");
1347
+ 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;
1348
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1349
+ t,
1350
+ variant: model.variant,
1351
+ toolName,
1352
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconQuestionOutline14, {}),
1353
+ title: t("ask.rowTitle"),
1354
+ summary,
1355
+ body: model.body,
1356
+ output: model.output,
1357
+ state,
1358
+ inspect
1359
+ });
1360
+ }
1361
+ /**
1362
+ * The ask-question row as a plain registrant plugin following the chat
1363
+ * toolview declaration across independent activation and reload lifetimes.
1364
+ */
1365
+ const askQuestionToolview = {
1366
+ name: "ask-question-toolview",
1367
+ inject: ["slots"],
1368
+ /**
1369
+ * Register the ask-question row into the Tool-owned keyed view slot.
1370
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1371
+ */
1372
+ apply(ctx) {
1373
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1374
+ name: "tool.call.toolview",
1375
+ key: "ask_user_question",
1376
+ locale: CONVERSATION_NS
1377
+ }, AskQuestionRow));
1378
+ }
1379
+ };
1380
+ //#endregion
1381
+ //#region \0hydra-css:/home/runner/work/Hydra-Harness/Hydra-Harness/packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css.mjs
1382
+ const css = ".JLsvgW_card{flex-direction:column;display:flex}.JLsvgW_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}.JLsvgW_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}.JLsvgW_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.JLsvgW_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.JLsvgW_ioSection::-webkit-scrollbar-track{margin:6px 0}.JLsvgW_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.JLsvgW_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.JLsvgW_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.JLsvgW_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.JLsvgW_root[data-expandable]{cursor:pointer}.JLsvgW_root{align-items:center;min-width:0;height:24px;display:flex;position:relative;overflow:hidden}.JLsvgW_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 JLsvgW_hydra-bash-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes JLsvgW_hydra-bash-row-sweep{0%{left:-300px}90%,to{left:100%}}.JLsvgW_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}.JLsvgW_chevron{color:var(--dsw-alias-label-secondary)}.JLsvgW_iconIdle{opacity:1;transition:opacity .1s;display:inline-flex}.JLsvgW_chevronHover{opacity:0;margin:auto;transition:opacity .1s;position:absolute;inset:0}.JLsvgW_root:hover .JLsvgW_iconIdle{opacity:0}.JLsvgW_root:hover .JLsvgW_chevronHover{opacity:1}.JLsvgW_title{color:var(--dsw-alias-label-secondary);flex:none;font-size:14px;line-height:24px}.JLsvgW_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.JLsvgW_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}.JLsvgW_errorSummary{color:var(--dsw-alias-state-error-primary)}.JLsvgW_bodyWrap{flex-direction:column;display:flex}.JLsvgW_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}.JLsvgW_card:hover .JLsvgW_inspectButton,.JLsvgW_inspectButton:focus-visible{opacity:1}.JLsvgW_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.JLsvgW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
1383
+ const tagId = "@hydraharness/harness-client-ui-tool/bash-sample.module.css";
1384
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1385
+ const tag = document.createElement("style");
1386
+ tag.dataset.plugin = "@hydraharness/harness-client-ui-tool";
1387
+ tag.dataset.pluginCss = tagId;
1388
+ tag.textContent = css;
1389
+ document.head.appendChild(tag);
1390
+ }
1391
+ var bash_sample_module_css_default = {
1392
+ "bodyWrap": "JLsvgW_bodyWrap",
1393
+ "card": "JLsvgW_card",
1394
+ "chevron": "JLsvgW_chevron",
1395
+ "chevronHover": "JLsvgW_chevronHover",
1396
+ "errorSummary": "JLsvgW_errorSummary",
1397
+ "hydra-bash-row-sweep": "JLsvgW_hydra-bash-row-sweep",
1398
+ "iconIdle": "JLsvgW_iconIdle",
1399
+ "inspectButton": "JLsvgW_inspectButton",
1400
+ "ioCard": "JLsvgW_ioCard",
1401
+ "ioDivider": "JLsvgW_ioDivider",
1402
+ "ioLabel": "JLsvgW_ioLabel",
1403
+ "ioSection": "JLsvgW_ioSection",
1404
+ "ioText": "JLsvgW_ioText",
1405
+ "leading": "JLsvgW_leading",
1406
+ "root": "JLsvgW_root",
1407
+ "sep": "JLsvgW_sep",
1408
+ "summary": "JLsvgW_summary",
1409
+ "terminal": "JLsvgW_terminal",
1410
+ "title": "JLsvgW_title",
1411
+ "visuallyHidden": "JLsvgW_visuallyHidden"
1412
+ };
1413
+ //#endregion
1414
+ //#region lib/types/client/tool/toolviews/bash-sample.js
1415
+ function leadingFor(state) {
1416
+ switch (state) {
1417
+ case "error": return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.StateDot, { state: "error" });
1418
+ case "stopped": return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.StateDot, { state: "warning" });
1419
+ default: return (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconApiOutline14, { size: 14 });
1420
+ }
1421
+ }
1422
+ /** Visually hidden status — StateDot is aria-hidden; AT needs a text label. */
1423
+ function stateStatus(state, t) {
1424
+ switch (state) {
1425
+ case "running": return t("bash.running");
1426
+ case "error": return t("bash.failed");
1427
+ case "stopped": return t("bash.stopped");
1428
+ default: return null;
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Bash row: icon + Bash · {description} in the shared ToolRow chrome, the
1433
+ * whole row toggling the command's terminal or generic error card (ToolRow's unified
1434
+ * expand interaction, replicated locally per the registrant posture).
1435
+ */
1436
+ function BashRow({ toolName, block, sessionId, useSessions, inspect, t }) {
1437
+ const model = toolRowModel(toolName, block);
1438
+ const terminal = terminalCardModel(block, useSessions((list) => list.byId[sessionId]?.cwd));
1439
+ const state = model.state === "ok" && terminal !== null && terminalFailed(terminal) ? "error" : model.state;
1440
+ const status = stateStatus(state, t);
1441
+ const [expanded, setExpanded] = (0, react.useState)(false);
1442
+ const genericError = terminal === null && model.state === "error" && (model.body !== null || model.output !== null);
1443
+ const expandable = terminal !== null || genericError;
1444
+ const open = expanded && expandable;
1445
+ const failureLine = model.state === "error" ? model.errorSummary : null;
1446
+ const toggleExpand = () => {
1447
+ setExpanded((v) => !v);
1448
+ };
1449
+ const toggleFromKeyboard = (event) => {
1450
+ if (!expandable || event.key !== "Enter" && event.key !== " ") return;
1451
+ event.preventDefault();
1452
+ toggleExpand();
1453
+ };
1454
+ const leading = open ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_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", {
1455
+ className: bash_sample_module_css_default.iconIdle,
1456
+ children: leadingFor(state)
1457
+ }), (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconChevronDownOutline14, { className: clsx(bash_sample_module_css_default.chevron, bash_sample_module_css_default.chevronHover) })] }) : leadingFor(state);
1458
+ return (0, react_jsx_runtime.jsxs)("div", {
1459
+ className: bash_sample_module_css_default.card,
1460
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1461
+ className: bash_sample_module_css_default.root,
1462
+ "data-sample": "bash",
1463
+ "data-variant": "bash",
1464
+ "data-state": state,
1465
+ "data-expandable": expandable || void 0,
1466
+ role: expandable ? "button" : void 0,
1467
+ tabIndex: expandable ? 0 : void 0,
1468
+ "aria-expanded": expandable ? open : void 0,
1469
+ onClick: expandable ? toggleExpand : void 0,
1470
+ onKeyDown: expandable ? toggleFromKeyboard : void 0,
1471
+ children: [
1472
+ (0, react_jsx_runtime.jsx)("span", {
1473
+ className: bash_sample_module_css_default.leading,
1474
+ children: leading
1475
+ }),
1476
+ status !== null && (0, react_jsx_runtime.jsx)("span", {
1477
+ className: bash_sample_module_css_default.visuallyHidden,
1478
+ children: status
1479
+ }),
1480
+ (0, react_jsx_runtime.jsx)("span", {
1481
+ className: bash_sample_module_css_default.title,
1482
+ children: model.title
1483
+ }),
1484
+ (0, react_jsx_runtime.jsx)("span", {
1485
+ className: bash_sample_module_css_default.sep,
1486
+ "aria-hidden": true
1487
+ }),
1488
+ (0, react_jsx_runtime.jsx)("span", {
1489
+ className: clsx(bash_sample_module_css_default.summary, failureLine !== null && bash_sample_module_css_default.errorSummary),
1490
+ children: failureLine ?? terminal?.description ?? model.summary
1491
+ })
1492
+ ]
1493
+ }), open && (0, react_jsx_runtime.jsxs)("div", {
1494
+ className: bash_sample_module_css_default.bodyWrap,
1495
+ children: [terminal !== null ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.TerminalBlock, {
1496
+ ...terminal.card,
1497
+ maxLines: Infinity,
1498
+ labels: terminalBlockLabels(t),
1499
+ className: bash_sample_module_css_default.terminal
1500
+ }) : (0, react_jsx_runtime.jsxs)("div", {
1501
+ className: bash_sample_module_css_default.ioCard,
1502
+ children: [
1503
+ model.body !== null && (0, react_jsx_runtime.jsxs)("div", {
1504
+ className: bash_sample_module_css_default.ioSection,
1505
+ children: [(0, react_jsx_runtime.jsx)("span", {
1506
+ className: bash_sample_module_css_default.ioLabel,
1507
+ children: "IN"
1508
+ }), (0, react_jsx_runtime.jsx)("span", {
1509
+ className: bash_sample_module_css_default.ioText,
1510
+ children: model.body
1511
+ })]
1512
+ }),
1513
+ model.body !== null && model.output !== null && (0, react_jsx_runtime.jsx)("span", {
1514
+ className: bash_sample_module_css_default.ioDivider,
1515
+ "aria-hidden": true
1516
+ }),
1517
+ model.output !== null && (0, react_jsx_runtime.jsxs)("div", {
1518
+ className: bash_sample_module_css_default.ioSection,
1519
+ children: [(0, react_jsx_runtime.jsx)("span", {
1520
+ className: bash_sample_module_css_default.ioLabel,
1521
+ children: "OUT"
1522
+ }), (0, react_jsx_runtime.jsx)("span", {
1523
+ className: bash_sample_module_css_default.ioText,
1524
+ "data-error": true,
1525
+ children: model.output
1526
+ })]
1527
+ })
1528
+ ]
1529
+ }), inspect !== void 0 && (0, react_jsx_runtime.jsxs)("button", {
1530
+ type: "button",
1531
+ className: bash_sample_module_css_default.inspectButton,
1532
+ onClick: inspect,
1533
+ children: [(0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconInspectOutline12, {}), "Inspect"]
1534
+ })]
1535
+ })]
1536
+ });
1537
+ }
1538
+ /**
1539
+ * The sample as a plain registrant plugin. Slot injection follows the chat
1540
+ * toolview declaration across independent activation and reload lifetimes.
1541
+ */
1542
+ const bashToolviewSample = {
1543
+ name: "bash-toolview-sample",
1544
+ inject: ["slots"],
1545
+ /**
1546
+ * Register the bash row into the Tool-owned keyed view slot.
1547
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1548
+ */
1549
+ apply(ctx) {
1550
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1551
+ name: "tool.call.toolview",
1552
+ key: "bash",
1553
+ locale: CONVERSATION_NS
1554
+ }, BashRow));
1555
+ }
1556
+ };
1557
+ //#endregion
1558
+ //#region lib/types/client/tool/toolviews/file-mutation-row.js
1559
+ /**
1560
+ * File-mutation row: icon + {Edit,Write} · {path} in the shared ToolRow chrome,
1561
+ * with the applied diff as the row's collapsed-by-default card body. The
1562
+ * summary is a path link (a file tool's interaction); the host's `openFile`
1563
+ * resolves it against the session cwd, so this passes the tool's own path
1564
+ * verbatim. An errored mutation has no diff card, so ToolRow surfaces the
1565
+ * model-facing error text through its Output section and its first line in the
1566
+ * collapsed summary instead.
1567
+ */
1568
+ function FileMutationRow({ toolName, block, cwd, home, openFile, inspect, t }) {
1569
+ const model = toolRowModel(toolName, block, cwd, home);
1570
+ const diff = diffCardModel(block);
1571
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1572
+ t,
1573
+ variant: model.variant,
1574
+ toolName,
1575
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconEditOutline16, { size: 14 }),
1576
+ title: model.title,
1577
+ summary: model.summary,
1578
+ body: null,
1579
+ output: model.output,
1580
+ errorSummary: model.errorSummary,
1581
+ diff,
1582
+ state: model.state,
1583
+ filePath: model.filePath,
1584
+ onOpenFile: openFile,
1585
+ inspect
1586
+ });
1587
+ }
1588
+ /**
1589
+ * The file-mutation rows as a plain registrant plugin following the chat
1590
+ * toolview declaration across independent activation and reload lifetimes.
1591
+ */
1592
+ const fileMutationToolview = {
1593
+ name: "file-mutation-toolview",
1594
+ inject: ["slots"],
1595
+ /**
1596
+ * Register the file-mutation row into the Tool-owned keyed view slot
1597
+ * under both mutation tool names.
1598
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1599
+ */
1600
+ apply(ctx) {
1601
+ ctx.slots.inject("tool.call.toolview", function* () {
1602
+ yield ctx.slots.register({
1603
+ name: "tool.call.toolview",
1604
+ key: "edit",
1605
+ locale: CONVERSATION_NS
1606
+ }, FileMutationRow);
1607
+ yield ctx.slots.register({
1608
+ name: "tool.call.toolview",
1609
+ key: "write",
1610
+ locale: CONVERSATION_NS
1611
+ }, FileMutationRow);
1612
+ });
1613
+ }
1614
+ };
1615
+ //#endregion
1616
+ //#region lib/types/client/tool/toolviews/read-row.js
1617
+ /**
1618
+ * Read row: icon + Read · {path} in the shared ToolRow chrome, with the file's
1619
+ * read card as the row's collapsed-by-default card body. The summary path is an
1620
+ * openable host link when the row names a single file.
1621
+ */
1622
+ function ReadRow({ toolName, block, cwd, home, openFile, inspect, t }) {
1623
+ const model = toolRowModel(toolName, block, cwd, home);
1624
+ const read = readCardModel(block, cwd, home);
1625
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1626
+ t,
1627
+ variant: model.variant,
1628
+ toolName,
1629
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
1630
+ title: model.title,
1631
+ summary: model.summary,
1632
+ body: null,
1633
+ output: model.output,
1634
+ errorSummary: model.errorSummary,
1635
+ read,
1636
+ state: model.state,
1637
+ filePath: model.filePath,
1638
+ onOpenFile: openFile,
1639
+ inspect
1640
+ });
1641
+ }
1642
+ /**
1643
+ * The read row as a plain registrant plugin following the atomic Tool-view
1644
+ * declaration across independent activation and reload lifetimes.
1645
+ */
1646
+ const readToolview = {
1647
+ name: "read-toolview",
1648
+ inject: ["slots"],
1649
+ /**
1650
+ * Register the read row into the Tool-owned keyed view slot.
1651
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1652
+ */
1653
+ apply(ctx) {
1654
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1655
+ name: "tool.call.toolview",
1656
+ key: "read",
1657
+ locale: CONVERSATION_NS
1658
+ }, ReadRow));
1659
+ }
1660
+ };
1661
+ //#endregion
1662
+ //#region lib/types/client/tool/toolviews/search-row.js
1663
+ const SEARCH_TITLES = {
1664
+ grep: "Grep",
1665
+ glob: "Glob"
1666
+ };
1667
+ /**
1668
+ * Search row: icon + Grep/Glob · {summary} in the shared ToolRow chrome, with the
1669
+ * completed search's card as the row's collapsed-by-default card body (a capped
1670
+ * search's recovery footer rides below it, inside ToolRow). Registered under
1671
+ * both `grep` and `glob`; the derived model's `kind` decides the card shape. A
1672
+ * settled call with no search card surfaces its model-facing text through
1673
+ * ToolRow's Output section, since the keyed SearchRow owns this render slot.
1674
+ */
1675
+ function SearchRow({ toolName, block, inspect, t }) {
1676
+ const model = toolRowModel(toolName, block);
1677
+ const search = searchCardModel(block);
1678
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1679
+ t,
1680
+ variant: model.variant,
1681
+ toolName,
1682
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconSearchOutline16, { size: 14 }),
1683
+ title: SEARCH_TITLES[toolName] ?? model.title,
1684
+ summary: search?.title ?? model.summary,
1685
+ body: null,
1686
+ output: model.output,
1687
+ errorSummary: model.errorSummary,
1688
+ search,
1689
+ state: model.state,
1690
+ inspect
1691
+ });
1692
+ }
1693
+ /**
1694
+ * The search view follows the atomic Tool-view declaration across activation
1695
+ * and reload. One component registers under both keys because `grep` and
1696
+ * `glob` are the same visual object discriminated by the result view's `kind`.
1697
+ */
1698
+ const searchToolview = {
1699
+ name: "search-toolview",
1700
+ inject: ["slots"],
1701
+ /**
1702
+ * Register the search row into the Tool-owned keyed view slot under both
1703
+ * the `grep` and `glob` tool names.
1704
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1705
+ */
1706
+ apply(ctx) {
1707
+ ctx.slots.inject("tool.call.toolview", function* () {
1708
+ yield ctx.slots.register({
1709
+ name: "tool.call.toolview",
1710
+ key: "grep",
1711
+ locale: CONVERSATION_NS
1712
+ }, SearchRow);
1713
+ yield ctx.slots.register({
1714
+ name: "tool.call.toolview",
1715
+ key: "glob",
1716
+ locale: CONVERSATION_NS
1717
+ }, SearchRow);
1718
+ });
1719
+ }
1720
+ };
1721
+ //#endregion
1722
+ //#region lib/types/client/tool/toolviews/subagent-row.js
1723
+ /**
1724
+ * subagent toolview: subagent-flavored summary row replacing the generic
1725
+ * "Tool call" card, registered into the keyed 'tool.call.toolview'
1726
+ * slot like todo-row and ask-question-row.
1727
+ */
1728
+ function parseSubagentArgs(argsRaw) {
1729
+ try {
1730
+ const parsed = JSON.parse(argsRaw);
1731
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
1732
+ } catch {
1733
+ return null;
1734
+ }
1735
+ }
1736
+ /** One-line subagent delegation row (the whole row toggles the call's Input/Output sections). */
1737
+ function SubagentRow({ toolName, block, inspect, t }) {
1738
+ const model = toolRowModel(toolName, block);
1739
+ const parsed = parseSubagentArgs(("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "");
1740
+ const firstPromptLine = parsed?.prompt ? parsed.prompt.split("\n")[0] ?? "" : "";
1741
+ const description = parsed?.description || firstPromptLine || model.summary;
1742
+ const isBackground = parsed?.run_in_background === true;
1743
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1744
+ t,
1745
+ variant: "code",
1746
+ toolName,
1747
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconBranchOutline16, { size: 14 }),
1748
+ title: "Subagent",
1749
+ summary: description,
1750
+ summarySuffix: isBackground ? "(background)" : null,
1751
+ body: parsed?.prompt ?? model.body,
1752
+ output: model.output,
1753
+ errorSummary: model.errorSummary,
1754
+ state: model.state,
1755
+ inspect
1756
+ });
1757
+ }
1758
+ /**
1759
+ * The subagent row as a plain registrant plugin following the atomic Tool-view
1760
+ * declaration across independent activation and reload lifetimes.
1761
+ */
1762
+ const subagentToolview = {
1763
+ name: "subagent-toolview",
1764
+ inject: ["slots"],
1765
+ /**
1766
+ * Register the subagent row into the Tool-owned keyed view slot.
1767
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1768
+ */
1769
+ apply(ctx) {
1770
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1771
+ name: "tool.call.toolview",
1772
+ key: "subagent",
1773
+ locale: CONVERSATION_NS
1774
+ }, SubagentRow));
1775
+ }
1776
+ };
1777
+ //#endregion
1778
+ //#region lib/types/client/tool/toolviews/plan-summary.js
1779
+ /**
1780
+ * Pure plan derivation for the todo_write row's one-line summary. Several items
1781
+ * may be `in_progress` at once — parallel work runs concurrent tasks, so a
1782
+ * summary built from one active item would silently drop the rest. The plan
1783
+ * strip header derives its own counts inline and shares nothing with this, so
1784
+ * this stays inside the toolviews domain rather than in `contract/` (the
1785
+ * inter-domain face).
1786
+ * @module
1787
+ */
1788
+ /**
1789
+ * Derive the counts and the active summary from a whole-list snapshot. It names
1790
+ * the first `in_progress` item and counts the remaining active ones, so a
1791
+ * parallel plan reports how many tasks are running rather than naming one and
1792
+ * hiding the others. `activeContent` is null when nothing is in progress, or
1793
+ * when the first active item's content is missing, mistyped, or blank once
1794
+ * trimmed — the tool's own rule for usable content, applied here because a
1795
+ * rejected call keeps its args verbatim. The row then renders the counts alone
1796
+ * rather than falling back to the generic tool summary: the counts are already
1797
+ * known to be good, and the active-item clause is the only part an unusable
1798
+ * name costs.
1799
+ * @param todos - the whole list, in model order.
1800
+ * @returns the done/total counts and the two summary halves.
1801
+ */
1802
+ function planSummary(todos) {
1803
+ const active = todos.filter((t) => t.status === "in_progress");
1804
+ const first = active[0]?.content;
1805
+ const named = typeof first === "string" && first.trim() !== "";
1806
+ return {
1807
+ done: todos.filter((t) => t.status === "completed").length,
1808
+ total: todos.length,
1809
+ activeContent: named ? first : null,
1810
+ activeExtra: named ? active.length - 1 : 0
1811
+ };
1812
+ }
1813
+ //#endregion
1814
+ //#region lib/types/client/tool/toolviews/todo-row.js
1815
+ function isItem(value) {
1816
+ return typeof value === "object" && value !== null;
1817
+ }
1818
+ function summarize(argsRaw, t) {
1819
+ let parsed;
1820
+ try {
1821
+ parsed = JSON.parse(argsRaw);
1822
+ } catch {
1823
+ return null;
1824
+ }
1825
+ if (typeof parsed !== "object" || parsed === null) return null;
1826
+ const todos = parsed.todos;
1827
+ if (!Array.isArray(todos) || !todos.every(isItem)) return null;
1828
+ const { done, total, activeContent, activeExtra } = planSummary(todos);
1829
+ const head = t("todo.completed", {
1830
+ done,
1831
+ total
1832
+ });
1833
+ return {
1834
+ text: activeContent === null ? head : `${head} · ${activeContent}`,
1835
+ extra: activeExtra
1836
+ };
1837
+ }
1838
+ /** One-line plan update row (the whole row toggles the call's Input/Output
1839
+ * sections, ToolRow's unified expand). Non-ok execution states keep the
1840
+ * shared row's dot semantics — a cancelled call wrote no todo/write, so it
1841
+ * must not read as a completed update. */
1842
+ function TodoRow({ toolName, block, inspect, t }) {
1843
+ const model = toolRowModel(toolName, block);
1844
+ const summary = summarize(("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "", t) ?? {
1845
+ text: model.summary,
1846
+ extra: 0
1847
+ };
1848
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1849
+ t,
1850
+ variant: model.variant,
1851
+ toolName,
1852
+ icon: (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconChecklistOutline14, {}),
1853
+ title: t("todo.rowTitle"),
1854
+ summary: summary.text,
1855
+ summarySuffix: summary.extra > 0 ? `+${summary.extra}` : null,
1856
+ body: model.body,
1857
+ output: model.output,
1858
+ errorSummary: model.errorSummary,
1859
+ state: model.state,
1860
+ inspect
1861
+ });
1862
+ }
1863
+ /**
1864
+ * The todo row as a plain registrant plugin following the atomic Tool-view
1865
+ * declaration across independent activation and reload lifetimes.
1866
+ */
1867
+ const todoToolview = {
1868
+ name: "todo-toolview",
1869
+ inject: ["slots"],
1870
+ /**
1871
+ * Register the todo row into the Tool-owned keyed view slot.
1872
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1873
+ */
1874
+ apply(ctx) {
1875
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
1876
+ name: "tool.call.toolview",
1877
+ key: "todo_write",
1878
+ locale: CONVERSATION_NS
1879
+ }, TodoRow));
1880
+ }
1881
+ };
1882
+ //#endregion
1883
+ //#region lib/types/client/tool/toolviews/web-row.js
1884
+ /** web_fetch reads one URL; web_search queries. Titles are figma literals. */
1885
+ const WEB_TITLES = {
1886
+ web_search: "Search",
1887
+ web_fetch: "Fetch"
1888
+ };
1889
+ /**
1890
+ * Web row: icon + Search/Fetch · {summary} in the shared ToolRow chrome, with
1891
+ * the completed retrieval's web card as the row's collapsed-by-default card
1892
+ * body. The row discriminates on `toolName` only to pick its icon and title.
1893
+ */
1894
+ function WebRow({ toolName, block, inspect, t }) {
1895
+ const model = toolRowModel(toolName, block);
1896
+ const web = webCardModel(block);
1897
+ const icon = toolName === "web_fetch" ? (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconBrowseOutline16, { size: 14 }) : (0, react_jsx_runtime.jsx)(_hydraharness_harness_client_ui_primitives.IconGlobeOutline14, { size: 14 });
1898
+ return (0, react_jsx_runtime.jsx)(ToolRow, {
1899
+ t,
1900
+ variant: model.variant,
1901
+ toolName,
1902
+ icon,
1903
+ title: WEB_TITLES[toolName] ?? model.title,
1904
+ summary: model.summary,
1905
+ body: null,
1906
+ output: model.output,
1907
+ errorSummary: model.errorSummary,
1908
+ web,
1909
+ state: model.state,
1910
+ inspect
1911
+ });
1912
+ }
1913
+ /**
1914
+ * The web rows follow the atomic Tool-view declaration across activation and
1915
+ * reload. One WebRow component registers under both web tool names.
1916
+ */
1917
+ const webToolview = {
1918
+ name: "web-toolview",
1919
+ inject: ["slots"],
1920
+ /**
1921
+ * Register the web row under both web tool names' keyed toolview holes.
1922
+ * @param ctx - registrant context (disposal rides ctx.effect inside slots.register).
1923
+ */
1924
+ apply(ctx) {
1925
+ ctx.slots.inject("tool.call.toolview", function* () {
1926
+ yield ctx.slots.register({
1927
+ name: "tool.call.toolview",
1928
+ key: "web_search",
1929
+ locale: CONVERSATION_NS
1930
+ }, WebRow);
1931
+ yield ctx.slots.register({
1932
+ name: "tool.call.toolview",
1933
+ key: "web_fetch",
1934
+ locale: CONVERSATION_NS
1935
+ }, WebRow);
1936
+ });
1937
+ }
1938
+ };
1939
+ //#endregion
1940
+ //#region lib/types/client/apply.js
1941
+ /** Required services: the slot registry and the Host description used for POSIX `~`. */
1942
+ const inject = ["slots", "connection"];
1943
+ /**
1944
+ * Mount the whole-Tool renderers and built-in atomic Tool registrations.
1945
+ * @param ctx - Client root context.
1946
+ */
1947
+ function apply(ctx) {
1948
+ const connection = ctx.get("connection");
1949
+ const toolInject = () => ({ hooks: { hostDescription: connection.hostDescription } });
1950
+ const loadModelName = async (provider, model) => {
1951
+ const response = await connection.api.llm.models({});
1952
+ if (!response.result.ok) return void 0;
1953
+ const matches = response.result.value.groups.filter((group) => provider === void 0 || group.id === provider).flatMap((group) => group.models.filter((entry) => entry.id === model));
1954
+ return matches.length === 1 ? matches[0]?.name : void 0;
1955
+ };
1956
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1957
+ name: "conversation.chat.node",
1958
+ key: "tool-call",
1959
+ locale: CONVERSATION_NS,
1960
+ children: {
1961
+ "tool.call.toolview": {
1962
+ kind: "keyed",
1963
+ scope: "session"
1964
+ },
1965
+ "tool.call.review": {
1966
+ kind: "list",
1967
+ scope: "session"
1968
+ }
1969
+ },
1970
+ inject: toolInject
1971
+ }, ToolCallTree));
1972
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1973
+ name: "conversation.chat.node",
1974
+ key: "media-generation",
1975
+ locale: CONVERSATION_NS,
1976
+ inject: () => ({ loadModelName })
1977
+ }, MediaGeneration));
1978
+ ctx.slots.inject("conversation.details.tool", () => ctx.slots.register({
1979
+ name: "conversation.details.tool",
1980
+ locale: CONVERSATION_NS,
1981
+ inject: toolInject
1982
+ }, ToolDetails));
1983
+ ctx.plugin(bashToolviewSample);
1984
+ ctx.plugin(readToolview);
1985
+ ctx.plugin(fileMutationToolview);
1986
+ ctx.plugin(searchToolview);
1987
+ ctx.plugin(webToolview);
1988
+ ctx.plugin(todoToolview);
1989
+ ctx.plugin(askQuestionToolview);
1990
+ ctx.plugin(subagentToolview);
1991
+ }
1992
+ //#endregion
1993
+ exports.apply = apply;
1994
+ exports.inject = inject;
1995
+ return module.exports;
1996
+ }
1997
+ });
1998
+
1999
+ //# sourceMappingURL=client.js.map