@prettier-ai/dsh-client-ui-tool 0.1.2-alpha.1 → 0.1.2-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-tool/README.md
5
- README.md: 773a93801ebc214e2d5c94d52864f5c5dd887100
6
- README.zh.md: 88df08d7b5b7d5d3978d90fd4df4cbbb2efeb1fa
5
+ README.md: 895274748d8ee5b7bece16e5b76836966cc52663
6
+ README.zh.md: f4cf11cabdb94f45c9b11e7f7538ad5042a791e6
package/README.md CHANGED
@@ -61,7 +61,7 @@ The package realizes one dispatch rule: atomic Tool views are keyed by wire Tool
61
61
 
62
62
  ### Details and cards
63
63
 
64
- The package fills `conversation.details.tool` with `ToolDetails`. Row and Details renderers share one pure card model for each terminal, read, diff, search, and web card. These models validate raw call arguments, result content, failure state, persisted metadata, Code Dispatch `parentCallId`, and Session path facts. Unsupported or malformed inputs use flattened Tool result text. Card-specific limits and fallback rules remain in the owning [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md), [diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.md), [read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md), [search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.md), [web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.md), and [question](../../../.agents/notes/implemented/feature/2026-07-29-ask-question-web-presentation.md) notes.
64
+ The package fills `conversation.details.tool` with `ToolDetails`. Row and Details renderers share one pure card model for each terminal, read, diff, search, and web card. These models validate raw call arguments, result content, failure state, persisted metadata, Code Dispatch `parentCallId`, and Session path facts. Generic rows retain the original `argsRaw` reference and format their input body only while it is expanded; structured cards skip generic-body formatting. Unsupported or malformed inputs use flattened Tool result text. Card-specific limits and fallback rules remain in the owning [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.md), [diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.md), [read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.md), [search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.md), [web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.md), and [question](../../../.agents/notes/implemented/feature/2026-07-29-ask-question-web-presentation.md) notes.
65
65
 
66
66
  </details>
67
67
 
package/README.zh.md CHANGED
@@ -61,7 +61,7 @@ owner 载荷为 `ToolCallOwnerProps`:`callId`、`toolName`、冻结的 `block`
61
61
 
62
62
  ### 详情与卡片
63
63
 
64
- 本包通过 `ToolDetails` 填充 `conversation.details.tool`。行 renderer 与 Details renderer 分别为 terminal、read、diff、search 和 web 卡片复用同一个纯 card model。这些 model 校验原始调用参数、结果内容、失败状态、持久 metadata、Code Dispatch `parentCallId` 与 Session 路径事实。不受支持或格式错误的输入使用压平的工具结果文本。各类卡片的上限与 fallback 规则仍由对应的 [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md)、[diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.zh.md)、[read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md)、[search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.zh.md)、[web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.zh.md) 与 [question](../../../.agents/notes/implemented/feature/2026-07-29-ask-question-web-presentation.zh.md) 笔记负责。
64
+ 本包通过 `ToolDetails` 填充 `conversation.details.tool`。行 renderer 与 Details renderer 分别为 terminal、read、diff、search 和 web 卡片复用同一个纯 card model。这些 model 校验原始调用参数、结果内容、失败状态、持久 metadata、Code Dispatch `parentCallId` 与 Session 路径事实。Generic row 保留原始 `argsRaw` 引用,只在展开期间格式化 input body;结构化卡片跳过 generic body 格式化。不受支持或格式错误的输入使用压平的工具结果文本。各类卡片的上限与 fallback 规则仍由对应的 [terminal](../../../.agents/notes/implemented/feature/2026-07-28-web-terminal-card.zh.md)、[diff](../../../.agents/notes/implemented/feature/2026-07-30-web-diff-card.zh.md)、[read](../../../.agents/notes/implemented/feature/2026-07-30-web-read-card-frontend.zh.md)、[search](../../../.agents/notes/implemented/feature/2026-07-30-web-search-card.zh.md)、[web](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card-frontend.zh.md) 与 [question](../../../.agents/notes/implemented/feature/2026-07-29-ask-question-web-presentation.zh.md) 笔记负责。
65
65
 
66
66
  </details>
67
67
 
