@kanadego/dsh-heartbeat 1.7.2 → 1.7.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 CHANGED
@@ -183,7 +183,8 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
183
183
 
184
184
  | 插件版本 | 适配的 DSH | 说明 |
185
185
  |---|---|---|
186
- | **v1.7.2**(当前) | ≥ `0.1.2-rc.1`(新增 `0.2.0-rc.1`) | 声明适配 `0.2.0-rc.1`,无行为改动 |
186
+ | **v1.7.3**(当前) | ≥ `0.1.2-rc.1` | 修复心跳进行中拖住宿主关闭/更新的问题 |
187
+ | v1.7.2 | ≥ `0.1.2-rc.1`(新增 `0.2.0-rc.1`) | 声明适配 `0.2.0-rc.1`,无行为改动 |
187
188
  | v1.7.1 | ≥ `0.1.2-rc.1` | 修复 `0.1.7` 下「心跳模式」预设丢失:预设改为声明式注册,不再依赖文件系统用户根 |
188
189
  | v1.7.0 | ≥ `0.1.2-rc.1`(在 `0.1.7-rc.2` 上验收) | 适配 0.1.7 的接口换代(设置接口 / 客户端服务 / 节律配置自持 / 会话消息源);新增 psy 分区开关与节省 token 模式 |
189
190
  | v1.6.0 – v1.6.4 | ≥ `0.1.2-rc.1` | 素材闭环 + 视觉识别;修好 `0.1.5` 下节律配置保存不生效。**建议直接用 v1.6.4**:v1.6.3 的设置卡有渲染 bug,别停在那一版 |
@@ -199,6 +200,10 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
199
200
  <details open>
200
201
  <summary><b>v1.7.x</b></summary>
201
202
 
203
+ ### v1.7.3 · 2026-09-29(修复:心跳拖住 DSH 关闭)
204
+
205
+ 修复心跳正在跑时 DSH 关不掉的问题——此前桌面端更新若恰逢一跳在跑,宿主等心跳结束等不到,更新会报「未能安全停止任务」。现在收到关闭信号,心跳会立刻停下手里这一跳,不再拖住宿主。
206
+
202
207
  ### v1.7.2 · 2026-09-29(适配 DSH `0.2.0-rc.1`)
203
208
 
204
209
  声明适配 `0.2.0-rc.1`,无行为改动。
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ import {
64
64
  removeBinding
65
65
  } from "./chunk-J6ZTRFFW.js";
66
66
 
67
- // node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.3/node_modules/@deepseek-ai/cosmokit/lib/index.js
67
+ // node_modules/@deepseek-ai/cosmokit/lib/index.js
68
68
  function isNullable(value) {
69
69
  return value === null || value === void 0;
70
70
  }
@@ -83,6 +83,32 @@ function pick(source, keys, forced) {
83
83
  for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
84
84
  return result;
85
85
  }
86
+ var write = /* @__PURE__ */ Symbol.for("cosmokit.volatile.write");
87
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
88
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
89
+ if (value === null || typeof value !== "object") return value;
90
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
91
+ ancestors.add(value);
92
+ try {
93
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
94
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
95
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
96
+ } finally {
97
+ ancestors.delete(value);
98
+ }
99
+ }
100
+ function createVolatile(value) {
101
+ let current = snapshot(value);
102
+ return Object.freeze({
103
+ get: () => current,
104
+ [write]: (value2) => {
105
+ current = value2;
106
+ }
107
+ });
108
+ }
109
+ function isVolatile(value) {
110
+ return typeof value === "object" && value !== null && write in value;
111
+ }
86
112
  function is(type, value) {
87
113
  if (arguments.length === 1) return (value2) => is(type, value2);
88
114
  return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
@@ -161,24 +187,37 @@ function clone(source, refs = /* @__PURE__ */ new Map()) {
161
187
  return result;
162
188
  }
163
189
  function deepEqual(a, b, strict) {
164
- if (a === b) return true;
165
- if (!strict && isNullable(a) && isNullable(b)) return true;
166
- if (typeof a !== typeof b) return false;
167
- if (typeof a !== "object") return false;
168
- if (!a || !b) return false;
169
- function check(test, then) {
170
- return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
171
- }
172
- return check(Array.isArray, (a2, b2) => a2.length === b2.length && a2.every((item, index) => deepEqual(item, b2[index]))) ?? check(is("Date"), (a2, b2) => a2.valueOf() === b2.valueOf()) ?? check(is("RegExp"), (a2, b2) => a2.source === b2.source && a2.flags === b2.flags) ?? check(isArrayBufferLike, (a2, b2) => {
173
- if (a2.byteLength !== b2.byteLength) return false;
174
- const viewA = new Uint8Array(a2);
175
- const viewB = new Uint8Array(b2);
176
- for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
177
- return true;
178
- }) ?? Object.keys({
179
- ...a,
180
- ...b
181
- }).every((key) => deepEqual(a[key], b[key], strict));
190
+ const ancestors = /* @__PURE__ */ new Set();
191
+ function compare(a2, b2) {
192
+ if (a2 === b2) return true;
193
+ if (isVolatile(a2) || isVolatile(b2)) return isVolatile(a2) && isVolatile(b2);
194
+ if (!strict && isNullable(a2) && isNullable(b2)) return true;
195
+ if (typeof a2 !== typeof b2 || typeof a2 !== "object" || !a2 || !b2) return false;
196
+ if (ancestors.has(a2)) return false;
197
+ function check(test, then) {
198
+ return test(a2) ? test(b2) ? then(a2, b2) : false : test(b2) ? false : void 0;
199
+ }
200
+ ancestors.add(a2);
201
+ try {
202
+ return check(Array.isArray, (a3, b3) => {
203
+ if (a3.length !== b3.length) return false;
204
+ for (let index = 0; index < a3.length; index++) if (!compare(a3[index], b3[index])) return false;
205
+ return true;
206
+ }) ?? check(is("Date"), (a3, b3) => a3.valueOf() === b3.valueOf()) ?? check(is("URL"), (a3, b3) => a3.href === b3.href) ?? check(is("RegExp"), (a3, b3) => a3.source === b3.source && a3.flags === b3.flags) ?? check(isArrayBufferLike, (a3, b3) => {
207
+ if (a3.byteLength !== b3.byteLength) return false;
208
+ const viewA = new Uint8Array(a3);
209
+ const viewB = new Uint8Array(b3);
210
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
211
+ return true;
212
+ }) ?? ((!strict || [a2, b2].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
213
+ ...a2,
214
+ ...b2
215
+ }).every((key) => compare(a2[key], b2[key])));
216
+ } finally {
217
+ ancestors.delete(a2);
218
+ }
219
+ }
220
+ return compare(a, b);
182
221
  }
