dsh-conversation-overview 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -24
  2. package/client.js +132 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,24 +8,12 @@ DeepSeek Harness(DSH)插件:在对话/轨迹旁增加「概览」标签页
8
8
 
9
9
  ## 功能特性
10
10
 
11
-
12
- - **进展**
13
- - 目标(goal)与阶段徽标;任务计划(todos)进度条与逐条状态;
14
- - 无任务计划的轮次显示「本轮关键步骤」:最近 6 次工具调用,时间正序(旧上新下),新步骤底部渐入、旧步骤顶部浅出,可一键展开本轮全部步骤;
15
- - 等待人为确认(权限审批/提问/计划确认)的步骤以琥珀色确认风格高亮;
16
- - 可开启「里程碑步骤置顶」:上下文压缩、用户审批与失败的工具调用固定在关键步骤顶部,不被最近窗口挤出;
17
- - 团队会话显示各成员阶段(创建中/运行中/空闲/失败/阻塞),可展开团队详情;
18
- - 点击步骤行复制其完整目标文本(命令/文件路径/查询等),悬停查看全文;
19
- - 双击 todo 行把「重新执行指定步骤」的提示词填入聊天输入框(不自动发送),提示词模板可自定义(支持 `{index}`、`{content}` 占位符)。
20
- - **产物**:产出/交付文件列表、按轮次分组的变更审阅入口、Finder/系统定位;右键菜单支持「打开方式」选择应用与系统定位。
21
- - **引用**:上下文注入、OpenViking 记忆(`viking://`)、技能目录、文件与会话引用;
22
- - 类型多选下拉筛选(选中类型以 chips 显示)、「过滤常见」隐藏每轮都出现的注入、排序(最新/类型/次数,可点击取消);
23
- - `viking://` 记忆在右侧边栏新标签页内预览(frontmatter 结构化 + Markdown 渲染 + 预览/源码切换,经 OpenViking HTTP API);
24
- - 运行时上下文注入可点击打开快照预览标签页;文件路径点击定位。
25
- - **范围选择器**:最新一轮 / 第 N 轮 / 全部轮次;轮次统计胶囊(用时、工具调用、命令、修改文件、失败数),可整体开关,时间支持「时间点 / 本轮计时」两种显示。
26
- - **时间轴**:点击用时胶囊打开时间轴,按任意时间点回看当时的进展/产物/引用;支持「回到最新」、在时间轴保持打开时切换轮次。
27
- - **快速导航联动**:可强制显示内置轮次导航轨(含窄容器下),轨道刻度带轮次号提示,点击刻度联动概览切换到对应轮次。
28
- - **设置页**:DSH 设置中的独立分区 + 插件行内配置,全部选项即时生效;内置「问题反馈」入口直达 GitHub Issues;显示当前版本并自动检查 GitHub 最新 Release,有新版时出现「新版本」徽标,点击直达 Releases。
11
+ - **进展**:目标与任务计划进度;本轮关键步骤(可展开全部、等待确认高亮、里程碑可置顶);团队成员状态;双击待办生成重跑提示词。
12
+ - **产物**:生成/交付文件列表、按轮次的变更审阅入口、系统定位与「打开方式」。
13
+ - **引用**:上下文注入、OpenViking 记忆(侧边栏内预览)、技能、文件与会话引用;类型筛选、隐藏常见项、多种排序。
14
+ - **统计与时间轴**:轮次统计胶囊(用时/调用/命令/改动文件/失败,失败可点击查看详情);打开时间轴拖到任意时间点,回看当时的进展、产物与引用。
15
+ - **轮次导航**:范围选择器(最新 / 第 N 轮 / 全部),与内置轮次导航轨双向联动。
16
+ - **设置**:DSH 设置独立分区 + 插件行内配置,选项即时生效;问题反馈入口与新版本徽标。
29
17
 
30
18
  ## 安装
31
19
 
@@ -39,10 +27,32 @@ dsh-conversation-overview
39
27
 
40
28
  方式二:GUI「插件管理」中选择安装 bundle,输入上面的 npm 包名(或 `dsh-conversation-overview@latest`)。
41
29
 
