dsh-conversation-overview 1.0.3 → 1.2.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 +20 -6
- package/client.js +1635 -136
- package/cordis.patch.yml +5 -0
- package/index.js +6 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +3 -2
package/client.js
CHANGED
|
@@ -18,8 +18,155 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
const OVERLAY_KEY = "dsh.conversationOverview.config";
|
|
19
19
|
const ROW_ID = "conversation-overview";
|
|
20
20
|
const ROW_KEY = "dsh-conversation-overview#conversation-overview";
|
|
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
|
+
}
|
|
21
167
|
const PLACEMENTS = ["conversation", "sidebar", "both"];
|
|
22
|
-
const
|
|
168
|
+
const TIME_MODES = ["clock", "elapsed"];
|
|
169
|
+
const CONFIG_FIELDS = ["showProgress", "showArtifacts", "showReferences", "showStats", "timeMode", "stepAnchors", "placement", "sectionFullHeight", "todoPrompt", "todoPromptTemplate", "quickNav", "quickNavLink", "vikingPreview", "vikingUrl", "vikingAccount", "vikingUser"];
|
|
23
170
|
const VIKING_TAB_ID = "dsh-conversation-overview/viking";
|
|
24
171
|
const VIKING_TAB_KIND = "viking-memory";
|
|
25
172
|
const VIKING_ADDRESS_PREFIX = "dsh-resource://viking-memory/";
|
|
@@ -38,7 +185,7 @@ window.__ModuleLoader__.load({
|
|
|
38
185
|
const zh = {
|
|
39
186
|
"view.tab": "概览",
|
|
40
187
|
"panel.title": "概览",
|
|
41
|
-
"guide.title": "
|
|
188
|
+
"guide.title": "会话概览",
|
|
42
189
|
"guide.description": "进展、产物与引用",
|
|
43
190
|
"section.progress": "进展",
|
|
44
191
|
"section.artifacts": "产物",
|
|
@@ -46,7 +193,20 @@ window.__ModuleLoader__.load({
|
|
|
46
193
|
"progress.empty": "生成任务/待办后将在此展示进展",
|
|
47
194
|
"progress.emptyTurn": "本轮未创建任务计划",
|
|
48
195
|
"progress.recentSteps": "本轮关键步骤(最近 6 条)",
|
|
49
|
-
"
|
|
196
|
+
"step.compact": "上下文压缩",
|
|
197
|
+
"step.approval": "用户审批",
|
|
198
|
+
"progress.allSteps": "本轮关键步骤(全部)",
|
|
199
|
+
"progress.showEarlierSteps": "显示更早的步骤",
|
|
200
|
+
"progress.moreStepsCount": "还有 {n} 条",
|
|
201
|
+
"progress.hideEarlierSteps": "收起早期步骤",
|
|
202
|
+
"team.phase.provisioning": "创建中",
|
|
203
|
+
"team.phase.active": "运行中",
|
|
204
|
+
"team.phase.inactive": "空闲",
|
|
205
|
+
"team.phase.failed": "失败",
|
|
206
|
+
"team.blocked": "阻塞 {n}",
|
|
207
|
+
"progress.expandTeam": "展开团队详情",
|
|
208
|
+
"progress.collapseTeam": "收起团队详情",
|
|
209
|
+
"progress.todoHint": "双击将该步骤的重执行提示词填入对话框",
|
|
50
210
|
"progress.todoInserted": "已填入对话框",
|
|
51
211
|
"progress.todoCopied": "未找到对话框,提示词已复制到剪贴板",
|
|
52
212
|
"progress.todoFailed": "填入失败,请重试",
|
|
@@ -56,11 +216,23 @@ window.__ModuleLoader__.load({
|
|
|
56
216
|
"artifacts.viewAll": "查看所有改动",
|
|
57
217
|
"settings.summary": "控制概览的板块与显示位置",
|
|
58
218
|
"settings.nav": "概览",
|
|
59
|
-
"settings.pageTitle": "
|
|
219
|
+
"settings.pageTitle": "会话概览(dsh-conversation-overview)",
|
|
60
220
|
"settings.sections": "板块显示",
|
|
61
221
|
"settings.showProgress": "进展(任务与待办)",
|
|
62
222
|
"settings.showArtifacts": "产物(本次对话产出)",
|
|
63
223
|
"settings.showReferences": "引用(上下文、记忆等)",
|
|
224
|
+
"settings.showStats": "统计(用时、执行次数等)",
|
|
225
|
+
"settings.timeMode": "时间显示",
|
|
226
|
+
"settings.stepAnchors": "里程碑步骤置顶",
|
|
227
|
+
"settings.stepAnchorsNote": "开启后,上下文压缩、用户审批与失败的工具调用固定在关键步骤顶部,不被最近窗口挤出;默认关闭,随窗口滚动。",
|
|
228
|
+
"settings.timeMode.clock": "时间点",
|
|
229
|
+
"settings.timeMode.elapsed": "本轮计时",
|
|
230
|
+
"settings.timeModeNote": "「本轮计时」以本轮任务开始为 0s 起点,显示相对用时;悬停时间可查看对应的具体时间点。",
|
|
231
|
+
"settings.currentVersion": "当前版本",
|
|
232
|
+
"settings.latestBadge": "新版本",
|
|
233
|
+
"settings.latestTitle": "在 GitHub 查看最新版本",
|
|
234
|
+
"settings.feedbackGroup": "反馈",
|
|
235
|
+
"settings.feedback": "问题反馈(GitHub Issues)",
|
|
64
236
|
"settings.fullHeightRow": "分区完整展示(不限高)",
|
|
65
237
|
"settings.fullHeightNote": "关闭时进展/产物/引用各自限制最大高度,超出部分在分区内滚动。",
|
|
66
238
|
"settings.layout": "布局",
|
|
@@ -78,6 +250,11 @@ window.__ModuleLoader__.load({
|
|
|
78
250
|
"settings.quickNavLinkRow": "导航联动概览",
|
|
79
251
|
"settings.quickNavNote": "对话区右侧的轮次刻度条:悬停预览该轮问答,点击跳转;开启后窄窗口下也保持显示,刻度悬停可见轮次号。",
|
|
80
252
|
"settings.quickNavLinkNote": "点击导航刻度跳转对话时,已打开的概览同步切换到对应轮次。",
|
|
253
|
+
"settings.interaction": "交互",
|
|
254
|
+
"settings.todoPromptRow": "双击待办生成提示词",
|
|
255
|
+
"settings.todoPromptTemplate": "提示词模板",
|
|
256
|
+
"settings.todoPromptNote": "双击进展中的待办行,把重执行提示词填入输入框。",
|
|
257
|
+
"settings.todoPromptTemplateNote": "模板留空则使用内置提示词;支持占位符 {index}(步骤序号)与 {content}(步骤内容)。",
|
|
81
258
|
"settings.placementRow": "概览显示位置",
|
|
82
259
|
"settings.viking": "OpenViking 记忆预览",
|
|
83
260
|
"settings.vikingEnable": "启用记忆预览",
|
|
@@ -95,12 +272,12 @@ window.__ModuleLoader__.load({
|
|
|
95
272
|
"viking.dirEmpty": "此目录为空",
|
|
96
273
|
"viking.field.scenarios": "使用场景",
|
|
97
274
|
"viking.field.keywords": "关键词",
|
|
275
|
+
"viking.field.source": "来源",
|
|
98
276
|
"reference.sort.time": "时间",
|
|
99
|
-
"reference.sort.kind": "类型",
|
|
100
277
|
"reference.sort.count": "次数",
|
|
101
278
|
"reference.sort.time.title": "按最近引用排序",
|
|
102
|
-
"reference.sort.kind.title": "按类型分组排序(记忆、文件、技能、会话、上下文)",
|
|
103
279
|
"reference.sort.count.title": "按引用次数排序",
|
|
280
|
+
"reference.sort.cancel": "点击取消排序",
|
|
104
281
|
"reference.previewHint": "点击预览注入的内容快照",
|
|
105
282
|
"scope.label": "概览范围",
|
|
106
283
|
"scope.latest": "最新一轮",
|
|
@@ -122,6 +299,12 @@ window.__ModuleLoader__.load({
|
|
|
122
299
|
"reference.hideCommon": "过滤常见",
|
|
123
300
|
"stats.duration": "用时 {v}",
|
|
124
301
|
"stats.duration.title": "本轮用时(首末事件间隔)",
|
|
302
|
+
"stats.duration.timeline": "点击打开时间轴,按时间点回看进展 / 产物 / 引用",
|
|
303
|
+
"timeline.live": "最新",
|
|
304
|
+
"timeline.reset": "回到最新",
|
|
305
|
+
"timeline.prevTurn": "上一轮",
|
|
306
|
+
"timeline.prevTurnTitle": "切换到第 {n} 轮,时间轴保持打开",
|
|
307
|
+
"timeline.close": "关闭时间轴",
|
|
125
308
|
"stats.tools": "工具 {n}",
|
|
126
309
|
"stats.tools.title": "工具调用次数",
|
|
127
310
|
"stats.commands": "命令 {n}",
|
|
@@ -143,6 +326,15 @@ window.__ModuleLoader__.load({
|
|
|
143
326
|
"goal.blocked": "受阻",
|
|
144
327
|
"goal.complete": "已完成",
|
|
145
328
|
"artifact.presented": "交付",
|
|
329
|
+
"artifact.memory": "记忆",
|
|
330
|
+
"artifact.menu.reveal": "在上层文件夹中显示",
|
|
331
|
+
"artifact.menu.openWith": "打开方式",
|
|
332
|
+
"artifact.menu.loading": "正在读取可用应用…",
|
|
333
|
+
"artifact.menu.unavailable": "当前部署不支持系统打开",
|
|
334
|
+
"artifact.menu.noApps": "未找到关联应用",
|
|
335
|
+
"artifact.menu.default": "默认",
|
|
336
|
+
"artifact.menu.revealError": "无法在上层文件夹中显示,请重试",
|
|
337
|
+
"artifact.menu.openError": "无法用该应用打开,请重试",
|
|
146
338
|
"artifact.produced": "生成",
|
|
147
339
|
"artifact.binary": "二进制",
|
|
148
340
|
"reference.kind.context": "上下文",
|
|
@@ -170,7 +362,7 @@ window.__ModuleLoader__.load({
|
|
|
170
362
|
const en = {
|
|
171
363
|
"view.tab": "Overview",
|
|
172
364
|
"panel.title": "Overview",
|
|
173
|
-
"guide.title": "Overview",
|
|
365
|
+
"guide.title": "Conversation Overview",
|
|
174
366
|
"guide.description": "Progress, artifacts & references",
|
|
175
367
|
"section.progress": "Progress",
|
|
176
368
|
"section.artifacts": "Artifacts",
|
|
@@ -178,21 +370,46 @@ window.__ModuleLoader__.load({
|
|
|
178
370
|
"progress.empty": "Progress appears here once tasks or todos are generated",
|
|
179
371
|
"progress.emptyTurn": "No task plan was created in this turn",
|
|
180
372
|
"progress.recentSteps": "Key steps in this turn (latest 6)",
|
|
181
|
-
"
|
|
373
|
+
"step.compact": "Context compaction",
|
|
374
|
+
"step.approval": "User approval",
|
|
375
|
+
"progress.allSteps": "Key steps in this turn (all)",
|
|
376
|
+
"progress.showEarlierSteps": "Show earlier steps",
|
|
377
|
+
"progress.moreStepsCount": "{n} more steps",
|
|
378
|
+
"progress.hideEarlierSteps": "Hide earlier steps",
|
|
379
|
+
"progress.expandTeam": "Expand team details",
|
|
380
|
+
"progress.collapseTeam": "Collapse team details",
|
|
381
|
+
"progress.todoHint": "Double-click to fill the composer with a re-run prompt for this step",
|
|
182
382
|
"progress.todoInserted": "Added to the composer",
|
|
183
383
|
"progress.todoCopied": "Composer not found — prompt copied to the clipboard",
|
|
184
384
|
"progress.todoFailed": "Could not fill the composer, try again",
|
|
185
385
|
"progress.waiting": "Waiting for confirmation",
|
|
386
|
+
"team.phase.provisioning": "Provisioning",
|
|
387
|
+
"team.phase.active": "Active",
|
|
388
|
+
"team.phase.inactive": "Inactive",
|
|
389
|
+
"team.phase.failed": "Failed",
|
|
390
|
+
"team.blocked": "Blocked {n}",
|
|
186
391
|
"progress.counts": "{completed} of {total} completed",
|
|
187
392
|
"artifacts.empty": "Files produced in this conversation appear here",
|
|
188
393
|
"artifacts.viewAll": "Review all changes",
|
|
189
394
|
"settings.summary": "Control the overview sections and where they appear",
|
|
190
395
|
"settings.nav": "Overview",
|
|
191
|
-
"settings.pageTitle": "Conversation Overview",
|
|
396
|
+
"settings.pageTitle": "Conversation Overview (dsh-conversation-overview)",
|
|
192
397
|
"settings.sections": "Sections",
|
|
193
398
|
"settings.showProgress": "Progress (tasks and todos)",
|
|
194
399
|
"settings.showArtifacts": "Artifacts (files produced)",
|
|
195
400
|
"settings.showReferences": "References (context, memories)",
|
|
401
|
+
"settings.showStats": "Stats (duration, tool calls, …)",
|
|
402
|
+
"settings.timeMode": "Time display",
|
|
403
|
+
"settings.stepAnchors": "Pin milestone steps",
|
|
404
|
+
"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.",
|
|
405
|
+
"settings.timeMode.clock": "Clock time",
|
|
406
|
+
"settings.timeMode.elapsed": "Turn elapsed",
|
|
407
|
+
"settings.timeModeNote": "'Turn elapsed' counts from 0s at the start of the turn; hover a time to see its clock value.",
|
|
408
|
+
"settings.currentVersion": "Current version",
|
|
409
|
+
"settings.latestBadge": "New version",
|
|
410
|
+
"settings.latestTitle": "View the latest release on GitHub",
|
|
411
|
+
"settings.feedbackGroup": "Feedback",
|
|
412
|
+
"settings.feedback": "Report an issue (GitHub Issues)",
|
|
196
413
|
"settings.fullHeightRow": "Show sections in full (no max height)",
|
|
197
414
|
"settings.fullHeightNote": "When off, Progress/Artifacts/References cap their height and scroll inside each section.",
|
|
198
415
|
"settings.layout": "Layout",
|
|
@@ -210,6 +427,11 @@ window.__ModuleLoader__.load({
|
|
|
210
427
|
"settings.quickNavLinkRow": "Link rail with overview",
|
|
211
428
|
"settings.quickNavNote": "The turn rail on the right edge of the chat: hover for a turn preview, click to jump. When enabled it stays visible in narrow windows too, and ticks show their turn number on hover.",
|
|
212
429
|
"settings.quickNavLinkNote": "When you click a rail tick to jump, an open overview switches to that turn as well.",
|
|
430
|
+
"settings.interaction": "Interaction",
|
|
431
|
+
"settings.todoPromptRow": "Double-click todo prompt",
|
|
432
|
+
"settings.todoPromptTemplate": "Prompt template",
|
|
433
|
+
"settings.todoPromptNote": "Double-click a todo row in Progress to fill the rerun prompt into the composer.",
|
|
434
|
+
"settings.todoPromptTemplateNote": "Leave empty for the built-in prompt; placeholders: {index} (step number), {content} (step text).",
|
|
213
435
|
"settings.placementRow": "Show the overview in",
|
|
214
436
|
"settings.viking": "OpenViking memory preview",
|
|
215
437
|
"settings.vikingEnable": "Enable memory preview",
|
|
@@ -227,12 +449,12 @@ window.__ModuleLoader__.load({
|
|
|
227
449
|
"viking.dirEmpty": "This directory is empty",
|
|
228
450
|
"viking.field.scenarios": "Usage scenarios",
|
|
229
451
|
"viking.field.keywords": "Keywords",
|
|
452
|
+
"viking.field.source": "Source",
|
|
230
453
|
"reference.sort.time": "Time",
|
|
231
|
-
"reference.sort.kind": "Kind",
|
|
232
454
|
"reference.sort.count": "Count",
|
|
233
455
|
"reference.sort.time.title": "Sort by most recently referenced",
|
|
234
|
-
"reference.sort.kind.title": "Group by kind (memories, files, skills, sessions, context)",
|
|
235
456
|
"reference.sort.count.title": "Sort by reference count",
|
|
457
|
+
"reference.sort.cancel": "Click again to clear",
|
|
236
458
|
"reference.previewHint": "Click to preview the injected content snapshot",
|
|
237
459
|
"scope.label": "Scope",
|
|
238
460
|
"scope.latest": "Latest turn",
|
|
@@ -254,6 +476,12 @@ window.__ModuleLoader__.load({
|
|
|
254
476
|
"reference.hideCommon": "Filter common",
|
|
255
477
|
"stats.duration": "{v} elapsed",
|
|
256
478
|
"stats.duration.title": "Turn duration (first to last event)",
|
|
479
|
+
"stats.duration.timeline": "Open the timeline to review progress, artifacts, and references at any moment",
|
|
480
|
+
"timeline.live": "Live",
|
|
481
|
+
"timeline.reset": "Back to live",
|
|
482
|
+
"timeline.prevTurn": "Previous turn",
|
|
483
|
+
"timeline.prevTurnTitle": "Switch to turn {n} and keep the timeline open",
|
|
484
|
+
"timeline.close": "Close timeline",
|
|
257
485
|
"stats.tools": "{n} tools",
|
|
258
486
|
"stats.tools.title": "Tool calls",
|
|
259
487
|
"stats.commands": "{n} cmds",
|
|
@@ -275,6 +503,15 @@ window.__ModuleLoader__.load({
|
|
|
275
503
|
"goal.blocked": "Blocked",
|
|
276
504
|
"goal.complete": "Completed",
|
|
277
505
|
"artifact.presented": "Delivered",
|
|
506
|
+
"artifact.memory": "Memory",
|
|
507
|
+
"artifact.menu.reveal": "Show in Enclosing Folder",
|
|
508
|
+
"artifact.menu.openWith": "Open With",
|
|
509
|
+
"artifact.menu.loading": "Loading applications…",
|
|
510
|
+
"artifact.menu.unavailable": "System open is unavailable in this deployment",
|
|
511
|
+
"artifact.menu.noApps": "No associated applications found",
|
|
512
|
+
"artifact.menu.default": "Default",
|
|
513
|
+
"artifact.menu.revealError": "Could not reveal the file; try again",
|
|
514
|
+
"artifact.menu.openError": "Could not open with this application; try again",
|
|
278
515
|
"artifact.produced": "Produced",
|
|
279
516
|
"artifact.binary": "binary",
|
|
280
517
|
"reference.kind.context": "Context",
|
|
@@ -322,8 +559,13 @@ window.__ModuleLoader__.load({
|
|
|
322
559
|
showProgress: flag(source.showProgress),
|
|
323
560
|
showArtifacts: flag(source.showArtifacts),
|
|
324
561
|
showReferences: flag(source.showReferences),
|
|
562
|
+
showStats: flag(source.showStats),
|
|
563
|
+
timeMode: TIME_MODES.indexOf(source.timeMode) !== -1 ? source.timeMode : "clock",
|
|
564
|
+
stepAnchors: source.stepAnchors === true,
|
|
325
565
|
placement: PLACEMENTS.indexOf(source.placement) !== -1 ? source.placement : "both",
|
|
326
566
|
sectionFullHeight: source.sectionFullHeight === true,
|
|
567
|
+
todoPrompt: typeof source.todoPrompt === "boolean" ? source.todoPrompt : true,
|
|
568
|
+
todoPromptTemplate: typeof source.todoPromptTemplate === "string" ? source.todoPromptTemplate : "",
|
|
327
569
|
quickNav: typeof source.quickNav === "boolean" ? source.quickNav : true,
|
|
328
570
|
quickNavLink: typeof source.quickNavLink === "boolean" ? source.quickNavLink : true,
|
|
329
571
|
vikingPreview: typeof source.vikingPreview === "boolean" ? source.vikingPreview : true,
|
|
@@ -452,8 +694,18 @@ window.__ModuleLoader__.load({
|
|
|
452
694
|
return String(value).padStart(2, "0");
|
|
453
695
|
}
|
|
454
696
|
|
|
455
|
-
|
|
697
|
+
/** Compact "since turn start" reading: 0s → 59s → 3m05s → 1h02m. */
|
|
698
|
+
function formatElapsed(ms) {
|
|
699
|
+
const sec = Math.max(0, Math.floor(ms / 1000));
|
|
700
|
+
if (sec < 60) return `${sec}s`;
|
|
701
|
+
const min = Math.floor(sec / 60);
|
|
702
|
+
if (min < 60) return `${min}m${pad2(sec % 60)}s`;
|
|
703
|
+
return `${Math.floor(min / 60)}h${pad2(min % 60)}m`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function formatTime(time, mode, base) {
|
|
456
707
|
if (typeof time !== "number" || !Number.isFinite(time) || time <= 0) return "";
|
|
708
|
+
if (mode === "elapsed" && typeof base === "number" && base > 0 && time >= base) return formatElapsed(time - base);
|
|
457
709
|
const date = new Date(time);
|
|
458
710
|
const now = new Date();
|
|
459
711
|
const hm = `${pad2(date.getHours())}:${pad2(date.getMinutes())}`;
|
|
@@ -584,6 +836,125 @@ window.__ModuleLoader__.load({
|
|
|
584
836
|
}
|
|
585
837
|
}
|
|
586
838
|
|
|
839
|
+
/* ------------------------------------------------------------------ */
|
|
840
|
+
/* Host desktop file actions (reveal / open with) via the session remote */
|
|
841
|
+
/* ------------------------------------------------------------------ */
|
|
842
|
+
|
|
843
|
+
const desktopOpenState = { value: null, inflight: null };
|
|
844
|
+
|
|
845
|
+
function hostSessionRemote() {
|
|
846
|
+
const ctx = runtime && runtime.ctx;
|
|
847
|
+
if (!ctx) return null;
|
|
848
|
+
try {
|
|
849
|
+
/* the cordis context proxy THROWS for services this entry has not
|
|
850
|
+
injected; degrade to "no desktop actions" instead of crashing render */
|
|
851
|
+
const remote = ctx.remote;
|
|
852
|
+
return isRecord(remote) && isRecord(remote.session) ? remote.session : null;
|
|
853
|
+
} catch {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function desktopCanOpen() {
|
|
859
|
+
const remote = hostSessionRemote();
|
|
860
|
+
if (remote === null || typeof remote.canOpenWorkspacePath !== "function") return Promise.resolve(false);
|
|
861
|
+
if (desktopOpenState.value !== null) return Promise.resolve(desktopOpenState.value);
|
|
862
|
+
if (desktopOpenState.inflight !== null) return desktopOpenState.inflight;
|
|
863
|
+
desktopOpenState.inflight = Promise.resolve()
|
|
864
|
+
.then(() => remote.canOpenWorkspacePath())
|
|
865
|
+
.then((result) => {
|
|
866
|
+
desktopOpenState.value = isRecord(result) && result.ok === true && result.value === true;
|
|
867
|
+
desktopOpenState.inflight = null;
|
|
868
|
+
return desktopOpenState.value;
|
|
869
|
+
})
|
|
870
|
+
.catch(() => {
|
|
871
|
+
desktopOpenState.inflight = null;
|
|
872
|
+
return false;
|
|
873
|
+
});
|
|
874
|
+
return desktopOpenState.inflight;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function desktopOpenPath(path, action, application) {
|
|
878
|
+
const remote = hostSessionRemote();
|
|
879
|
+
if (remote === null || typeof remote.openWorkspacePath !== "function" || typeof path !== "string" || path === "") return Promise.resolve(false);
|
|
880
|
+
const request = action === "reveal" ? { path, action } : application === undefined ? { path } : { path, application };
|
|
881
|
+
return Promise.resolve()
|
|
882
|
+
.then(() => remote.openWorkspacePath(request))
|
|
883
|
+
.then((result) => isRecord(result) && result.ok === true)
|
|
884
|
+
.catch(() => false);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function desktopApplications(path, signal) {
|
|
888
|
+
const remote = hostSessionRemote();
|
|
889
|
+
if (remote === null || typeof remote.workspacePathApplications !== "function") return Promise.resolve(null);
|
|
890
|
+
return Promise.resolve()
|
|
891
|
+
.then(() => remote.workspacePathApplications({ path }, signal))
|
|
892
|
+
.then((result) => (isRecord(result) && result.ok === true && Array.isArray(result.value) ? result.value : null))
|
|
893
|
+
.catch(() => null);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* Right-click menu on an artifact row: reveal in the enclosing folder, or pick
|
|
897
|
+
one of the OS-registered applications to open the file with. */
|
|
898
|
+
function ArtifactFileMenu({ menu, t, onClose }) {
|
|
899
|
+
const [phase, setPhase] = React.useState("loading"); /* loading | ready | unavailable */
|
|
900
|
+
const [apps, setApps] = React.useState([]);
|
|
901
|
+
const [failed, setFailed] = React.useState(null);
|
|
902
|
+
const pathRef = React.useRef(menu.path);
|
|
903
|
+
React.useEffect(() => {
|
|
904
|
+
let cancelled = false;
|
|
905
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
906
|
+
desktopCanOpen().then((can) => {
|
|
907
|
+
if (cancelled) return undefined;
|
|
908
|
+
if (!can) {
|
|
909
|
+
setPhase("unavailable");
|
|
910
|
+
return undefined;
|
|
911
|
+
}
|
|
912
|
+
return desktopApplications(pathRef.current, controller !== null ? controller.signal : undefined).then((list) => {
|
|
913
|
+
if (cancelled || (controller !== null && controller.signal.aborted)) return;
|
|
914
|
+
setApps(list === null ? [] : list.filter((app) => isRecord(app) && typeof app.id === "string" && app.id !== "" && typeof app.name === "string"));
|
|
915
|
+
setPhase("ready");
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
return () => {
|
|
919
|
+
cancelled = true;
|
|
920
|
+
if (controller !== null) controller.abort();
|
|
921
|
+
};
|
|
922
|
+
}, []);
|
|
923
|
+
const runAction = (action, application) => {
|
|
924
|
+
desktopOpenPath(menu.path, action, application).then((ok) => {
|
|
925
|
+
if (ok) onClose();
|
|
926
|
+
else setFailed(action === "reveal" ? t("artifact.menu.revealError") : t("artifact.menu.openError"));
|
|
927
|
+
});
|
|
928
|
+
};
|
|
929
|
+
const clamp = (value, max) => Math.max(8, Math.min(value, max));
|
|
930
|
+
const style = {
|
|
931
|
+
left: `${clamp(menu.x, (typeof window !== "undefined" ? window.innerWidth : 1200) - 250)}px`,
|
|
932
|
+
top: `${clamp(menu.y, (typeof window !== "undefined" ? window.innerHeight : 800) - 320)}px`,
|
|
933
|
+
};
|
|
934
|
+
return h(
|
|
935
|
+
"div",
|
|
936
|
+
{ className: "dsov-fileMenu", style, onMouseDown: (event) => event.stopPropagation(), onContextMenu: (event) => event.preventDefault() },
|
|
937
|
+
h("button", { type: "button", className: "dsov-fileMenuItem", onClick: () => runAction("reveal") }, t("artifact.menu.reveal")),
|
|
938
|
+
h("div", { className: "dsov-fileMenuSep" }),
|
|
939
|
+
h("div", { className: "dsov-fileMenuHead" }, t("artifact.menu.openWith")),
|
|
940
|
+
phase === "loading" ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.loading")) : null,
|
|
941
|
+
phase === "unavailable" ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.unavailable")) : null,
|
|
942
|
+
phase === "ready" && apps.length === 0 ? h("div", { className: "dsov-fileMenuHint" }, t("artifact.menu.noApps")) : null,
|
|
943
|
+
phase === "ready"
|
|
944
|
+
? apps.map((app) =>
|
|
945
|
+
h(
|
|
946
|
+
"button",
|
|
947
|
+
{ type: "button", className: "dsov-fileMenuItem", key: app.id, onClick: () => runAction("open", app.id) },
|
|
948
|
+
typeof app.icon === "string" && app.icon !== "" ? h("img", { className: "dsov-fileMenuIcon", src: app.icon, alt: "" }) : h("span", { className: "dsov-fileMenuIcon" }),
|
|
949
|
+
h("span", { className: "dsov-fileMenuName" }, app.name),
|
|
950
|
+
app.default === true ? h("span", { className: "dsov-fileMenuTag" }, t("artifact.menu.default")) : null
|
|
951
|
+
)
|
|
952
|
+
)
|
|
953
|
+
: null,
|
|
954
|
+
failed !== null ? h("div", { className: "dsov-fileMenuErr" }, failed) : null
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
587
958
|
function openOverviewTab() {
|
|
588
959
|
const ctx = runtime && runtime.ctx;
|
|
589
960
|
const sidebar = ctx && ctx.sidebarRight;
|
|
@@ -683,6 +1054,32 @@ window.__ModuleLoader__.load({
|
|
|
683
1054
|
return name === "" ? String(uri) : name;
|
|
684
1055
|
}
|
|
685
1056
|
|
|
1057
|
+
/** SKILL.md refs read as their skill folder name so several skills stay distinguishable. */
|
|
1058
|
+
function vikingSkillName(uri) {
|
|
1059
|
+
const match = /\/skills\/([^/]+)\/SKILL\.md$/i.exec(String(uri));
|
|
1060
|
+
return match !== null ? match[1] : null;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function vikingLabel(uri) {
|
|
1064
|
+
const skill = vikingSkillName(uri);
|
|
1065
|
+
return skill !== null ? skill : vikingName(uri);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** Ref kind: anything under a skills/ root reads as SKILL, the rest stays a memory file. */
|
|
1069
|
+
function vikingRefKind(uri) {
|
|
1070
|
+
const value = String(uri);
|
|
1071
|
+
return /\/skills\/[^/]+(\/SKILL\.md)?\/?$/i.test(value) ? "skill" : "memory-file";
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/** Folder URIs end with "/" or their last segment carries no file extension. */
|
|
1075
|
+
function vikingIsDir(uri) {
|
|
1076
|
+
const value = String(uri);
|
|
1077
|
+
if (value.endsWith("/")) return true;
|
|
1078
|
+
const parts = value.split("/").filter((part) => part !== "");
|
|
1079
|
+
const last = parts.length > 0 ? parts[parts.length - 1] : "";
|
|
1080
|
+
return last !== "" && last.indexOf(".") === -1;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
686
1083
|
function vikingAddress(uri) {
|
|
687
1084
|
return `${VIKING_ADDRESS_PREFIX}${encodeURIComponent(uri)}`;
|
|
688
1085
|
}
|
|
@@ -944,6 +1341,10 @@ window.__ModuleLoader__.load({
|
|
|
944
1341
|
}
|
|
945
1342
|
|
|
946
1343
|
function openArtifact(binding, artifact, stats) {
|
|
1344
|
+
if (typeof artifact.path === "string" && artifact.path.startsWith("viking://")) {
|
|
1345
|
+
openVikingPreview(artifact.path);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
947
1348
|
if (!binding) return;
|
|
948
1349
|
const stat = stats ? stats.get(absPath(sessionCwd(binding.sessionId), artifact.path)) : undefined;
|
|
949
1350
|
if (stat && stat.coords && openChangesReview(binding.sessionId, stat.coords, stat.index)) return;
|
|
@@ -1033,6 +1434,8 @@ window.__ModuleLoader__.load({
|
|
|
1033
1434
|
}
|
|
1034
1435
|
|
|
1035
1436
|
const MEMORY_REFERENCE_TOOLS = new Set(["search", "find", "read", "glob", "grep", "list", "tree"]);
|
|
1437
|
+
/* viking write tools produce durable memory files: they fold into the artifacts section */
|
|
1438
|
+
const MEMORY_MUTATION_TOOLS = new Set(["write", "edit", "add_resource", "add_skill"]);
|
|
1036
1439
|
|
|
1037
1440
|
function refKindForSource(source, kind) {
|
|
1038
1441
|
if (kind.startsWith("plugin:openviking") || kind.startsWith("plugin:ov")) return "memory";
|
|
@@ -1106,18 +1509,28 @@ window.__ModuleLoader__.load({
|
|
|
1106
1509
|
pending: new Map(),
|
|
1107
1510
|
changes: [],
|
|
1108
1511
|
steps: [],
|
|
1512
|
+
anchors: [],
|
|
1109
1513
|
stepByCall: new Map(),
|
|
1110
1514
|
byTurn: new Map(),
|
|
1515
|
+
members: new Map(),
|
|
1516
|
+
teamTasks: new Map(),
|
|
1517
|
+
subagents: new Map(),
|
|
1111
1518
|
stats: createStats(),
|
|
1112
1519
|
changed: false,
|
|
1113
1520
|
};
|
|
1114
1521
|
}
|
|
1115
1522
|
|
|
1523
|
+
/** Upsert one team/member, team/task or subagent/catalog record; first observation time is kept. */
|
|
1524
|
+
function upsertTeamRecord(map, key, record) {
|
|
1525
|
+
const existing = map.get(key);
|
|
1526
|
+
map.set(key, existing === undefined ? { ...record, firstTime: record.time } : { ...existing, ...record, firstTime: existing.firstTime });
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1116
1529
|
function createTurnBucket() {
|
|
1117
|
-
return { artifacts: new Map(), refs: new Map(), refOrder: [], changes: [], steps: [], stats: createStats() };
|
|
1530
|
+
return { artifacts: new Map(), refs: new Map(), refOrder: [], changes: [], steps: [], anchors: [], members: new Map(), teamTasks: new Map(), subagents: new Map(), stats: createStats() };
|
|
1118
1531
|
}
|
|
1119
1532
|
|
|
1120
|
-
const EMPTY_BUCKET = Object.freeze({ artifacts: new Map(), refs: new Map(), refOrder: [], changes: [], steps: [], stats: Object.freeze(createStats()) });
|
|
1533
|
+
const EMPTY_BUCKET = Object.freeze({ artifacts: new Map(), refs: new Map(), refOrder: [], changes: [], steps: [], anchors: [], members: new Map(), teamTasks: new Map(), subagents: new Map(), stats: Object.freeze(createStats()) });
|
|
1121
1534
|
|
|
1122
1535
|
function turnBucket(folder, turn) {
|
|
1123
1536
|
let bucket = folder.byTurn.get(turn);
|
|
@@ -1182,23 +1595,27 @@ window.__ModuleLoader__.load({
|
|
|
1182
1595
|
for (const listener of [...navScopeStore.listeners]) listener();
|
|
1183
1596
|
}
|
|
1184
1597
|
|
|
1185
|
-
/* Re-run prompt for one todo step
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1598
|
+
/* Re-run prompt for one todo step. Placeholder {index}/{content} are substituted;
|
|
1599
|
+
an empty template falls back to the built-in default. */
|
|
1600
|
+
const DEFAULT_TODO_TEMPLATE = [
|
|
1601
|
+
"请查看当前任务的完整 TODO 计划,重新执行以下指定步骤:",
|
|
1602
|
+
"",
|
|
1603
|
+
"**目标步骤:{index}. {content}**",
|
|
1604
|
+
"",
|
|
1605
|
+
"要求:",
|
|
1606
|
+
"1. 查看完整计划及相关上下文,明确目标步骤的执行要求和前置依赖。",
|
|
1607
|
+
"2. 无论该步骤当前状态如何,都必须重新检查并实际执行,不得直接跳过。",
|
|
1608
|
+
"3. 仅处理目标步骤所需内容,避免重复执行其他步骤或修改无关内容。",
|
|
1609
|
+
"4. 执行必要的测试或验证,如实反馈结果。",
|
|
1610
|
+
"5. 更新目标步骤的 TODO 状态,保持其他步骤状态不变。",
|
|
1611
|
+
"6. 汇报执行结果、修改内容及未解决的问题。",
|
|
1612
|
+
"",
|
|
1613
|
+
"**请直接执行,不要只提供方案。**",
|
|
1614
|
+
].join("\n");
|
|
1615
|
+
|
|
1616
|
+
function rerunPrompt(number, content, template) {
|
|
1617
|
+
const source = typeof template === "string" && template.trim() !== "" ? template : DEFAULT_TODO_TEMPLATE;
|
|
1618
|
+
return source.split("{index}").join(String(number)).split("{content}").join(String(content));
|
|
1202
1619
|
}
|
|
1203
1620
|
|
|
1204
1621
|
/** The visible Lexical chat composer root (contenteditable textbox), or null. */
|
|
@@ -1267,9 +1684,18 @@ window.__ModuleLoader__.load({
|
|
|
1267
1684
|
}
|
|
1268
1685
|
|
|
1269
1686
|
/** True while the session has a pending interaction (approval / question / plan exit). */
|
|
1687
|
+
function readSessionStatusSource() {
|
|
1688
|
+
const ctx = runtime && runtime.ctx;
|
|
1689
|
+
if (!ctx) return undefined;
|
|
1690
|
+
try {
|
|
1691
|
+
return ctx.uiSession && ctx.uiSession.sessionStatus ? ctx.uiSession.sessionStatus : undefined;
|
|
1692
|
+
} catch {
|
|
1693
|
+
return undefined;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1270
1697
|
function useSessionWaiting(sessionId) {
|
|
1271
|
-
const
|
|
1272
|
-
const source = ctx && ctx.uiSession && ctx.uiSession.sessionStatus ? ctx.uiSession.sessionStatus : undefined;
|
|
1698
|
+
const source = readSessionStatusSource();
|
|
1273
1699
|
const subscribe = React.useCallback(
|
|
1274
1700
|
(onChange) => (source !== undefined && typeof source.subscribe === "function" ? source.subscribe(onChange) : () => {}),
|
|
1275
1701
|
[source]
|
|
@@ -1318,6 +1744,7 @@ window.__ModuleLoader__.load({
|
|
|
1318
1744
|
presented: update.presented === true,
|
|
1319
1745
|
produced: update.produced === true,
|
|
1320
1746
|
time: typeof update.time === "number" ? update.time : 0,
|
|
1747
|
+
firstTime: typeof update.time === "number" ? update.time : 0,
|
|
1321
1748
|
seq: typeof update.seq === "number" ? update.seq : 0,
|
|
1322
1749
|
};
|
|
1323
1750
|
folder.artifacts.set(path, artifact);
|
|
@@ -1359,7 +1786,7 @@ window.__ModuleLoader__.load({
|
|
|
1359
1786
|
touch(folder);
|
|
1360
1787
|
return;
|
|
1361
1788
|
}
|
|
1362
|
-
const stored = { ...ref, count: 1 };
|
|
1789
|
+
const stored = { ...ref, count: 1, firstTime: typeof ref.time === "number" ? ref.time : 0 };
|
|
1363
1790
|
noteRefTurn(stored, folder);
|
|
1364
1791
|
folder.refs.set(ref.key, stored);
|
|
1365
1792
|
folder.refOrder.push(ref.key);
|
|
@@ -1370,9 +1797,87 @@ window.__ModuleLoader__.load({
|
|
|
1370
1797
|
|
|
1371
1798
|
/* Key-step stream: every tool call is one step; the result marks it done. Shown by
|
|
1372
1799
|
the Progress section when a turn has no todo plan of its own. */
|
|
1373
|
-
const STEP_CAP =
|
|
1800
|
+
const STEP_CAP = 200;
|
|
1374
1801
|
|
|
1375
1802
|
/** One-line human target for a tool call: file, command, query, url, name… */
|
|
1803
|
+
/** Team-record key a tool call creates: teammate name / task subject / subagent label. */
|
|
1804
|
+
/** Match team delegates (members, subagents, tasks) to todo items by normalized text
|
|
1805
|
+
containment, so each delegate renders under the todo it belongs to. */
|
|
1806
|
+
function planTodoAttachments(todos, members, teamTasks, subagents) {
|
|
1807
|
+
const attachments = new Map();
|
|
1808
|
+
if (!Array.isArray(todos) || todos.length === 0) return attachments;
|
|
1809
|
+
const normText = (value) =>
|
|
1810
|
+
typeof value === "string"
|
|
1811
|
+
? value
|
|
1812
|
+
.toLowerCase()
|
|
1813
|
+
.replace(/[-_/]+/g, " ")
|
|
1814
|
+
.replace(/[^a-z0-9\u4e00-\u9fff ]+/g, "")
|
|
1815
|
+
.replace(/\s+/g, " ")
|
|
1816
|
+
.trim()
|
|
1817
|
+
: "";
|
|
1818
|
+
const matchScore = (a, b) => {
|
|
1819
|
+
const x = normText(a);
|
|
1820
|
+
const y = normText(b);
|
|
1821
|
+
if (x === "" || y === "") return 0;
|
|
1822
|
+
if (x === y) return x.length;
|
|
1823
|
+
if (x.length >= 6 && y.includes(x)) return x.length;
|
|
1824
|
+
if (y.length >= 6 && x.includes(y)) return y.length;
|
|
1825
|
+
return 0;
|
|
1826
|
+
};
|
|
1827
|
+
const ownedBy = (id) => teamTasks.filter((task) => isRecord(task) && task.ownerId === id);
|
|
1828
|
+
const bestFor = (texts) => {
|
|
1829
|
+
let best = -1;
|
|
1830
|
+
let bestScore = 5;
|
|
1831
|
+
todos.forEach((todo, index) => {
|
|
1832
|
+
const content = isRecord(todo) ? todo.content : "";
|
|
1833
|
+
let score = 0;
|
|
1834
|
+
for (const text of texts) score = Math.max(score, matchScore(text, content));
|
|
1835
|
+
if (score > bestScore) {
|
|
1836
|
+
bestScore = score;
|
|
1837
|
+
best = index;
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
return best;
|
|
1841
|
+
};
|
|
1842
|
+
const attach = (index, item) => {
|
|
1843
|
+
const list = attachments.get(index);
|
|
1844
|
+
if (list === undefined) attachments.set(index, [item]);
|
|
1845
|
+
else list.push(item);
|
|
1846
|
+
};
|
|
1847
|
+
for (const member of members.filter(isRecord)) {
|
|
1848
|
+
const owned = ownedBy(member.id);
|
|
1849
|
+
const index = bestFor([member.name, member.description, ...owned.map((task) => task.subject)]);
|
|
1850
|
+
if (index >= 0) attach(index, { kind: "member", record: member, owned });
|
|
1851
|
+
}
|
|
1852
|
+
for (const sub of subagents.filter(isRecord)) {
|
|
1853
|
+
const index = bestFor([sub.label]);
|
|
1854
|
+
if (index >= 0) attach(index, { kind: "sub", record: sub, owned: [] });
|
|
1855
|
+
}
|
|
1856
|
+
const memberOwned = new Set();
|
|
1857
|
+
for (const list of attachments.values()) {
|
|
1858
|
+
for (const item of list) {
|
|
1859
|
+
if (item.kind === "member") for (const task of item.owned) memberOwned.add(task.id);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
for (const task of teamTasks.filter(isRecord)) {
|
|
1863
|
+
if (memberOwned.has(task.id)) continue;
|
|
1864
|
+
if (typeof task.ownerId === "string" && members.some((member) => isRecord(member) && member.id === task.ownerId)) continue;
|
|
1865
|
+
const index = bestFor([task.subject]);
|
|
1866
|
+
if (index >= 0) attach(index, { kind: "task", record: task, owned: [] });
|
|
1867
|
+
}
|
|
1868
|
+
const timeOf = (item) => (isRecord(item.record) && typeof item.record.time === "number" ? item.record.time : 0);
|
|
1869
|
+
for (const list of attachments.values()) list.sort((left, right) => timeOf(left) - timeOf(right));
|
|
1870
|
+
return attachments;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
function teamStepKey(name, args) {
|
|
1874
|
+
if (!isRecord(args)) return undefined;
|
|
1875
|
+
if (name === "spawn_teammate" && typeof args.name === "string" && args.name !== "") return args.name;
|
|
1876
|
+
if (name === "team_task_create" && typeof args.subject === "string" && args.subject !== "") return args.subject;
|
|
1877
|
+
if ((name === "subagent" || name === "subagent_fork") && typeof args.description === "string" && args.description !== "") return args.description;
|
|
1878
|
+
return undefined;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1376
1881
|
function stepDetail(name, args) {
|
|
1377
1882
|
if (!isRecord(args)) return "";
|
|
1378
1883
|
const keys = ["file_path", "path", "command", "pattern", "url", "query", "prompt", "subject", "description", "name", "uri", "target", "id"];
|
|
@@ -1405,7 +1910,16 @@ window.__ModuleLoader__.load({
|
|
|
1405
1910
|
return text.length > 4000 ? text.slice(0, 4000) : text;
|
|
1406
1911
|
}
|
|
1407
1912
|
|
|
1913
|
+
const ANCHOR_CAP = 8;
|
|
1408
1914
|
function addStep(folder, bucket, step) {
|
|
1915
|
+
if (typeof step.teamKey === "string") {
|
|
1916
|
+
folder.anchors.push(step);
|
|
1917
|
+
if (folder.anchors.length > ANCHOR_CAP) folder.anchors.shift();
|
|
1918
|
+
if (isRecord(bucket) && Array.isArray(bucket.anchors)) {
|
|
1919
|
+
bucket.anchors.push(step);
|
|
1920
|
+
if (bucket.anchors.length > ANCHOR_CAP) bucket.anchors.shift();
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1409
1923
|
folder.steps.push(step);
|
|
1410
1924
|
if (folder.steps.length > STEP_CAP) {
|
|
1411
1925
|
const dropped = folder.steps.shift();
|
|
@@ -1465,6 +1979,83 @@ window.__ModuleLoader__.load({
|
|
|
1465
1979
|
touch(folder);
|
|
1466
1980
|
return;
|
|
1467
1981
|
}
|
|
1982
|
+
case "team/member": {
|
|
1983
|
+
if (!isRecord(data) || !isRecord(data.member)) return;
|
|
1984
|
+
const member = data.member;
|
|
1985
|
+
if (typeof member.id !== "string" || member.id === "" || typeof member.name !== "string") return;
|
|
1986
|
+
const memberRecord = {
|
|
1987
|
+
id: member.id,
|
|
1988
|
+
name: member.name,
|
|
1989
|
+
description: typeof member.description === "string" ? member.description : "",
|
|
1990
|
+
phase: typeof member.phase === "string" ? member.phase : "",
|
|
1991
|
+
seq: event.seq,
|
|
1992
|
+
time: typeof event.time === "number" ? event.time : 0,
|
|
1993
|
+
};
|
|
1994
|
+
upsertTeamRecord(folder.members, member.id, memberRecord);
|
|
1995
|
+
upsertTeamRecord(turnBucket(folder, folder.currentTurn).members, member.id, memberRecord);
|
|
1996
|
+
touch(folder);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
case "team/task": {
|
|
2000
|
+
if (!isRecord(data) || !isRecord(data.task)) return;
|
|
2001
|
+
const task = data.task;
|
|
2002
|
+
if (typeof task.id !== "string" || task.id === "" || typeof task.subject !== "string") return;
|
|
2003
|
+
const taskRecord = {
|
|
2004
|
+
id: task.id,
|
|
2005
|
+
subject: task.subject,
|
|
2006
|
+
description: typeof task.description === "string" ? task.description : "",
|
|
2007
|
+
status: typeof task.status === "string" ? task.status : "",
|
|
2008
|
+
ownerId: typeof task.ownerId === "string" ? task.ownerId : undefined,
|
|
2009
|
+
blockedBy: Array.isArray(task.blockedBy) ? task.blockedBy.filter((item) => typeof item === "string") : [],
|
|
2010
|
+
revision: typeof task.revision === "number" ? task.revision : 0,
|
|
2011
|
+
seq: event.seq,
|
|
2012
|
+
time: typeof event.time === "number" ? event.time : 0,
|
|
2013
|
+
};
|
|
2014
|
+
upsertTeamRecord(folder.teamTasks, task.id, taskRecord);
|
|
2015
|
+
upsertTeamRecord(turnBucket(folder, folder.currentTurn).teamTasks, task.id, taskRecord);
|
|
2016
|
+
touch(folder);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
case "subagent/catalog": {
|
|
2020
|
+
if (!isRecord(data) || typeof data.childId !== "string" || data.childId === "") return;
|
|
2021
|
+
const subRecord = {
|
|
2022
|
+
childId: data.childId,
|
|
2023
|
+
label: typeof data.label === "string" ? data.label : "",
|
|
2024
|
+
mode: typeof data.mode === "string" ? data.mode : "",
|
|
2025
|
+
seq: event.seq,
|
|
2026
|
+
time: typeof event.time === "number" ? event.time : 0,
|
|
2027
|
+
};
|
|
2028
|
+
upsertTeamRecord(folder.subagents, data.childId, subRecord);
|
|
2029
|
+
upsertTeamRecord(turnBucket(folder, folder.currentTurn).subagents, data.childId, subRecord);
|
|
2030
|
+
touch(folder);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
case "approval/asked": {
|
|
2034
|
+
if (!isRecord(data) || typeof data.id !== "string" || data.id === "") return;
|
|
2035
|
+
const approvalBucket = turnBucket(folder, folder.currentTurn);
|
|
2036
|
+
const toolName = typeof data.toolName === "string" ? data.toolName : "";
|
|
2037
|
+
addStep(folder, approvalBucket, {
|
|
2038
|
+
callId: `approval-${data.id}`,
|
|
2039
|
+
name: "approval",
|
|
2040
|
+
detail: toolName,
|
|
2041
|
+
full: typeof data.reason === "string" && data.reason !== "" ? `${toolName} · ${data.reason}` : toolName,
|
|
2042
|
+
milestone: true,
|
|
2043
|
+
done: false,
|
|
2044
|
+
seq: event.seq,
|
|
2045
|
+
time: event.time,
|
|
2046
|
+
});
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
case "approval/decided": {
|
|
2050
|
+
if (!isRecord(data) || typeof data.id !== "string" || data.id === "") return;
|
|
2051
|
+
const outcome = typeof data.outcome === "string" ? data.outcome : "";
|
|
2052
|
+
const approvalStep = folder.stepByCall.get(`approval-${data.id}`);
|
|
2053
|
+
if (approvalStep !== undefined && outcome !== "" && typeof approvalStep.detail === "string" && !approvalStep.detail.includes(outcome)) {
|
|
2054
|
+
approvalStep.detail = approvalStep.detail === "" ? outcome : `${approvalStep.detail} · ${outcome}`;
|
|
2055
|
+
}
|
|
2056
|
+
markStepDone(folder, `approval-${data.id}`, event.time, /denied|reject/i.test(outcome));
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
1468
2059
|
case "workspace/changes": {
|
|
1469
2060
|
if (!isRecord(data) || typeof data.turn !== "number" || !Number.isSafeInteger(data.turn) || data.turn < 1) return;
|
|
1470
2061
|
if (typeof event.seq !== "number") return;
|
|
@@ -1495,7 +2086,7 @@ window.__ModuleLoader__.load({
|
|
|
1495
2086
|
const name = data.name;
|
|
1496
2087
|
const args = parseArgs(data.arguments);
|
|
1497
2088
|
const callBucket = turnBucket(folder, folder.currentTurn);
|
|
1498
|
-
addStep(folder, callBucket, { callId, name, detail: stepDetail(name, args), full: stepFull(name, args), done: false, seq: event.seq, time: event.time });
|
|
2089
|
+
addStep(folder, callBucket, { callId, name, detail: stepDetail(name, args), full: stepFull(name, args), teamKey: teamStepKey(name, args), done: false, seq: event.seq, time: event.time });
|
|
1499
2090
|
folder.stats.toolCalls += 1;
|
|
1500
2091
|
callBucket.stats.toolCalls += 1;
|
|
1501
2092
|
if (name === "bash") {
|
|
@@ -1518,6 +2109,23 @@ window.__ModuleLoader__.load({
|
|
|
1518
2109
|
return;
|
|
1519
2110
|
}
|
|
1520
2111
|
const memoryTool = memoryToolsOf(name);
|
|
2112
|
+
if (memoryTool !== null && MEMORY_MUTATION_TOOLS.has(memoryTool)) {
|
|
2113
|
+
const uri =
|
|
2114
|
+
args !== null && isRecord(args)
|
|
2115
|
+
? isVikingUri(args.uri)
|
|
2116
|
+
? args.uri
|
|
2117
|
+
: isVikingUri(args.to)
|
|
2118
|
+
? args.to
|
|
2119
|
+
: isVikingUri(args.parent)
|
|
2120
|
+
? args.parent
|
|
2121
|
+
: null
|
|
2122
|
+
: null;
|
|
2123
|
+
if (uri !== null) {
|
|
2124
|
+
const description = typeof args.description === "string" ? args.description : "";
|
|
2125
|
+
folder.pending.set(callId, { type: "artifact", path: uri, description });
|
|
2126
|
+
}
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
1521
2129
|
if (memoryTool !== null && MEMORY_REFERENCE_TOOLS.has(memoryTool)) {
|
|
1522
2130
|
const detail = [];
|
|
1523
2131
|
const uris = [];
|
|
@@ -1537,10 +2145,10 @@ window.__ModuleLoader__.load({
|
|
|
1537
2145
|
const refs = [];
|
|
1538
2146
|
for (const uri of uris.slice(0, 8)) {
|
|
1539
2147
|
refs.push({
|
|
1540
|
-
kind:
|
|
2148
|
+
kind: vikingRefKind(uri),
|
|
1541
2149
|
uri,
|
|
1542
2150
|
producer: `openviking · ${memoryTool}`,
|
|
1543
|
-
label:
|
|
2151
|
+
label: vikingLabel(uri),
|
|
1544
2152
|
detail: [],
|
|
1545
2153
|
key: `memory-file|${uri}`,
|
|
1546
2154
|
seq: event.seq,
|
|
@@ -1580,7 +2188,12 @@ window.__ModuleLoader__.load({
|
|
|
1580
2188
|
if (message.isError === true) return;
|
|
1581
2189
|
const bucket = turnBucket(folder, folder.currentTurn);
|
|
1582
2190
|
if (pending.type === "artifact") {
|
|
1583
|
-
upsertArtifact(folder, bucket, pending.path, {
|
|
2191
|
+
upsertArtifact(folder, bucket, pending.path, {
|
|
2192
|
+
produced: true,
|
|
2193
|
+
...(typeof pending.description === "string" && pending.description !== "" ? { description: pending.description } : {}),
|
|
2194
|
+
seq: event.seq,
|
|
2195
|
+
time: event.time,
|
|
2196
|
+
});
|
|
1584
2197
|
} else {
|
|
1585
2198
|
const resultText = textOf(message.content);
|
|
1586
2199
|
for (const ref of pending.refs) {
|
|
@@ -1595,9 +2208,11 @@ window.__ModuleLoader__.load({
|
|
|
1595
2208
|
return;
|
|
1596
2209
|
}
|
|
1597
2210
|
case "user/message": {
|
|
1598
|
-
if (!
|
|
2211
|
+
if (!isRecord(data)) return;
|
|
1599
2212
|
const source = isRecord(data.source) ? data.source : null;
|
|
1600
2213
|
const kind = source !== null && typeof source.kind === "string" ? source.kind : "user";
|
|
2214
|
+
/* a compact checkpoint replaces the event span it summarizes; every other message must append */
|
|
2215
|
+
if (kind !== "compact-checkpoint" && !isAppendSurfaceEvent(event)) return;
|
|
1601
2216
|
const bucket = turnBucket(folder, folder.currentTurn);
|
|
1602
2217
|
if (kind === "user") {
|
|
1603
2218
|
const mentions = mentionsOfUserText(textOf(data.content));
|
|
@@ -1609,6 +2224,22 @@ window.__ModuleLoader__.load({
|
|
|
1609
2224
|
}
|
|
1610
2225
|
return;
|
|
1611
2226
|
}
|
|
2227
|
+
if (kind === "compact-checkpoint") {
|
|
2228
|
+
/* context compaction is a milestone: anchor it as a durable key step */
|
|
2229
|
+
const compactionId = source !== null && typeof source.compactionId === "string" ? source.compactionId : `seq-${event.seq}`;
|
|
2230
|
+
const summaryText = textOf(data.content);
|
|
2231
|
+
addStep(folder, bucket, {
|
|
2232
|
+
name: "compact-checkpoint",
|
|
2233
|
+
detail: summaryText.length > 1024 ? `≈${Math.round(summaryText.length / 1024)} KB` : "",
|
|
2234
|
+
full: compactionId,
|
|
2235
|
+
teamKey: `compact:${compactionId}`,
|
|
2236
|
+
milestone: true,
|
|
2237
|
+
done: true,
|
|
2238
|
+
seq: event.seq,
|
|
2239
|
+
time: event.time,
|
|
2240
|
+
});
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
1612
2243
|
const label = contextLabel(source, kind);
|
|
1613
2244
|
const detail = contextDetail(source, data.content);
|
|
1614
2245
|
const refKind = refKindForSource(source, kind);
|
|
@@ -1617,11 +2248,11 @@ window.__ModuleLoader__.load({
|
|
|
1617
2248
|
if (refKind === "memory" && detail.length > 0 && detail.every(isVikingUri)) {
|
|
1618
2249
|
for (const uri of detail) {
|
|
1619
2250
|
addReference(folder, bucket, {
|
|
1620
|
-
kind:
|
|
2251
|
+
kind: vikingRefKind(uri),
|
|
1621
2252
|
uri,
|
|
1622
2253
|
producer,
|
|
1623
2254
|
form,
|
|
1624
|
-
label:
|
|
2255
|
+
label: vikingLabel(uri),
|
|
1625
2256
|
detail: [],
|
|
1626
2257
|
key: `memory-file|${uri}`,
|
|
1627
2258
|
seq: event.seq,
|
|
@@ -1715,13 +2346,39 @@ window.__ModuleLoader__.load({
|
|
|
1715
2346
|
errors: st.errors,
|
|
1716
2347
|
files: st.files.size,
|
|
1717
2348
|
};
|
|
2349
|
+
/* Agent-team projections: members, shared board tasks, and non-team subagents. */
|
|
2350
|
+
const byFirstTime = (left, right) => (left.firstTime || left.time || 0) - (right.firstTime || right.time || 0) || (left.seq || 0) - (right.seq || 0);
|
|
2351
|
+
const mapValues = (map) => (map instanceof Map ? [...map.values()] : []);
|
|
2352
|
+
const members = mapValues(source.members).sort(byFirstTime);
|
|
2353
|
+
const subagents = mapValues(source.subagents)
|
|
2354
|
+
.filter((sub) => !(source.members instanceof Map && source.members.has(sub.childId)))
|
|
2355
|
+
.sort(byFirstTime);
|
|
2356
|
+
const team = { members, tasks: mapValues(source.teamTasks).sort(byFirstTime), subagents };
|
|
2357
|
+
/* team anchor steps (spawn/task-create/subagent) stay pinned above the recent window
|
|
2358
|
+
so member/task rows can nest under their creating step */
|
|
2359
|
+
const allSteps = Array.isArray(source.steps) ? source.steps : [];
|
|
2360
|
+
const recentSteps = allSteps.slice(-6);
|
|
2361
|
+
const anchorCandidates = [...(Array.isArray(source.anchors) ? source.anchors : []), ...allSteps].filter(
|
|
2362
|
+
(step, index, list) =>
|
|
2363
|
+
isRecord(step) && typeof step.teamKey === "string" && step.milestone !== true && list.indexOf(step) === index && recentSteps.indexOf(step) === -1
|
|
2364
|
+
);
|
|
2365
|
+
const anchorSteps = anchorCandidates.slice(-6);
|
|
2366
|
+
/* milestone steps (compaction, approvals) ride the anchor lane for retention only;
|
|
2367
|
+
whether they pin above the window is a display choice (cfg.stepAnchors) */
|
|
2368
|
+
const milestoneSteps = (Array.isArray(source.anchors) ? source.anchors : []).filter((step) => isRecord(step) && step.milestone === true);
|
|
1718
2369
|
return {
|
|
1719
2370
|
todos,
|
|
1720
2371
|
todosTime,
|
|
1721
2372
|
artifacts,
|
|
1722
2373
|
references,
|
|
1723
|
-
steps:
|
|
2374
|
+
steps: [...anchorSteps, ...recentSteps],
|
|
2375
|
+
stepsAll: allSteps.slice(),
|
|
2376
|
+
team,
|
|
1724
2377
|
stats,
|
|
2378
|
+
turnStart: st.start > 0 ? st.start : null,
|
|
2379
|
+
turnEnd: st.end > 0 ? st.end : null,
|
|
2380
|
+
milestones: milestoneSteps,
|
|
2381
|
+
todoHistory: folder.todoRecords.slice(-300),
|
|
1725
2382
|
changeTurns: changeTurns.slice(),
|
|
1726
2383
|
hasMore: windowSnapshot.hasMore === true,
|
|
1727
2384
|
localeRev,
|
|
@@ -1731,7 +2388,7 @@ window.__ModuleLoader__.load({
|
|
|
1731
2388
|
};
|
|
1732
2389
|
}
|
|
1733
2390
|
|
|
1734
|
-
const EMPTY_STATE = { todos: null, todosTime: 0, artifacts: [], references: [], steps: [], stats: { durationMs: 0, toolCalls: 0, commands: 0, errors: 0, files: 0 }, changeTurns: [], hasMore: false, localeRev: 0, turns: [], latestTurn: null, scope: "latest" };
|
|
2391
|
+
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" };
|
|
1735
2392
|
|
|
1736
2393
|
function createSummaryStore(binding) {
|
|
1737
2394
|
const source = binding.eventSource;
|
|
@@ -1903,7 +2560,7 @@ window.__ModuleLoader__.load({
|
|
|
1903
2560
|
/* Prefer the jump loader: it pages back until the window covers seq and
|
|
1904
2561
|
survives concurrent loaders. loadOlder() is a no-op while another load
|
|
1905
2562
|
is in flight, so retry until the window actually stops reporting more. */
|
|
1906
|
-
for (let attempt = 0; attempt <
|
|
2563
|
+
for (let attempt = 0; attempt < 200 && !cancelled; attempt += 1) {
|
|
1907
2564
|
const snapshot = binding.eventSource.getSnapshot();
|
|
1908
2565
|
if (!isRecord(snapshot) || snapshot.hasMore !== true) return;
|
|
1909
2566
|
const session = binding.session;
|
|
@@ -2602,13 +3259,18 @@ function FileTypeIcon(props) {
|
|
|
2602
3259
|
);
|
|
2603
3260
|
}
|
|
2604
3261
|
|
|
3262
|
+
/* Same artwork as icon.svg, the icon the plugin list and settings show. */
|
|
2605
3263
|
function OverviewGlyph({ size = 16, className }) {
|
|
2606
|
-
return svg(
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
3264
|
+
return svg(
|
|
3265
|
+
size,
|
|
3266
|
+
className,
|
|
3267
|
+
[
|
|
3268
|
+
h("rect", { key: "r", x: 8, y: 8, width: 48, height: 48, rx: 12, stroke: "#565e6d", strokeWidth: 3 }),
|
|
3269
|
+
h("path", { key: "l", d: "M20 24h24M20 33h24M20 42h14", stroke: "#247bbf", strokeWidth: 3, strokeLinecap: "round" }),
|
|
3270
|
+
h("circle", { key: "c", cx: 43, cy: 42, r: 4, fill: "#2ea36a" }),
|
|
3271
|
+
],
|
|
3272
|
+
"0 0 64 64"
|
|
3273
|
+
);
|
|
2612
3274
|
}
|
|
2613
3275
|
|
|
2614
3276
|
function IconProgress({ size = 16, className }) {
|
|
@@ -2677,6 +3339,22 @@ function FileTypeIcon(props) {
|
|
|
2677
3339
|
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" })]);
|
|
2678
3340
|
}
|
|
2679
3341
|
|
|
3342
|
+
function IconCompact({ size = 16, className }) {
|
|
3343
|
+
return svg(size, className, [
|
|
3344
|
+
h("path", { key: "a", d: "M3.2 8h9.6", stroke: "currentColor", strokeLinecap: "round" }),
|
|
3345
|
+
h("path", { key: "b", d: "M8 2.4v3.4M6.3 4.1 8 5.8l1.7-1.7", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
3346
|
+
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" }),
|
|
3347
|
+
]);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
function IconHistory({ size = 16, className }) {
|
|
3351
|
+
return svg(size, className, [
|
|
3352
|
+
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" }),
|
|
3353
|
+
h("path", { key: "b", d: "M2.6 2.4v3.2h3.2", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
3354
|
+
h("path", { key: "c", d: "M8 4.9v3.2l2.7 1.5", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
3355
|
+
]);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
2680
3358
|
function IconCompare({ size = 16, className }) {
|
|
2681
3359
|
return svg(size, className, [
|
|
2682
3360
|
h("path", { key: "a", d: "M3 5.4h8.2M9.2 3.4l2 2-2 2", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
@@ -2698,6 +3376,13 @@ function FileTypeIcon(props) {
|
|
|
2698
3376
|
]);
|
|
2699
3377
|
}
|
|
2700
3378
|
|
|
3379
|
+
function IconAgent({ size = 16, className }) {
|
|
3380
|
+
return svg(size, className, [
|
|
3381
|
+
h("circle", { key: "a", cx: 8, cy: 5.4, r: 2.5, stroke: "currentColor" }),
|
|
3382
|
+
h("path", { key: "b", d: "M3.4 13c.7-2.2 2.4-3.4 4.6-3.4S12 10.8 12.6 13", stroke: "currentColor", strokeLinecap: "round" }),
|
|
3383
|
+
]);
|
|
3384
|
+
}
|
|
3385
|
+
|
|
2701
3386
|
function TodoStatusIcon({ status }) {
|
|
2702
3387
|
if (status === "completed") {
|
|
2703
3388
|
return svg(15, undefined, [
|
|
@@ -2732,12 +3417,13 @@ function FileTypeIcon(props) {
|
|
|
2732
3417
|
|
|
2733
3418
|
/* References ordering, shared by both surfaces and persisted per browser. */
|
|
2734
3419
|
const REF_SORT_KEY = "dsh.conversationOverview.refSort";
|
|
2735
|
-
const REF_SORT_MODES = ["time", "
|
|
3420
|
+
const REF_SORT_MODES = ["time", "count"];
|
|
2736
3421
|
const KIND_ORDER = ["memory-file", "memory", "file", "skill", "session", "context"];
|
|
2737
3422
|
|
|
2738
3423
|
function readStoredRefSort() {
|
|
2739
3424
|
try {
|
|
2740
3425
|
const value = globalThis.localStorage ? globalThis.localStorage.getItem(REF_SORT_KEY) : null;
|
|
3426
|
+
if (value === "none") return "";
|
|
2741
3427
|
return REF_SORT_MODES.indexOf(value) !== -1 ? value : "time";
|
|
2742
3428
|
} catch (_unreadable) {
|
|
2743
3429
|
return "time";
|
|
@@ -2747,10 +3433,11 @@ function FileTypeIcon(props) {
|
|
|
2747
3433
|
const refSortStore = { mode: readStoredRefSort(), listeners: new Set() };
|
|
2748
3434
|
|
|
2749
3435
|
function setRefSort(mode) {
|
|
2750
|
-
|
|
2751
|
-
refSortStore.mode
|
|
3436
|
+
const next = mode === "" ? "" : REF_SORT_MODES.indexOf(mode) !== -1 ? mode : undefined;
|
|
3437
|
+
if (next === undefined || refSortStore.mode === next) return;
|
|
3438
|
+
refSortStore.mode = next;
|
|
2752
3439
|
try {
|
|
2753
|
-
if (globalThis.localStorage) globalThis.localStorage.setItem(REF_SORT_KEY,
|
|
3440
|
+
if (globalThis.localStorage) globalThis.localStorage.setItem(REF_SORT_KEY, next === "" ? "none" : next);
|
|
2754
3441
|
} catch (_unwritable) {
|
|
2755
3442
|
/* the choice still applies for this page load */
|
|
2756
3443
|
}
|
|
@@ -2867,7 +3554,6 @@ function FileTypeIcon(props) {
|
|
|
2867
3554
|
/** Incoming order is newest-first; kind/count modes re-rank, breaking ties by recency. */
|
|
2868
3555
|
function sortReferences(refs, mode) {
|
|
2869
3556
|
const byRecency = (left, right) => (right.time || right.seq || 0) - (left.time || left.seq || 0) || (right.seq || 0) - (left.seq || 0);
|
|
2870
|
-
if (mode === "kind") return refs.slice().sort((left, right) => kindRank(left.kind) - kindRank(right.kind) || byRecency(left, right));
|
|
2871
3557
|
if (mode === "count") return refs.slice().sort((left, right) => (right.count || 1) - (left.count || 1) || byRecency(left, right));
|
|
2872
3558
|
return refs;
|
|
2873
3559
|
}
|
|
@@ -2907,6 +3593,14 @@ function FileTypeIcon(props) {
|
|
|
2907
3593
|
completed: "todo.completed",
|
|
2908
3594
|
};
|
|
2909
3595
|
|
|
3596
|
+
/* Agent-team member phases (tooltip text; status is conveyed by icons). */
|
|
3597
|
+
const MEMBER_PHASE_KEYS = {
|
|
3598
|
+
provisioning: "team.phase.provisioning",
|
|
3599
|
+
active: "team.phase.active",
|
|
3600
|
+
inactive: "team.phase.inactive",
|
|
3601
|
+
failed: "team.phase.failed",
|
|
3602
|
+
};
|
|
3603
|
+
|
|
2910
3604
|
const GOAL_PHASE_KEYS = {
|
|
2911
3605
|
active: "goal.active",
|
|
2912
3606
|
paused: "goal.paused",
|
|
@@ -2921,13 +3615,21 @@ function FileTypeIcon(props) {
|
|
|
2921
3615
|
complete: "success",
|
|
2922
3616
|
};
|
|
2923
3617
|
|
|
2924
|
-
function ProgressSection({ summary, goal, t, waiting }) {
|
|
3618
|
+
function ProgressSection({ summary, goal, t, waiting, cfg, asOf }) {
|
|
3619
|
+
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
3620
|
+
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3621
|
+
/* timeline scrubbing: only what already happened at `asOf` is shown */
|
|
3622
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
3623
|
+
const pinMilestones = isRecord(cfg) && cfg.stepAnchors === true;
|
|
3624
|
+
const inTime = (record) => asOfTime === null || ((record.firstTime || record.time || 0) <= asOfTime);
|
|
2925
3625
|
/* todo → composer prompt flash */
|
|
2926
3626
|
const [promptFlash, setPromptFlash] = React.useState(null);
|
|
2927
3627
|
const flashTimer = React.useRef(undefined);
|
|
2928
3628
|
/* step → copy summary flash */
|
|
2929
3629
|
const [stepFlash, setStepFlash] = React.useState(null);
|
|
2930
3630
|
const copyTimer = React.useRef(undefined);
|
|
3631
|
+
/* per-member expansion overrides for the merged team rows */
|
|
3632
|
+
const [teamExpanded, setTeamExpanded] = React.useState(null);
|
|
2931
3633
|
React.useEffect(
|
|
2932
3634
|
() => () => {
|
|
2933
3635
|
if (flashTimer.current !== undefined) clearTimeout(flashTimer.current);
|
|
@@ -2935,8 +3637,11 @@ function FileTypeIcon(props) {
|
|
|
2935
3637
|
},
|
|
2936
3638
|
[]
|
|
2937
3639
|
);
|
|
3640
|
+
const todoPromptOn = !isRecord(cfg) || cfg.todoPrompt !== false;
|
|
3641
|
+
const todoPromptTemplate = isRecord(cfg) && typeof cfg.todoPromptTemplate === "string" ? cfg.todoPromptTemplate : "";
|
|
2938
3642
|
const onTodoClick = (index, content) => {
|
|
2939
|
-
|
|
3643
|
+
if (!todoPromptOn) return;
|
|
3644
|
+
const result = insertIntoComposer(rerunPrompt(index + 1, content, todoPromptTemplate));
|
|
2940
3645
|
if (flashTimer.current !== undefined) clearTimeout(flashTimer.current);
|
|
2941
3646
|
setPromptFlash({ index, state: result });
|
|
2942
3647
|
flashTimer.current = setTimeout(() => {
|
|
@@ -2971,16 +3676,66 @@ function FileTypeIcon(props) {
|
|
|
2971
3676
|
if (!handled) setFlash("err");
|
|
2972
3677
|
};
|
|
2973
3678
|
/* steps: newest-first window; keep the evicted bottom row as a fading ghost */
|
|
2974
|
-
const
|
|
3679
|
+
const stepsRaw = Array.isArray(summary.steps) ? summary.steps.filter((step) => isRecord(step)) : [];
|
|
3680
|
+
const stepsAllRaw = Array.isArray(summary.stepsAll) ? summary.stepsAll.filter((step) => isRecord(step)) : [];
|
|
3681
|
+
const stepAt = (step) => asOfTime === null || (typeof step.time === "number" ? step.time <= asOfTime : true);
|
|
3682
|
+
/* a step whose result landed after the scrub point reads as still running */
|
|
3683
|
+
const stepClamped = (step) => {
|
|
3684
|
+
if (asOfTime === null || step.done !== true) return step;
|
|
3685
|
+
const end = typeof step.endTime === "number" ? step.endTime : typeof step.time === "number" ? step.time : 0;
|
|
3686
|
+
return end <= asOfTime ? step : { ...step, done: false, error: false, endTime: undefined };
|
|
3687
|
+
};
|
|
3688
|
+
const stepsAll = stepsAllRaw.filter(stepAt).map(stepClamped);
|
|
3689
|
+
/* while scrubbing, rebuild the "latest 6" window as it looked at time T:
|
|
3690
|
+
the live window sits near the turn end, so filtering it by T would empty
|
|
3691
|
+
the section mid-drag even though earlier steps already happened */
|
|
3692
|
+
const steps =
|
|
3693
|
+
asOfTime === null
|
|
3694
|
+
? stepsRaw.filter(stepAt).map(stepClamped)
|
|
3695
|
+
: (() => {
|
|
3696
|
+
const visible = stepsAllRaw.filter(stepAt);
|
|
3697
|
+
const recent = visible.slice(-6);
|
|
3698
|
+
const anchors = stepsRaw.filter(
|
|
3699
|
+
(step) => stepAt(step) && typeof step.teamKey === "string" && step.milestone !== true && recent.indexOf(step) === -1
|
|
3700
|
+
);
|
|
3701
|
+
return [...anchors, ...recent].map(stepClamped);
|
|
3702
|
+
})();
|
|
2975
3703
|
const [ghost, setGhost] = React.useState(null);
|
|
3704
|
+
const [stepsExpanded, setStepsExpanded] = React.useState(false);
|
|
2976
3705
|
const ghostTimer = React.useRef(undefined);
|
|
2977
3706
|
const prevSteps = React.useRef([]);
|
|
2978
3707
|
const prevScope = React.useRef(summary.scope);
|
|
2979
3708
|
/* enter animation only for rows that arrive while mounted; first paint per scope is silent */
|
|
2980
3709
|
const stepKeyOf = (step, index) => (typeof step.callId === "string" && step.callId !== "" ? `step-${step.callId}` : `step-idx-${index}`);
|
|
3710
|
+
const milestoneKeyOf = (step) => (typeof step.callId === "string" && step.callId !== "" ? step.callId : `${step.name}|${step.seq}`);
|
|
2981
3711
|
const seenSteps = React.useRef(null);
|
|
3712
|
+
/* full retained history for the expander: anchors outside the window + the whole bucket */
|
|
3713
|
+
const allStepIds = new Set(stepsAll.map((step) => step.callId).filter((id) => typeof id === "string"));
|
|
3714
|
+
const anchorExtras = steps.filter((step) => typeof step.callId === "string" && !allStepIds.has(step.callId));
|
|
3715
|
+
const expandedSteps = [...anchorExtras, ...stepsAll];
|
|
3716
|
+
const canExpand = expandedSteps.length > steps.length;
|
|
3717
|
+
const stepsOpen = stepsExpanded && canExpand;
|
|
3718
|
+
/* cfg.stepAnchors pins compaction/approval/failure steps above the window */
|
|
3719
|
+
const milestoneExtras = [];
|
|
3720
|
+
if (pinMilestones) {
|
|
3721
|
+
const baseSteps = stepsOpen ? expandedSteps : steps;
|
|
3722
|
+
const baseKeys = new Set(baseSteps.map(milestoneKeyOf));
|
|
3723
|
+
const candidates = [
|
|
3724
|
+
...(Array.isArray(summary.milestones) ? summary.milestones.filter((step) => isRecord(step) && step.milestone === true) : []),
|
|
3725
|
+
...stepsAll.filter((step) => step.error === true),
|
|
3726
|
+
];
|
|
3727
|
+
for (const candidate of candidates) {
|
|
3728
|
+
if (!stepAt(candidate)) continue;
|
|
3729
|
+
const key = milestoneKeyOf(candidate);
|
|
3730
|
+
if (baseKeys.has(key)) continue;
|
|
3731
|
+
baseKeys.add(key);
|
|
3732
|
+
milestoneExtras.push(stepClamped(candidate));
|
|
3733
|
+
}
|
|
3734
|
+
milestoneExtras.sort((left, right) => (left.time || 0) - (right.time || 0));
|
|
3735
|
+
}
|
|
3736
|
+
const displaySteps = milestoneExtras.length > 0 ? [...milestoneExtras, ...(stepsOpen ? expandedSteps : steps)] : stepsOpen ? expandedSteps : steps;
|
|
2982
3737
|
if (seenSteps.current === null || seenSteps.current.scope !== summary.scope) {
|
|
2983
|
-
seenSteps.current = { scope: summary.scope, keys: new Set(
|
|
3738
|
+
seenSteps.current = { scope: summary.scope, keys: new Set(displaySteps.map(stepKeyOf)) };
|
|
2984
3739
|
}
|
|
2985
3740
|
React.useEffect(
|
|
2986
3741
|
() => () => {
|
|
@@ -2995,7 +3750,7 @@ function FileTypeIcon(props) {
|
|
|
2995
3750
|
prevScope.current = summary.scope;
|
|
2996
3751
|
return;
|
|
2997
3752
|
}
|
|
2998
|
-
if (previous.length === 0) return;
|
|
3753
|
+
if (asOfTime !== null || previous.length === 0) return;
|
|
2999
3754
|
const currentIds = new Set(steps.map((step) => step.callId));
|
|
3000
3755
|
const dropped = previous.filter((step) => typeof step.callId === "string" && !currentIds.has(step.callId));
|
|
3001
3756
|
if (dropped.length === 0) return;
|
|
@@ -3006,6 +3761,193 @@ function FileTypeIcon(props) {
|
|
|
3006
3761
|
setGhost(null);
|
|
3007
3762
|
}, 400);
|
|
3008
3763
|
}, [steps]);
|
|
3764
|
+
/* team data + todos drive both the merged list and the step nesting */
|
|
3765
|
+
const team = isRecord(summary.team) ? summary.team : null;
|
|
3766
|
+
const members = team && Array.isArray(team.members) ? team.members.filter((member) => isRecord(member) && inTime(member)) : [];
|
|
3767
|
+
const teamTasks = team && Array.isArray(team.tasks) ? team.tasks.filter((task) => isRecord(task) && inTime(task)) : [];
|
|
3768
|
+
const subagents = team && Array.isArray(team.subagents) ? team.subagents.filter((sub) => isRecord(sub) && inTime(sub)) : [];
|
|
3769
|
+
const todoHistory = Array.isArray(summary.todoHistory) ? summary.todoHistory : [];
|
|
3770
|
+
let todos = Array.isArray(summary.todos) ? summary.todos : null;
|
|
3771
|
+
if (asOfTime !== null) {
|
|
3772
|
+
/* mirror buildState's turn scoping: a turn without its own todos must
|
|
3773
|
+
not inherit an earlier turn's snapshot while scrubbing */
|
|
3774
|
+
const scopeText = typeof summary.scope === "string" ? summary.scope : "";
|
|
3775
|
+
const scopedTurn = scopeText.startsWith("turn:")
|
|
3776
|
+
? Number.parseInt(scopeText.slice(5), 10)
|
|
3777
|
+
: scopeText === "latest" && Number.isFinite(summary.latestTurn)
|
|
3778
|
+
? summary.latestTurn
|
|
3779
|
+
: null;
|
|
3780
|
+
let snapshot = null;
|
|
3781
|
+
for (const record of todoHistory) {
|
|
3782
|
+
if (!isRecord(record) || !Array.isArray(record.todos)) continue;
|
|
3783
|
+
if (scopedTurn !== null && record.turn !== scopedTurn) continue;
|
|
3784
|
+
if ((record.time || 0) <= asOfTime) snapshot = record;
|
|
3785
|
+
}
|
|
3786
|
+
todos = snapshot !== null ? snapshot.todos : null;
|
|
3787
|
+
}
|
|
3788
|
+
const hasTodos = todos !== null && todos.length > 0;
|
|
3789
|
+
const hasTeam = members.length + teamTasks.length + subagents.length > 0;
|
|
3790
|
+
const showSteps = !hasTodos && steps.length > 0;
|
|
3791
|
+
const flashOf = (key) => (stepFlash !== null && isRecord(stepFlash) && stepFlash.key === key ? stepFlash.state : null);
|
|
3792
|
+
const overrideOf = (key, fallback) => {
|
|
3793
|
+
const stored = teamExpanded !== null && isRecord(teamExpanded) ? teamExpanded[key] : undefined;
|
|
3794
|
+
return typeof stored === "boolean" ? stored : fallback;
|
|
3795
|
+
};
|
|
3796
|
+
const toggleKey = (key, next) => setTeamExpanded({ ...(teamExpanded !== null && isRecord(teamExpanded) ? teamExpanded : {}), [key]: next });
|
|
3797
|
+
const ownedBy = (id) => teamTasks.filter((task) => task.ownerId === id);
|
|
3798
|
+
const taskRow = (task, depth) => {
|
|
3799
|
+
const status = TODO_KEYS[task.status] ? task.status : "pending";
|
|
3800
|
+
const blocked = Array.isArray(task.blockedBy) ? task.blockedBy.length : 0;
|
|
3801
|
+
const flash = flashOf(`teamtask-${task.id}`);
|
|
3802
|
+
const titleParts = [task.subject];
|
|
3803
|
+
if (task.description !== "") titleParts.push(task.description);
|
|
3804
|
+
titleParts.push(blocked > 0 ? `${t(TODO_KEYS[status])} · ${t("team.blocked", { n: blocked })}` : t(TODO_KEYS[status]));
|
|
3805
|
+
return h(
|
|
3806
|
+
"div",
|
|
3807
|
+
{
|
|
3808
|
+
className: "dsov-todo dsov-task",
|
|
3809
|
+
key: `task-${task.id}`,
|
|
3810
|
+
"data-status": status,
|
|
3811
|
+
"data-depth": depth > 0 ? String(depth) : undefined,
|
|
3812
|
+
"data-flash": flash === "ok" ? "ok" : flash === "err" ? "err" : undefined,
|
|
3813
|
+
title: titleParts.join("\n\n"),
|
|
3814
|
+
onClick: () => onStepClick({ callId: `teamtask-${task.id}`, name: task.subject, full: task.subject, seq: task.seq }),
|
|
3815
|
+
},
|
|
3816
|
+
h("span", { className: "dsov-todoIcon", "data-status": status }, h(TodoStatusIcon, { status })),
|
|
3817
|
+
h("span", { className: "dsov-taskSubject" }, task.subject),
|
|
3818
|
+
h("span", { className: "dsov-time", title: timeTitle(task.time) }, fmtTime(task.time))
|
|
3819
|
+
);
|
|
3820
|
+
};
|
|
3821
|
+
const subRow = (sub, depth) => {
|
|
3822
|
+
const flash = flashOf(`subagent-${sub.childId}`);
|
|
3823
|
+
return h(
|
|
3824
|
+
"div",
|
|
3825
|
+
{
|
|
3826
|
+
className: "dsov-todo dsov-subagent",
|
|
3827
|
+
key: `subagent-${sub.childId}`,
|
|
3828
|
+
"data-depth": depth > 0 ? String(depth) : undefined,
|
|
3829
|
+
"data-flash": flash === "ok" ? "ok" : flash === "err" ? "err" : undefined,
|
|
3830
|
+
title: sub.label !== "" ? sub.label : sub.childId,
|
|
3831
|
+
onClick: () => onStepClick({ callId: `subagent-${sub.childId}`, name: sub.label, full: sub.label !== "" ? sub.label : sub.childId, seq: sub.seq }),
|
|
3832
|
+
},
|
|
3833
|
+
h("span", { className: "dsov-todoIcon dsov-agentIcon" }, h(IconAgent, { size: 14 })),
|
|
3834
|
+
h("span", { className: "dsov-memberName" }, sub.label !== "" ? sub.label : sub.childId),
|
|
3835
|
+
sub.mode !== "" ? h("span", { className: "dsov-badge", "data-tone": "quiet" }, sub.mode) : null,
|
|
3836
|
+
h("span", { className: "dsov-time", title: timeTitle(sub.time) }, fmtTime(sub.time))
|
|
3837
|
+
);
|
|
3838
|
+
};
|
|
3839
|
+
/* members and subagents always read as agents: person icon, tinted red on failure */
|
|
3840
|
+
const memberIcon = (member, owned) => {
|
|
3841
|
+
const phase = typeof member.phase === "string" ? member.phase : "";
|
|
3842
|
+
if (phase === "failed") {
|
|
3843
|
+
return h("span", { className: "dsov-todoIcon dsov-agentIcon", "data-status": "failed" }, h(IconAgent, { size: 14 }));
|
|
3844
|
+
}
|
|
3845
|
+
return h("span", { className: "dsov-todoIcon dsov-agentIcon" }, h(IconAgent, { size: 14 }));
|
|
3846
|
+
};
|
|
3847
|
+
const memberRow = (member, depth, expandable, open) => {
|
|
3848
|
+
const owned = ownedBy(member.id);
|
|
3849
|
+
const phase = typeof member.phase === "string" ? member.phase : "";
|
|
3850
|
+
const titleParts = [];
|
|
3851
|
+
if (member.description !== "") titleParts.push(member.description);
|
|
3852
|
+
if (MEMBER_PHASE_KEYS[phase]) titleParts.push(t(MEMBER_PHASE_KEYS[phase]));
|
|
3853
|
+
return h(
|
|
3854
|
+
"div",
|
|
3855
|
+
{
|
|
3856
|
+
className: "dsov-todo dsov-member",
|
|
3857
|
+
key: `member-${member.id}`,
|
|
3858
|
+
"data-depth": depth > 0 ? String(depth) : undefined,
|
|
3859
|
+
"data-expandable": expandable ? "true" : "false",
|
|
3860
|
+
"data-open": expandable ? (open ? "true" : "false") : undefined,
|
|
3861
|
+
title: titleParts.length > 0 ? titleParts.join(" · ") : member.name,
|
|
3862
|
+
onClick: expandable ? () => toggleKey(`member:${member.id}`, !open) : undefined,
|
|
3863
|
+
},
|
|
3864
|
+
expandable ? h("span", { className: "dsov-chevron", "data-open": open ? "true" : "false" }, h(IconChevron, { size: 11 })) : null,
|
|
3865
|
+
memberIcon(member, owned),
|
|
3866
|
+
h("span", { className: "dsov-memberName" }, member.name),
|
|
3867
|
+
h("span", { className: "dsov-memberDesc" }, member.description),
|
|
3868
|
+
h("span", { className: "dsov-time", title: timeTitle(member.time) }, fmtTime(member.time))
|
|
3869
|
+
);
|
|
3870
|
+
};
|
|
3871
|
+
/* attach team records under their creating step (steps view only) */
|
|
3872
|
+
const consumedMembers = new Set();
|
|
3873
|
+
const consumedTasks = new Set();
|
|
3874
|
+
const consumedSubs = new Set();
|
|
3875
|
+
/* delegates matched to a todo render nested under it and skip the flat tail */
|
|
3876
|
+
const todoAttachments = hasTodos && hasTeam ? planTodoAttachments(todos, members, teamTasks, subagents) : new Map();
|
|
3877
|
+
for (const list of todoAttachments.values()) {
|
|
3878
|
+
for (const item of list) {
|
|
3879
|
+
if (item.kind === "member") {
|
|
3880
|
+
consumedMembers.add(item.record.id);
|
|
3881
|
+
item.owned.forEach((task) => consumedTasks.add(task.id));
|
|
3882
|
+
} else if (item.kind === "sub") {
|
|
3883
|
+
consumedSubs.add(item.record.childId);
|
|
3884
|
+
} else {
|
|
3885
|
+
consumedTasks.add(item.record.id);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
const stepAttachments = new Map();
|
|
3890
|
+
if (showSteps && hasTeam) {
|
|
3891
|
+
displaySteps.forEach((step, index) => {
|
|
3892
|
+
const teamKey = typeof step.teamKey === "string" ? step.teamKey : "";
|
|
3893
|
+
if (teamKey === "" || typeof step.name !== "string") return;
|
|
3894
|
+
const attached = [];
|
|
3895
|
+
if (step.name === "spawn_teammate") {
|
|
3896
|
+
const member = members.find((candidate) => candidate.name === teamKey && !consumedMembers.has(candidate.id));
|
|
3897
|
+
if (member !== undefined) {
|
|
3898
|
+
consumedMembers.add(member.id);
|
|
3899
|
+
const phase = typeof member.phase === "string" ? member.phase : "";
|
|
3900
|
+
attached.push({ row: memberRow(member, 1, false, false), unfinished: phase === "provisioning" || phase === "active" });
|
|
3901
|
+
ownedBy(member.id).forEach((task) => {
|
|
3902
|
+
if (consumedTasks.has(task.id)) return;
|
|
3903
|
+
consumedTasks.add(task.id);
|
|
3904
|
+
attached.push({ row: taskRow(task, 2), unfinished: task.status !== "completed" });
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
} else if (step.name === "subagent" || step.name === "subagent_fork") {
|
|
3908
|
+
const sub = subagents.find((candidate) => (candidate.label === teamKey || candidate.childId === teamKey) && !consumedSubs.has(candidate.childId));
|
|
3909
|
+
if (sub !== undefined) {
|
|
3910
|
+
consumedSubs.add(sub.childId);
|
|
3911
|
+
attached.push({ row: subRow(sub, 1), unfinished: false });
|
|
3912
|
+
}
|
|
3913
|
+
} else if (step.name === "team_task_create") {
|
|
3914
|
+
/* owned tasks nest under their member instead; only unowned tasks attach here */
|
|
3915
|
+
const task = teamTasks.find(
|
|
3916
|
+
(candidate) =>
|
|
3917
|
+
candidate.subject === teamKey &&
|
|
3918
|
+
!consumedTasks.has(candidate.id) &&
|
|
3919
|
+
!(typeof candidate.ownerId === "string" && members.some((member) => member.id === candidate.ownerId))
|
|
3920
|
+
);
|
|
3921
|
+
if (task !== undefined) {
|
|
3922
|
+
consumedTasks.add(task.id);
|
|
3923
|
+
attached.push({ row: taskRow(task, 1), unfinished: task.status !== "completed" });
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
if (attached.length > 0) stepAttachments.set(stepKeyOf(step, index), attached);
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
/* records not attached to a visible step render flat in the list */
|
|
3930
|
+
const pushFlatTeam = (target) => {
|
|
3931
|
+
teamTasks.forEach((task) => {
|
|
3932
|
+
if (consumedTasks.has(task.id)) return;
|
|
3933
|
+
const ownerKnown = typeof task.ownerId === "string" && members.some((member) => member.id === task.ownerId);
|
|
3934
|
+
if (ownerKnown) return;
|
|
3935
|
+
target.push(taskRow(task, 0));
|
|
3936
|
+
});
|
|
3937
|
+
members.forEach((member) => {
|
|
3938
|
+
if (consumedMembers.has(member.id)) return;
|
|
3939
|
+
const owned = ownedBy(member.id).filter((task) => !consumedTasks.has(task.id));
|
|
3940
|
+
const expandable = owned.length > 0;
|
|
3941
|
+
const phase = typeof member.phase === "string" ? member.phase : "";
|
|
3942
|
+
const open = expandable ? overrideOf(`member:${member.id}`, owned.some((task) => task.status !== "completed") || phase === "provisioning") : false;
|
|
3943
|
+
target.push(memberRow(member, 0, expandable, open));
|
|
3944
|
+
if (expandable && open) owned.forEach((task) => target.push(taskRow(task, 1)));
|
|
3945
|
+
});
|
|
3946
|
+
subagents.forEach((sub) => {
|
|
3947
|
+
if (consumedSubs.has(sub.childId)) return;
|
|
3948
|
+
target.push(subRow(sub, 0));
|
|
3949
|
+
});
|
|
3950
|
+
};
|
|
3009
3951
|
const stepRow = (step, key, extraProps, isWaiting) => {
|
|
3010
3952
|
const stepRunning = step.done !== true;
|
|
3011
3953
|
const failed = step.error === true;
|
|
@@ -3013,7 +3955,10 @@ function FileTypeIcon(props) {
|
|
|
3013
3955
|
const detail = typeof step.detail === "string" && step.detail !== "" ? step.detail : "";
|
|
3014
3956
|
const full = typeof step.full === "string" && step.full !== "" ? step.full : detail !== "" ? detail : typeof step.name === "string" ? step.name : "";
|
|
3015
3957
|
const flash = stepFlash !== null && isRecord(stepFlash) && stepFlash.key === (typeof step.callId === "string" && step.callId !== "" ? step.callId : `${step.name}|${step.seq}`) ? stepFlash.state : null;
|
|
3016
|
-
|
|
3958
|
+
const attached = stepAttachments.get(key);
|
|
3959
|
+
const expandable = Array.isArray(attached) && attached.length > 0;
|
|
3960
|
+
const open = expandable ? overrideOf(`step:${key}`, attached.some((item) => item.unfinished)) : false;
|
|
3961
|
+
const row = h(
|
|
3017
3962
|
"div",
|
|
3018
3963
|
{
|
|
3019
3964
|
className: "dsov-step",
|
|
@@ -3025,7 +3970,24 @@ function FileTypeIcon(props) {
|
|
|
3025
3970
|
...(extraProps === undefined ? {} : extraProps),
|
|
3026
3971
|
onClick: () => onStepClick(step),
|
|
3027
3972
|
},
|
|
3028
|
-
|
|
3973
|
+
expandable
|
|
3974
|
+
? h(
|
|
3975
|
+
"span",
|
|
3976
|
+
{
|
|
3977
|
+
className: "dsov-chevron dsov-stepChevron",
|
|
3978
|
+
"data-open": open ? "true" : "false",
|
|
3979
|
+
title: t(open ? "progress.collapseTeam" : "progress.expandTeam"),
|
|
3980
|
+
onClick: (event) => {
|
|
3981
|
+
event.stopPropagation();
|
|
3982
|
+
toggleKey(`step:${key}`, !open);
|
|
3983
|
+
},
|
|
3984
|
+
},
|
|
3985
|
+
h(IconChevron, { size: 11 })
|
|
3986
|
+
)
|
|
3987
|
+
: null,
|
|
3988
|
+
step.name === "compact-checkpoint"
|
|
3989
|
+
? h("span", { className: "dsov-todoIcon dsov-compactIcon" }, h(IconCompact, { size: 15 }))
|
|
3990
|
+
: waitingRow
|
|
3029
3991
|
? h(
|
|
3030
3992
|
"span",
|
|
3031
3993
|
{ className: "dsov-todoIcon", "data-status": "waiting" },
|
|
@@ -3037,10 +3999,20 @@ function FileTypeIcon(props) {
|
|
|
3037
3999
|
)
|
|
3038
4000
|
)
|
|
3039
4001
|
: h("span", { className: "dsov-todoIcon", "data-status": stepRunning ? "in_progress" : "completed" }, h(TodoStatusIcon, { status: stepRunning ? "in_progress" : "completed" })),
|
|
3040
|
-
h(
|
|
4002
|
+
h(
|
|
4003
|
+
"span",
|
|
4004
|
+
{ className: "dsov-stepName" },
|
|
4005
|
+
step.name === "compact-checkpoint" ? t("step.compact") : step.name === "approval" ? t("step.approval") : typeof step.name === "string" ? step.name : ""
|
|
4006
|
+
),
|
|
3041
4007
|
h("span", { className: "dsov-stepDetail" }, detail),
|
|
3042
|
-
h("span", { className: "dsov-time" },
|
|
4008
|
+
h("span", { className: "dsov-time", title: timeTitle(typeof step.endTime === "number" ? step.endTime : step.time) }, fmtTime(typeof step.endTime === "number" ? step.endTime : step.time))
|
|
3043
4009
|
);
|
|
4010
|
+
return expandable && open ? [row, ...attached.map((item) => item.row)] : row;
|
|
4011
|
+
};
|
|
4012
|
+
const pushStepRow = (target, step, key, extraProps, isWaiting) => {
|
|
4013
|
+
const rendered = stepRow(step, key, extraProps, isWaiting);
|
|
4014
|
+
if (Array.isArray(rendered)) target.push(...rendered);
|
|
4015
|
+
else target.push(rendered);
|
|
3044
4016
|
};
|
|
3045
4017
|
const kids = [];
|
|
3046
4018
|
if (goal) {
|
|
@@ -3055,8 +4027,7 @@ function FileTypeIcon(props) {
|
|
|
3055
4027
|
)
|
|
3056
4028
|
);
|
|
3057
4029
|
}
|
|
3058
|
-
|
|
3059
|
-
if (todos && todos.length > 0) {
|
|
4030
|
+
if (hasTodos) {
|
|
3060
4031
|
const total = todos.length;
|
|
3061
4032
|
const done = todos.filter((item) => item.status === "completed").length;
|
|
3062
4033
|
kids.push(
|
|
@@ -3067,18 +4038,34 @@ function FileTypeIcon(props) {
|
|
|
3067
4038
|
h("div", { className: "dsov-progressMeta" }, t("progress.counts", { completed: done, total }))
|
|
3068
4039
|
)
|
|
3069
4040
|
);
|
|
4041
|
+
const todoRows = [];
|
|
3070
4042
|
todos.forEach((todo, index) => {
|
|
3071
4043
|
const status = TODO_KEYS[todo.status] ? todo.status : "pending";
|
|
3072
4044
|
const flash = promptFlash !== null && isRecord(promptFlash) && promptFlash.index === index ? promptFlash.state : null;
|
|
3073
|
-
const title =
|
|
3074
|
-
|
|
4045
|
+
const title = !todoPromptOn
|
|
4046
|
+
? todo.content
|
|
4047
|
+
: flash === "inserted"
|
|
3075
4048
|
? t("progress.todoInserted")
|
|
3076
4049
|
: flash === "copied"
|
|
3077
4050
|
? t("progress.todoCopied")
|
|
3078
4051
|
: flash === "failed"
|
|
3079
4052
|
? t("progress.todoFailed")
|
|
3080
4053
|
: t("progress.todoHint");
|
|
3081
|
-
|
|
4054
|
+
const attached = todoAttachments.get(index);
|
|
4055
|
+
const expandable = Array.isArray(attached) && attached.length > 0;
|
|
4056
|
+
const open = expandable
|
|
4057
|
+
? overrideOf(
|
|
4058
|
+
`todo:${index}`,
|
|
4059
|
+
attached.some((item) =>
|
|
4060
|
+
item.kind === "task"
|
|
4061
|
+
? item.record.status !== "completed"
|
|
4062
|
+
: item.kind === "member"
|
|
4063
|
+
? item.record.phase === "provisioning" || item.record.phase === "active" || item.owned.some((task) => task.status !== "completed")
|
|
4064
|
+
: false
|
|
4065
|
+
)
|
|
4066
|
+
)
|
|
4067
|
+
: false;
|
|
4068
|
+
todoRows.push(
|
|
3082
4069
|
h(
|
|
3083
4070
|
"div",
|
|
3084
4071
|
{
|
|
@@ -3086,10 +4073,13 @@ function FileTypeIcon(props) {
|
|
|
3086
4073
|
key: `todo-${index}`,
|
|
3087
4074
|
"data-status": status,
|
|
3088
4075
|
"data-flash": flash === "inserted" || flash === "copied" ? "ok" : flash === "failed" ? "err" : undefined,
|
|
4076
|
+
"data-expandable": expandable ? "true" : undefined,
|
|
4077
|
+
"data-open": expandable ? (open ? "true" : "false") : undefined,
|
|
3089
4078
|
role: "button",
|
|
3090
|
-
tabIndex: 0,
|
|
4079
|
+
tabIndex: todoPromptOn ? 0 : undefined,
|
|
3091
4080
|
title,
|
|
3092
|
-
onClick: () =>
|
|
4081
|
+
onClick: expandable ? () => toggleKey(`todo:${index}`, !open) : undefined,
|
|
4082
|
+
onDoubleClick: () => onTodoClick(index, todo.content),
|
|
3093
4083
|
onKeyDown: (event) => {
|
|
3094
4084
|
if (event.key === "Enter" || event.key === " ") {
|
|
3095
4085
|
event.preventDefault();
|
|
@@ -3097,18 +4087,54 @@ function FileTypeIcon(props) {
|
|
|
3097
4087
|
}
|
|
3098
4088
|
},
|
|
3099
4089
|
},
|
|
3100
|
-
h(
|
|
4090
|
+
h(
|
|
4091
|
+
"span",
|
|
4092
|
+
{ className: "dsov-todoIconSlot" },
|
|
4093
|
+
h("span", { className: "dsov-todoIcon", "data-status": status }, h(TodoStatusIcon, { status })),
|
|
4094
|
+
expandable
|
|
4095
|
+
? h(
|
|
4096
|
+
"span",
|
|
4097
|
+
{
|
|
4098
|
+
className: "dsov-chevron dsov-todoChevron",
|
|
4099
|
+
"data-open": open ? "true" : "false",
|
|
4100
|
+
title: t(open ? "progress.collapseTeam" : "progress.expandTeam"),
|
|
4101
|
+
onClick: (event) => {
|
|
4102
|
+
event.stopPropagation();
|
|
4103
|
+
toggleKey(`todo:${index}`, !open);
|
|
4104
|
+
},
|
|
4105
|
+
},
|
|
4106
|
+
h(IconChevron, { size: 11 })
|
|
4107
|
+
)
|
|
4108
|
+
: null
|
|
4109
|
+
),
|
|
3101
4110
|
h("span", { className: "dsov-todoText" }, todo.content),
|
|
3102
4111
|
h("span", { className: "dsov-todoStatus" }, t(TODO_KEYS[status]))
|
|
3103
4112
|
)
|
|
3104
4113
|
);
|
|
4114
|
+
if (expandable && open) {
|
|
4115
|
+
attached.forEach((item) => {
|
|
4116
|
+
if (item.kind === "member") {
|
|
4117
|
+
const memberExpandable = item.owned.length > 0;
|
|
4118
|
+
const memberOpen = memberExpandable ? overrideOf(`member:${item.record.id}`, item.owned.some((task) => task.status !== "completed")) : false;
|
|
4119
|
+
todoRows.push(memberRow(item.record, 1, memberExpandable, memberOpen));
|
|
4120
|
+
if (memberExpandable && memberOpen) item.owned.forEach((task) => todoRows.push(taskRow(task, 2)));
|
|
4121
|
+
} else if (item.kind === "sub") {
|
|
4122
|
+
todoRows.push(subRow(item.record, 1));
|
|
4123
|
+
} else {
|
|
4124
|
+
todoRows.push(taskRow(item.record, 1));
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
}
|
|
3105
4128
|
});
|
|
3106
|
-
|
|
4129
|
+
kids.push(h("div", { className: "dsov-todos", key: "todos" }, todoRows));
|
|
4130
|
+
}
|
|
4131
|
+
if (hasTeam && !showSteps) pushFlatTeam(kids);
|
|
4132
|
+
if (showSteps) {
|
|
3107
4133
|
const markWaiting = waiting === true && summary.scope === "latest";
|
|
3108
4134
|
let waitingIndex = -1;
|
|
3109
4135
|
if (markWaiting) {
|
|
3110
|
-
for (let index =
|
|
3111
|
-
if (
|
|
4136
|
+
for (let index = displaySteps.length - 1; index >= 0; index -= 1) {
|
|
4137
|
+
if (displaySteps[index].done !== true && displaySteps[index].error !== true) {
|
|
3112
4138
|
waitingIndex = index;
|
|
3113
4139
|
break;
|
|
3114
4140
|
}
|
|
@@ -3116,27 +4142,71 @@ function FileTypeIcon(props) {
|
|
|
3116
4142
|
}
|
|
3117
4143
|
const rows = [];
|
|
3118
4144
|
let freshCount = 0;
|
|
3119
|
-
|
|
4145
|
+
displaySteps.forEach((step, index) => {
|
|
3120
4146
|
const key = stepKeyOf(step, index);
|
|
3121
|
-
const fresh = !seenSteps.current.keys.has(key);
|
|
4147
|
+
const fresh = asOfTime === null && !seenSteps.current.keys.has(key);
|
|
3122
4148
|
if (fresh) {
|
|
3123
4149
|
seenSteps.current.keys.add(key);
|
|
3124
4150
|
freshCount += 1;
|
|
3125
4151
|
}
|
|
3126
|
-
rows
|
|
4152
|
+
pushStepRow(rows, step, key, fresh ? { "data-enter": "true", style: { animationDelay: `${(freshCount - 1) * 45}ms` } } : undefined, index === waitingIndex);
|
|
3127
4153
|
});
|
|
3128
|
-
if (ghost !== null && isRecord(ghost)) {
|
|
3129
|
-
rows
|
|
4154
|
+
if (ghost !== null && isRecord(ghost) && !stepsOpen) {
|
|
4155
|
+
pushStepRow(rows, ghost, `ghost-${ghost.callId}`, { "data-exit": "true" }, false);
|
|
3130
4156
|
}
|
|
3131
|
-
|
|
4157
|
+
if (hasTeam) pushFlatTeam(rows);
|
|
4158
|
+
kids.push(
|
|
4159
|
+
h(
|
|
4160
|
+
"div",
|
|
4161
|
+
{ className: "dsov-stepsHead", key: "stepsHead" },
|
|
4162
|
+
h("span", null, t(stepsOpen ? "progress.allSteps" : "progress.recentSteps")),
|
|
4163
|
+
canExpand
|
|
4164
|
+
? h(
|
|
4165
|
+
"button",
|
|
4166
|
+
{
|
|
4167
|
+
type: "button",
|
|
4168
|
+
className: "dsov-moreSteps",
|
|
4169
|
+
title: stepsOpen ? t("progress.hideEarlierSteps") : t("progress.moreStepsCount", { n: expandedSteps.length - steps.length }),
|
|
4170
|
+
onClick: () => setStepsExpanded(!stepsOpen),
|
|
4171
|
+
},
|
|
4172
|
+
t(stepsOpen ? "progress.hideEarlierSteps" : "progress.showEarlierSteps"),
|
|
4173
|
+
h(IconHistory, { size: 12, className: "dsov-moreIcon" })
|
|
4174
|
+
)
|
|
4175
|
+
: null
|
|
4176
|
+
)
|
|
4177
|
+
);
|
|
3132
4178
|
kids.push(h("div", { className: "dsov-steps", key: "steps" }, rows));
|
|
3133
4179
|
}
|
|
3134
4180
|
return h(Section, { id: "progress", icon: h(IconProgress, { size: 15 }), title: t("section.progress"), empty: t(summary.scope === "all" ? "progress.empty" : "progress.emptyTurn") }, kids);
|
|
3135
4181
|
}
|
|
3136
4182
|
|
|
3137
|
-
function ArtifactsSection({ summary, t, binding, stats }) {
|
|
4183
|
+
function ArtifactsSection({ summary, t, binding, stats, asOf }) {
|
|
4184
|
+
const cfg = useConfig();
|
|
4185
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
4186
|
+
const visibleArtifacts = asOfTime === null ? summary.artifacts : summary.artifacts.filter((artifact) => (artifact.firstTime || artifact.time || 0) <= asOfTime);
|
|
4187
|
+
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
4188
|
+
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3138
4189
|
const sessionId = binding ? binding.sessionId : undefined;
|
|
3139
4190
|
const cwd = sessionCwd(sessionId);
|
|
4191
|
+
const canFileMenu = hostSessionRemote() !== null;
|
|
4192
|
+
const [fileMenu, setFileMenu] = React.useState(null);
|
|
4193
|
+
React.useEffect(() => {
|
|
4194
|
+
if (fileMenu === null) return undefined;
|
|
4195
|
+
const close = () => setFileMenu(null);
|
|
4196
|
+
const onKey = (event) => {
|
|
4197
|
+
if (event.key === "Escape") close();
|
|
4198
|
+
};
|
|
4199
|
+
document.addEventListener("mousedown", close);
|
|
4200
|
+
document.addEventListener("keydown", onKey);
|
|
4201
|
+
window.addEventListener("scroll", close, true);
|
|
4202
|
+
window.addEventListener("resize", close);
|
|
4203
|
+
return () => {
|
|
4204
|
+
document.removeEventListener("mousedown", close);
|
|
4205
|
+
document.removeEventListener("keydown", onKey);
|
|
4206
|
+
window.removeEventListener("scroll", close, true);
|
|
4207
|
+
window.removeEventListener("resize", close);
|
|
4208
|
+
};
|
|
4209
|
+
}, [fileMenu]);
|
|
3140
4210
|
const turns = summary.changeTurns;
|
|
3141
4211
|
const latest = turns.length > 0 ? turns[turns.length - 1] : null;
|
|
3142
4212
|
const action =
|
|
@@ -3148,9 +4218,10 @@ function FileTypeIcon(props) {
|
|
|
3148
4218
|
t("artifacts.viewAll")
|
|
3149
4219
|
)
|
|
3150
4220
|
: null;
|
|
3151
|
-
const rows =
|
|
4221
|
+
const rows = visibleArtifacts.map((artifact, index) => {
|
|
3152
4222
|
const stat = stats ? stats.get(absPath(cwd, artifact.path)) : undefined;
|
|
3153
4223
|
const showCounts = stat !== undefined && !stat.binary && !stat.oversized && (stat.added > 0 || stat.deleted > 0);
|
|
4224
|
+
const isMemory = typeof artifact.path === "string" && artifact.path.startsWith("viking://");
|
|
3154
4225
|
return h(
|
|
3155
4226
|
"button",
|
|
3156
4227
|
{
|
|
@@ -3159,6 +4230,13 @@ function FileTypeIcon(props) {
|
|
|
3159
4230
|
key: `${artifact.path}#${index}`,
|
|
3160
4231
|
title: artifact.description ? `${artifact.path} — ${artifact.description}` : artifact.path,
|
|
3161
4232
|
onClick: () => openArtifact(binding, artifact, stats),
|
|
4233
|
+
onContextMenu:
|
|
4234
|
+
canFileMenu && !isMemory
|
|
4235
|
+
? (event) => {
|
|
4236
|
+
event.preventDefault();
|
|
4237
|
+
setFileMenu({ x: event.clientX, y: event.clientY, path: absPath(cwd, artifact.path) });
|
|
4238
|
+
}
|
|
4239
|
+
: undefined,
|
|
3162
4240
|
},
|
|
3163
4241
|
h("span", { className: "dsov-fileIcon" }, h(FileTypeIcon, { path: artifact.path, size: 20 })),
|
|
3164
4242
|
h("span", { className: "dsov-artifactName" }, basename(artifact.path)),
|
|
@@ -3172,17 +4250,19 @@ function FileTypeIcon(props) {
|
|
|
3172
4250
|
: stat !== undefined && (stat.binary || stat.oversized)
|
|
3173
4251
|
? h("span", { className: "dsov-badge", "data-tone": "quiet" }, t("artifact.binary"))
|
|
3174
4252
|
: null,
|
|
4253
|
+
isMemory ? h("span", { className: "dsov-badge", "data-tone": "quiet" }, t("artifact.memory")) : null,
|
|
3175
4254
|
artifact.presented ? h("span", { className: "dsov-badge", "data-tone": "brand" }, t("artifact.presented")) : null,
|
|
3176
|
-
h("span", { className: "dsov-rowTail" }, h("span", { className: "dsov-time" },
|
|
4255
|
+
h("span", { className: "dsov-rowTail" }, h("span", { className: "dsov-time", title: timeTitle(artifact.time) }, fmtTime(artifact.time)))
|
|
3177
4256
|
);
|
|
3178
4257
|
});
|
|
4258
|
+
if (fileMenu !== null) rows.push(h(ArtifactFileMenu, { key: "dsov-file-menu", menu: fileMenu, t, onClose: () => setFileMenu(null) }));
|
|
3179
4259
|
return h(
|
|
3180
4260
|
Section,
|
|
3181
4261
|
{
|
|
3182
4262
|
id: "artifacts",
|
|
3183
4263
|
icon: h(IconArtifact, { size: 15 }),
|
|
3184
4264
|
title: t("section.artifacts"),
|
|
3185
|
-
badge:
|
|
4265
|
+
badge: visibleArtifacts.length > 0 ? String(visibleArtifacts.length) : undefined,
|
|
3186
4266
|
empty: t("artifacts.empty"),
|
|
3187
4267
|
action,
|
|
3188
4268
|
},
|
|
@@ -3288,8 +4368,21 @@ function FileTypeIcon(props) {
|
|
|
3288
4368
|
);
|
|
3289
4369
|
}
|
|
3290
4370
|
|
|
3291
|
-
|
|
4371
|
+
/** Ref tile: viking rows get file/folder icons, other kinds keep their type icon. */
|
|
4372
|
+
function refTile(ref) {
|
|
4373
|
+
if (ref.kind === "file") return h(FileTypeIcon, { path: ref.label, size: 18 });
|
|
4374
|
+
if ((ref.kind === "memory-file" || ref.kind === "skill") && typeof ref.uri === "string" && isVikingUri(ref.uri)) {
|
|
4375
|
+
return vikingIsDir(ref.uri) ? h(FileTypeIcon, { kind: "folder", size: 18 }) : h(FileTypeIcon, { path: ref.uri, size: 18 });
|
|
4376
|
+
}
|
|
4377
|
+
return h(RefIcon, { kind: ref.kind, size: 14 });
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
function ReferencesSection({ summary, t, sessionId, asOf }) {
|
|
3292
4381
|
const cfg = useConfig();
|
|
4382
|
+
const asOfTime = typeof asOf === "number" && asOf > 0 ? asOf : null;
|
|
4383
|
+
const allRefs = asOfTime === null ? summary.references : summary.references.filter((ref) => (ref.firstTime || ref.time || 0) <= asOfTime);
|
|
4384
|
+
const fmtTime = (ts) => formatTime(ts, cfg.timeMode, summary.turnStart);
|
|
4385
|
+
const timeTitle = (ts) => (cfg.timeMode === "elapsed" ? formatTime(ts) : undefined);
|
|
3293
4386
|
const sortMode = useRefSort();
|
|
3294
4387
|
const hideCommon = useHideCommonRefs();
|
|
3295
4388
|
const typeFilter = useRefTypes();
|
|
@@ -3325,7 +4418,7 @@ function FileTypeIcon(props) {
|
|
|
3325
4418
|
}, 1200);
|
|
3326
4419
|
}
|
|
3327
4420
|
};
|
|
3328
|
-
const scoped = hideCommon ?
|
|
4421
|
+
const scoped = hideCommon ? allRefs.filter((ref) => ref.ubiquitous !== true) : allRefs;
|
|
3329
4422
|
const selectedTypes = Array.isArray(typeFilter) ? typeFilter : [];
|
|
3330
4423
|
const filtered = selectedTypes.length === 0 ? scoped : scoped.filter((ref) => selectedTypes.indexOf(refDisplayType(ref)) !== -1);
|
|
3331
4424
|
const sorted = sortReferences(filtered, sortMode);
|
|
@@ -3335,7 +4428,7 @@ function FileTypeIcon(props) {
|
|
|
3335
4428
|
const extra = detail.length - shown.length;
|
|
3336
4429
|
const skillCatalog = ref.producer === "skill-catalog" && shown.length > 0;
|
|
3337
4430
|
const content = [
|
|
3338
|
-
h("span", { className: "dsov-tile", key: "tile" },
|
|
4431
|
+
h("span", { className: "dsov-tile", key: "tile" }, refTile(ref)),
|
|
3339
4432
|
h(
|
|
3340
4433
|
"span",
|
|
3341
4434
|
{ className: "dsov-rowText", key: "text" },
|
|
@@ -3386,7 +4479,7 @@ function FileTypeIcon(props) {
|
|
|
3386
4479
|
)
|
|
3387
4480
|
: null
|
|
3388
4481
|
),
|
|
3389
|
-
h("span", { className: "dsov-time", key: "time" },
|
|
4482
|
+
h("span", { className: "dsov-time", key: "time", title: timeTitle(ref.time) }, fmtTime(ref.time)),
|
|
3390
4483
|
];
|
|
3391
4484
|
const isFile = ref.kind === "file";
|
|
3392
4485
|
if (isFile) {
|
|
@@ -3402,7 +4495,7 @@ function FileTypeIcon(props) {
|
|
|
3402
4495
|
...content
|
|
3403
4496
|
);
|
|
3404
4497
|
}
|
|
3405
|
-
if (ref.kind === "memory-file" && isVikingUri(ref.uri) && cfg.vikingPreview && cfg.vikingUrl !== "") {
|
|
4498
|
+
if ((ref.kind === "memory-file" || ref.kind === "skill") && isVikingUri(ref.uri) && cfg.vikingPreview && cfg.vikingUrl !== "") {
|
|
3406
4499
|
return h(
|
|
3407
4500
|
"button",
|
|
3408
4501
|
{
|
|
@@ -3430,13 +4523,13 @@ function FileTypeIcon(props) {
|
|
|
3430
4523
|
}
|
|
3431
4524
|
return h(
|
|
3432
4525
|
"div",
|
|
3433
|
-
{ className: "dsov-row dsov-rowStatic", key: `ref-${index}`, title:
|
|
4526
|
+
{ className: "dsov-row dsov-rowStatic", key: `ref-${index}`, title: isVikingUri(ref.uri) ? ref.uri : ref.label },
|
|
3434
4527
|
...content
|
|
3435
4528
|
);
|
|
3436
4529
|
});
|
|
3437
|
-
const ubiquitousCount =
|
|
4530
|
+
const ubiquitousCount = allRefs.filter((ref) => ref.ubiquitous === true).length;
|
|
3438
4531
|
const typeSet = [];
|
|
3439
|
-
for (const ref of
|
|
4532
|
+
for (const ref of allRefs) {
|
|
3440
4533
|
const kind = refDisplayType(ref);
|
|
3441
4534
|
if (kind !== "" && typeSet.indexOf(kind) === -1) typeSet.push(kind);
|
|
3442
4535
|
}
|
|
@@ -3472,7 +4565,7 @@ function FileTypeIcon(props) {
|
|
|
3472
4565
|
badge: filtered.length > 0 ? String(filtered.length) : undefined,
|
|
3473
4566
|
empty: hideCommon && ubiquitousCount > 0 && scoped.length === 0 ? t("references.onlyCommon") : t("references.empty"),
|
|
3474
4567
|
action:
|
|
3475
|
-
|
|
4568
|
+
allRefs.length > 1
|
|
3476
4569
|
? h(
|
|
3477
4570
|
"div",
|
|
3478
4571
|
{ className: "dsov-refControls" },
|
|
@@ -3504,8 +4597,8 @@ function FileTypeIcon(props) {
|
|
|
3504
4597
|
key: mode,
|
|
3505
4598
|
className: "dsov-segBtn",
|
|
3506
4599
|
"data-active": sortMode === mode ? "true" : "false",
|
|
3507
|
-
title: t(`reference.sort.${mode}.title`),
|
|
3508
|
-
onClick: () => setRefSort(mode),
|
|
4600
|
+
title: sortMode === mode ? `${t(`reference.sort.${mode}.title`)} · ${t("reference.sort.cancel")}` : t(`reference.sort.${mode}.title`),
|
|
4601
|
+
onClick: () => setRefSort(sortMode === mode ? "" : mode),
|
|
3509
4602
|
},
|
|
3510
4603
|
t(`reference.sort.${mode}`)
|
|
3511
4604
|
)
|
|
@@ -3534,19 +4627,47 @@ function FileTypeIcon(props) {
|
|
|
3534
4627
|
}
|
|
3535
4628
|
|
|
3536
4629
|
/** Left-hand stat pills on the scope row: duration, tool calls, commands, files, failures. */
|
|
3537
|
-
function StatsBar({ stats, t }) {
|
|
4630
|
+
function StatsBar({ stats, t, canTimeline, timelineOpen, scrubbing, onToggleTimeline, prevJump, onPrevTurn }) {
|
|
3538
4631
|
if (!isRecord(stats)) return null;
|
|
3539
4632
|
const duration = typeof stats.durationMs === "number" ? stats.durationMs : 0;
|
|
3540
4633
|
const toolCalls = typeof stats.toolCalls === "number" ? stats.toolCalls : 0;
|
|
3541
4634
|
const commands = typeof stats.commands === "number" ? stats.commands : 0;
|
|
3542
4635
|
const files = typeof stats.files === "number" ? stats.files : 0;
|
|
3543
4636
|
const errors = typeof stats.errors === "number" ? stats.errors : 0;
|
|
3544
|
-
|
|
4637
|
+
/* scrubbed to 0s: every pill is zero and the bar would vanish — keep it
|
|
4638
|
+
alive with the "previous turn" jump instead */
|
|
4639
|
+
const jumpBtn =
|
|
4640
|
+
Number.isFinite(prevJump) && typeof onPrevTurn === "function"
|
|
4641
|
+
? h(
|
|
4642
|
+
"button",
|
|
4643
|
+
{ type: "button", className: "dsov-stat dsov-statBtn dsov-prevTurn", key: "prevTurn", title: t("timeline.prevTurnTitle", { n: prevJump }), onClick: onPrevTurn },
|
|
4644
|
+
`\u2039 ${t("timeline.prevTurn")}`
|
|
4645
|
+
)
|
|
4646
|
+
: null;
|
|
4647
|
+
if (duration === 0 && toolCalls === 0) return jumpBtn === null ? null : h("div", { className: "dsov-stats" }, jumpBtn);
|
|
3545
4648
|
const pill = (key, text, title, tone) => h("span", { className: "dsov-stat", key, title, ...(tone === undefined ? {} : { "data-tone": tone }) }, text);
|
|
4649
|
+
const scrubable = canTimeline === true && typeof onToggleTimeline === "function";
|
|
3546
4650
|
return h(
|
|
3547
4651
|
"div",
|
|
3548
4652
|
{ className: "dsov-stats" },
|
|
3549
|
-
|
|
4653
|
+
jumpBtn,
|
|
4654
|
+
duration > 0
|
|
4655
|
+
? scrubable
|
|
4656
|
+
? h(
|
|
4657
|
+
"button",
|
|
4658
|
+
{
|
|
4659
|
+
type: "button",
|
|
4660
|
+
className: "dsov-stat dsov-statBtn",
|
|
4661
|
+
key: "duration",
|
|
4662
|
+
title: t("stats.duration.timeline"),
|
|
4663
|
+
"data-active": timelineOpen === true ? "true" : undefined,
|
|
4664
|
+
"data-scrub": scrubbing === true ? "true" : undefined,
|
|
4665
|
+
onClick: onToggleTimeline,
|
|
4666
|
+
},
|
|
4667
|
+
t("stats.duration", { v: formatDuration(duration) })
|
|
4668
|
+
)
|
|
4669
|
+
: pill("duration", t("stats.duration", { v: formatDuration(duration) }), t("stats.duration.title"))
|
|
4670
|
+
: null,
|
|
3550
4671
|
pill("tools", t("stats.tools", { n: toolCalls }), t("stats.tools.title")),
|
|
3551
4672
|
commands > 0 ? pill("commands", t("stats.commands", { n: commands }), t("stats.commands.title")) : null,
|
|
3552
4673
|
files > 0 ? pill("files", t("stats.files", { n: files }), t("stats.files.title")) : null,
|
|
@@ -3556,6 +4677,9 @@ function FileTypeIcon(props) {
|
|
|
3556
4677
|
|
|
3557
4678
|
function OverviewContent({ binding, t, variant }) {
|
|
3558
4679
|
const [selection, setSelection] = React.useState("latest");
|
|
4680
|
+
/* timeline scrubbing: a null asOf always means "live/latest state" */
|
|
4681
|
+
const [asOf, setAsOf] = React.useState(null);
|
|
4682
|
+
const [timelineOpen, setTimelineOpen] = React.useState(false);
|
|
3559
4683
|
const sessionWaiting = useSessionWaiting(binding ? binding.sessionId : undefined);
|
|
3560
4684
|
/* Follow quick-nav linkage clicks issued after this instance mounted. */
|
|
3561
4685
|
const navRev = useNavScopeRev();
|
|
@@ -3564,6 +4688,11 @@ function FileTypeIcon(props) {
|
|
|
3564
4688
|
if (navScopeStore.rev === mountNavRev.current) return;
|
|
3565
4689
|
if (typeof navScopeStore.selection === "string") setSelection(navScopeStore.selection);
|
|
3566
4690
|
}, [navRev]);
|
|
4691
|
+
React.useEffect(() => {
|
|
4692
|
+
/* new scope: back to live, but keep the timeline row open so a
|
|
4693
|
+
"上一轮" jump lands ready to drag in the previous turn */
|
|
4694
|
+
setAsOf(null);
|
|
4695
|
+
}, [selection]);
|
|
3567
4696
|
const summary = useSummary(binding, selection);
|
|
3568
4697
|
useBackfill(binding, summary.hasMore);
|
|
3569
4698
|
const goal = useGoal(binding);
|
|
@@ -3571,6 +4700,7 @@ function FileTypeIcon(props) {
|
|
|
3571
4700
|
const cfg = useConfig();
|
|
3572
4701
|
const sessionId = binding ? binding.sessionId : undefined;
|
|
3573
4702
|
const rootClass = variant === "panel" ? "dsov-panel" : "dsov-view";
|
|
4703
|
+
const rootRef = React.useRef(null);
|
|
3574
4704
|
const scope = summary.scope === selection || selection === "latest" ? summary.scope : selection;
|
|
3575
4705
|
const hasTurns = Array.isArray(summary.turns) && summary.turns.length >= 2;
|
|
3576
4706
|
const turnOptions = Array.isArray(summary.turns) ? summary.turns.slice().reverse() : [];
|
|
@@ -3591,49 +4721,217 @@ function FileTypeIcon(props) {
|
|
|
3591
4721
|
h("option", { value: "all", key: "all" }, t("scope.all"))
|
|
3592
4722
|
)
|
|
3593
4723
|
: null;
|
|
3594
|
-
|
|
3595
|
-
const
|
|
4724
|
+
/* while scrubbing, the stats pills recompute from the steps that already happened */
|
|
4725
|
+
const viewStats = React.useMemo(() => {
|
|
4726
|
+
if (asOf === null || typeof summary.turnStart !== "number") return summary.stats;
|
|
4727
|
+
const list = (Array.isArray(summary.stepsAll) ? summary.stepsAll : []).filter((step) => isRecord(step) && (step.time || 0) <= asOf);
|
|
4728
|
+
return {
|
|
4729
|
+
...summary.stats,
|
|
4730
|
+
durationMs: Math.max(0, asOf - summary.turnStart),
|
|
4731
|
+
toolCalls: list.length,
|
|
4732
|
+
commands: list.filter((step) => step.name === "bash").length,
|
|
4733
|
+
errors: list.filter((step) => step.error === true && (step.endTime || step.time || 0) <= asOf).length,
|
|
4734
|
+
};
|
|
4735
|
+
}, [asOf, summary]);
|
|
4736
|
+
const canTimeline = typeof summary.turnStart === "number" && typeof summary.turnEnd === "number" && summary.turnEnd > summary.turnStart;
|
|
4737
|
+
/* at the 0s point nothing has happened yet in this turn: offer a jump to
|
|
4738
|
+
the previous turn, rendered inside the (otherwise empty) stats bar */
|
|
4739
|
+
const turnNums = Array.isArray(summary.turns) ? summary.turns.filter((value) => Number.isFinite(value)) : [];
|
|
4740
|
+
let prevTurn = null;
|
|
4741
|
+
if (scopeTurn !== null) {
|
|
4742
|
+
const oldest = turnNums.length > 0 ? turnNums[0] : 1;
|
|
4743
|
+
if (scopeTurn - 1 >= oldest) prevTurn = scopeTurn - 1;
|
|
4744
|
+
} else if (scope === "latest" && turnNums.length >= 2) {
|
|
4745
|
+
prevTurn = turnNums[turnNums.length - 2];
|
|
4746
|
+
}
|
|
4747
|
+
const statsBar =
|
|
4748
|
+
cfg.showStats !== false
|
|
4749
|
+
? h(StatsBar, {
|
|
4750
|
+
stats: viewStats,
|
|
4751
|
+
t,
|
|
4752
|
+
canTimeline,
|
|
4753
|
+
timelineOpen,
|
|
4754
|
+
scrubbing: asOf !== null,
|
|
4755
|
+
prevJump: asOf !== null && asOf <= summary.turnStart ? prevTurn : null,
|
|
4756
|
+
onPrevTurn: () => {
|
|
4757
|
+
setSelection(`turn:${prevTurn}`);
|
|
4758
|
+
setAsOf(null);
|
|
4759
|
+
setTimelineOpen(true);
|
|
4760
|
+
},
|
|
4761
|
+
onToggleTimeline: () => {
|
|
4762
|
+
setTimelineOpen((open) => {
|
|
4763
|
+
if (open) setAsOf(null);
|
|
4764
|
+
return !open;
|
|
4765
|
+
});
|
|
4766
|
+
},
|
|
4767
|
+
})
|
|
4768
|
+
: null;
|
|
4769
|
+
const timelineTotal = canTimeline ? summary.turnEnd - summary.turnStart : 0;
|
|
4770
|
+
const timelineValue = asOf === null ? summary.turnEnd : Math.min(Math.max(asOf, summary.turnStart), summary.turnEnd);
|
|
4771
|
+
const timelinePct = timelineTotal > 0 ? ((timelineValue - summary.turnStart) / timelineTotal) * 100 : 100;
|
|
4772
|
+
const timelineBubble = asOf === null ? t("timeline.live") : `${formatElapsed(asOf - summary.turnStart)} · ${formatTime(asOf)}`;
|
|
4773
|
+
const timelineRow =
|
|
4774
|
+
timelineOpen && canTimeline
|
|
4775
|
+
? h(
|
|
4776
|
+
"div",
|
|
4777
|
+
{ className: "dsov-timeline" },
|
|
4778
|
+
/* fixed-width label: the scope's total duration never changes while scrubbing */
|
|
4779
|
+
h("span", { className: "dsov-timelineLabel" }, t("stats.duration", { v: formatDuration(timelineTotal) })),
|
|
4780
|
+
h(
|
|
4781
|
+
"div",
|
|
4782
|
+
{ className: "dsov-timelineTrack" },
|
|
4783
|
+
h("input", {
|
|
4784
|
+
type: "range",
|
|
4785
|
+
className: "dsov-timelineSlider",
|
|
4786
|
+
min: summary.turnStart,
|
|
4787
|
+
max: summary.turnEnd,
|
|
4788
|
+
step: Math.max(1, Math.floor(timelineTotal / 1000)),
|
|
4789
|
+
value: timelineValue,
|
|
4790
|
+
title: timelineBubble,
|
|
4791
|
+
onChange: (event) => {
|
|
4792
|
+
const next = Number(event.target.value);
|
|
4793
|
+
setAsOf(next >= summary.turnEnd ? null : next);
|
|
4794
|
+
},
|
|
4795
|
+
}),
|
|
4796
|
+
h("span", { className: "dsov-timelineBubble", style: { left: `${timelinePct}%` } }, timelineBubble)
|
|
4797
|
+
),
|
|
4798
|
+
h(
|
|
4799
|
+
"button",
|
|
4800
|
+
{ type: "button", className: "dsov-moreSteps", title: t("timeline.reset"), disabled: asOf === null, onClick: () => setAsOf(null) },
|
|
4801
|
+
t("timeline.reset")
|
|
4802
|
+
),
|
|
4803
|
+
h(
|
|
4804
|
+
"button",
|
|
4805
|
+
{
|
|
4806
|
+
type: "button",
|
|
4807
|
+
className: "dsov-moreSteps",
|
|
4808
|
+
title: t("timeline.close"),
|
|
4809
|
+
"aria-label": t("timeline.close"),
|
|
4810
|
+
onClick: () => {
|
|
4811
|
+
setTimelineOpen(false);
|
|
4812
|
+
setAsOf(null);
|
|
4813
|
+
},
|
|
4814
|
+
},
|
|
4815
|
+
"\u00d7"
|
|
4816
|
+
)
|
|
4817
|
+
)
|
|
4818
|
+
: null;
|
|
4819
|
+
const hasStats = cfg.showStats !== false && isRecord(summary.stats) && (summary.stats.durationMs > 0 || summary.stats.toolCalls > 0);
|
|
4820
|
+
const barPresent = variant === "panel" ? scopeSelect !== null || hasStats : !(scopeSelect === null && cfg.placement === "conversation" && !hasStats);
|
|
4821
|
+
/* toggle data-stuck on the sticky wrapper: background only shows while pinned */
|
|
4822
|
+
React.useEffect(() => {
|
|
4823
|
+
const root = rootRef.current;
|
|
4824
|
+
if (root === null || typeof window === "undefined" || typeof getComputedStyle !== "function") return undefined;
|
|
4825
|
+
const wrapper = root.querySelector('[data-sticky-bar="true"]');
|
|
4826
|
+
if (wrapper === null) return undefined;
|
|
4827
|
+
let scroller = root;
|
|
4828
|
+
while (scroller !== null && scroller !== document.body && scroller !== document.documentElement) {
|
|
4829
|
+
const oy = getComputedStyle(scroller).overflowY;
|
|
4830
|
+
if (oy === "auto" || oy === "scroll" || oy === "overlay") break;
|
|
4831
|
+
/* an overflow:hidden ancestor only counts when it actually scrolls; otherwise the
|
|
4832
|
+
search continues to the real scroller (the conversation tab has hidden wrappers) */
|
|
4833
|
+
if (oy === "hidden" && scroller.scrollHeight > scroller.clientHeight + 1) break;
|
|
4834
|
+
scroller = scroller.parentElement;
|
|
4835
|
+
}
|
|
4836
|
+
const target = scroller !== null && scroller !== document.body && scroller !== document.documentElement ? scroller : null;
|
|
4837
|
+
/* Transform-based pinning: CSS sticky binds to the nearest scrollport, which an
|
|
4838
|
+
overflow:hidden wrapper can hijack, leaving the bar to slide under the tab bar. */
|
|
4839
|
+
let lastDelta = 0;
|
|
4840
|
+
const update = () => {
|
|
4841
|
+
const frameTop = target === null ? 0 : target.getBoundingClientRect().top;
|
|
4842
|
+
const scrolled = target === null ? window.scrollY : target.scrollTop;
|
|
4843
|
+
const natural = wrapper.getBoundingClientRect().top - lastDelta - frameTop + scrolled;
|
|
4844
|
+
const delta = Math.max(0, scrolled - natural);
|
|
4845
|
+
if (delta !== lastDelta) {
|
|
4846
|
+
wrapper.style.transform = delta > 0 ? `translateY(${delta}px)` : "";
|
|
4847
|
+
lastDelta = delta;
|
|
4848
|
+
}
|
|
4849
|
+
const flag = delta > 0.5 ? "true" : "false";
|
|
4850
|
+
if (wrapper.getAttribute("data-stuck") !== flag) wrapper.setAttribute("data-stuck", flag);
|
|
4851
|
+
};
|
|
4852
|
+
update();
|
|
4853
|
+
const host = target === null ? window : target;
|
|
4854
|
+
host.addEventListener("scroll", update, { passive: true });
|
|
4855
|
+
window.addEventListener("resize", update);
|
|
4856
|
+
return () => {
|
|
4857
|
+
host.removeEventListener("scroll", update);
|
|
4858
|
+
window.removeEventListener("resize", update);
|
|
4859
|
+
wrapper.style.transform = "";
|
|
4860
|
+
};
|
|
4861
|
+
}, [variant, barPresent]);
|
|
3596
4862
|
return h(
|
|
3597
4863
|
"div",
|
|
3598
|
-
{ className: rootClass, "data-conversation-overview": variant === "panel" ? "panel" : "view" },
|
|
4864
|
+
{ className: rootClass, ref: rootRef, "data-conversation-overview": variant === "panel" ? "panel" : "view" },
|
|
3599
4865
|
variant === "panel"
|
|
3600
4866
|
? scopeSelect !== null || hasStats
|
|
3601
|
-
? h(
|
|
4867
|
+
? h(
|
|
4868
|
+
"div",
|
|
4869
|
+
{ className: "dsov-turnbarWrap", "data-sticky-bar": "true", "data-stuck": "false" },
|
|
4870
|
+
h(
|
|
4871
|
+
"div",
|
|
4872
|
+
{ className: "dsov-turnbar" },
|
|
4873
|
+
statsBar !== null ? h("div", { className: "dsov-turnbarScroll" }, statsBar) : null,
|
|
4874
|
+
scopeSelect !== null ? h("div", { className: "dsov-turnbarFixed" }, scopeSelect) : null
|
|
4875
|
+
),
|
|
4876
|
+
timelineRow
|
|
4877
|
+
)
|
|
3602
4878
|
: null
|
|
3603
4879
|
: scopeSelect === null && cfg.placement === "conversation" && !hasStats
|
|
3604
4880
|
? null
|
|
3605
4881
|
: h(
|
|
3606
4882
|
"div",
|
|
3607
|
-
{ className: "dsov-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
? null
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
4883
|
+
{ className: "dsov-toolbarWrap", "data-sticky-bar": "true", "data-stuck": "false" },
|
|
4884
|
+
h(
|
|
4885
|
+
"div",
|
|
4886
|
+
{ className: "dsov-toolbar" },
|
|
4887
|
+
statsBar !== null ? h("div", { className: "dsov-toolbarScroll" }, statsBar) : null,
|
|
4888
|
+
h(
|
|
4889
|
+
"div",
|
|
4890
|
+
{ className: "dsov-toolbarFixed" },
|
|
4891
|
+
scopeSelect,
|
|
4892
|
+
cfg.placement === "conversation"
|
|
4893
|
+
? null
|
|
4894
|
+
: h(
|
|
4895
|
+
"button",
|
|
4896
|
+
{ type: "button", className: "dsov-iconBtn", onClick: openOverviewTab, title: t("action.openInSidebar") },
|
|
4897
|
+
h(IconOpenSidebar, { size: 14 }),
|
|
4898
|
+
t("action.openInSidebar")
|
|
4899
|
+
)
|
|
4900
|
+
)
|
|
4901
|
+
),
|
|
4902
|
+
timelineRow
|
|
3618
4903
|
),
|
|
3619
4904
|
summary.hasMore ? h("div", { className: "dsov-loading" }, t("state.loadingHistory")) : null,
|
|
3620
|
-
cfg.showProgress ? h(ProgressSection, { summary, goal, t, waiting: sessionWaiting }) : null,
|
|
3621
|
-
cfg.showArtifacts ? h(ArtifactsSection, { summary, t, binding, stats }) : null,
|
|
3622
|
-
cfg.showReferences ? h(ReferencesSection, { summary, t, sessionId }) : null
|
|
4905
|
+
cfg.showProgress ? h(ProgressSection, { summary, goal, t, waiting: sessionWaiting && asOf === null, cfg, asOf }) : null,
|
|
4906
|
+
cfg.showArtifacts ? h(ArtifactsSection, { summary, t, binding, stats, asOf }) : null,
|
|
4907
|
+
cfg.showReferences ? h(ReferencesSection, { summary, t, sessionId, asOf }) : null
|
|
3623
4908
|
);
|
|
3624
4909
|
}
|
|
3625
4910
|
|
|
3626
4911
|
class OverviewBoundary extends React.Component {
|
|
3627
4912
|
constructor(props) {
|
|
3628
4913
|
super(props);
|
|
3629
|
-
this.state = { failed: false };
|
|
4914
|
+
this.state = { failed: false, message: "" };
|
|
4915
|
+
}
|
|
4916
|
+
static getDerivedStateFromError(error) {
|
|
4917
|
+
return { failed: true, message: error && typeof error.message === "string" ? error.message : String(error) };
|
|
3630
4918
|
}
|
|
3631
|
-
|
|
3632
|
-
|
|
4919
|
+
componentDidCatch(error, info) {
|
|
4920
|
+
/* surface the real cause: in the console and inside the fallback panel */
|
|
4921
|
+
try {
|
|
4922
|
+
console.error("[dsh-conversation-overview] overview render failed:", error, info && info.componentStack);
|
|
4923
|
+
} catch {
|
|
4924
|
+
/* console is best-effort */
|
|
4925
|
+
}
|
|
3633
4926
|
}
|
|
3634
4927
|
render() {
|
|
3635
4928
|
if (this.state.failed) {
|
|
3636
|
-
return h(
|
|
4929
|
+
return h(
|
|
4930
|
+
"div",
|
|
4931
|
+
{ className: this.props.variant === "panel" ? "dsov-panel" : "dsov-view" },
|
|
4932
|
+
h("div", { className: "dsov-empty" }, translate("state.error")),
|
|
4933
|
+
this.state.message !== "" ? h("pre", { className: "dsov-renderErr" }, this.state.message) : null
|
|
4934
|
+
);
|
|
3637
4935
|
}
|
|
3638
4936
|
return this.props.children;
|
|
3639
4937
|
}
|
|
@@ -3803,7 +5101,8 @@ function FileTypeIcon(props) {
|
|
|
3803
5101
|
const FRONTMATTER_TITLE_KEYS = ["title", "name"];
|
|
3804
5102
|
const FRONTMATTER_TAG_KEYS = ["keywords", "tags"];
|
|
3805
5103
|
const FRONTMATTER_SCENARIO_KEYS = ["usage_scenario", "when_to_use"];
|
|
3806
|
-
const
|
|
5104
|
+
const FRONTMATTER_SOURCE_KEYS = ["source", "sources"];
|
|
5105
|
+
const FRONTMATTER_SKIP_KEYS = new Set([...FRONTMATTER_TITLE_KEYS, ...FRONTMATTER_TAG_KEYS, ...FRONTMATTER_SCENARIO_KEYS, ...FRONTMATTER_SOURCE_KEYS, "description"]);
|
|
3807
5106
|
|
|
3808
5107
|
function frontmatterTitle(fields) {
|
|
3809
5108
|
for (const key of FRONTMATTER_TITLE_KEYS) {
|
|
@@ -3822,6 +5121,26 @@ function FileTypeIcon(props) {
|
|
|
3822
5121
|
if (description && description.value !== "") {
|
|
3823
5122
|
blocks.push(h("p", { className: "dsov-vikingDesc", key: "description" }, description.value));
|
|
3824
5123
|
}
|
|
5124
|
+
const sourceField = fields.find((field) => FRONTMATTER_SOURCE_KEYS.indexOf(field.key) !== -1 && (field.value !== "" || field.items.length > 0));
|
|
5125
|
+
if (sourceField) {
|
|
5126
|
+
const values = sourceField.items.length > 0 ? sourceField.items : [sourceField.value];
|
|
5127
|
+
blocks.push(
|
|
5128
|
+
h(
|
|
5129
|
+
"div",
|
|
5130
|
+
{ className: "dsov-vikingField", key: "source" },
|
|
5131
|
+
h("div", { className: "dsov-vikingFieldLabel" }, t("viking.field.source")),
|
|
5132
|
+
h(
|
|
5133
|
+
"div",
|
|
5134
|
+
{ className: "dsov-vikingSource" },
|
|
5135
|
+
values.map((value, index) =>
|
|
5136
|
+
/^https?:\/\//i.test(value)
|
|
5137
|
+
? h("a", { key: `${value}#${index}`, className: "dsov-vikingSourceLink", href: value, target: "_blank", rel: "noreferrer" }, value)
|
|
5138
|
+
: h("span", { key: `${value}#${index}`, className: "dsov-vikingFieldValue" }, value)
|
|
5139
|
+
)
|
|
5140
|
+
)
|
|
5141
|
+
)
|
|
5142
|
+
);
|
|
5143
|
+
}
|
|
3825
5144
|
const scenarios = fields.filter((field) => FRONTMATTER_SCENARIO_KEYS.indexOf(field.key) !== -1 && field.items.length > 0);
|
|
3826
5145
|
if (scenarios.length > 0) {
|
|
3827
5146
|
blocks.push(
|
|
@@ -4042,6 +5361,41 @@ function FileTypeIcon(props) {
|
|
|
4042
5361
|
);
|
|
4043
5362
|
}
|
|
4044
5363
|
|
|
5364
|
+
/* Multi-line text row committing on blur and on unmount (prompt template). */
|
|
5365
|
+
function SettingTextarea({ label, value, placeholder, onCommit }) {
|
|
5366
|
+
const [draft, setDraft] = React.useState(value);
|
|
5367
|
+
const [focused, setFocused] = React.useState(false);
|
|
5368
|
+
const pending = React.useRef({ draft: value, value, commit: onCommit });
|
|
5369
|
+
pending.current = { draft, value, commit: onCommit };
|
|
5370
|
+
React.useEffect(
|
|
5371
|
+
() => () => {
|
|
5372
|
+
const state = pending.current;
|
|
5373
|
+
if (typeof state.draft === "string" && state.draft !== state.value) state.commit(state.draft);
|
|
5374
|
+
},
|
|
5375
|
+
[]
|
|
5376
|
+
);
|
|
5377
|
+
return h(
|
|
5378
|
+
"div",
|
|
5379
|
+
{ className: "dsov-set-row dsov-set-arearow" },
|
|
5380
|
+
h("span", { className: "dsov-set-label" }, label),
|
|
5381
|
+
h("textarea", {
|
|
5382
|
+
className: "dsov-set-textarea",
|
|
5383
|
+
rows: 7,
|
|
5384
|
+
value: focused === true ? draft : value,
|
|
5385
|
+
placeholder,
|
|
5386
|
+
onFocus: () => {
|
|
5387
|
+
setDraft(value);
|
|
5388
|
+
setFocused(true);
|
|
5389
|
+
},
|
|
5390
|
+
onBlur: () => {
|
|
5391
|
+
setFocused(false);
|
|
5392
|
+
if (typeof draft === "string" && draft !== value) onCommit(draft);
|
|
5393
|
+
},
|
|
5394
|
+
onChange: (event) => setDraft(event.target.value),
|
|
5395
|
+
})
|
|
5396
|
+
);
|
|
5397
|
+
}
|
|
5398
|
+
|
|
4045
5399
|
function SettingsForm(props) {
|
|
4046
5400
|
const t = typeof props.t === "function" ? props.t : translate;
|
|
4047
5401
|
const hookForm = useSettingsForm();
|
|
@@ -4070,6 +5424,10 @@ function FileTypeIcon(props) {
|
|
|
4070
5424
|
{ id: "sidebar", label: t("settings.placement.sidebar") },
|
|
4071
5425
|
{ id: "both", label: t("settings.placement.both") },
|
|
4072
5426
|
];
|
|
5427
|
+
const timeOptions = [
|
|
5428
|
+
{ id: "clock", label: t("settings.timeMode.clock") },
|
|
5429
|
+
{ id: "elapsed", label: t("settings.timeMode.elapsed") },
|
|
5430
|
+
];
|
|
4073
5431
|
const boolRow = (label, field) =>
|
|
4074
5432
|
h(SettingSelect, {
|
|
4075
5433
|
label,
|
|
@@ -4089,7 +5447,26 @@ function FileTypeIcon(props) {
|
|
|
4089
5447
|
t("settings.sections"),
|
|
4090
5448
|
boolRow(t("settings.showProgress"), "showProgress"),
|
|
4091
5449
|
boolRow(t("settings.showArtifacts"), "showArtifacts"),
|
|
4092
|
-
boolRow(t("settings.showReferences"), "showReferences")
|
|
5450
|
+
boolRow(t("settings.showReferences"), "showReferences"),
|
|
5451
|
+
boolRow(t("settings.showStats"), "showStats")
|
|
5452
|
+
),
|
|
5453
|
+
group(
|
|
5454
|
+
t("settings.interaction"),
|
|
5455
|
+
h(SettingSelect, { label: t("settings.timeMode"), value: cfg.timeMode, options: timeOptions, onPick: (id) => set("timeMode", id) }),
|
|
5456
|
+
note(t("settings.timeModeNote")),
|
|
5457
|
+
boolRow(t("settings.stepAnchors"), "stepAnchors"),
|
|
5458
|
+
note(t("settings.stepAnchorsNote")),
|
|
5459
|
+
boolRow(t("settings.todoPromptRow"), "todoPrompt"),
|
|
5460
|
+
cfg.todoPrompt
|
|
5461
|
+
? h(SettingTextarea, {
|
|
5462
|
+
label: t("settings.todoPromptTemplate"),
|
|
5463
|
+
value: cfg.todoPromptTemplate,
|
|
5464
|
+
placeholder: DEFAULT_TODO_TEMPLATE,
|
|
5465
|
+
onCommit: (value) => set("todoPromptTemplate", value),
|
|
5466
|
+
})
|
|
5467
|
+
: null,
|
|
5468
|
+
note(t("settings.todoPromptNote")),
|
|
5469
|
+
cfg.todoPrompt ? note(t("settings.todoPromptTemplateNote")) : null
|
|
4093
5470
|
),
|
|
4094
5471
|
group(
|
|
4095
5472
|
t("settings.quickNav"),
|
|
@@ -4123,6 +5500,20 @@ function FileTypeIcon(props) {
|
|
|
4123
5500
|
: null,
|
|
4124
5501
|
note(cfg.vikingPreview ? t("settings.vikingNote") : t("settings.vikingOffNote"))
|
|
4125
5502
|
),
|
|
5503
|
+
group(
|
|
5504
|
+
t("settings.feedbackGroup"),
|
|
5505
|
+
h(VersionRow, { t }),
|
|
5506
|
+
h(
|
|
5507
|
+
"a",
|
|
5508
|
+
{ className: "dsov-set-link", href: FEEDBACK_URL, target: "_blank", rel: "noreferrer", title: FEEDBACK_URL },
|
|
5509
|
+
h("span", null, t("settings.feedback")),
|
|
5510
|
+
h(
|
|
5511
|
+
"svg",
|
|
5512
|
+
{ width: 11, height: 11, viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true" },
|
|
5513
|
+
h("path", { d: "M4.6 2.4H2.8c-.7 0-1.3.6-1.3 1.3v5.5c0 .7.6 1.3 1.3 1.3h5.5c.7 0 1.3-.6 1.3-1.3V7.4M7.2 1.5h3.3v3.3M6.3 5.7 10.2 1.8", stroke: "currentColor", strokeWidth: 1.1, strokeLinecap: "round", strokeLinejoin: "round" })
|
|
5514
|
+
)
|
|
5515
|
+
)
|
|
5516
|
+
),
|
|
4126
5517
|
formUsable ? note(t("settings.hint")) : null
|
|
4127
5518
|
);
|
|
4128
5519
|
}
|
|
@@ -4134,8 +5525,15 @@ function FileTypeIcon(props) {
|
|
|
4134
5525
|
const CSS = `
|
|
4135
5526
|
.dsov-view{box-sizing:border-box;width:100%;max-width:880px;margin:0 auto;padding:20px 28px 64px;display:flex;flex-direction:column;gap:14px}
|
|
4136
5527
|
.dsov-panel{box-sizing:border-box;height:100%;min-height:0;overflow-y:auto;padding:14px 16px 28px;display:flex;flex-direction:column;gap:12px}
|
|
4137
|
-
.dsov-
|
|
4138
|
-
.dsov-
|
|
5528
|
+
.dsov-panel,.dsov-viking,.dsov-secBody,.dsov-typeMenu,.dsov-md-pre,.dsov-turnbarScroll,.dsov-toolbarScroll{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--dsw-alias-label-secondary) 35%,transparent) transparent}
|
|
5529
|
+
.dsov-panel::-webkit-scrollbar,.dsov-viking::-webkit-scrollbar,.dsov-secBody::-webkit-scrollbar,.dsov-typeMenu::-webkit-scrollbar,.dsov-md-pre::-webkit-scrollbar,.dsov-turnbarScroll::-webkit-scrollbar,.dsov-toolbarScroll::-webkit-scrollbar{width:6px;height:5px}
|
|
5530
|
+
.dsov-panel::-webkit-scrollbar-thumb,.dsov-viking::-webkit-scrollbar-thumb,.dsov-secBody::-webkit-scrollbar-thumb,.dsov-typeMenu::-webkit-scrollbar-thumb,.dsov-md-pre::-webkit-scrollbar-thumb,.dsov-turnbarScroll::-webkit-scrollbar-thumb,.dsov-toolbarScroll::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--dsw-alias-label-secondary) 35%,transparent);border-radius:3px}
|
|
5531
|
+
.dsov-panel::-webkit-scrollbar-track,.dsov-viking::-webkit-scrollbar-track,.dsov-secBody::-webkit-scrollbar-track,.dsov-typeMenu::-webkit-scrollbar-track,.dsov-md-pre::-webkit-scrollbar-track,.dsov-turnbarScroll::-webkit-scrollbar-track,.dsov-toolbarScroll::-webkit-scrollbar-track{background:transparent}
|
|
5532
|
+
.dsov-toolbar{display:flex;align-items:center;gap:8px}
|
|
5533
|
+
.dsov-toolbarScroll{flex:1 1 auto;min-width:0;display:flex;align-items:center;overflow-x:auto;overflow-y:hidden;padding-bottom:5px}
|
|
5534
|
+
.dsov-toolbarFixed{flex:none;display:flex;align-items:center;gap:8px;margin-left:auto;padding-bottom:5px}
|
|
5535
|
+
.dsov-iconBtn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border-radius:8px;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;white-space:nowrap;flex:none}
|
|
5536
|
+
.dsov-turnSelect{flex:none}
|
|
4139
5537
|
.dsov-iconBtn:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 7%,transparent);color:var(--dsw-alias-label-primary)}
|
|
4140
5538
|
.dsov-section{background:var(--dsw-alias-bg-layer-1);border:.5px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px}
|
|
4141
5539
|
.dsov-sectionHead{display:flex;align-items:center;gap:6px}
|
|
@@ -4143,6 +5541,9 @@ function FileTypeIcon(props) {
|
|
|
4143
5541
|
.dsov-sectionToggle:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)}
|
|
4144
5542
|
.dsov-chevron{flex:none;display:inline-flex;color:var(--dsw-alias-label-secondary);transition:transform .15s ease}
|
|
4145
5543
|
.dsov-chevron[data-open=true]{transform:rotate(90deg)}
|
|
5544
|
+
.dsov-moreSteps{appearance:none;background:transparent;border:none;cursor:pointer;display:inline-flex;align-items:center;gap:4px;flex:none;margin-left:auto;padding:1px 6px;border-radius:8px;font-size:10.5px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
|
|
5545
|
+
.dsov-moreSteps:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
5546
|
+
.dsov-moreIcon{flex:none;display:inline-flex;opacity:.75}
|
|
4146
5547
|
.dsov-sectionIcon{flex:none;display:inline-flex;color:var(--dsw-alias-label-secondary)}
|
|
4147
5548
|
.dsov-sectionTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
4148
5549
|
.dsov-count{flex:none;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-radius:999px;padding:0 8px;font-variant-numeric:tabular-nums}
|
|
@@ -4178,7 +5579,7 @@ function FileTypeIcon(props) {
|
|
|
4178
5579
|
.dsov-todoText{flex:1;min-width:0;overflow-wrap:anywhere}
|
|
4179
5580
|
.dsov-todo[data-status=completed] .dsov-todoText{color:var(--dsw-alias-label-secondary)}
|
|
4180
5581
|
.dsov-todoStatus{flex:none;font-size:11px;line-height:20px;color:var(--dsw-alias-label-secondary)}
|
|
4181
|
-
.dsov-stepsHead{font-size:10.5px;line-height:16px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));letter-spacing:.02em;padding:1px 2px 4px}
|
|
5582
|
+
.dsov-stepsHead{display:flex;align-items:center;gap:8px;font-size:10.5px;line-height:16px;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));letter-spacing:.02em;padding:1px 2px 4px}
|
|
4182
5583
|
.dsov-steps{position:relative;display:flex;flex-direction:column;gap:3px}
|
|
4183
5584
|
.dsov-step{display:flex;align-items:center;gap:6px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary);padding:3px 6px;min-width:0;border-radius:8px;cursor:pointer;transition:background-color .18s ease}
|
|
4184
5585
|
.dsov-step:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 5%,transparent)}
|
|
@@ -4192,6 +5593,24 @@ function FileTypeIcon(props) {
|
|
|
4192
5593
|
.dsov-step[data-waiting=true]{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#ff9f0a) 9%,transparent)}
|
|
4193
5594
|
.dsov-step[data-waiting=true] .dsov-stepName{color:var(--dsw-alias-state-warn-primary,#ff9f0a)}
|
|
4194
5595
|
.dsov-step[data-waiting=true] .dsov-todoIcon[data-status=waiting]{color:var(--dsw-alias-state-warn-primary,#ff9f0a)}
|
|
5596
|
+
.dsov-agentIcon{color:var(--dsw-alias-brand-primary)}
|
|
5597
|
+
.dsov-memberName{flex:none;font-weight:500}
|
|
5598
|
+
.dsov-memberDesc{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
5599
|
+
.dsov-todo .dsov-chevron{margin-top:4px}
|
|
5600
|
+
.dsov-todoIcon[data-status=failed]{color:var(--dsw-alias-state-error-primary)}
|
|
5601
|
+
.dsov-taskSubject{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
5602
|
+
.dsov-todo[data-depth="1"]{position:relative;margin-left:26px}
|
|
5603
|
+
.dsov-todo[data-depth="2"]{position:relative;margin-left:46px}
|
|
5604
|
+
.dsov-todo[data-depth]::before{content:"";position:absolute;left:-15px;top:-4px;bottom:50%;width:9px;border-left:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:6px}
|
|
5605
|
+
.dsov-member[data-expandable=false]{cursor:default}
|
|
5606
|
+
.dsov-member[data-expandable=false]:hover{background:transparent}
|
|
5607
|
+
.dsov-stepChevron{flex:none;margin-right:-2px}
|
|
5608
|
+
.dsov-compactIcon{color:var(--dsw-alias-label-secondary)}
|
|
5609
|
+
.dsov-todoIconSlot{position:relative;flex:none;display:inline-flex;margin-top:2px}
|
|
5610
|
+
.dsov-todoIconSlot .dsov-todoIcon{margin-top:0}
|
|
5611
|
+
.dsov-todoChevron{position:absolute;inset:0;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .12s ease}
|
|
5612
|
+
.dsov-todo[data-expandable=true]:hover .dsov-todoChevron,.dsov-todo[data-expandable=true][data-open=true] .dsov-todoChevron{opacity:1;pointer-events:auto}
|
|
5613
|
+
.dsov-todo[data-expandable=true]:hover .dsov-todoIconSlot .dsov-todoIcon,.dsov-todo[data-expandable=true][data-open=true] .dsov-todoIconSlot .dsov-todoIcon{visibility:hidden}
|
|
4195
5614
|
.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}
|
|
4196
5615
|
.dsov-row:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)}
|
|
4197
5616
|
.dsov-rowStatic{cursor:default}
|
|
@@ -4214,13 +5633,15 @@ function FileTypeIcon(props) {
|
|
|
4214
5633
|
.dsov-settingsPage{display:flex;flex-direction:column;gap:14px;padding:6px 2px;max-width:560px}
|
|
4215
5634
|
.dsov-settingsHeading{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}
|
|
4216
5635
|
.dsov-set-prefs{padding:4px 0}
|
|
4217
|
-
.dsov-set-group{border-top:.5px solid var(--dsw-alias-border-l2);margin-top:
|
|
5636
|
+
.dsov-set-group{border-top:.5px solid var(--dsw-alias-border-l2);margin-top:20px;padding-top:16px}
|
|
4218
5637
|
.dsov-set-group:first-child{border-top:none;margin-top:0;padding-top:0}
|
|
4219
|
-
.dsov-set-
|
|
4220
|
-
.dsov-set-
|
|
4221
|
-
.dsov-set-
|
|
5638
|
+
.dsov-set-link{display:inline-flex;align-items:center;gap:6px;padding:6px 0;font-size:13px;line-height:20px;color:var(--dsw-alias-brand-primary);text-decoration:none;width:fit-content}
|
|
5639
|
+
.dsov-set-link:hover{text-decoration:underline}
|
|
5640
|
+
.dsov-set-group-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:18px;letter-spacing:.02em;margin-bottom:10px}
|
|
5641
|
+
.dsov-set-row{align-items:center;gap:10px;padding:8px 0;display:flex}
|
|
5642
|
+
.dsov-set-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:400;line-height:20px}
|
|
4222
5643
|
.dsov-set-control{position:relative;flex:none;display:inline-flex}
|
|
4223
|
-
.dsov-set-select{background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));height:
|
|
5644
|
+
.dsov-set-select{background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));height:32px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:none;border-radius:16px;align-items:center;gap:10px;padding:0 12px;font-size:13px;line-height:20px;display:inline-flex}
|
|
4224
5645
|
.dsov-set-select:hover{background:var(--dsw-alias-interactive-bg-hover,color-mix(in srgb,var(--dsw-alias-label-primary) 8%,transparent))}
|
|
4225
5646
|
.dsov-set-selectLabel{max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
4226
5647
|
.dsov-set-caret{flex:none;display:inline-flex;color:var(--dsw-alias-label-secondary);transform:rotate(90deg);transition:transform .15s ease}
|
|
@@ -4229,11 +5650,16 @@ function FileTypeIcon(props) {
|
|
|
4229
5650
|
.dsov-set-menuItem{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border:none;border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;text-align:left;cursor:pointer}
|
|
4230
5651
|
.dsov-set-menuItem:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 7%,transparent)}
|
|
4231
5652
|
.dsov-set-menuItem[data-selected=true]{color:var(--dsw-alias-brand-primary);font-weight:500}
|
|
4232
|
-
.dsov-set-note{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:
|
|
4233
|
-
.dsov-set-note+.dsov-set-note{margin-top:
|
|
5653
|
+
.dsov-set-note{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:4px 0 0;font-size:12px;line-height:1.55}
|
|
5654
|
+
.dsov-set-note+.dsov-set-note{margin-top:2px}
|
|
4234
5655
|
.dsov-set-textrow{flex-direction:column;align-items:stretch;gap:6px}
|
|
4235
5656
|
.dsov-set-input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4,var(--dsw-alias-border-l2));border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));width:100%;color:var(--dsw-alias-label-primary);font:inherit;padding:6px 12px;font-size:13px;line-height:1.5}
|
|
4236
5657
|
.dsov-set-input:focus{outline:none;border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 55%,transparent)}
|
|
5658
|
+
.dsov-set-arearow{display:block}
|
|
5659
|
+
.dsov-set-arearow .dsov-set-label{display:block;margin-bottom:6px}
|
|
5660
|
+
.dsov-set-textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4,var(--dsw-alias-border-l2));border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));width:100%;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;line-height:1.55;padding:8px 12px;resize:vertical}
|
|
5661
|
+
.dsov-set-textarea:focus{outline:none;border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 55%,transparent)}
|
|
5662
|
+
.dsov-set-textarea::placeholder{color:var(--dsw-alias-label-quaternary,var(--dsw-alias-label-secondary));opacity:.75}
|
|
4237
5663
|
html[data-dsov-quicknav="on"] nav:has(button[data-index][aria-label]),html[data-dsov-quicknav="on"] nav[class*="_frame"]{display:block!important}
|
|
4238
5664
|
html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data-dsov-quicknav="off"] nav[class*="_frame"]{display:none!important}
|
|
4239
5665
|
.dsov-viking{box-sizing:border-box;height:100%;min-height:0;overflow-y:auto;padding:14px 16px 28px;display:flex;flex-direction:column;gap:10px}
|
|
@@ -4267,10 +5693,42 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4267
5693
|
.dsov-filterBtn{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 8px;border:.5px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:10.5px;line-height:1;cursor:pointer;white-space:nowrap}
|
|
4268
5694
|
.dsov-filterBtn:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)}
|
|
4269
5695
|
.dsov-filterBtn[data-active=true]{color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,transparent)}
|
|
4270
|
-
.dsov-turnbar{display:flex;justify-content:flex-
|
|
4271
|
-
.dsov-
|
|
5696
|
+
.dsov-turnbar{display:flex;justify-content:flex-start;align-items:center;gap:8px;padding:0}
|
|
5697
|
+
.dsov-turnbarScroll{flex:1 1 auto;min-width:0;display:flex;align-items:center;overflow-x:auto;overflow-y:hidden;padding-bottom:5px}
|
|
5698
|
+
.dsov-turnbarFixed{flex:none;display:flex;align-items:center;margin-left:auto;padding-bottom:5px}
|
|
5699
|
+
.dsov-panel .dsov-turnbarWrap{position:relative;z-index:5;margin:-14px -16px 0;padding:8px 16px 3px;background:transparent;transition:background-color .18s ease,box-shadow .18s ease}
|
|
5700
|
+
.dsov-panel .dsov-turnbarWrap[data-stuck=true]{background:var(--dsw-specific-sidebar-fill,var(--dsw-alias-bg-base));box-shadow:0 6px 10px -8px color-mix(in srgb,var(--dsw-alias-label-primary) 25%,transparent)}
|
|
5701
|
+
.dsov-view .dsov-toolbarWrap{position:relative;z-index:4;margin:-20px -28px 0;padding:10px 28px 5px;background:transparent;transition:background-color .18s ease,box-shadow .18s ease}
|
|
5702
|
+
.dsov-view .dsov-toolbarWrap[data-stuck=true]{background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-1));backdrop-filter:var(--dsw-menu-backdrop-filter,none);box-shadow:0 6px 10px -8px color-mix(in srgb,var(--dsw-alias-label-primary) 22%,transparent)}
|
|
5703
|
+
.dsov-stats{display:flex;align-items:center;gap:5px;margin-right:auto;flex:none}
|
|
4272
5704
|
.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}
|
|
4273
5705
|
.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)}
|
|
5706
|
+
.dsov-statBtn{cursor:pointer;border:none;margin:0;font-family:inherit;transition:color .15s ease,background-color .15s ease}
|
|
5707
|
+
.dsov-statBtn:hover{color:var(--dsw-alias-label-primary)}
|
|
5708
|
+
.dsov-statBtn[data-active=true]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,transparent)}
|
|
5709
|
+
.dsov-statBtn[data-scrub=true]{color:var(--dsw-alias-brand-primary)}
|
|
5710
|
+
.dsov-timeline{display:flex;align-items:center;gap:8px;margin-top:6px}
|
|
5711
|
+
.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}
|
|
5712
|
+
.dsov-prevTurn{color:var(--dsw-alias-brand-primary,#247bbf)}
|
|
5713
|
+
.dsov-timelineTrack{position:relative;flex:1 1 auto;min-width:0;display:flex;align-items:center}
|
|
5714
|
+
.dsov-timelineSlider{flex:1 1 auto;min-width:0;height:18px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary)}
|
|
5715
|
+
.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}
|
|
5716
|
+
.dsov-timelineTrack:hover .dsov-timelineBubble,.dsov-timelineSlider:focus-visible+.dsov-timelineBubble{opacity:1}
|
|
5717
|
+
.dsov-moreSteps[disabled]{opacity:.45;cursor:default;pointer-events:none}
|
|
5718
|
+
.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)}
|
|
5719
|
+
.dsov-newVer:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,transparent)}
|
|
5720
|
+
.dsov-set-ver{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:3px 0}
|
|
5721
|
+
.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)}
|
|
5722
|
+
.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}
|
|
5723
|
+
.dsov-fileMenuItem:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
5724
|
+
.dsov-fileMenuIcon{flex:none;width:16px;height:16px;border-radius:4px;object-fit:contain;background:var(--dsw-alias-bg-layer-2)}
|
|
5725
|
+
.dsov-fileMenuName{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
5726
|
+
.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)}
|
|
5727
|
+
.dsov-fileMenuHead{padding:5px 8px 3px;font-size:10.5px;color:var(--dsw-alias-label-secondary)}
|
|
5728
|
+
.dsov-fileMenuSep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1)}
|
|
5729
|
+
.dsov-fileMenuHint{padding:5px 8px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
|
|
5730
|
+
.dsov-fileMenuErr{padding:5px 8px;font-size:11px;color:var(--dsw-alias-state-error-primary,#ff453a)}
|
|
5731
|
+
.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}
|
|
4274
5732
|
.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}
|
|
4275
5733
|
.dsov-turnSelect:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3,var(--dsw-alias-border-l2))}
|
|
4276
5734
|
@keyframes dsov-spin{to{transform:rotate(360deg)}}
|
|
@@ -4287,6 +5745,12 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4287
5745
|
.dsov-segBtn{border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;line-height:18px;padding:0 8px;border-radius:6px;cursor:pointer;white-space:nowrap}
|
|
4288
5746
|
.dsov-segBtn:hover{color:var(--dsw-alias-label-primary)}
|
|
4289
5747
|
.dsov-segBtn[data-active=true]{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-weight:500;box-shadow:0 0 0 .5px var(--dsw-alias-border-l1)}
|
|
5748
|
+
.dsov-refControls .dsov-seg{display:inline-flex;align-items:stretch;gap:0;padding:0;border:.5px solid var(--dsw-alias-border-l2);border-radius:10px;background:transparent;overflow:hidden}
|
|
5749
|
+
.dsov-refControls .dsov-segBtn{height:19px;align-self:center;line-height:1;padding:0 9px;border:none;border-radius:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:10.5px}
|
|
5750
|
+
.dsov-refControls .dsov-segBtn+.dsov-segBtn{border-left:.5px solid var(--dsw-alias-border-l2)}
|
|
5751
|
+
.dsov-refControls .dsov-segBtn:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)}
|
|
5752
|
+
.dsov-refControls .dsov-segBtn[data-active=true]{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 8%,transparent);font-weight:500;box-shadow:none}
|
|
5753
|
+
.dsov-refControls .dsov-segBtn[data-active=true]:hover{background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
4290
5754
|
.dsov-vikingList{display:flex;flex-direction:column}
|
|
4291
5755
|
.dsov-vikingSize{font-size:11px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
|
|
4292
5756
|
.dsov-vikingSource{white-space:pre-wrap;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l1);border-radius:8px;padding:10px 12px;margin:0}
|
|
@@ -4298,6 +5762,9 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4298
5762
|
.dsov-vikingTags{display:flex;flex-wrap:wrap;gap:5px}
|
|
4299
5763
|
.dsov-tag{font-size:11px;line-height:19px;padding:0 8px;border-radius:999px;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) 30%,transparent)}
|
|
4300
5764
|
.dsov-vikingFieldValue{font-size:12.5px;line-height:1.6;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
|
|
5765
|
+
.dsov-vikingSource{display:flex;flex-direction:column;gap:2px}
|
|
5766
|
+
.dsov-vikingSourceLink{font-size:12.5px;line-height:1.6;color:var(--dsw-alias-brand-primary);text-decoration:none;overflow-wrap:anywhere}
|
|
5767
|
+
.dsov-vikingSourceLink:hover{text-decoration:underline}
|
|
4301
5768
|
.dsov-vikingError{display:flex;flex-direction:column;align-items:flex-start;gap:8px;font-size:13px;color:var(--dsw-alias-state-error-primary)}
|
|
4302
5769
|
.dsov-vikingErrorDetail{font-size:12px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}
|
|
4303
5770
|
.dsov-md{font-size:13.5px;line-height:1.65;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:2px}
|
|
@@ -4613,6 +6080,24 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4613
6080
|
};
|
|
4614
6081
|
}, "conversation-overview: quick nav link");
|
|
4615
6082
|
|
|
6083
|
+
ctx.effect(
|
|
6084
|
+
() =>
|
|
6085
|
+
ctx.slots.inject(
|
|
6086
|
+
"plugins.detail.badge",
|
|
6087
|
+
() =>
|
|
6088
|
+
ctx.slots.register(
|
|
6089
|
+
{
|
|
6090
|
+
name: "plugins.detail.badge",
|
|
6091
|
+
id: "dsov-new-version",
|
|
6092
|
+
order: 100,
|
|
6093
|
+
locale: NS,
|
|
6094
|
+
},
|
|
6095
|
+
NewVersionBadge
|
|
6096
|
+
)
|
|
6097
|
+
),
|
|
6098
|
+
"conversation-overview: new version badge"
|
|
6099
|
+
);
|
|
6100
|
+
|
|
4616
6101
|
ctx.effect(
|
|
4617
6102
|
() =>
|
|
4618
6103
|
ctx.slots.inject(
|
|
@@ -4658,6 +6143,10 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4658
6143
|
foldEvent,
|
|
4659
6144
|
foldEntry,
|
|
4660
6145
|
buildState,
|
|
6146
|
+
planTodoAttachments,
|
|
6147
|
+
vikingLabel,
|
|
6148
|
+
vikingRefKind,
|
|
6149
|
+
vikingIsDir,
|
|
4661
6150
|
normalizeSelection,
|
|
4662
6151
|
vikingParentUri,
|
|
4663
6152
|
rememberContextPreview,
|
|
@@ -4678,6 +6167,15 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4678
6167
|
setFormConfig,
|
|
4679
6168
|
setOverlayConfig,
|
|
4680
6169
|
clearOverlayConfig,
|
|
6170
|
+
compareVersions,
|
|
6171
|
+
ProgressSection,
|
|
6172
|
+
ArtifactsSection,
|
|
6173
|
+
ReferencesSection,
|
|
6174
|
+
StatsBar,
|
|
6175
|
+
ArtifactFileMenu,
|
|
6176
|
+
OverviewGlyph,
|
|
6177
|
+
OverviewContent,
|
|
6178
|
+
OverviewBoundary,
|
|
4681
6179
|
SettingsForm,
|
|
4682
6180
|
SettingsSection,
|
|
4683
6181
|
vikingName,
|
|
@@ -4698,6 +6196,7 @@ html[data-dsov-quicknav="off"] nav:has(button[data-index][aria-label]),html[data
|
|
|
4698
6196
|
refDisplayType,
|
|
4699
6197
|
stepDetail,
|
|
4700
6198
|
stepFull,
|
|
6199
|
+
teamStepKey,
|
|
4701
6200
|
formatDuration,
|
|
4702
6201
|
rerunPrompt,
|
|
4703
6202
|
insertIntoComposer,
|