183
222
  var Time;
184
223
  (function(Time2) {
@@ -250,7 +289,7 @@ var Time;
250
289
  Time2.template = template;
251
290
  })(Time || (Time = {}));
252
291
 
253
- // node_modules/.pnpm/@deepseek-ai+schemastery@3.18.2/node_modules/@deepseek-ai/schemastery/lib/index.mjs
292
+ // node_modules/@deepseek-ai/schemastery/lib/index.mjs
254
293
  var kSchema = /* @__PURE__ */ Symbol.for("schemastery");
255
294
  var kValidationError = /* @__PURE__ */ Symbol.for("ValidationError");
256
295
  globalThis.__schemastery_index__ ??= 0;
@@ -427,6 +466,7 @@ Schema.prototype.pattern = function pattern(regexp) {
427
466
  return schema;
428
467
  };
429
468
  Schema.prototype.simplify = function simplify(value) {
469
+ if (isVolatile(value)) value = value.get();
430
470
  if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
431
471
  if (isNullable(value)) return value;
432
472
  if (this.type === "object" || this.type === "dict") {
@@ -484,12 +524,49 @@ for (const key of [
484
524
  };
485
525
  return schema;
486
526
  } });
527
+ Schema.prototype.volatile = function volatile() {
528
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
529
+ return this.extra("volatile", true);
530
+ };
487
531
  var resolvers = {};
532
+ var checkedVolatile = /* @__PURE__ */ Symbol("checked-volatile-schema");
533
+ function validateVolatileSchema(schema, path13 = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
534
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
535
+ if (states.has(blocked)) return;
536
+ states.add(blocked);
537
+ seen.set(schema, states);
538
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path: path13 });
539
+ const nested = blocked || !!schema.meta?.volatile;
540
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path13, key], nested, seen);
541
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path13, "<key>"], true, seen);
542
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path13, "*"], true, seen);
543
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path13, String(index)], true, seen);
544
+ }
488
545
  Schema.extend = function extend(type, resolve2) {
489
546
  resolvers[type] = resolve2;
490
547
  };
491
548
  Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
492
549
  if (!schema) return [data];
550
+ if (!options[checkedVolatile]) {
551
+ validateVolatileSchema(schema, options.path);
552
+ options = {
553
+ ...options,
554
+ [checkedVolatile]: true
555
+ };
556
+ }
557
+ if (schema.meta?.volatile) {
558
+ const inner = Schema(schema);
559
+ inner.meta = {
560
+ ...schema.meta,
561
+ volatile: false
562
+ };
563
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
564
+ try {
565
+ return [createVolatile(value), adapted];
566
+ } catch (error) {
567
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
568
+ }
569
+ }
493
570
  if (options.ignore?.(data, schema)) return [data];
494
571
  if (isNullable(data) && schema.type !== "lazy") {
495
572
  if (schema.meta.required) throw new ValidationError(`missing required value`, options);
@@ -597,6 +674,7 @@ Schema.extend("lazy", (data, schema, options, strict) => {
597
674
  ...schema.meta,
598
675
  ...schema.inner.meta
599
676
  };
677
+ validateVolatileSchema(schema.inner, options.path, true);
600
678
  }
601
679
  return Schema.resolve(data, schema.inner, options, strict);
602
680
  });