30
+ 方式三:已全局安装 DSH(终端可用 `dsh` 命令)时,用 CLI 安装进指定 profile(`web` 为 profile 名,按需替换;该命令等价于在 profile 目录执行 pnpm):
31
+
32
+ ```bash
33
+ dsh plugin --profile web add dsh-conversation-overview
34
+ ```
35
+
36
+ CLI 安装后重启 `dsh` 生效(方式一/二安装后刷新浏览器页面即可)。
37
+
42
38
  安装后在「设置 → 会话概览」或插件行内配置中调整选项;对话页刷新即可看到「概览」标签页与右侧边栏入口。
43
39
 
44
40
  > 注意:若此前安装过开发版 `@local/dsh-conversation-overview`,请先移除它再安装本包——两者使用相同的插件行 id(`conversation-overview`)与插槽 key,不应同时启用。
45
41
 
42
+ **插件更新**:
43
+
44
+ - CLI(已全局安装 DSH):
45
+
46
+ ```bash
47
+ dsh plugin --profile web update dsh-conversation-overview
48
+ # 或指定最新版重装:dsh plugin --profile web add dsh-conversation-overview@latest
49
+ # 卸载:dsh plugin --profile web remove dsh-conversation-overview
50
+ ```
51
+
52
+ 更新后重启 `dsh` 生效。
53
+
54
+ - GUI / 会话内:在 DSH 会话中让 Agent 依次执行插件管理器的 `remove_bundle`(target: `dsh-conversation-overview`)→ `install_bundle`(target: `dsh-conversation-overview@latest`),或在 GUI「插件管理」中移除后重新安装最新版。更新后刷新浏览器页面;若新版本增加了配置字段或宿主端代码有改动,需重启一次 DSH。
55
+
46
56
  ## 配置项
47
57
 
48
58
  | 字段 | 默认 | 说明 |
@@ -64,12 +74,6 @@ dsh-conversation-overview
64
74
  | `vikingAccount` | `""` | OpenViking 账户(预览记忆必填) |
65
75
  | `vikingUser` | `""` | OpenViking 用户(预览记忆必填) |
66
76
 
67
- ## 版本更新
68
-
69
- **升级已安装的插件**:在 DSH「插件管理」中先移除 `dsh-conversation-overview`,再安装最新版(或在会话中让 Agent 依次执行 `remove_bundle` → `install_bundle dsh-conversation-overview@latest`),完成后刷新浏览器页面;若新版本增加了配置字段或宿主端代码有改动,需重启一次 DSH。
70
-
71
- **维护者发布**:将开发目录的改动同步到本目录并做发布转换(包名 id、OpenViking 账户/用户默认值置空)→ 跑离线测试套件与 `npm pack --dry-run` 核对文件清单 → `npm version patch|minor|major`(自动生成 git 提交与标签)→ `npm publish --registry https://registry.npmjs.org/`(本机默认 registry 是镜像,必须显式指定官方源)→ `git push origin main --tags`。
72
-
73
77
  ## 开发与测试
74
78
 
75
79
  包内无构建步骤:`index.js`(Host 侧,schemastery Config + 资源定位)与 `client.js`(浏览器侧,`window.__ModuleLoader__` 工厂格式)均为源文件直发。
