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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1",
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/server.ts CHANGED
@@ -2337,7 +2337,13 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
2337
2337
  const input = url.searchParams.get("path")?.trim();
2338
2338
  if (!input) return sendJson(res, 400, { error: "path is required" });
2339
2339
  try {
2340
- const target = normalizePath(input);
2340
+ const sessionId = url.searchParams.get("sessionId");
2341
+ const workspaceId = url.searchParams.get("workspaceId");
2342
+ const session = sessionId ? sessions.get(sessionId) : undefined;
2343
+ const workspace = workspaceId ? state.workspaces.find((item) => item.id === workspaceId) : undefined;
2344
+ if (sessionId && !session) return sendJson(res, 404, { error: "session not found" });
2345
+ if (workspaceId && !workspace) return sendJson(res, 404, { error: "workspace not found" });
2346
+ const target = normalizePath(resolve(session?.cwd ?? workspace?.path ?? process.cwd(), input));
2341
2347
  const info = await stat(target);
2342
2348
  if (!info.isFile()) return sendJson(res, 400, { error: "path is not a file" });
2343
2349
  const image = url.searchParams.get("format") === "image";
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
@@ -586,7 +586,8 @@ async function openFilePreview(path, line, href, trigger = document.activeElemen
586
586
  }
587
587
  const requestedLine = Number(line);
588
588
  const normalizedLine = Number.isFinite(requestedLine) && requestedLine > 0 ? Math.max(1, requestedLine) : null;
589
- if (S.filePreview?.path === path && !S.filePreview.error) {
589
+ const previewContext = S.currentSessionId || S.currentWorkspaceId || "";
590
+ if ((S.filePreview?.requestedPath || S.filePreview?.path) === path && S.filePreview?.context === previewContext && !S.filePreview.error) {
590
591
  S.filePreview = { ...S.filePreview, line: normalizedLine, href };
591
592
  applyFrameLayout();
592
593
  renderFilePreview();
@@ -596,15 +597,17 @@ async function openFilePreview(path, line, href, trigger = document.activeElemen
596
597
  }
597
598
 
598
599
  const generation = ++S.filePreviewGeneration;
599
- S.filePreview = { path, line: normalizedLine, href, name: fileNameFromPath(path), loading: true };
600
+ S.filePreview = { path, requestedPath: path, context: previewContext, line: normalizedLine, href, name: fileNameFromPath(path), loading: true };
600
601
  applyFrameLayout();
601
602
  renderFilePreview();
602
603
  if (mobile) $(".pI_x6G_detailsCol")?.focus({ preventScroll: true });
603
604
  try {
604
- const data = await api(`/api/fs/view?path=${encodeURIComponent(path)}&format=json`);
605
+ const context = S.currentSessionId ? `&sessionId=${encodeURIComponent(S.currentSessionId)}`
606
+ : S.currentWorkspaceId ? `&workspaceId=${encodeURIComponent(S.currentWorkspaceId)}` : "";
607
+ const data = await api(`/api/fs/view?path=${encodeURIComponent(path)}&format=json${context}`);
605
608
  if (generation !== S.filePreviewGeneration) return;
606
609
  const activeLine = S.filePreview?.path === path ? S.filePreview.line : normalizedLine;
607
- S.filePreview = { ...data, path, line: activeLine, href, loading: false, error: null };
610
+ S.filePreview = { ...data, path: data.path || path, requestedPath: path, context: previewContext, line: activeLine, href, loading: false, error: null };
608
611
  } catch (error) {
609
612
  if (generation !== S.filePreviewGeneration) return;
610
613
  S.filePreview = { ...S.filePreview, loading: false, error: error.message || String(error) };
@@ -1573,6 +1576,93 @@ function compactionUsageSummary(usage) {
1573
1576
  };
1574
1577
  }
1575
1578
 
1579
+ function subagentPresentation(tool) {
1580
+ const toolName = (tool?.name || tool?.toolName || "").toLowerCase();
1581
+ if (!["agent", "steer_subagent", "get_subagent_result"].includes(toolName)) return null;
1582
+ const rawOutput = tool.result?.content || tool.text || "";
1583
+ const isError = Boolean(tool.result?.isError || tool.isError);
1584
+ if (toolName === "get_subagent_result") {
1585
+ 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);
1586
+ const status = isError ? "error" : header?.[3]?.trim().toLowerCase() || (tool.running ? "fetching" : "");
1587
+ const body = header ? rawOutput.slice(header[0].length) : rawOutput;
1588
+ const trace = /\r?\n\r?\n--- Agent Conversation ---\r?\n/.exec(body);
1589
+ return {
1590
+ retrieval: true,
1591
+ target: tool.subagentResult?.target || header?.[1] || "",
1592
+ type: tool.subagentResult?.type || header?.[2]?.trim() || "",
1593
+ task: tool.subagentResult?.task || header?.[4] || "",
1594
+ prompt: "",
1595
+ status,
1596
+ output: trace ? body.slice(0, trace.index) : body,
1597
+ conversation: trace ? body.slice(trace.index + trace[0].length) : "",
1598
+ isError: isError || ["error", "failed"].includes(status),
1599
+ };
1600
+ }
1601
+ if (toolName === "steer_subagent") {
1602
+ const feedback = tool.subagentFeedback;
1603
+ const sent = !isError && /^Steering message sent to agent .+\./.test(rawOutput.trim());
1604
+ return {
1605
+ feedback: true,
1606
+ target: feedback?.target || rawOutput.match(/^Steering message sent to agent (.+?)\. (?:The agent|$)/)?.[1] || "",
1607
+ type: feedback?.type || "",
1608
+ task: feedback?.task || "",
1609
+ prompt: feedback?.message || "",
1610
+ promptUnavailable: tool.type === "toolCall" && typeof feedback?.message !== "string",
1611
+ output: sent ? "" : rawOutput,
1612
+ isError,
1613
+ };
1614
+ }
1615
+ const receipt = !isError && /^Agent started in background\.(?:\r?\n|$)/.test(rawOutput.trim());
1616
+ const receiptField = (label) => receipt ? rawOutput.match(new RegExp(`^${label}:\\s*(.*)$`, "m"))?.[1]?.trim() || "" : "";
1617
+ return {
1618
+ type: tool.subagent?.type || receiptField("Type"),
1619
+ task: tool.subagent?.task || receiptField("Description"),
1620
+ prompt: tool.subagent?.prompt || "",
1621
+ promptUnavailable: tool.type === "toolCall" && typeof tool.subagent?.prompt !== "string",
1622
+ output: receipt ? "" : rawOutput,
1623
+ isError,
1624
+ };
1625
+ }
1626
+
1627
+ function subagentTitle(agent, fallback) {
1628
+ if (agent?.feedback || agent?.retrieval) {
1629
+ const target = agent.type || (agent.target.length > 24 ? `${agent.target.slice(0, 21)}…` : agent.target) || "Agent";
1630
+ return agent.retrieval ? `${t("查看结果")} ← ${target}` : `${t("补充指令")} → ${target}`;
1631
+ }
1632
+ return agent?.type ? `Agent · ${agent.type}` : fallback;
1633
+ }
1634
+
1635
+ function subagentStatusLabel(status) {
1636
+ return { completed: "已完成", done: "已完成", running: "运行中", pending: "等待执行", queued: "等待执行", scheduled: "等待执行", error: "失败", failed: "失败", aborted: "已取消", cancelled: "已取消", canceled: "已取消", fetching: "正在获取结果" }[status] || status;
1637
+ }
1638
+
1639
+ function subagentSummary(agent) {
1640
+ if (agent.retrieval) return [agent.task, t(subagentStatusLabel(agent.status))].filter(Boolean).join(" · ");
1641
+ return agent.feedback
1642
+ ? [agent.task, agent.prompt.replace(/\s+/g, " ").slice(0, 120)].filter(Boolean).join(" · ")
1643
+ : agent.task;
1644
+ }
1645
+
1646
+ function renderSubagentDetail(agent) {
1647
+ 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>` : "";
1648
+ if (agent.retrieval) {
1649
+ const label = agent.isError ? "失败原因" : agent.status === "running" ? "当前进展" : "执行结果";
1650
+ const result = agent.output
1651
+ ? `<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>`
1652
+ : `<div class="pi-subagentNotice">${agent.status === "fetching" ? "正在获取结果" : agent.status === "running" ? "子 agent 仍在运行,尚无结果。" : "暂无结果内容。"}</div>`;
1653
+ const conversation = agent.conversation ? `<details class="pi-subagentConversation"><summary>执行记录</summary><pre class="pi-subagentText" translate="no">${esc(agent.conversation)}</pre></details>` : "";
1654
+ 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>`;
1655
+ }
1656
+ const label = agent.feedback ? "指令内容" : "提示词";
1657
+ const identity = agent.feedback
1658
+ ? field("目标 agent", [agent.type, agent.task].filter(Boolean).join(" · ") || agent.target)
1659
+ : field("类型", agent.type) + field("委派任务", agent.task);
1660
+ const notice = agent.feedback
1661
+ ? "当前运行时未提供完整指令内容。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。"
1662
+ : "当前运行时未提供完整提示词。请等待所有会话空闲,在设置 → 维护中重新加载运行时,然后刷新页面。";
1663
+ 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>`;
1664
+ }
1665
+
1576
1666
  function renderMessageDetail(msg, blockIndex) {
1577
1667
  if (msg.kind === "compaction") {
1578
1668
  const usage = compactionUsageSummary(msg.usage);
@@ -1589,6 +1679,8 @@ function renderMessageDetail(msg, blockIndex) {
1589
1679
  if (block?.type === "thinking") {
1590
1680
  return `<div class="Y0dWHa_thinkingQuote"><pre class="Y0dWHa_payload">${esc(block.thinking || "")}</pre></div>`;
1591
1681
  }
1682
+ const agent = subagentPresentation(block || msg);
1683
+ if (agent) return renderSubagentDetail(agent);
1592
1684
  const argsText = block?.argumentsText || "";
1593
1685
  const output = block?.result?.content || (msg.kind === "bashExecution" ? msg.output : msg.text) || "";
1594
1686
  const isError = block ? block.result?.isError : msg.kind === "toolResult" && msg.isError;
@@ -1704,15 +1796,16 @@ function renderMessage(msg, messageIndex) {
1704
1796
  return `<div class="pi-assistantMessageGroup">${content}${error}${actions}</div>`;
1705
1797
  }
1706
1798
  if (msg.kind === "toolResult") {
1707
- const output = msg.text || "";
1799
+ const agent = subagentPresentation(msg);
1800
+ const output = agent ? agent.output : msg.text || "";
1708
1801
  const lineCount = output ? output.split("\n").length : 0;
1709
- const hasDetail = output !== "";
1802
+ const hasDetail = Boolean(agent ? agent.type || agent.task || agent.target || agent.status || agent.prompt || output : output);
1710
1803
  const toolKey = `result:${msg.toolCallId || msg.timestamp || msg.toolName || "tool"}`;
1711
1804
  const expanded = hasDetail && S.expandedTools.has(toolKey);
1712
1805
  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>
1806
+ <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"` : ""}>
1807
+ <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>
1808
+ <span class="CY-8Ka_summary ${agent?.isError || msg.isError ? "CY-8Ka_errorSummary" : ""}">${agent ? esc(subagentSummary(agent) || (msg.isError ? "失败" : "")) : `${msg.isError ? "失败" : "完成"}${lineCount ? ` · ${lineCount} 行输出` : ""}`}</span>
1716
1809
  </div>
1717
1810
  ${hasDetail ? `<div class="Sxvs8a_root" ${expanded ? "" : "hidden"}>${expanded ? renderMessageDetail(msg) : ""}</div>` : ""}
1718
1811
  </div></div></div>`;
@@ -1749,13 +1842,14 @@ function renderBlock(block, msg, idx, messageIndex) {
1749
1842
  }
1750
1843
  if (block.type === "toolCall") {
1751
1844
  const result = block.result;
1752
- const state = result ? (result.isError ? "error" : "ok") : block.running ? "running" : "ok";
1845
+ const agent = subagentPresentation(block);
1846
+ const state = agent?.isError || result?.isError ? "error" : agent?.status === "running" || block.running ? "running" : "ok";
1753
1847
  const argsText = block.argumentsText || "";
1754
- const resultText = result?.content || "";
1848
+ const resultText = agent ? agent.output : result?.content || "";
1755
1849
  const resultLines = resultText ? resultText.split("\n").length : 0;
1756
- const hasDetail = argsText !== "" || resultText !== "";
1850
+ const hasDetail = Boolean(agent ? agent.type || agent.task || agent.target || agent.status || agent.prompt || agent.promptUnavailable || resultText : argsText || resultText);
1757
1851
  const resultHint = resultLines ? `${resultLines} 行输出` : state === "running" ? "运行中" : "";
1758
- const summary = [argsText, resultHint].filter(Boolean).join(" · ");
1852
+ const summary = agent ? subagentSummary(agent) : [argsText, resultHint].filter(Boolean).join(" · ");
1759
1853
  const toolKey = `call:${block.id || `${block.name || "tool"}:${idx}`}`;
1760
1854
  const expanded = hasDetail && S.expandedTools.has(toolKey);
1761
1855
  return `<div class="Md3f7G_flowItem" data-chat-flow-kind="tool-call">
@@ -1763,8 +1857,8 @@ function renderBlock(block, msg, idx, messageIndex) {
1763
1857
  <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
1858
  <span class="CY-8Ka_leading"><span class="CY-8Ka_iconIdle">${ICONS.bash}</span></span>
1765
1859
  <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>
1860
+ <span class="CY-8Ka_title">${esc(subagentTitle(agent, block.name || "Tool"))}</span><span class="CY-8Ka_sep"></span>
1861
+ <span class="CY-8Ka_summary ${state === "error" ? "CY-8Ka_errorSummary" : ""}">${esc(summary)}</span>
1768
1862
  </div>
1769
1863
  ${hasDetail ? `<div class="Sxvs8a_root" data-block-index="${idx}" ${expanded ? "" : "hidden"}>${expanded ? renderMessageDetail(msg, idx) : ""}</div>` : ""}
1770
1864
  </div>