dsh-expert-group 0.1.2 → 0.1.4
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 +12 -139
- package/client-bundle.js +1578 -74
- package/client-expert-group-sidebar.js +842 -21
- package/client.js +736 -53
- package/host/system-toast.js +3 -2
- package/host/turn-wait.js +78 -6
- package/index.js +851 -103
- package/orchestrator.js +3 -3
- package/package.json +25 -28
- package/prompt-contracts.js +2 -0
- package/scripts/assemble-client.mjs +1 -1
- package/shared.js +122 -31
- package/desktop-uninstall.ps1 +0 -679
- package/web-install.ps1 +0 -219
- package/web-uninstall.ps1 +0 -962
package/host/system-toast.js
CHANGED
|
@@ -192,8 +192,9 @@ export function buildToastXml({ title, taskName, body } = {}) {
|
|
|
192
192
|
/**
|
|
193
193
|
* 默认提示音路径:以本模块文件位置解析包内 assets/notification-whoosh.wav。
|
|
194
194
|
*
|
|
195
|
-
* 用 import.meta.url 而不是 __dirname/cwd
|
|
196
|
-
* profiles\packages\dsh-expert-group
|
|
195
|
+
* 用 import.meta.url 而不是 __dirname/cwd:assets/ 随 package.json 的 files 白名单发布,
|
|
196
|
+
* 安装副本(web 的 profiles\packages\dsh-expert-group、desktop 的 profile node_modules)
|
|
197
|
+
* 无论落在哪里,都按模块路径解析到同一份随包发布的资源。
|
|
197
198
|
* 返回值只用于 stdin JSON,绝不进入命令行、日志、诊断或脚本文本。
|
|
198
199
|
*/
|
|
199
200
|
export function defaultNotificationAudioPath() {
|
package/host/turn-wait.js
CHANGED
|
@@ -124,6 +124,33 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// 查询结果未知或仍发现运行中的后代时,使用无期限的退避复查。timer 只服务于
|
|
128
|
+
// 当前仍有 waiter 的活动记录;记录退休或最后一个 waiter 取消时必须清除它。
|
|
129
|
+
const RETRY_INITIAL_DELAY = 10;
|
|
130
|
+
const RETRY_MAX_DELAY = 1000;
|
|
131
|
+
function clearRetryTimer(record) {
|
|
132
|
+
if (record.retryTimer === void 0) return;
|
|
133
|
+
clearTimeout(record.retryTimer);
|
|
134
|
+
record.retryTimer = void 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function scheduleRetry(record) {
|
|
138
|
+
if (disposed || record.destroyed === true || record.released === true
|
|
139
|
+
|| record.waiters.size === 0 || record.retryTimer !== void 0) return;
|
|
140
|
+
const delay = record.retryDelay;
|
|
141
|
+
const timer = setTimeout(() => {
|
|
142
|
+
if (record.retryTimer !== timer) return;
|
|
143
|
+
record.retryTimer = void 0;
|
|
144
|
+
if (disposed || record.destroyed === true || record.released === true
|
|
145
|
+
|| record.waiters.size === 0) return;
|
|
146
|
+
scheduleCheck(record);
|
|
147
|
+
}, delay);
|
|
148
|
+
record.retryTimer = timer;
|
|
149
|
+
// unref 只避免测试/宿主进程因一个永不超时的等待记录被强行保持;进程仍活跃时 timer 正常执行。
|
|
150
|
+
timer.unref?.();
|
|
151
|
+
record.retryDelay = Math.min(RETRY_MAX_DELAY, delay * 2);
|
|
152
|
+
}
|
|
153
|
+
|
|
127
154
|
/**
|
|
128
155
|
* **唯一**的记录销毁路径:等待者收口、监听解除、登记表移除、exact Agent 强引用清空。
|
|
129
156
|
*
|
|
@@ -148,6 +175,7 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
148
175
|
record.checking = false;
|
|
149
176
|
record.recheck = false;
|
|
150
177
|
record.inSnapshot = false;
|
|
178
|
+
clearRetryTimer(record);
|
|
151
179
|
releaseListeners(record);
|
|
152
180
|
settleWaiters(record, released === true, reason);
|
|
153
181
|
record.works.clear();
|
|
@@ -165,6 +193,8 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
165
193
|
*/
|
|
166
194
|
function scheduleCheck(record) {
|
|
167
195
|
if (disposed || record.destroyed === true || record.released === true) return;
|
|
196
|
+
// 显式活动事件或新 waiter 优先于退避 timer,避免同一记录并发发起重复查询。
|
|
197
|
+
clearRetryTimer(record);
|
|
168
198
|
// 正在 onKnownSnapshot 的同步回调里:本次结算由外层判定统一收口,不再嵌套发起查询。
|
|
169
199
|
if (record.inSnapshot === true) return;
|
|
170
200
|
if (record.checking) {
|
|
@@ -198,9 +228,9 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
198
228
|
* 不消费该标志,那次「有新活动,请复查」的意图就被吞掉,等待可能永久悬挂。因此
|
|
199
229
|
* 两条分支都必须消费 `recheck` 并重跑一次查询。
|
|
200
230
|
*
|
|
201
|
-
*
|
|
231
|
+
* 事件驱动的 `recheck` 只在**已有查询在飞**时被置位,而置位者只能是事件
|
|
202
232
|
* (track/settle/invalidate 都会推进 version)或新到达的 waiter。每次 rejection
|
|
203
|
-
* 最多消费一个 `recheck
|
|
233
|
+
* 最多消费一个 `recheck`;没有事件时不会同步自旋,unknown 结果改由退避 timer 异步复查。
|
|
204
234
|
*/
|
|
205
235
|
function handleQuerySettled(record, queryId, version, result, error) {
|
|
206
236
|
if (record.activeQueryId !== queryId) return;
|
|
@@ -210,10 +240,12 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
210
240
|
|
|
211
241
|
if (error !== void 0) {
|
|
212
242
|
report(error);
|
|
213
|
-
// unknown
|
|
243
|
+
// 查询失败仍是 unknown:保留 fail-closed,并在有 waiter 时退避重查。
|
|
214
244
|
if (record.recheck === true) {
|
|
215
245
|
record.recheck = false;
|
|
216
246
|
scheduleCheck(record);
|
|
247
|
+
} else {
|
|
248
|
+
scheduleRetry(record);
|
|
217
249
|
}
|
|
218
250
|
return;
|
|
219
251
|
}
|
|
@@ -229,8 +261,13 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
229
261
|
const known = result?.known === true;
|
|
230
262
|
const running = known && Array.isArray(result.running) ? result.running : [];
|
|
231
263
|
if (!known) {
|
|
232
|
-
//
|
|
233
|
-
if (record.recheck === true) {
|
|
264
|
+
// 未知(结构非法 / 状态无法确定):继续等待,**绝不**当成无人运行。
|
|
265
|
+
if (record.recheck === true) {
|
|
266
|
+
record.recheck = false;
|
|
267
|
+
scheduleCheck(record);
|
|
268
|
+
} else {
|
|
269
|
+
scheduleRetry(record);
|
|
270
|
+
}
|
|
234
271
|
return;
|
|
235
272
|
}
|
|
236
273
|
// 明确快照:先给调用方一次「完成通知缺失」的回退结算机会。回调只减少未结算工作,
|
|
@@ -246,7 +283,14 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
246
283
|
// child 创建或 fanout retry。
|
|
247
284
|
if (record.works.size > 0 || running.length > 0) {
|
|
248
285
|
// 仍有未结算工作,或确有 running 后代:继续等待。
|
|
249
|
-
if (record.recheck === true) {
|
|
286
|
+
if (record.recheck === true) {
|
|
287
|
+
record.recheck = false;
|
|
288
|
+
scheduleCheck(record);
|
|
289
|
+
} else if (record.works.size === 0 && running.length > 0) {
|
|
290
|
+
// 登记工作已清空后,后代仍可能静默转为非运行,不能依赖 agent/status/disposed
|
|
291
|
+
// 事件才能再次观察;工作尚未结算时仍由其精确 settlement 触发复查。
|
|
292
|
+
scheduleRetry(record);
|
|
293
|
+
}
|
|
250
294
|
return;
|
|
251
295
|
}
|
|
252
296
|
if (!isLiveRoot(record.agent)) {
|
|
@@ -285,6 +329,8 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
285
329
|
cancelled: false,
|
|
286
330
|
destroyed: false,
|
|
287
331
|
releaseReason: void 0,
|
|
332
|
+
retryTimer: void 0,
|
|
333
|
+
retryDelay: RETRY_INITIAL_DELAY,
|
|
288
334
|
unlisten: void 0,
|
|
289
335
|
};
|
|
290
336
|
if (typeof record.rootId !== "string" || record.rootId.length === 0) return void 0;
|
|
@@ -348,6 +394,30 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
348
394
|
scheduleCheck(record);
|
|
349
395
|
}
|
|
350
396
|
|
|
397
|
+
/**
|
|
398
|
+
* 取消当前 root 的所有 waiter,但保留未结算工作与监听。
|
|
399
|
+
*
|
|
400
|
+
* 用户插话只应打断上一轮 root 的等待,让新一轮消息立即进入处理;专家 child
|
|
401
|
+
* 仍可能运行,admission 也必须继续保留,后续 turn 才能再次等待同一批工作。
|
|
402
|
+
*
|
|
403
|
+
* @param {object} agent - exact root Agent。
|
|
404
|
+
* @param {string} reason - 取消原因码。
|
|
405
|
+
* @returns {boolean} 是否至少取消了一个 waiter。
|
|
406
|
+
*/
|
|
407
|
+
function cancelWaiters(agent, reason = "cancelled") {
|
|
408
|
+
const record = records.get(agent);
|
|
409
|
+
if (record === void 0 || record.destroyed === true || record.waiters.size === 0) return false;
|
|
410
|
+
settleWaiters(record, false, reason);
|
|
411
|
+
clearRetryTimer(record);
|
|
412
|
+
// 让已经在飞的查询失效;它的迟到结果只能复位内部状态,不能重新收口旧 waiter。
|
|
413
|
+
if (record.checking) {
|
|
414
|
+
record.activeQueryId = ++record.querySequence;
|
|
415
|
+
record.checking = false;
|
|
416
|
+
record.recheck = false;
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
|
|
351
421
|
/**
|
|
352
422
|
* 等待该 root 静止。**永不超时**:只有精确结算 + root-scoped 明确空闲、
|
|
353
423
|
* 或 retire/dispose 才会收口。
|
|
@@ -371,6 +441,7 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
371
441
|
waiter.cleanup?.();
|
|
372
442
|
waiter.cleanup = void 0;
|
|
373
443
|
waiter.resolve({ released: false, reason: "signal-aborted" });
|
|
444
|
+
if (record.waiters.size === 0) clearRetryTimer(record);
|
|
374
445
|
// 最后一个 waiter 取消后,忽略它启动的在途快照并释放 checking。
|
|
375
446
|
if (record.waiters.size === 0 && record.checking) {
|
|
376
447
|
record.activeQueryId = ++record.querySequence;
|
|
@@ -452,6 +523,7 @@ export function createRootTurnWaitRegistry(options = {}) {
|
|
|
452
523
|
track,
|
|
453
524
|
settle,
|
|
454
525
|
invalidate,
|
|
526
|
+
cancelWaiters,
|
|
455
527
|
engage,
|
|
456
528
|
isEngaged,
|
|
457
529
|
wait,
|