dsh-dock 0.9.2 → 0.9.3
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 +5 -3
- package/client.js +160 -26
- package/features/animation/host.js +19 -1
- package/features/animation/view.jsx +171 -28
- package/package.json +2 -2
- package/src/client.jsx +1 -1
- package/src/host-core.js +2 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
| **用量记录** | 记录全部 LLM API 调用:秒级时间筛选、Token/费用统计(峰谷计价 + 官网价目自动同步)、分组汇总、明细检索与 CSV 导出 |
|
|
20
20
|
| **模型设置** | 编辑各 Provider 模型目录:输入类型(文本/图片 + 标注)与思考强度档位;图片能力可由你手动声明,新增 Provider 自动刷新加载,写回官方配置热生效 |
|
|
21
21
|
| **模型余额** | 展示所有模型 Provider 账户余额(5 分钟自动刷新) |
|
|
22
|
-
| **任务动画** | 19
|
|
22
|
+
| **任务动画** | 19 种任务运行动画(含星际远征、星云潮汐、曲速航道、量子雷达、星座网络、深海脉动、神经电路等)与完成/需确认通知(通知音、浏览器系统通知、钉钉/飞书群机器人推送),动画速度随任务活动联动,两组开关独立,配置全部持久化 |
|
|
23
23
|
|
|
24
24
|
| **图片理解代理** | 图片识别等(visionproxy);原图候选默认折叠展示,图片能力以你的声明为准 |
|
|
25
25
|
| **手机接力** | 手机扫码安全接入同一 DSH(反向代理 + 任务/备注同步);**局域网电脑直连(0.0.0.0)**——另起一个 0.0.0.0 绑定的完整 DSH 子实例,局域网电脑访问到的功能、交互与 127.0.0.1 完全一致(会话记录、工作区文件夹选择等) |
|
|
@@ -122,7 +122,8 @@ dsh plugin --profile web remove dsh-dock # 卸载
|
|
|
122
122
|
- 代码雨:字符列沿屏幕缓落如数据流
|
|
123
123
|
- 星野:细碎星点飘移闪烁,带弯月与流星
|
|
124
124
|
- 极光:屏幕顶部柔光带缓慢呼吸流动
|
|
125
|
-
- **完成通知**:任务结束时右上角弹出通知卡片(标题 / 模型 / 耗时 / 回合步骤 / Token / 结果摘要 / 结束原因),可分别开关「完成 / 异常」通知、选择停留时长、开启浏览器系统通知(仅页面后台时推送);**提示音** 6 种 WebAudio 合成音效可选(完成 /
|
|
125
|
+
- **完成通知**:任务结束时右上角弹出通知卡片(标题 / 模型 / 耗时 / 回合步骤 / Token / 结果摘要 / 结束原因),可分别开关「完成 / 异常」通知、选择停留时长、开启浏览器系统通知(仅页面后台时推送);**提示音** 6 种 WebAudio 合成音效可选(完成 / 异常 / 确认各配一音,可单独试听)
|
|
126
|
+
- **需确认提醒**:工具等待用户批准(如沙箱越界执行命令)时弹常驻通知卡片 + 确认音(页面后台时同步系统通知),人不在屏幕前任务也不会干等;等待 / 卡住状态卡片只对当前正在看的会话出现,停靠屏幕右上不挡正文
|
|
126
127
|
2. 任务结束动效自动消失,完成瞬间一缕流光掠过;功能坞面板打开时环境动效自动隐藏避免重叠
|
|
127
128
|
|
|
128
129
|
3. **钉钉推送**(可选):任务结束由宿主直发 markdown 消息到钉钉群机器人(浏览器关着也能推)——填入 Webhook 地址保存,「发送测试消息」验证连通;机器人安全设置选「自定义关键词」填「任务」或「dsh」;推送哪些事件跟随上方「完成 / 异常通知」开关
|
|
@@ -249,7 +250,8 @@ dsh plugin --profile web remove dsh-dock # 卸载
|
|
|
249
250
|
- 代码雨:字符列沿屏幕缓落如数据流
|
|
250
251
|
- 星野:细碎星点飘移闪烁,带弯月与流星
|
|
251
252
|
- 极光:屏幕顶部柔光带缓慢呼吸流动
|
|
252
|
-
- **完成通知**:任务结束时右上角弹出通知卡片(标题 / 模型 / 耗时 / 回合步骤 / Token / 结果摘要 / 结束原因),可分别开关「完成 / 异常」通知、选择停留时长、开启浏览器系统通知(仅页面后台时推送);**提示音** 6 种 WebAudio 合成音效可选(完成 /
|
|
253
|
+
- **完成通知**:任务结束时右上角弹出通知卡片(标题 / 模型 / 耗时 / 回合步骤 / Token / 结果摘要 / 结束原因),可分别开关「完成 / 异常」通知、选择停留时长、开启浏览器系统通知(仅页面后台时推送);**提示音** 6 种 WebAudio 合成音效可选(完成 / 异常 / 确认各配一音,可单独试听)
|
|
254
|
+
- **需确认提醒**:工具等待用户批准(如沙箱越界执行命令)时弹常驻通知卡片 + 确认音(页面后台时同步系统通知),人不在屏幕前任务也不会干等;等待 / 卡住状态卡片只对当前正在看的会话出现,停靠屏幕右上不挡正文
|
|
253
255
|
2. 任务结束动效自动消失,完成瞬间一缕流光掠过;功能坞面板打开时环境动效自动隐藏避免重叠
|
|
254
256
|
|
|
255
257
|
3. **钉钉推送**(可选):任务结束由宿主直发 markdown 消息到钉钉群机器人(浏览器关着也能推)——填入 Webhook 地址保存,「发送测试消息」验证连通;机器人安全设置选「自定义关键词」填「任务」或「dsh」;推送哪些事件跟随上方「完成 / 异常通知」开关
|
package/client.js
CHANGED
|
@@ -3876,6 +3876,13 @@ var PHASE_COLORS = { think: "#2f6fed", write: "#0d9488", code: "#b45309", search
|
|
|
3876
3876
|
function phaseColor(p) {
|
|
3877
3877
|
return PHASE_COLORS[p] || "#2f6fed";
|
|
3878
3878
|
}
|
|
3879
|
+
function toolLabel(name) {
|
|
3880
|
+
const n = String(name || "").toLowerCase();
|
|
3881
|
+
if (/bash|shell|terminal|cmd|pwsh|powershell|exec/.test(n)) return "\u6267\u884C\u547D\u4EE4";
|
|
3882
|
+
if (/write|edit|patch|apply|create|mkdir|remove|delete/.test(n)) return "\u4FEE\u6539\u6587\u4EF6";
|
|
3883
|
+
if (/web|fetch|browser|navigate|search/.test(n)) return "\u8BBF\u95EE\u7F51\u9875";
|
|
3884
|
+
return "";
|
|
3885
|
+
}
|
|
3879
3886
|
var END_LABELS = {
|
|
3880
3887
|
completed: { label: "\u5B8C\u6210", cls: "ok" },
|
|
3881
3888
|
error: { label: "\u51FA\u9519", cls: "err" },
|
|
@@ -4109,12 +4116,12 @@ function RobotScene(props) {
|
|
|
4109
4116
|
] });
|
|
4110
4117
|
}
|
|
4111
4118
|
var SOUND_LIBRARY = {
|
|
4112
|
-
chime: { name: "\u6E05\u8106\u53CC\u97F3", notes: { done: [[659.25, 0, 0.14], [880, 0.13, 0.24]], err: [[220, 0, 0.16], [164.81, 0.15, 0.3]] } },
|
|
4113
|
-
ding: { name: "\u53EE", notes: { done: [[987.77, 0, 0.35]], err: [[246.94, 0, 0.4]] } },
|
|
4114
|
-
coin: { name: "\u91D1\u5E01", notes: { done: [[988, 0, 0.08], [1319, 0.08, 0.35], [988, 0, 0.08, "square"], [1319, 0.08, 0.3, "square"]], err: [[196, 0, 0.12], [147, 0.11, 0.35]] } },
|
|
4115
|
-
bell: { name: "\u949F\u58F0", notes: { done: [[523.25, 0, 0.5], [659.25, 0.02, 0.45], [783.99, 0.04, 0.4]], err: [[174.61, 0, 0.5], [130.81, 0.05, 0.5]] } },
|
|
4116
|
-
pulse: { name: "\u8109\u51B2", notes: { done: [[440, 0, 0.09], [440, 0.14, 0.09], [440, 0.28, 0.16]], err: [[174.61, 0, 0.1], [174.61, 0.14, 0.1], [174.61, 0.28, 0.18]] } },
|
|
4117
|
-
arp: { name: "\u7436\u97F3", notes: { done: [[523.25, 0, 0.12], [659.25, 0.09, 0.12], [783.99, 0.18, 0.12], [1046.5, 0.27, 0.3]], err: [[392, 0, 0.12], [329.63, 0.1, 0.12], [261.63, 0.2, 0.12], [196, 0.3, 0.32]] } }
|
|
4119
|
+
chime: { name: "\u6E05\u8106\u53CC\u97F3", notes: { done: [[659.25, 0, 0.14], [880, 0.13, 0.24]], err: [[220, 0, 0.16], [164.81, 0.15, 0.3]], ask: [[587.33, 0, 0.16], [880, 0.19, 0.34]] } },
|
|
4120
|
+
ding: { name: "\u53EE", notes: { done: [[987.77, 0, 0.35]], err: [[246.94, 0, 0.4]], ask: [[783.99, 0, 0.18], [1174.66, 0.2, 0.42]] } },
|
|
4121
|
+
coin: { name: "\u91D1\u5E01", notes: { done: [[988, 0, 0.08], [1319, 0.08, 0.35], [988, 0, 0.08, "square"], [1319, 0.08, 0.3, "square"]], err: [[196, 0, 0.12], [147, 0.11, 0.35]], ask: [[880, 0, 0.09], [1108.73, 0.1, 0.12], [1318.51, 0.22, 0.3]] } },
|
|
4122
|
+
bell: { name: "\u949F\u58F0", notes: { done: [[523.25, 0, 0.5], [659.25, 0.02, 0.45], [783.99, 0.04, 0.4]], err: [[174.61, 0, 0.5], [130.81, 0.05, 0.5]], ask: [[659.25, 0, 0.4], [523.25, 0.05, 0.55]] } },
|
|
4123
|
+
pulse: { name: "\u8109\u51B2", notes: { done: [[440, 0, 0.09], [440, 0.14, 0.09], [440, 0.28, 0.16]], err: [[174.61, 0, 0.1], [174.61, 0.14, 0.1], [174.61, 0.28, 0.18]], ask: [[523.25, 0, 0.09], [523.25, 0.15, 0.09], [659.25, 0.3, 0.22]] } },
|
|
4124
|
+
arp: { name: "\u7436\u97F3", notes: { done: [[523.25, 0, 0.12], [659.25, 0.09, 0.12], [783.99, 0.18, 0.12], [1046.5, 0.27, 0.3]], err: [[392, 0, 0.12], [329.63, 0.1, 0.12], [261.63, 0.2, 0.12], [196, 0.3, 0.32]], ask: [[440, 0, 0.11], [554.37, 0.12, 0.11], [659.25, 0.24, 0.11], [880, 0.36, 0.34]] } }
|
|
4118
4125
|
};
|
|
4119
4126
|
var soundCtx = null;
|
|
4120
4127
|
function playTone(seq) {
|
|
@@ -4146,6 +4153,10 @@ function playDoneSound(success, effect) {
|
|
|
4146
4153
|
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
4147
4154
|
playTone(success ? lib.notes.done : lib.notes.err);
|
|
4148
4155
|
}
|
|
4156
|
+
function playAskSound(effect) {
|
|
4157
|
+
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
4158
|
+
playTone(lib.notes.ask || lib.notes.done);
|
|
4159
|
+
}
|
|
4149
4160
|
function previewSound(effect) {
|
|
4150
4161
|
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
4151
4162
|
playTone(lib.notes.done);
|
|
@@ -4168,11 +4179,14 @@ function Toast(props) {
|
|
|
4168
4179
|
const timer = setTimeout(() => props.onClose(t.id), 240);
|
|
4169
4180
|
return () => clearTimeout(timer);
|
|
4170
4181
|
}, [closing]);
|
|
4171
|
-
const markColor = t.kind === "success" ? "var(--dk-ok)" : t.kind === "error" ? "var(--dk-err)" : "var(--dk-warn)";
|
|
4172
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-toast" + (closing ? " out" : ""), children: [
|
|
4182
|
+
const markColor = t.kind === "success" ? "var(--dk-ok)" : t.kind === "error" ? "var(--dk-err)" : t.kind === "confirm" ? "var(--dk-warn)" : "var(--dk-warn)";
|
|
4183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-toast" + (t.kind === "confirm" ? " dkan-toast-confirm" : "") + (closing ? " out" : ""), children: [
|
|
4173
4184
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-toast-head", children: [
|
|
4174
4185
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-toast-mark", style: { background: markColor } }),
|
|
4175
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
4186
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dkan-toast-title", title: t.title, children: [
|
|
4187
|
+
t.kind === "confirm" ? "\u270B " : "",
|
|
4188
|
+
t.title
|
|
4189
|
+
] }),
|
|
4176
4190
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dkan-toast-close", "aria-label": "\u5173\u95ED", onClick: close, children: "\u2715" })
|
|
4177
4191
|
] }),
|
|
4178
4192
|
t.body ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-toast-body", children: t.body }) : null
|
|
@@ -4376,6 +4390,24 @@ function visibleRect(el) {
|
|
|
4376
4390
|
const r = el.getBoundingClientRect();
|
|
4377
4391
|
return r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight ? r : null;
|
|
4378
4392
|
}
|
|
4393
|
+
function currentSessionTitle() {
|
|
4394
|
+
if (typeof document === "undefined") return "";
|
|
4395
|
+
for (const el of document.querySelectorAll('[role="treeitem"][aria-selected="true"]')) {
|
|
4396
|
+
const r = el.getBoundingClientRect();
|
|
4397
|
+
if (r.width > 0 && r.height > 0 && r.right <= window.innerWidth * 0.35) {
|
|
4398
|
+
const text = (el.textContent || "").trim();
|
|
4399
|
+
if (text) return text;
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
return "";
|
|
4403
|
+
}
|
|
4404
|
+
function taskOnScreen(task, titleCache) {
|
|
4405
|
+
const title = task && (task.title || "");
|
|
4406
|
+
if (!title) return true;
|
|
4407
|
+
const current = titleCache !== void 0 ? titleCache : currentSessionTitle();
|
|
4408
|
+
if (!current) return true;
|
|
4409
|
+
return current === title || current.indexOf(title) !== -1 || title.indexOf(current) !== -1;
|
|
4410
|
+
}
|
|
4379
4411
|
var COMPOSER_SEND_SELECTOR = 'button[aria-label="Send message"],button[aria-label*="\u53D1\u9001"],button[data-testid*="send"],button[data-testid*="submit"],button[type="submit"]';
|
|
4380
4412
|
function conversationComposer() {
|
|
4381
4413
|
if (typeof document === "undefined" || typeof window === "undefined") return null;
|
|
@@ -4445,7 +4477,6 @@ function useSpaceDocks(enabled) {
|
|
|
4445
4477
|
let frame = 0;
|
|
4446
4478
|
let idle = 0;
|
|
4447
4479
|
let timer = 0;
|
|
4448
|
-
let pollTimer = 0;
|
|
4449
4480
|
let retries = 0;
|
|
4450
4481
|
let pending = false;
|
|
4451
4482
|
let observedComposer = null;
|
|
@@ -4501,7 +4532,25 @@ function useSpaceDocks(enabled) {
|
|
|
4501
4532
|
}) : null;
|
|
4502
4533
|
if (mutationObserver && document.body) mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
4503
4534
|
schedule(0, true);
|
|
4504
|
-
|
|
4535
|
+
let calibrationInterval = 700;
|
|
4536
|
+
let lastActivityAt = Date.now();
|
|
4537
|
+
const markActivity = () => {
|
|
4538
|
+
lastActivityAt = Date.now();
|
|
4539
|
+
if (calibrationInterval !== 700) {
|
|
4540
|
+
calibrationInterval = 700;
|
|
4541
|
+
restartPoll();
|
|
4542
|
+
}
|
|
4543
|
+
};
|
|
4544
|
+
const unsubscribeActivity = animationStore.subscribe(markActivity);
|
|
4545
|
+
let pollTimer = 0;
|
|
4546
|
+
function restartPoll() {
|
|
4547
|
+
if (pollTimer) window.clearInterval(pollTimer);
|
|
4548
|
+
pollTimer = window.setInterval(() => {
|
|
4549
|
+
if (Date.now() - lastActivityAt > 2e4 && calibrationInterval !== 5e3) calibrationInterval = 5e3;
|
|
4550
|
+
schedule();
|
|
4551
|
+
}, calibrationInterval);
|
|
4552
|
+
}
|
|
4553
|
+
restartPoll();
|
|
4505
4554
|
const onResize = () => {
|
|
4506
4555
|
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
4507
4556
|
if (timer) window.clearTimeout(timer);
|
|
@@ -4517,6 +4566,7 @@ function useSpaceDocks(enabled) {
|
|
|
4517
4566
|
if (pollTimer) window.clearInterval(pollTimer);
|
|
4518
4567
|
if (mutationObserver) mutationObserver.disconnect();
|
|
4519
4568
|
if (resizeObserver) resizeObserver.disconnect();
|
|
4569
|
+
unsubscribeActivity();
|
|
4520
4570
|
window.removeEventListener("resize", onResize);
|
|
4521
4571
|
};
|
|
4522
4572
|
}, [enabled]);
|
|
@@ -4597,13 +4647,14 @@ function MissionDeparture(props) {
|
|
|
4597
4647
|
}, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FreighterHull, { units: 3, empty: true }) });
|
|
4598
4648
|
}
|
|
4599
4649
|
function ProgrammingStuck(props) {
|
|
4600
|
-
|
|
4650
|
+
const waiting = !!props.waiting;
|
|
4651
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-space-stuck" + (waiting ? " waiting" : ""), "aria-hidden": "true", children: [
|
|
4601
4652
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FreighterHull, { units: 3, empty: true }),
|
|
4602
4653
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dkan-stuck-code", children: [
|
|
4603
4654
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("i", {}),
|
|
4604
4655
|
`{ }`
|
|
4605
4656
|
] }),
|
|
4606
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "\u7F16\u7A0B\u7B49\u5F85\u4E2D" }),
|
|
4657
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: waiting ? "\u7B49\u5F85\u786E\u8BA4" : "\u7F16\u7A0B\u7B49\u5F85\u4E2D" }),
|
|
4607
4658
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("small", { children: truncate(props.task && props.task.title || "\u5DE5\u5177\u6682\u65E0\u65B0\u54CD\u5E94", 28) })
|
|
4608
4659
|
] });
|
|
4609
4660
|
}
|
|
@@ -4725,6 +4776,56 @@ function AnimationOverlay(props) {
|
|
|
4725
4776
|
}
|
|
4726
4777
|
prevActiveRef.current = st2.active.slice();
|
|
4727
4778
|
}, [snap.status]);
|
|
4779
|
+
const remindedApprovalsRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
|
|
4780
|
+
(0, import_react8.useEffect)(() => {
|
|
4781
|
+
const st2 = snap.status;
|
|
4782
|
+
if (!st2 || !st2.active) return;
|
|
4783
|
+
const cfg2 = st2.config || {};
|
|
4784
|
+
const activeIds = new Set(st2.active.map((x) => x.sessionId));
|
|
4785
|
+
for (const [sid, seen] of remindedApprovalsRef.current) {
|
|
4786
|
+
if (!activeIds.has(sid)) remindedApprovalsRef.current.delete(sid);
|
|
4787
|
+
}
|
|
4788
|
+
for (const task of st2.active) {
|
|
4789
|
+
const approvals = Array.isArray(task.approvals) ? task.approvals : [];
|
|
4790
|
+
if (approvals.length === 0) continue;
|
|
4791
|
+
let seen = remindedApprovalsRef.current.get(task.sessionId);
|
|
4792
|
+
if (!seen) {
|
|
4793
|
+
seen = /* @__PURE__ */ new Set();
|
|
4794
|
+
remindedApprovalsRef.current.set(task.sessionId, seen);
|
|
4795
|
+
}
|
|
4796
|
+
const fresh = approvals.filter((a) => a && !seen.has(a.id));
|
|
4797
|
+
if (fresh.length === 0) continue;
|
|
4798
|
+
for (const a of fresh) seen.add(a.id);
|
|
4799
|
+
if (!cfg2.notifyEnabled || cfg2.notifyOnConfirm === false) continue;
|
|
4800
|
+
const toolName = fresh[0].toolName || "";
|
|
4801
|
+
const scene = toolLabel(toolName);
|
|
4802
|
+
const lines = [
|
|
4803
|
+
"\u4EFB\u52A1\uFF1A" + (task.title || "(\u65E0\u6807\u9898)"),
|
|
4804
|
+
"\u5DE5\u5177\uFF1A" + (toolName || "\u672A\u77E5") + (scene ? "\uFF08" + scene + "\uFF09" : "")
|
|
4805
|
+
];
|
|
4806
|
+
if (fresh[0].reason) lines.push("\u8BF4\u660E\uFF1A" + truncate(fresh[0].reason, 140));
|
|
4807
|
+
lines.push(fresh.length > 1 ? "\u5171 " + fresh.length + " \u9879\u7B49\u5F85\u4F60\u7684\u786E\u8BA4" : "\u8BF7\u5728\u4F1A\u8BDD\u91CC\u786E\u8BA4\u540E\u7EE7\u7EED");
|
|
4808
|
+
const toast = {
|
|
4809
|
+
id: Date.now() + Math.random(),
|
|
4810
|
+
kind: "confirm",
|
|
4811
|
+
title: "\u4EFB\u52A1\u9700\u8981\u786E\u8BA4",
|
|
4812
|
+
body: lines.join("\n"),
|
|
4813
|
+
// 需确认常驻到用户处理(notifyStayMs=0 同款语义),关掉开关的间隙也不会自动消失
|
|
4814
|
+
stayMs: 0
|
|
4815
|
+
};
|
|
4816
|
+
setToasts((prev) => prev.concat([toast]).slice(-4));
|
|
4817
|
+
if (cfg2.soundNotify !== false) playAskSound(cfg2.soundEffect);
|
|
4818
|
+
if (cfg2.systemNotify && typeof document !== "undefined" && document.hidden && typeof Notification !== "undefined" && Notification.permission === "granted") {
|
|
4819
|
+
try {
|
|
4820
|
+
new Notification("dsh \u4EFB\u52A1\u9700\u8981\u786E\u8BA4", {
|
|
4821
|
+
body: (task.title || "(\u65E0\u6807\u9898)") + " \xB7 " + (toolName || "\u5DE5\u5177") + " \u7B49\u5F85\u786E\u8BA4",
|
|
4822
|
+
tag: "dsh-dock-approval-" + task.sessionId
|
|
4823
|
+
});
|
|
4824
|
+
} catch {
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
}, [snap.status]);
|
|
4728
4829
|
const handleTaskEnd = (task, record, cfg2) => {
|
|
4729
4830
|
const reason = record && record.endReason || "";
|
|
4730
4831
|
const success = isSuccessReason(reason);
|
|
@@ -4795,6 +4896,18 @@ function AnimationOverlay(props) {
|
|
|
4795
4896
|
const animOn = !!(cfg && cfg.animationEnabled && active.length > 0);
|
|
4796
4897
|
const mode = cfg && cfg.effectMode ? cfg.effectMode : "flow";
|
|
4797
4898
|
const phase = active.length > 0 ? active.reduce((best, x) => !best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best, null).phase : "think";
|
|
4899
|
+
const waitingTask = active.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
4900
|
+
const sessionTitleRef = (0, import_react8.useRef)({ at: 0, title: "" });
|
|
4901
|
+
const currentTitle = (0, import_react8.useMemo)(() => {
|
|
4902
|
+
const c = sessionTitleRef.current;
|
|
4903
|
+
if (typeof window === "undefined") return "";
|
|
4904
|
+
if (Date.now() - c.at < 1e3) return c.title;
|
|
4905
|
+
c.at = Date.now();
|
|
4906
|
+
c.title = currentSessionTitle();
|
|
4907
|
+
return c.title;
|
|
4908
|
+
}, [snap.status]);
|
|
4909
|
+
const onScreenTasks = (0, import_react8.useMemo)(() => active.filter((x) => taskOnScreen(x, currentTitle)), [active, currentTitle]);
|
|
4910
|
+
const waitingTaskOnScreen = onScreenTasks.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
4798
4911
|
useTicker(ctx, animOn);
|
|
4799
4912
|
const now = Date.now();
|
|
4800
4913
|
const elapsed = active.length > 0 ? fmtClock(now - Math.min(...active.map((x) => x.startTime || now))) : "";
|
|
@@ -4828,7 +4941,7 @@ function AnimationOverlay(props) {
|
|
|
4828
4941
|
const spaceModeOn = !!(cfg && cfg.animationEnabled && mode === "space" && !panelOpen);
|
|
4829
4942
|
const spaceDocks = useSpaceDocks(spaceModeOn);
|
|
4830
4943
|
const spaceGalaxies = spaceModeOn && spaceDocks ? spaceGalaxyLayout() : null;
|
|
4831
|
-
const stuckTask =
|
|
4944
|
+
const stuckTask = onScreenTasks.find((task) => task.phase === "code" && now - (task.lastActivityAt || task.phaseAt || now) >= 18e3) || null;
|
|
4832
4945
|
const robotScaleFromConfig = Math.max(0.85, Math.min(2.2, Number(cfg && cfg.robotScale) || 1.35));
|
|
4833
4946
|
const speedStyle = { "--dkan-speed": Number(speed).toFixed(2) };
|
|
4834
4947
|
const saveRobotScale = (0, import_react8.useCallback)((robotScale) => {
|
|
@@ -4943,7 +5056,7 @@ function AnimationOverlay(props) {
|
|
|
4943
5056
|
ambientOn && mode === "flow" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-line", style: speedStyle, "aria-hidden": "true" }) : null,
|
|
4944
5057
|
ambientOn && AMBIENT_EFFECT_MODES.has(mode) && (mode !== "space" || spaceGalaxies) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AmbientLayer, { mode, phase, speed: Number(speed).toFixed(2), tasks: active, galaxies: spaceGalaxies }) : null,
|
|
4945
5058
|
spaceModeOn && spaceDocks && active.length === 0 && deliveries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReadyFreighter, { dock: spaceDocks.ready }) : null,
|
|
4946
|
-
spaceModeOn && stuckTask ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProgrammingStuck, { task: stuckTask }) : null,
|
|
5059
|
+
spaceModeOn && stuckTask ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProgrammingStuck, { task: stuckTask, waiting: !!(stuckTask.approvals && stuckTask.approvals.length) }) : null,
|
|
4947
5060
|
!panelOpen ? departures.map((departure) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MissionDeparture, { departure }, departure.id)) : null,
|
|
4948
5061
|
ambientOn && mode === "orbit" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-orbit", style: speedStyle, "aria-hidden": "true" }) : null,
|
|
4949
5062
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BurstLayer, { bursts }),
|
|
@@ -4953,8 +5066,8 @@ function AnimationOverlay(props) {
|
|
|
4953
5066
|
className: "dkan-botcard",
|
|
4954
5067
|
role: "button",
|
|
4955
5068
|
tabIndex: 0,
|
|
4956
|
-
style: botCardStyle,
|
|
4957
|
-
title: active.length + " \u4E2A\u4EFB\u52A1 \xB7 " + phaseLabel(phase) + " \xB7 \u70B9\u51FB\u67E5\u770B\u4EFB\u52A1\u52A8\u753B\u9875 \xB7 \u53EF\u62D6\u52A8\u5230\u4EFB\u610F\u4F4D\u7F6E",
|
|
5069
|
+
style: Object.assign({}, botCardStyle, waitingTask ? { "--dkan-phase": "#b45309" } : null),
|
|
5070
|
+
title: active.length + " \u4E2A\u4EFB\u52A1 \xB7 " + (waitingTask ? "\u7B49\u5F85\u786E\u8BA4" : phaseLabel(phase)) + " \xB7 \u70B9\u51FB\u67E5\u770B\u4EFB\u52A1\u52A8\u753B\u9875 \xB7 \u53EF\u62D6\u52A8\u5230\u4EFB\u610F\u4F4D\u7F6E",
|
|
4958
5071
|
onPointerDown: onBotPointerDown,
|
|
4959
5072
|
onPointerMove: onBotPointerMove,
|
|
4960
5073
|
onPointerUp: onBotPointerUp,
|
|
@@ -4973,7 +5086,7 @@ function AnimationOverlay(props) {
|
|
|
4973
5086
|
" \u4E2A\u4EFB\u52A1",
|
|
4974
5087
|
elapsed ? " \xB7 " + elapsed : "",
|
|
4975
5088
|
" \xB7 ",
|
|
4976
|
-
phaseLabel(phase)
|
|
5089
|
+
waitingTask ? "\u7B49\u5F85\u786E\u8BA4" : phaseLabel(phase)
|
|
4977
5090
|
] }),
|
|
4978
5091
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4979
5092
|
"button",
|
|
@@ -5004,8 +5117,8 @@ function AnimationOverlay(props) {
|
|
|
5004
5117
|
{
|
|
5005
5118
|
type: "button",
|
|
5006
5119
|
className: "dkan-badge" + (mode === "breathe" ? " dkan-badge-breathe" : ""),
|
|
5007
|
-
style: Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(phase) }),
|
|
5008
|
-
title: active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D \xB7 " + phaseLabel(phase) + " \xB7 \u70B9\u51FB\u67E5\u770B\u4EFB\u52A1\u52A8\u753B\u9875",
|
|
5120
|
+
style: Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(waitingTask ? "code" : phase) }),
|
|
5121
|
+
title: active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D \xB7 " + (waitingTask ? "\u7B49\u5F85\u786E\u8BA4" : phaseLabel(phase)) + " \xB7 \u70B9\u51FB\u67E5\u770B\u4EFB\u52A1\u52A8\u753B\u9875",
|
|
5009
5122
|
onClick: () => openPanel("animation"),
|
|
5010
5123
|
children: [
|
|
5011
5124
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dkan-dotwrap", children: [
|
|
@@ -5018,7 +5131,7 @@ function AnimationOverlay(props) {
|
|
|
5018
5131
|
" \u4E2A\u4EFB\u52A1",
|
|
5019
5132
|
elapsed ? " \xB7 " + elapsed : "",
|
|
5020
5133
|
" \xB7 ",
|
|
5021
|
-
phaseLabel(phase)
|
|
5134
|
+
waitingTask ? "\u7B49\u5F85\u786E\u8BA4" : phaseLabel(phase)
|
|
5022
5135
|
] })
|
|
5023
5136
|
]
|
|
5024
5137
|
}
|
|
@@ -5093,10 +5206,11 @@ function ModePreview({ id }) {
|
|
|
5093
5206
|
function TaskRow(props) {
|
|
5094
5207
|
const t = props.t;
|
|
5095
5208
|
const info = endInfo(t.endReason);
|
|
5209
|
+
const waiting = !props.done && Array.isArray(t.approvals) && t.approvals.length > 0;
|
|
5096
5210
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-task", children: [
|
|
5097
5211
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-task-head", children: [
|
|
5098
5212
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-task-title", title: t.title, children: t.title || "(\u65E0\u6807\u9898)" }),
|
|
5099
|
-
props.done ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-tag " + info.cls, children: info.label }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-tag on", children: phaseLabel(t.phase) })
|
|
5213
|
+
props.done ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-tag " + info.cls, children: info.label }) : waiting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-tag warn", children: "\u7B49\u5F85\u786E\u8BA4" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-tag on", children: phaseLabel(t.phase) })
|
|
5100
5214
|
] }),
|
|
5101
5215
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-task-meta", children: [
|
|
5102
5216
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: (t.models && t.models.length ? t.models.join(", ") : "\u672A\u77E5") + (t.provider ? "\uFF08" + t.provider + "\uFF09" : "") }),
|
|
@@ -5105,6 +5219,7 @@ function TaskRow(props) {
|
|
|
5105
5219
|
t.totalTokens ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u21A7" + fmtCompact2(t.inputTokens) + " \u21A5" + fmtCompact2(t.outputTokens) }) : null,
|
|
5106
5220
|
props.done && t.endTime ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: fmtTime2(t.endTime) }) : null
|
|
5107
5221
|
] }),
|
|
5222
|
+
waiting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-task-err", children: t.approvals.map((a, i) => (a && a.toolName ? a.toolName : "\u672A\u77E5\u5DE5\u5177") + (a && a.reason ? "\uFF1A" + truncate(a.reason, 80) : "")).join("\uFF1B") }) : null,
|
|
5108
5223
|
props.done && t.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-task-err", children: truncate(t.errorMessage, 120) }) : null
|
|
5109
5224
|
] });
|
|
5110
5225
|
}
|
|
@@ -5232,6 +5347,7 @@ function AnimationView(props) {
|
|
|
5232
5347
|
if (!cfg) {
|
|
5233
5348
|
rows.push(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-note", children: snap.error ? "\u72B6\u6001\u4E0D\u53EF\u7528\uFF1A" + snap.error : snap.loading ? "\u6B63\u5728\u62C9\u53D6\u4EFB\u52A1\u72B6\u6001\u2026" : "\u7B49\u5F85\u4EFB\u52A1\u72B6\u6001" }, "load"));
|
|
5234
5349
|
} else {
|
|
5350
|
+
const waitingCount2 = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
|
|
5235
5351
|
rows.push(
|
|
5236
5352
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec", children: [
|
|
5237
5353
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec-head", children: [
|
|
@@ -5351,6 +5467,19 @@ function AnimationView(props) {
|
|
|
5351
5467
|
),
|
|
5352
5468
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-sub", children: "\u51FA\u9519 / \u4E2D\u6B62 / \u8FBE\u8F93\u51FA\u4E0A\u9650\u65F6\u901A\u77E5" })
|
|
5353
5469
|
] }),
|
|
5470
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-row", children: [
|
|
5471
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-label", children: "\u9700\u786E\u8BA4\u63D0\u9192" }),
|
|
5472
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5473
|
+
"button",
|
|
5474
|
+
{
|
|
5475
|
+
type: "button",
|
|
5476
|
+
className: "dkm-miniswitch" + (cfg.notifyOnConfirm !== false ? " on" : ""),
|
|
5477
|
+
onClick: () => patch({ notifyOnConfirm: cfg.notifyOnConfirm === false }),
|
|
5478
|
+
children: cfg.notifyOnConfirm !== false ? "\u5F00" : "\u5173"
|
|
5479
|
+
}
|
|
5480
|
+
),
|
|
5481
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-sub", children: "\u5DE5\u5177\u7B49\u5F85\u4F60\u6279\u51C6\u65F6\u5F39\u5361\u7247\u5E76\u54CD\u786E\u8BA4\u97F3\uFF08\u5E38\u9A7B\u4E0D\u81EA\u52A8\u6D88\u5931\uFF09" })
|
|
5482
|
+
] }),
|
|
5354
5483
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-row", children: [
|
|
5355
5484
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-label", children: "\u505C\u7559\u65F6\u957F" }),
|
|
5356
5485
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -5393,7 +5522,7 @@ function AnimationView(props) {
|
|
|
5393
5522
|
children: cfg.soundNotify !== false ? "\u5F00" : "\u5173"
|
|
5394
5523
|
}
|
|
5395
5524
|
),
|
|
5396
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-sub", children: "\u4EFB\u52A1\u7ED3\u675F\u65F6\u64AD\u653E\uFF08\u8BD5\u542C\u4E3A\u5148\u64AD\u5B8C\u6210\u97F3\u3001\u540E\u64AD\u5F02\u5E38\u97F3\uFF09" })
|
|
5525
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-row-sub", children: "\u4EFB\u52A1\u7ED3\u675F/\u9700\u786E\u8BA4\u65F6\u64AD\u653E\uFF08\u8BD5\u542C\u4E3A\u5148\u64AD\u5B8C\u6210\u97F3\u3001\u540E\u64AD\u5F02\u5E38\u97F3\uFF09" })
|
|
5397
5526
|
] }),
|
|
5398
5527
|
cfg.soundNotify !== false ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-sounds", children: Object.keys(SOUND_LIBRARY).map((key) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5399
5528
|
"button",
|
|
@@ -5558,7 +5687,7 @@ function AnimationView(props) {
|
|
|
5558
5687
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec", children: [
|
|
5559
5688
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec-head", children: [
|
|
5560
5689
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-sec-title", children: "\u8FD0\u884C\u72B6\u6001" }),
|
|
5561
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-sec-sub", children: snap.error ? "\u72B6\u6001\u62C9\u53D6\u5931\u8D25\uFF1A" + snap.error : active.length > 0 ? active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D" : recent.length > 0 ? "\u7A7A\u95F2 \xB7 \u663E\u793A\u6700\u8FD1\u5B8C\u6210" : "\u7A7A\u95F2 \xB7 \u6682\u65E0\u4EFB\u52A1\u8BB0\u5F55" }),
|
|
5690
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dkan-sec-sub", children: snap.error ? "\u72B6\u6001\u62C9\u53D6\u5931\u8D25\uFF1A" + snap.error : waitingCount > 0 ? waitingCount + " \u9879\u7B49\u5F85\u786E\u8BA4 \xB7 " + active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D" : active.length > 0 ? active.length + " \u4E2A\u4EFB\u52A1\u8FDB\u884C\u4E2D" : recent.length > 0 ? "\u7A7A\u95F2 \xB7 \u663E\u793A\u6700\u8FD1\u5B8C\u6210" : "\u7A7A\u95F2 \xB7 \u6682\u65E0\u4EFB\u52A1\u8BB0\u5F55" }),
|
|
5562
5691
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dkan-refresh", onClick: () => animationStore.refresh(), children: snap.loading ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0" })
|
|
5563
5692
|
] }),
|
|
5564
5693
|
active.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-tasks", children: active.map((t) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TaskRow, { t }, t.sessionId)) }) : null,
|
|
@@ -5630,7 +5759,9 @@ var css2 = [
|
|
|
5630
5759
|
".dkan-delivery-label{position:absolute;left:21px;top:58px;display:flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 30%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-delivery-label b{color:var(--dsw-alias-label-primary);font-weight:650}.dkan-delivery-label span{color:var(--dk-accent);}",
|
|
5631
5760
|
"@media (max-width:1100px){.dkan-space-system,.dkan-space-runner{display:none}}",
|
|
5632
5761
|
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:9994;pointer-events:none;filter:drop-shadow(0 10px 14px rgb(0 0 0 / .18));}.dkan-ready-freighter>span:last-child{position:absolute;left:21px;top:58px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 28%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 80%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-departure{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-departure-flight var(--dkan-departure-duration,8600ms) cubic-bezier(.42,0,.85,.45) forwards;}@keyframes dkan-departure-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}100%{opacity:0;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(.24) rotate(var(--dkan-flight-angle,0deg))}}",
|
|
5633
|
-
".dkan-space-stuck{position:fixed;
|
|
5762
|
+
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:9993;pointer-events:none;border:1px solid color-mix(in srgb,#f59e0b 48%,var(--dsw-alias-border-l1));border-radius:20px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(16px);box-shadow:0 18px 54px rgb(0 0 0 / .28),0 0 30px color-mix(in srgb,#f59e0b 15%,transparent);}.dkan-space-stuck .dkan-freighter{left:37px;top:11px;transform:scale(.9);transform-origin:top left;}.dkan-stuck-code{position:absolute;left:17px;right:17px;top:60px;height:36px;border-radius:7px;background:linear-gradient(90deg,color-mix(in srgb,#f59e0b 18%,transparent),transparent);color:var(--dk-warn);font:700 14px/36px var(--ds-font-family-code,monospace);letter-spacing:.12em;text-align:center;overflow:hidden;}.dkan-stuck-code::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fef3c7 70%,transparent),transparent);transform:translateX(-110%);animation:dkan-stuck-scan 1.25s linear infinite;}.dkan-stuck-code i{display:inline-block;width:7px;height:7px;margin-right:8px;border-radius:50%;background:#f59e0b;box-shadow:0 0 9px #f59e0b;animation:dkan-stuck-pulse .8s ease-in-out infinite alternate;}.dkan-space-stuck strong{position:absolute;left:0;right:0;top:108px;color:var(--dsw-alias-label-primary);font:650 13px/1.2 var(--ds-font-family,system-ui,sans-serif);text-align:center;}.dkan-space-stuck small{position:absolute;left:18px;right:18px;top:128px;color:var(--dsw-alias-label-secondary);font:11px/1.25 var(--ds-font-family,system-ui,sans-serif);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@keyframes dkan-stuck-scan{to{transform:translateX(110%)}}@keyframes dkan-stuck-pulse{to{transform:scale(.55);opacity:.45}}",
|
|
5763
|
+
// 窄屏(手机/分屏)贴右会溢出:回落到水平居中、屏幕上方,避开正文重心
|
|
5764
|
+
"@media (max-width:760px){.dkan-space-stuck{right:50%;transform:translateX(50%);top:64px;}}",
|
|
5634
5765
|
// 极光:顶部柔光带呼吸流动(加浓)
|
|
5635
5766
|
".dkan-aurora i{position:absolute;top:-40%;left:-20%;width:70%;height:80%;border-radius:50%;filter:blur(50px);opacity:.32;animation:dkan-aurora ease-in-out infinite alternate;}",
|
|
5636
5767
|
".dkan-aurora i:nth-child(1){background:#4d9fff;}",
|
|
@@ -5719,6 +5850,9 @@ var css2 = [
|
|
|
5719
5850
|
".dkan-toast-close{cursor:pointer;flex:none;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:6px;width:22px;height:22px;font-size:11px;line-height:1;display:inline-flex;align-items:center;justify-content:center;font-family:inherit;}",
|
|
5720
5851
|
".dkan-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
|
|
5721
5852
|
".dkan-toast-body{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.7;white-space:pre-line;word-break:break-word;}",
|
|
5853
|
+
// 需确认卡片:琥珀描边 + 缓慢呼吸光晕,常驻提醒直到用户处理
|
|
5854
|
+
".dkan-toast-confirm{border-color:color-mix(in srgb,var(--dk-warn) 55%,transparent);animation:dkan-toast-in .32s var(--ds-ease-in-out),dkan-confirm-glow 2.4s ease-in-out 0.4s infinite;}",
|
|
5855
|
+
"@keyframes dkan-confirm-glow{0%,100%{box-shadow:0 10px 36px rgb(0 0 0 / .22),0 0 0 0 color-mix(in srgb,var(--dk-warn) 30%,transparent)}50%{box-shadow:0 10px 36px rgb(0 0 0 / .22),0 0 14px 2px color-mix(in srgb,var(--dk-warn) 32%,transparent)}}",
|
|
5722
5856
|
// 面板页布局
|
|
5723
5857
|
".dkan-root{display:flex;flex-direction:column;gap:10px;}",
|
|
5724
5858
|
".dkan-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
|
|
@@ -9861,7 +9995,7 @@ var feature8 = {
|
|
|
9861
9995
|
};
|
|
9862
9996
|
|
|
9863
9997
|
// src/client.jsx
|
|
9864
|
-
var DOCK_VERSION = "0.9.
|
|
9998
|
+
var DOCK_VERSION = "0.9.3";
|
|
9865
9999
|
var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8];
|
|
9866
10000
|
var PLANNED_FEATURES = [];
|
|
9867
10001
|
var PLANNED_NOTES = {};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// 配置模型(schemastery schema 见 src/host-core.js DockConfig.animation):
|
|
11
11
|
// animationEnabled / effectMode / notifyEnabled / notifyOnComplete /
|
|
12
|
-
// notifyOnError / notifyStayMs / systemNotify
|
|
12
|
+
// notifyOnError / notifyOnConfirm / notifyStayMs / systemNotify
|
|
13
13
|
// 动画与通知是两个独立开关,可只开其一;全部经 settings 持久化,重启后恢复。
|
|
14
14
|
import { DOCK_NS, ANIMATION_MODES, SOUND_EFFECTS, sendJson, readBody } from '../../src/host-core.js'
|
|
15
15
|
|
|
@@ -22,6 +22,7 @@ function defaultConfig() {
|
|
|
22
22
|
notifyEnabled: true,
|
|
23
23
|
notifyOnComplete: true,
|
|
24
24
|
notifyOnError: true,
|
|
25
|
+
notifyOnConfirm: true,
|
|
25
26
|
notifyStayMs: 8000,
|
|
26
27
|
systemNotify: false,
|
|
27
28
|
soundNotify: true,
|
|
@@ -132,6 +133,8 @@ export const feature = {
|
|
|
132
133
|
errorMessage: '',
|
|
133
134
|
lastText: '',
|
|
134
135
|
firstPrompt: '',
|
|
136
|
+
// 工具等待用户确认(dsh 会话流 approval/asked):浏览器侧据此弹"需确认"通知
|
|
137
|
+
approvals: new Map(), // approvalId -> { toolName, reason, askedAt }
|
|
135
138
|
phase: 'think',
|
|
136
139
|
phaseAt: Date.now(),
|
|
137
140
|
lastActivityAt: Date.now(),
|
|
@@ -393,6 +396,19 @@ export const feature = {
|
|
|
393
396
|
const msg = (reason.error && (reason.error.message || reason.error.code)) || ''
|
|
394
397
|
s.errorMessage = typeof msg === 'string' ? msg : String(msg)
|
|
395
398
|
}
|
|
399
|
+
} else if (event.type === 'approval/asked' && event.data) {
|
|
400
|
+
// 工具等待用户确认/批准:挂在会话记录上,/status 下发给浏览器弹"需确认"通知
|
|
401
|
+
const id = typeof event.data.id === 'string' ? event.data.id : ''
|
|
402
|
+
if (id) {
|
|
403
|
+
s.approvals.set(id, {
|
|
404
|
+
toolName: typeof event.data.toolName === 'string' ? event.data.toolName : '',
|
|
405
|
+
reason: typeof event.data.reason === 'string' ? truncate(event.data.reason, 200) : '',
|
|
406
|
+
askedAt: event.time || Date.now(),
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
s.lastActivityAt = now
|
|
410
|
+
} else if (event.type === 'approval/decided' && event.data) {
|
|
411
|
+
s.approvals.delete(typeof event.data.id === 'string' ? event.data.id : '')
|
|
396
412
|
} else if (event.type === 'tool/call') {
|
|
397
413
|
s.toolCalls++
|
|
398
414
|
s.phase = phaseOfToolName(event.data && event.data.name)
|
|
@@ -506,6 +522,7 @@ export const feature = {
|
|
|
506
522
|
lastActivityAt: s.lastActivityAt,
|
|
507
523
|
motionTicks: s.motionTicks,
|
|
508
524
|
lastMotionAt: s.lastMotionAt,
|
|
525
|
+
approvals: [...s.approvals.entries()].map(([id, v]) => ({ id, ...v })),
|
|
509
526
|
})
|
|
510
527
|
}
|
|
511
528
|
return sendJson(res, 200, {
|
|
@@ -531,6 +548,7 @@ export const feature = {
|
|
|
531
548
|
if (typeof p.notifyEnabled === 'boolean') cfg.notifyEnabled = p.notifyEnabled
|
|
532
549
|
if (typeof p.notifyOnComplete === 'boolean') cfg.notifyOnComplete = p.notifyOnComplete
|
|
533
550
|
if (typeof p.notifyOnError === 'boolean') cfg.notifyOnError = p.notifyOnError
|
|
551
|
+
if (typeof p.notifyOnConfirm === 'boolean') cfg.notifyOnConfirm = p.notifyOnConfirm
|
|
534
552
|
if (typeof p.notifyStayMs === 'number' && Number.isFinite(p.notifyStayMs)) {
|
|
535
553
|
cfg.notifyStayMs = Math.max(0, Math.min(600000, Math.round(p.notifyStayMs)))
|
|
536
554
|
}
|
|
@@ -66,6 +66,15 @@ function phaseLabel(p) { return PHASE_LABELS[p] || "工作中"; }
|
|
|
66
66
|
const PHASE_COLORS = { think: "#2f6fed", write: "#0d9488", code: "#b45309", search: "#0e7490" };
|
|
67
67
|
function phaseColor(p) { return PHASE_COLORS[p] || "#2f6fed"; }
|
|
68
68
|
|
|
69
|
+
// 工具名 → 中文场景("需确认"通知里说清楚是什么在等确认)
|
|
70
|
+
function toolLabel(name) {
|
|
71
|
+
const n = String(name || "").toLowerCase();
|
|
72
|
+
if (/bash|shell|terminal|cmd|pwsh|powershell|exec/.test(n)) return "执行命令";
|
|
73
|
+
if (/write|edit|patch|apply|create|mkdir|remove|delete/.test(n)) return "修改文件";
|
|
74
|
+
if (/web|fetch|browser|navigate|search/.test(n)) return "访问网页";
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
// ---------- 结束原因 ----------
|
|
70
79
|
const END_LABELS = {
|
|
71
80
|
completed: { label: "完成", cls: "ok" },
|
|
@@ -305,15 +314,16 @@ function RobotScene(props) {
|
|
|
305
314
|
}
|
|
306
315
|
|
|
307
316
|
// ---------- 提示音(WebAudio 合成;macOS/Windows 通用,无音频文件) ----------
|
|
308
|
-
// 音效库:每种含完成音(done
|
|
309
|
-
// 音符 [频率, 起始秒, 时长秒, 波形?],波形缺省 sine
|
|
317
|
+
// 音效库:每种含完成音(done)、异常音(err)与确认音(ask)三套音符序列。
|
|
318
|
+
// 音符 [频率, 起始秒, 时长秒, 波形?],波形缺省 sine。确认音双音上扬、偏长,
|
|
319
|
+
// 听感是"来人搭话",和完成音的收束感、异常音的下坠感区分开。
|
|
310
320
|
const SOUND_LIBRARY = {
|
|
311
|
-
chime: { name: "清脆双音", notes: { done: [[659.25, 0, .14], [880, .13, .24]], err: [[220, 0, .16], [164.81, .15, .3]] } },
|
|
312
|
-
ding: { name: "叮", notes: { done: [[987.77, 0, .35]], err: [[246.94, 0, .4]] } },
|
|
313
|
-
coin: { name: "金币", notes: { done: [[988, 0, .08], [1319, .08, .35], [988, 0, .08, "square"], [1319, .08, .3, "square"]], err: [[196, 0, .12], [147, .11, .35]] } },
|
|
314
|
-
bell: { name: "钟声", notes: { done: [[523.25, 0, .5], [659.25, .02, .45], [783.99, .04, .4]], err: [[174.61, 0, .5], [130.81, .05, .5]] } },
|
|
315
|
-
pulse: { name: "脉冲", notes: { done: [[440, 0, .09], [440, .14, .09], [440, .28, .16]], err: [[174.61, 0, .1], [174.61, .14, .1], [174.61, .28, .18]] } },
|
|
316
|
-
arp: { name: "琶音", notes: { done: [[523.25, 0, .12], [659.25, .09, .12], [783.99, .18, .12], [1046.5, .27, .3]], err: [[392, 0, .12], [329.63, .1, .12], [261.63, .2, .12], [196, .3, .32]] } },
|
|
321
|
+
chime: { name: "清脆双音", notes: { done: [[659.25, 0, .14], [880, .13, .24]], err: [[220, 0, .16], [164.81, .15, .3]], ask: [[587.33, 0, .16], [880, .19, .34]] } },
|
|
322
|
+
ding: { name: "叮", notes: { done: [[987.77, 0, .35]], err: [[246.94, 0, .4]], ask: [[783.99, 0, .18], [1174.66, .2, .42]] } },
|
|
323
|
+
coin: { name: "金币", notes: { done: [[988, 0, .08], [1319, .08, .35], [988, 0, .08, "square"], [1319, .08, .3, "square"]], err: [[196, 0, .12], [147, .11, .35]], ask: [[880, 0, .09], [1108.73, .1, .12], [1318.51, .22, .3]] } },
|
|
324
|
+
bell: { name: "钟声", notes: { done: [[523.25, 0, .5], [659.25, .02, .45], [783.99, .04, .4]], err: [[174.61, 0, .5], [130.81, .05, .5]], ask: [[659.25, 0, .4], [523.25, .05, .55]] } },
|
|
325
|
+
pulse: { name: "脉冲", notes: { done: [[440, 0, .09], [440, .14, .09], [440, .28, .16]], err: [[174.61, 0, .1], [174.61, .14, .1], [174.61, .28, .18]], ask: [[523.25, 0, .09], [523.25, .15, .09], [659.25, .3, .22]] } },
|
|
326
|
+
arp: { name: "琶音", notes: { done: [[523.25, 0, .12], [659.25, .09, .12], [783.99, .18, .12], [1046.5, .27, .3]], err: [[392, 0, .12], [329.63, .1, .12], [261.63, .2, .12], [196, .3, .32]], ask: [[440, 0, .11], [554.37, .12, .11], [659.25, .24, .11], [880, .36, .34]] } },
|
|
317
327
|
};
|
|
318
328
|
// AudioContext 按需创建(浏览器自动播放策略:首次用户交互后才能出声,静默失败不报错)
|
|
319
329
|
let soundCtx = null;
|
|
@@ -338,11 +348,15 @@ function playTone(seq) {
|
|
|
338
348
|
}
|
|
339
349
|
} catch { /* 音频不可用静默 */ }
|
|
340
350
|
}
|
|
341
|
-
// 按音效播放:完成音 /
|
|
351
|
+
// 按音效播放:完成音 / 异常音 / 确认音(未知音效回退 chime)
|
|
342
352
|
function playDoneSound(success, effect) {
|
|
343
353
|
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
344
354
|
playTone(success ? lib.notes.done : lib.notes.err);
|
|
345
355
|
}
|
|
356
|
+
function playAskSound(effect) {
|
|
357
|
+
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
358
|
+
playTone(lib.notes.ask || lib.notes.done);
|
|
359
|
+
}
|
|
346
360
|
// 试听:完成音 + 异常音 连播(间隔 0.55s)
|
|
347
361
|
function previewSound(effect) {
|
|
348
362
|
const lib = SOUND_LIBRARY[effect] || SOUND_LIBRARY.chime;
|
|
@@ -371,12 +385,14 @@ function Toast(props) {
|
|
|
371
385
|
? "var(--dk-ok)"
|
|
372
386
|
: t.kind === "error"
|
|
373
387
|
? "var(--dk-err)"
|
|
374
|
-
: "
|
|
388
|
+
: t.kind === "confirm"
|
|
389
|
+
? "var(--dk-warn)"
|
|
390
|
+
: "var(--dk-warn)";
|
|
375
391
|
return (
|
|
376
|
-
<div className={"dkan-toast" + (closing ? " out" : "")}>
|
|
392
|
+
<div className={"dkan-toast" + (t.kind === "confirm" ? " dkan-toast-confirm" : "") + (closing ? " out" : "")}>
|
|
377
393
|
<div className="dkan-toast-head">
|
|
378
394
|
<span className="dkan-toast-mark" style={{ background: markColor }} />
|
|
379
|
-
<span className="dkan-toast-title" title={t.title}>{t.title}</span>
|
|
395
|
+
<span className="dkan-toast-title" title={t.title}>{t.kind === "confirm" ? "✋ " : ""}{t.title}</span>
|
|
380
396
|
<button type="button" className="dkan-toast-close" aria-label="关闭" onClick={close}>✕</button>
|
|
381
397
|
</div>
|
|
382
398
|
{t.body ? <div className="dkan-toast-body">{t.body}</div> : null}
|
|
@@ -587,6 +603,28 @@ function visibleRect(el) {
|
|
|
587
603
|
const r = el.getBoundingClientRect();
|
|
588
604
|
return r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight ? r : null;
|
|
589
605
|
}
|
|
606
|
+
// 当前正在看的会话标题(侧栏高亮行)。dsh 会话树行 = role="treeitem" + aria-selected,
|
|
607
|
+
// 行文本即会话标题;据此把"需确认/卡住"这类会话级卡片限定到当前会话,别的会话等待时不打扰。
|
|
608
|
+
function currentSessionTitle() {
|
|
609
|
+
if (typeof document === "undefined") return "";
|
|
610
|
+
for (const el of document.querySelectorAll('[role="treeitem"][aria-selected="true"]')) {
|
|
611
|
+
const r = el.getBoundingClientRect();
|
|
612
|
+
// 侧栏在左:跳过正文区可能出现的同名高亮节点(右上搜索结果等)
|
|
613
|
+
if (r.width > 0 && r.height > 0 && r.right <= window.innerWidth * .35) {
|
|
614
|
+
const text = (el.textContent || "").trim();
|
|
615
|
+
if (text) return text;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return "";
|
|
619
|
+
}
|
|
620
|
+
// 指定任务是否属于当前正在看的会话(按标题对齐;侧栏不可见时退回"是"避免整层失效)
|
|
621
|
+
function taskOnScreen(task, titleCache) {
|
|
622
|
+
const title = task && (task.title || "");
|
|
623
|
+
if (!title) return true;
|
|
624
|
+
const current = titleCache !== undefined ? titleCache : currentSessionTitle();
|
|
625
|
+
if (!current) return true;
|
|
626
|
+
return current === title || current.indexOf(title) !== -1 || title.indexOf(current) !== -1;
|
|
627
|
+
}
|
|
590
628
|
const COMPOSER_SEND_SELECTOR = 'button[aria-label="Send message"],button[aria-label*="发送"],button[data-testid*="send"],button[data-testid*="submit"],button[type="submit"]';
|
|
591
629
|
function conversationComposer() {
|
|
592
630
|
if (typeof document === "undefined" || typeof window === "undefined") return null;
|
|
@@ -651,7 +689,6 @@ function useSpaceDocks(enabled) {
|
|
|
651
689
|
let frame = 0;
|
|
652
690
|
let idle = 0;
|
|
653
691
|
let timer = 0;
|
|
654
|
-
let pollTimer = 0;
|
|
655
692
|
let retries = 0;
|
|
656
693
|
let pending = false;
|
|
657
694
|
let observedComposer = null;
|
|
@@ -710,7 +747,21 @@ function useSpaceDocks(enabled) {
|
|
|
710
747
|
if (mutationObserver && document.body) mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
711
748
|
schedule(0, true);
|
|
712
749
|
// 部分会话会复用同一输入框节点、只改变祖先布局;低频校准覆盖这种无 DOM 替换的位移。
|
|
713
|
-
|
|
750
|
+
// 校准只在任务活动时才值得跑:空闲期每 700ms 一次全页 querySelectorAll + getBoundingClientRect
|
|
751
|
+
// 是持续掉帧的来源(每次都强制布局)。空闲 20s 后降频到 5s,任务恢复立即回到快节奏。
|
|
752
|
+
let calibrationInterval = 700;
|
|
753
|
+
let lastActivityAt = Date.now();
|
|
754
|
+
const markActivity = () => { lastActivityAt = Date.now(); if (calibrationInterval !== 700) { calibrationInterval = 700; restartPoll(); } };
|
|
755
|
+
const unsubscribeActivity = animationStore.subscribe(markActivity);
|
|
756
|
+
let pollTimer = 0;
|
|
757
|
+
function restartPoll() {
|
|
758
|
+
if (pollTimer) window.clearInterval(pollTimer);
|
|
759
|
+
pollTimer = window.setInterval(() => {
|
|
760
|
+
if (Date.now() - lastActivityAt > 20000 && calibrationInterval !== 5000) calibrationInterval = 5000;
|
|
761
|
+
schedule();
|
|
762
|
+
}, calibrationInterval);
|
|
763
|
+
}
|
|
764
|
+
restartPoll();
|
|
714
765
|
const onResize = () => {
|
|
715
766
|
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
716
767
|
if (timer) window.clearTimeout(timer);
|
|
@@ -726,6 +777,7 @@ function useSpaceDocks(enabled) {
|
|
|
726
777
|
if (pollTimer) window.clearInterval(pollTimer);
|
|
727
778
|
if (mutationObserver) mutationObserver.disconnect();
|
|
728
779
|
if (resizeObserver) resizeObserver.disconnect();
|
|
780
|
+
unsubscribeActivity();
|
|
729
781
|
window.removeEventListener("resize", onResize);
|
|
730
782
|
};
|
|
731
783
|
}, [enabled]);
|
|
@@ -787,7 +839,8 @@ function MissionDeparture(props) {
|
|
|
787
839
|
}} aria-hidden="true"><FreighterHull units={3} empty /></div>;
|
|
788
840
|
}
|
|
789
841
|
function ProgrammingStuck(props) {
|
|
790
|
-
|
|
842
|
+
const waiting = !!props.waiting;
|
|
843
|
+
return <div className={"dkan-space-stuck" + (waiting ? " waiting" : "")} aria-hidden="true"><FreighterHull units={3} empty /><span className="dkan-stuck-code"><i />{`{ }`}</span><strong>{waiting ? "等待确认" : "编程等待中"}</strong><small>{truncate((props.task && props.task.title) || "工具暂无新响应", 28)}</small></div>;
|
|
791
844
|
}
|
|
792
845
|
|
|
793
846
|
// ---------- 全局浮层:轮询 + 动效 + 通知(功能启用即常驻) ----------
|
|
@@ -915,6 +968,58 @@ export function AnimationOverlay(props) {
|
|
|
915
968
|
prevActiveRef.current = st.active.slice();
|
|
916
969
|
}, [snap.status]);
|
|
917
970
|
|
|
971
|
+
// ===== 需确认检测:工具等待用户批准时通知(页内卡片 + 确认音 + 后台系统通知) =====
|
|
972
|
+
// host 从会话流 approval/asked 收集待确认项,approval/decided 后移除;
|
|
973
|
+
// 这里对"任务首次出现待确认"触发一次提醒,已提醒过的会话不再重复打扰。
|
|
974
|
+
const remindedApprovalsRef = useRef(new Map()); // sessionId -> 提醒过的 approvalId 集合
|
|
975
|
+
useEffect(() => {
|
|
976
|
+
const st = snap.status;
|
|
977
|
+
if (!st || !st.active) return;
|
|
978
|
+
const cfg = st.config || {};
|
|
979
|
+
const activeIds = new Set(st.active.map((x) => x.sessionId));
|
|
980
|
+
for (const [sid, seen] of remindedApprovalsRef.current) {
|
|
981
|
+
if (!activeIds.has(sid)) remindedApprovalsRef.current.delete(sid);
|
|
982
|
+
}
|
|
983
|
+
for (const task of st.active) {
|
|
984
|
+
const approvals = Array.isArray(task.approvals) ? task.approvals : [];
|
|
985
|
+
if (approvals.length === 0) continue;
|
|
986
|
+
let seen = remindedApprovalsRef.current.get(task.sessionId);
|
|
987
|
+
if (!seen) { seen = new Set(); remindedApprovalsRef.current.set(task.sessionId, seen); }
|
|
988
|
+
const fresh = approvals.filter((a) => a && !seen.has(a.id));
|
|
989
|
+
if (fresh.length === 0) continue;
|
|
990
|
+
for (const a of fresh) seen.add(a.id);
|
|
991
|
+
// 提醒开关在"收到新确认项"时检查,保证开关中途打开对下一项生效
|
|
992
|
+
if (!cfg.notifyEnabled || cfg.notifyOnConfirm === false) continue;
|
|
993
|
+
const toolName = fresh[0].toolName || "";
|
|
994
|
+
const scene = toolLabel(toolName);
|
|
995
|
+
const lines = [
|
|
996
|
+
"任务:" + (task.title || "(无标题)"),
|
|
997
|
+
"工具:" + (toolName || "未知") + (scene ? "(" + scene + ")" : ""),
|
|
998
|
+
];
|
|
999
|
+
if (fresh[0].reason) lines.push("说明:" + truncate(fresh[0].reason, 140));
|
|
1000
|
+
lines.push(fresh.length > 1 ? "共 " + fresh.length + " 项等待你的确认" : "请在会话里确认后继续");
|
|
1001
|
+
const toast = {
|
|
1002
|
+
id: Date.now() + Math.random(),
|
|
1003
|
+
kind: "confirm",
|
|
1004
|
+
title: "任务需要确认",
|
|
1005
|
+
body: lines.join("\n"),
|
|
1006
|
+
// 需确认常驻到用户处理(notifyStayMs=0 同款语义),关掉开关的间隙也不会自动消失
|
|
1007
|
+
stayMs: 0,
|
|
1008
|
+
};
|
|
1009
|
+
setToasts((prev) => prev.concat([toast]).slice(-4));
|
|
1010
|
+
if (cfg.soundNotify !== false) playAskSound(cfg.soundEffect);
|
|
1011
|
+
if (cfg.systemNotify && typeof document !== "undefined" && document.hidden
|
|
1012
|
+
&& typeof Notification !== "undefined" && Notification.permission === "granted") {
|
|
1013
|
+
try {
|
|
1014
|
+
new Notification("dsh 任务需要确认", {
|
|
1015
|
+
body: (task.title || "(无标题)") + " · " + (toolName || "工具") + " 等待确认",
|
|
1016
|
+
tag: "dsh-dock-approval-" + task.sessionId,
|
|
1017
|
+
});
|
|
1018
|
+
} catch { /* 系统通知失败不影响页面内通知 */ }
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}, [snap.status]);
|
|
1022
|
+
|
|
918
1023
|
const handleTaskEnd = (task, record, cfg) => {
|
|
919
1024
|
const reason = (record && record.endReason) || "";
|
|
920
1025
|
const success = isSuccessReason(reason);
|
|
@@ -1002,6 +1107,21 @@ export function AnimationOverlay(props) {
|
|
|
1002
1107
|
const phase = active.length > 0
|
|
1003
1108
|
? active.reduce((best, x) => (!best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best), null).phase
|
|
1004
1109
|
: "think";
|
|
1110
|
+
// 有会话在等用户确认时,状态文字让位给"等待确认"(优先级最高:不动手就卡住)。
|
|
1111
|
+
// 会话级卡片(等待确认/编程等待中)只对"当前正在看的会话"出现,别的会话等待不打扰。
|
|
1112
|
+
const waitingTask = active.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
1113
|
+
const sessionTitleRef = useRef({ at: 0, title: "" });
|
|
1114
|
+
const currentTitle = useMemo(() => {
|
|
1115
|
+
// 标题查询要 getBoundingClientRect(强制布局),限频 1s;面板页/任务行为不受此限制
|
|
1116
|
+
const c = sessionTitleRef.current;
|
|
1117
|
+
if (typeof window === "undefined") return "";
|
|
1118
|
+
if (Date.now() - c.at < 1000) return c.title;
|
|
1119
|
+
c.at = Date.now();
|
|
1120
|
+
c.title = currentSessionTitle();
|
|
1121
|
+
return c.title;
|
|
1122
|
+
}, [snap.status]);
|
|
1123
|
+
const onScreenTasks = useMemo(() => active.filter((x) => taskOnScreen(x, currentTitle)), [active, currentTitle]);
|
|
1124
|
+
const waitingTaskOnScreen = onScreenTasks.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
1005
1125
|
useTicker(ctx, animOn);
|
|
1006
1126
|
// 徽标计时:以最早开始的任务为基准
|
|
1007
1127
|
const now = Date.now();
|
|
@@ -1033,7 +1153,8 @@ export function AnimationOverlay(props) {
|
|
|
1033
1153
|
const spaceDocks = useSpaceDocks(spaceModeOn);
|
|
1034
1154
|
const spaceGalaxies = spaceModeOn && spaceDocks ? spaceGalaxyLayout() : null;
|
|
1035
1155
|
// Host 在编程阶段持续 18 秒没有 chunk/tool 活动,才把它视为卡住;正常长任务不会误触发。
|
|
1036
|
-
|
|
1156
|
+
// 卡住卡片同样只对当前会话出现。
|
|
1157
|
+
const stuckTask = onScreenTasks.find((task) => task.phase === "code" && now - (task.lastActivityAt || task.phaseAt || now) >= 18000) || null;
|
|
1037
1158
|
const robotScaleFromConfig = Math.max(.85, Math.min(2.2, Number(cfg && cfg.robotScale) || 1.35));
|
|
1038
1159
|
const speedStyle = { "--dkan-speed": Number(speed).toFixed(2) };
|
|
1039
1160
|
const saveRobotScale = useCallback((robotScale) => {
|
|
@@ -1147,7 +1268,7 @@ export function AnimationOverlay(props) {
|
|
|
1147
1268
|
? <AmbientLayer mode={mode} phase={phase} speed={Number(speed).toFixed(2)} tasks={active} galaxies={spaceGalaxies} /> : null}
|
|
1148
1269
|
{/* 星际模式:空闲时在输入框上方待命;任务卡住时在中央呈现编程等待状态。 */}
|
|
1149
1270
|
{spaceModeOn && spaceDocks && active.length === 0 && deliveries.length === 0 ? <ReadyFreighter dock={spaceDocks.ready} /> : null}
|
|
1150
|
-
{spaceModeOn && stuckTask ? <ProgrammingStuck task={stuckTask} /> : null}
|
|
1271
|
+
{spaceModeOn && stuckTask ? <ProgrammingStuck task={stuckTask} waiting={!!(stuckTask.approvals && stuckTask.approvals.length)} /> : null}
|
|
1151
1272
|
{!panelOpen ? departures.map((departure) => <MissionDeparture key={departure.id} departure={departure} />) : null}
|
|
1152
1273
|
{/* 环屏巡航:一颗光点沿屏幕边缘转圈(orbit 模式) */}
|
|
1153
1274
|
{ambientOn && mode === "orbit" ? <div className="dkan-orbit" style={speedStyle} aria-hidden="true" /> : null}
|
|
@@ -1155,8 +1276,8 @@ export function AnimationOverlay(props) {
|
|
|
1155
1276
|
<BurstLayer bursts={bursts} />
|
|
1156
1277
|
{/* 桌面伙伴:背侧视角紧凑双工位人物,阶段与任务同步;整卡可拖到任意位置(robot 模式) */}
|
|
1157
1278
|
{ambientOn && mode === "robot" ? (
|
|
1158
|
-
<div className="dkan-botcard" role="button" tabIndex={0} style={botCardStyle}
|
|
1159
|
-
title={active.length + " 个任务 · " + phaseLabel(phase) + " · 点击查看任务动画页 · 可拖动到任意位置"}
|
|
1279
|
+
<div className="dkan-botcard" role="button" tabIndex={0} style={Object.assign({}, botCardStyle, waitingTask ? { "--dkan-phase": "#b45309" } : null)}
|
|
1280
|
+
title={active.length + " 个任务 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页 · 可拖动到任意位置"}
|
|
1160
1281
|
onPointerDown={onBotPointerDown}
|
|
1161
1282
|
onPointerMove={onBotPointerMove}
|
|
1162
1283
|
onPointerUp={onBotPointerUp}
|
|
@@ -1165,7 +1286,7 @@ export function AnimationOverlay(props) {
|
|
|
1165
1286
|
onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onBotClick(); } }}>
|
|
1166
1287
|
<RobotScene phase={phase} tasks={active} />
|
|
1167
1288
|
<span className="dkan-bot-cap">
|
|
1168
|
-
<span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {phaseLabel(phase)}
|
|
1289
|
+
<span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}
|
|
1169
1290
|
</span>
|
|
1170
1291
|
<button type="button" className="dkan-bot-resize" aria-label="调整桌面伙伴大小"
|
|
1171
1292
|
title="拖动调整桌面伙伴大小"
|
|
@@ -1185,15 +1306,15 @@ export function AnimationOverlay(props) {
|
|
|
1185
1306
|
{/* 右下角状态徽标:dot 随模式变化(breathe/ring 装饰),颜色随任务阶段、呼吸随吞吐;点击进功能坞动画页 */}
|
|
1186
1307
|
{ambientOn && mode !== "robot" ? (
|
|
1187
1308
|
<button type="button" className={"dkan-badge" + (mode === "breathe" ? " dkan-badge-breathe" : "")}
|
|
1188
|
-
style={Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(phase) })}
|
|
1189
|
-
title={active.length + " 个任务进行中 · " + phaseLabel(phase) + " · 点击查看任务动画页"}
|
|
1309
|
+
style={Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(waitingTask ? "code" : phase) })}
|
|
1310
|
+
title={active.length + " 个任务进行中 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页"}
|
|
1190
1311
|
onClick={() => openPanel("animation")}>
|
|
1191
1312
|
<span className="dkan-dotwrap">
|
|
1192
1313
|
<span className="dkan-dot" />
|
|
1193
1314
|
{mode === "breathe" ? <span className="dkan-halo" /> : null}
|
|
1194
1315
|
{mode === "ring" ? <span className="dkan-ring" /> : null}
|
|
1195
1316
|
</span>
|
|
1196
|
-
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {phaseLabel(phase)}</span>
|
|
1317
|
+
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}</span>
|
|
1197
1318
|
</button>
|
|
1198
1319
|
) : null}
|
|
1199
1320
|
{/* 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场 */}
|
|
@@ -1273,12 +1394,14 @@ function ModePreview({ id }) {
|
|
|
1273
1394
|
function TaskRow(props) {
|
|
1274
1395
|
const t = props.t;
|
|
1275
1396
|
const info = endInfo(t.endReason);
|
|
1397
|
+
const waiting = !props.done && Array.isArray(t.approvals) && t.approvals.length > 0;
|
|
1276
1398
|
return (
|
|
1277
1399
|
<div className="dkan-task">
|
|
1278
1400
|
<div className="dkan-task-head">
|
|
1279
1401
|
<span className="dkan-task-title" title={t.title}>{t.title || "(无标题)"}</span>
|
|
1280
1402
|
{props.done ? <span className={"dkan-tag " + info.cls}>{info.label}</span>
|
|
1281
|
-
: <span className="dkan-tag
|
|
1403
|
+
: waiting ? <span className="dkan-tag warn">等待确认</span>
|
|
1404
|
+
: <span className="dkan-tag on">{phaseLabel(t.phase)}</span>}
|
|
1282
1405
|
</div>
|
|
1283
1406
|
<div className="dkan-task-meta">
|
|
1284
1407
|
<span>{(t.models && t.models.length ? t.models.join(", ") : "未知") + (t.provider ? "(" + t.provider + ")" : "")}</span>
|
|
@@ -1287,6 +1410,11 @@ function TaskRow(props) {
|
|
|
1287
1410
|
{t.totalTokens ? <span>{"↧" + fmtCompact(t.inputTokens) + " ↥" + fmtCompact(t.outputTokens)}</span> : null}
|
|
1288
1411
|
{props.done && t.endTime ? <span>{fmtTime(t.endTime)}</span> : null}
|
|
1289
1412
|
</div>
|
|
1413
|
+
{waiting ? (
|
|
1414
|
+
<div className="dkan-task-err">
|
|
1415
|
+
{t.approvals.map((a, i) => (a && a.toolName ? a.toolName : "未知工具") + (a && a.reason ? ":" + truncate(a.reason, 80) : "")).join(";")}
|
|
1416
|
+
</div>
|
|
1417
|
+
) : null}
|
|
1290
1418
|
{props.done && t.errorMessage ? <div className="dkan-task-err">{truncate(t.errorMessage, 120)}</div> : null}
|
|
1291
1419
|
</div>
|
|
1292
1420
|
);
|
|
@@ -1434,6 +1562,7 @@ function AnimationView(props) {
|
|
|
1434
1562
|
if (!cfg) {
|
|
1435
1563
|
rows.push(<div key="load" className="dkan-note">{snap.error ? "状态不可用:" + snap.error : (snap.loading ? "正在拉取任务状态…" : "等待任务状态")}</div>);
|
|
1436
1564
|
} else {
|
|
1565
|
+
const waitingCount = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
|
|
1437
1566
|
rows.push(
|
|
1438
1567
|
<div key="anim" className="dkan-sec">
|
|
1439
1568
|
<div className="dkan-sec-head">
|
|
@@ -1510,6 +1639,14 @@ function AnimationView(props) {
|
|
|
1510
1639
|
</button>
|
|
1511
1640
|
<span className="dkan-row-sub">出错 / 中止 / 达输出上限时通知</span>
|
|
1512
1641
|
</div>
|
|
1642
|
+
<div className="dkan-row">
|
|
1643
|
+
<span className="dkan-row-label">需确认提醒</span>
|
|
1644
|
+
<button type="button" className={"dkm-miniswitch" + (cfg.notifyOnConfirm !== false ? " on" : "")}
|
|
1645
|
+
onClick={() => patch({ notifyOnConfirm: cfg.notifyOnConfirm === false })}>
|
|
1646
|
+
{cfg.notifyOnConfirm !== false ? "开" : "关"}
|
|
1647
|
+
</button>
|
|
1648
|
+
<span className="dkan-row-sub">工具等待你批准时弹卡片并响确认音(常驻不自动消失)</span>
|
|
1649
|
+
</div>
|
|
1513
1650
|
<div className="dkan-row">
|
|
1514
1651
|
<span className="dkan-row-label">停留时长</span>
|
|
1515
1652
|
<select className="dkan-select" value={String(cfg.notifyStayMs)}
|
|
@@ -1535,7 +1672,7 @@ function AnimationView(props) {
|
|
|
1535
1672
|
onClick={() => patch({ soundNotify: cfg.soundNotify === false })}>
|
|
1536
1673
|
{cfg.soundNotify !== false ? "开" : "关"}
|
|
1537
1674
|
</button>
|
|
1538
|
-
<span className="dkan-row-sub"
|
|
1675
|
+
<span className="dkan-row-sub">任务结束/需确认时播放(试听为先播完成音、后播异常音)</span>
|
|
1539
1676
|
</div>
|
|
1540
1677
|
{cfg.soundNotify !== false ? (
|
|
1541
1678
|
<div className="dkan-sounds">
|
|
@@ -1646,8 +1783,9 @@ function AnimationView(props) {
|
|
|
1646
1783
|
<span className="dkan-sec-title">运行状态</span>
|
|
1647
1784
|
<span className="dkan-sec-sub">
|
|
1648
1785
|
{snap.error ? "状态拉取失败:" + snap.error
|
|
1649
|
-
:
|
|
1650
|
-
:
|
|
1786
|
+
: waitingCount > 0 ? waitingCount + " 项等待确认 · " + active.length + " 个任务进行中"
|
|
1787
|
+
: active.length > 0 ? active.length + " 个任务进行中"
|
|
1788
|
+
: recent.length > 0 ? "空闲 · 显示最近完成" : "空闲 · 暂无任务记录"}
|
|
1651
1789
|
</span>
|
|
1652
1790
|
<button type="button" className="dkan-refresh" onClick={() => animationStore.refresh()}>
|
|
1653
1791
|
{snap.loading ? "刷新中…" : "刷新"}
|
|
@@ -1727,7 +1865,9 @@ const css = [
|
|
|
1727
1865
|
".dkan-delivery-label{position:absolute;left:21px;top:58px;display:flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 30%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-delivery-label b{color:var(--dsw-alias-label-primary);font-weight:650}.dkan-delivery-label span{color:var(--dk-accent);}",
|
|
1728
1866
|
"@media (max-width:1100px){.dkan-space-system,.dkan-space-runner{display:none}}",
|
|
1729
1867
|
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:9994;pointer-events:none;filter:drop-shadow(0 10px 14px rgb(0 0 0 / .18));}.dkan-ready-freighter>span:last-child{position:absolute;left:21px;top:58px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 28%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 80%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-departure{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-departure-flight var(--dkan-departure-duration,8600ms) cubic-bezier(.42,0,.85,.45) forwards;}@keyframes dkan-departure-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}100%{opacity:0;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(.24) rotate(var(--dkan-flight-angle,0deg))}}",
|
|
1730
|
-
".dkan-space-stuck{position:fixed;
|
|
1868
|
+
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:9993;pointer-events:none;border:1px solid color-mix(in srgb,#f59e0b 48%,var(--dsw-alias-border-l1));border-radius:20px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(16px);box-shadow:0 18px 54px rgb(0 0 0 / .28),0 0 30px color-mix(in srgb,#f59e0b 15%,transparent);}.dkan-space-stuck .dkan-freighter{left:37px;top:11px;transform:scale(.9);transform-origin:top left;}.dkan-stuck-code{position:absolute;left:17px;right:17px;top:60px;height:36px;border-radius:7px;background:linear-gradient(90deg,color-mix(in srgb,#f59e0b 18%,transparent),transparent);color:var(--dk-warn);font:700 14px/36px var(--ds-font-family-code,monospace);letter-spacing:.12em;text-align:center;overflow:hidden;}.dkan-stuck-code::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fef3c7 70%,transparent),transparent);transform:translateX(-110%);animation:dkan-stuck-scan 1.25s linear infinite;}.dkan-stuck-code i{display:inline-block;width:7px;height:7px;margin-right:8px;border-radius:50%;background:#f59e0b;box-shadow:0 0 9px #f59e0b;animation:dkan-stuck-pulse .8s ease-in-out infinite alternate;}.dkan-space-stuck strong{position:absolute;left:0;right:0;top:108px;color:var(--dsw-alias-label-primary);font:650 13px/1.2 var(--ds-font-family,system-ui,sans-serif);text-align:center;}.dkan-space-stuck small{position:absolute;left:18px;right:18px;top:128px;color:var(--dsw-alias-label-secondary);font:11px/1.25 var(--ds-font-family,system-ui,sans-serif);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@keyframes dkan-stuck-scan{to{transform:translateX(110%)}}@keyframes dkan-stuck-pulse{to{transform:scale(.55);opacity:.45}}",
|
|
1869
|
+
// 窄屏(手机/分屏)贴右会溢出:回落到水平居中、屏幕上方,避开正文重心
|
|
1870
|
+
"@media (max-width:760px){.dkan-space-stuck{right:50%;transform:translateX(50%);top:64px;}}",
|
|
1731
1871
|
// 极光:顶部柔光带呼吸流动(加浓)
|
|
1732
1872
|
".dkan-aurora i{position:absolute;top:-40%;left:-20%;width:70%;height:80%;border-radius:50%;filter:blur(50px);opacity:.32;animation:dkan-aurora ease-in-out infinite alternate;}",
|
|
1733
1873
|
".dkan-aurora i:nth-child(1){background:#4d9fff;}",
|
|
@@ -1816,6 +1956,9 @@ const css = [
|
|
|
1816
1956
|
".dkan-toast-close{cursor:pointer;flex:none;border:none;background:transparent;color:var(--dsw-alias-label-tertiary);border-radius:6px;width:22px;height:22px;font-size:11px;line-height:1;display:inline-flex;align-items:center;justify-content:center;font-family:inherit;}",
|
|
1817
1957
|
".dkan-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);}",
|
|
1818
1958
|
".dkan-toast-body{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.7;white-space:pre-line;word-break:break-word;}",
|
|
1959
|
+
// 需确认卡片:琥珀描边 + 缓慢呼吸光晕,常驻提醒直到用户处理
|
|
1960
|
+
".dkan-toast-confirm{border-color:color-mix(in srgb,var(--dk-warn) 55%,transparent);animation:dkan-toast-in .32s var(--ds-ease-in-out),dkan-confirm-glow 2.4s ease-in-out 0.4s infinite;}",
|
|
1961
|
+
"@keyframes dkan-confirm-glow{0%,100%{box-shadow:0 10px 36px rgb(0 0 0 / .22),0 0 0 0 color-mix(in srgb,var(--dk-warn) 30%,transparent)}50%{box-shadow:0 10px 36px rgb(0 0 0 / .22),0 0 14px 2px color-mix(in srgb,var(--dk-warn) 32%,transparent)}}",
|
|
1819
1962
|
// 面板页布局
|
|
1820
1963
|
".dkan-root{display:flex;flex-direction:column;gap:10px;}",
|
|
1821
1964
|
".dkan-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dock",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计)、模型设置、图片理解代理、模型余额、任务动画(
|
|
3
|
+
"version": "0.9.3",
|
|
4
|
+
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D 场景、完成/需确认通知与提示音、钉钉/飞书推送,开关独立、配置持久化);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
package/src/client.jsx
CHANGED
|
@@ -28,7 +28,7 @@ import { feature as fGames } from "../features/games/view.jsx";
|
|
|
28
28
|
import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
|
|
29
29
|
|
|
30
30
|
const name = "dsh-dock";
|
|
31
|
-
const DOCK_VERSION = "0.9.
|
|
31
|
+
const DOCK_VERSION = "0.9.3";
|
|
32
32
|
|
|
33
33
|
// ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
|
|
34
34
|
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation, fGames, fMobileRelay];
|
package/src/host-core.js
CHANGED
|
@@ -27,6 +27,8 @@ export const DockConfig = z.object({
|
|
|
27
27
|
notifyEnabled: z.boolean().default(true),
|
|
28
28
|
notifyOnComplete: z.boolean().default(true),
|
|
29
29
|
notifyOnError: z.boolean().default(true),
|
|
30
|
+
// 工具等待用户确认/批准时提醒(dsh 会话流 approval/asked → decided)
|
|
31
|
+
notifyOnConfirm: z.boolean().default(true),
|
|
30
32
|
// 通知停留毫秒数(0 = 常驻直到手动关闭)
|
|
31
33
|
notifyStayMs: z.number().default(8000),
|
|
32
34
|
// 浏览器系统通知(页面后台时推送)
|