dsh-subagent-workspace-ui 1.9.1 → 1.10.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/README.md +16 -1
- package/README.zh.md +16 -1
- package/lib/client.js +1007 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -87,6 +87,13 @@ Working on a checkout instead of the npm release? See [Local checkout (contribut
|
|
|
87
87
|
- Show each child's current **type and model provider/id** (`provider/model`, plus the reasoning effort when the host publishes one) from the host's public projections only — no new RPC, no model-switch UI. See [Type and model](#type-and-model-read-only-projections).
|
|
88
88
|
- Show each child's usage inline, computed with the host's own definitions: `↑ 131.3k (未缓存 39.1k) / ↓ 12.7k · 命中 70% · 104 tps · 3 轮 · 9 步` (English UI: `miss` / `Hit` / `rnds` / `stps`). The `↑` figure is the **billed input** (`uncachedInputTokens + cacheReadTokens + cacheWriteTokens`) and that billed input alone is the cache-hit denominator — exactly what DSH's own composer footer does, so the plugin and the host agree instead of disagreeing by six points. `tps` is `decodeTokens / (decodeMs / 1000)` from the same public `sessionStats` projection. Hovering the row or the floating panel reveals the complete breakdown — total, every bucket, the share, the speed, and the session's LLM / tool / TTFT timings — in the native `title` tooltip.
|
|
89
89
|
|
|
90
|
+
### Prompt preview inside a subagent session
|
|
91
|
+
|
|
92
|
+
- A subagent's prompt is usually one long structured markdown brief (a task book, a review checklist). Each user message inside a child session therefore carries a `预览` / `原文` toggle in the official action row: `预览` renders that message's raw text as markdown in place of the official body, `原文` — pinned to the top-right of the rendered block, next to `展开` while the block is folded — puts the official rendering back. Switching either way never moves the layout, and opening a preview scrolls the block's top into view only when it was not already visible.
|
|
93
|
+
- The renderer is **self-built and text-only**: headings, nested ordered/unordered lists, bold/italic, inline code, fenced code with its language, nested blockquotes, and links whose target is `http(s)` or relative. Every element is created as a text node — no third-party markdown library, no `dangerouslySetInnerHTML`, no `innerHTML` — so an injected `<script>` and a `javascript:` / `data:` link stay inert text.
|
|
94
|
+
- Long bodies start folded and `展开` / `收起` toggles them; a 20 000-character body stays responsive. Activation binds to `pointerdown` (a host that rebuilds the action row between press and release cannot lose it) with a per-message guard against a double toggle, while `click` keeps Enter/Space working.
|
|
95
|
+
- The official `conversation.chat.node` renderer is never taken over: the plugin adds a second, **headless** cell to the same slot, so a host change costs that one cell rather than the conversation. A non-subagent session gets no preview at all, and the text source is documented under [Prompt text](#prompt-text-read-from-the-childs-user-messages).
|
|
96
|
+
|
|
90
97
|
## Screenshot guide
|
|
91
98
|
|
|
92
99
|
Both screenshots were taken on the `1.7.0-dev` line (commit `79d9d0f`) against a real dsh **0.1.6-alpha.2** instance in its dark theme. The session had four background subagents at once — three still running and one already finished — so every row could be shown with its real type, model and usage figures. The pair below is the English UI; the Chinese UI pair is in [`README.zh.md`](README.zh.md).
|
|
@@ -110,10 +117,18 @@ Both screenshots were taken on the `1.7.0-dev` line (commit `79d9d0f`) against a
|
|
|
110
117
|
|
|
111
118
|
The public DSH Web session store exposes subagent summaries that have been discovered in the current browser runtime. It deliberately does not expose a global historical subagent index or a mode for every unvisited child. Therefore this first plugin version manages the discovered catalog; rows whose type is not yet loaded remain visible and searchable and fall back to DSH's retained session navigation. Exact catalog navigation is used automatically as soon as DSH supplies the address and mode.
|
|
112
119
|
|
|
113
|
-
A full persistent workspace-wide archive view requires a host-side catalog RPC (or an upstream DSH API) that enumerates every child address and its mode. The public `SessionSummary`
|
|
120
|
+
A full persistent workspace-wide archive view requires a host-side catalog RPC (or an upstream DSH API) that enumerates every child address and its mode. The public `SessionSummary` exposes no prompt, so the prompt is never read from it: the preview's text comes from the child session's own `user/message` bodies through the same retained binding and is documented in [Prompt text](#prompt-text-read-from-the-childs-user-messages); **type and model** come from the public projections documented in [Type and model](#type-and-model-read-only-projections). Live output and tool/context activity are read from a bound session automatically: on dsh **0.1.2-alpha.2** they are derived from the raw `binding.eventSource` event stream (showing the tool description or target filename), while older hosts (e.g. **0.1.1-rc.2**) fall back to `session.getSnapshot().chat.legacy`. On hosts with the retain contract (**0.1.6-alpha.2** and on) the plugin first **retains** each running child itself — `sessions.binding(id)` merely borrows a binding somebody else retained, so without retaining, only the child the main view had selected ever streamed. Retention is bounded to 8 children (the float first, then the panel's rendered running rows, most recently active first) and is released as soon as a row stops rendering or stops running, when a surface closes, or when the plugin is disposed; a child beyond the cap, and any host without `retain`, falls back to the borrowed binding and then to the durable summary. Capability detection selects the path (never a version check), so the plugin stays forward compatible. The UI is isolated in [`lib/client.js`](lib/client.js), so it can switch to a richer source without changing the panel interaction model.
|
|
114
121
|
|
|
115
122
|
Archive, category and recent-use order are kept in browser `localStorage`; nothing is ever written into the DSH session log.
|
|
116
123
|
|
|
124
|
+
### Prompt text (read from the child's user messages)
|
|
125
|
+
|
|
126
|
+
The prompt shown by the preview is read from the child session's own **`user/message` events**, through the same retained binding the live-output features use, and only for a session the plugin is currently rendering. `SessionSummary.prompt` is still not a source — that projection carries no prompt at all:
|
|
127
|
+
|
|
128
|
+
- A message node is matched to its record **by message id**; the node key's `<anchor>` is a feed counter, not `event.seq`, and is used only when a key carries no id at all. Positional filling never crosses into an addressable node.
|
|
129
|
+
- A node whose own record is outside the retained event window is not guessed from a neighbour: that node's **own** body is read from the DOM, and wherever the record is available the result is asserted against it (length plus a rolling hash).
|
|
130
|
+
- Reading the DOM merges every body fragment of that node in document order — marked and unmarked — through a walk that drops chrome (clock, action buttons, card title, footer). Nothing is written back: the session log is never modified, and archive/category state stays in `localStorage`.
|
|
131
|
+
|
|
117
132
|
### Type and model (read-only projections)
|
|
118
133
|
|
|
119
134
|
A child's type and model come from public host projections. The plugin adds no RPC and writes no state:
|
package/README.zh.md
CHANGED
|
@@ -199,6 +199,13 @@ http://127.0.0.1:3080
|
|
|
199
199
|
- **类型与模型**:`provider/model`(宿主发布时附推理档位)只读宿主公开投影,类型与模型各自独立降级;不新增任何 RPC,也不提供模型切换入口。详见「类型与模型(只读投影)」。
|
|
200
200
|
- **行内用量**:卡片详情行与浮窗共用同一行 `↑ 计费输入 (未缓存…) / ↓ 输出 · 命中 n% · tps · 轮数 · 步数`,口径与官方 composer 页脚一致;完整信息(总量、各分桶、命中率、速度、模型/工具/首字耗时)在原生 `title` tooltip 中。
|
|
201
201
|
|
|
202
|
+
### 子代理会话内预览提示词
|
|
203
|
+
|
|
204
|
+
- 子代理收到的提示词往往是一整篇结构化 markdown(任务书、评审要求、核查清单)。因此子会话里每条用户消息在官方操作行多一个 `原文` / `预览` 切换:`预览` 把该消息的原始文本**按 markdown 渲染**成块,`原文` 一键切回官方渲染;「原文」按钮固定在块**右上角**(折叠时旁边还有「展开」)。两态切换不产生布局跳动,点开预览时只在块**原本不在视野内**才把它的顶部滚入视野。
|
|
205
|
+
- 渲染器**完全自研、只创建文本节点**:标题、嵌套有序/无序列表、粗体斜体、行内代码、带语言标注的围栏代码、嵌套引用块,以及目标为 `http(s)` 或相对路径的链接。不引第三方 markdown 库、不用 `dangerouslySetInnerHTML`、不用 `innerHTML`,因此注入的 `<script>` 与 `javascript:` / `data:` 链接都保持惰性文本。
|
|
206
|
+
- 超长正文默认折叠,用「展开」/「收起」切换(20 000 字符量级仍保持可交互)。激活绑在 `pointerdown` 上(宿主即使在按下与抬起之间重建操作行也不会丢激活),每条消息带去重守卫避免重复切换,`click` 则保证 Enter/Space 键盘可用。
|
|
207
|
+
- 官方 `conversation.chat.node` 渲染器**不被接管**:插件只是在同一槽位追加一个**无头单元**,宿主改动该槽最多损失这一个单元、而不会影响对话。非子代理会话完全不出现预览;文本来源见「提示词文本(读自子会话的用户消息)」。
|
|
208
|
+
|
|
202
209
|
## 界面截图
|
|
203
210
|
|
|
204
211
|
两张截图拍摄于 `1.7.0-dev` 分支(commit `79d9d0f`)上真实运行的 dsh **0.1.6-alpha.2** 实例,深色主题。拍摄时同一个会话里并存 4 个后台子代理——3 个仍在运行、1 个已完成,因此每行都能展示真实的类型、模型与用量数字。这里是中文界面;英文界面两张见 [`README.md`](README.md)。
|
|
@@ -226,7 +233,7 @@ http://127.0.0.1:3080
|
|
|
226
233
|
|
|
227
234
|
插件只管理当前 DSH Web 客户端运行时已经发现的子代理目录,不伪造不存在的历史数据。首次加载以 40 条为一页;普通分页可以继续加载,批量时间选择最多扩展到 1000 条。
|
|
228
235
|
|
|
229
|
-
公共 `SessionSummary`
|
|
236
|
+
公共 `SessionSummary` 不保证提供原始提示词或全部历史日志;提示词**不来自**该投影,v1.10.0 起改从子会话自身的 `user/message` 事件正文读取(见「提示词文本(读自子会话的用户消息)」)。**类型与模型**改由宿主的公开投影提供,见下一节;实时输出按能力探测自动切换两种公开接口:
|
|
230
237
|
|
|
231
238
|
```text
|
|
232
239
|
# dsh 0.1.6-alpha.2 起(retain 契约):插件自己创建并持有绑定
|
|
@@ -248,6 +255,14 @@ sessions.binding(childId).session
|
|
|
248
255
|
|
|
249
256
|
如果在当前宿主拿不到对应的实时数据(没有 `retain`、也借不到绑定,或该行超出 8 个保留上限),插件只显示持久化的会话摘要和统计信息。
|
|
250
257
|
|
|
258
|
+
### 提示词文本(读自子会话的用户消息)
|
|
259
|
+
|
|
260
|
+
预览显示的提示词读自子会话**自身的 `user/message` 事件**,走与实时输出相同的保留绑定,且只针对插件当前正在渲染的会话。`SessionSummary.prompt` 仍然不是来源——该投影本就不提供提示词:
|
|
261
|
+
|
|
262
|
+
- 消息节点与记录**按消息 id 匹配**;节点键里的 `<anchor>` 是 feed 锚位计数、不是 `event.seq`,只在「键内没有 id」时才用它兜底。位置填充**绝不**跨到可寻址节点上。
|
|
263
|
+
- 记录不在保留事件窗口内的节点**不做邻居猜测**:直接读该节点**自身**的 DOM 正文;凡记录可取时,都会把抽取结果与记录做等价断言(长度 + 滚动哈希)。
|
|
264
|
+
- 读 DOM 时按文档顺序合并该节点的全部正文片段——标记的与未标记的都算——并经开销过滤遍历丢弃 chrome(时间戳、操作按钮、卡片标题、页脚)。全程**不回写**:会话日志永不被修改,归档/分类状态仍只存在 `localStorage`。
|
|
265
|
+
|
|
251
266
|
### 类型与模型(只读投影)
|
|
252
267
|
|
|
253
268
|
子代理的「类型」与「模型 provider/id」来自宿主的公开投影,不新增任何 RPC、也不写入任何状态:
|
package/lib/client.js
CHANGED
|
@@ -34,6 +34,41 @@ body[data-ds-dark-theme] .dsh-sam-row-hidden{background:rgba(255,255,255,0.03)!i
|
|
|
34
34
|
.dsh-sam-lineage-current{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}
|
|
35
35
|
.dsh-sam-dot.dsh-sam-dot-normal{background:transparent;box-shadow:inset 0 0 0 2px var(--dsw-alias-state-success-primary,#42bd7b)}
|
|
36
36
|
.dsh-sam-dot.dsh-sam-dot-abnormal{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 0 3px rgba(236,19,19,.14)}
|
|
37
|
+
.dsh-sam-prompt-host{display:none}
|
|
38
|
+
.dsh-sam-prompt-hide{display:none!important}
|
|
39
|
+
.dsh-sam-prompt{position:relative;margin-top:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}
|
|
40
|
+
.dsh-sam-prompt-block-hidden{display:none!important}
|
|
41
|
+
.dsh-sam-prompt-bar{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-wrap:wrap;padding:4px 8px 0}
|
|
42
|
+
.dsh-sam-prompt-tab,.dsh-sam-prompt-fold{border:1px solid transparent;background:0 0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:11px;line-height:16px;padding:2px 8px;border-radius:6px;cursor:pointer}
|
|
43
|
+
.dsh-sam-prompt-tab:hover,.dsh-sam-prompt-fold:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
44
|
+
.dsh-sam-prompt-trigger{display:none;align-items:center;height:20px;padding:0 6px;border:0;border-radius:4px;background:0 0;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:11px;line-height:16px;cursor:pointer}
|
|
45
|
+
.dsh-sam-prompt-trigger-on{display:inline-flex}
|
|
46
|
+
.dsh-sam-prompt-trigger:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
47
|
+
.dsh-sam-prompt-notice,.dsh-sam-prompt-cut,.dsh-sam-prompt-head{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
|
|
48
|
+
.dsh-sam-prompt-notice{display:block;padding:8px 10px;white-space:normal}
|
|
49
|
+
.dsh-sam-prompt-head{padding:6px 10px 0;white-space:normal}
|
|
50
|
+
.dsh-sam-prompt-cut{display:block;padding:0 10px 8px;overflow-wrap:anywhere}
|
|
51
|
+
.dsh-sam-prompt-body{padding:8px 10px;overflow-wrap:anywhere;white-space:pre-wrap;font-size:var(--dsh-content-font-size,13px);line-height:1.55}
|
|
52
|
+
.dsh-sam-prompt-body-hidden{display:none}
|
|
53
|
+
.dsh-sam-prompt-folded{max-height:340px;overflow:auto;box-shadow:inset 0 -10px 10px -10px rgba(0,0,0,.25)}
|
|
54
|
+
.dsh-sam-prompt-p{margin:0 0 8px}
|
|
55
|
+
.dsh-sam-prompt-body>:last-child{margin-bottom:0}
|
|
56
|
+
.dsh-sam-prompt-h{font-weight:600;margin:10px 0 6px;color:var(--dsw-alias-label-primary);line-height:1.35}
|
|
57
|
+
.dsh-sam-prompt-h1{font-size:1.28em}
|
|
58
|
+
.dsh-sam-prompt-h2{font-size:1.18em}
|
|
59
|
+
.dsh-sam-prompt-h3{font-size:1.1em}
|
|
60
|
+
.dsh-sam-prompt-h4,.dsh-sam-prompt-h5,.dsh-sam-prompt-h6{font-size:1em;color:var(--dsw-alias-label-secondary)}
|
|
61
|
+
.dsh-sam-prompt-list{margin:6px 0;padding-left:20px}
|
|
62
|
+
.dsh-sam-prompt-li{margin:2px 0}
|
|
63
|
+
.dsh-sam-prompt-list .dsh-sam-prompt-list{margin:2px 0;padding-left:18px}
|
|
64
|
+
.dsh-sam-prompt-quote{border-left:3px solid var(--dsw-alias-border-l2);margin:6px 0;padding:2px 0 2px 10px;color:var(--dsw-alias-label-secondary)}
|
|
65
|
+
.dsh-sam-prompt-quote .dsh-sam-prompt-quote{margin:4px 0}
|
|
66
|
+
.dsh-sam-prompt-rule{border:none;border-top:1px solid var(--dsw-alias-border-l2);margin:10px 0}
|
|
67
|
+
.dsh-sam-prompt-code{margin:8px 0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}
|
|
68
|
+
.dsh-sam-prompt-code-lang{display:block;padding:2px 8px;font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-border-l2);white-space:normal}
|
|
69
|
+
.dsh-sam-prompt-pre{margin:0;padding:8px 10px;overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.5}
|
|
70
|
+
.dsh-sam-prompt-icode{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.92em;background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 4px}
|
|
71
|
+
.dsh-sam-prompt-link{color:var(--dsw-alias-interactive-label-brand);text-decoration:underline;overflow-wrap:anywhere}
|
|
37
72
|
`
|
|
38
73
|
|
|
39
74
|
if (!document.querySelector('style[data-plugin-css="dsh-subagent-workspace-ui"]')) { const tag=document.createElement('style'); tag.dataset.pluginCss='dsh-subagent-workspace-ui'; tag.textContent=css; document.head.appendChild(tag) }
|
|
@@ -185,6 +220,265 @@ function LiveOutput({t:tr,parentId,childId,running,prefix=true,autoLoad=true,onL
|
|
|
185
220
|
|
|
186
221
|
const elapsed=(tr,stamp)=>{const seconds=Math.max(0,Math.floor((Date.now()-(stamp||Date.now()))/1000));return seconds>=3600?tr('elapsed.hm',{h:Math.floor(seconds/3600),m:Math.floor(seconds%3600/60)}):seconds>=60?tr('elapsed.ms',{m:Math.floor(seconds/60),s:seconds%60}):tr('elapsed.s',{s:seconds})},compactPath=v=>{const value=String(v).replace(/\\/g,'/'),max=72;if(value.length<=max)return value;const parts=value.split('/').filter(Boolean);let result=parts.pop()||value;while(parts.length&&(`.../${parts[parts.length-1]}/${result}`).length<=max)result=`${parts.pop()}/${result}`;return `.../${result}`},toolDetail=v=>{if(!v)return '';try{const value=JSON.parse(v),key=value.description!=null?'description':value.command!=null?'command':value.file_path!=null?'file_path':value.path!=null?'path':value.prompt!=null?'prompt':null,preferred=key?value[key]:undefined;if(preferred)return (key==='file_path'||key==='path'?compactPath: String)(String(preferred).split('\n')[0].slice(0,100));const first=Object.values(value).find(x=>typeof x==='string');return first?first.split('\n')[0].slice(0,100):String(v).slice(0,100)}catch{return String(v).split('\n')[0].slice(0,100)}}
|
|
187
222
|
|
|
223
|
+
const PROMPT_FOLD_CHARS = 2000
|
|
224
|
+
const PROMPT_FOLD_LINES = 40
|
|
225
|
+
const PROMPT_RENDER_MAX_CHARS = 200000
|
|
226
|
+
const PROMPT_MAX_INLINE_DEPTH = 8
|
|
227
|
+
const PROMPT_MAX_HREF_CHARS = 2048
|
|
228
|
+
|
|
229
|
+
const PROMPT_FENCE_RE = /^ {0,3}(`{3,}|~{3,})[ \t]*([^ \t`]*)[^\n]*$/
|
|
230
|
+
const PROMPT_HEAD_RE = /^ {0,3}(#{1,6})[ \t]+(.*?)[ \t]*#*[ \t]*$/
|
|
231
|
+
const PROMPT_RULE_RE = /^ {0,3}(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/
|
|
232
|
+
const PROMPT_ITEM_RE = /^([ \t]*)(?:([-*+])|([0-9]{1,9})([.)]))[ \t]+(.*)$/
|
|
233
|
+
const PROMPT_QUOTE_RE = /^ {0,3}>[ ]?(.*)$/
|
|
234
|
+
const PROMPT_CONT_RE = /^([ \t]*)(.*)$/
|
|
235
|
+
|
|
236
|
+
const PROMPT_CODE_RE = /^`([^`\n]*)`/
|
|
237
|
+
const PROMPT_STRONG_RE = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/
|
|
238
|
+
const PROMPT_EM_RE = /^(\*|_)(?=\S)([\s\S]*?\S)\1/
|
|
239
|
+
const PROMPT_LINK_RE = /^\[([^\]\n]*)\]\(([^()\n\s]*)\)/
|
|
240
|
+
|
|
241
|
+
const promptLead = (line) => {
|
|
242
|
+
const hit = PROMPT_CONT_RE.exec(line)
|
|
243
|
+
return hit ? hit[1].replace(/\t/g, ' ').length : 0
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const promptFenceEnd = (line, mark) => {
|
|
247
|
+
const hit = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line)
|
|
248
|
+
return Boolean(hit) && hit[1][0] === mark[0] && hit[1].length >= mark.length
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const promptBlockStart = (line) =>
|
|
252
|
+
PROMPT_FENCE_RE.test(line) || PROMPT_HEAD_RE.test(line) || PROMPT_RULE_RE.test(line) || PROMPT_ITEM_RE.test(line) || PROMPT_QUOTE_RE.test(line)
|
|
253
|
+
|
|
254
|
+
const promptSafeHref = (raw) => {
|
|
255
|
+
const url = String(raw || '').trim()
|
|
256
|
+
if (!url || url.length > PROMPT_MAX_HREF_CHARS) return ''
|
|
257
|
+
if (url.startsWith('//')) return ''
|
|
258
|
+
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(url)
|
|
259
|
+
if (scheme) return /^https?$/i.test(scheme[1]) ? url : ''
|
|
260
|
+
return url
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const promptInlineInto = (target, text, depth) => {
|
|
264
|
+
if (depth > PROMPT_MAX_INLINE_DEPTH) {
|
|
265
|
+
target.appendChild(document.createTextNode(text))
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
let buffer = ''
|
|
269
|
+
const flush = () => {
|
|
270
|
+
if (!buffer) return
|
|
271
|
+
target.appendChild(document.createTextNode(buffer))
|
|
272
|
+
buffer = ''
|
|
273
|
+
}
|
|
274
|
+
let index = 0
|
|
275
|
+
while (index < text.length) {
|
|
276
|
+
const rest = text.slice(index)
|
|
277
|
+
const code = PROMPT_CODE_RE.exec(rest)
|
|
278
|
+
if (code) {
|
|
279
|
+
flush()
|
|
280
|
+
const node = document.createElement('code')
|
|
281
|
+
node.className = 'dsh-sam-prompt-icode'
|
|
282
|
+
node.textContent = code[1]
|
|
283
|
+
target.appendChild(node)
|
|
284
|
+
index += code[0].length
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
const strong = PROMPT_STRONG_RE.exec(rest)
|
|
288
|
+
if (strong) {
|
|
289
|
+
const strongBefore = index > 0 ? text[index - 1] : ''
|
|
290
|
+
const strongAfter = text[index + strong[0].length] || ''
|
|
291
|
+
const strongInWord = strong[1] === '__' && (/[0-9A-Za-z_]/.test(strongBefore) || /[0-9A-Za-z_]/.test(strongAfter))
|
|
292
|
+
if (!strongInWord) {
|
|
293
|
+
flush()
|
|
294
|
+
const node = document.createElement('strong')
|
|
295
|
+
promptInlineInto(node, strong[2], depth + 1)
|
|
296
|
+
target.appendChild(node)
|
|
297
|
+
index += strong[0].length
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const em = PROMPT_EM_RE.exec(rest)
|
|
302
|
+
if (em) {
|
|
303
|
+
const before = index > 0 ? text[index - 1] : ''
|
|
304
|
+
const after = text[index + em[0].length] || ''
|
|
305
|
+
const insideWord = em[1] === '_' && (/[0-9A-Za-z_]/.test(before) || /[0-9A-Za-z_]/.test(after))
|
|
306
|
+
if (!insideWord) {
|
|
307
|
+
flush()
|
|
308
|
+
const node = document.createElement('em')
|
|
309
|
+
promptInlineInto(node, em[2], depth + 1)
|
|
310
|
+
target.appendChild(node)
|
|
311
|
+
index += em[0].length
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const link = PROMPT_LINK_RE.exec(rest)
|
|
316
|
+
if (link) {
|
|
317
|
+
const href = promptSafeHref(link[2])
|
|
318
|
+
if (href) {
|
|
319
|
+
flush()
|
|
320
|
+
const anchor = document.createElement('a')
|
|
321
|
+
anchor.className = 'dsh-sam-prompt-link'
|
|
322
|
+
anchor.href = href
|
|
323
|
+
anchor.target = '_blank'
|
|
324
|
+
anchor.rel = 'noopener noreferrer nofollow'
|
|
325
|
+
anchor.textContent = link[1]
|
|
326
|
+
target.appendChild(anchor)
|
|
327
|
+
index += link[0].length
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
buffer += text[index]
|
|
332
|
+
index += 1
|
|
333
|
+
}
|
|
334
|
+
flush()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const promptCodeBlock = (lines, lang) => {
|
|
338
|
+
const wrap = document.createElement('div')
|
|
339
|
+
wrap.className = 'dsh-sam-prompt-code'
|
|
340
|
+
if (lang) {
|
|
341
|
+
const label = document.createElement('span')
|
|
342
|
+
label.className = 'dsh-sam-prompt-code-lang'
|
|
343
|
+
label.textContent = lang
|
|
344
|
+
wrap.appendChild(label)
|
|
345
|
+
}
|
|
346
|
+
const pre = document.createElement('pre')
|
|
347
|
+
pre.className = 'dsh-sam-prompt-pre'
|
|
348
|
+
const code = document.createElement('code')
|
|
349
|
+
code.textContent = lines.join('\n')
|
|
350
|
+
pre.appendChild(code)
|
|
351
|
+
wrap.appendChild(pre)
|
|
352
|
+
return wrap
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const promptBlocksInto = (target, text) => {
|
|
356
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n')
|
|
357
|
+
const stack = []
|
|
358
|
+
const container = () => (stack.length ? stack[stack.length - 1].item : target)
|
|
359
|
+
const closeFor = (indent, orEqual) => {
|
|
360
|
+
while (stack.length && (orEqual ? indent <= stack[stack.length - 1].indent : indent < stack[stack.length - 1].indent)) stack.pop()
|
|
361
|
+
}
|
|
362
|
+
const openList = (ordered, start, indent) => {
|
|
363
|
+
const host = container()
|
|
364
|
+
const list = document.createElement(ordered ? 'ol' : 'ul')
|
|
365
|
+
list.className = 'dsh-sam-prompt-list'
|
|
366
|
+
if (ordered && start !== '1') list.setAttribute('start', start)
|
|
367
|
+
host.appendChild(list)
|
|
368
|
+
stack.push({ indent, ordered, list, item: list })
|
|
369
|
+
return stack[stack.length - 1]
|
|
370
|
+
}
|
|
371
|
+
let i = 0
|
|
372
|
+
while (i < lines.length) {
|
|
373
|
+
const line = lines[i]
|
|
374
|
+
if (!line.trim()) {
|
|
375
|
+
i += 1
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
const indent = promptLead(line)
|
|
379
|
+
const fence = PROMPT_FENCE_RE.exec(line)
|
|
380
|
+
if (fence) {
|
|
381
|
+
closeFor(indent, true)
|
|
382
|
+
const mark = fence[1]
|
|
383
|
+
const body = []
|
|
384
|
+
i += 1
|
|
385
|
+
while (i < lines.length && !promptFenceEnd(lines[i], mark)) {
|
|
386
|
+
body.push(lines[i])
|
|
387
|
+
i += 1
|
|
388
|
+
}
|
|
389
|
+
i += 1
|
|
390
|
+
container().appendChild(promptCodeBlock(body, fence[2]))
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
const head = PROMPT_HEAD_RE.exec(line)
|
|
394
|
+
if (head) {
|
|
395
|
+
closeFor(indent, true)
|
|
396
|
+
const level = head[1].length
|
|
397
|
+
const node = document.createElement('div')
|
|
398
|
+
node.className = 'dsh-sam-prompt-h dsh-sam-prompt-h' + level
|
|
399
|
+
node.setAttribute('role', 'heading')
|
|
400
|
+
node.setAttribute('aria-level', String(level))
|
|
401
|
+
promptInlineInto(node, head[2], 0)
|
|
402
|
+
container().appendChild(node)
|
|
403
|
+
i += 1
|
|
404
|
+
continue
|
|
405
|
+
}
|
|
406
|
+
if (PROMPT_RULE_RE.test(line)) {
|
|
407
|
+
closeFor(indent, true)
|
|
408
|
+
const rule = document.createElement('hr')
|
|
409
|
+
rule.className = 'dsh-sam-prompt-rule'
|
|
410
|
+
container().appendChild(rule)
|
|
411
|
+
i += 1
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
const quote = PROMPT_QUOTE_RE.exec(line)
|
|
415
|
+
if (quote) {
|
|
416
|
+
closeFor(indent, true)
|
|
417
|
+
const inner = []
|
|
418
|
+
while (i < lines.length) {
|
|
419
|
+
const hit = PROMPT_QUOTE_RE.exec(lines[i])
|
|
420
|
+
if (hit) {
|
|
421
|
+
inner.push(hit[1])
|
|
422
|
+
i += 1
|
|
423
|
+
continue
|
|
424
|
+
}
|
|
425
|
+
if (!lines[i].trim()) {
|
|
426
|
+
let next = i
|
|
427
|
+
while (next < lines.length && !lines[next].trim()) next += 1
|
|
428
|
+
if (next < lines.length && PROMPT_QUOTE_RE.test(lines[next])) {
|
|
429
|
+
inner.push('')
|
|
430
|
+
i = next
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
break
|
|
435
|
+
}
|
|
436
|
+
const node = document.createElement('blockquote')
|
|
437
|
+
node.className = 'dsh-sam-prompt-quote'
|
|
438
|
+
promptBlocksInto(node, inner.join('\n'))
|
|
439
|
+
container().appendChild(node)
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
const item = PROMPT_ITEM_RE.exec(line)
|
|
443
|
+
if (item) {
|
|
444
|
+
const ordered = item[3] !== undefined
|
|
445
|
+
closeFor(indent, false)
|
|
446
|
+
const top = stack.length ? stack[stack.length - 1] : null
|
|
447
|
+
if (!top || indent > top.indent || top.ordered !== ordered) {
|
|
448
|
+
if (top && indent <= top.indent && top.ordered !== ordered) stack.pop()
|
|
449
|
+
openList(ordered, ordered ? item[3] : '', indent)
|
|
450
|
+
}
|
|
451
|
+
const frame = stack[stack.length - 1]
|
|
452
|
+
const li = document.createElement('li')
|
|
453
|
+
li.className = 'dsh-sam-prompt-li'
|
|
454
|
+
promptInlineInto(li, item[5], 0)
|
|
455
|
+
frame.list.appendChild(li)
|
|
456
|
+
frame.item = li
|
|
457
|
+
i += 1
|
|
458
|
+
continue
|
|
459
|
+
}
|
|
460
|
+
closeFor(indent, true)
|
|
461
|
+
const para = []
|
|
462
|
+
while (i < lines.length) {
|
|
463
|
+
const next = lines[i]
|
|
464
|
+
if (!next.trim() || promptBlockStart(next)) break
|
|
465
|
+
para.push(next)
|
|
466
|
+
i += 1
|
|
467
|
+
}
|
|
468
|
+
const node = document.createElement('div')
|
|
469
|
+
node.className = 'dsh-sam-prompt-p'
|
|
470
|
+
promptInlineInto(node, para.join('\n'), 0)
|
|
471
|
+
container().appendChild(node)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const promptBodyInto = (target, source) => {
|
|
476
|
+
const text = source || ''
|
|
477
|
+
const kept = text.length > PROMPT_RENDER_MAX_CHARS ? text.slice(0, PROMPT_RENDER_MAX_CHARS) : text
|
|
478
|
+
promptBlocksInto(target, kept)
|
|
479
|
+
return text.length - kept.length
|
|
480
|
+
}
|
|
481
|
+
|
|
188
482
|
const liveFromEvents=(tr,entries,running)=>{const tail=s=>{const lines=String(s||'').split('\n').map(x=>x.trim()).filter(Boolean);return lines.length?lines.slice(-2).join('\n'):''},finalOf=blocks=>{const list=Array.isArray(blocks)?blocks:[];for(let i=list.length-1;i>=0;i--){const b=list[i];if(b?.type==='text'||b?.type==='reasoning'){const t=tail(b.text);if(t)return (b.type==='reasoning'?'💭 ':'')+t}if(b?.type==='tool-call'&&b.name)return tr('callTool')+b.name}return ''};let text='',reasoning='',finalText='',hasStream=false,lastKind='text';const pending=[],done=[],context=[],byCall=new Map();for(const entry of entries){if(!entry||entry.type!=='event')continue;const e=entry.event;if(!e||typeof e.type!=='string')continue;switch(e.type){case 'assistant/chunk':{const c=e.data?.chunk;if(!c)break;if(c.type==='text-delta'){text+=c.text||'';hasStream=true;lastKind='text'}else if(c.type==='reasoning-delta'){reasoning+=c.text||'';hasStream=true;lastKind='reasoning'}else if(c.type==='tool-call-delta'&&c.id){let rec=byCall.get(c.id);if(!rec){rec={name:c.name||tr('toolCall'),args:'',complete:false,running:true};byCall.set(c.id,rec);pending.push(rec)}if(c.name)rec.name=c.name;rec.args+=c.argumentsDelta||''}else if(c.type==='block-end'&&c.block?.type==='tool-call'){let rec=byCall.get(c.block.id);if(!rec){rec={name:c.block.name||tr('toolCall'),args:'',complete:false,running:true};byCall.set(c.block.id,rec);pending.push(rec)}if(c.block.name)rec.name=c.block.name;rec.args=c.block.arguments||rec.args;rec.complete=true}break}case 'user/message':{const src=e.data?.source;if(src?.kind==='plugin'||src?.kind==='relay'||src?.kind==='recall')context.push(tr('contextInjection',{name:`${src?.form||src?.plugin||src?.kind}`}));break}case 'assistant/message':{finalText=finalOf(e.data?.message?.content);text='';reasoning='';hasStream=false;break}case 'tool/call':{let rec=byCall.get(e.data?.callId);if(!rec){rec={name:e.data?.name||tr('toolCall'),args:'',complete:false,running:true};byCall.set(e.data?.callId,rec);pending.push(rec)}if(e.data?.name)rec.name=e.data?.name;if(e.data?.arguments!=null){rec.args=e.data?.arguments;rec.complete=true}rec.running=true;break}case 'tool/result':{const rec=byCall.get(e.data?.message?.source?.callId),block=e.data?.message?.content?.[0];if(rec){rec.running=false;done.push({name:rec.name,args:rec.args,complete:rec.complete,isError:block?.isError})}break}default:break}}const open=pending.filter(r=>r.running!==false);return{pendingCount:open.length,activity:[...open.slice(-2).map(r=>`▶ ${r.name||tr('toolCall')}${r.complete&&r.args?` · ${toolDetail(r.args)}`:''}`),...done.slice(-2).map(r=>`${r.isError?'✖':'✓'} ${r.name||tr('toolCall')}${r.complete&&r.args?` · ${toolDetail(r.args)}`:''}`),...context.slice(-2)],streamText:hasStream?((lastKind==='reasoning'?'💭 ':'')+tail(lastKind==='reasoning'?reasoning:text)):'',thinking:hasStream&&lastKind==='reasoning',finalText}}
|
|
189
483
|
|
|
190
484
|
function ActiveFloat({t:tr,rows,openRow,onHide,stopAgent,stopAllAgents,stoppingIds,liveEnabled,onLiveChange,liveCap,asideAddressOf,openAside}){const [,tick]=React.useState(0),[position,setPosition]=React.useState(null),drag=React.useRef(null);React.useEffect(()=>{const timer=setInterval(()=>tick(v=>v+1),1000);return()=>clearInterval(timer)},[]);React.useEffect(()=>{retainDeclare('float',rows.filter(r=>r.running).map(r=>({childId:r.id,parentId:r.parentId,mode:r.mode,updatedAt:r.updatedAt})))},[rows]);React.useEffect(()=>()=>retainClear('float'),[]);const move=e=>{if(!drag.current)return;const {offsetX,offsetY,width,height}=drag.current;setPosition({x:Math.max(8,Math.min(window.innerWidth-width-8,e.clientX-offsetX)),y:Math.max(8,Math.min(window.innerHeight-height-8,e.clientY-offsetY))})},startDrag=e=>{if(e.button!==0||e.target.closest('button,input,label'))return;const rect=e.currentTarget.parentElement.getBoundingClientRect();drag.current={offsetX:e.clientX-rect.left,offsetY:e.clientY-rect.top,width:rect.width,height:rect.height};e.currentTarget.setPointerCapture?.(e.pointerId)},stopDrag=()=>{drag.current=null},dragStyle=position?{left:position.x,top:position.y,right:'auto'}:undefined;const continuableRows=rows.filter(r=>r.mode!=='one-shot');return jsx('aside',{className:'dsh-sam-active-float',style:dragStyle,onPointerMove:move,onPointerUp:stopDrag,onPointerCancel:stopDrag,role:'status',children:[jsxs('div',{className:'dsh-sam-active-head dsh-sam-active-drag-handle',onPointerDown:startDrag,children: [jsx('span',{children:tr('activeFloat.title',{count:rows.length})}),jsxs('div',{className:'dsh-sam-active-actions',children:[continuableRows.length>0&&jsx('button',{type:'button',className:'dsh-sam-stop-all-btn',title:tr('pauseAllActive'),disabled:continuableRows.some(r=>stoppingIds.has(r.id)),onClick:e=>{e.stopPropagation();stopAllAgents(continuableRows)},children:continuableRows.some(r=>stoppingIds.has(r.id))?tr('pausing'):tr('pauseAll')}),jsx('label',{className:'dsh-sam-active-live-toggle',children:[jsx('input',{type:'checkbox',checked:liveEnabled,onChange:e=>onLiveChange(e.target.checked)}),tr('liveOutput')]}),jsx('button',{type:'button',className:'dsh-sam-active-close',onClick:e=>{e.stopPropagation();onHide()},'aria-label':tr('hideActiveFloat'),children:'×'})]})]}),rows.map((row,idx)=>jsxs('div',{className:'dsh-sam-active-item',onClick:()=>openRow(row),children:[jsxs('div',{className:'dsh-sam-active-title',children:[jsx('span',{className:'dsh-sam-active-icon',children:'🧩'}),jsx('span',{className:'dsh-sam-active-name',children:row.name}),jsx('span',{className:'dsh-sam-active-elapsed',children:elapsed(tr,row.createdAt||row.updatedAt)}),asideButton({tr,row,addressOf:asideAddressOf,open:openAside,compact:true}),row.mode!=='one-shot'&&jsx('button',{type:'button',className:'dsh-sam-stop-btn',disabled:stoppingIds.has(row.id),onClick:e=>{e.stopPropagation();stopAgent(row)},'aria-label':tr('pause.named',{name:row.name}),'title':tr('pauseSubagent'),children:stoppingIds.has(row.id)?tr('pausing'):tr('pause')})]}),liveEnabled&&(liveCap<=0||idx<liveCap)&&jsx(LiveOutput,{t:tr,parentId:row.parentId,childId:row.id,running:true,prefix:false}),jsx('div',{className:'dsh-sam-active-model',children:modeModelCompact(tr,row)}),jsx('div',{className:'dsh-sam-active-stats',title:statsUsageTitle(tr,row),children:statsLine(tr,row)})]},row.id))]})}
|
|
@@ -293,8 +587,718 @@ function Manager({t:tr,useSessions,openChild,openSession,refresh,setCatalogOpen,
|
|
|
293
587
|
}
|
|
294
588
|
|
|
295
589
|
const NS='subagent-workspace-ui'
|
|
296
|
-
const zh={"activeFloat.title": "活跃子代理 · {count}", "age.dAgo": "{d} 天前", "age.hmAgo": "{h} 小时 {m} 分钟前", "age.mAgo": "{m} 分钟前", "age.sAgo": "{s} 秒前", "batch": "批量操作", "batch.delete": "批量删除 ({count})", "batch.hide": "批量隐藏 ({count})", "batchDeleteFailed": "批量删除失败:", "batchDeleteRequestFailed": "批量删除请求失败:", "batchUnhide": "批量显示", "callTool": "⚙ 调用工具 ", "categoryNamePrompt": "分类名称", "chip.count": "子代理 {active}/{total}", "clear": "清空", "clearSearch": "清空搜索", "collapseFilters": "⚙ 折叠 ▴", "collapseFiltersFull": "折叠筛选与配置", "command": "命令", "command.named": "⌘ {name}", "confirm.deleteCategory": "确认删除分类“{name}”?", "confirm.deleteMany": "确认永久删除选中的 {count} 个子代理?删除后不可恢复!", "confirm.deleteOne": "确认永久删除子代理“{name}”?删除后不可恢复!", "confirm.deleteRunning": "子代理“{name}”正在运行中,删除将强制终止它。确认继续?", "confirm.pauseNamed": "确认暂停子代理“{name}”?", "confirm.pauseSelected": "确认暂停选中的 {count} 个子代理?", "confirmHideAll": "确认隐藏当前筛选出的全部子代理?", "contextInjection": "上下文注入 · {name}", "continuable": "可继续", "continue": "▶ 继续", "continue.named": "继续 {name}", "continuePending": "继续中", "continuePrompt": "继续", "continueSubagent": "向该子代理发送一条继续指令", "dark": "深色", "delete.named": "删除 {name}", "deleteAll": "一键删除全部", "deleteCategory.named": "删除分类“{name}”", "deleteFailed": "删除失败:", "deleteForever": "永久删除该子代理", "deleteRequestFailed": "删除请求失败:", "deleteTrash": "🗑 删除", "done": "完成", "elapsed.hm": "{h}时{m}分", "elapsed.ms": "{m}分{s}秒", "elapsed.s": "{s}秒", "empty": "没有符合条件的子代理。", "end.abnormal": "异常结束", "end.abnormalNamed": "异常结束 · {name}", "end.normal": "正常结束", "end.normalNamed": "正常结束 · {name}", "expandFilters": "⚙ 展开筛选 ▾", "expandFiltersFull": "展开筛选与配置", "filterSort": "筛选排序", "group.active": "活跃子代理", "group.allResults": "全部结果", "group.other": "其他", "group.otherSessions": "其他会话", "grouping": "分组", "grouping.byCategory": "按分类", "grouping.bySession": "按会话", "grouping.byType": "按类型", "grouping.byWorkspace": "按工作区", "grouping.current": "分组 · {name}", "grouping.none": "不分组", "grouping.optionCategory": "分组:按分类", "grouping.optionNone": "分组:不分组", "grouping.optionSession": "分组:按会话", "grouping.optionType": "分组:按类型", "grouping.optionWorkspace": "分组:按工作区", "head.summary": "当前会话 {session} 个 · 当前工作区 {workspace} 个 · 活跃 {active} 个", "hidden": "已隐藏", "hide": "隐藏", "hideActiveFloat": "隐藏活跃子代理浮窗", "hideAll": "一键全部隐藏", "hideLongInactive": "隐藏长期未活跃", "hideNote": "隐藏仅在列表不显示,不删除会话", "hideOneShot": "隐藏一次性", "hideSelectedTitle": "将选中的子代理隐藏", "hideShort": "⊘ 隐藏", "hideTitle": "隐藏(仅在列表隐藏,不删除会话)", "lastActivity": "最近活动:{time}", "light": "浅色", "liveOutput": "实时输出", "liveOutputPrefix": "实时输出:", "loadHistoryTitle": "加载该子代理的历史最新消息", "loadLatest": "📥 加载最新消息", "loadMore": "加载下一页(剩余 {remaining} 个)", "loading": "⏳ 加载中...", "managerTitle": "子代理管理", "mode.label": "类型:{mode}", "model.label": "模型:{model}", "modelUnknown": "模型未知", "name": "名字", "newAgent": "新子代理:{name}", "newCategory": "+ 新建分类", "noParent": "无父会话", "oneShot": "一次性", "oneShotBadge": "⚡ 一次性", "openInSidebar": "在侧边栏打开", "openInSidebar.named": "在侧边栏打开 {name}", "openInSidebarFailed": "无法在侧边栏打开该子代理会话。", "openInSidebarUnavailable": "该条目没有可用的子代理地址,无法在侧边栏打开", "openManager": "打开子代理管理", "openUnsupported": "当前 dsh 版本不支持打开子代理会话(缺少可用的会话导航接口)。", "pause": "⏸ 暂停", "pause.named": "暂停 {name}", "pauseAll": "⏸ 一键暂停", "pauseAllActive": "一键暂停所有活跃可继续子代理", "pauseSubagent": "暂停子代理", "pausing": "暂停中", "prompt.label": "提示词:{prompt}", "regexPrompt": "匹配正则(例如:^审计|review)", "resetFilters": "重置筛选", "scope": "范围", "scope.allSessions": "全部会话", "scope.allWorkspaces": "全部工作区", "scope.currentSession": "当前会话", "scope.currentWorkspace": "当前工作区", "scope.custom": "自定义范围", "scopeAria": "显示范围", "searchPlaceholder": "搜索名称、标题或工作区;输入 id: 搜索 Session ID", "select.named": "选择 {name}", "selectAll": "全选", "selectHoursPrompt": "选择多少小时前的子代理?", "selectOld": "选择 N 小时前", "session.currentDot": "当前会话 · {name}", "session.currentParen": "当前会话 ({name})", "session.currentWorkspaceDot": "当前工作区 · {name}", "session.named": "会话 · {name}", "showActiveFloat": "显示活跃浮窗", "showDetails": "显示详情", "showHidden": "显示已隐藏", "sort": "排序", "sort.current": "排序 · {name}", "sort.name": "名字", "sort.optionName": "排序:名字", "sort.optionRecent": "排序:最近活跃", "sort.optionType": "排序:类型", "sort.recent": "最近活跃", "sort.type": "类型", "stats.f.billed": "计费输入 Billed input: {value}", "stats.f.cacheRead": "缓存读取 Cache read: {value}", "stats.f.cacheWrite": "缓存写入 Cache write: {value}", "stats.f.hit": "缓存命中 Cache hit: {value}", "stats.f.llm": "模型耗时 LLM time: {value}", "stats.f.output": "输出 Output: {value}", "stats.f.steps": "步数 Steps: {value}", "stats.f.tool": "工具耗时 Tool time: {value}", "stats.f.total": "总量 Total: {value}", "stats.f.tps": "输出速度 TPS: {value}", "stats.f.ttft": "首字延迟 TTFT: {value}", "stats.f.turns": "轮数 Turns: {value}", "stats.f.uncached": "未缓存输入 Uncached input: {value}", "stats.hit": "命中 {percent}%", "stats.steps": "{steps} 步", "stats.titleLine": "子代理用量 Subagent usage", "stats.tps": "{tps} tps", "stats.turns": "{turns} 轮", "stats.usage": "↑ {input} (未缓存 {uncached}) / ↓ {output}", "subagentBg": "子代理背景色", "subagentBgDarkTitle": "深色主题下的子代理背景色", "subagentBgLightTitle": "浅色主题下的子代理背景色", "summary.showing": "显示 {shown}/{total} 个", "tab.all": "全部", "tab.audit": "审计", "tab.implement": "实现", "tab.other": "其他", "tab.planning": "规划", "tab.test": "测试", "thinking": "思考中…", "toolCall": "工具调用", "type": "类型", "typeLoading": "类型待加载", "uncategorized": "未分类", "unhide": "取消隐藏", "unhideSelectedTitle": "取消隐藏选中的子代理", "unhideShort": "👁 显示", "unknown": "未知", "unknownError": "未知错误", "unknownWorkspace": "未知工作区", "workspace.named": "工作区 · {name}"}
|
|
297
|
-
const en={"activeFloat.title": "Active subagents · {count}", "age.dAgo": "{d}d ago", "age.hmAgo": "{h}h {m}m ago", "age.mAgo": "{m}m ago", "age.sAgo": "{s}s ago", "batch": "Batch", "batch.delete": "Delete ({count})", "batch.hide": "Hide ({count})", "batchDeleteFailed": "Batch delete failed: ", "batchDeleteRequestFailed": "Batch delete request failed: ", "batchUnhide": "Unhide", "callTool": "⚙ Call tool ", "categoryNamePrompt": "Category name", "chip.count": "Subagents {active}/{total}", "clear": "Clear", "clearSearch": "Clear search", "collapseFilters": "⚙ Collapse ▴", "collapseFiltersFull": "Collapse filters & options", "command": "Command", "command.named": "⌘ {name}", "confirm.deleteCategory": "Delete category \"{name}\"?", "confirm.deleteMany": "Permanently delete the selected {count} subagents? This cannot be undone!", "confirm.deleteOne": "Permanently delete subagent \"{name}\"? This cannot be undone!", "confirm.deleteRunning": "Subagent \"{name}\" is still running; deleting will force-terminate it. Continue?", "confirm.pauseNamed": "Pause subagent \"{name}\"?", "confirm.pauseSelected": "Pause the selected {count} subagents?", "confirmHideAll": "Hide ALL currently filtered subagents?", "contextInjection": "Context injection · {name}", "continuable": "continuable", "continue": "▶ Continue", "continue.named": "Continue {name}", "continuePending": "Continuing…", "continuePrompt": "continue", "continueSubagent": "Send this subagent a continue instruction", "dark": "Dark", "delete.named": "Delete {name}", "deleteAll": "Delete all", "deleteCategory.named": "Delete category \"{name}\"", "deleteFailed": "Delete failed: ", "deleteForever": "Permanently delete this subagent", "deleteRequestFailed": "Delete request failed: ", "deleteTrash": "🗑 Delete", "done": "Done", "elapsed.hm": "{h}h{m}m", "elapsed.ms": "{m}m{s}s", "elapsed.s": "{s}s", "empty": "No subagents match the current filters.", "end.abnormal": "Ended abnormally", "end.abnormalNamed": "Ended abnormally · {name}", "end.normal": "Ended normally", "end.normalNamed": "Ended normally · {name}", "expandFilters": "⚙ Expand ▾", "expandFiltersFull": "Expand filters & options", "filterSort": "Filter & sort", "group.active": "Active subagents", "group.allResults": "All results", "group.other": "Other", "group.otherSessions": "Other sessions", "grouping": "Grouping", "grouping.byCategory": "by category", "grouping.bySession": "by session", "grouping.byType": "by type", "grouping.byWorkspace": "by workspace", "grouping.current": "Group · {name}", "grouping.none": "none", "grouping.optionCategory": "Group: by category", "grouping.optionNone": "Group: none", "grouping.optionSession": "Group: by session", "grouping.optionType": "Group: by type", "grouping.optionWorkspace": "Group: by workspace", "head.summary": "Current session {session} · Current workspace {workspace} · Active {active}", "hidden": "hidden", "hide": "Hide", "hideActiveFloat": "Hide the active-subagents float", "hideAll": "Hide all", "hideLongInactive": "Hide long-inactive", "hideNote": "Hiding only removes it from the list; the session is not deleted", "hideOneShot": "Hide one-shot", "hideSelectedTitle": "Hide the selected subagents", "hideShort": "⊘ Hide", "hideTitle": "Hide (hides from the list only; the session is not deleted)", "lastActivity": "Last activity: {time}", "light": "Light", "liveOutput": "Live output", "liveOutputPrefix": "Live output: ", "loadHistoryTitle": "Load the latest messages from this subagent’s history", "loadLatest": "📥 Load latest messages", "loadMore": "Load next page ({remaining} left)", "loading": "⏳ Loading...", "managerTitle": "Subagent manager", "mode.label": "Type: {mode}", "model.label": "Model: {model}", "modelUnknown": "model unknown", "name": "name", "newAgent": "New subagent: {name}", "newCategory": "+ New category", "noParent": "no parent session", "oneShot": "one-shot", "oneShotBadge": "⚡ one-shot", "openInSidebar": "Open in the sidebar", "openInSidebar.named": "Open {name} in the sidebar", "openInSidebarFailed": "Unable to open this subagent session in the sidebar.", "openInSidebarUnavailable": "This entry has no subagent address, so it cannot open in the sidebar", "openManager": "Open subagent manager", "openUnsupported": "This dsh version does not expose a session navigation API, so the subagent session cannot be opened.", "pause": "⏸ Pause", "pause.named": "Pause {name}", "pauseAll": "⏸ Pause all", "pauseAllActive": "Pause all active continuable subagents", "pauseSubagent": "Pause subagent", "pausing": "Pausing…", "prompt.label": "Prompt: {prompt}", "regexPrompt": "Match regex (e.g. ^audit|review)", "resetFilters": "Reset filters", "scope": "Scope", "scope.allSessions": "All sessions", "scope.allWorkspaces": "All workspaces", "scope.currentSession": "Current session", "scope.currentWorkspace": "Current workspace", "scope.custom": "Custom scope", "scopeAria": "Scope", "searchPlaceholder": "Search names, titles, or workspaces; type id: to search Session IDs", "select.named": "Select {name}", "selectAll": "Select all", "selectHoursPrompt": "Select subagents from how many hours back?", "selectOld": "Select N hours ago", "session.currentDot": "Current session · {name}", "session.currentParen": "Current session ({name})", "session.currentWorkspaceDot": "Current workspace · {name}", "session.named": "Session · {name}", "showActiveFloat": "Show active float", "showDetails": "Show details", "showHidden": "Show hidden", "sort": "Sort", "sort.current": "Sort · {name}", "sort.name": "name", "sort.optionName": "Sort: name", "sort.optionRecent": "Sort: recent activity", "sort.optionType": "Sort: type", "sort.recent": "recent activity", "sort.type": "type", "stats.f.billed": "Billed input 计费输入: {value}", "stats.f.cacheRead": "Cache read 缓存读取: {value}", "stats.f.cacheWrite": "Cache write 缓存写入: {value}", "stats.f.hit": "Cache hit 缓存命中: {value}", "stats.f.llm": "LLM time 模型耗时: {value}", "stats.f.output": "Output 输出: {value}", "stats.f.steps": "Steps 步数: {value}", "stats.f.tool": "Tool time 工具耗时: {value}", "stats.f.total": "Total 总量: {value}", "stats.f.tps": "TPS 输出速度: {value}", "stats.f.ttft": "TTFT 首字延迟: {value}", "stats.f.turns": "Turns 轮数: {value}", "stats.f.uncached": "Uncached input 未缓存输入: {value}", "stats.hit": "Hit {percent}%", "stats.steps": "{steps} stps", "stats.titleLine": "Subagent usage 子代理用量", "stats.tps": "{tps} tps", "stats.turns": "{turns} rnds", "stats.usage": "↑ {input} (miss {uncached}) / ↓ {output}", "subagentBg": "Subagent background color", "subagentBgDarkTitle": "Subagent background color in dark theme", "subagentBgLightTitle": "Subagent background color in light theme", "summary.showing": "Showing {shown}/{total}", "tab.all": "All", "tab.audit": "audit", "tab.implement": "implement", "tab.other": "Other", "tab.planning": "planning", "tab.test": "test", "thinking": "thinking…", "toolCall": "Tool call", "type": "type", "typeLoading": "type loading…", "uncategorized": "uncategorized", "unhide": "Unhide", "unhideSelectedTitle": "Unhide selected subagents", "unhideShort": "👁 Unhide", "unknown": "unknown", "unknownError": "unknown error", "unknownWorkspace": "Unknown workspace", "workspace.named": "Workspace · {name}"}
|
|
590
|
+
const zh={"activeFloat.title": "活跃子代理 · {count}", "age.dAgo": "{d} 天前", "age.hmAgo": "{h} 小时 {m} 分钟前", "age.mAgo": "{m} 分钟前", "age.sAgo": "{s} 秒前", "batch": "批量操作", "batch.delete": "批量删除 ({count})", "batch.hide": "批量隐藏 ({count})", "batchDeleteFailed": "批量删除失败:", "batchDeleteRequestFailed": "批量删除请求失败:", "batchUnhide": "批量显示", "callTool": "⚙ 调用工具 ", "categoryNamePrompt": "分类名称", "chip.count": "子代理 {active}/{total}", "clear": "清空", "clearSearch": "清空搜索", "collapseFilters": "⚙ 折叠 ▴", "collapseFiltersFull": "折叠筛选与配置", "command": "命令", "command.named": "⌘ {name}", "confirm.deleteCategory": "确认删除分类“{name}”?", "confirm.deleteMany": "确认永久删除选中的 {count} 个子代理?删除后不可恢复!", "confirm.deleteOne": "确认永久删除子代理“{name}”?删除后不可恢复!", "confirm.deleteRunning": "子代理“{name}”正在运行中,删除将强制终止它。确认继续?", "confirm.pauseNamed": "确认暂停子代理“{name}”?", "confirm.pauseSelected": "确认暂停选中的 {count} 个子代理?", "confirmHideAll": "确认隐藏当前筛选出的全部子代理?", "contextInjection": "上下文注入 · {name}", "continuable": "可继续", "continue": "▶ 继续", "continue.named": "继续 {name}", "continuePending": "继续中", "continuePrompt": "继续", "continueSubagent": "向该子代理发送一条继续指令", "dark": "深色", "delete.named": "删除 {name}", "deleteAll": "一键删除全部", "deleteCategory.named": "删除分类“{name}”", "deleteFailed": "删除失败:", "deleteForever": "永久删除该子代理", "deleteRequestFailed": "删除请求失败:", "deleteTrash": "🗑 删除", "done": "完成", "elapsed.hm": "{h}时{m}分", "elapsed.ms": "{m}分{s}秒", "elapsed.s": "{s}秒", "empty": "没有符合条件的子代理。", "end.abnormal": "异常结束", "end.abnormalNamed": "异常结束 · {name}", "end.normal": "正常结束", "end.normalNamed": "正常结束 · {name}", "expandFilters": "⚙ 展开筛选 ▾", "expandFiltersFull": "展开筛选与配置", "filterSort": "筛选排序", "group.active": "活跃子代理", "group.allResults": "全部结果", "group.other": "其他", "group.otherSessions": "其他会话", "grouping": "分组", "grouping.byCategory": "按分类", "grouping.bySession": "按会话", "grouping.byType": "按类型", "grouping.byWorkspace": "按工作区", "grouping.current": "分组 · {name}", "grouping.none": "不分组", "grouping.optionCategory": "分组:按分类", "grouping.optionNone": "分组:不分组", "grouping.optionSession": "分组:按会话", "grouping.optionType": "分组:按类型", "grouping.optionWorkspace": "分组:按工作区", "head.summary": "当前会话 {session} 个 · 当前工作区 {workspace} 个 · 活跃 {active} 个", "hidden": "已隐藏", "hide": "隐藏", "hideActiveFloat": "隐藏活跃子代理浮窗", "hideAll": "一键全部隐藏", "hideLongInactive": "隐藏长期未活跃", "hideNote": "隐藏仅在列表不显示,不删除会话", "hideOneShot": "隐藏一次性", "hideSelectedTitle": "将选中的子代理隐藏", "hideShort": "⊘ 隐藏", "hideTitle": "隐藏(仅在列表隐藏,不删除会话)", "lastActivity": "最近活动:{time}", "light": "浅色", "liveOutput": "实时输出", "liveOutputPrefix": "实时输出:", "loadHistoryTitle": "加载该子代理的历史最新消息", "loadLatest": "📥 加载最新消息", "loadMore": "加载下一页(剩余 {remaining} 个)", "loading": "⏳ 加载中...", "managerTitle": "子代理管理", "mode.label": "类型:{mode}", "model.label": "模型:{model}", "modelUnknown": "模型未知", "name": "名字", "newAgent": "新子代理:{name}", "newCategory": "+ 新建分类", "noParent": "无父会话", "oneShot": "一次性", "oneShotBadge": "⚡ 一次性", "openInSidebar": "在侧边栏打开", "openInSidebar.named": "在侧边栏打开 {name}", "openInSidebarFailed": "无法在侧边栏打开该子代理会话。", "openInSidebarUnavailable": "该条目没有可用的子代理地址,无法在侧边栏打开", "openManager": "打开子代理管理", "openUnsupported": "当前 dsh 版本不支持打开子代理会话(缺少可用的会话导航接口)。", "pause": "⏸ 暂停", "pause.named": "暂停 {name}", "pauseAll": "⏸ 一键暂停", "pauseAllActive": "一键暂停所有活跃可继续子代理", "pauseSubagent": "暂停子代理", "pausing": "暂停中", "prompt.label": "提示词:{prompt}", "prompt.expandCard": "更早的消息未载入;展开该轮卡片后可预览", "prompt.limited": "页面上的正文自身已被截断(以省略号结尾),预览不完整", "prompt.collapse": "收起", "prompt.expand": "展开", "prompt.notLoaded": "更早的消息尚未载入,可用上方「加载更早」后重试", "prompt.preview": "预览", "prompt.previewAria": "展开该消息的渲染预览", "prompt.raw": "原文", "prompt.toggleAria": "切换该消息的显示方式:渲染 Markdown 或查看原文", "prompt.truncated": "内容过长,仅渲染前 {shown} 个字符(共 {total} 个字符)", "prompt.unavailable": "该消息的原文不在当前事件窗口内", "regexPrompt": "匹配正则(例如:^审计|review)", "resetFilters": "重置筛选", "scope": "范围", "scope.allSessions": "全部会话", "scope.allWorkspaces": "全部工作区", "scope.currentSession": "当前会话", "scope.currentWorkspace": "当前工作区", "scope.custom": "自定义范围", "scopeAria": "显示范围", "searchPlaceholder": "搜索名称、标题或工作区;输入 id: 搜索 Session ID", "select.named": "选择 {name}", "selectAll": "全选", "selectHoursPrompt": "选择多少小时前的子代理?", "selectOld": "选择 N 小时前", "session.currentDot": "当前会话 · {name}", "session.currentParen": "当前会话 ({name})", "session.currentWorkspaceDot": "当前工作区 · {name}", "session.named": "会话 · {name}", "showActiveFloat": "显示活跃浮窗", "showDetails": "显示详情", "showHidden": "显示已隐藏", "sort": "排序", "sort.current": "排序 · {name}", "sort.name": "名字", "sort.optionName": "排序:名字", "sort.optionRecent": "排序:最近活跃", "sort.optionType": "排序:类型", "sort.recent": "最近活跃", "sort.type": "类型", "stats.f.billed": "计费输入 Billed input: {value}", "stats.f.cacheRead": "缓存读取 Cache read: {value}", "stats.f.cacheWrite": "缓存写入 Cache write: {value}", "stats.f.hit": "缓存命中 Cache hit: {value}", "stats.f.llm": "模型耗时 LLM time: {value}", "stats.f.output": "输出 Output: {value}", "stats.f.steps": "步数 Steps: {value}", "stats.f.tool": "工具耗时 Tool time: {value}", "stats.f.total": "总量 Total: {value}", "stats.f.tps": "输出速度 TPS: {value}", "stats.f.ttft": "首字延迟 TTFT: {value}", "stats.f.turns": "轮数 Turns: {value}", "stats.f.uncached": "未缓存输入 Uncached input: {value}", "stats.hit": "命中 {percent}%", "stats.steps": "{steps} 步", "stats.titleLine": "子代理用量 Subagent usage", "stats.tps": "{tps} tps", "stats.turns": "{turns} 轮", "stats.usage": "↑ {input} (未缓存 {uncached}) / ↓ {output}", "subagentBg": "子代理背景色", "subagentBgDarkTitle": "深色主题下的子代理背景色", "subagentBgLightTitle": "浅色主题下的子代理背景色", "summary.showing": "显示 {shown}/{total} 个", "tab.all": "全部", "tab.audit": "审计", "tab.implement": "实现", "tab.other": "其他", "tab.planning": "规划", "tab.test": "测试", "thinking": "思考中…", "toolCall": "工具调用", "type": "类型", "typeLoading": "类型待加载", "uncategorized": "未分类", "unhide": "取消隐藏", "unhideSelectedTitle": "取消隐藏选中的子代理", "unhideShort": "👁 显示", "unknown": "未知", "unknownError": "未知错误", "unknownWorkspace": "未知工作区", "workspace.named": "工作区 · {name}"}
|
|
591
|
+
const en={"activeFloat.title": "Active subagents · {count}", "age.dAgo": "{d}d ago", "age.hmAgo": "{h}h {m}m ago", "age.mAgo": "{m}m ago", "age.sAgo": "{s}s ago", "batch": "Batch", "batch.delete": "Delete ({count})", "batch.hide": "Hide ({count})", "batchDeleteFailed": "Batch delete failed: ", "batchDeleteRequestFailed": "Batch delete request failed: ", "batchUnhide": "Unhide", "callTool": "⚙ Call tool ", "categoryNamePrompt": "Category name", "chip.count": "Subagents {active}/{total}", "clear": "Clear", "clearSearch": "Clear search", "collapseFilters": "⚙ Collapse ▴", "collapseFiltersFull": "Collapse filters & options", "command": "Command", "command.named": "⌘ {name}", "confirm.deleteCategory": "Delete category \"{name}\"?", "confirm.deleteMany": "Permanently delete the selected {count} subagents? This cannot be undone!", "confirm.deleteOne": "Permanently delete subagent \"{name}\"? This cannot be undone!", "confirm.deleteRunning": "Subagent \"{name}\" is still running; deleting will force-terminate it. Continue?", "confirm.pauseNamed": "Pause subagent \"{name}\"?", "confirm.pauseSelected": "Pause the selected {count} subagents?", "confirmHideAll": "Hide ALL currently filtered subagents?", "contextInjection": "Context injection · {name}", "continuable": "continuable", "continue": "▶ Continue", "continue.named": "Continue {name}", "continuePending": "Continuing…", "continuePrompt": "continue", "continueSubagent": "Send this subagent a continue instruction", "dark": "Dark", "delete.named": "Delete {name}", "deleteAll": "Delete all", "deleteCategory.named": "Delete category \"{name}\"", "deleteFailed": "Delete failed: ", "deleteForever": "Permanently delete this subagent", "deleteRequestFailed": "Delete request failed: ", "deleteTrash": "🗑 Delete", "done": "Done", "elapsed.hm": "{h}h{m}m", "elapsed.ms": "{m}m{s}s", "elapsed.s": "{s}s", "empty": "No subagents match the current filters.", "end.abnormal": "Ended abnormally", "end.abnormalNamed": "Ended abnormally · {name}", "end.normal": "Ended normally", "end.normalNamed": "Ended normally · {name}", "expandFilters": "⚙ Expand ▾", "expandFiltersFull": "Expand filters & options", "filterSort": "Filter & sort", "group.active": "Active subagents", "group.allResults": "All results", "group.other": "Other", "group.otherSessions": "Other sessions", "grouping": "Grouping", "grouping.byCategory": "by category", "grouping.bySession": "by session", "grouping.byType": "by type", "grouping.byWorkspace": "by workspace", "grouping.current": "Group · {name}", "grouping.none": "none", "grouping.optionCategory": "Group: by category", "grouping.optionNone": "Group: none", "grouping.optionSession": "Group: by session", "grouping.optionType": "Group: by type", "grouping.optionWorkspace": "Group: by workspace", "head.summary": "Current session {session} · Current workspace {workspace} · Active {active}", "hidden": "hidden", "hide": "Hide", "hideActiveFloat": "Hide the active-subagents float", "hideAll": "Hide all", "hideLongInactive": "Hide long-inactive", "hideNote": "Hiding only removes it from the list; the session is not deleted", "hideOneShot": "Hide one-shot", "hideSelectedTitle": "Hide the selected subagents", "hideShort": "⊘ Hide", "hideTitle": "Hide (hides from the list only; the session is not deleted)", "lastActivity": "Last activity: {time}", "light": "Light", "liveOutput": "Live output", "liveOutputPrefix": "Live output: ", "loadHistoryTitle": "Load the latest messages from this subagent’s history", "loadLatest": "📥 Load latest messages", "loadMore": "Load next page ({remaining} left)", "loading": "⏳ Loading...", "managerTitle": "Subagent manager", "mode.label": "Type: {mode}", "model.label": "Model: {model}", "modelUnknown": "model unknown", "name": "name", "newAgent": "New subagent: {name}", "newCategory": "+ New category", "noParent": "no parent session", "oneShot": "one-shot", "oneShotBadge": "⚡ one-shot", "openInSidebar": "Open in the sidebar", "openInSidebar.named": "Open {name} in the sidebar", "openInSidebarFailed": "Unable to open this subagent session in the sidebar.", "openInSidebarUnavailable": "This entry has no subagent address, so it cannot open in the sidebar", "openManager": "Open subagent manager", "openUnsupported": "This dsh version does not expose a session navigation API, so the subagent session cannot be opened.", "pause": "⏸ Pause", "pause.named": "Pause {name}", "pauseAll": "⏸ Pause all", "pauseAllActive": "Pause all active continuable subagents", "pauseSubagent": "Pause subagent", "pausing": "Pausing…", "prompt.label": "Prompt: {prompt}", "prompt.expandCard": "Earlier messages are not loaded; expand this turn card to preview", "prompt.limited": "The body on the page is itself truncated (ends with an ellipsis); this preview is incomplete", "prompt.collapse": "Collapse", "prompt.expand": "Expand", "prompt.notLoaded": "Earlier messages are not loaded yet — use “Load earlier” above, then retry", "prompt.preview": "Preview", "prompt.previewAria": "Open the rendered preview of this message", "prompt.raw": "Raw", "prompt.toggleAria": "Switch this message between rendered Markdown and the raw text", "prompt.truncated": "Too long to render in full: showing the first {shown} of {total} characters", "prompt.unavailable": "This message's raw text is outside the loaded event window", "regexPrompt": "Match regex (e.g. ^audit|review)", "resetFilters": "Reset filters", "scope": "Scope", "scope.allSessions": "All sessions", "scope.allWorkspaces": "All workspaces", "scope.currentSession": "Current session", "scope.currentWorkspace": "Current workspace", "scope.custom": "Custom scope", "scopeAria": "Scope", "searchPlaceholder": "Search names, titles, or workspaces; type id: to search Session IDs", "select.named": "Select {name}", "selectAll": "Select all", "selectHoursPrompt": "Select subagents from how many hours back?", "selectOld": "Select N hours ago", "session.currentDot": "Current session · {name}", "session.currentParen": "Current session ({name})", "session.currentWorkspaceDot": "Current workspace · {name}", "session.named": "Session · {name}", "showActiveFloat": "Show active float", "showDetails": "Show details", "showHidden": "Show hidden", "sort": "Sort", "sort.current": "Sort · {name}", "sort.name": "name", "sort.optionName": "Sort: name", "sort.optionRecent": "Sort: recent activity", "sort.optionType": "Sort: type", "sort.recent": "recent activity", "sort.type": "type", "stats.f.billed": "Billed input 计费输入: {value}", "stats.f.cacheRead": "Cache read 缓存读取: {value}", "stats.f.cacheWrite": "Cache write 缓存写入: {value}", "stats.f.hit": "Cache hit 缓存命中: {value}", "stats.f.llm": "LLM time 模型耗时: {value}", "stats.f.output": "Output 输出: {value}", "stats.f.steps": "Steps 步数: {value}", "stats.f.tool": "Tool time 工具耗时: {value}", "stats.f.total": "Total 总量: {value}", "stats.f.tps": "TPS 输出速度: {value}", "stats.f.ttft": "TTFT 首字延迟: {value}", "stats.f.turns": "Turns 轮数: {value}", "stats.f.uncached": "Uncached input 未缓存输入: {value}", "stats.hit": "Hit {percent}%", "stats.steps": "{steps} stps", "stats.titleLine": "Subagent usage 子代理用量", "stats.tps": "{tps} tps", "stats.turns": "{turns} rnds", "stats.usage": "↑ {input} (miss {uncached}) / ↓ {output}", "subagentBg": "Subagent background color", "subagentBgDarkTitle": "Subagent background color in dark theme", "subagentBgLightTitle": "Subagent background color in light theme", "summary.showing": "Showing {shown}/{total}", "tab.all": "All", "tab.audit": "audit", "tab.implement": "implement", "tab.other": "Other", "tab.planning": "planning", "tab.test": "test", "thinking": "thinking…", "toolCall": "Tool call", "type": "type", "typeLoading": "type loading…", "uncategorized": "uncategorized", "unhide": "Unhide", "unhideSelectedTitle": "Unhide selected subagents", "unhideShort": "👁 Unhide", "unknown": "unknown", "unknownError": "unknown error", "unknownWorkspace": "Unknown workspace", "workspace.named": "Workspace · {name}"}
|
|
592
|
+
|
|
593
|
+
const promptMode = new Map()
|
|
594
|
+
const promptUnfolded = new Set()
|
|
595
|
+
const PROMPT_MODE_RENDERED = 'rendered'
|
|
596
|
+
const PROMPT_MODE_RAW = 'raw'
|
|
597
|
+
const PROMPT_BLOCK_ATTR = 'data-dsh-sam-prompt-block'
|
|
598
|
+
const PROMPT_HIDE_CLASS = 'dsh-sam-prompt-hide'
|
|
599
|
+
const PROMPT_NODE_SELECTOR = '[data-chat-flow-kind="user"], [data-chat-flow-kind="turn-trigger"]'
|
|
600
|
+
const promptNodeIsCard = (node) => node.getAttribute('data-chat-flow-kind') === 'turn-trigger'
|
|
601
|
+
const PROMPT_TRIGGER_ATTR = 'data-dsh-sam-prompt-trigger'
|
|
602
|
+
const PROMPT_TRIGGER_CLASS = 'dsh-sam-prompt-trigger'
|
|
603
|
+
const PROMPT_TRIGGER_ON_CLASS = 'dsh-sam-prompt-trigger-on'
|
|
604
|
+
const PROMPT_PRESS_GUARD_MS = 700
|
|
605
|
+
const PROMPT_BLOCK_HIDDEN_CLASS = 'dsh-sam-prompt-block-hidden'
|
|
606
|
+
const PROMPT_COPY_TEXT = /复制|cop(y|ied)/i
|
|
607
|
+
const PROMPT_ROW_SELECTOR = '[data-clock]'
|
|
608
|
+
const PROMPT_OFFICIAL_SELECTOR = '[data-slot="conversation.chat.node"]'
|
|
609
|
+
const PROMPT_BODY_SELECTOR = '[data-context-text="true"]'
|
|
610
|
+
const PROMPT_CHROME_SELECTOR = '[data-clock],button,time,svg,style,script,[aria-hidden="true"]'
|
|
611
|
+
const PROMPT_CLIPPED_MARK = /(?:…|⋯)\s*$/
|
|
612
|
+
const PROMPT_CARD_COLLAPSED_SELECTOR = '[data-turn-trigger] [aria-expanded="false"]'
|
|
613
|
+
const PROMPT_MAX_ANCHOR_HOPS = 12
|
|
614
|
+
const PROMPT_SYNC_DELAY = 80
|
|
615
|
+
|
|
616
|
+
const promptStoreCurrent = () => sessionsRt?.list?.getSnapshot?.()?.current || ''
|
|
617
|
+
|
|
618
|
+
const promptIsSubagent = (sessionId) => {
|
|
619
|
+
if (!sessionId) return false
|
|
620
|
+
const summary = sessionsRt?.list?.getSnapshot?.()?.byId?.[sessionId]
|
|
621
|
+
return Boolean(summary && (summary.origin === 'subagent' || typeof summary.parentId === 'string'))
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const promptContinueTexts = () => [zh['continuePrompt'], en['continuePrompt']].filter(Boolean)
|
|
625
|
+
|
|
626
|
+
const promptIsPreviewable = (message) => {
|
|
627
|
+
if (!message.text.trim()) return false
|
|
628
|
+
const stamp = message.text.trim()
|
|
629
|
+
for (const candidate of promptContinueTexts()) if (candidate && stamp === candidate) return false
|
|
630
|
+
return true
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const promptSampleHash = (text) => {
|
|
634
|
+
const step = text.length > 512 ? Math.floor(text.length / 8) : 1
|
|
635
|
+
let hash = text.length
|
|
636
|
+
for (let index = 0; index < text.length; index += step) hash = (hash * 31 + text.charCodeAt(index)) | 0
|
|
637
|
+
return String(hash)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const promptNodeKey = (node) => node.getAttribute('data-chat-node-key') || ''
|
|
641
|
+
const promptSeqOfKey = (key) => {
|
|
642
|
+
const hit = /^([0-9]+):/.exec(key)
|
|
643
|
+
return hit ? Number(hit[1]) : null
|
|
644
|
+
}
|
|
645
|
+
const promptIdOfKey = (key) => {
|
|
646
|
+
const cut = key.indexOf(':')
|
|
647
|
+
if (cut < 0) return ''
|
|
648
|
+
return key.slice(cut + 1).replace(/^[a-z][a-z-]*/, '')
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const promptMessagesFromWindow = (win) => {
|
|
652
|
+
const out = []
|
|
653
|
+
const entries = win && Array.isArray(win.entries) ? win.entries : []
|
|
654
|
+
for (const entry of entries) {
|
|
655
|
+
if (!entry || entry.type !== 'event') continue
|
|
656
|
+
const event = entry.event
|
|
657
|
+
if (!event || event.type !== 'user/message' || event.surfaceOp !== 'append') continue
|
|
658
|
+
const data = event.data
|
|
659
|
+
if (!data) continue
|
|
660
|
+
const blocks = Array.isArray(data.content) ? data.content : Array.isArray(data.message?.content) ? data.message.content : []
|
|
661
|
+
let text = ''
|
|
662
|
+
let textOnly = true
|
|
663
|
+
for (const block of blocks) {
|
|
664
|
+
if (!block) continue
|
|
665
|
+
const kind = typeof block.type === 'string' ? block.type : ''
|
|
666
|
+
if (kind && kind !== 'text') {
|
|
667
|
+
textOnly = false
|
|
668
|
+
continue
|
|
669
|
+
}
|
|
670
|
+
if (typeof block.text === 'string') text += block.text
|
|
671
|
+
}
|
|
672
|
+
const source = data.source
|
|
673
|
+
out.push({
|
|
674
|
+
seq: typeof event.seq === 'number' ? event.seq : null,
|
|
675
|
+
id: typeof data.id === 'string' ? data.id : '',
|
|
676
|
+
kind: typeof source?.kind === 'string' ? source.kind : '',
|
|
677
|
+
text,
|
|
678
|
+
textOnly,
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
return out
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const promptContainerFor = (anchor, sessionId) => {
|
|
685
|
+
if (sessionId) {
|
|
686
|
+
const bodies = document.querySelectorAll('[data-conversation-content][data-conversation-session]')
|
|
687
|
+
for (const body of Array.from(bodies)) {
|
|
688
|
+
if (body.getAttribute('data-conversation-session') !== sessionId) continue
|
|
689
|
+
const scroll = body.querySelector('[data-conversation-scroll]')
|
|
690
|
+
if (scroll) return scroll
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
let node = anchor ? anchor.parentElement : null
|
|
694
|
+
let hops = 0
|
|
695
|
+
while (node && hops < PROMPT_MAX_ANCHOR_HOPS) {
|
|
696
|
+
const found = node.querySelectorAll('[data-conversation-scroll]')
|
|
697
|
+
if (found.length === 1) return found[0]
|
|
698
|
+
if (found.length > 1) break
|
|
699
|
+
node = node.parentElement
|
|
700
|
+
hops += 1
|
|
701
|
+
}
|
|
702
|
+
const all = document.querySelectorAll('[data-conversation-scroll]')
|
|
703
|
+
return all.length === 1 ? all[0] : null
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const promptScrollerFor = (el) => {
|
|
707
|
+
let node = el.parentElement
|
|
708
|
+
let hops = 0
|
|
709
|
+
while (node && hops < PROMPT_MAX_ANCHOR_HOPS) {
|
|
710
|
+
const overflow = window.getComputedStyle(node).overflowY
|
|
711
|
+
const scrolls = overflow === 'auto' || overflow === 'scroll' || overflow === 'overlay'
|
|
712
|
+
if (scrolls && node.scrollHeight > node.clientHeight + 1) return node
|
|
713
|
+
node = node.parentElement
|
|
714
|
+
hops += 1
|
|
715
|
+
}
|
|
716
|
+
const doc = document.scrollingElement
|
|
717
|
+
if (doc && doc.scrollHeight > doc.clientHeight + 1) return doc
|
|
718
|
+
return null
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const promptRevealBlock = (block) => {
|
|
722
|
+
if (!block) return
|
|
723
|
+
if (block.getAttribute('data-dsh-sam-prompt-state') !== PROMPT_MODE_RENDERED) return
|
|
724
|
+
const scroller = promptScrollerFor(block)
|
|
725
|
+
if (!scroller) return
|
|
726
|
+
const box = block.getBoundingClientRect()
|
|
727
|
+
if (!box.width && !box.height) return
|
|
728
|
+
const frame = scroller.getBoundingClientRect()
|
|
729
|
+
if (box.top >= frame.top && box.top <= frame.bottom) return
|
|
730
|
+
scroller.scrollTop += box.top - frame.top
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const promptActivateTrigger = (key, node, tr) => {
|
|
734
|
+
promptSetMode(key, PROMPT_MODE_RENDERED, tr)
|
|
735
|
+
promptRevealBlock(node ? node.querySelector('[data-dsh-sam-prompt-block]') : null)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const promptTriggerPress = new Map()
|
|
739
|
+
|
|
740
|
+
const promptChromeNode = (el) => el.matches(PROMPT_CHROME_SELECTOR) || promptIsOurs(el)
|
|
741
|
+
|
|
742
|
+
const promptTextClipped = (text) => PROMPT_CLIPPED_MARK.test(text)
|
|
743
|
+
|
|
744
|
+
const promptCollapsedCard = (node) => Boolean(node.querySelector(PROMPT_CARD_COLLAPSED_SELECTOR))
|
|
745
|
+
|
|
746
|
+
const promptTextOf = (el) => {
|
|
747
|
+
let out = ''
|
|
748
|
+
const walk = (node) => {
|
|
749
|
+
if (node.nodeType === 3) {
|
|
750
|
+
out += node.nodeValue || ''
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
if (!(node instanceof Element)) return
|
|
754
|
+
if (promptChromeNode(node)) return
|
|
755
|
+
for (const child of Array.from(node.childNodes)) walk(child)
|
|
756
|
+
}
|
|
757
|
+
walk(el)
|
|
758
|
+
return out
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const promptLongestLeafNode = (root) => {
|
|
762
|
+
let best = null
|
|
763
|
+
let bestLength = 0
|
|
764
|
+
const visit = (node) => {
|
|
765
|
+
if (!(node instanceof Element)) return
|
|
766
|
+
if (promptChromeNode(node)) return
|
|
767
|
+
let branch = false
|
|
768
|
+
for (const child of Array.from(node.children)) {
|
|
769
|
+
if (!promptChromeNode(child)) branch = true
|
|
770
|
+
}
|
|
771
|
+
if (!branch) {
|
|
772
|
+
const length = promptTextOf(node).trim().length
|
|
773
|
+
if (length > bestLength) {
|
|
774
|
+
bestLength = length
|
|
775
|
+
best = node
|
|
776
|
+
}
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
for (const child of Array.from(node.children)) visit(child)
|
|
780
|
+
}
|
|
781
|
+
for (const child of Array.from(root.children)) visit(child)
|
|
782
|
+
return best
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const promptCommonAncestor = (els) => {
|
|
786
|
+
let region = els.length ? els[0].parentElement : null
|
|
787
|
+
while (region) {
|
|
788
|
+
let covers = true
|
|
789
|
+
for (const el of els) {
|
|
790
|
+
if (el !== region && !region.contains(el)) {
|
|
791
|
+
covers = false
|
|
792
|
+
break
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (covers) return region
|
|
796
|
+
region = region.parentElement
|
|
797
|
+
}
|
|
798
|
+
return null
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const promptOfficialText = (node) => {
|
|
802
|
+
const marked = []
|
|
803
|
+
for (const el of Array.from(node.querySelectorAll(PROMPT_BODY_SELECTOR))) {
|
|
804
|
+
if (promptIsOurs(el)) continue
|
|
805
|
+
if ((el.textContent || '').trim()) marked.push(el)
|
|
806
|
+
}
|
|
807
|
+
if (marked.length) {
|
|
808
|
+
const region = promptCommonAncestor(marked)
|
|
809
|
+
if (region && region !== node) {
|
|
810
|
+
const merged = promptTextOf(region)
|
|
811
|
+
if (merged.trim()) return merged
|
|
812
|
+
}
|
|
813
|
+
const parts = []
|
|
814
|
+
for (const el of marked) {
|
|
815
|
+
const part = promptTextOf(el)
|
|
816
|
+
if (part.trim()) parts.push(part)
|
|
817
|
+
}
|
|
818
|
+
return parts.join('\n\n')
|
|
819
|
+
}
|
|
820
|
+
const leaf = promptLongestLeafNode(node)
|
|
821
|
+
if (leaf) {
|
|
822
|
+
const parent = leaf.parentElement
|
|
823
|
+
if (parent && parent !== node && !promptChromeNode(parent)) {
|
|
824
|
+
const outer = promptTextOf(parent)
|
|
825
|
+
if (outer.trim().length >= promptTextOf(leaf).trim().length) return outer
|
|
826
|
+
}
|
|
827
|
+
return promptTextOf(leaf)
|
|
828
|
+
}
|
|
829
|
+
return promptTextOf(node)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const promptIsOurs = (el) => el.hasAttribute(PROMPT_BLOCK_ATTR) || el.hasAttribute(PROMPT_TRIGGER_ATTR)
|
|
833
|
+
|
|
834
|
+
const promptButtonLabel = (button) => String(button.getAttribute('aria-label') || '') + ' ' + String(button.getAttribute('title') || '')
|
|
835
|
+
|
|
836
|
+
const promptCopyButton = (node) => {
|
|
837
|
+
const buttons = Array.from(node.querySelectorAll('button')).filter(button => !promptIsOurs(button))
|
|
838
|
+
for (const button of buttons) {
|
|
839
|
+
if (PROMPT_COPY_TEXT.test(promptButtonLabel(button))) return button
|
|
840
|
+
}
|
|
841
|
+
return null
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const promptActionAnchor = (node) => {
|
|
845
|
+
const copy = promptCopyButton(node)
|
|
846
|
+
if (copy) return copy
|
|
847
|
+
const row = node.querySelector(PROMPT_ROW_SELECTOR)
|
|
848
|
+
if (!row) return null
|
|
849
|
+
const buttons = Array.from(row.querySelectorAll('button')).filter(button => !promptIsOurs(button))
|
|
850
|
+
return buttons.length ? buttons[buttons.length - 1] : row
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const promptApplyTrigger = (trigger, tr) => {
|
|
854
|
+
const key = trigger.getAttribute('data-dsh-sam-prompt-key') || ''
|
|
855
|
+
const state = promptMode.get(key) === PROMPT_MODE_RAW ? PROMPT_MODE_RAW : PROMPT_MODE_RENDERED
|
|
856
|
+
trigger.setAttribute('data-dsh-sam-prompt-state', state)
|
|
857
|
+
trigger.classList.toggle(PROMPT_TRIGGER_ON_CLASS, state === PROMPT_MODE_RAW)
|
|
858
|
+
const label = tr('prompt.previewAria')
|
|
859
|
+
if (trigger.textContent !== tr('prompt.preview')) trigger.textContent = tr('prompt.preview')
|
|
860
|
+
if (trigger.getAttribute('aria-label') !== label) trigger.setAttribute('aria-label', label)
|
|
861
|
+
if (trigger.getAttribute('title') !== label) trigger.setAttribute('title', label)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const promptRowChild = (node, el) => {
|
|
865
|
+
const row = node.querySelector(PROMPT_ROW_SELECTOR)
|
|
866
|
+
if (!row) return null
|
|
867
|
+
let current = el
|
|
868
|
+
while (current && current.parentElement && current.parentElement !== row) current = current.parentElement
|
|
869
|
+
if (!current || current.parentElement !== row) return null
|
|
870
|
+
return current
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const promptTriggerFor = (key, node, tr) => {
|
|
874
|
+
const trigger = document.createElement('button')
|
|
875
|
+
trigger.type = 'button'
|
|
876
|
+
trigger.className = PROMPT_TRIGGER_CLASS
|
|
877
|
+
trigger.setAttribute(PROMPT_TRIGGER_ATTR, '')
|
|
878
|
+
trigger.setAttribute('data-dsh-sam-prompt-key', key)
|
|
879
|
+
promptApplyTrigger(trigger, tr)
|
|
880
|
+
trigger.addEventListener('mousedown', event => event.stopPropagation())
|
|
881
|
+
trigger.addEventListener('pointerdown', event => {
|
|
882
|
+
promptTriggerPress.set(key, Date.now())
|
|
883
|
+
promptActivateTrigger(key, node, tr)
|
|
884
|
+
})
|
|
885
|
+
trigger.addEventListener('click', event => {
|
|
886
|
+
event.preventDefault()
|
|
887
|
+
event.stopPropagation()
|
|
888
|
+
if (Date.now() - (promptTriggerPress.get(key) || 0) < PROMPT_PRESS_GUARD_MS) return
|
|
889
|
+
promptActivateTrigger(key, node, tr)
|
|
890
|
+
})
|
|
891
|
+
const anchor = promptActionAnchor(node)
|
|
892
|
+
const rowChild = anchor ? promptRowChild(node, anchor) : null
|
|
893
|
+
if (rowChild) rowChild.after(trigger)
|
|
894
|
+
else if (anchor && anchor.tagName === 'BUTTON') anchor.after(trigger)
|
|
895
|
+
else if (anchor) anchor.appendChild(trigger)
|
|
896
|
+
else {
|
|
897
|
+
const official = node.querySelector(PROMPT_OFFICIAL_SELECTOR)
|
|
898
|
+
if (official) official.appendChild(trigger)
|
|
899
|
+
else node.appendChild(trigger)
|
|
900
|
+
}
|
|
901
|
+
return trigger
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const promptBlockFor = (spec) => {
|
|
905
|
+
const tr = spec.tr
|
|
906
|
+
const wrapper = document.createElement('div')
|
|
907
|
+
wrapper.className = 'dsh-sam-prompt'
|
|
908
|
+
wrapper.setAttribute(PROMPT_BLOCK_ATTR, '')
|
|
909
|
+
wrapper.setAttribute('data-dsh-sam-prompt-key', spec.key)
|
|
910
|
+
wrapper.setAttribute('data-dsh-sam-prompt-source', spec.source)
|
|
911
|
+
wrapper.setAttribute('data-dsh-sam-prompt-sig', promptBlockSignature(spec))
|
|
912
|
+
const bar = document.createElement('div')
|
|
913
|
+
bar.className = 'dsh-sam-prompt-bar'
|
|
914
|
+
if (!spec.text) {
|
|
915
|
+
wrapper.setAttribute('data-dsh-sam-prompt-kind', 'notice')
|
|
916
|
+
wrapper.setAttribute('data-dsh-sam-prompt-state', 'notice')
|
|
917
|
+
const note = document.createElement('span')
|
|
918
|
+
note.className = 'dsh-sam-prompt-notice'
|
|
919
|
+
note.textContent = spec.notice || tr('prompt.unavailable')
|
|
920
|
+
wrapper.appendChild(note)
|
|
921
|
+
promptApplyState(wrapper, spec.tr)
|
|
922
|
+
return wrapper
|
|
923
|
+
}
|
|
924
|
+
if (spec.head) {
|
|
925
|
+
const head = document.createElement('div')
|
|
926
|
+
head.className = 'dsh-sam-prompt-head'
|
|
927
|
+
head.textContent = spec.head
|
|
928
|
+
wrapper.appendChild(head)
|
|
929
|
+
}
|
|
930
|
+
wrapper.setAttribute('data-dsh-sam-prompt-kind', 'preview')
|
|
931
|
+
const lines = spec.text.split('\n').length
|
|
932
|
+
const foldable = spec.text.length > PROMPT_FOLD_CHARS || lines > PROMPT_FOLD_LINES
|
|
933
|
+
wrapper.setAttribute('data-dsh-sam-prompt-foldable', foldable ? 'true' : 'false')
|
|
934
|
+
wrapper.setAttribute('data-dsh-sam-prompt-limited', spec.limited ? 'true' : 'false')
|
|
935
|
+
wrapper.setAttribute('data-dsh-sam-prompt-hide-official', spec.textOnly ? 'true' : 'false')
|
|
936
|
+
const raw = document.createElement('button')
|
|
937
|
+
raw.type = 'button'
|
|
938
|
+
raw.className = 'dsh-sam-prompt-tab'
|
|
939
|
+
raw.setAttribute('data-dsh-sam-prompt-mode', PROMPT_MODE_RAW)
|
|
940
|
+
raw.title = tr('prompt.toggleAria')
|
|
941
|
+
raw.textContent = tr('prompt.raw')
|
|
942
|
+
bar.appendChild(raw)
|
|
943
|
+
if (foldable) {
|
|
944
|
+
const fold = document.createElement('button')
|
|
945
|
+
fold.type = 'button'
|
|
946
|
+
fold.className = 'dsh-sam-prompt-fold'
|
|
947
|
+
fold.setAttribute('data-dsh-sam-prompt-fold', '')
|
|
948
|
+
bar.appendChild(fold)
|
|
949
|
+
}
|
|
950
|
+
const body = document.createElement('div')
|
|
951
|
+
body.className = 'dsh-sam-prompt-body'
|
|
952
|
+
body.setAttribute('data-dsh-sam-prompt-body', '')
|
|
953
|
+
const dropped = promptBodyInto(body, spec.text)
|
|
954
|
+
if (dropped > 0) {
|
|
955
|
+
const cut = document.createElement('span')
|
|
956
|
+
cut.className = 'dsh-sam-prompt-cut'
|
|
957
|
+
cut.textContent = tr('prompt.truncated', { shown: String(PROMPT_RENDER_MAX_CHARS), total: String(spec.text.length) })
|
|
958
|
+
wrapper.appendChild(cut)
|
|
959
|
+
}
|
|
960
|
+
if (spec.limited) {
|
|
961
|
+
const clipped = document.createElement('span')
|
|
962
|
+
clipped.className = 'dsh-sam-prompt-cut dsh-sam-prompt-limited'
|
|
963
|
+
clipped.textContent = tr('prompt.limited')
|
|
964
|
+
wrapper.appendChild(clipped)
|
|
965
|
+
}
|
|
966
|
+
wrapper.appendChild(bar)
|
|
967
|
+
wrapper.appendChild(body)
|
|
968
|
+
wrapper.addEventListener('mousedown', event => event.stopPropagation())
|
|
969
|
+
wrapper.addEventListener('click', event => {
|
|
970
|
+
const target = event.target instanceof Element ? event.target : null
|
|
971
|
+
if (!target) return
|
|
972
|
+
const mode = target.closest('[data-dsh-sam-prompt-mode]')
|
|
973
|
+
if (mode) {
|
|
974
|
+
event.preventDefault()
|
|
975
|
+
event.stopPropagation()
|
|
976
|
+
promptSetMode(spec.key, mode.getAttribute('data-dsh-sam-prompt-mode') || PROMPT_MODE_RENDERED, spec.tr)
|
|
977
|
+
promptRevealBlock(wrapper)
|
|
978
|
+
return
|
|
979
|
+
}
|
|
980
|
+
if (target.closest('[data-dsh-sam-prompt-fold]')) {
|
|
981
|
+
event.preventDefault()
|
|
982
|
+
event.stopPropagation()
|
|
983
|
+
promptToggleFold(spec.key, spec.tr)
|
|
984
|
+
}
|
|
985
|
+
})
|
|
986
|
+
promptApplyState(wrapper, spec.tr)
|
|
987
|
+
return wrapper
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const promptBlockSignature = (spec) => {
|
|
991
|
+
const kind = spec.text ? 'preview' : 'notice'
|
|
992
|
+
const text = spec.text ? promptSampleHash(spec.text) : ''
|
|
993
|
+
return [kind, spec.source, spec.text.length, text, spec.textOnly ? 't' : 'm', spec.limited ? 'c' : '', spec.notice, spec.head, spec.tr('prompt.raw')].join('|')
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const promptApplyState = (block, tr) => {
|
|
997
|
+
const key = block.getAttribute('data-dsh-sam-prompt-key') || ''
|
|
998
|
+
const state = promptMode.get(key) === PROMPT_MODE_RAW ? PROMPT_MODE_RAW : PROMPT_MODE_RENDERED
|
|
999
|
+
const rendered = state === PROMPT_MODE_RENDERED
|
|
1000
|
+
block.setAttribute('data-dsh-sam-prompt-state', state)
|
|
1001
|
+
block.classList.toggle(PROMPT_BLOCK_HIDDEN_CLASS, !rendered)
|
|
1002
|
+
const body = block.querySelector('[data-dsh-sam-prompt-body]')
|
|
1003
|
+
if (body) {
|
|
1004
|
+
const foldable = block.getAttribute('data-dsh-sam-prompt-foldable') === 'true'
|
|
1005
|
+
const folded = foldable && !promptUnfolded.has(key)
|
|
1006
|
+
body.classList.toggle('dsh-sam-prompt-body-hidden', !rendered)
|
|
1007
|
+
body.classList.toggle('dsh-sam-prompt-folded', folded)
|
|
1008
|
+
const fold = block.querySelector('[data-dsh-sam-prompt-fold]')
|
|
1009
|
+
if (fold) fold.textContent = folded ? tr('prompt.expand') : tr('prompt.collapse')
|
|
1010
|
+
}
|
|
1011
|
+
const tab = block.querySelector('[data-dsh-sam-prompt-mode="' + PROMPT_MODE_RAW + '"]')
|
|
1012
|
+
if (tab) tab.textContent = tr('prompt.raw')
|
|
1013
|
+
const attachPoint = block.parentElement
|
|
1014
|
+
if (!attachPoint) return
|
|
1015
|
+
const hide = rendered && block.getAttribute('data-dsh-sam-prompt-hide-official') === 'true'
|
|
1016
|
+
for (const child of Array.from(attachPoint.children)) {
|
|
1017
|
+
if (child === block || promptIsOurs(child)) continue
|
|
1018
|
+
child.classList.toggle(PROMPT_HIDE_CLASS, hide)
|
|
1019
|
+
}
|
|
1020
|
+
for (const trigger of Array.from(attachPoint.querySelectorAll('[' + PROMPT_TRIGGER_ATTR + ']'))) {
|
|
1021
|
+
if (trigger.getAttribute('data-dsh-sam-prompt-key') === key) promptApplyTrigger(trigger, tr)
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const promptRefreshKey = (key, tr) => {
|
|
1026
|
+
const blocks = document.querySelectorAll('[' + PROMPT_BLOCK_ATTR + ']')
|
|
1027
|
+
for (const block of Array.from(blocks)) {
|
|
1028
|
+
if (block.getAttribute('data-dsh-sam-prompt-key') !== key) continue
|
|
1029
|
+
promptApplyState(block, tr)
|
|
1030
|
+
}
|
|
1031
|
+
const triggers = document.querySelectorAll('[' + PROMPT_TRIGGER_ATTR + ']')
|
|
1032
|
+
for (const trigger of Array.from(triggers)) {
|
|
1033
|
+
if (trigger.getAttribute('data-dsh-sam-prompt-key') !== key) continue
|
|
1034
|
+
promptApplyTrigger(trigger, tr)
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const promptSetMode = (key, mode, tr) => {
|
|
1039
|
+
if (mode !== PROMPT_MODE_RAW && mode !== PROMPT_MODE_RENDERED) return
|
|
1040
|
+
promptMode.set(key, mode)
|
|
1041
|
+
promptRefreshKey(key, tr)
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const promptToggleFold = (key, tr) => {
|
|
1045
|
+
if (promptUnfolded.has(key)) promptUnfolded.delete(key)
|
|
1046
|
+
else promptUnfolded.add(key)
|
|
1047
|
+
promptRefreshKey(key, tr)
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const promptSyncs = new Set()
|
|
1051
|
+
const promptPreviewRefresh = () => {
|
|
1052
|
+
for (const sync of Array.from(promptSyncs)) {
|
|
1053
|
+
try {
|
|
1054
|
+
sync()
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
console.warn('subagent-workspace-ui: unable to refresh the prompt preview', error)
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const promptPreviewAttach = (anchor, propSessionId, translate) => {
|
|
1062
|
+
const blocks = new Map()
|
|
1063
|
+
const triggers = new Map()
|
|
1064
|
+
const hidden = new Set()
|
|
1065
|
+
let container = null
|
|
1066
|
+
let feed = null
|
|
1067
|
+
let unsubscribe = null
|
|
1068
|
+
let disposed = false
|
|
1069
|
+
let scheduled = false
|
|
1070
|
+
let timer = null
|
|
1071
|
+
|
|
1072
|
+
const detachFeed = () => {
|
|
1073
|
+
if (unsubscribe) {
|
|
1074
|
+
try {
|
|
1075
|
+
unsubscribe()
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.warn('subagent-workspace-ui: unable to release the event feed subscription', error)
|
|
1078
|
+
}
|
|
1079
|
+
unsubscribe = null
|
|
1080
|
+
}
|
|
1081
|
+
feed = null
|
|
1082
|
+
}
|
|
1083
|
+
const restoreOfficial = () => {
|
|
1084
|
+
for (const child of Array.from(hidden)) child.classList.remove(PROMPT_HIDE_CLASS)
|
|
1085
|
+
hidden.clear()
|
|
1086
|
+
}
|
|
1087
|
+
const dropBlocks = () => {
|
|
1088
|
+
for (const block of Array.from(blocks.values())) if (block.parentNode) block.parentNode.removeChild(block)
|
|
1089
|
+
blocks.clear()
|
|
1090
|
+
for (const trigger of Array.from(triggers.values())) if (trigger.parentNode) trigger.parentNode.removeChild(trigger)
|
|
1091
|
+
triggers.clear()
|
|
1092
|
+
}
|
|
1093
|
+
const clearAll = () => {
|
|
1094
|
+
dropBlocks()
|
|
1095
|
+
restoreOfficial()
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const sync = () => {
|
|
1099
|
+
if (disposed) return
|
|
1100
|
+
const tr = translate()
|
|
1101
|
+
const sessionId = propSessionId || promptStoreCurrent()
|
|
1102
|
+
if (!sessionId || !promptIsSubagent(sessionId)) {
|
|
1103
|
+
detachFeed()
|
|
1104
|
+
clearAll()
|
|
1105
|
+
container = null
|
|
1106
|
+
return
|
|
1107
|
+
}
|
|
1108
|
+
const nextContainer = promptContainerFor(anchor, sessionId)
|
|
1109
|
+
if (nextContainer !== container) {
|
|
1110
|
+
clearAll()
|
|
1111
|
+
container = nextContainer
|
|
1112
|
+
}
|
|
1113
|
+
if (!container) return
|
|
1114
|
+
const source = liveBindingFor(sessionId)?.eventSource || null
|
|
1115
|
+
if (source !== feed) {
|
|
1116
|
+
detachFeed()
|
|
1117
|
+
feed = source
|
|
1118
|
+
if (source && typeof source.subscribe === 'function') {
|
|
1119
|
+
const off = source.subscribe(schedule)
|
|
1120
|
+
unsubscribe = typeof off === 'function' ? off : null
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const win = source && typeof source.getSnapshot === 'function' ? source.getSnapshot() || null : null
|
|
1124
|
+
const records = promptMessagesFromWindow(win)
|
|
1125
|
+
const bySeq = new Map()
|
|
1126
|
+
const byId = new Map()
|
|
1127
|
+
for (const message of records) {
|
|
1128
|
+
if (message.seq !== null && !bySeq.has(message.seq)) bySeq.set(message.seq, message)
|
|
1129
|
+
if (message.id && !byId.has(message.id)) byId.set(message.id, message)
|
|
1130
|
+
}
|
|
1131
|
+
const used = new Set()
|
|
1132
|
+
const nodes = Array.from(container.querySelectorAll(PROMPT_NODE_SELECTOR))
|
|
1133
|
+
const addressed = new Set()
|
|
1134
|
+
const mapped = nodes.map((node, index) => {
|
|
1135
|
+
const key = promptNodeKey(node)
|
|
1136
|
+
const id = promptIdOfKey(key)
|
|
1137
|
+
if (id) {
|
|
1138
|
+
addressed.add(index)
|
|
1139
|
+
const hit = byId.get(id)
|
|
1140
|
+
if (hit && !used.has(hit)) {
|
|
1141
|
+
used.add(hit)
|
|
1142
|
+
return hit
|
|
1143
|
+
}
|
|
1144
|
+
return null
|
|
1145
|
+
}
|
|
1146
|
+
const seq = promptSeqOfKey(key)
|
|
1147
|
+
if (seq !== null) {
|
|
1148
|
+
const hit = bySeq.get(seq)
|
|
1149
|
+
if (hit && !used.has(hit)) {
|
|
1150
|
+
used.add(hit)
|
|
1151
|
+
return hit
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return null
|
|
1155
|
+
})
|
|
1156
|
+
const freeNodes = []
|
|
1157
|
+
mapped.forEach((hit, index) => {
|
|
1158
|
+
if (!hit && !addressed.has(index)) freeNodes.push(index)
|
|
1159
|
+
})
|
|
1160
|
+
const freeMessages = records.filter(message => !used.has(message))
|
|
1161
|
+
if (freeMessages.length && freeNodes.length) {
|
|
1162
|
+
const count = Math.min(freeMessages.length, freeNodes.length)
|
|
1163
|
+
const nodeStart = freeNodes.length - count
|
|
1164
|
+
const messageStart = freeMessages.length - count
|
|
1165
|
+
for (let step = 0; step < count; step += 1) mapped[freeNodes[nodeStart + step]] = freeMessages[messageStart + step]
|
|
1166
|
+
}
|
|
1167
|
+
const paged = Boolean(win && win.hasMore)
|
|
1168
|
+
let headPending = paged
|
|
1169
|
+
const seen = new Set()
|
|
1170
|
+
nodes.forEach((node, index) => {
|
|
1171
|
+
const key = promptNodeKey(node) || 'node:' + index
|
|
1172
|
+
seen.add(key)
|
|
1173
|
+
const message = mapped[index]
|
|
1174
|
+
const preview = message ? promptIsPreviewable(message) : false
|
|
1175
|
+
if (message && !preview) {
|
|
1176
|
+
const existing = blocks.get(key)
|
|
1177
|
+
if (existing) {
|
|
1178
|
+
if (existing.parentNode) existing.parentNode.removeChild(existing)
|
|
1179
|
+
blocks.delete(key)
|
|
1180
|
+
}
|
|
1181
|
+
return
|
|
1182
|
+
}
|
|
1183
|
+
let text = ''
|
|
1184
|
+
let sourceKind = 'events'
|
|
1185
|
+
let textOnly = true
|
|
1186
|
+
let limited = false
|
|
1187
|
+
let notice = ''
|
|
1188
|
+
if (message) {
|
|
1189
|
+
text = message.text
|
|
1190
|
+
textOnly = message.textOnly && !promptNodeIsCard(node)
|
|
1191
|
+
} else {
|
|
1192
|
+
const dom = promptOfficialText(node)
|
|
1193
|
+
if (dom.trim()) {
|
|
1194
|
+
text = dom
|
|
1195
|
+
sourceKind = 'dom'
|
|
1196
|
+
limited = promptTextClipped(text)
|
|
1197
|
+
textOnly = !node.querySelector('img') && !promptNodeIsCard(node)
|
|
1198
|
+
} else if (promptCollapsedCard(node)) {
|
|
1199
|
+
notice = tr('prompt.expandCard')
|
|
1200
|
+
} else {
|
|
1201
|
+
notice = paged ? tr('prompt.notLoaded') : tr('prompt.unavailable')
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const head = text && headPending ? tr('prompt.notLoaded') : ''
|
|
1205
|
+
if (text) headPending = false
|
|
1206
|
+
const spec = { key, tr, text, source: sourceKind, textOnly, limited, notice, head }
|
|
1207
|
+
const signature = promptBlockSignature(spec)
|
|
1208
|
+
const trigger = triggers.get(key)
|
|
1209
|
+
if (trigger && trigger.isConnected) promptApplyTrigger(trigger, tr)
|
|
1210
|
+
else triggers.set(key, promptTriggerFor(key, node, tr))
|
|
1211
|
+
const existing = blocks.get(key)
|
|
1212
|
+
if (existing && existing.parentElement === node && existing.getAttribute('data-dsh-sam-prompt-sig') === signature) {
|
|
1213
|
+
promptApplyState(existing, tr)
|
|
1214
|
+
return
|
|
1215
|
+
}
|
|
1216
|
+
if (existing) {
|
|
1217
|
+
if (existing.parentNode) existing.parentNode.removeChild(existing)
|
|
1218
|
+
blocks.delete(key)
|
|
1219
|
+
}
|
|
1220
|
+
const block = promptBlockFor(spec)
|
|
1221
|
+
node.appendChild(block)
|
|
1222
|
+
blocks.set(key, block)
|
|
1223
|
+
})
|
|
1224
|
+
for (const key of Array.from(blocks.keys())) {
|
|
1225
|
+
if (seen.has(key)) continue
|
|
1226
|
+
const block = blocks.get(key)
|
|
1227
|
+
if (block && block.parentNode) block.parentNode.removeChild(block)
|
|
1228
|
+
blocks.delete(key)
|
|
1229
|
+
}
|
|
1230
|
+
for (const key of Array.from(triggers.keys())) {
|
|
1231
|
+
if (seen.has(key)) continue
|
|
1232
|
+
const trigger = triggers.get(key)
|
|
1233
|
+
if (trigger && trigger.parentNode) trigger.parentNode.removeChild(trigger)
|
|
1234
|
+
triggers.delete(key)
|
|
1235
|
+
}
|
|
1236
|
+
for (const block of Array.from(blocks.values())) promptApplyState(block, tr)
|
|
1237
|
+
const claimed = new Set()
|
|
1238
|
+
for (const block of Array.from(blocks.values())) {
|
|
1239
|
+
if (block.getAttribute('data-dsh-sam-prompt-state') !== PROMPT_MODE_RENDERED) continue
|
|
1240
|
+
if (block.getAttribute('data-dsh-sam-prompt-hide-official') !== 'true') continue
|
|
1241
|
+
const owner = block.parentElement
|
|
1242
|
+
if (!owner) continue
|
|
1243
|
+
for (const child of Array.from(owner.children)) {
|
|
1244
|
+
if (child === block || promptIsOurs(child)) continue
|
|
1245
|
+
claimed.add(child)
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
for (const child of Array.from(hidden)) {
|
|
1249
|
+
if (claimed.has(child)) continue
|
|
1250
|
+
child.classList.remove(PROMPT_HIDE_CLASS)
|
|
1251
|
+
hidden.delete(child)
|
|
1252
|
+
}
|
|
1253
|
+
for (const child of Array.from(claimed)) hidden.add(child)
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
const run = () => {
|
|
1257
|
+
try {
|
|
1258
|
+
sync()
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
console.warn('subagent-workspace-ui: prompt preview sync failed', error)
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function schedule() {
|
|
1265
|
+
if (disposed || scheduled) return
|
|
1266
|
+
scheduled = true
|
|
1267
|
+
timer = setTimeout(() => {
|
|
1268
|
+
scheduled = false
|
|
1269
|
+
timer = null
|
|
1270
|
+
run()
|
|
1271
|
+
}, PROMPT_SYNC_DELAY)
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
promptSyncs.add(run)
|
|
1275
|
+
const observer = new MutationObserver(schedule)
|
|
1276
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
1277
|
+
run()
|
|
1278
|
+
return () => {
|
|
1279
|
+
disposed = true
|
|
1280
|
+
promptSyncs.delete(run)
|
|
1281
|
+
observer.disconnect()
|
|
1282
|
+
detachFeed()
|
|
1283
|
+
if (timer) clearTimeout(timer)
|
|
1284
|
+
clearAll()
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function PromptPreview({ t: tr, sessionId }) {
|
|
1289
|
+
const hostRef = React.useRef(null)
|
|
1290
|
+
const trRef = React.useRef(tr)
|
|
1291
|
+
trRef.current = tr
|
|
1292
|
+
React.useEffect(() => {
|
|
1293
|
+
const host = hostRef.current
|
|
1294
|
+
if (!host) return
|
|
1295
|
+
return promptPreviewAttach(host, sessionId || '', () => trRef.current)
|
|
1296
|
+
}, [sessionId])
|
|
1297
|
+
React.useEffect(() => {
|
|
1298
|
+
promptPreviewRefresh()
|
|
1299
|
+
}, [tr])
|
|
1300
|
+
return jsx('span', { ref: hostRef, className: 'dsh-sam-prompt-host', 'aria-hidden': 'true' })
|
|
1301
|
+
}
|
|
298
1302
|
|
|
299
1303
|
const uiWorkspaceOf=ctx=>{try{const ws=ctx.get?.('uiWorkspace');return typeof ws?.openSession==='function'?ws:null}catch{return null}}
|
|
300
1304
|
const serviceOf=(ctx,name)=>{try{const value=ctx.get?.(name);return value??null}catch(error){console.warn('subagent-workspace-ui: service probe failed for',name,error);return null}}
|
|
@@ -337,6 +1341,7 @@ function apply(ctx){
|
|
|
337
1341
|
},
|
|
338
1342
|
face=()=>({...actions,asideAddressOf,openAside})
|
|
339
1343
|
ctx.slots.inject('conversation.session.header.actions',()=>ctx.slots.register({name:'conversation.session.header.actions',id:'subagent-workspace-manager',order:100,locale:NS,inject:face},Manager))
|
|
1344
|
+
ctx.slots.inject('conversation.session.header.actions',()=>ctx.slots.register({name:'conversation.session.header.actions',id:'subagent-prompt-preview',order:101,locale:NS},PromptPreview))
|
|
340
1345
|
registerShadow(ctx,'conversation.session.header.actions',{name:'conversation.session.header.actions',id:'subagent-catalog',priority:-1},CatalogShadow)
|
|
341
1346
|
registerShadow(ctx,'conversation.session.header.lineage',{name:'conversation.session.header.lineage',priority:-1},LineageShadow)
|
|
342
1347
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-subagent-workspace-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Searchable workspace subagent manager for DeepSeek Harness Web",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
42
42
|
},
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"sucrase": "^3.35.1",
|
|
45
|
-
"typescript": "^7.0.2"
|
|
46
|
-
},
|
|
47
43
|
"scripts": {
|
|
48
44
|
"build": "node scripts/build.mjs",
|
|
49
45
|
"verify:build": "node scripts/verify-build.mjs",
|
|
50
46
|
"typecheck": "tsc --noEmit",
|
|
51
47
|
"check": "node scripts/build.mjs && tsc --noEmit && node --check lib/index.js && node scripts/verify-fresh.mjs"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"sucrase": "^3.35.1",
|
|
51
|
+
"typescript": "^7.0.2"
|
|
52
52
|
}
|
|
53
|
-
}
|
|
53
|
+
}
|