dsh-conversation-overview 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -16
- package/client.js +1091 -80
- package/cordis.patch.yml +1 -0
- package/index.js +1 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -19,9 +19,154 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
const ROW_ID = "conversation-overview";
|
|
20
20
|
const ROW_KEY = "dsh-conversation-overview#conversation-overview";
|
|
21
21
|
const FEEDBACK_URL = "https://github.com/LingF/dsh-conversation-overview/issues";
|
|
22
|
+
const RELEASES_URL = "https://github.com/LingF/dsh-conversation-overview/releases";
|
|
23
|
+
const RELEASE_API_URL = "https://api.github.com/repos/LingF/dsh-conversation-overview/releases/latest";
|
|
24
|
+
const RELEASE_TAGS_API_URL = "https://api.github.com/repos/LingF/dsh-conversation-overview/tags?per_page=100";
|
|
25
|
+
const TAGS_URL = "https://github.com/LingF/dsh-conversation-overview/tags";
|
|
26
|
+
/* keep in sync with package.json */
|
|
27
|
+
const PLUGIN_VERSION = "1.1.0";
|
|
28
|
+
const LATEST_CACHE_KEY = "dsh.conversationOverview.latestRelease";
|
|
29
|
+
const LATEST_CACHE_TTL = 6 * 60 * 60 * 1000;
|
|
30
|
+
const latestStore = { tag: null, via: "release", loaded: false, inflight: null };
|
|
31
|
+
|
|
32
|
+
function compareVersions(left, right) {
|
|
33
|
+
const parse = (value) =>
|
|
34
|
+
String(value)
|
|
35
|
+
.replace(/^v/i, "")
|
|
36
|
+
.split("-")[0]
|
|
37
|
+
.split(".")
|
|
38
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
39
|
+
const a = parse(left);
|
|
40
|
+
const b = parse(right);
|
|
41
|
+
for (let index = 0; index < 3; index += 1) {
|
|
42
|
+
const delta = (a[index] || 0) - (b[index] || 0);
|
|
43
|
+
if (delta !== 0) return delta;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fetchLatestRelease() {
|
|
49
|
+
if (latestStore.loaded) return Promise.resolve(latestStore.tag);
|
|
50
|
+
if (latestStore.inflight !== null) return latestStore.inflight;
|
|
51
|
+
try {
|
|
52
|
+
const raw = localStorage.getItem(LATEST_CACHE_KEY);
|
|
53
|
+
if (typeof raw === "string") {
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
if (isRecord(parsed) && typeof parsed.tag === "string" && typeof parsed.time === "number" && Date.now() - parsed.time < LATEST_CACHE_TTL) {
|
|
56
|
+
latestStore.tag = parsed.tag;
|
|
57
|
+
latestStore.via = parsed.via === "tags" ? "tags" : "release";
|
|
58
|
+
latestStore.loaded = true;
|
|
59
|
+
return Promise.resolve(parsed.tag);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
/* cache is best-effort */
|
|
64
|
+
}
|
|
65
|
+
if (typeof fetch !== "function") return Promise.resolve(null);
|
|
66
|
+
latestStore.inflight = fetch(RELEASE_API_URL, { headers: { Accept: "application/vnd.github+json" } })
|
|
67
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
68
|
+
.then((payload) => {
|
|
69
|
+
/* repos without formal releases fall back to the highest version tag */
|
|
70
|
+
if (isRecord(payload) && typeof payload.tag_name === "string") return { tag: payload.tag_name, via: "release" };
|
|
71
|
+
return fetch(RELEASE_TAGS_API_URL, { headers: { Accept: "application/vnd.github+json" } })
|
|
72
|
+
.then((response) => (response.ok ? response.json() : []))
|
|
73
|
+
.then((list) => {
|
|
74
|
+
if (!Array.isArray(list)) return { tag: null, via: "release" };
|
|
75
|
+
let best = null;
|
|
76
|
+
for (const entry of list) {
|
|
77
|
+
const name = isRecord(entry) && typeof entry.name === "string" ? entry.name : null;
|
|
78
|
+
if (name !== null && (best === null || compareVersions(name, best) > 0)) best = name;
|
|
79
|
+
}
|
|
80
|
+
return { tag: best, via: "tags" };
|
|
81
|
+
});
|
|
82
|
+
})
|
|
83
|
+
.then((found) => {
|
|
84
|
+
latestStore.inflight = null;
|
|
85
|
+
latestStore.loaded = true;
|
|
86
|
+
latestStore.tag = found.tag;
|
|
87
|
+
latestStore.via = found.via;
|
|
88
|
+
if (found.tag !== null) {
|
|
89
|
+
try {
|
|
90
|
+
localStorage.setItem(LATEST_CACHE_KEY, JSON.stringify({ tag: found.tag, via: found.via, time: Date.now() }));
|
|
91
|
+
} catch {
|
|
92
|
+
/* storage is best-effort */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return found.tag;
|
|
96
|
+
})
|
|
97
|
+
.catch(() => {
|
|
98
|
+
latestStore.inflight = null;
|
|
99
|
+
return null;
|
|
100
|
+
});
|
|
101
|
+
return latestStore.inflight;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function useLatestVersion() {
|
|
105
|
+
const [tag, setTag] = React.useState(latestStore.tag);
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
let cancelled = false;
|
|
108
|
+
Promise.resolve(fetchLatestRelease()).then((value) => {
|
|
109
|
+
if (!cancelled && typeof value === "string") setTag(value);
|
|
110
|
+
});
|
|
111
|
+
return () => {
|
|
112
|
+
cancelled = true;
|
|
113
|
+
};
|
|
114
|
+
}, []);
|
|
115
|
+
return typeof tag === "string" && compareVersions(tag, PLUGIN_VERSION) > 0 ? tag : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* The plugin-manager renders every detail.badge entry on every page; only answer for ours. */
|
|
119
|
+
function isOverviewSubject(subject) {
|
|
120
|
+
if (!isRecord(subject)) return false;
|
|
121
|
+
if (subject.kind === "item") return typeof subject.id === "string" && subject.id.indexOf("conversation-overview") !== -1;
|
|
122
|
+
if (subject.kind === "row") {
|
|
123
|
+
const row = isRecord(subject.row) ? subject.row : null;
|
|
124
|
+
const pkg = isRecord(subject.pkg) ? subject.pkg : null;
|
|
125
|
+
return (row !== null && String(row.rowId ?? "").indexOf("conversation-overview") !== -1) || (pkg !== null && String(pkg.name ?? "").indexOf("conversation-overview") !== -1);
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function NewVersionBadge({ subject }) {
|
|
131
|
+
const tag = useLatestVersion();
|
|
132
|
+
if (tag === null || !isOverviewSubject(subject)) return null;
|
|
133
|
+
return h(
|
|
134
|
+
"a",
|
|
135
|
+
{
|
|
136
|
+
className: "dsov-newVer",
|
|
137
|
+
href: latestStore.via === "tags" ? TAGS_URL : `${RELEASES_URL}/tag/${encodeURIComponent(tag)}`,
|
|
138
|
+
target: "_blank",
|
|
139
|
+
rel: "noreferrer",
|
|
140
|
+
title: translate("settings.latestTitle"),
|
|
141
|
+
},
|
|
142
|
+
`${translate("settings.latestBadge")} ${tag}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function VersionRow({ t }) {
|
|
147
|
+
const tag = useLatestVersion();
|
|
148
|
+
return h(
|
|
149
|
+
"div",
|
|
150
|
+
{ className: "dsov-set-ver" },
|
|
151
|
+
tag !== null
|
|
152
|
+
? h(
|
|
153
|
+
"a",
|
|
154
|
+
{
|
|
155
|
+
className: "dsov-newVer",
|
|
156
|
+
href: latestStore.via === "tags" ? TAGS_URL : `${RELEASES_URL}/tag/${encodeURIComponent(tag)}`,
|
|
157
|
+
target: "_blank",
|
|
158
|
+
rel: "noreferrer",
|
|
159
|
+
title: t("settings.latestTitle"),
|
|
160
|
+
},
|
|
161
|
+
`${t("settings.latestBadge")} ${tag}`
|
|
162
|
+
)
|
|
163
|
+
: null,
|
|
164
|
+
h("span", null, `${t("settings.currentVersion")} v${PLUGIN_VERSION}`)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
22
167
|
const PLACEMENTS = ["conversation", "sidebar", "both"];
|
|
23
168
|
const TIME_MODES = ["clock", "elapsed"];
|
|
24
|
-
const CONFIG_FIELDS = ["showProgress", "showArtifacts", "showReferences", "showStats", "timeMode", "placement", "sectionFullHeight", "todoPrompt", "todoPromptTemplate", "quickNav", "quickNavLink", "vikingPreview", "vikingUrl", "vikingAccount", "vikingUser"];
|
|
169
|
+
const CONFIG_FIELDS = ["showProgress", "showArtifacts", "showReferences", "showStats", "timeMode", "stepAnchors", "placement", "sectionFullHeight", "todoPrompt", "todoPromptTemplate", "quickNav", "quickNavLink", "vikingPreview", "vikingUrl", "vikingAccount", "vikingUser"];
|
|
25
170
|
const VIKING_TAB_ID = "dsh-conversation-overview/viking";
|
|
26
171
|
const VIKING_TAB_KIND = "viking-memory";
|
|
27
172
|
const VIKING_ADDRESS_PREFIX = "dsh-resource://viking-memory/";
|
|
@@ -40,7 +185,7 @@ window.__ModuleLoader__.load({
|
|
|
40
185
|
const zh = {
|
|
41
186
|
"view.tab": "概览",
|
|
42
187
|
"panel.title": "概览",
|
|
43
|
-
"guide.title": "
|
|
188
|
+
"guide.title": "会话概览",
|
|
44
189
|
"guide.description": "进展、产物与引用",
|
|
45
190
|
"section.progress": "进展",
|
|
46
191
|
"section.artifacts": "产物",
|
|
@@ -48,6 +193,8 @@ window.__ModuleLoader__.load({
|
|
|
48
193
|
"progress.empty": "生成任务/待办后将在此展示进展",
|
|
49
194
|
"progress.emptyTurn": "本轮未创建任务计划",
|
|
50
195
|
"progress.recentSteps": "本轮关键步骤(最近 6 条)",
|
|
196
|
+
"step.compact": "上下文压缩",
|
|
197
|
+
"step.approval": "用户审批",
|
|
51
198
|
"progress.allSteps": "本轮关键步骤(全部)",
|
|
52
199
|
"progress.showEarlierSteps": "显示更早的步骤",
|
|
53
200
|
"progress.moreStepsCount": "还有 {n} 条",
|
|
@@ -69,15 +216,21 @@ window.__ModuleLoader__.load({
|
|
|
69
216
|
"artifacts.viewAll": "查看所有改动",
|
|
70
217
|
"settings.summary": "控制概览的板块与显示位置",
|
|
71
218
|
"settings.nav": "概览",
|
|
72
|
-
"settings.pageTitle": "
|
|
219
|
+
"settings.pageTitle": "会话概览(dsh-conversation-overview)",
|
|
73
220
|
"settings.sections": "板块显示",
|
|
74
221
|
"settings.showProgress": "进展(任务与待办)",
|
|
75
222
|
"settings.showArtifacts": "产物(本次对话产出)",
|
|
76
223
|
"settings.showReferences": "引用(上下文、记忆等)",
|
|
77
224
|
"settings.showStats": "统计(用时、执行次数等)",
|
|
78
225
|
"settings.timeMode": "时间显示",
|
|
226
|
+
"settings.stepAnchors": "里程碑步骤置顶",
|
|
227
|
+
"settings.stepAnchorsNote": "开启后,上下文压缩、用户审批与失败的工具调用固定在关键步骤顶部,不被最近窗口挤出;默认关闭,随窗口滚动。",
|
|
79
228
|
"settings.timeMode.clock": "时间点",
|
|
80
|
-
"settings.timeMode.elapsed": "
|
|
229
|
+
"settings.timeMode.elapsed": "本轮计时",
|
|
230
|
+
"settings.timeModeNote": "「本轮计时」以本轮任务开始为 0s 起点,显示相对用时;悬停时间可查看对应的具体时间点。",
|
|
231
|
+
"settings.currentVersion": "当前版本",
|
|
232
|
+
"settings.latestBadge": "新版本",
|
|
233
|
+
"settings.latestTitle": "在 GitHub 查看最新版本",
|
|
81
234
|
"settings.feedbackGroup": "反馈",
|
|
82
235
|
"settings.feedback": "问题反馈(GitHub Issues)",
|
|
83
236
|
"settings.fullHeightRow": "分区完整展示(不限高)",
|
|
@@ -146,6 +299,12 @@ window.__ModuleLoader__.load({
|
|
|
146
299
|
"reference.hideCommon": "过滤常见",
|
|
147
300
|
"stats.duration": "用时 {v}",
|
|
148
301
|
"stats.duration.title": "本轮用时(首末事件间隔)",
|
|
302
|
+
"stats.duration.timeline": "点击打开时间轴,按时间点回看进展 / 产物 / 引用",
|
|
303
|
+
"timeline.live": "最新",
|
|
304
|
+
"timeline.reset": "回到最新",
|
|
305
|
+
"timeline.prevTurn": "上一轮",
|
|
306
|
+
"timeline.prevTurnTitle": "切换到第 {n} 轮,时间轴保持打开",
|
|
307
|
+
"timeline.close": "关闭时间轴",
|
|
149
308
|
"stats.tools": "工具 {n}",
|
|
150
309
|
"stats.tools.title": "工具调用次数",
|
|
151
310
|
"stats.commands": "命令 {n}",
|
|
@@ -154,6 +313,10 @@ window.__ModuleLoader__.load({
|
|
|
154
313
|
"stats.files.title": "修改文件数",
|
|
155
314
|
"stats.errors": "失败 {n}",
|
|
156
315
|
"stats.errors.title": "失败的工具调用次数",
|
|
316
|
+
"stats.errors.popTitle": "点击查看失败详情",
|
|
317
|
+
"stats.errors.list": "失败详情({n})",
|
|
318
|
+
"stats.errors.truncated": "仅列出步骤窗口内的失败",
|
|
319
|
+
"stats.errors.close": "关闭",
|
|
157
320
|
"reference.types": "类型",
|
|
158
321
|
"reference.types.title": "按类型筛选",
|
|
159
322
|
"reference.types.all": "全部类型",
|
|
@@ -167,6 +330,15 @@ window.__ModuleLoader__.load({
|
|
|
167
330
|
"goal.blocked": "受阻",
|
|
168
331
|
"goal.complete": "已完成",
|
|
169
332
|
"artifact.presented": "交付",
|
|
333
|
+
"artifact.memory": "记忆",
|
|
334
|
+
"artifact.menu.reveal": "在上层文件夹中显示",
|
|
335
|
+
"artifact.menu.openWith": "打开方式",
|
|
336
|
+
"artifact.menu.loading": "正在读取可用应用…",
|
|
337
|
+
"artifact.menu.unavailable": "当前部署不支持系统打开",
|
|
338
|
+
"artifact.menu.noApps": "未找到关联应用",
|
|
339
|
+
"artifact.menu.default": "默认",
|
|
340
|
+
"artifact.menu.revealError": "无法在上层文件夹中显示,请重试",
|
|
341
|
+
"artifact.menu.openError": "无法用该应用打开,请重试",
|
|
170
342
|
"artifact.produced": "生成",
|
|
171
343
|
"artifact.binary": "二进制",
|
|
172
344
|
"reference.kind.context": "上下文",
|
|
@@ -194,7 +366,7 @@ window.__ModuleLoader__.load({
|
|
|
194
366
|
const en = {
|
|
195
367
|
"view.tab": "Overview",
|
|
196
368
|
"panel.title": "Overview",
|
|
197
|
-
"guide.title": "Overview",
|
|
369
|
+
"guide.title": "Conversation Overview",
|
|
198
370
|
"guide.description": "Progress, artifacts & references",
|
|
199
371
|
"section.progress": "Progress",
|
|
200
372
|
"section.artifacts": "Artifacts",
|
|
@@ -202,6 +374,8 @@ window.__ModuleLoader__.load({
|
|
|
202
374
|
"progress.empty": "Progress appears here once tasks or todos are generated",
|
|
203
375
|
"progress.emptyTurn": "No task plan was created in this turn",
|
|
204
376
|
"progress.recentSteps": "Key steps in this turn (latest 6)",
|
|
377
|
+
"step.compact": "Context compaction",
|
|
378
|
+
"step.approval": "User approval",
|
|
205
379
|
"progress.allSteps": "Key steps in this turn (all)",
|
|
206
380
|
"progress.showEarlierSteps": "Show earlier steps",
|
|
207
381
|
"progress.moreStepsCount": "{n} more steps",
|
|
@@ -223,15 +397,21 @@ window.__ModuleLoader__.load({
|
|
|
223
397
|
"artifacts.viewAll": "Review all changes",
|
|
224
398
|
"settings.summary": "Control the overview sections and where they appear",
|
|
225
399
|
"settings.nav": "Overview",
|
|
226
|
-
"settings.pageTitle": "Conversation Overview",
|
|
400
|
+
"settings.pageTitle": "Conversation Overview (dsh-conversation-overview)",
|
|
227
401
|
"settings.sections": "Sections",
|
|
228
402
|
"settings.showProgress": "Progress (tasks and todos)",
|
|
229
403
|
"settings.showArtifacts": "Artifacts (files produced)",
|
|
230
404
|
"settings.showReferences": "References (context, memories)",
|
|
231
405
|
"settings.showStats": "Stats (duration, tool calls, …)",
|
|
232
406
|
"settings.timeMode": "Time display",
|
|
407
|
+
"settings.stepAnchors": "Pin milestone steps",
|
|
408
|
+
"settings.stepAnchorsNote": "When on, context compaction, user approvals, and failed tool calls stay pinned above the latest-steps window instead of scrolling out. Off by default.",
|
|
233
409
|
"settings.timeMode.clock": "Clock time",
|
|
234
|
-
"settings.timeMode.elapsed": "Turn elapsed
|
|
410
|
+
"settings.timeMode.elapsed": "Turn elapsed",
|
|
411
|
+
"settings.timeModeNote": "'Turn elapsed' counts from 0s at the start of the turn; hover a time to see its clock value.",
|
|
412
|
+
"settings.currentVersion": "Current version",
|
|
413
|
+
"settings.latestBadge": "New version",
|
|
414
|
+
"settings.latestTitle": "View the latest release on GitHub",
|
|
235
415
|
"settings.feedbackGroup": "Feedback",
|
|
236
416
|
"settings.feedback": "Report an issue (GitHub Issues)",
|
|
237
417
|
"settings.fullHeightRow": "Show sections in full (no max height)",
|
|
@@ -300,6 +480,12 @@ window.__ModuleLoader__.load({
|
|
|
300
480
|
"reference.hideCommon": "Filter common",
|
|
301
481
|
"stats.duration": "{v} elapsed",
|
|
302
482
|
"stats.duration.title": "Turn duration (first to last event)",
|
|
483
|
+
"stats.duration.timeline": "Open the timeline to review progress, artifacts, and references at any moment",
|
|
484
|
+
"timeline.live": "Live",
|
|
485
|
+
"timeline.reset": "Back to live",
|
|
486
|
+
"timeline.prevTurn": "Previous turn",
|
|
487
|
+
"timeline.prevTurnTitle": "Switch to turn {n} and keep the timeline open",
|
|
488
|
+
"timeline.close": "Close timeline",
|
|
303
489
|
"stats.tools": "{n} tools",
|
|
304
490
|
"stats.tools.title": "Tool calls",
|
|
305
491
|
"stats.commands": "{n} cmds",
|
|
@@ -308,6 +494,10 @@ window.__ModuleLoader__.load({
|
|
|
308
494
|
"stats.files.title": "Files changed",
|
|
309
495
|
"stats.errors": "{n} failed",
|
|
310
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",
|
|
311
501
|
"reference.types": "Types",
|
|
312
502
|
"reference.types.title": "Filter by type",
|
|
313
503
|
"reference.types.all": "All types",
|
|
@@ -321,6 +511,15 @@ window.__ModuleLoader__.load({
|
|
|
321
511
|
"goal.blocked": "Blocked",
|
|
322
512
|
"goal.complete": "Completed",
|
|
323
513
|
"artifact.presented": "Delivered",
|
|
514
|
+
"artifact.memory": "Memory",
|
|
515
|
+
"artifact.menu.reveal": "Show in Enclosing Folder",
|
|
516
|
+
"artifact.menu.openWith": "Open With",
|
|
517
|
+
"artifact.menu.loading": "Loading applications…",
|
|
518
|
+
"artifact.menu.unavailable": "System open is unavailable in this deployment",
|
|
519
|
+
"artifact.menu.noApps": "No associated applications found",
|
|
520
|
+
"artifact.menu.default": "Default",
|
|
521
|
+
"artifact.menu.revealError": "Could not reveal the file; try again",
|
|
522
|
+
"artifact.menu.openError": "Could not open with this application; try again",
|
|
324
523
|
"artifact.produced": "Produced",
|
|
325
524
|
"artifact.binary": "binary",
|
|
326
525
|
"reference.kind.context": "Context",
|
|
@@ -370,6 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
370
569
|
showReferences: flag(source.showReferences),
|
|
371
570
|
showStats: flag(source.showStats),
|
|
372
571
|
timeMode: TIME_MODES.indexOf(source.timeMode) !== -1 ? source.timeMode : "clock",
|
|
572
|
+
stepAnchors: source.stepAnchors === true,
|
|
373
573
|
placement: PLACEMENTS.indexOf(source.placement) !== -1 ? source.placement : "both",
|
|
374
574
|
sectionFullHeight: source.sectionFullHeight === true,
|
|
375
575
|
todoPrompt: typeof source.todoPrompt === "boolean" ? source.todoPrompt : true,
|
|
@@ -644,6 +844,125 @@ window.__ModuleLoader__.load({
|
|
|
644
844
|
}
|
|
645
845
|
}
|
|
646
846
|
|
|
847
|
+
/* ------------------------------------------------------------------ */
|
|
848
|
+
/* Host desktop file actions (reveal / open with) via the session remote */
|
|
849
|
+
/* ------------------------------------------------------------------ */
|
|
850
|
+
|
|
851
|
+
const desktopOpenState = { value: null, inflight: null };
|
|
852
|
+
|
|
853
|
+
function hostSessionRemote() {
|
|
854
|
+
const ctx = runtime && runtime.ctx;
|
|
855
|
+
if (!ctx) return null;
|
|
856
|
+
try {
|
|
857
|
+
/* the cordis context proxy THROWS for services this entry has not
|
|
858
|
+
injected; degrade to "no desktop actions" instead of crashing render */
|
|
859
|
+
const remote = ctx.remote;
|
|
860
|
+
return isRecord(remote) && isRecord(remote.session) ? remote.session : null;
|
|
861
|
+
} catch {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function desktopCanOpen() {
|
|
867
|
+
const remote = hostSessionRemote();
|
|
868
|
+
if (remote === null || typeof remote.canOpenWorkspacePath !== "function") return Promise.resolve(false);
|
|
869
|
+
if (desktopOpenState.value !== null) return Promise.resolve(desktopOpenState.value);
|
|
870
|
+
if (desktopOpenState.inflight !== null) return desktopOpenState.inflight;
|
|
871
|
+
desktopOpenState.inflight = Promise.resolve()
|
|
872
|
+
.then(() => remote.canOpenWorkspacePath())
|
|
873
|
+
.then((result) => {
|
|
874
|
+
desktopOpenState.value = isRecord(result) && result.ok === true && result.value === true;
|
|
875
|
+
desktopOpenState.inflight = null;
|
|
876
|
+
return desktopOpenState.value;
|
|
877
|
+
})
|
|
878
|
+
.catch(() => {
|
|
879
|
+
desktopOpenState.inflight = null;
|
|
880
|
+
return false;
|
|
881
|
+
});
|
|
882
|
+
return desktopOpenState.inflight;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function desktopOpenPath(path, action, application) {
|
|
886
|
+
const remote = hostSessionRemote();
|
|
887
|
+
if (remote === null || typeof remote.openWorkspacePath !== "function" || typeof path !== "string" || path === "") return Promise.resolve(false);
|
|
888
|
+
const request = action === "reveal" ? { path, action } : application === undefined ? { path } : { path, application };
|
|
889
|
+
return Promise.resolve()
|
|
890
|
+
.then(() => remote.openWorkspacePath(request))
|
|
891
|
+
.then((result) => isRecord(result) && result.ok === true)
|
|
892
|
+
.catch(() => false);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function desktopApplications(path, signal) {
|
|
896
|
+
const remote = hostSessionRemote();
|
|
897
|
+
if (remote === null || typeof remote.workspacePathApplications !== "function") return Promise.resolve(null);
|
|
898
|
+
return Promise.resolve()
|
|
899
|
+
.then(() => remote.workspacePathApplications({ path }, signal))
|
|
900
|
+
.then((result) => (isRecord(result) && result.ok === true && Array.isArray(result.value) ? result.value : null))
|
|
901
|
+
.catch(() => null);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/* Right-click menu on an artifact row: reveal in the enclosing folder, or pick
|
|
905
|
+
one of the OS-registered applications to open the file with. */
|
|
906
|
+
function ArtifactFileMenu({ menu, t, onClose }) {
|
|
907
|
+
const [phase, setPhase] = React.useState("loading"); /* loading | ready | unavailable */
|
|
908
|
+
const [apps, setApps] = React.useState([]);
|
|
909
|
+
const [failed, setFailed] = React.useState(null);
|
|
910
|
+
const pathRef = React.useRef(menu.path);
|
|
911
|
+
React.useEffect(() => {
|
|
912
|
+
let cancelled = false;
|
|
913
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
914
|
+
desktopCanOpen().then((can) => {
|
|
915
|
+
if (cancelled) return undefined;
|
|
916
|
+
if (!can) {
|
|
917
|
+
setPhase("unavailable");
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
return desktopApplications(pathRef.current, controller !== null ? controller.signal : undefined).then((list) => {
|
|
921
|
+
if (cancelled || (controller !== null && controller.signal.aborted)) return;
|
|
922
|
+
setApps(list === null ? [] : list.filter((app) => isRecord(app) && typeof app.id === "string" && app.id !== "" && typeof app.name === "string"));
|
|
923
|
+
setPhase("ready");
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
return () => {
|
|
927
|
+
cancelled = true;
|
|
928
|
+
if (controller !== null) controller.abort();
|
|
929
|
+
};
|
|
930
|
+
}, []);
|
|
931
|
+
const runAction = (action, application) => {
|
|
932
|
+
desktopOpenPath(menu.path, action, application).then((ok) => {
|
|
933
|
+
if (ok) onClose();
|
|
934
|
+
else setFailed(action === "reveal" ? t("artifact.menu.revealError") : t("artifact.menu.openError"));
|
|
935
|
+
});
|
|
936
|
+
};
|
|
937
|
+
const clamp = (value, max) => Math.max(8, Math.min(value, max));
|
|
938
|
+
const style = {
|
|
939
|
+
left: `${clamp(menu.x, (typeof window !== "undefined" ? window.innerWidth : 1200) - 250)}px`,
|
|
940
|
+
top: `${clamp(menu.y, (typeof window !== "undefined" ? window.innerHeight : 800) - 320)}px`,
|
|
941
|
+
};
|
|
942
|
+
return h(
|
|
943
|
+
"div",
|
|
944
|
+
{ className: "dsov-fileMenu", style, onMouseDown: (event) => event.stopPropagation(), onContextMenu: (event) => event.preventDefault() },
|
|
945
|
+
h("button", { type: "button", className: "dsov-fileMenuItem", onClick: () => runAction("reveal") }, t("artifact.menu.reveal")),
|
|
946
|
+
h("div", { className: "dsov-fileMenuSep" }),
|
|
947
|
+
h("div", { className: "dsov-fileMenuHead" }, t("artifact.menu.openWith")),
|
|
948
|
+
phase === "loading" ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.loading")) : null,
|
|
949
|
+
phase === "unavailable" ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.unavailable")) : null,
|
|
950
|
+
phase === "ready" && apps.length === 0 ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.noApps")) : null,
|
|
951
|
+
phase === "ready"
|
|
952
|
+
? apps.map((app) =>
|
|
953
|
+
h(
|
|
954
|
+
"button",
|
|
955
|
+
{ type: "button", className: "dsov-fileMenuItem", key: app.id, onClick: () => runAction("open", app.id) },
|
|
956
|
+
typeof app.icon === "string" && app.icon !== "" ? h("img", { className: "dsov-fileMenuIcon", src: app.icon, alt: "" }) : h("span", { className: "dsov-fileMenuIcon" }),
|
|
957
|
+
h("span", { className: "dsov-fileMenuName" }, app.name),
|
|
958
|
+
app.default === true ? h("span", { className: "dsov-fileMenuTag" }, t("artifact.menu.default")) : null
|
|
959
|
+
)
|
|
960
|
+
)
|
|
961
|
+
: null,
|
|
962
|
+
failed !== null ? h("div", { className: "dsov-fileMenuErr" }, failed) : null
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
647
966
|
function openOverviewTab() {
|
|
648
967
|
const ctx = runtime && runtime.ctx;
|
|
649
968
|
const sidebar = ctx && ctx.sidebarRight;
|
|
@@ -743,6 +1062,32 @@ window.__ModuleLoader__.load({
|
|
|
743
1062
|
return name === "" ? String(uri) : name;
|
|
744
1063
|
}
|
|
745
1064
|
|
|
1065
|
+
/** SKILL.md refs read as their skill folder name so several skills stay distinguishable. */
|
|
1066
|
+
function vikingSkillName(uri) {
|
|
1067
|
+
const match = /\/skills\/([^/]+)\/SKILL\.md$/i.exec(String(uri));
|
|
1068
|
+
return match !== null ? match[1] : null;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function vikingLabel(uri) {
|
|
1072
|
+
const skill = vikingSkillName(uri);
|
|
1073
|
+
return skill !== null ? skill : vikingName(uri);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/** Ref kind: anything under a skills/ root reads as SKILL, the rest stays a memory file. */
|
|
1077
|
+
function vikingRefKind(uri) {
|
|
1078
|
+
const value = String(uri);
|
|
1079
|
+
return /\/skills\/[^/]+(\/SKILL\.md)?\/?$/i.test(value) ? "skill" : "memory-file";
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/** Folder URIs end with "/" or their last segment carries no file extension. */
|
|
1083
|
+
function vikingIsDir(uri) {
|
|
1084
|
+
const value = String(uri);
|
|
1085
|
+
if (value.endsWith("/")) return true;
|
|
1086
|
+
const parts = value.split("/").filter((part) => part !== "");
|
|
1087
|
+
const last = parts.length > 0 ? parts[parts.length - 1] : "";
|
|
1088
|
+
return last !== "" && last.indexOf(".") === -1;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
746
1091
|
function vikingAddress(uri) {
|
|
747
1092
|
return `${VIKING_ADDRESS_PREFIX}${encodeURIComponent(uri)}`;
|
|
748
1093
|
}
|
|
@@ -1004,6 +1349,10 @@ window.__ModuleLoader__.load({
|
|
|
1004
1349
|
}
|
|
1005
1350
|
|
|
1006
1351
|
function openArtifact(binding, artifact, stats) {
|
|
1352
|
+
if (typeof artifact.path === "string" && artifact.path.startsWith("viking://")) {
|
|
1353
|
+
openVikingPreview(artifact.path);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1007
1356
|
if (!binding) return;
|
|
1008
1357
|
const stat = stats ? stats.get(absPath(sessionCwd(binding.sessionId), artifact.path)) : undefined;
|
|
1009
1358
|
if (stat && stat.coords && openChangesReview(binding.sessionId, stat.coords, stat.index)) return;
|
|
@@ -1093,6 +1442,8 @@ window.__ModuleLoader__.load({
|
|
|
1093
1442
|
}
|
|
1094
1443
|
|
|
1095
1444
|
const MEMORY_REFERENCE_TOOLS = new Set(["search", "find", "read", "glob", "grep", "list", "tree"]);
|
|
1445
|
+
/* viking write tools produce durable memory files: they fold into the artifacts section */
|
|
1446
|
+
const MEMORY_MUTATION_TOOLS = new Set(["write", "edit", "add_resource", "add_skill"]);
|
|
1096
1447
|
|
|
1097
1448
|
function refKindForSource(source, kind) {
|
|
1098
1449
|
if (kind.startsWith("plugin:openviking") || kind.startsWith("plugin:ov")) return "memory";
|
|
@@ -1341,9 +1692,18 @@ window.__ModuleLoader__.load({
|
|
|
1341
1692
|
}
|
|
1342
1693
|
|
|
1343
1694
|
/** True while the session has a pending interaction (approval / question / plan exit). */
|
|
1695
|
+
function readSessionStatusSource() {
|
|
1696
|
+
const ctx = runtime && runtime.ctx;
|
|
1697
|
+
if (!ctx) return undefined;
|
|
1698
|
+
try {
|
|
1699
|
+
return ctx.uiSession && ctx.uiSession.sessionStatus ? ctx.uiSession.sessionStatus : undefined;
|
|
1700
|
+
} catch {
|
|
1701
|
+
return undefined;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1344
1705
|
function useSessionWaiting(sessionId) {
|
|
1345
|
-
const
|
|
1346
|
-
const source = ctx && ctx.uiSession && ctx.uiSession.sessionStatus ? ctx.uiSession.sessionStatus : undefined;
|
|
1706
|
+
const source = readSessionStatusSource();
|
|
1347
1707
|
const subscribe = React.useCallback(
|
|
1348
1708
|
(onChange) => (source !== undefined && typeof source.subscribe === "function" ? source.subscribe(onChange) : () => {}),
|
|
1349
1709
|
[source]
|
|
@@ -1392,6 +1752,7 @@ window.__ModuleLoader__.load({
|
|
|
1392
1752
|
presented: update.presented === true,
|
|
1393
1753
|
produced: update.produced === true,
|
|
1394
1754
|
time: typeof update.time === "number" ? update.time : 0,
|
|
1755
|
+
firstTime: typeof update.time === "number" ? update.time : 0,
|
|
1395
1756
|
seq: typeof update.seq === "number" ? update.seq : 0,
|
|
1396
1757
|
};
|
|
1397
1758
|
folder.artifacts.set(path, artifact);
|
|
@@ -1433,7 +1794,7 @@ window.__ModuleLoader__.load({
|
|
|
1433
1794
|
touch(folder);
|
|
1434
1795
|
return;
|
|
1435
1796
|
}
|
|
1436
|
-
const stored = { ...ref, count: 1 };
|
|
1797
|
+
const stored = { ...ref, count: 1, firstTime: typeof ref.time === "number" ? ref.time : 0 };
|
|
1437
1798
|
noteRefTurn(stored, folder);
|
|
1438
1799
|
folder.refs.set(ref.key, stored);
|
|
1439
1800
|
folder.refOrder.push(ref.key);
|
|
@@ -1448,6 +1809,75 @@ window.__ModuleLoader__.load({
|
|
|
1448
1809
|
|
|
1449
1810
|
/** One-line human target for a tool call: file, command, query, url, name… */
|
|
1450
1811
|
/** Team-record key a tool call creates: teammate name / task subject / subagent label. */
|
|
1812
|
+
/** Match team delegates (members, subagents, tasks) to todo items by normalized text
|
|
1813
|
+
containment, so each delegate renders under the todo it belongs to. */
|
|
1814
|
+
function planTodoAttachments(todos, members, teamTasks, subagents) {
|
|
1815
|
+
const attachments = new Map();
|
|
1816
|
+
if (!Array.isArray(todos) || todos.length === 0) return attachments;
|
|
1817
|
+
const normText = (value) =>
|
|
1818
|
+
typeof value === "string"
|
|
1819
|
+
? value
|
|
1820
|
+
.toLowerCase()
|
|
1821
|
+
.replace(/[-_/]+/g, " ")
|
|
1822
|
+
.replace(/[^a-z0-9\u4e00-\u9fff ]+/g, "")
|
|
1823
|
+
.replace(/\s+/g, " ")
|
|
1824
|
+
.trim()
|
|
1825
|
+
: "";
|
|
1826
|
+
const matchScore = (a, b) => {
|
|
1827
|
+
const x = normText(a);
|
|
1828
|
+
const y = normText(b);
|
|
1829
|
+
if (x === "" || y === "") return 0;
|
|
1830
|
+
if (x === y) return x.length;
|
|
1831
|
+
if (x.length >= 6 && y.includes(x)) return x.length;
|
|
1832
|
+
if (y.length >= 6 && x.includes(y)) return y.length;
|
|
1833
|
+
return 0;
|
|
1834
|
+
};
|
|
1835
|
+
const ownedBy = (id) => teamTasks.filter((task) => isRecord(task) && task.ownerId === id);
|
|
1836
|
+
const bestFor = (texts) => {
|
|
1837
|
+
let best = -1;
|
|
1838
|
+
let bestScore = 5;
|
|
1839
|
+
todos.forEach((todo, index) => {
|
|
1840
|
+
const content = isRecord(todo) ? todo.content : "";
|
|
1841
|
+
let score = 0;
|
|
1842
|
+
for (const text of texts) score = Math.max(score, matchScore(text, content));
|
|
1843
|
+
if (score > bestScore) {
|
|
1844
|
+
bestScore = score;
|
|
1845
|
+
best = index;
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1848
|
+
return best;
|
|
1849
|
+
};
|
|
1850
|
+
const attach = (index, item) => {
|
|
1851
|
+
const list = attachments.get(index);
|
|
1852
|
+
if (list === undefined) attachments.set(index, [item]);
|
|
1853
|
+
else list.push(item);
|
|
1854
|
+
};
|
|
1855
|
+
for (const member of members.filter(isRecord)) {
|
|
1856
|
+
const owned = ownedBy(member.id);
|
|
1857
|
+
const index = bestFor([member.name, member.description, ...owned.map((task) => task.subject)]);
|
|
1858
|
+
if (index >= 0) attach(index, { kind: "member", record: member, owned });
|
|
1859
|
+
}
|
|
1860
|
+
for (const sub of subagents.filter(isRecord)) {
|
|
1861
|
+
const index = bestFor([sub.label]);
|
|
1862
|
+
if (index >= 0) attach(index, { kind: "sub", record: sub, owned: [] });
|
|
1863
|
+
}
|
|
1864
|
+
const memberOwned = new Set();
|
|
1865
|
+
for (const list of attachments.values()) {
|
|
1866
|
+
for (const item of list) {
|
|
1867
|
+
if (item.kind === "member") for (const task of item.owned) memberOwned.add(task.id);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
for (const task of teamTasks.filter(isRecord)) {
|
|
1871
|
+
if (memberOwned.has(task.id)) continue;
|
|
1872
|
+
if (typeof task.ownerId === "string" && members.some((member) => isRecord(member) && member.id === task.ownerId)) continue;
|
|
1873
|
+
const index = bestFor([task.subject]);
|
|
1874
|
+
if (index >= 0) attach(index, { kind: "task", record: task, owned: [] });
|
|
1875
|
+
}
|
|
1876
|
+
const timeOf = (item) => (isRecord(item.record) && typeof item.record.time === "number" ? item.record.time : 0);
|
|
1877
|
+
for (const list of attachments.values()) list.sort((left, right) => timeOf(left) - timeOf(right));
|
|
1878
|
+
return attachments;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1451
1881
|
function teamStepKey(name, args) {
|
|
1452
1882
|
if (!isRecord(args)) return undefined;
|
|
1453
1883
|
if (name === "spawn_teammate" && typeof args.name === "string" && args.name !== "") return args.name;
|
|
@@ -1511,11 +1941,14 @@ window.__ModuleLoader__.load({
|
|
|
1511
1941
|
touch(folder);
|
|
1512
1942
|
}
|
|
1513
1943
|
|
|
1514
|
-
function markStepDone(folder, callId, time, failed) {
|
|
1944
|
+
function markStepDone(folder, callId, time, failed, errText) {
|
|
1515
1945
|
const step = folder.stepByCall.get(callId);
|
|
1516
1946
|
if (step === undefined) return;
|
|
1517
1947
|
step.done = true;
|
|
1518
|
-
if (failed === true)
|
|
1948
|
+
if (failed === true) {
|
|
1949
|
+
step.error = true;
|
|
1950
|
+
if (typeof errText === "string" && errText.trim() !== "") step.err = errText.trim().slice(0, 400);
|
|
1951
|
+
}
|
|
1519
1952
|
if (typeof time === "number") step.endTime = time;
|
|
1520
1953
|
touch(folder);
|
|
1521
1954
|
}
|
|
@@ -1608,6 +2041,32 @@ window.__ModuleLoader__.load({
|
|
|
1608
2041
|
touch(folder);
|
|
1609
2042
|
return;
|
|
1610
2043
|
}
|
|
2044
|
+
case "approval/asked": {
|
|
2045
|
+
if (!isRecord(data) || typeof data.id !== "string" || data.id === "") return;
|
|
2046
|
+
const approvalBucket = turnBucket(folder, folder.currentTurn);
|
|
2047
|
+
const toolName = typeof data.toolName === "string" ? data.toolName : "";
|
|
2048
|
+
addStep(folder, approvalBucket, {
|
|
2049
|
+
callId: `approval-${data.id}`,
|
|
2050
|
+
name: "approval",
|
|
2051
|
+
detail: toolName,
|
|
2052
|
+
full: typeof data.reason === "string" && data.reason !== "" ? `${toolName} · ${data.reason}` : toolName,
|
|
2053
|
+
milestone: true,
|
|
2054
|
+
done: false,
|
|
2055
|
+
seq: event.seq,
|
|
2056
|
+
time: event.time,
|
|
2057
|
+
});
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
case "approval/decided": {
|
|
2061
|
+
if (!isRecord(data) || typeof data.id !== "string" || data.id === "") return;
|
|
2062
|
+
const outcome = typeof data.outcome === "string" ? data.outcome : "";
|
|
2063
|
+
const approvalStep = folder.stepByCall.get(`approval-${data.id}`);
|
|
2064
|
+
if (approvalStep !== undefined && outcome !== "" && typeof approvalStep.detail === "string" && !approvalStep.detail.includes(outcome)) {
|
|
2065
|
+
approvalStep.detail = approvalStep.detail === "" ? outcome : `${approvalStep.detail} · ${outcome}`;
|
|
2066
|
+
}
|
|
2067
|
+
markStepDone(folder, `approval-${data.id}`, event.time, /denied|reject/i.test(outcome), outcome);
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
1611
2070
|
case "workspace/changes": {
|
|
1612
2071
|
if (!isRecord(data) || typeof data.turn !== "number" || !Number.isSafeInteger(data.turn) || data.turn < 1) return;
|
|
1613
2072
|
if (typeof event.seq !== "number") return;
|
|
@@ -1661,6 +2120,23 @@ window.__ModuleLoader__.load({
|
|
|
1661
2120
|
return;
|
|
1662
2121
|
}
|
|
1663
2122
|
const memoryTool = memoryToolsOf(name);
|
|
2123
|
+
if (memoryTool !== null && MEMORY_MUTATION_TOOLS.has(memoryTool)) {
|
|
2124
|
+
const uri =
|
|
2125
|
+
args !== null && isRecord(args)
|
|
2126
|
+
? isVikingUri(args.uri)
|
|
2127
|
+
? args.uri
|
|
2128
|
+
: isVikingUri(args.to)
|
|
2129
|
+
? args.to
|
|
2130
|
+
: isVikingUri(args.parent)
|
|
2131
|
+
? args.parent
|
|
2132
|
+
: null
|
|
2133
|
+
: null;
|
|
2134
|
+
if (uri !== null) {
|
|
2135
|
+
const description = typeof args.description === "string" ? args.description : "";
|
|
2136
|
+
folder.pending.set(callId, { type: "artifact", path: uri, description });
|
|
2137
|
+
}
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
1664
2140
|
if (memoryTool !== null && MEMORY_REFERENCE_TOOLS.has(memoryTool)) {
|
|
1665
2141
|
const detail = [];
|
|
1666
2142
|
const uris = [];
|
|
@@ -1680,10 +2156,10 @@ window.__ModuleLoader__.load({
|
|
|
1680
2156
|
const refs = [];
|
|
1681
2157
|
for (const uri of uris.slice(0, 8)) {
|
|
1682
2158
|
refs.push({
|
|
1683
|
-
kind:
|
|
2159
|
+
kind: vikingRefKind(uri),
|
|
1684
2160
|
uri,
|
|
1685
2161
|
producer: `openviking · ${memoryTool}`,
|
|
1686
|
-
label:
|
|
2162
|
+
label: vikingLabel(uri),
|
|
1687
2163
|
detail: [],
|
|
1688
2164
|
key: `memory-file|${uri}`,
|
|
1689
2165
|
seq: event.seq,
|
|
@@ -1711,7 +2187,7 @@ window.__ModuleLoader__.load({
|
|
|
1711
2187
|
const source = message.source;
|
|
1712
2188
|
const callId = isRecord(source) && typeof source.callId === "string" ? source.callId : undefined;
|
|
1713
2189
|
if (callId === undefined) return;
|
|
1714
|
-
markStepDone(folder, callId, event.time, message.isError === true);
|
|
2190
|
+
markStepDone(folder, callId, event.time, message.isError === true, message.isError === true ? textOf(message.content) : undefined);
|
|
1715
2191
|
if (message.isError === true) {
|
|
1716
2192
|
folder.stats.errors += 1;
|
|
1717
2193
|
const errorBucket = folder.byTurn.get(folder.currentTurn);
|
|
@@ -1723,7 +2199,12 @@ window.__ModuleLoader__.load({
|
|
|
1723
2199
|
if (message.isError === true) return;
|
|
1724
2200
|
const bucket = turnBucket(folder, folder.currentTurn);
|
|
1725
2201
|
if (pending.type === "artifact") {
|
|
1726
|
-
upsertArtifact(folder, bucket, pending.path, {
|
|
2202
|
+
upsertArtifact(folder, bucket, pending.path, {
|
|
2203
|
+
produced: true,
|
|
2204
|
+
...(typeof pending.description === "string" && pending.description !== "" ? { description: pending.description } : {}),
|
|
2205
|
+
seq: event.seq,
|
|
2206
|
+
time: event.time,
|
|
2207
|
+
});
|
|
1727
2208
|
} else {
|
|
1728
2209
|
const resultText = textOf(message.content);
|
|
1729
2210
|
for (const ref of pending.refs) {
|
|
@@ -1738,9 +2219,11 @@ window.__ModuleLoader__.load({
|
|
|
1738
2219
|
return;
|
|
1739
2220
|
}
|
|
1740
2221
|
case "user/message": {
|
|
1741
|
-
if (!
|
|
2222
|
+
if (!isRecord(data)) return;
|
|
1742
2223
|
const source = isRecord(data.source) ? data.source : null;
|
|
1743
2224
|
const kind = source !== null && typeof source.kind === "string" ? source.kind : "user";
|
|
2225
|
+
/* a compact checkpoint replaces the event span it summarizes; every other message must append */
|
|
2226
|
+
if (kind !== "compact-checkpoint" && !isAppendSurfaceEvent(event)) return;
|
|
1744
2227
|
const bucket = turnBucket(folder, folder.currentTurn);
|
|
1745
2228
|
if (kind === "user") {
|
|
1746
2229
|
const mentions = mentionsOfUserText(textOf(data.content));
|
|
@@ -1752,6 +2235,22 @@ window.__ModuleLoader__.load({
|
|
|
1752
2235
|
}
|
|
1753
2236
|
return;
|
|
1754
2237
|
}
|
|
2238
|
+
if (kind === "compact-checkpoint") {
|
|
2239
|
+
/* context compaction is a milestone: anchor it as a durable key step */
|
|
2240
|
+
const compactionId = source !== null && typeof source.compactionId === "string" ? source.compactionId : `seq-${event.seq}`;
|
|
2241
|
+
const summaryText = textOf(data.content);
|
|
2242
|
+
addStep(folder, bucket, {
|
|
2243
|
+
name: "compact-checkpoint",
|
|
2244
|
+
detail: summaryText.length > 1024 ? `≈${Math.round(summaryText.length / 1024)} KB` : "",
|
|
2245
|
+
full: compactionId,
|
|
2246
|
+
teamKey: `compact:${compactionId}`,
|
|
2247
|
+
milestone: true,
|
|
2248
|
+
done: true,
|
|
2249
|
+
seq: event.seq,
|
|
2250
|
+
time: event.time,
|
|
2251
|
+
});
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
1755
2254
|
const label = contextLabel(source, kind);
|
|
1756
2255
|
const detail = contextDetail(source, data.content);
|
|
1757
2256
|
const refKind = refKindForSource(source, kind);
|
|
@@ -1760,11 +2259,11 @@ window.__ModuleLoader__.load({
|
|
|
1760
2259
|
if (refKind === "memory" && detail.length > 0 && detail.every(isVikingUri)) {
|
|
1761
2260
|
for (const uri of detail) {
|
|
1762
2261
|
addReference(folder, bucket, {
|
|
1763
|
-
kind:
|
|
2262
|
+
kind: vikingRefKind(uri),
|
|
1764
2263
|
uri,
|
|
1765
2264
|
producer,
|
|
1766
2265
|
form,
|
|
1767
|
-
label:
|
|
2266
|
+
label: vikingLabel(uri),
|
|
1768
2267
|
detail: [],
|
|
1769
2268
|
key: `memory-file|${uri}`,
|
|
1770
2269
|
seq: event.seq,
|
|
@@ -1871,9 +2370,13 @@ window.__ModuleLoader__.load({
|
|
|
1871
2370
|
const allSteps = Array.isArray(source.steps) ? source.steps : [];
|
|
1872
2371
|
const recentSteps = allSteps.slice(-6);
|
|
1873
2372
|
const anchorCandidates = [...(Array.isArray(source.anchors) ? source.anchors : []), ...allSteps].filter(
|
|
1874
|
-
(step, index, list) =>
|
|
2373
|
+
(step, index, list) =>
|
|
2374
|
+
isRecord(step) && typeof step.teamKey === "string" && step.milestone !== true && list.indexOf(step) === index && recentSteps.indexOf(step) === -1
|
|
1875
2375
|
);
|
|
1876
2376
|
const anchorSteps = anchorCandidates.slice(-6);
|
|
2377
|
+
/* milestone steps (compaction, approvals) ride the anchor lane for retention only;
|
|
2378
|
+
whether they pin above the window is a display choice (cfg.stepAnchors) */
|
|
2379
|
+
const milestoneSteps = (Array.isArray(source.anchors) ? source.anchors : []).filter((step) => isRecord(step) && step.milestone === true);
|
|
1877
2380
|
return {
|
|
1878
2381
|
todos,
|
|
1879
2382
|
todosTime,
|
|
@@ -1884,6 +2387,9 @@ window.__ModuleLoader__.load({
|
|
|
1884
2387
|
team,
|
|
1885
2388
|
stats,
|
|
1886
2389
|
turnStart: st.start > 0 ? st.start : null,
|
|
2390
|
+
turnEnd: st.end > 0 ? st.end : null,
|
|
2391
|
+
milestones: milestoneSteps,
|
|
2392
|
+
todoHistory: folder.todoRecords.slice(-300),
|
|
1887
2393
|
changeTurns: changeTurns.slice(),
|
|
1888
2394
|
hasMore: windowSnapshot.hasMore === true,
|
|
1889
2395
|
localeRev,
|
|
@@ -1893,7 +2399,7 @@ window.__ModuleLoader__.load({
|
|
|
1893
2399
|
};
|
|
1894
2400
|
}
|
|
1895
2401
|
|
|
1896
|
-
const EMPTY_STATE = { todos: null, todosTime: 0, artifacts: [], references: [], steps: [], stepsAll: [], team: { members: [], tasks: [], subagents: [] }, stats: { durationMs: 0, toolCalls: 0, commands: 0, errors: 0, files: 0 }, turnStart: null, changeTurns: [], hasMore: false, localeRev: 0, turns: [], latestTurn: null, scope: "latest" };
|
|
2402
|
+
const EMPTY_STATE = { todos: null, todosTime: 0, artifacts: [], references: [], steps: [], stepsAll: [], team: { members: [], tasks: [], subagents: [] }, stats: { durationMs: 0, toolCalls: 0, commands: 0, errors: 0, files: 0 }, turnStart: null, turnEnd: null, milestones: [], todoHistory: [], changeTurns: [], hasMore: false, localeRev: 0, turns: [], latestTurn: null, scope: "latest" };
|
|
1897
2403
|
|
|
1898
2404
|
function createSummaryStore(binding) {
|
|
1899
2405
|
const source = binding.eventSource;
|
|
@@ -2764,13 +3270,18 @@ function FileTypeIcon(props) {
|
|
|
2764
3270
|
);
|
|
2765
3271
|
}
|
|
2766
3272
|
|
|
3273
|
+
/* Same artwork as icon.svg, the icon the plugin list and settings show. */
|
|
2767
3274
|
function OverviewGlyph({ size = 16, className }) {
|
|
2768
|
-
return svg(
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3275
|
+
return svg(
|
|
3276
|
+
size,
|
|
3277
|
+
className,
|
|
3278
|
+
[
|
|
3279
|
+
h("rect", { key: "r", x: 8, y: 8, width: 48, height: 48, rx: 12, stroke: "#565e6d", strokeWidth: 3 }),
|
|
3280
|
+
h("path", { key: "l", d: "M20 24h24M20 33h24M20 42h14", stroke: "#247bbf", strokeWidth: 3, strokeLinecap: "round" }),
|
|
3281
|
+
h("circle", { key: "c", cx: 43, cy: 42, r: 4, fill: "#2ea36a" }),
|
|
3282
|
+
],
|
|
3283
|
+
"0 0 64 64"
|
|
3284
|
+
);
|
|
2774
3285
|
}
|
|
2775
3286
|
|
|
2776
3287
|
function IconProgress({ size = 16, className }) {
|
|
@@ -2839,6 +3350,14 @@ function FileTypeIcon(props) {
|
|
|
2839
3350
|
return svg(size, className, [h("path", { key: "a", d: "m5.8 3.6 4.4 4.4-4.4 4.4", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" })]);
|
|
2840
3351
|
}
|
|
2841
3352
|
|
|
3353
|
+
function IconCompact({ size = 16, className }) {
|
|
3354
|
+
return svg(size, className, [
|
|
3355
|
+
h("path", { key: "a", d: "M3.2 8h9.6", stroke: "currentColor", strokeLinecap: "round" }),
|
|
3356
|
+
h("path", { key: "b", d: "M8 2.4v3.4M6.3 4.1 8 5.8l1.7-1.7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
3357
|
+
h("path", { key: "c", d: "M8 13.6v-3.4M6.3 11.9 8 10.2l1.7 1.7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
3358
|
+
]);
|
|
3359
|
+
}
|
|
3360
|
+
|
|
2842
3361
|
function IconHistory({ size = 16, className }) {
|
|
2843
3362
|
return svg(size, className, [
|
|
2844
3363
|
h("path", { key: "a", d: "M2.6 8a5.4 5.4 0 1 0 5.4-5.4A5.9 5.9 0 0 0 3.9 4.3L2.6 5.6", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
@@ -3107,9 +3626,13 @@ function FileTypeIcon(props) {
|
|
|
3107
3626
|
complete: "success",
|
|
3108
3627
|
};
|
|
3109
3628
|
|
|
3110
|
-
function ProgressSection({ summary, goal, t, waiting, cfg }) {
|
|
3629
|
+
function ProgressSection({ summary, goal, t, waiting, cfg, asOf }) {
|
|
3111
3630
|
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
3112
3631
|
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3632
|
+
/* timeline scrubbing: only what already happened at `asOf` is shown */
|
|
3633
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
3634
|
+
const pinMilestones = isRecord(cfg) && cfg.stepAnchors === true;
|
|
3635
|
+
const inTime = (record) => asOfTime === null || ((record.firstTime || record.time || 0) <= asOfTime);
|
|
3113
3636
|
/* todo → composer prompt flash */
|
|
3114
3637
|
const [promptFlash, setPromptFlash] = React.useState(null);
|
|
3115
3638
|
const flashTimer = React.useRef(undefined);
|
|
@@ -3164,8 +3687,30 @@ function FileTypeIcon(props) {
|
|
|
3164
3687
|
if (!handled) setFlash("err");
|
|
3165
3688
|
};
|
|
3166
3689
|
/* steps: newest-first window; keep the evicted bottom row as a fading ghost */
|
|
3167
|
-
const
|
|
3168
|
-
const
|
|
3690
|
+
const stepsRaw = Array.isArray(summary.steps) ? summary.steps.filter((step) => isRecord(step)) : [];
|
|
3691
|
+
const stepsAllRaw = Array.isArray(summary.stepsAll) ? summary.stepsAll.filter((step) => isRecord(step)) : [];
|
|
3692
|
+
const stepAt = (step) => asOfTime === null || (typeof step.time === "number" ? step.time <= asOfTime : true);
|
|
3693
|
+
/* a step whose result landed after the scrub point reads as still running */
|
|
3694
|
+
const stepClamped = (step) => {
|
|
3695
|
+
if (asOfTime === null || step.done !== true) return step;
|
|
3696
|
+
const end = typeof step.endTime === "number" ? step.endTime : typeof step.time === "number" ? step.time : 0;
|
|
3697
|
+
return end <= asOfTime ? step : { ...step, done: false, error: false, endTime: undefined };
|
|
3698
|
+
};
|
|
3699
|
+
const stepsAll = stepsAllRaw.filter(stepAt).map(stepClamped);
|
|
3700
|
+
/* while scrubbing, rebuild the "latest 6" window as it looked at time T:
|
|
3701
|
+
the live window sits near the turn end, so filtering it by T would empty
|
|
3702
|
+
the section mid-drag even though earlier steps already happened */
|
|
3703
|
+
const steps =
|
|
3704
|
+
asOfTime === null
|
|
3705
|
+
? stepsRaw.filter(stepAt).map(stepClamped)
|
|
3706
|
+
: (() => {
|
|
3707
|
+
const visible = stepsAllRaw.filter(stepAt);
|
|
3708
|
+
const recent = visible.slice(-6);
|
|
3709
|
+
const anchors = stepsRaw.filter(
|
|
3710
|
+
(step) => stepAt(step) && typeof step.teamKey === "string" && step.milestone !== true && recent.indexOf(step) === -1
|
|
3711
|
+
);
|
|
3712
|
+
return [...anchors, ...recent].map(stepClamped);
|
|
3713
|
+
})();
|
|
3169
3714
|
const [ghost, setGhost] = React.useState(null);
|
|
3170
3715
|
const [stepsExpanded, setStepsExpanded] = React.useState(false);
|
|
3171
3716
|
const ghostTimer = React.useRef(undefined);
|
|
@@ -3173,6 +3718,7 @@ function FileTypeIcon(props) {
|
|
|
3173
3718
|
const prevScope = React.useRef(summary.scope);
|
|
3174
3719
|
/* enter animation only for rows that arrive while mounted; first paint per scope is silent */
|
|
3175
3720
|
const stepKeyOf = (step, index) => (typeof step.callId === "string" && step.callId !== "" ? `step-${step.callId}` : `step-idx-${index}`);
|
|
3721
|
+
const milestoneKeyOf = (step) => (typeof step.callId === "string" && step.callId !== "" ? step.callId : `${step.name}|${step.seq}`);
|
|
3176
3722
|
const seenSteps = React.useRef(null);
|
|
3177
3723
|
/* full retained history for the expander: anchors outside the window + the whole bucket */
|
|
3178
3724
|
const allStepIds = new Set(stepsAll.map((step) => step.callId).filter((id) => typeof id === "string"));
|
|
@@ -3180,7 +3726,25 @@ function FileTypeIcon(props) {
|
|
|
3180
3726
|
const expandedSteps = [...anchorExtras, ...stepsAll];
|
|
3181
3727
|
const canExpand = expandedSteps.length > steps.length;
|
|
3182
3728
|
const stepsOpen = stepsExpanded && canExpand;
|
|
3183
|
-
|
|
3729
|
+
/* cfg.stepAnchors pins compaction/approval/failure steps above the window */
|
|
3730
|
+
const milestoneExtras = [];
|
|
3731
|
+
if (pinMilestones) {
|
|
3732
|
+
const baseSteps = stepsOpen ? expandedSteps : steps;
|
|
3733
|
+
const baseKeys = new Set(baseSteps.map(milestoneKeyOf));
|
|
3734
|
+
const candidates = [
|
|
3735
|
+
...(Array.isArray(summary.milestones) ? summary.milestones.filter((step) => isRecord(step) && step.milestone === true) : []),
|
|
3736
|
+
...stepsAll.filter((step) => step.error === true),
|
|
3737
|
+
];
|
|
3738
|
+
for (const candidate of candidates) {
|
|
3739
|
+
if (!stepAt(candidate)) continue;
|
|
3740
|
+
const key = milestoneKeyOf(candidate);
|
|
3741
|
+
if (baseKeys.has(key)) continue;
|
|
3742
|
+
baseKeys.add(key);
|
|
3743
|
+
milestoneExtras.push(stepClamped(candidate));
|
|
3744
|
+
}
|
|
3745
|
+
milestoneExtras.sort((left, right) => (left.time || 0) - (right.time || 0));
|
|
3746
|
+
}
|
|
3747
|
+
const displaySteps = milestoneExtras.length > 0 ? [...milestoneExtras, ...(stepsOpen ? expandedSteps : steps)] : stepsOpen ? expandedSteps : steps;
|
|
3184
3748
|
if (seenSteps.current === null || seenSteps.current.scope !== summary.scope) {
|
|
3185
3749
|
seenSteps.current = { scope: summary.scope, keys: new Set(displaySteps.map(stepKeyOf)) };
|
|
3186
3750
|
}
|
|
@@ -3197,7 +3761,7 @@ function FileTypeIcon(props) {
|
|
|
3197
3761
|
prevScope.current = summary.scope;
|
|
3198
3762
|
return;
|
|
3199
3763
|
}
|
|
3200
|
-
if (previous.length === 0) return;
|
|
3764
|
+
if (asOfTime !== null || previous.length === 0) return;
|
|
3201
3765
|
const currentIds = new Set(steps.map((step) => step.callId));
|
|
3202
3766
|
const dropped = previous.filter((step) => typeof step.callId === "string" && !currentIds.has(step.callId));
|
|
3203
3767
|
if (dropped.length === 0) return;
|
|
@@ -3210,10 +3774,28 @@ function FileTypeIcon(props) {
|
|
|
3210
3774
|
}, [steps]);
|
|
3211
3775
|
/* team data + todos drive both the merged list and the step nesting */
|
|
3212
3776
|
const team = isRecord(summary.team) ? summary.team : null;
|
|
3213
|
-
const members = team && Array.isArray(team.members) ? team.members.filter((member) => isRecord(member)) : [];
|
|
3214
|
-
const teamTasks = team && Array.isArray(team.tasks) ? team.tasks.filter((task) => isRecord(task)) : [];
|
|
3215
|
-
const subagents = team && Array.isArray(team.subagents) ? team.subagents.filter((sub) => isRecord(sub)) : [];
|
|
3216
|
-
const
|
|
3777
|
+
const members = team && Array.isArray(team.members) ? team.members.filter((member) => isRecord(member) && inTime(member)) : [];
|
|
3778
|
+
const teamTasks = team && Array.isArray(team.tasks) ? team.tasks.filter((task) => isRecord(task) && inTime(task)) : [];
|
|
3779
|
+
const subagents = team && Array.isArray(team.subagents) ? team.subagents.filter((sub) => isRecord(sub) && inTime(sub)) : [];
|
|
3780
|
+
const todoHistory = Array.isArray(summary.todoHistory) ? summary.todoHistory : [];
|
|
3781
|
+
let todos = Array.isArray(summary.todos) ? summary.todos : null;
|
|
3782
|
+
if (asOfTime !== null) {
|
|
3783
|
+
/* mirror buildState's turn scoping: a turn without its own todos must
|
|
3784
|
+
not inherit an earlier turn's snapshot while scrubbing */
|
|
3785
|
+
const scopeText = typeof summary.scope === "string" ? summary.scope : "";
|
|
3786
|
+
const scopedTurn = scopeText.startsWith("turn:")
|
|
3787
|
+
? Number.parseInt(scopeText.slice(5), 10)
|
|
3788
|
+
: scopeText === "latest" && Number.isFinite(summary.latestTurn)
|
|
3789
|
+
? summary.latestTurn
|
|
3790
|
+
: null;
|
|
3791
|
+
let snapshot = null;
|
|
3792
|
+
for (const record of todoHistory) {
|
|
3793
|
+
if (!isRecord(record) || !Array.isArray(record.todos)) continue;
|
|
3794
|
+
if (scopedTurn !== null && record.turn !== scopedTurn) continue;
|
|
3795
|
+
if ((record.time || 0) <= asOfTime) snapshot = record;
|
|
3796
|
+
}
|
|
3797
|
+
todos = snapshot !== null ? snapshot.todos : null;
|
|
3798
|
+
}
|
|
3217
3799
|
const hasTodos = todos !== null && todos.length > 0;
|
|
3218
3800
|
const hasTeam = members.length + teamTasks.length + subagents.length > 0;
|
|
3219
3801
|
const showSteps = !hasTodos && steps.length > 0;
|
|
@@ -3265,23 +3847,11 @@ function FileTypeIcon(props) {
|
|
|
3265
3847
|
h("span", { className: "dsov-time", title: timeTitle(sub.time) }, fmtTime(sub.time))
|
|
3266
3848
|
);
|
|
3267
3849
|
};
|
|
3850
|
+
/* members and subagents always read as agents: person icon, tinted red on failure */
|
|
3268
3851
|
const memberIcon = (member, owned) => {
|
|
3269
3852
|
const phase = typeof member.phase === "string" ? member.phase : "";
|
|
3270
3853
|
if (phase === "failed") {
|
|
3271
|
-
return h(
|
|
3272
|
-
"span",
|
|
3273
|
-
{ className: "dsov-todoIcon", "data-status": "failed" },
|
|
3274
|
-
h(
|
|
3275
|
-
"svg",
|
|
3276
|
-
{ width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true" },
|
|
3277
|
-
h("circle", { cx: 7, cy: 7, r: 5.6, stroke: "currentColor", strokeWidth: 1.3 }),
|
|
3278
|
-
h("path", { d: "M7 4v3.6", stroke: "currentColor", strokeWidth: 1.3, strokeLinecap: "round" }),
|
|
3279
|
-
h("circle", { cx: 7, cy: 9.9, r: 0.8, fill: "currentColor" })
|
|
3280
|
-
)
|
|
3281
|
-
);
|
|
3282
|
-
}
|
|
3283
|
-
if (owned.length > 0 && owned.every((task) => task.status === "completed")) {
|
|
3284
|
-
return h("span", { className: "dsov-todoIcon", "data-status": "completed" }, h(TodoStatusIcon, { status: "completed" }));
|
|
3854
|
+
return h("span", { className: "dsov-todoIcon dsov-agentIcon", "data-status": "failed" }, h(IconAgent, { size: 14 }));
|
|
3285
3855
|
}
|
|
3286
3856
|
return h("span", { className: "dsov-todoIcon dsov-agentIcon" }, h(IconAgent, { size: 14 }));
|
|
3287
3857
|
};
|
|
@@ -3313,6 +3883,20 @@ function FileTypeIcon(props) {
|
|
|
3313
3883
|
const consumedMembers = new Set();
|
|
3314
3884
|
const consumedTasks = new Set();
|
|
3315
3885
|
const consumedSubs = new Set();
|
|
3886
|
+
/* delegates matched to a todo render nested under it and skip the flat tail */
|
|
3887
|
+
const todoAttachments = hasTodos && hasTeam ? planTodoAttachments(todos, members, teamTasks, subagents) : new Map();
|
|
3888
|
+
for (const list of todoAttachments.values()) {
|
|
3889
|
+
for (const item of list) {
|
|
3890
|
+
if (item.kind === "member") {
|
|
3891
|
+
consumedMembers.add(item.record.id);
|
|
3892
|
+
item.owned.forEach((task) => consumedTasks.add(task.id));
|
|
3893
|
+
} else if (item.kind === "sub") {
|
|
3894
|
+
consumedSubs.add(item.record.childId);
|
|
3895
|
+
} else {
|
|
3896
|
+
consumedTasks.add(item.record.id);
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3316
3900
|
const stepAttachments = new Map();
|
|
3317
3901
|
if (showSteps && hasTeam) {
|
|
3318
3902
|
displaySteps.forEach((step, index) => {
|
|
@@ -3412,7 +3996,9 @@ function FileTypeIcon(props) {
|
|
|
3412
3996
|
h(IconChevron, { size: 11 })
|
|
3413
3997
|
)
|
|
3414
3998
|
: null,
|
|
3415
|
-
|
|
3999
|
+
step.name === "compact-checkpoint"
|
|
4000
|
+
? h("span", { className: "dsov-todoIcon dsov-compactIcon" }, h(IconCompact, { size: 15 }))
|
|
4001
|
+
: waitingRow
|
|
3416
4002
|
? h(
|
|
3417
4003
|
"span",
|
|
3418
4004
|
{ className: "dsov-todoIcon", "data-status": "waiting" },
|
|
@@ -3424,7 +4010,11 @@ function FileTypeIcon(props) {
|
|
|
3424
4010
|
)
|
|
3425
4011
|
)
|
|
3426
4012
|
: h("span", { className: "dsov-todoIcon", "data-status": stepRunning ? "in_progress" : "completed" }, h(TodoStatusIcon, { status: stepRunning ? "in_progress" : "completed" })),
|
|
3427
|
-
h(
|
|
4013
|
+
h(
|
|
4014
|
+
"span",
|
|
4015
|
+
{ className: "dsov-stepName" },
|
|
4016
|
+
step.name === "compact-checkpoint" ? t("step.compact") : step.name === "approval" ? t("step.approval") : typeof step.name === "string" ? step.name : ""
|
|
4017
|
+
),
|
|
3428
4018
|
h("span", { className: "dsov-stepDetail" }, detail),
|
|
3429
4019
|
h("span", { className: "dsov-time", title: timeTitle(typeof step.endTime === "number" ? step.endTime : step.time) }, fmtTime(typeof step.endTime === "number" ? step.endTime : step.time))
|
|
3430
4020
|
);
|
|
@@ -3459,6 +4049,7 @@ function FileTypeIcon(props) {
|
|
|
3459
4049
|
h("div", { className: "dsov-progressMeta" }, t("progress.counts", { completed: done, total }))
|
|
3460
4050
|
)
|
|
3461
4051
|
);
|
|
4052
|
+
const todoRows = [];
|
|
3462
4053
|
todos.forEach((todo, index) => {
|
|
3463
4054
|
const status = TODO_KEYS[todo.status] ? todo.status : "pending";
|
|
3464
4055
|
const flash = promptFlash !== null && isRecord(promptFlash) && promptFlash.index === index ? promptFlash.state : null;
|
|
@@ -3471,7 +4062,21 @@ function FileTypeIcon(props) {
|
|
|
3471
4062
|
: flash === "failed"
|
|
3472
4063
|
? t("progress.todoFailed")
|
|
3473
4064
|
: t("progress.todoHint");
|
|
3474
|
-
|
|
4065
|
+
const attached = todoAttachments.get(index);
|
|
4066
|
+
const expandable = Array.isArray(attached) && attached.length > 0;
|
|
4067
|
+
const open = expandable
|
|
4068
|
+
? overrideOf(
|
|
4069
|
+
`todo:${index}`,
|
|
4070
|
+
attached.some((item) =>
|
|
4071
|
+
item.kind === "task"
|
|
4072
|
+
? item.record.status !== "completed"
|
|
4073
|
+
: item.kind === "member"
|
|
4074
|
+
? item.record.phase === "provisioning" || item.record.phase === "active" || item.owned.some((task) => task.status !== "completed")
|
|
4075
|
+
: false
|
|
4076
|
+
)
|
|
4077
|
+
)
|
|
4078
|
+
: false;
|
|
4079
|
+
todoRows.push(
|
|
3475
4080
|
h(
|
|
3476
4081
|
"div",
|
|
3477
4082
|
{
|
|
@@ -3479,9 +4084,12 @@ function FileTypeIcon(props) {
|
|
|
3479
4084
|
key: `todo-${index}`,
|
|
3480
4085
|
"data-status": status,
|
|
3481
4086
|
"data-flash": flash === "inserted" || flash === "copied" ? "ok" : flash === "failed" ? "err" : undefined,
|
|
4087
|
+
"data-expandable": expandable ? "true" : undefined,
|
|
4088
|
+
"data-open": expandable ? (open ? "true" : "false") : undefined,
|
|
3482
4089
|
role: "button",
|
|
3483
4090
|
tabIndex: todoPromptOn ? 0 : undefined,
|
|
3484
4091
|
title,
|
|
4092
|
+
onClick: expandable ? () => toggleKey(`todo:${index}`, !open) : undefined,
|
|
3485
4093
|
onDoubleClick: () => onTodoClick(index, todo.content),
|
|
3486
4094
|
onKeyDown: (event) => {
|
|
3487
4095
|
if (event.key === "Enter" || event.key === " ") {
|
|
@@ -3490,12 +4098,46 @@ function FileTypeIcon(props) {
|
|
|
3490
4098
|
}
|
|
3491
4099
|
},
|
|
3492
4100
|
},
|
|
3493
|
-
h(
|
|
4101
|
+
h(
|
|
4102
|
+
"span",
|
|
4103
|
+
{ className: "dsov-todoIconSlot" },
|
|
4104
|
+
h("span", { className: "dsov-todoIcon", "data-status": status }, h(TodoStatusIcon, { status })),
|
|
4105
|
+
expandable
|
|
4106
|
+
? h(
|
|
4107
|
+
"span",
|
|
4108
|
+
{
|
|
4109
|
+
className: "dsov-chevron dsov-todoChevron",
|
|
4110
|
+
"data-open": open ? "true" : "false",
|
|
4111
|
+
title: t(open ? "progress.collapseTeam" : "progress.expandTeam"),
|
|
4112
|
+
onClick: (event) => {
|
|
4113
|
+
event.stopPropagation();
|
|
4114
|
+
toggleKey(`todo:${index}`, !open);
|
|
4115
|
+
},
|
|
4116
|
+
},
|
|
4117
|
+
h(IconChevron, { size: 11 })
|
|
4118
|
+
)
|
|
4119
|
+
: null
|
|
4120
|
+
),
|
|
3494
4121
|
h("span", { className: "dsov-todoText" }, todo.content),
|
|
3495
4122
|
h("span", { className: "dsov-todoStatus" }, t(TODO_KEYS[status]))
|
|
3496
4123
|
)
|
|
3497
4124
|
);
|
|
4125
|
+
if (expandable && open) {
|
|
4126
|
+
attached.forEach((item) => {
|
|
4127
|
+
if (item.kind === "member") {
|
|
4128
|
+
const memberExpandable = item.owned.length > 0;
|
|
4129
|
+
const memberOpen = memberExpandable ? overrideOf(`member:${item.record.id}`, item.owned.some((task) => task.status !== "completed")) : false;
|
|
4130
|
+
todoRows.push(memberRow(item.record, 1, memberExpandable, memberOpen));
|
|
4131
|
+
if (memberExpandable && memberOpen) item.owned.forEach((task) => todoRows.push(taskRow(task, 2)));
|
|
4132
|
+
} else if (item.kind === "sub") {
|
|
4133
|
+
todoRows.push(subRow(item.record, 1));
|
|
4134
|
+
} else {
|
|
4135
|
+
todoRows.push(taskRow(item.record, 1));
|
|
4136
|
+
}
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
3498
4139
|
});
|
|
4140
|
+
kids.push(h("div", { className: "dsov-todos", key: "todos" }, todoRows));
|
|
3499
4141
|
}
|
|
3500
4142
|
if (hasTeam && !showSteps) pushFlatTeam(kids);
|
|
3501
4143
|
if (showSteps) {
|
|
@@ -3513,7 +4155,7 @@ function FileTypeIcon(props) {
|
|
|
3513
4155
|
let freshCount = 0;
|
|
3514
4156
|
displaySteps.forEach((step, index) => {
|
|
3515
4157
|
const key = stepKeyOf(step, index);
|
|
3516
|
-
const fresh = !seenSteps.current.keys.has(key);
|
|
4158
|
+
const fresh = asOfTime === null && !seenSteps.current.keys.has(key);
|
|
3517
4159
|
if (fresh) {
|
|
3518
4160
|
seenSteps.current.keys.add(key);
|
|
3519
4161
|
freshCount += 1;
|
|
@@ -3549,12 +4191,33 @@ function FileTypeIcon(props) {
|
|
|
3549
4191
|
return h(Section, { id: "progress", icon: h(IconProgress, { size: 15 }), title: t("section.progress"), empty: t(summary.scope === "all" ? "progress.empty" : "progress.emptyTurn") }, kids);
|
|
3550
4192
|
}
|
|
3551
4193
|
|
|
3552
|
-
function ArtifactsSection({ summary, t, binding, stats }) {
|
|
4194
|
+
function ArtifactsSection({ summary, t, binding, stats, asOf }) {
|
|
3553
4195
|
const cfg = useConfig();
|
|
4196
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
4197
|
+
const visibleArtifacts = asOfTime === null ? summary.artifacts : summary.artifacts.filter((artifact) => (artifact.firstTime || artifact.time || 0) <= asOfTime);
|
|
3554
4198
|
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
3555
4199
|
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3556
4200
|
const sessionId = binding ? binding.sessionId : undefined;
|
|
3557
4201
|
const cwd = sessionCwd(sessionId);
|
|
4202
|
+
const canFileMenu = hostSessionRemote() !== null;
|
|
4203
|
+
const [fileMenu, setFileMenu] = React.useState(null);
|
|
4204
|
+
React.useEffect(() => {
|
|
4205
|
+
if (fileMenu === null) return undefined;
|
|
4206
|
+
const close = () => setFileMenu(null);
|
|
4207
|
+
const onKey = (event) => {
|
|
4208
|
+
if (event.key === "Escape") close();
|
|
4209
|
+
};
|
|
4210
|
+
document.addEventListener("mousedown", close);
|
|
4211
|
+
document.addEventListener("keydown", onKey);
|
|
4212
|
+
window.addEventListener("scroll", close, true);
|
|
4213
|
+
window.addEventListener("resize", close);
|
|
4214
|
+
return () => {
|
|
4215
|
+
document.removeEventListener("mousedown", close);
|
|
4216
|
+
document.removeEventListener("keydown", onKey);
|
|
4217
|
+
window.removeEventListener("scroll", close, true);
|
|
4218
|
+
window.removeEventListener("resize", close);
|
|
4219
|
+
};
|
|
4220
|
+
}, [fileMenu]);
|
|
3558
4221
|
const turns = summary.changeTurns;
|
|
3559
4222
|
const latest = turns.length > 0 ? turns[turns.length - 1] : null;
|
|
3560
4223
|
const action =
|
|
@@ -3566,9 +4229,10 @@ function FileTypeIcon(props) {
|
|
|
3566
4229
|
t("artifacts.viewAll")
|
|
3567
4230
|
)
|
|
3568
4231
|
: null;
|
|
3569
|
-
const rows =
|
|
4232
|
+
const rows = visibleArtifacts.map((artifact, index) => {
|
|
3570
4233
|
const stat = stats ? stats.get(absPath(cwd, artifact.path)) : undefined;
|
|
3571
4234
|
const showCounts = stat !== undefined && !stat.binary && !stat.oversized && (stat.added > 0 || stat.deleted > 0);
|
|
4235
|
+
const isMemory = typeof artifact.path === "string" && artifact.path.startsWith("viking://");
|
|
3572
4236
|
return h(
|
|
3573
4237
|
"button",
|
|
3574
4238
|
{
|
|
@@ -3577,6 +4241,13 @@ function FileTypeIcon(props) {
|
|
|
3577
4241
|
key: `${artifact.path}#${index}`,
|
|
3578
4242
|
title: artifact.description ? `${artifact.path} — ${artifact.description}` : artifact.path,
|
|
3579
4243
|
onClick: () => openArtifact(binding, artifact, stats),
|
|
4244
|
+
onContextMenu:
|
|
4245
|
+
canFileMenu && !isMemory
|
|
4246
|
+
? (event) => {
|
|
4247
|
+
event.preventDefault();
|
|
4248
|
+
setFileMenu({ x: event.clientX, y: event.clientY, path: absPath(cwd, artifact.path) });
|
|
4249
|
+
}
|
|
4250
|
+
: undefined,
|
|
3580
4251
|
},
|
|
3581
4252
|
h("span", { className: "dsov-fileIcon" }, h(FileTypeIcon, { path: artifact.path, size: 20 })),
|
|
3582
4253
|
h("span", { className: "dsov-artifactName" }, basename(artifact.path)),
|
|
@@ -3590,17 +4261,19 @@ function FileTypeIcon(props) {
|
|
|
3590
4261
|
: stat !== undefined && (stat.binary || stat.oversized)
|
|
3591
4262
|
? h("span", { className: "dsov-badge", "data-tone": "quiet" }, t("artifact.binary"))
|
|
3592
4263
|
: null,
|
|
4264
|
+
isMemory ? h("span", { className: "dsov-badge", "data-tone": "quiet" }, t("artifact.memory")) : null,
|
|
3593
4265
|
artifact.presented ? h("span", { className: "dsov-badge", "data-tone": "brand" }, t("artifact.presented")) : null,
|
|
3594
4266
|
h("span", { className: "dsov-rowTail" }, h("span", { className: "dsov-time", title: timeTitle(artifact.time) }, fmtTime(artifact.time)))
|
|
3595
4267
|
);
|
|
3596
4268
|
});
|
|
4269
|
+
if (fileMenu !== null) rows.push(h(ArtifactFileMenu, { key: "dsov-file-menu", menu: fileMenu, t, onClose: () => setFileMenu(null) }));
|
|
3597
4270
|
return h(
|
|
3598
4271
|
Section,
|
|
3599
4272
|
{
|
|
3600
4273
|
id: "artifacts",
|
|
3601
4274
|
icon: h(IconArtifact, { size: 15 }),
|
|
3602
4275
|
title: t("section.artifacts"),
|
|
3603
|
-
badge:
|
|
4276
|
+
badge: visibleArtifacts.length > 0 ? String(visibleArtifacts.length) : undefined,
|
|
3604
4277
|
empty: t("artifacts.empty"),
|
|
3605
4278
|
action,
|
|
3606
4279
|
},
|
|
@@ -3706,8 +4379,19 @@ function FileTypeIcon(props) {
|
|
|
3706
4379
|
);
|
|
3707
4380
|
}
|
|
3708
4381
|
|
|
3709
|
-
|
|
4382
|
+
/** Ref tile: viking rows get file/folder icons, other kinds keep their type icon. */
|
|
4383
|
+
function refTile(ref) {
|
|
4384
|
+
if (ref.kind === "file") return h(FileTypeIcon, { path: ref.label, size: 18 });
|
|
4385
|
+
if ((ref.kind === "memory-file" || ref.kind === "skill") && typeof ref.uri === "string" && isVikingUri(ref.uri)) {
|
|
4386
|
+
return vikingIsDir(ref.uri) ? h(FileTypeIcon, { kind: "folder", size: 18 }) : h(FileTypeIcon, { path: ref.uri, size: 18 });
|
|
4387
|
+
}
|
|
4388
|
+
return h(RefIcon, { kind: ref.kind, size: 14 });
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
function ReferencesSection({ summary, t, sessionId, asOf }) {
|
|
3710
4392
|
const cfg = useConfig();
|
|
4393
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
4394
|
+
const allRefs = asOfTime === null ? summary.references : summary.references.filter((ref) => (ref.firstTime || ref.time || 0) <= asOfTime);
|
|
3711
4395
|
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
3712
4396
|
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3713
4397
|
const sortMode = useRefSort();
|
|
@@ -3745,7 +4429,7 @@ function FileTypeIcon(props) {
|
|
|
3745
4429
|
}, 1200);
|
|
3746
4430
|
}
|
|
3747
4431
|
};
|
|
3748
|
-
const scoped = hideCommon ?
|
|
4432
|
+
const scoped = hideCommon ? allRefs.filter((ref) => ref.ubiquitous !== true) : allRefs;
|
|
3749
4433
|
const selectedTypes = Array.isArray(typeFilter) ? typeFilter : [];
|
|
3750
4434
|
const filtered = selectedTypes.length === 0 ? scoped : scoped.filter((ref) => selectedTypes.indexOf(refDisplayType(ref)) !== -1);
|
|
3751
4435
|
const sorted = sortReferences(filtered, sortMode);
|
|
@@ -3755,7 +4439,7 @@ function FileTypeIcon(props) {
|
|
|
3755
4439
|
const extra = detail.length - shown.length;
|
|
3756
4440
|
const skillCatalog = ref.producer === "skill-catalog" && shown.length > 0;
|
|
3757
4441
|
const content = [
|
|
3758
|
-
h("span", { className: "dsov-tile", key: "tile" },
|
|
4442
|
+
h("span", { className: "dsov-tile", key: "tile" }, refTile(ref)),
|
|
3759
4443
|
h(
|
|
3760
4444
|
"span",
|
|
3761
4445
|
{ className: "dsov-rowText", key: "text" },
|
|
@@ -3822,7 +4506,7 @@ function FileTypeIcon(props) {
|
|
|
3822
4506
|
...content
|
|
3823
4507
|
);
|
|
3824
4508
|
}
|
|
3825
|
-
if (ref.kind === "memory-file" && isVikingUri(ref.uri) && cfg.vikingPreview && cfg.vikingUrl !== "") {
|
|
4509
|
+
if ((ref.kind === "memory-file" || ref.kind === "skill") && isVikingUri(ref.uri) && cfg.vikingPreview && cfg.vikingUrl !== "") {
|
|
3826
4510
|
return h(
|
|
3827
4511
|
"button",
|
|
3828
4512
|
{
|
|
@@ -3850,13 +4534,13 @@ function FileTypeIcon(props) {
|
|
|
3850
4534
|
}
|
|
3851
4535
|
return h(
|
|
3852
4536
|
"div",
|
|
3853
|
-
{ className: "dsov-row dsov-rowStatic", key: `ref-${index}`, title:
|
|
4537
|
+
{ className: "dsov-row dsov-rowStatic", key: `ref-${index}`, title: isVikingUri(ref.uri) ? ref.uri : ref.label },
|
|
3854
4538
|
...content
|
|
3855
4539
|
);
|
|
3856
4540
|
});
|
|
3857
|
-
const ubiquitousCount =
|
|
4541
|
+
const ubiquitousCount = allRefs.filter((ref) => ref.ubiquitous === true).length;
|
|
3858
4542
|
const typeSet = [];
|
|
3859
|
-
for (const ref of
|
|
4543
|
+
for (const ref of allRefs) {
|
|
3860
4544
|
const kind = refDisplayType(ref);
|
|
3861
4545
|
if (kind !== "" && typeSet.indexOf(kind) === -1) typeSet.push(kind);
|
|
3862
4546
|
}
|
|
@@ -3892,7 +4576,7 @@ function FileTypeIcon(props) {
|
|
|
3892
4576
|
badge: filtered.length > 0 ? String(filtered.length) : undefined,
|
|
3893
4577
|
empty: hideCommon && ubiquitousCount > 0 && scoped.length === 0 ? t("references.onlyCommon") : t("references.empty"),
|
|
3894
4578
|
action:
|
|
3895
|
-
|
|
4579
|
+
allRefs.length > 1
|
|
3896
4580
|
? h(
|
|
3897
4581
|
"div",
|
|
3898
4582
|
{ className: "dsov-refControls" },
|
|
@@ -3954,28 +4638,150 @@ function FileTypeIcon(props) {
|
|
|
3954
4638
|
}
|
|
3955
4639
|
|
|
3956
4640
|
/** Left-hand stat pills on the scope row: duration, tool calls, commands, files, failures. */
|
|
3957
|
-
function StatsBar({ stats, t }) {
|
|
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]);
|
|
3958
4669
|
if (!isRecord(stats)) return null;
|
|
3959
4670
|
const duration = typeof stats.durationMs === "number" ? stats.durationMs : 0;
|
|
3960
4671
|
const toolCalls = typeof stats.toolCalls === "number" ? stats.toolCalls : 0;
|
|
3961
4672
|
const commands = typeof stats.commands === "number" ? stats.commands : 0;
|
|
3962
4673
|
const files = typeof stats.files === "number" ? stats.files : 0;
|
|
3963
4674
|
const errors = typeof stats.errors === "number" ? stats.errors : 0;
|
|
3964
|
-
|
|
4675
|
+
/* scrubbed to 0s: every pill is zero and the bar would vanish — keep it
|
|
4676
|
+
alive with the "previous turn" jump instead */
|
|
4677
|
+
const jumpBtn =
|
|
4678
|
+
Number.isFinite(prevJump) && typeof onPrevTurn === "function"
|
|
4679
|
+
? h(
|
|
4680
|
+
"button",
|
|
4681
|
+
{ type: "button", className: "dsov-stat dsov-statBtn dsov-prevTurn", key: "prevTurn", title: t("timeline.prevTurnTitle", { n: prevJump }), onClick: onPrevTurn },
|
|
4682
|
+
`\u2039 ${t("timeline.prevTurn")}`
|
|
4683
|
+
)
|
|
4684
|
+
: null;
|
|
4685
|
+
if (duration === 0 && toolCalls === 0) return jumpBtn === null ? null : h("div", { className: "dsov-stats" }, jumpBtn);
|
|
3965
4686
|
const pill = (key, text, title, tone) => h("span", { className: "dsov-stat", key, title, ...(tone === undefined ? {} : { "data-tone": tone }) }, text);
|
|
4687
|
+
const scrubable = canTimeline === true && typeof onToggleTimeline === "function";
|
|
3966
4688
|
return h(
|
|
3967
4689
|
"div",
|
|
3968
4690
|
{ className: "dsov-stats" },
|
|
3969
|
-
|
|
4691
|
+
jumpBtn,
|
|
4692
|
+
duration > 0
|
|
4693
|
+
? scrubable
|
|
4694
|
+
? h(
|
|
4695
|
+
"button",
|
|
4696
|
+
{
|
|
4697
|
+
type: "button",
|
|
4698
|
+
className: "dsov-stat dsov-statBtn",
|
|
4699
|
+
key: "duration",
|
|
4700
|
+
title: t("stats.duration.timeline"),
|
|
4701
|
+
"data-active": timelineOpen === true ? "true" : undefined,
|
|
4702
|
+
"data-scrub": scrubbing === true ? "true" : undefined,
|
|
4703
|
+
onClick: onToggleTimeline,
|
|
4704
|
+
},
|
|
4705
|
+
t("stats.duration", { v: formatDuration(duration) })
|
|
4706
|
+
)
|
|
4707
|
+
: pill("duration", t("stats.duration", { v: formatDuration(duration) }), t("stats.duration.title"))
|
|
4708
|
+
: null,
|
|
3970
4709
|
pill("tools", t("stats.tools", { n: toolCalls }), t("stats.tools.title")),
|
|
3971
4710
|
commands > 0 ? pill("commands", t("stats.commands", { n: commands }), t("stats.commands.title")) : null,
|
|
3972
4711
|
files > 0 ? pill("files", t("stats.files", { n: files }), t("stats.files.title")) : null,
|
|
3973
|
-
errors > 0
|
|
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
|
|
3974
4777
|
);
|
|
3975
4778
|
}
|
|
3976
4779
|
|
|
3977
4780
|
function OverviewContent({ binding, t, variant }) {
|
|
3978
4781
|
const [selection, setSelection] = React.useState("latest");
|
|
4782
|
+
/* timeline scrubbing: a null asOf always means "live/latest state" */
|
|
4783
|
+
const [asOf, setAsOf] = React.useState(null);
|
|
4784
|
+
const [timelineOpen, setTimelineOpen] = React.useState(false);
|
|
3979
4785
|
const sessionWaiting = useSessionWaiting(binding ? binding.sessionId : undefined);
|
|
3980
4786
|
/* Follow quick-nav linkage clicks issued after this instance mounted. */
|
|
3981
4787
|
const navRev = useNavScopeRev();
|
|
@@ -3984,6 +4790,11 @@ function FileTypeIcon(props) {
|
|
|
3984
4790
|
if (navScopeStore.rev === mountNavRev.current) return;
|
|
3985
4791
|
if (typeof navScopeStore.selection === "string") setSelection(navScopeStore.selection);
|
|
3986
4792
|
}, [navRev]);
|
|
4793
|
+
React.useEffect(() => {
|
|
4794
|
+
/* new scope: back to live, but keep the timeline row open so a
|
|
4795
|
+
"上一轮" jump lands ready to drag in the previous turn */
|
|
4796
|
+
setAsOf(null);
|
|
4797
|
+
}, [selection]);
|
|
3987
4798
|
const summary = useSummary(binding, selection);
|
|
3988
4799
|
useBackfill(binding, summary.hasMore);
|
|
3989
4800
|
const goal = useGoal(binding);
|
|
@@ -4012,7 +4823,111 @@ function FileTypeIcon(props) {
|
|
|
4012
4823
|
h("option", { value: "all", key: "all" }, t("scope.all"))
|
|
4013
4824
|
)
|
|
4014
4825
|
: null;
|
|
4015
|
-
|
|
4826
|
+
/* while scrubbing, the stats pills recompute from the steps that already happened */
|
|
4827
|
+
const viewStats = React.useMemo(() => {
|
|
4828
|
+
if (asOf === null || typeof summary.turnStart !== "number") return summary.stats;
|
|
4829
|
+
const list = (Array.isArray(summary.stepsAll) ? summary.stepsAll : []).filter((step) => isRecord(step) && (step.time || 0) <= asOf);
|
|
4830
|
+
return {
|
|
4831
|
+
...summary.stats,
|
|
4832
|
+
durationMs: Math.max(0, asOf - summary.turnStart),
|
|
4833
|
+
toolCalls: list.length,
|
|
4834
|
+
commands: list.filter((step) => step.name === "bash").length,
|
|
4835
|
+
errors: list.filter((step) => step.error === true && (step.endTime || step.time || 0) <= asOf).length,
|
|
4836
|
+
};
|
|
4837
|
+
}, [asOf, summary]);
|
|
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]);
|
|
4848
|
+
/* at the 0s point nothing has happened yet in this turn: offer a jump to
|
|
4849
|
+
the previous turn, rendered inside the (otherwise empty) stats bar */
|
|
4850
|
+
const turnNums = Array.isArray(summary.turns) ? summary.turns.filter((value) => Number.isFinite(value)) : [];
|
|
4851
|
+
let prevTurn = null;
|
|
4852
|
+
if (scopeTurn !== null) {
|
|
4853
|
+
const oldest = turnNums.length > 0 ? turnNums[0] : 1;
|
|
4854
|
+
if (scopeTurn - 1 >= oldest) prevTurn = scopeTurn - 1;
|
|
4855
|
+
} else if (scope === "latest" && turnNums.length >= 2) {
|
|
4856
|
+
prevTurn = turnNums[turnNums.length - 2];
|
|
4857
|
+
}
|
|
4858
|
+
const statsBar =
|
|
4859
|
+
cfg.showStats !== false
|
|
4860
|
+
? h(StatsBar, {
|
|
4861
|
+
stats: viewStats,
|
|
4862
|
+
t,
|
|
4863
|
+
canTimeline,
|
|
4864
|
+
timelineOpen,
|
|
4865
|
+
scrubbing: asOf !== null,
|
|
4866
|
+
failedSteps,
|
|
4867
|
+
prevJump: asOf !== null && asOf <= summary.turnStart ? prevTurn : null,
|
|
4868
|
+
onPrevTurn: () => {
|
|
4869
|
+
setSelection(`turn:${prevTurn}`);
|
|
4870
|
+
setAsOf(null);
|
|
4871
|
+
setTimelineOpen(true);
|
|
4872
|
+
},
|
|
4873
|
+
onToggleTimeline: () => {
|
|
4874
|
+
setTimelineOpen((open) => {
|
|
4875
|
+
if (open) setAsOf(null);
|
|
4876
|
+
return !open;
|
|
4877
|
+
});
|
|
4878
|
+
},
|
|
4879
|
+
})
|
|
4880
|
+
: null;
|
|
4881
|
+
const timelineTotal = canTimeline ? summary.turnEnd - summary.turnStart : 0;
|
|
4882
|
+
const timelineValue = asOf === null ? summary.turnEnd : Math.min(Math.max(asOf, summary.turnStart), summary.turnEnd);
|
|
4883
|
+
const timelinePct = timelineTotal > 0 ? ((timelineValue - summary.turnStart) / timelineTotal) * 100 : 100;
|
|
4884
|
+
const timelineBubble = asOf === null ? t("timeline.live") : `${formatElapsed(asOf - summary.turnStart)} · ${formatTime(asOf)}`;
|
|
4885
|
+
const timelineRow =
|
|
4886
|
+
timelineOpen && canTimeline
|
|
4887
|
+
? h(
|
|
4888
|
+
"div",
|
|
4889
|
+
{ className: "dsov-timeline" },
|
|
4890
|
+
/* fixed-width label: the scope's total duration never changes while scrubbing */
|
|
4891
|
+
h("span", { className: "dsov-timelineLabel" }, t("stats.duration", { v: formatDuration(timelineTotal) })),
|
|
4892
|
+
h(
|
|
4893
|
+
"div",
|
|
4894
|
+
{ className: "dsov-timelineTrack" },
|
|
4895
|
+
h("input", {
|
|
4896
|
+
type: "range",
|
|
4897
|
+
className: "dsov-timelineSlider",
|
|
4898
|
+
min: summary.turnStart,
|
|
4899
|
+
max: summary.turnEnd,
|
|
4900
|
+
step: Math.max(1, Math.floor(timelineTotal / 1000)),
|
|
4901
|
+
value: timelineValue,
|
|
4902
|
+
title: timelineBubble,
|
|
4903
|
+
onChange: (event) => {
|
|
4904
|
+
const next = Number(event.target.value);
|
|
4905
|
+
setAsOf(next >= summary.turnEnd ? null : next);
|
|
4906
|
+
},
|
|
4907
|
+
}),
|
|
4908
|
+
h("span", { className: "dsov-timelineBubble", style: { left: `${timelinePct}%` } }, timelineBubble)
|
|
4909
|
+
),
|
|
4910
|
+
h(
|
|
4911
|
+
"button",
|
|
4912
|
+
{ type: "button", className: "dsov-moreSteps", title: t("timeline.reset"), disabled: asOf === null, onClick: () => setAsOf(null) },
|
|
4913
|
+
t("timeline.reset")
|
|
4914
|
+
),
|
|
4915
|
+
h(
|
|
4916
|
+
"button",
|
|
4917
|
+
{
|
|
4918
|
+
type: "button",
|
|
4919
|
+
className: "dsov-moreSteps",
|
|
4920
|
+
title: t("timeline.close"),
|
|
4921
|
+
"aria-label": t("timeline.close"),
|
|
4922
|
+
onClick: () => {
|
|
4923
|
+
setTimelineOpen(false);
|
|
4924
|
+
setAsOf(null);
|
|
4925
|
+
},
|
|
4926
|
+
},
|
|
4927
|
+
"\u00d7"
|
|
4928
|
+
)
|
|
4929
|
+
)
|
|
4930
|
+
: null;
|
|
4016
4931
|
const hasStats = cfg.showStats !== false && isRecord(summary.stats) && (summary.stats.durationMs > 0 || summary.stats.toolCalls > 0);
|
|
4017
4932
|
const barPresent = variant === "panel" ? scopeSelect !== null || hasStats : !(scopeSelect === null && cfg.placement === "conversation" && !hasStats);
|
|
4018
4933
|
/* toggle data-stuck on the sticky wrapper: background only shows while pinned */
|
|
@@ -4069,7 +4984,8 @@ function FileTypeIcon(props) {
|
|
|
4069
4984
|
{ className: "dsov-turnbar" },
|
|
4070
4985
|
statsBar !== null ? h("div", { className: "dsov-turnbarScroll" }, statsBar) : null,
|
|
4071
4986
|
scopeSelect !== null ? h("div", { className: "dsov-turnbarFixed" }, scopeSelect) : null
|
|
4072
|
-
)
|
|
4987
|
+
),
|
|
4988
|
+
timelineRow
|
|
4073
4989
|
)
|
|
4074
4990
|
: null
|
|
4075
4991
|
: scopeSelect === null && cfg.placement === "conversation" && !hasStats
|
|
@@ -4094,26 +5010,40 @@ function FileTypeIcon(props) {
|
|
|
4094
5010
|
t("action.openInSidebar")
|
|
4095
5011
|
)
|
|
4096
5012
|
)
|
|
4097
|
-
)
|
|
5013
|
+
),
|
|
5014
|
+
timelineRow
|
|
4098
5015
|
),
|
|
4099
5016
|
summary.hasMore ? h("div", { className: "dsov-loading" }, t("state.loadingHistory")) : null,
|
|
4100
|
-
cfg.showProgress ? h(ProgressSection, { summary, goal, t, waiting: sessionWaiting, cfg }) : null,
|
|
4101
|
-
cfg.showArtifacts ? h(ArtifactsSection, { summary, t, binding, stats }) : null,
|
|
4102
|
-
cfg.showReferences ? h(ReferencesSection, { summary, t, sessionId }) : null
|
|
5017
|
+
cfg.showProgress ? h(ProgressSection, { summary, goal, t, waiting: sessionWaiting && asOf === null, cfg, asOf }) : null,
|
|
5018
|
+
cfg.showArtifacts ? h(ArtifactsSection, { summary, t, binding, stats, asOf }) : null,
|
|
5019
|
+
cfg.showReferences ? h(ReferencesSection, { summary, t, sessionId, asOf }) : null
|
|
4103
5020
|
);
|
|
4104
5021
|
}
|
|
4105
5022
|
|
|
4106
5023
|
class OverviewBoundary extends React.Component {
|
|
4107
5024
|
constructor(props) {
|
|
4108
5025
|
super(props);
|
|
4109
|
-
this.state = { failed: false };
|
|
5026
|
+
this.state = { failed: false, message: "" };
|
|
4110
5027
|
}
|
|
4111
|
-
static getDerivedStateFromError() {
|
|
4112
|
-
return { failed: true };
|
|
5028
|
+
static getDerivedStateFromError(error) {
|
|
5029
|
+
return { failed: true, message: error && typeof error.message === "string" ? error.message : String(error) };
|
|
5030
|
+
}
|
|
5031
|
+
componentDidCatch(error, info) {
|
|
5032
|
+
/* surface the real cause: in the console and inside the fallback panel */
|
|
5033
|
+
try {
|
|
5034
|
+
console.error("[dsh-conversation-overview] overview render failed:", error, info && info.componentStack);
|
|
5035
|
+
} catch {
|
|
5036
|
+
/* console is best-effort */
|
|
5037
|
+
}
|
|
4113
5038
|
}
|
|
4114
5039
|
render() {
|
|
4115
5040
|
if (this.state.failed) {
|
|
4116
|
-
return h(
|
|
5041
|
+
return h(
|
|
5042
|
+
"div",
|
|
5043
|
+
{ className: this.props.variant === "panel" ? "dsov-panel" : "dsov-view" },
|
|
5044
|
+
h("div", { className: "dsov-empty" }, translate("state.error")),
|
|
5045
|
+
this.state.message !== "" ? h("pre", { className: "dsov-renderErr" }, this.state.message) : null
|
|
5046
|
+
);
|
|
4117
5047
|
}
|
|
4118
5048
|
return this.props.children;
|
|
4119
5049
|
}
|
|
@@ -4635,6 +5565,9 @@ function FileTypeIcon(props) {
|
|
|
4635
5565
|
group(
|
|
4636
5566
|
t("settings.interaction"),
|
|
4637
5567
|
h(SettingSelect, { label: t("settings.timeMode"), value: cfg.timeMode, options: timeOptions, onPick: (id) => set("timeMode", id) }),
|
|
5568
|
+
note(t("settings.timeModeNote")),
|
|
5569
|
+
boolRow(t("settings.stepAnchors"), "stepAnchors"),
|
|
5570
|
+
note(t("settings.stepAnchorsNote")),
|
|
4638
5571
|
boolRow(t("settings.todoPromptRow"), "todoPrompt"),
|
|
4639
5572
|
cfg.todoPrompt
|
|
4640
5573
|
? h(SettingTextarea, {
|
|
@@ -4681,6 +5614,7 @@ function FileTypeIcon(props) {
|
|
|
4681
5614
|
),
|
|
4682
5615
|
group(
|
|
4683
5616
|
t("settings.feedbackGroup"),
|
|
5617
|
+
h(VersionRow, { t }),
|
|
4684
5618
|
h(
|
|
4685
5619
|
"a",
|
|
4686
5620
|
{ className: "dsov-set-link", href: FEEDBACK_URL, target: "_blank", rel: "noreferrer", title: FEEDBACK_URL },
|
|
@@ -4783,6 +5717,12 @@ function FileTypeIcon(props) {
|
|
|
4783
5717
|
.dsov-member[data-expandable=false]{cursor:default}
|
|
4784
5718
|
.dsov-member[data-expandable=false]:hover{background:transparent}
|
|
4785
5719
|
.dsov-stepChevron{flex:none;margin-right:-2px}
|
|
5720
|
+
.dsov-compactIcon{color:var(--dsw-alias-label-secondary)}
|
|
5721
|
+
.dsov-todoIconSlot{position:relative;flex:none;display:inline-flex;margin-top:2px}
|
|
5722
|
+
.dsov-todoIconSlot .dsov-todoIcon{margin-top:0}
|
|
5723
|
+
.dsov-todoChevron{position:absolute;inset:0;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .12s ease}
|
|
5724
|
+
.dsov-todo[data-expandable=true]:hover .dsov-todoChevron,.dsov-todo[data-expandable=true][data-open=true] .dsov-todoChevron{opacity:1;pointer-events:auto}
|
|
5725
|
+
.dsov-todo[data-expandable=true]:hover .dsov-todoIconSlot .dsov-todoIcon,.dsov-todo[data-expandable=true][data-open=true] .dsov-todoIconSlot .dsov-todoIcon{visibility:hidden}
|
|
4786
5726
|
.dsov-row{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:6px 8px;margin:0 -8px;border-radius:10px;border:none;background:transparent;cursor:pointer;font:inherit;color:inherit;box-sizing:content-box}
|
|
4787
5727
|
.dsov-row:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)}
|
|
4788
5728
|
.dsov-rowStatic{cursor:default}
|
|
@@ -4875,6 +5815,46 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4875
5815
|
.dsov-stats{display:flex;align-items:center;gap:5px;margin-right:auto;flex:none}
|
|
4876
5816
|
.dsov-stat{flex:none;font-size:10.5px;line-height:18px;padding:0 8px;border-radius:999px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
4877
5817
|
.dsov-stat[data-tone=err]{color:var(--dsw-alias-state-error-primary,#ff453a);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ff453a) 10%,transparent)}
|
|
5818
|
+
.dsov-statBtn{cursor:pointer;border:none;margin:0;font-family:inherit;transition:color .15s ease,background-color .15s ease}
|
|
5819
|
+
.dsov-statBtn:hover{color:var(--dsw-alias-label-primary)}
|
|
5820
|
+
.dsov-statBtn[data-active=true]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,transparent)}
|
|
5821
|
+
.dsov-statBtn[data-scrub=true]{color:var(--dsw-alias-brand-primary)}
|
|
5822
|
+
.dsov-timeline{display:flex;align-items:center;gap:8px;margin-top:6px}
|
|
5823
|
+
.dsov-timelineLabel{flex:none;font-size:10.5px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
5824
|
+
.dsov-prevTurn{color:var(--dsw-alias-brand-primary,#247bbf)}
|
|
5825
|
+
.dsov-timelineTrack{position:relative;flex:1 1 auto;min-width:0;display:flex;align-items:center}
|
|
5826
|
+
.dsov-timelineSlider{flex:1 1 auto;min-width:0;height:18px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary)}
|
|
5827
|
+
.dsov-timelineBubble{position:absolute;bottom:19px;left:0;transform:translateX(-50%);z-index:6;padding:1px 7px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);font-size:10.5px;line-height:16px;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .12s ease;font-variant-numeric:tabular-nums}
|
|
5828
|
+
.dsov-timelineTrack:hover .dsov-timelineBubble,.dsov-timelineSlider:focus-visible+.dsov-timelineBubble{opacity:1}
|
|
5829
|
+
.dsov-moreSteps[disabled]{opacity:.45;cursor:default;pointer-events:none}
|
|
5830
|
+
.dsov-newVer{display:inline-flex;align-items:center;flex:none;padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;text-decoration:none;white-space:nowrap;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 10%,transparent);border:.5px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 40%,transparent)}
|
|
5831
|
+
.dsov-newVer:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,transparent)}
|
|
5832
|
+
.dsov-set-ver{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:3px 0}
|
|
5833
|
+
.dsov-fileMenu{position:fixed;z-index:80;min-width:190px;max-width:260px;max-height:320px;overflow-y:auto;padding:4px;border-radius:9px;background:var(--dsw-alias-bg-layer-1);border:.5px solid var(--dsw-alias-border-l1);box-shadow:0 10px 30px rgba(0,0,0,.30)}
|
|
5834
|
+
.dsov-fileMenuItem{display:flex;align-items:center;gap:7px;width:100%;padding:5px 8px;border:none;margin:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:12px;line-height:18px;text-align:left;cursor:pointer}
|
|
5835
|
+
.dsov-fileMenuItem:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
5836
|
+
.dsov-fileMenuIcon{flex:none;width:16px;height:16px;border-radius:4px;object-fit:contain;background:var(--dsw-alias-bg-layer-2)}
|
|
5837
|
+
.dsov-fileMenuName{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
5838
|
+
.dsov-fileMenuTag{flex:none;font-size:10px;line-height:14px;padding:0 5px;border-radius:999px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}
|
|
5839
|
+
.dsov-fileMenuHead{padding:5px 8px 3px;font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
5840
|
+
.dsov-fileMenuSep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1)}
|
|
5841
|
+
.dsov-fileMenuHint{padding:5px 8px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
|
|
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}
|
|
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}
|
|
4878
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}
|
|
4879
5859
|
.dsov-turnSelect:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,var(--dsw-alias-border-l2))}
|
|
4880
5860
|
@keyframes dsov-spin{to{transform:rotate(360deg)}}
|
|
@@ -5226,6 +6206,24 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
5226
6206
|
};
|
|
5227
6207
|
}, "conversation-overview: quick nav link");
|
|
5228
6208
|
|
|
6209
|
+
ctx.effect(
|
|
6210
|
+
() =>
|
|
6211
|
+
ctx.slots.inject(
|
|
6212
|
+
"plugins.detail.badge",
|
|
6213
|
+
() =>
|
|
6214
|
+
ctx.slots.register(
|
|
6215
|
+
{
|
|
6216
|
+
name: "plugins.detail.badge",
|
|
6217
|
+
id: "dsov-new-version",
|
|
6218
|
+
order: 100,
|
|
6219
|
+
locale: NS,
|
|
6220
|
+
},
|
|
6221
|
+
NewVersionBadge
|
|
6222
|
+
)
|
|
6223
|
+
),
|
|
6224
|
+
"conversation-overview: new version badge"
|
|
6225
|
+
);
|
|
6226
|
+
|
|
5229
6227
|
ctx.effect(
|
|
5230
6228
|
() =>
|
|
5231
6229
|
ctx.slots.inject(
|
|
@@ -5271,6 +6269,10 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
5271
6269
|
foldEvent,
|
|
5272
6270
|
foldEntry,
|
|
5273
6271
|
buildState,
|
|
6272
|
+
planTodoAttachments,
|
|
6273
|
+
vikingLabel,
|
|
6274
|
+
vikingRefKind,
|
|
6275
|
+
vikingIsDir,
|
|
5274
6276
|
normalizeSelection,
|
|
5275
6277
|
vikingParentUri,
|
|
5276
6278
|
rememberContextPreview,
|
|
@@ -5291,6 +6293,15 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
5291
6293
|
setFormConfig,
|
|
5292
6294
|
setOverlayConfig,
|
|
5293
6295
|
clearOverlayConfig,
|
|
6296
|
+
compareVersions,
|
|
6297
|
+
ProgressSection,
|
|
6298
|
+
ArtifactsSection,
|
|
6299
|
+
ReferencesSection,
|
|
6300
|
+
StatsBar,
|
|
6301
|
+
ArtifactFileMenu,
|
|
6302
|
+
OverviewGlyph,
|
|
6303
|
+
OverviewContent,
|
|
6304
|
+
OverviewBoundary,
|
|
5294
6305
|
SettingsForm,
|
|
5295
6306
|
SettingsSection,
|
|
5296
6307
|
vikingName,
|