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.
@@ -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:模块被 web-install.ps1 复制到
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
- * 不自旋的保证:`recheck` 只在**已有查询在飞**时被置位,而置位者只能是事件
231
+ * 事件驱动的 `recheck` 只在**已有查询在飞**时被置位,而置位者只能是事件
202
232
  * (track/settle/invalidate 都会推进 version)或新到达的 waiter。每次 rejection
203
- * 最多消费一个 `recheck`,因此「连续 reject 且没有新事件」时第二轮之后不再重跑。
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) { record.recheck = false; scheduleCheck(record); }
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) { record.recheck = false; scheduleCheck(record); }
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,