@oversk7/pi-web-dsh-style 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oversk7/pi-web-dsh-style",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "An unofficial DSH-styled Windows browser UI for pi coding agent with configurable LAN and native frp relay access",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/transcript.ts CHANGED
@@ -8,6 +8,9 @@ export interface RenderedBlock {
8
8
  name?: string;
9
9
  arguments?: unknown;
10
10
  argumentsText?: string;
11
+ subagent?: { type: string; task: string; prompt: string };
12
+ subagentFeedback?: { target: string; type: string; task: string; message: string };
13
+ subagentResult?: { target: string; type: string; task: string };
11
14
  result?: { content: string; isError: boolean; details?: unknown };
12
15
  running?: boolean;
13
16
  }
@@ -37,11 +40,56 @@ export interface RenderedMessage {
37
40
  }
38
41
 
39
42
  export function browserMessages(messages: RenderedMessage[]): RenderedMessage[] {
43
+ type AgentIdentity = { id: string; type: string; task: string };
44
+ const agents = new Map<string, AgentIdentity>();
45
+ const names = new Map<string, string>();
46
+ const types = new Map<string, string | null>();
47
+ const text = (value: unknown): string => typeof value === "string" ? value : "";
48
+ const lookup = (target: string): AgentIdentity | undefined => agents.get(target) || agents.get(names.get(target) || types.get(target.toLowerCase()) || "");
40
49
  return messages.map((message) => !message.blocks ? message : {
41
50
  ...message,
42
51
  blocks: message.blocks.map((block) => {
43
52
  if (block.type !== "toolCall") return block;
44
- const { arguments: _arguments, result, ...visible } = block;
53
+ const { arguments: args, result, ...visible } = block;
54
+ // Keep display fields without sending every tool's raw arguments to the browser.
55
+ const record = args && typeof args === "object" ? args as Json : {};
56
+ const toolName = block.name?.toLowerCase();
57
+ if (toolName === "agent") {
58
+ const resumed = lookup(text(record.resume));
59
+ if (args && typeof args === "object") {
60
+ visible.subagent = {
61
+ type: text(record.subagent_type) || resumed?.type || "",
62
+ task: text(record.description) || resumed?.task || "",
63
+ prompt: text(record.prompt),
64
+ };
65
+ visible.argumentsText = visible.subagent.task;
66
+ }
67
+ const details = result?.details && typeof result.details === "object" ? result.details as Json : {};
68
+ const receiptField = (label: string): string => result?.content.match(new RegExp(`^${label}:[ \\t]*(.*)$`, "m"))?.[1]?.trim() || "";
69
+ const id = text(details.agentId) || receiptField("Agent ID") || resumed?.id;
70
+ if (id && !result?.isError) {
71
+ const identity = {
72
+ id,
73
+ type: visible.subagent?.type || text(details.subagentType) || receiptField("Type"),
74
+ task: visible.subagent?.task || text(details.description) || receiptField("Description"),
75
+ };
76
+ agents.set(id, identity);
77
+ if (text(record.name)) names.set(text(record.name), id);
78
+ // A type is a useful fallback only when it identifies one agent unambiguously.
79
+ const typeKey = identity.type.toLowerCase();
80
+ if (typeKey) {
81
+ const previous = types.get(typeKey);
82
+ types.set(typeKey, previous === undefined || previous === id ? id : null);
83
+ }
84
+ }
85
+ } else if ((toolName === "steer_subagent" || toolName === "get_subagent_result") && args && typeof args === "object") {
86
+ const target = text(record.agent_id);
87
+ const identity = lookup(target);
88
+ const agent = { target, type: identity?.type || "", task: identity?.task || "" };
89
+ if (toolName === "steer_subagent") visible.subagentFeedback = { ...agent, message: text(record.message) };
90
+ else visible.subagentResult = agent;
91
+ visible.argumentsText = [identity?.type || target, identity?.task].filter(Boolean).join(" · ");
92
+ }
45
93
  return result
46
94
  ? { ...visible, result: { content: result.content, isError: result.isError } }
47
95
  : visible;
package/web/app.css CHANGED
@@ -312,6 +312,20 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
312
312
  .CY-8Ka_root[data-state=running] .CY-8Ka_leading,.CY-8Ka_root[data-state=running] .CY-8Ka_title{color:var(--dsw-alias-state-business-primary)}
313
313
  .CY-8Ka_root[data-state=error] .CY-8Ka_leading,.CY-8Ka_root[data-state=error] .CY-8Ka_title{color:var(--dsw-alias-state-error-primary)}
314
314
 
315
+ /* Agent delegation details */
316
+ .pi-subagentDetail{display:flex;flex-direction:column;gap:12px;padding:10px 14px 14px;min-width:0}
317
+ .pi-subagentField{min-width:0}
318
+ .pi-subagentLabel{margin-bottom:4px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
319
+ .pi-subagentText{margin:0;color:var(--dsw-alias-label-primary);font:13px/21px var(--dsw-font-family);white-space:pre-wrap;overflow-wrap:anywhere}
320
+ .pi-subagentField .Y0dWHa_payload{min-height:0}
321
+ .pi-subagentNotice{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:21px;overflow-wrap:anywhere}
322
+ .pi-subagentResult{min-width:0;overflow-wrap:anywhere}
323
+ .pi-subagentResult ._markdown_1nba0_5{font-size:13px;line-height:21px}
324
+ .pi-subagentResult.Y0dWHa_payloadError{color:var(--dsw-alias-state-error-primary)}
325
+ .pi-subagentConversation{min-width:0}
326
+ .pi-subagentConversation summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;margin-bottom:6px}
327
+ .pi-subagentConversation summary:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
328
+
315
329
  /* Extension notifications */
316
330
  .pi-toast{z-index:1500;box-sizing:border-box;position:fixed;right:24px;bottom:24px;max-width:min(420px,calc(100vw - 48px));background:var(--dsw-specific-menu);border:1px solid var(--dsw-alias-border-l2);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:12px;padding:10px 14px;font-size:13px;line-height:20px;animation:pi-toast-in .16s var(--ds-ease-in-out)}
317
331
  .pi-toast-warning{border-color:var(--dsw-alias-state-warn-secondary);color:var(--dsw-alias-state-warn-label)}
package/web/app.js CHANGED
@@ -1573,6 +1573,93 @@ function compactionUsageSummary(usage) {
1573
1573
  };
1574
1574
  }
1575
1575
 
1576
+ function subagentPresentation(tool) {
1577
+ const toolName = (tool?.name || tool?.toolName || "").toLowerCase();
1578
+ if (!["agent", "steer_subagent", "get_subagent_result"].includes(toolName)) return null;
1579
+ const rawOutput = tool.result?.content || tool.text || "";
1580
+ const isError = Boolean(tool.result?.isError || tool.isError);
1581
+ if (toolName === "get_subagent_result") {
1582
+ const header = /^Agent: ([^\r\n]+)\r?\nType: ([^|\r\n]+)\s*\|\s*Status: ([^|\r\n]+)[^\r\n]*\r?\nDescription: ([^\r\n]*)\r?\n(?:\r?\n)?/.exec(rawOutput);
1583
+ const status = isError ? "error" : header?.[3]?.trim().toLowerCase() || (tool.running ? "fetching" : "");
1584
+ const body = header ? rawOutput.slice(header[0].length) : rawOutput;
1585
+ const trace = /\r?\n\r?\n--- Agent Conversation ---\r?\n/.exec(body);
1586
+ return {
1587
+ retrieval: true,
1588
+ target: tool.subagentResult?.target || header?.[1] || "",
1589
+ type: tool.subagentResult?.type || header?.[2]?.trim() || "",
1590
+ task: tool.subagentResult?.task || header?.[4] || "",
1591
+ prompt: "",
1592
+ status,
1593
+ output: trace ? body.slice(0, trace.index) : body,
1594
+ conversation: trace ? body.slice(trace.index + trace[0].length) : "",
1595
+ isError: isError || ["error", "failed"].includes(status),
1596
+ };
1597
+ }
1598
+ if (toolName === "steer_subagent") {
1599
+ const feedback = tool.subagentFeedback;
1600
+ const sent = !isError && /^Steering message sent to agent .+\./.test(rawOutput.trim());
1601
+ return {
1602
+ feedback: true,
1603
+ target: feedback?.target || rawOutput.match(/^Steering message sent to agent (.+?)\. (?:The agent|$)/)?.[1] || "",
1604
+ type: feedback?.type || "",
1605
+ task: feedback?.task || "",
1606
+ prompt: feedback?.message || "",
1607
+ promptUnavailable: tool.type === "toolCall" && typeof feedback?.message !== "string",
1608
+ output: sent ? "" : rawOutput,
1609
+ isError,
1610
+ };
1611
+ }
1612
+ const receipt = !isError && /^Agent started in background\.(?:\r?\n|$)/.test(rawOutput.trim());
1613
+ const receiptField = (label) => receipt ? rawOutput.match(new RegExp(`^${label}:\\s*(.*)$`, "m"))?.[1]?.trim() || "" : "";
1614
+ return {
1615
+ type: tool.subagent?.type || receiptField("Type"),
1616
+ task: tool.subagent?.task || receiptField("Description"),
1617
+ prompt: tool.subagent?.prompt || "",
1618
+ promptUnavailable: tool.type === "toolCall" && typeof tool.subagent?.prompt !== "string",
1619
+ output: receipt ? "" : rawOutput,
1620
+ isError,
1621
+ };
1622
+ }
1623
+
1624
+ function subagentTitle(agent, fallback) {
1625
+ if (agent?.feedback || agent?.retrieval) {
1626
+ const target = agent.type || (agent.target.length > 24 ? `${agent.target.slice(0, 21)}…` : agent.target) || "Agent";
1627
+ return agent.retrieval ? `${t("查看结果")} ← ${target}` : `${t("补充指令")} → ${target}`;
1628
+ }
1629
+ return agent?.type ? `Agent · ${agent.type}` : fallback;
1630
+ }
1631
+
1632
+ function subagentStatusLabel(status) {
1633
+ return { completed: "已完成", done: "已完成", running: "运行中", pending: "等待执行", queued: "等待执行", scheduled: "等待执行", error: "失败", failed: "失败", aborted: "已取消", cancelled: "已取消", canceled: "已取消", fetching: "正在获取结果" }[status] || status;
1634
+ }
1635
+
1636
+ function subagentSummary(agent) {
1637
+ if (agent.retrieval) return [agent.task, t(subagentStatusLabel(agent.status))].filter(Boolean).join(" · ");
1638
+ return agent.feedback
1639
+ ? [agent.task, agent.prompt.replace(/\s+/g, " ").slice(0, 120)].filter(Boolean).join(" · ")
1640
+ : agent.task;
1641
+ }
1642
+
1643
+ function renderSubagentDetail(agent) {
1644
+ const field = (label, value) => value ? `<div class="pi-subagentField"><div class="pi-subagentLabel">${label}</div><pre class="pi-subagentText" translate="no">${esc(value)}</pre></div>` : "";
1645
+ if (agent.retrieval) {
1646
+ const label = agent.isError ? "失败原因" : agent.status === "running" ? "当前进展" : "执行结果";
1647
+ const result = agent.output
1648
+ ? `<div class="pi-subagentField"><div class="pi-subagentLabel">${label}</div><div class="pi-subagentResult ${agent.isError ? "Y0dWHa_payloadError" : ""}"><div class="_markdown_1nba0_5">${markdown(agent.output)}</div></div></div>`
1649
+ : `<div class="pi-subagentNotice">${agent.status === "fetching" ? "正在获取结果" : agent.status === "running" ? "子 agent 仍在运行,尚无结果。" : "暂无结果内容。"}</div>`;
1650
+ const conversation = agent.conversation ? `<details class="pi-subagentConversation"><summary>执行记录</summary><pre class="pi-subagentText" translate="no">${esc(agent.conversation)}</pre></details>` : "";
1651
+ return `<div class="Sxvs8a_body pi-subagentDetail">${field("结果来源", [agent.type, agent.task].filter(Boolean).join(" · ") || agent.target)}${agent.status ? `<div class="pi-subagentField"><div class="pi-subagentLabel">状态</div><div>${esc(subagentStatusLabel(agent.status))}</div></div>` : ""}${result}${conversation}</div>`;
1652
+ }
1653
+ const label = agent.feedback ? "指令内容" : "提示词";
1654
+ const identity = agent.feedback
1655
+ ? field("目标 agent", [agent.type, agent.task].filter(Boolean).join(" · ") || agent.target)
1656
+ : field("类型", agent.type) + field("委派任务", agent.task);
1657
+ const notice = agent.feedback
1658
+ ? "当前运行时未提供完整指令内容。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。"
1659
+ : "当前运行时未提供完整提示词。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。";
1660
+ return `<div class="Sxvs8a_body pi-subagentDetail">${identity}${field(label, agent.prompt)}${agent.promptUnavailable ? `<div class="pi-subagentField"><div class="pi-subagentLabel">${label}</div><div class="pi-subagentNotice">${notice}</div></div>` : ""}${agent.output ? `<div class="pi-subagentField"><div class="pi-subagentLabel">${agent.isError ? "失败" : "结果"}</div><pre class="Y0dWHa_payload ${agent.isError ? "Y0dWHa_payloadError" : ""}" translate="no">${esc(agent.output)}</pre></div>` : ""}</div>`;
1661
+ }
1662
+
1576
1663
  function renderMessageDetail(msg, blockIndex) {
1577
1664
  if (msg.kind === "compaction") {
1578
1665
  const usage = compactionUsageSummary(msg.usage);
@@ -1589,6 +1676,8 @@ function renderMessageDetail(msg, blockIndex) {
1589
1676
  if (block?.type === "thinking") {
1590
1677
  return `<div class="Y0dWHa_thinkingQuote"><pre class="Y0dWHa_payload">${esc(block.thinking || "")}</pre></div>`;
1591
1678
  }
1679
+ const agent = subagentPresentation(block || msg);
1680
+ if (agent) return renderSubagentDetail(agent);
1592
1681
  const argsText = block?.argumentsText || "";
1593
1682
  const output = block?.result?.content || (msg.kind === "bashExecution" ? msg.output : msg.text) || "";
1594
1683
  const isError = block ? block.result?.isError : msg.kind === "toolResult" && msg.isError;
@@ -1704,15 +1793,16 @@ function renderMessage(msg, messageIndex) {
1704
1793
  return `<div class="pi-assistantMessageGroup">${content}${error}${actions}</div>`;
1705
1794
  }
1706
1795
  if (msg.kind === "toolResult") {
1707
- const output = msg.text || "";
1796
+ const agent = subagentPresentation(msg);
1797
+ const output = agent ? agent.output : msg.text || "";
1708
1798
  const lineCount = output ? output.split("\n").length : 0;
1709
- const hasDetail = output !== "";
1799
+ const hasDetail = Boolean(agent ? agent.type || agent.task || agent.target || agent.status || agent.prompt || output : output);
1710
1800
  const toolKey = `result:${msg.toolCallId || msg.timestamp || msg.toolName || "tool"}`;
1711
1801
  const expanded = hasDetail && S.expandedTools.has(toolKey);
1712
1802
  return `<div class="Md3f7G_flowItem" data-chat-flow-kind="tool-result"><div class="ztWv_q_callRow" data-tool-key="${esc(toolKey)}"><div class="CY-8Ka_card">
1713
- <div class="CY-8Ka_root" data-state="${msg.isError ? "error" : "ok"}" ${hasDetail ? `role="button" data-expandable aria-expanded="${expanded}" data-action="toggle-tool"` : ""}>
1714
- <span class="CY-8Ka_leading">${ICONS.bash}</span><span class="CY-8Ka_title">${esc(msg.toolName || "Tool")}</span><span class="CY-8Ka_sep"></span>
1715
- <span class="CY-8Ka_summary ${msg.isError ? "CY-8Ka_errorSummary" : ""}">${msg.isError ? "失败" : "完成"}${lineCount ? ` · ${lineCount} 行输出` : ""}</span>
1803
+ <div class="CY-8Ka_root" data-state="${agent?.isError || msg.isError ? "error" : agent?.status === "running" ? "running" : "ok"}" ${hasDetail ? `role="button" data-expandable aria-expanded="${expanded}" data-action="toggle-tool"` : ""}>
1804
+ <span class="CY-8Ka_leading">${ICONS.bash}</span><span class="CY-8Ka_title">${esc(subagentTitle(agent, msg.toolName || "Tool"))}</span><span class="CY-8Ka_sep"></span>
1805
+ <span class="CY-8Ka_summary ${agent?.isError || msg.isError ? "CY-8Ka_errorSummary" : ""}">${agent ? esc(subagentSummary(agent) || (msg.isError ? "失败" : "")) : `${msg.isError ? "失败" : "完成"}${lineCount ? ` · ${lineCount} 行输出` : ""}`}</span>
1716
1806
  </div>
1717
1807
  ${hasDetail ? `<div class="Sxvs8a_root" ${expanded ? "" : "hidden"}>${expanded ? renderMessageDetail(msg) : ""}</div>` : ""}
1718
1808
  </div></div></div>`;
@@ -1749,13 +1839,14 @@ function renderBlock(block, msg, idx, messageIndex) {
1749
1839
  }
1750
1840
  if (block.type === "toolCall") {
1751
1841
  const result = block.result;
1752
- const state = result ? (result.isError ? "error" : "ok") : block.running ? "running" : "ok";
1842
+ const agent = subagentPresentation(block);
1843
+ const state = agent?.isError || result?.isError ? "error" : agent?.status === "running" || block.running ? "running" : "ok";
1753
1844
  const argsText = block.argumentsText || "";
1754
- const resultText = result?.content || "";
1845
+ const resultText = agent ? agent.output : result?.content || "";
1755
1846
  const resultLines = resultText ? resultText.split("\n").length : 0;
1756
- const hasDetail = argsText !== "" || resultText !== "";
1847
+ const hasDetail = Boolean(agent ? agent.type || agent.task || agent.target || agent.status || agent.prompt || agent.promptUnavailable || resultText : argsText || resultText);
1757
1848
  const resultHint = resultLines ? `${resultLines} 行输出` : state === "running" ? "运行中" : "";
1758
- const summary = [argsText, resultHint].filter(Boolean).join(" · ");
1849
+ const summary = agent ? subagentSummary(agent) : [argsText, resultHint].filter(Boolean).join(" · ");
1759
1850
  const toolKey = `call:${block.id || `${block.name || "tool"}:${idx}`}`;
1760
1851
  const expanded = hasDetail && S.expandedTools.has(toolKey);
1761
1852
  return `<div class="Md3f7G_flowItem" data-chat-flow-kind="tool-call">
@@ -1763,8 +1854,8 @@ function renderBlock(block, msg, idx, messageIndex) {
1763
1854
  <div class="CY-8Ka_card"><div class="CY-8Ka_root" data-sample="${esc(block.name || "tool")}" data-variant="${esc(block.name || "tool")}" data-state="${state}" ${hasDetail ? `role="button" data-expandable aria-expanded="${expanded}" data-action="toggle-tool"` : ""}>
1764
1855
  <span class="CY-8Ka_leading"><span class="CY-8Ka_iconIdle">${ICONS.bash}</span></span>
1765
1856
  <span class="CY-8Ka_visuallyHidden">${state === "error" ? "失败" : state === "running" ? "运行中" : "完成"}</span>
1766
- <span class="CY-8Ka_title">${esc(block.name || "Tool")}</span><span class="CY-8Ka_sep"></span>
1767
- <span class="CY-8Ka_summary ${result?.isError ? "CY-8Ka_errorSummary" : ""}">${esc(summary)}</span>
1857
+ <span class="CY-8Ka_title">${esc(subagentTitle(agent, block.name || "Tool"))}</span><span class="CY-8Ka_sep"></span>
1858
+ <span class="CY-8Ka_summary ${state === "error" ? "CY-8Ka_errorSummary" : ""}">${esc(summary)}</span>
1768
1859
  </div>
1769
1860
  ${hasDetail ? `<div class="Sxvs8a_root" data-block-index="${idx}" ${expanded ? "" : "hidden"}>${expanded ? renderMessageDetail(msg, idx) : ""}</div>` : ""}
1770
1861
  </div>
package/web/i18n.js CHANGED
@@ -1,6 +1,10 @@
1
1
  // Browser-only UI copy. Chinese is the source language; unknown text (including
2
2
  // user messages, paths and server responses) is left untouched.
3
3
  const english = {
4
+ "查看结果": "View result", "结果来源": "Result from", "执行结果": "Agent result", "当前进展": "Progress", "执行记录": "Execution transcript", "状态": "Status", "失败原因": "Failure reason", "等待执行": "Pending", "已取消": "Canceled", "正在获取结果": "Fetching result", "子 agent 仍在运行,尚无结果。": "The agent is still running; no result yet.", "暂无结果内容。": "No result content available.",
5
+ "类型": "Type", "委派任务": "Delegated task", "提示词": "Prompt", "结果": "Result", "补充指令": "Additional instructions", "目标 agent": "Target agent", "指令内容": "Instruction content",
6
+ "当前运行时未提供完整指令内容。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。": "The current runtime did not provide the full instructions. Wait until all sessions are idle, reload the runtime in Settings → Maintenance, then refresh the page.",
7
+ "当前运行时未提供完整提示词。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。": "The current runtime did not provide the full prompt. Wait until all sessions are idle, reload the runtime in Settings → Maintenance, then refresh the page.",
4
8
  "刚刚": "just now", "选择模型": "Select model", "内置": "Built-in", "模板": "Prompt", "扩展": "Extension", "技能": "Skill", "命令": "Command",
5
9
  "文件预览": "File preview", "文件": "File", "正在读取": "Reading", "无法预览": "Preview unavailable", "正在加载文件…": "Loading file…", "文件预览失败": "File preview failed", "预览": "Preview", "源代码": "Source code", "返回对话": "Back to chat", "查看源码": "View source", "查看预览": "View preview", "源码": "Source", "在系统文件管理器中定位": "Show in file manager", "关闭预览": "Close preview", "纯文本模式": "Plain text mode",
6
10
  "关闭侧边栏": "Close sidebar", "调整侧边栏宽度": "Resize sidebar", "调整文件预览宽度": "Resize file preview", "可拖动排序": "drag to reorder", "新会话": "New session", "等待输入": "Waiting for input", "会话操作": "Session actions", "工作区操作": "Workspace actions", "新建会话": "New session", "没有匹配的会话": "No matching sessions", "暂无会话": "No sessions yet", "没有工作区。点击 + 添加一个目录。": "No workspaces. Click + to add a folder.", "收起侧边栏": "Collapse sidebar", "展开侧边栏": "Expand sidebar", "工作区": "Workspaces", "搜索会话": "Search sessions", "搜索会话…": "Search sessions…", "视图选项": "View options", "添加工作区": "Add workspace", "会话": "Sessions", "设置": "Settings", "选择工作区": "Select workspace", "和你的代码库对话": "Chat with your codebase", "Web 预览版": "Web preview", "正在创建会话…": "Creating session…", "这个会话由 pi coding agent 驱动": "This session runs on pi coding agent",