package/client.js CHANGED
@@ -313,6 +313,10 @@ window.__ModuleLoader__.load({
313
313
  "stats.files.title": "修改文件数",
314
314
  "stats.errors": "失败 {n}",
315
315
  "stats.errors.title": "失败的工具调用次数",
316
+ "stats.errors.popTitle": "点击查看失败详情",
317
+ "stats.errors.list": "失败详情({n})",
318
+ "stats.errors.truncated": "仅列出步骤窗口内的失败",
319
+ "stats.errors.close": "关闭",
316
320
  "reference.types": "类型",
317
321
  "reference.types.title": "按类型筛选",
318
322
  "reference.types.all": "全部类型",
@@ -490,6 +494,10 @@ window.__ModuleLoader__.load({
490
494
  "stats.files.title": "Files changed",
491
495
  "stats.errors": "{n} failed",
492
496
  "stats.errors.title": "Failed tool calls",
497
+ "stats.errors.popTitle": "Click for failure details",
498
+ "stats.errors.list": "Failure details ({n})",
499
+ "stats.errors.truncated": "Only failures within the step window are listed",
500
+ "stats.errors.close": "Close",
493
501
  "reference.types": "Types",
494
502
  "reference.types.title": "Filter by type",
495
503
  "reference.types.all": "All types",
@@ -1933,11 +1941,14 @@ window.__ModuleLoader__.load({
1933
1941
  touch(folder);
1934
1942
  }
1935
1943
 
1936
- function markStepDone(folder, callId, time, failed) {
1944
+ function markStepDone(folder, callId, time, failed, errText) {
1937
1945
  const step = folder.stepByCall.get(callId);
1938
1946
  if (step === undefined) return;
1939
1947
  step.done = true;
1940
- if (failed === true) step.error = true;
1948
+ if (failed === true) {
1949
+ step.error = true;
1950
+ if (typeof errText === "string" && errText.trim() !== "") step.err = errText.trim().slice(0, 400);
1951
+ }
1941
1952
  if (typeof time === "number") step.endTime = time;
1942
1953
  touch(folder);
1943
1954
  }
@@ -2053,7 +2064,7 @@ window.__ModuleLoader__.load({
2053
2064
  if (approvalStep !== undefined && outcome !== "" && typeof approvalStep.detail === "string" && !approvalStep.detail.includes(outcome)) {
2054
2065
  approvalStep.detail = approvalStep.detail === "" ? outcome : `${approvalStep.detail} · ${outcome}`;
2055
2066
  }
2056
- markStepDone(folder, `approval-${data.id}`, event.time, /denied|reject/i.test(outcome));
2067
+ markStepDone(folder, `approval-${data.id}`, event.time, /denied|reject/i.test(outcome), outcome);
2057
2068
  return;
2058
2069
  }
2059
2070
  case "workspace/changes": {
@@ -2176,7 +2187,7 @@ window.__ModuleLoader__.load({
2176
2187
  const source = message.source;
2177
2188
  const callId = isRecord(source) && typeof source.callId === "string" ? source.callId : undefined;
2178
2189
  if (callId === undefined) return;
2179
- markStepDone(folder, callId, event.time, message.isError === true);
2190
+ markStepDone(folder, callId, event.time, message.isError === true, message.isError === true ? textOf(message.content) : undefined);
2180
2191
  if (message.isError === true) {
2181
2192
  folder.stats.errors += 1;
2182
2193
  const errorBucket = folder.byTurn.get(folder.currentTurn);
@@ -4627,7 +4638,34 @@ function FileTypeIcon(props) {
4627
4638
  }
4628
4639
 
4629
4640
  /** Left-hand stat pills on the scope row: duration, tool calls, commands, files, failures. */
4630
- function StatsBar({ stats, t, canTimeline, timelineOpen, scrubbing, onToggleTimeline, prevJump, onPrevTurn }) {
4641
+ function StatsBar({ stats, t, canTimeline, timelineOpen, scrubbing, onToggleTimeline, prevJump, onPrevTurn, failedSteps }) {
4642
+ /* failure details popover: hooks must run before any early return */
4643
+ const failed = Array.isArray(failedSteps) ? failedSteps.filter((step) => isRecord(step)) : [];
4644
+ /* fixed-position popover: the toolbar wrappers clip absolutely positioned
4645
+ children (overflow hidden/auto), so anchor to the viewport instead */
4646
+ const [errPop, setErrPop] = React.useState(null);
4647
+ const errOpen = isRecord(errPop);
4648
+ const errRef = React.useRef(null);
4649
+ React.useEffect(() => {
4650
+ if (!errOpen) return undefined;
4651
+ const close = () => setErrPop(null);
4652
+ const onDown = (event) => {
4653
+ if (errRef.current && !errRef.current.contains(event.target)) close();
4654
+ };
4655
+ const onKey = (event) => {
4656
+ if (event.key === "Escape") close();
4657
+ };
4658
+ document.addEventListener("mousedown", onDown);
4659
+ document.addEventListener("keydown", onKey);
4660
+ window.addEventListener("scroll", close, true);
4661
+ window.addEventListener("resize", close);
4662
+ return () => {
4663
+ document.removeEventListener("mousedown", onDown);
4664
+ document.removeEventListener("keydown", onKey);
4665
+ window.removeEventListener("scroll", close, true);
4666
+ window.removeEventListener("resize", close);
4667
+ };
4668
+ }, [errOpen]);
4631
4669
  if (!isRecord(stats)) return null;
4632
4670
  const duration = typeof stats.durationMs === "number" ? stats.durationMs : 0;
4633
4671
  const toolCalls = typeof stats.toolCalls === "number" ? stats.toolCalls : 0;
@@ -4671,7 +4709,71 @@ function FileTypeIcon(props) {
4671
4709
  pill("tools", t("stats.tools", { n: toolCalls }), t("stats.tools.title")),
4672
4710
  commands > 0 ? pill("commands", t("stats.commands", { n: commands }), t("stats.commands.title")) : null,
4673
4711
  files > 0 ? pill("files", t("stats.files", { n: files }), t("stats.files.title")) : null,
4674
- errors > 0 ? pill("errors", t("stats.errors", { n: errors }), t("stats.errors.title"), "err") : null
4712
+ errors > 0
4713
+ ? h(
4714
+ "span",
4715
+ { className: "dsov-statWrap", key: "errors", ref: errRef },
4716
+ h(
4717
+ "button",
4718
+ {
4719
+ type: "button",
4720
+ className: "dsov-stat dsov-statBtn",
4721
+ "data-tone": "err",
4722
+ "data-active": errOpen ? "true" : undefined,
4723
+ disabled: failed.length === 0,
4724
+ title: failed.length > 0 ? t("stats.errors.popTitle") : t("stats.errors.title"),
4725
+ onClick:
4726
+ failed.length > 0
4727
+ ? (event) => {
4728
+ if (errOpen) {
4729
+ setErrPop(null);
4730
+ return;
4731
+ }
4732
+ const rect = event.currentTarget.getBoundingClientRect();
4733
+ setErrPop({
4734
+ top: Math.min(rect.bottom + 5, Math.max(8, window.innerHeight - 292)),
4735
+ right: Math.max(8, window.innerWidth - rect.right),
4736
+ });
4737
+ }
4738
+ : undefined,
4739
+ },
4740
+ t("stats.errors", { n: errors })
4741
+ ),
4742
+ errOpen && failed.length > 0
4743
+ ? h(
4744
+ "div",
4745
+ { className: "dsov-errPop", style: { top: `${errPop.top}px`, right: `${errPop.right}px` } },
4746
+ h(
4747
+ "div",
4748
+ { className: "dsov-errPopHead" },
4749
+ h("span", null, t("stats.errors.list", { n: failed.length }), errors > failed.length ? h("span", { className: "dsov-errPopHint" }, t("stats.errors.truncated")) : null),
4750
+ h("button", { type: "button", className: "dsov-errPopClose", title: t("stats.errors.close"), onClick: () => setErrPop(null) }, "\u00d7")
4751
+ ),
4752
+ ...failed
4753
+ .slice()
4754
+ .reverse()
4755
+ .map((step, index) =>
4756
+ h(
4757
+ "div",
4758
+ { className: "dsov-errPopRow", key: typeof step.callId === "string" && step.callId !== "" ? step.callId : `err-${index}`, title: typeof step.full === "string" && step.full !== "" ? step.full : step.detail },
4759
+ h(
4760
+ "div",
4761
+ { className: "dsov-errPopTop" },
4762
+ h(
4763
+ "span",
4764
+ { className: "dsov-errPopName" },
4765
+ step.name === "compact-checkpoint" ? t("step.compact") : step.name === "approval" ? t("step.approval") : typeof step.name === "string" ? step.name : ""
4766
+ ),
4767
+ h("span", { className: "dsov-errPopTime" }, typeof step.timeText === "string" ? step.timeText : "")
4768
+ ),
4769
+ step.detail ? h("div", { className: "dsov-errPopDetail" }, step.detail) : null,
4770
+ step.err ? h("div", { className: "dsov-errPopMsg" }, step.err) : null
4771
+ )
4772
+ )
4773
+ )
4774
+ : null
4775
+ )
4776
+ : null
4675
4777
  );
4676
4778
  }
4677
4779
 
@@ -4734,6 +4836,15 @@ function FileTypeIcon(props) {
4734
4836
  };
4735
4837
  }, [asOf, summary]);
4736
4838
  const canTimeline = typeof summary.turnStart === "number" && typeof summary.turnEnd === "number" && summary.turnEnd > summary.turnStart;
4839
+ /* the errors pill drills into these: failed steps of the scope, honoring the scrub point */
4840
+ const failedSteps = React.useMemo(() => {
4841
+ const list = Array.isArray(summary.stepsAll) ? summary.stepsAll : [];
4842
+ const cut = asOf === null ? Number.POSITIVE_INFINITY : asOf;
4843
+ return list
4844
+ .filter((step) => isRecord(step) && step.error === true && (step.endTime || step.time || 0) <= cut)
4845
+ .slice(-50)
4846
+ .map((step) => ({ ...step, timeText: formatTime(step.time, cfg.timeMode, summary.turnStart) }));
4847
+ }, [summary, asOf, cfg]);
4737
4848
  /* at the 0s point nothing has happened yet in this turn: offer a jump to
4738
4849
  the previous turn, rendered inside the (otherwise empty) stats bar */
4739
4850
  const turnNums = Array.isArray(summary.turns) ? summary.turns.filter((value) => Number.isFinite(value)) : [];
@@ -4752,6 +4863,7 @@ function FileTypeIcon(props) {
4752
4863
  canTimeline,
4753
4864
  timelineOpen,
4754
4865
  scrubbing: asOf !== null,
4866
+ failedSteps,
4755
4867
  prevJump: asOf !== null && asOf <= summary.turnStart ? prevTurn : null,
4756
4868
  onPrevTurn: () => {
4757
4869
  setSelection(`turn:${prevTurn}`);
@@ -5728,6 +5840,20 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
5728
5840
  .dsov-fileMenuSep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1)}
5729
5841
  .dsov-fileMenuHint{padding:5px 8px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
5730
5842
  .dsov-fileMenuErr{padding:5px 8px;font-size:11px;color:var(--dsw-alias-state-error-primary,#ff453a)}
5843
+ .dsov-statWrap{position:relative;display:inline-flex}
5844
+ .dsov-statBtn[disabled]{cursor:default}
5845
+ .dsov-errPop{position:fixed;z-index:80;width:min(360px,78vw);max-height:280px;overflow-y:auto;padding:4px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l1);box-shadow:0 8px 22px rgba(0,0,0,.20)}
5846
+ .dsov-errPopHead{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:2px 4px 4px;font-size:11px;color:var(--dsw-alias-label-secondary)}
5847
+ .dsov-errPopHint{margin-left:6px;font-size:10px;color:var(--dsw-alias-label-tertiary)}
5848
+ .dsov-errPopClose{border:0;background:none;cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1;padding:2px 5px;border-radius:4px}
5849
+ .dsov-errPopClose:hover{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}
5850
+ .dsov-errPopRow{padding:4px 6px;border-radius:6px;font-size:11px;line-height:1.5}
5851
+ .dsov-errPopRow:hover{background:var(--dsw-alias-bg-layer-3)}
5852
+ .dsov-errPopTop{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
5853
+ .dsov-errPopName{font-weight:600;color:var(--dsw-alias-label-primary)}
5854
+ .dsov-errPopTime{color:var(--dsw-alias-label-tertiary);font-size:10px;white-space:nowrap}
5855
+ .dsov-errPopDetail{color:var(--dsw-alias-label-secondary);word-break:break-all;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
5856
+ .dsov-errPopMsg{margin-top:2px;color:var(--dsw-alias-state-error-primary,#ff453a);white-space:pre-wrap;word-break:break-all;font-size:10.5px;max-height:76px;overflow-y:auto}
5731
5857
  .dsov-renderErr{margin:8px 12px 0;padding:6px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-state-error-primary,#ff453a);font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-all;text-align:left;max-height:140px;overflow:auto}
5732
5858
  .dsov-turnSelect{flex:none;max-width:150px;height:24px;padding:0 22px 0 9px;border:.5px solid var(--dsw-alias-border-l2);border-radius:12px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 12px) 10px,calc(100% - 8px) 10px;background-size:4px 4px,4px 4px;background-repeat:no-repeat}
5733
5859
  .dsov-turnSelect:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,var(--dsw-alias-border-l2))}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-conversation-overview",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "DeepSeek Harness (DSH) plugin: an Overview tab beside Chat/Trajectory plus a right-sidebar page summarizing session progress (goal, todos, key steps), artifacts, and references, with per-turn scoping, OpenViking memory preview, and quick-nav linkage.",
6
6
  "keywords": [