@@ -696,7 +774,7 @@ function property(data, key, schema, options) {
696
774
  } catch (e) {
697
775
  if (!options?.autofix) throw e;
698
776
  delete data[key];
699
- return schema.meta.default;
777
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
700
778
  }
701
779
  }
702
780
  Schema.extend("array", (data, { inner, meta }, options) => {
@@ -1026,7 +1104,7 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
1026
1104
  const idle = probeIdle(guard, paths);
1027
1105
  const windowClass = classifyProcess(fgProcess, rules);
1028
1106
  const t = timeContext(now);
1029
- const snapshot = {
1107
+ const snapshot2 = {
1030
1108
  takenAt: now.toISOString(),
1031
1109
  idleSeconds: idle,
1032
1110
  presence: presenceOf(idle, windowClass),
@@ -1037,24 +1115,24 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
1037
1115
  festival: t.festival
1038
1116
  };
1039
1117
  try {
1040
- fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(snapshot, null, 1), "utf8");
1118
+ fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(snapshot2, null, 1), "utf8");
1041
1119
  } catch {
1042
1120
  }
1043
- writePulseStream(paths, snapshot);
1044
- return snapshot;
1121
+ writePulseStream(paths, snapshot2);
1122
+ return snapshot2;
1045
1123
  }
1046
- function writePulseStream(paths, snapshot) {
1124
+ function writePulseStream(paths, snapshot2) {
1047
1125
  try {
1048
1126
  appendAuditLine(path3.join(paths.logsDir, "envpulse.jsonl"), {
1049
1127
  event: "pulse",
1050
- takenAt: snapshot.takenAt,
1051
- idleSeconds: snapshot.idleSeconds,
1052
- presence: snapshot.presence,
1053
- windowClass: snapshot.windowClass,
1054
- daypart: snapshot.daypart,
1055
- weekday: snapshot.weekday,
1056
- isWeekend: snapshot.isWeekend,
1057
- festival: snapshot.festival
1128
+ takenAt: snapshot2.takenAt,
1129
+ idleSeconds: snapshot2.idleSeconds,
1130
+ presence: snapshot2.presence,
1131
+ windowClass: snapshot2.windowClass,
1132
+ daypart: snapshot2.daypart,
1133
+ weekday: snapshot2.weekday,
1134
+ isWeekend: snapshot2.isWeekend,
1135
+ festival: snapshot2.festival
1058
1136
  });
1059
1137
  } catch {
1060
1138
  }
@@ -1799,8 +1877,8 @@ function sessionEvents(session) {
1799
1877
  if (!session) return [];
1800
1878
  if (typeof session.snapshotEvents === "function") {
1801
1879
  try {
1802
- const snapshot = session.snapshotEvents();
1803
- if (Array.isArray(snapshot)) return snapshot;
1880
+ const snapshot2 = session.snapshotEvents();
1881
+ if (Array.isArray(snapshot2)) return snapshot2;
1804
1882
  } catch {
1805
1883
  }
1806
1884
  }
@@ -1815,6 +1893,7 @@ var IDLE_WAIT_TIMEOUT_MS = 24e4;
1815
1893
  var EXPRESSION_IDLE_WAIT_MS = 6e5;
1816
1894
  var agentPromise = null;
1817
1895
  var beating = false;
1896
+ var beatCancel;
1818
1897
  var lastBeat = null;
1819
1898
  function getLastBeat() {
1820
1899
  return lastBeat;
@@ -2468,6 +2547,14 @@ async function beat(deps) {
2468
2547
  appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "beat_start" });
2469
2548
  const beatStart = new Date(now).toISOString();
2470
2549
  const agent = await ensureAgent(deps);
2550
+ if (agent) {
2551
+ beatCancel = () => {
2552
+ try {
2553
+ agent.cancel?.();
2554
+ } catch {
2555
+ }
2556
+ };
2557
+ }
2471
2558
  let wandered = false;
2472
2559
  if (agent) {
2473
2560
  deferredRetries = 0;
@@ -2506,6 +2593,7 @@ async function beat(deps) {
2506
2593
  }
2507
2594
  } finally {
2508
2595
  beating = false;
2596
+ beatCancel = void 0;
2509
2597
  }
2510
2598
  }
2511
2599
  function startOrchestrator(deps) {
@@ -2541,7 +2629,12 @@ function startOrchestrator(deps) {
2541
2629
  return () => {
2542
2630
  if (timer) clearInterval(timer);
2543
2631
  if (first) clearTimeout(first);
2544
- appendAuditLine(deps.paths.logsDir + "/heartbeat.jsonl", { event: "orchestrator_disposed" });
2632
+ const cancelledInFlight = beating && beatCancel !== void 0;
2633
+ try {
2634
+ beatCancel?.();
2635
+ } catch {
2636
+ }
2637
+ appendAuditLine(deps.paths.logsDir + "/heartbeat.jsonl", { event: "orchestrator_disposed", beatCancelled: cancelledInFlight });
2545
2638
  deps.ctx.logger.info("heartbeat: orchestrator timer disposed");
2546
2639
  };
2547
2640
  }, "heartbeat: timer");