package/lib/client.js CHANGED
@@ -186,7 +186,13 @@ window.__ModuleLoader__.load({
186
186
  const picked = pickString(parsed, FILE_PATH_KEYS);
187
187
  return picked === void 0 ? void 0 : firstLine(picked);
188
188
  }
189
- function deriveBody(variant, argsRaw) {
189
+ /**
190
+ * Format one argument payload when its generic input body becomes visible.
191
+ * @param variant - row presentation selected for the Tool name.
192
+ * @param argsRaw - original argument JSON or incomplete raw text.
193
+ * @returns display body, or null for empty input.
194
+ */
195
+ function formatToolBody(variant, argsRaw) {
190
196
  if (argsRaw === "") return null;
191
197
  const parsed = parseArgs(argsRaw);
192
198
  if (parsed === void 0) return argsRaw;
@@ -214,12 +220,13 @@ window.__ModuleLoader__.load({
214
220
  const summary = variant === "others" && toolName !== "" && toolTitleKey === void 0 ? `${toolName} · ${base}` : base;
215
221
  const output = done ? resultText(block) || null : null;
216
222
  const errorSummary = state === "error" && output !== null ? firstLine(output) : null;
223
+ const bodyRaw = argsRaw === "" ? null : argsRaw;
217
224
  return {
218
225
  variant,
219
226
  titleKey: toolTitleKey ?? VARIANT_TITLE_KEYS[variant],
220
227
  summary,
221
228
  filePath: deriveFilePath(variant, argsRaw),
222
- body: deriveBody(variant, argsRaw),
229
+ bodyRaw,
223
230
  output,
224
231
  errorSummary,
225
232
  state
@@ -1035,7 +1042,7 @@ window.__ModuleLoader__.load({
1035
1042
  }
1036
1043
  //#endregion
1037
1044
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-tool/src/client/tool/components/ToolRow.module.css.mjs
1038
- const css$3 = ".RAJz1a_root{flex-direction:column;display:flex}.RAJz1a_row{position:relative;overflow:hidden}.RAJz1a_root[data-state=running] .RAJz1a_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite RAJz1a_dsh-tool-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes RAJz1a_dsh-tool-row-sweep{0%{left:-300px}90%,to{left:100%}}.RAJz1a_leading{flex-shrink:0}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_leading,.RAJz1a_root[data-tool^=cordis_] .RAJz1a_title{color:var(--dsw-alias-state-business-primary)}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_title{font-weight:500}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_sep{background:var(--dsw-alias-state-business-primary)}.RAJz1a_chevron{color:var(--dsw-alias-label-secondary)}.RAJz1a_title{font-weight:400}.RAJz1a_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.RAJz1a_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}.RAJz1a_summarySuffix{white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;margin-left:4px}.RAJz1a_fileLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:inherit;text-align:left;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color: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;overflow:hidden}.RAJz1a_fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}.RAJz1a_errorSummary{color:var(--dsw-alias-state-error-primary)}.RAJz1a_bodyWrap{flex-direction:column;display:flex}.RAJz1a_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}.RAJz1a_root:hover .RAJz1a_inspectButton,.RAJz1a_inspectButton:focus-visible{opacity:1}.RAJz1a_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.RAJz1a_bodyScroll{max-height:260px;overflow-y:auto}.RAJz1a_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}.RAJz1a_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.RAJz1a_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.RAJz1a_ioSection::-webkit-scrollbar-track{margin:6px 0}.RAJz1a_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.RAJz1a_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.RAJz1a_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.RAJz1a_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.RAJz1a_codeBody,.RAJz1a_terminalBody,.RAJz1a_diffBody,.RAJz1a_readBody,.RAJz1a_searchBody,.RAJz1a_webBody{margin:4px 0 4px 4px}.RAJz1a_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}.RAJz1a_codeBody{--dsl-code-block-content-font:var(--dsw-font-markdown-code-block-small)}.RAJz1a_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:1px solid var(--dsw-alias-border-l1)}.RAJz1a_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
1045
+ const css$3 = ".RAJz1a_root{flex-direction:column;display:flex}.RAJz1a_row{position:relative;overflow:hidden}.RAJz1a_root[data-state=running] .RAJz1a_row:after{content:\"\";background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite RAJz1a_dsh-tool-row-sweep;position:absolute;top:0;bottom:0;left:0}@keyframes RAJz1a_dsh-tool-row-sweep{0%{left:-300px}90%,to{left:100%}}.RAJz1a_leading{flex-shrink:0}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_leading,.RAJz1a_root[data-tool^=cordis_] .RAJz1a_title{color:var(--dsw-alias-state-business-primary)}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_title{font-weight:500}.RAJz1a_root[data-tool^=cordis_] .RAJz1a_sep{background:var(--dsw-alias-state-business-primary)}.RAJz1a_chevron{color:var(--dsw-alias-label-secondary)}.RAJz1a_title{font-weight:400}.RAJz1a_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.RAJz1a_summary{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:auto;overflow:hidden}.RAJz1a_summarySuffix{white-space:nowrap;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);flex:none;margin-left:4px}.RAJz1a_diffStat{font-family:var(--ds-font-family-code);font-size:calc(var(--dsh-content-font-size-secondary,13px) - 2px);color:var(--dsw-alias-label-caption);margin-left:10px;transform:translateY(.5px)}.RAJz1a_fileLink{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:inherit;text-align:left;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-secondary);text-decoration:underline dotted;text-decoration-color:var(--dsw-alias-label-tertiary);text-underline-offset:3px;cursor:pointer;background:0 0;border:none;flex:0 auto;margin:0;padding:0;text-decoration-thickness:1px;overflow:hidden}.RAJz1a_fileLink:hover{color:var(--dsw-alias-label-primary);text-decoration-color:currentColor}.RAJz1a_errorSummary{color:var(--dsw-alias-state-error-primary)}.RAJz1a_bodyWrap{flex-direction:column;display:flex}.RAJz1a_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}.RAJz1a_root:hover .RAJz1a_inspectButton,.RAJz1a_inspectButton:focus-visible{opacity:1}.RAJz1a_inspectButton:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.RAJz1a_bodyScroll{max-height:260px;overflow-y:auto}.RAJz1a_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}.RAJz1a_ioSection{grid-template-columns:max-content 1fr;align-items:baseline;column-gap:14px;max-height:150px;padding:12px 16px;display:grid;overflow-y:auto}.RAJz1a_ioSection::-webkit-scrollbar-thumb{background-clip:padding-box;border:2px solid #0000;border-radius:6px}.RAJz1a_ioSection::-webkit-scrollbar-track{margin:6px 0}.RAJz1a_ioLabel{color:var(--dsw-alias-label-caption);align-self:start;position:sticky;top:0}.RAJz1a_ioDivider{background:var(--dsw-alias-border-l2);flex:none;height:1px}.RAJz1a_ioText{white-space:pre-wrap;word-break:break-word;min-width:0;color:var(--dsw-alias-label-secondary)}.RAJz1a_ioText[data-error]{color:var(--dsw-alias-state-error-primary)}.RAJz1a_codeBody,.RAJz1a_terminalBody,.RAJz1a_diffBody,.RAJz1a_readBody,.RAJz1a_searchBody,.RAJz1a_webBody{margin:4px 0 4px 4px}.RAJz1a_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}.RAJz1a_codeBody{--dsl-code-block-content-font:var(--dsw-font-markdown-code-block-small)}.RAJz1a_terminalBody{--dsl-terminal-font:var(--dsw-font-markdown-code-block-small);--dsl-terminal-line-height:18px;--dsl-terminal-output-max-height:224px;border:1px solid var(--dsw-alias-border-l1)}.RAJz1a_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
1039
1046
  const tagId$3 = "@prettier-ai/dsh-client-ui-tool/ToolRow.module.css";
1040
1047
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1041
1048
  const tag = document.createElement("style");
@@ -1050,6 +1057,7 @@ window.__ModuleLoader__.load({
1050
1057
  "chevron": "RAJz1a_chevron",
1051
1058
  "codeBody": "RAJz1a_codeBody",
1052
1059
  "diffBody": "RAJz1a_diffBody",
1060
+ "diffStat": "RAJz1a_diffStat",
1053
1061
  "dsh-tool-row-sweep": "RAJz1a_dsh-tool-row-sweep",
1054
1062
  "errorSummary": "RAJz1a_errorSummary",
1055
1063
  "fileLink": "RAJz1a_fileLink",
@@ -1094,7 +1102,7 @@ window.__ModuleLoader__.load({
1094
1102
  default: return null;
1095
1103
  }
1096
1104
  }
1097
- function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, body, output, askQuestion, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect }) {
1105
+ function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, bodyRaw, output, askQuestion, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect }) {
1098
1106
  const [expanded, setExpanded] = (0, react.useState)(false);
1099
1107
  const terminalLabels = (0, react.useMemo)(() => terminalBlockLabels(t), [t]);
1100
1108
  const diffLabels = (0, react.useMemo)(() => diffBlockLabels(t), [t]);
@@ -1108,12 +1116,24 @@ window.__ModuleLoader__.load({
1108
1116
  const webBody = web ?? null;
1109
1117
  const askQuestionBody = askQuestion ?? null;
1110
1118
  const outputText = output ?? null;
1111
- const expandable = body !== null || outputText !== null || (askQuestionBody ?? terminalBody ?? diffBody ?? readBody ?? searchBody ?? webBody) !== null;
1119
+ const card = askQuestionBody ?? terminalBody ?? diffBody ?? readBody ?? searchBody ?? webBody;
1120
+ const expandable = bodyRaw != null || outputText !== null || card !== null;
1112
1121
  const open = expanded && expandable;
1122
+ const bodyText = (0, react.useMemo)(() => open && card === null && bodyRaw != null ? formatToolBody(variant, bodyRaw) : null, [
1123
+ bodyRaw,
1124
+ card,
1125
+ open,
1126
+ variant
1127
+ ]);
1113
1128
  const status = stateStatus$1(state, t);
1114
1129
  const failureLine = state === "error" ? errorSummary ?? null : null;
1115
1130
  const summaryText = failureLine ?? terminalBody?.description ?? summary;
1116
- const suffix = failureLine === null ? summarySuffix ?? null : null;
1131
+ const diffStat = (0, react.useMemo)(() => {
1132
+ if (diffBody === null) return null;
1133
+ const { added, removed } = (0, _prettier_ai_dsh_client_ui_primitives.diffTotals)(diffBody.card.diffs);
1134
+ return `+${added} -${removed}`;
1135
+ }, [diffBody]);
1136
+ const suffix = failureLine === null ? summarySuffix ?? diffStat : null;
1117
1137
  const fileLink = filePath !== void 0 && onOpenFile !== void 0 && failureLine === null;
1118
1138
  const toggleExpand = () => {
1119
1139
  setExpanded((v) => !v);
@@ -1125,7 +1145,7 @@ window.__ModuleLoader__.load({
1125
1145
  const fileLinkKeyDown = (event) => {
1126
1146
  if (event.key === "Enter" || event.key === " ") event.stopPropagation();
1127
1147
  };
1128
- const cardBody = variant === "code" ? null : body;
1148
+ const cardBody = variant === "code" ? null : bodyText;
1129
1149
  return (0, react_jsx_runtime.jsxs)("div", {
1130
1150
  className: ToolRow_module_css_default.root,
1131
1151
  "data-variant": variant,
@@ -1162,7 +1182,7 @@ window.__ModuleLoader__.load({
1162
1182
  children: summaryText
1163
1183
  }),
1164
1184
  suffix !== null && (0, react_jsx_runtime.jsx)("span", {
1165
- className: ToolRow_module_css_default.summarySuffix,
1185
+ className: clsx(ToolRow_module_css_default.summarySuffix, suffix === diffStat && ToolRow_module_css_default.diffStat),
1166
1186
  children: suffix
1167
1187
  })
1168
1188
  ] }),
@@ -1195,10 +1215,10 @@ window.__ModuleLoader__.load({
1195
1215
  ...webBody,
1196
1216
  labels: webLabels,
1197
1217
  className: ToolRow_module_css_default.webBody
1198
- }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [variant === "code" && body !== null && (0, react_jsx_runtime.jsx)("div", {
1218
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [variant === "code" && bodyText !== null && (0, react_jsx_runtime.jsx)("div", {
1199
1219
  className: ToolRow_module_css_default.bodyScroll,
1200
1220
  children: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.CodeBlock, {
1201
- code: body,
1221
+ code: bodyText,
1202
1222
  lang: "typescript",
1203
1223
  copyLabel: t("copy"),
1204
1224
  copiedLabel: t("copied"),
@@ -1271,7 +1291,7 @@ window.__ModuleLoader__.load({
1271
1291
  icon: VARIANT_ICONS[model.variant],
1272
1292
  title: t(model.titleKey),
1273
1293
  summary: model.summary,
1274
- body: singleFile ? null : model.body,
1294
+ bodyRaw: singleFile ? null : model.bodyRaw,
1275
1295
  output: model.output,
1276
1296
  errorSummary: model.errorSummary,
1277
1297
  terminal,
@@ -1376,8 +1396,8 @@ window.__ModuleLoader__.load({
1376
1396
  * @param props - whole-Tool owner data and the Tool-owned child-slot share.
1377
1397
  * @returns the Tool call tree.
1378
1398
  */
1379
- function ToolCallTree({ renderSlot, node, selectedCallId, cwd, openFile, inspectCall, useConnectionGeneration, t }) {
1380
- const home = useConnectionGeneration((generation) => generation?.host.home);
1399
+ function ToolCallTree({ renderSlot, node, selectedCallId, cwd, openFile, inspectCall, useHostInfo, t }) {
1400
+ const home = useHostInfo((info) => info.home);
1381
1401
  const block = node.data.root;
1382
1402
  return (0, react_jsx_runtime.jsx)(ToolCallBranch, {
1383
1403
  renderSlot,
@@ -1419,8 +1439,8 @@ window.__ModuleLoader__.load({
1419
1439
  * @param props - selected call slice, workspace root, host home, and locale seat.
1420
1440
  * @returns the details output body.
1421
1441
  */
1422
- function ToolDetails({ block, cwd, useConnectionGeneration, t }) {
1423
- const home = useConnectionGeneration((generation) => generation?.host.home);
1442
+ function ToolDetails({ block, cwd, useHostInfo, t }) {
1443
+ const home = useHostInfo((info) => info.home);
1424
1444
  const terminalModel = terminalCardModel(block, cwd);
1425
1445
  if (terminalModel !== null) {
1426
1446
  const terminal = localizeTerminalCardModel(terminalModel, t);
@@ -1616,7 +1636,7 @@ window.__ModuleLoader__.load({
1616
1636
  icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconQuestionOutline14, {}),
1617
1637
  title: t("ask.rowTitle"),
1618
1638
  summary,
1619
- body: transcript === null ? model.body : null,
1639
+ bodyRaw: transcript === null ? model.bodyRaw : null,
1620
1640
  output: transcript === null ? model.output : null,
1621
1641
  askQuestion: transcript,
1622
1642
  state,
@@ -1694,9 +1714,15 @@ window.__ModuleLoader__.load({
1694
1714
  const state = model.state === "ok" && terminalModel !== null && terminalFailed(terminalModel) ? "error" : model.state;
1695
1715
  const status = stateStatus(state, t);
1696
1716
  const [expanded, setExpanded] = (0, react.useState)(false);
1697
- const genericBody = terminal === null && (model.state === "error" || isSettledPersistentShellCall(block)) && (model.body !== null || model.output !== null);
1717
+ const genericBody = terminal === null && (model.state === "error" || isSettledPersistentShellCall(block)) && (model.bodyRaw !== null || model.output !== null);
1698
1718
  const expandable = terminal !== null || genericBody;
1699
1719
  const open = expanded && expandable;
1720
+ const body = (0, react.useMemo)(() => open && genericBody && model.bodyRaw !== null ? formatToolBody(model.variant, model.bodyRaw) : null, [
1721
+ genericBody,
1722
+ model.bodyRaw,
1723
+ model.variant,
1724
+ open
1725
+ ]);
1700
1726
  const failureLine = model.state === "error" ? model.errorSummary : null;
1701
1727
  const toggleExpand = () => {
1702
1728
  setExpanded((v) => !v);
@@ -1755,17 +1781,17 @@ window.__ModuleLoader__.load({
1755
1781
  }) : (0, react_jsx_runtime.jsxs)("div", {
1756
1782
  className: bash_sample_module_css_default.ioCard,
1757
1783
  children: [
1758
- model.body !== null && (0, react_jsx_runtime.jsxs)("div", {
1784
+ body !== null && (0, react_jsx_runtime.jsxs)("div", {
1759
1785
  className: bash_sample_module_css_default.ioSection,
1760
1786
  children: [(0, react_jsx_runtime.jsx)("span", {
1761
1787
  className: bash_sample_module_css_default.ioLabel,
1762
1788
  children: t("row.input")
1763
1789
  }), (0, react_jsx_runtime.jsx)("span", {
1764
1790
  className: bash_sample_module_css_default.ioText,
1765
- children: model.body
1791
+ children: body
1766
1792
  })]
1767
1793
  }),
1768
- model.body !== null && model.output !== null && (0, react_jsx_runtime.jsx)("span", {
1794
+ body !== null && model.output !== null && (0, react_jsx_runtime.jsx)("span", {
1769
1795
  className: bash_sample_module_css_default.ioDivider,
1770
1796
  "aria-hidden": true
1771
1797
  }),
@@ -1817,7 +1843,6 @@ window.__ModuleLoader__.load({
1817
1843
  icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
1818
1844
  title: t(model.titleKey),
1819
1845
  summary: model.summary,
1820
- body: null,
1821
1846
  output: model.output,
1822
1847
  errorSummary: model.errorSummary,
1823
1848
  diff,
@@ -1861,7 +1886,6 @@ window.__ModuleLoader__.load({
1861
1886
  icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
1862
1887
  title: t(model.titleKey),
1863
1888
  summary: model.summary,
1864
- body: null,
1865
1889
  output: model.output,
1866
1890
  errorSummary: model.errorSummary,
1867
1891
  read,
@@ -1900,7 +1924,6 @@ window.__ModuleLoader__.load({
1900
1924
  icon: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
1901
1925
  title: t(toolName === "grep" ? SEARCH_TITLE_KEYS.grep : toolName === "glob" ? SEARCH_TITLE_KEYS.glob : model.titleKey),
1902
1926
  summary: model.summary,
1903
- body: null,
1904
1927
  output: model.output,
1905
1928
  errorSummary: model.errorSummary,
1906
1929
  search,
@@ -2003,7 +2026,7 @@ window.__ModuleLoader__.load({
2003
2026
  title: t("todo.rowTitle"),
2004
2027
  summary: summary.text,
2005
2028
  summarySuffix: summary.extra > 0 ? `+${summary.extra}` : null,
2006
- body: model.body,
2029
+ bodyRaw: model.bodyRaw,
2007
2030
  output: model.output,
2008
2031
  errorSummary: model.errorSummary,
2009
2032
  state: model.state,
@@ -2040,7 +2063,6 @@ window.__ModuleLoader__.load({
2040
2063
  icon,
2041
2064
  title: t(toolName === "web_search" ? WEB_TITLE_KEYS.web_search : toolName === "web_fetch" ? WEB_TITLE_KEYS.web_fetch : model.titleKey),
2042
2065
  summary: model.summary,
2043
- body: null,
2044
2066
  output: model.output,
2045
2067
  errorSummary: model.errorSummary,
2046
2068
  web,
@@ -2069,15 +2091,18 @@ window.__ModuleLoader__.load({
2069
2091
  };
2070
2092
  //#endregion
2071
2093
  //#region lib/types/client/apply.js
2072
- /** Required services: the slot registry and the Host description used for POSIX `~`. */
2073
- const inject = ["slots", "connection"];
2094
+ /** Required services: the slot registry and the Remote face carrying the Host home used for POSIX `~`. */
2095
+ const inject = ["slots", "remote"];
2074
2096
  /**
2075
2097
  * Mount the whole-Tool renderers and built-in atomic Tool registrations.
2076
2098
  * @param ctx - Client root context.
2077
2099
  */
2078
2100
  function apply(ctx) {
2079
- const connection = ctx.get("connection");
2080
- const toolInject = () => ({ hooks: { connectionGeneration: connection.generation } });
2101
+ const hostInfo = {
2102
+ getSnapshot: () => ctx.remote.$host,
2103
+ subscribe: (listener) => ctx.on("connection/reset", listener)
2104
+ };
2105
+ const toolInject = () => ({ hooks: { hostInfo } });
2081
2106
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
2082
2107
  name: "conversation.chat.node",
2083
2108
  key: "tool-call",
@@ -1,5 +1,6 @@
1
+ /** Register the Tool call tree, details renderer, and built-in atomic views. */
1
2
  import type { Context as ClientContext } from '@prettier-ai/cordis';
2
- /** Required services: the slot registry and the Host description used for POSIX `~`. */
3
+ /** Required services: the slot registry and the Remote face carrying the Host home used for POSIX `~`. */
3
4
  export declare const inject: string[];
4
5
  /**
5
6
  * Mount the whole-Tool renderers and built-in atomic Tool registrations.
@@ -1,6 +1,6 @@
1
1
  /** Tool UI slot declarations and their composed component props. */
2
- import type { ConnectionGenerationState } from '@prettier-ai/dsh-client-connection/client';
3
- import type { InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@prettier-ai/dsh-client-ui-slots';
2
+ import type { HostObservable, InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime } from '@prettier-ai/dsh-client-ui-slots';
3
+ import type { RemoteHostFacts } from '@prettier-ai/dsh-api-remotes/client';
4
4
  import type { ToolCallBlock } from '@prettier-ai/dsh-client-ui-chat/client';
5
5
  declare module '@prettier-ai/dsh-client-ui-slots' {
6
6
  interface SlotMap {
@@ -45,14 +45,19 @@ export interface ToolCallOwnerProps {
45
45
  /** Full props of a registered atomic Tool view. */
46
46
  export type ToolCallViewProps = PropsRuntime<'tool.call.toolview'>;
47
47
  /** Injected Host description for POSIX home-path display. */
48
- export type ToolConnectionGenerationInjected = {
48
+ export type ToolHostInfoInjected = {
49
49
  hooks: {
50
- /** Current Connection generation, bound by the slot renderer. */
51
- connectionGeneration: ConnectionGenerationState;
50
+ /**
51
+ * Fixed Host facts, reached through a hook rather than injected as values:
52
+ * the renderer memoizes an entry's inject result for the registration's
53
+ * lifetime, so facts read there would freeze at whatever the first render
54
+ * saw. Select the field the view needs (`info => info.home`).
55
+ */
56
+ hostInfo: HostObservable<RemoteHostFacts>;
52
57
  };
53
58
  };
54
59
  /** Full props of the Tool call-tree renderer registered as a `tool-call` Chat Node. */
55
- export type ToolTreeProps = PropsRuntime<'conversation.chat.node', 'tool-call'> & PropsRenderSlots<'tool.call.toolview'> & PropsLocale<'conversation'> & InjectFace<ToolConnectionGenerationInjected>;
60
+ export type ToolTreeProps = PropsRuntime<'conversation.chat.node', 'tool-call'> & PropsRenderSlots<'tool.call.toolview'> & PropsLocale<'conversation'> & InjectFace<ToolHostInfoInjected>;
56
61
  /** Full props of the selected Tool output renderer in the details panel. */
57
- export type ToolDetailsProps = PropsRuntime<'conversation.details.tool'> & PropsLocale<'conversation'> & InjectFace<ToolConnectionGenerationInjected>;
62
+ export type ToolDetailsProps = PropsRuntime<'conversation.details.tool'> & PropsLocale<'conversation'> & InjectFace<ToolHostInfoInjected>;
58
63
  //# sourceMappingURL=slots.d.ts.map
@@ -1,4 +1,4 @@
1
1
  /** Browser Tool plugin: whole-call composition and keyed atomic Tool views. */
2
2
  export { apply, inject } from './apply.ts';
3
- export type { ToolCallOwnerProps, ToolCallViewProps, ToolConnectionGenerationInjected, ToolDetailsProps, ToolTreeProps, } from './contract/slots.ts';
3
+ export type { ToolCallOwnerProps, ToolCallViewProps, ToolDetailsProps, ToolHostInfoInjected, ToolTreeProps, } from './contract/slots.ts';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -5,5 +5,5 @@ import type { ToolTreeProps } from '../contract/slots.ts';
5
5
  * @param props - whole-Tool owner data and the Tool-owned child-slot share.
6
6
  * @returns the Tool call tree.
7
7
  */
8
- export declare function ToolCallTree({ renderSlot, node, selectedCallId, cwd, openFile, inspectCall, useConnectionGeneration, t, }: ToolTreeProps): import("react").JSX.Element;
8
+ export declare function ToolCallTree({ renderSlot, node, selectedCallId, cwd, openFile, inspectCall, useHostInfo, t, }: ToolTreeProps): import("react").JSX.Element;
9
9
  //# sourceMappingURL=ToolCallTree.d.ts.map
@@ -5,5 +5,5 @@ import type { ToolDetailsProps } from '../contract/slots.ts';
5
5
  * @param props - selected call slice, workspace root, host home, and locale seat.
6
6
  * @returns the details output body.
7
7
  */
8
- export declare function ToolDetails({ block, cwd, useConnectionGeneration, t, }: Pick<ToolDetailsProps, 'block' | 'cwd' | 'useConnectionGeneration' | 't'>): import("react").JSX.Element;
8
+ export declare function ToolDetails({ block, cwd, useHostInfo, t, }: Pick<ToolDetailsProps, 'block' | 'cwd' | 'useHostInfo' | 't'>): import("react").JSX.Element;
9
9
  //# sourceMappingURL=ToolDetails.d.ts.map
@@ -5,7 +5,7 @@ import { type ReadCardModel } from '../models/read-card-model.ts';
5
5
  import { type SearchCardModel } from '../models/search-card-model.ts';
6
6
  import { type TerminalCardModel } from '../models/terminal-card-model.ts';
7
7
  import type { AskQuestionCardModel } from '../models/ask-question-card-model.ts';
8
- import type { ToolRowState, ToolRowVariant } from '../models/tool-call-model.ts';
8
+ import { type ToolRowState, type ToolRowVariant } from '../models/tool-call-model.ts';
9
9
  import type { WebCardModelProps } from '../models/web-card-model.ts';
10
10
  export interface ToolRowProps {
11
11
  t: TranslateNS<'conversation'>;
@@ -23,8 +23,8 @@ export interface ToolRowProps {
23
23
  * error row, whose collapsed summary is the failure line instead.
24
24
  */
25
25
  summarySuffix?: string | null | undefined;
26
- /** Expanded-body input text; null = no input section. */
27
- body: string | null;
26
+ /** Original argument JSON formatted only while the row is expanded. */
27
+ bodyRaw?: string | null | undefined;
28
28
  /** Flattened result text for the expanded Output section; null/absent = no output section. */
29
29
  output?: string | null | undefined;
30
30
  /** Ask-user transcript card; card fields are mutually exclusive and replace text sections. */
@@ -51,5 +51,5 @@ export interface ToolRowProps {
51
51
  */
52
52
  inspect?: (() => void) | undefined;
53
53
  }
54
- export declare function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, body, output, askQuestion, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect, }: ToolRowProps): import("react").JSX.Element;
54
+ export declare function ToolRow({ t, variant, toolName, icon, title, summary, summarySuffix, bodyRaw, output, askQuestion, errorSummary, terminal, diff, read, search, web, state, filePath, onOpenFile, inspect, }: ToolRowProps): import("react").JSX.Element;
55
55
  //# sourceMappingURL=ToolRow.d.ts.map
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Pure row-model derivation for tool summary rows: variant classification,
3
- * one-line summary, expanded-body text, and flattened result output from the
4
- * frozen call slice. Input material comes from the call ARGUMENTS; output and
5
- * error material from the settled result node. A supported terminal call gets
6
- * its expanded body from `terminalCardModel` instead.
3
+ * one-line summary, expansion-time body input, and flattened result output
4
+ * from the frozen call slice. Input material comes from the call ARGUMENTS;
5
+ * output and error material from the settled result node. A supported terminal
6
+ * call gets its expanded body from `terminalCardModel` instead.
7
7
  */
8
8
  import type { ToolCallBlock, ToolResultNode } from '@prettier-ai/dsh-client-ui-chat/client';
9
9
  import type { LocaleKeysOf } from '@prettier-ai/dsh-client-ui-slots';
@@ -40,8 +40,8 @@ export interface ToolRowModel {
40
40
  * relative values against the session cwd before opening.
41
41
  */
42
42
  filePath: string | undefined;
43
- /** Expanded-body input text (pretty args); null = no input section. */
44
- body: string | null;
43
+ /** Original argument JSON retained for expansion-time body formatting. */
44
+ bodyRaw: string | null;
45
45
  /** Flattened result text ({@link resultText}); null while running or when the result carries no text. */
46
46
  output: string | null;
47
47
  /** First line of the result text on an error row; null for every other state. */
@@ -63,6 +63,13 @@ export declare function resultText(node: ToolResultNode): string;
63
63
  * @returns the path relative to the workspace root, or unchanged when it is not rooted there.
64
64
  */
65
65
  export declare function relativizeToCwd(text: string, cwd: string | undefined): string;
66
+ /**
67
+ * Format one argument payload when its generic input body becomes visible.
68
+ * @param variant - row presentation selected for the Tool name.
69
+ * @param argsRaw - original argument JSON or incomplete raw text.
70
+ * @returns display body, or null for empty input.
71
+ */
72
+ export declare function formatToolBody(variant: ToolRowVariant, argsRaw: string): string | null;
66
73
  /**
67
74
  * Derive the full row model from a frozen call slice.
68
75
  * @param toolName - wire tool name (dispatch-supplied; survives windowless results).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prettier-ai/dsh-client-ui-tool",
3
3
  "description": "Client Tool call-tree renderer and keyed per-tool presentation slot",
4
- "version": "0.1.2-alpha.1",
4
+ "version": "0.1.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -45,37 +45,27 @@
45
45
  "clsx": "^2.0.0"
46
46
  },
47
47
  "peerDependencies": {
48
- "@prettier-ai/cordis": "^4.0.1",
49
- "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.1",
50
- "@prettier-ai/dsh-client-ui-conversation": "^0.1.2-alpha.1",
51
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
52
- "@prettier-ai/dsh-api-workspace-controller": "^0.1.2-alpha.1",
53
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
54
- "@prettier-ai/dsh-client-ui-chat": "^0.1.2-alpha.1",
55
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
56
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
57
- "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
58
- "@prettier-ai/dsh-util-workspace-path": "^0.1.2-alpha.1"
48
+ "@prettier-ai/cordis": "^4.0.2"
59
49
  },
60
50
  "devDependencies": {
61
51
  "@testing-library/react": "^16.1.0",
62
52
  "@types/react": "~18.3.1",
63
53
  "react": "^18.2.0",
64
54
  "react-dom": "^18.2.0",
65
- "@prettier-ai/cordis": "^4.0.1",
66
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
67
- "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.1",
68
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
69
- "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.1",
70
- "@prettier-ai/dsh-client-ui-conversation": "^0.1.2-alpha.1",
71
- "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.1",
72
- "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
73
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
74
- "@prettier-ai/dsh-api-workspace-controller": "^0.1.2-alpha.1",
75
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
76
- "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.1",
77
- "@prettier-ai/dsh-client-ui-chat": "^0.1.2-alpha.1",
78
- "@prettier-ai/dsh-util-workspace-path": "^0.1.2-alpha.1"
55
+ "@prettier-ai/cordis": "^4.0.2",
56
+ "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.3",
57
+ "@prettier-ai/dsh-client-connection": "^0.1.2-alpha.3",
58
+ "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.3",
59
+ "@prettier-ai/dsh-client-ui-conversation": "^0.1.2-alpha.3",
60
+ "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.3",
61
+ "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.3",
62
+ "@prettier-ai/dsh-invariants": "^0.1.2-alpha.3",
63
+ "@prettier-ai/dsh-api-workspace-controller": "^0.1.2-alpha.3",
64
+ "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
65
+ "@prettier-ai/dsh-client-ui-session": "^0.1.2-alpha.3",
66
+ "@prettier-ai/dsh-util-workspace-path": "^0.1.2-alpha.3",
67
+ "@prettier-ai/dsh-client-ui-chat": "^0.1.2-alpha.3",
68
+ "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.3"
79
69
  },
80
70
  "files": [
81
71
  "lib/index.js",