dsh-dock 0.17.0 → 0.19.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 +4 -3
- package/client.js +661 -98
- package/features/notify/host.js +44 -0
- package/features/notify/view.jsx +590 -49
- package/package.json +2 -2
- package/src/client.jsx +1 -1
- package/src/host-core.js +31 -0
- package/src/task-track.js +48 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dock",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计,支持各模型自定义单价、多段分时价与周末/节假日谷价)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计,支持各模型自定义单价、多段分时价与周末/节假日谷价)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D 场景)、任务通知(完成/异常/需确认通知、提示音、语音播报可选音色、系统通知、钉钉/飞书推送)、运行状态(进行中任务与最近完成一览)、语音朗读(浏览器 TTS 读回复:回合下方喇叭按钮 + 自动朗读);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
package/src/client.jsx
CHANGED
|
@@ -33,7 +33,7 @@ import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
|
|
|
33
33
|
import { feature as fMobile } from "../features/mobile/view.js";
|
|
34
34
|
|
|
35
35
|
const name = "dsh-dock";
|
|
36
|
-
const DOCK_VERSION = "0.
|
|
36
|
+
const DOCK_VERSION = "0.19.0";
|
|
37
37
|
|
|
38
38
|
// ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
|
|
39
39
|
const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fProviderConfig, fHeartbeat, fTheme, fBalance, fAnimation, fNotify, fRunState, fTts, fGames, fMobileRelay, fMobile];
|
package/src/host-core.js
CHANGED
|
@@ -101,6 +101,15 @@ export const DockConfig = z.object({
|
|
|
101
101
|
notifyOnError: z.boolean().default(true),
|
|
102
102
|
// 工具等待用户确认/批准时提醒(dsh 会话流 approval/asked → decided)
|
|
103
103
|
notifyOnConfirm: z.boolean().default(true),
|
|
104
|
+
// 卡住提醒卡片:编程阶段长时间无活动时弹一张(与 voiceOnStuck 同口径,但开关独立)
|
|
105
|
+
notifyOnStuck: z.boolean().default(true),
|
|
106
|
+
// 页内卡片总开关:关闭后右侧所有通知卡片一律不再弹出(含已挂着的)。
|
|
107
|
+
// 只管卡片——语音播报、提示音、系统通知、钉钉/飞书推送各有独立开关,不受它影响。
|
|
108
|
+
notifyCards: z.boolean().default(true),
|
|
109
|
+
// 卡片口径三挡(右侧卡片正文写多少):brief = 任务与耗时/谁卡住;medium = 加模型、
|
|
110
|
+
// 起止时间、停滞时长;detailed = 全部(回合步骤、Token、摘要或错误、最后动作)。
|
|
111
|
+
// 默认 detailed =历史内容——未设置时卡片不缩水。
|
|
112
|
+
cardMode: z.string().default('detailed'),
|
|
104
113
|
// 通知停留毫秒数(0 = 常驻直到手动关闭)
|
|
105
114
|
notifyStayMs: z.number().default(8000),
|
|
106
115
|
// 浏览器系统通知(页面后台时推送)
|
|
@@ -115,6 +124,23 @@ export const DockConfig = z.object({
|
|
|
115
124
|
// 飞书群机器人推送(宿主侧直发,浏览器关着也能推;事件跟随 notifyOnComplete/notifyOnError)
|
|
116
125
|
feishuEnabled: z.boolean().default(false),
|
|
117
126
|
feishuWebhook: z.string().default(''),
|
|
127
|
+
// ===== 语音播报(浏览器 speechSynthesis;与【语音朗读】同一套声音但相互独立开关) =====
|
|
128
|
+
// 总开关:关闭时下面所有播报都不发声(默认关,避免升级后突然说话吓人)。
|
|
129
|
+
voiceNotify: z.boolean().default(false),
|
|
130
|
+
// 播报口径三挡:brief = 一句话;medium = 点名任务 + 原因/耗时/停滞时长;
|
|
131
|
+
// detailed = 带标题/耗时/回合统计/工具名/最后动作。
|
|
132
|
+
voiceMode: z.string().default('brief'),
|
|
133
|
+
// 四个播报时刻,各自独立开关(开总开关后按这里筛选):
|
|
134
|
+
voiceOnComplete: z.boolean().default(true), // 任务正常完成
|
|
135
|
+
voiceOnError: z.boolean().default(true), // 任务停止(出错/中止/受阻/达上限/中断)
|
|
136
|
+
voiceOnConfirm: z.boolean().default(true), // 任务需要授权(工具等待批准)
|
|
137
|
+
voiceOnStuck: z.boolean().default(true), // 任务卡住(编程阶段 18 秒无活动,与【任务动画】同口径)
|
|
138
|
+
// 播报语速/音量/语言/音色。voiceURI 与【语音朗读】同款语义:设备相关的音色标识,
|
|
139
|
+
// 空 = 按语言自动挑;本机音色表里找不到时由客户端按语言回落(见 features/notify/view.jsx)。
|
|
140
|
+
voiceRate: z.number().default(1),
|
|
141
|
+
voiceVolume: z.number().default(1),
|
|
142
|
+
voiceLang: z.string().default(''),
|
|
143
|
+
voiceURI: z.string().default(''),
|
|
118
144
|
// 一次性迁移标记:animation 段的旧通知配置已搬到本段(搬完置 true,避免每次启动重复写)
|
|
119
145
|
migratedFromAnimation: z.boolean().default(false),
|
|
120
146
|
}).default({}).volatile(),
|
|
@@ -243,8 +269,13 @@ export async function mutateDockSection(ctx, path, value, expectedRevision) {
|
|
|
243
269
|
/** 通知配置字段清单(schema、迁移、客户端默认值三处共用同一份键名)。 */
|
|
244
270
|
export const NOTIFY_FIELDS = [
|
|
245
271
|
'notifyOnComplete', 'notifyOnError', 'notifyOnConfirm', 'notifyStayMs',
|
|
272
|
+
'notifyOnStuck', 'notifyCards', 'cardMode',
|
|
246
273
|
'systemNotify', 'soundNotify', 'soundEffect',
|
|
247
274
|
'dingtalkEnabled', 'dingtalkWebhook', 'feishuEnabled', 'feishuWebhook',
|
|
275
|
+
// 语音播报(新增段字段;旧 animation 段里不存在,迁移循环自然不会搬它们)
|
|
276
|
+
'voiceNotify', 'voiceMode',
|
|
277
|
+
'voiceOnComplete', 'voiceOnError', 'voiceOnConfirm', 'voiceOnStuck',
|
|
278
|
+
'voiceRate', 'voiceVolume', 'voiceLang', 'voiceURI',
|
|
248
279
|
]
|
|
249
280
|
|
|
250
281
|
/**
|
package/src/task-track.js
CHANGED
|
@@ -88,6 +88,32 @@ function createTracker(ctx) {
|
|
|
88
88
|
return ''
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* 会话标题 + 主/子任务归属:优先 readTitleSnapshot(一次观测同时拿到标题与会话头),
|
|
93
|
+
* 会话头里的 origin === 'subagent' / delegationDepth 是权威的子任务标记(见 dsh-session
|
|
94
|
+
* SessionHeader),fallback 路径只拿标题、按主任务处理。取不到头不报错——播报退化为
|
|
95
|
+
* 不带归属的旧口径,功能不受影响(测试桩只需实现 readTitle/readSession 也能跑)。
|
|
96
|
+
* @returns {Promise<{ title: string, header: object|null }>}
|
|
97
|
+
*/
|
|
98
|
+
async function readSessionFacts(sessionId, sessionQuery) {
|
|
99
|
+
const facts = { title: '', header: null }
|
|
100
|
+
if (!sessionQuery) return facts
|
|
101
|
+
try {
|
|
102
|
+
if (typeof sessionQuery.readTitleSnapshot === 'function') {
|
|
103
|
+
const obs = await sessionQuery.readTitleSnapshot(sessionId)
|
|
104
|
+
if (obs) {
|
|
105
|
+
const t = obs.title
|
|
106
|
+
if (typeof t === 'string') facts.title = t
|
|
107
|
+
else if (t && typeof t.title === 'string') facts.title = t.title
|
|
108
|
+
if (obs.session && typeof obs.session === 'object') facts.header = obs.session
|
|
109
|
+
if (facts.title || facts.header) return facts
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch { /* 换下一种读法 */ }
|
|
113
|
+
facts.title = await getSessionTitle(sessionId, sessionQuery)
|
|
114
|
+
return facts
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
function addActiveSession(sid, sessionQuery, extra = {}) {
|
|
92
118
|
if (activeSessions.has(sid)) return activeSessions.get(sid)
|
|
93
119
|
// 不在终端打「任务开始」日志:开始/结束状态界面(运行状态页)已实时可见
|
|
@@ -108,6 +134,11 @@ function createTracker(ctx) {
|
|
|
108
134
|
errorMessage: '',
|
|
109
135
|
lastText: '',
|
|
110
136
|
firstPrompt: '',
|
|
137
|
+
// 主/子任务归属 + 最近一次调用的工具:卡住播报与卡住卡要说清「哪里卡住、最后在干什么」
|
|
138
|
+
//(isSubtask 由会话头 origin/delegationDepth 异步判定,判定前按主任务处理)
|
|
139
|
+
isSubtask: false,
|
|
140
|
+
parentSessionId: '',
|
|
141
|
+
lastToolName: '',
|
|
111
142
|
// 工具等待用户确认(dsh 会话流 approval/asked):浏览器侧据此弹"需确认"通知
|
|
112
143
|
approvals: new Map(), // approvalId -> { toolName, reason, askedAt }
|
|
113
144
|
phase: 'think',
|
|
@@ -120,10 +151,17 @@ function createTracker(ctx) {
|
|
|
120
151
|
...extra,
|
|
121
152
|
}
|
|
122
153
|
activeSessions.set(sid, record)
|
|
123
|
-
|
|
154
|
+
readSessionFacts(sid, sessionQuery).then((facts) => {
|
|
124
155
|
const s = activeSessions.get(sid)
|
|
125
|
-
if (s)
|
|
126
|
-
|
|
156
|
+
if (!s) return
|
|
157
|
+
if (facts.title) s.title = facts.title
|
|
158
|
+
const h = facts.header
|
|
159
|
+
if (h) {
|
|
160
|
+
s.isSubtask = h.origin === 'subagent'
|
|
161
|
+
|| (typeof h.delegationDepth === 'number' && h.delegationDepth > 0)
|
|
162
|
+
s.parentSessionId = typeof h.parentSession === 'string' && h.parentSession ? h.parentSession : ''
|
|
163
|
+
}
|
|
164
|
+
}).catch(() => { /* 标题/归属读失败:title 走 firstPrompt 兜底,归属按主任务 */ })
|
|
127
165
|
return record
|
|
128
166
|
}
|
|
129
167
|
|
|
@@ -229,6 +267,9 @@ function createTracker(ctx) {
|
|
|
229
267
|
s.toolCalls++
|
|
230
268
|
s.phase = phaseOfToolName(event.data && event.data.name)
|
|
231
269
|
s.phaseAt = now
|
|
270
|
+
// 记下最后调用的工具名:卡住播报/卡住卡要回答「最后在干什么」
|
|
271
|
+
const called = event.data && typeof event.data.name === 'string' ? event.data.name : ''
|
|
272
|
+
if (called) s.lastToolName = called
|
|
232
273
|
// 工具启动是稀疏事件,给一个更明显的短促动作,检索/写文件也不会显得停住。
|
|
233
274
|
s.motionTicks += s.phase === 'search' ? 7 : 9
|
|
234
275
|
s.lastMotionAt = now
|
|
@@ -329,6 +370,10 @@ function createTracker(ctx) {
|
|
|
329
370
|
lastActivityAt: s.lastActivityAt,
|
|
330
371
|
motionTicks: s.motionTicks,
|
|
331
372
|
lastMotionAt: s.lastMotionAt,
|
|
373
|
+
// 卡住播报/卡住卡专用:主/子归属(会话头判定)与最后调用的工具
|
|
374
|
+
isSubtask: !!s.isSubtask,
|
|
375
|
+
parentSessionId: s.parentSessionId || '',
|
|
376
|
+
lastToolName: s.lastToolName || '',
|
|
332
377
|
approvals: [...s.approvals.entries()].map(([id, v]) => ({ id, ...v })),
|
|
333
378
|
})
|
|
334
379
|
}
|