@kanadego/dsh-heartbeat 1.7.2 → 1.7.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 +13 -1
- package/dist/index.js +257 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -183,7 +183,9 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
|
|
|
183
183
|
|
|
184
184
|
| 插件版本 | 适配的 DSH | 说明 |
|
|
185
185
|
|---|---|---|
|
|
186
|
-
| **v1.7.
|
|
186
|
+
| **v1.7.4**(当前) | ≥ `0.1.2-rc.1` | 修复引擎室会话无限增长撑爆上下文、心跳整体哑火的问题(自动轮换正身) |
|
|
187
|
+
| v1.7.3 | ≥ `0.1.2-rc.1` | 修复心跳进行中拖住宿主关闭/更新的问题 |
|
|
188
|
+
| v1.7.2 | ≥ `0.1.2-rc.1`(新增 `0.2.0-rc.1`) | 声明适配 `0.2.0-rc.1`,无行为改动 |
|
|
187
189
|
| v1.7.1 | ≥ `0.1.2-rc.1` | 修复 `0.1.7` 下「心跳模式」预设丢失:预设改为声明式注册,不再依赖文件系统用户根 |
|
|
188
190
|
| v1.7.0 | ≥ `0.1.2-rc.1`(在 `0.1.7-rc.2` 上验收) | 适配 0.1.7 的接口换代(设置接口 / 客户端服务 / 节律配置自持 / 会话消息源);新增 psy 分区开关与节省 token 模式 |
|
|
189
191
|
| v1.6.0 – v1.6.4 | ≥ `0.1.2-rc.1` | 素材闭环 + 视觉识别;修好 `0.1.5` 下节律配置保存不生效。**建议直接用 v1.6.4**:v1.6.3 的设置卡有渲染 bug,别停在那一版 |
|
|
@@ -199,6 +201,16 @@ dsh plugin --profile web remove @kanadego/dsh-heartbeat
|
|
|
199
201
|
<details open>
|
|
200
202
|
<summary><b>v1.7.x</b></summary>
|
|
201
203
|
|
|
204
|
+
### v1.7.4 · 2026-09-30(修复:引擎室会话撑爆上下文,心跳哑火)
|
|
205
|
+
|
|
206
|
+
修复了引擎室会话上下文涨过模型的上下文窗口后导致**合并、反刍、投递全部失败**,且宿主的自动压缩也救不了的bug。现在心跳会**自动轮换引擎室会话**:事件数超阈值、或某轮因上下文溢出失败时,自动新建一个干净的引擎室会话接替,旧会话原样留在会话列表里(画像、账本、素材池都在插件数据目录,不受影响)。
|
|
207
|
+
|
|
208
|
+
另外:设置卡「会话绑定」的会话列表现在自动隐藏**已归档**和**子代理**会话。
|
|
209
|
+
|
|
210
|
+
### v1.7.3 · 2026-09-29(修复:心跳拖住 DSH 关闭)
|
|
211
|
+
|
|
212
|
+
修复心跳正在跑时 DSH 关不掉的问题——此前桌面端更新若恰逢一跳在跑,宿主等心跳结束等不到,更新会报「未能安全停止任务」。现在收到关闭信号,心跳会立刻停下手里这一跳,不再拖住宿主。
|
|
213
|
+
|
|
202
214
|
### v1.7.2 · 2026-09-29(适配 DSH `0.2.0-rc.1`)
|
|
203
215
|
|
|
204
216
|
声明适配 `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
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
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
|
|
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(
|
|
1118
|
+
fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(snapshot2, null, 1), "utf8");
|
|
1041
1119
|
} catch {
|
|
1042
1120
|
}
|
|
1043
|
-
writePulseStream(paths,
|
|
1044
|
-
return
|
|
1121
|
+
writePulseStream(paths, snapshot2);
|
|
1122
|
+
return snapshot2;
|
|
1045
1123
|
}
|
|
1046
|
-
function writePulseStream(paths,
|
|
1124
|
+
function writePulseStream(paths, snapshot2) {
|
|
1047
1125
|
try {
|
|
1048
1126
|
appendAuditLine(path3.join(paths.logsDir, "envpulse.jsonl"), {
|
|
1049
1127
|
event: "pulse",
|
|
1050
|
-
takenAt:
|
|
1051
|
-
idleSeconds:
|
|
1052
|
-
presence:
|
|
1053
|
-
windowClass:
|
|
1054
|
-
daypart:
|
|
1055
|
-
weekday:
|
|
1056
|
-
isWeekend:
|
|
1057
|
-
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
|
|
1803
|
-
if (Array.isArray(
|
|
1880
|
+
const snapshot2 = session.snapshotEvents();
|
|
1881
|
+
if (Array.isArray(snapshot2)) return snapshot2;
|
|
1804
1882
|
} catch {
|
|
1805
1883
|
}
|
|
1806
1884
|
}
|
|
@@ -1811,10 +1889,24 @@ function sessionEventCount(session) {
|
|
|
1811
1889
|
if (typeof session.seq === "number") return session.seq;
|
|
1812
1890
|
return sessionEvents(session).length;
|
|
1813
1891
|
}
|
|
1892
|
+
var HOME_ROTATE_EVENT_COUNT = 3e3;
|
|
1893
|
+
function isContextOverflowError(msg) {
|
|
1894
|
+
return /CONTEXT_WINDOW_EXCEEDED|context overflow/i.test(msg);
|
|
1895
|
+
}
|
|
1896
|
+
function shouldRotateHome(input) {
|
|
1897
|
+
return input.eventCount >= (input.threshold ?? HOME_ROTATE_EVENT_COUNT);
|
|
1898
|
+
}
|
|
1814
1899
|
var IDLE_WAIT_TIMEOUT_MS = 24e4;
|
|
1815
1900
|
var EXPRESSION_IDLE_WAIT_MS = 6e5;
|
|
1816
1901
|
var agentPromise = null;
|
|
1817
1902
|
var beating = false;
|
|
1903
|
+
var beatCancel;
|
|
1904
|
+
var homeRotatePending = null;
|
|
1905
|
+
function markHomeRotateIfOverflow(error) {
|
|
1906
|
+
if (homeRotatePending === null && isContextOverflowError(String(error))) {
|
|
1907
|
+
homeRotatePending = `context overflow: ${String(error).slice(0, 120)}`;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1818
1910
|
var lastBeat = null;
|
|
1819
1911
|
function getLastBeat() {
|
|
1820
1912
|
return lastBeat;
|
|
@@ -1859,49 +1951,88 @@ function defaultAgentOptions(ctx) {
|
|
|
1859
1951
|
}
|
|
1860
1952
|
return void 0;
|
|
1861
1953
|
}
|
|
1954
|
+
function makeHomeSetup(deps) {
|
|
1955
|
+
const { ctx, paths } = deps;
|
|
1956
|
+
return async (agentCtx) => {
|
|
1957
|
+
const notes = [];
|
|
1958
|
+
const presetId = deps.agentPreset ?? "heartbeat";
|
|
1959
|
+
try {
|
|
1960
|
+
const presets = agentCtx.get("agentPresets");
|
|
1961
|
+
if (typeof presets?.mount !== "function") {
|
|
1962
|
+
notes.push("preset=no-api");
|
|
1963
|
+
} else {
|
|
1964
|
+
const preset = await presets.mount(agentCtx, presetId);
|
|
1965
|
+
const joined = preset?.id;
|
|
1966
|
+
notes.push(`preset=mounted(${joined ?? presetId})`);
|
|
1967
|
+
}
|
|
1968
|
+
} catch (e) {
|
|
1969
|
+
notes.push(`preset=threw(${String(e).slice(0, 200)})`);
|
|
1970
|
+
}
|
|
1971
|
+
const tools = agentCtx.get("tools");
|
|
1972
|
+
if (typeof tools?.restrict !== "function") {
|
|
1973
|
+
notes.push("restrict=no-api");
|
|
1974
|
+
} else {
|
|
1975
|
+
const allow = ["web_search"];
|
|
1976
|
+
try {
|
|
1977
|
+
tools.restrict({ allow });
|
|
1978
|
+
notes.push(`restrict=ok allow=${allow.join("|")}`);
|
|
1979
|
+
} catch (e) {
|
|
1980
|
+
notes.push(`restrict=threw(${String(e).slice(0, 200)})`);
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
const visible = (tools.schemas?.() ?? []).map((s) => String(s?.name ?? "?")).sort();
|
|
1984
|
+
notes.push(`visibleGlobal=${visible.length > 0 ? visible.join(",") : "(empty)"}`);
|
|
1985
|
+
} catch (e) {
|
|
1986
|
+
notes.push(`visibleGlobal=threw(${String(e).slice(0, 60)})`);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
ctx.logger.info("heartbeat: tool policy %s", notes.join(" "));
|
|
1990
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "tool_policy", policy: notes.join(" ") });
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
async function rotateHome(deps, reason) {
|
|
1994
|
+
const { ctx, guard, paths } = deps;
|
|
1995
|
+
const old = readBeatState(guard, paths).sessionId;
|
|
1996
|
+
const freshId = `session-${randomUUID2()}`;
|
|
1997
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
1998
|
+
event: "home_rotate_start",
|
|
1999
|
+
from: old,
|
|
2000
|
+
to: freshId,
|
|
2001
|
+
reason
|
|
2002
|
+
});
|
|
2003
|
+
const handle = await withTimeout(
|
|
2004
|
+
Promise.resolve(ctx.agents.create({
|
|
2005
|
+
sessionId: freshId,
|
|
2006
|
+
meta: { cwd: paths.dataDir },
|
|
2007
|
+
...defaultAgentOptions(ctx) ? { agentOptions: defaultAgentOptions(ctx) } : {},
|
|
2008
|
+
setup: makeHomeSetup(deps)
|
|
2009
|
+
})),
|
|
2010
|
+
3e4,
|
|
2011
|
+
"home rotate create timeout (30s)"
|
|
2012
|
+
);
|
|
2013
|
+
const agent = unwrapHomeHandle(handle);
|
|
2014
|
+
const realId = agent.session?.id ?? freshId;
|
|
2015
|
+
writeBeatState(guard, paths, { sessionId: realId });
|
|
2016
|
+
agentPromise = null;
|
|
2017
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
2018
|
+
event: "home_rotate_done",
|
|
2019
|
+
from: old,
|
|
2020
|
+
to: realId,
|
|
2021
|
+
reason
|
|
2022
|
+
});
|
|
2023
|
+
ctx.logger.info("heartbeat: home rotated %s -> %s (%s)", old ?? "(none)", realId, reason);
|
|
2024
|
+
}
|
|
2025
|
+
function unwrapHomeHandle(handle) {
|
|
2026
|
+
return handle.agent ?? handle;
|
|
2027
|
+
}
|
|
1862
2028
|
async function ensureAgent(deps) {
|
|
1863
2029
|
if (agentPromise) return agentPromise;
|
|
1864
2030
|
const { ctx, paths, guard } = deps;
|
|
1865
2031
|
const agentOptions = defaultAgentOptions(ctx);
|
|
2032
|
+
const setup = makeHomeSetup(deps);
|
|
1866
2033
|
agentPromise = (async () => {
|
|
1867
2034
|
const saved = readBeatState(guard, paths);
|
|
1868
2035
|
const savedId = saved.sessionId;
|
|
1869
|
-
const setup = async (agentCtx) => {
|
|
1870
|
-
const notes = [];
|
|
1871
|
-
const presetId = deps.agentPreset ?? "heartbeat";
|
|
1872
|
-
try {
|
|
1873
|
-
const presets = agentCtx.get("agentPresets");
|
|
1874
|
-
if (typeof presets?.mount !== "function") {
|
|
1875
|
-
notes.push("preset=no-api");
|
|
1876
|
-
} else {
|
|
1877
|
-
const preset = await presets.mount(agentCtx, presetId);
|
|
1878
|
-
const joined = preset?.id;
|
|
1879
|
-
notes.push(`preset=mounted(${joined ?? presetId})`);
|
|
1880
|
-
}
|
|
1881
|
-
} catch (e) {
|
|
1882
|
-
notes.push(`preset=threw(${String(e).slice(0, 200)})`);
|
|
1883
|
-
}
|
|
1884
|
-
const tools = agentCtx.get("tools");
|
|
1885
|
-
if (typeof tools?.restrict !== "function") {
|
|
1886
|
-
notes.push("restrict=no-api");
|
|
1887
|
-
} else {
|
|
1888
|
-
const allow = ["web_search"];
|
|
1889
|
-
try {
|
|
1890
|
-
tools.restrict({ allow });
|
|
1891
|
-
notes.push(`restrict=ok allow=${allow.join("|")}`);
|
|
1892
|
-
} catch (e) {
|
|
1893
|
-
notes.push(`restrict=threw(${String(e).slice(0, 200)})`);
|
|
1894
|
-
}
|
|
1895
|
-
try {
|
|
1896
|
-
const visible = (tools.schemas?.() ?? []).map((s) => String(s?.name ?? "?")).sort();
|
|
1897
|
-
notes.push(`visibleGlobal=${visible.length > 0 ? visible.join(",") : "(empty)"}`);
|
|
1898
|
-
} catch (e) {
|
|
1899
|
-
notes.push(`visibleGlobal=threw(${String(e).slice(0, 60)})`);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
ctx.logger.info("heartbeat: tool policy %s", notes.join(" "));
|
|
1903
|
-
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "tool_policy", policy: notes.join(" ") });
|
|
1904
|
-
};
|
|
1905
2036
|
const unwrap = (handle) => handle.agent ?? handle;
|
|
1906
2037
|
try {
|
|
1907
2038
|
let agent;
|
|
@@ -2033,6 +2164,7 @@ async function agentTurn(deps, agent, prompt, label, idleWaitMs = IDLE_WAIT_TIME
|
|
|
2033
2164
|
...turnError ? { turnError } : {},
|
|
2034
2165
|
window: shapes.slice(0, 12)
|
|
2035
2166
|
});
|
|
2167
|
+
if (turnError) markHomeRotateIfOverflow(turnError);
|
|
2036
2168
|
return "";
|
|
2037
2169
|
}
|
|
2038
2170
|
async function withTimeout(p, ms, label) {
|
|
@@ -2468,6 +2600,28 @@ async function beat(deps) {
|
|
|
2468
2600
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "beat_start" });
|
|
2469
2601
|
const beatStart = new Date(now).toISOString();
|
|
2470
2602
|
const agent = await ensureAgent(deps);
|
|
2603
|
+
if (agent) {
|
|
2604
|
+
beatCancel = () => {
|
|
2605
|
+
try {
|
|
2606
|
+
agent.cancel?.();
|
|
2607
|
+
} catch {
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
if (agent && homeRotatePending === null && shouldRotateHome({ eventCount: sessionEventCount(agent.session) })) {
|
|
2612
|
+
homeRotatePending = `size threshold (${sessionEventCount(agent.session)} events \u2265 ${HOME_ROTATE_EVENT_COUNT})`;
|
|
2613
|
+
}
|
|
2614
|
+
if (agent && homeRotatePending !== null) {
|
|
2615
|
+
try {
|
|
2616
|
+
await rotateHome(deps, homeRotatePending);
|
|
2617
|
+
homeRotatePending = null;
|
|
2618
|
+
} catch (e) {
|
|
2619
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "home_rotate_failed", error: String(e).slice(0, 160) });
|
|
2620
|
+
}
|
|
2621
|
+
writeBeatStatus(deps, { beatStart, wandered: false });
|
|
2622
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "silent", reason: "home rotated, next beat starts fresh" });
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2471
2625
|
let wandered = false;
|
|
2472
2626
|
if (agent) {
|
|
2473
2627
|
deferredRetries = 0;
|
|
@@ -2499,6 +2653,7 @@ async function beat(deps) {
|
|
|
2499
2653
|
writeBeatStatus(deps, { beatStart, wandered });
|
|
2500
2654
|
} catch (e) {
|
|
2501
2655
|
deps.ctx.logger.error("heartbeat: beat failed: %s", String(e).slice(0, 200));
|
|
2656
|
+
markHomeRotateIfOverflow(e);
|
|
2502
2657
|
try {
|
|
2503
2658
|
appendAuditLine(deps.paths.logsDir + "/heartbeat.jsonl", { event: "beat_error", error: String(e).slice(0, 200) });
|
|
2504
2659
|
noteBeat("error", { reason: String(e).slice(0, 120) });
|
|
@@ -2506,6 +2661,7 @@ async function beat(deps) {
|
|
|
2506
2661
|
}
|
|
2507
2662
|
} finally {
|
|
2508
2663
|
beating = false;
|
|
2664
|
+
beatCancel = void 0;
|
|
2509
2665
|
}
|
|
2510
2666
|
}
|
|
2511
2667
|
function startOrchestrator(deps) {
|
|
@@ -2541,7 +2697,12 @@ function startOrchestrator(deps) {
|
|
|
2541
2697
|
return () => {
|
|
2542
2698
|
if (timer) clearInterval(timer);
|
|
2543
2699
|
if (first) clearTimeout(first);
|
|
2544
|
-
|
|
2700
|
+
const cancelledInFlight = beating && beatCancel !== void 0;
|
|
2701
|
+
try {
|
|
2702
|
+
beatCancel?.();
|
|
2703
|
+
} catch {
|
|
2704
|
+
}
|
|
2705
|
+
appendAuditLine(deps.paths.logsDir + "/heartbeat.jsonl", { event: "orchestrator_disposed", beatCancelled: cancelledInFlight });
|
|
2545
2706
|
deps.ctx.logger.info("heartbeat: orchestrator timer disposed");
|
|
2546
2707
|
};
|
|
2547
2708
|
}, "heartbeat: timer");
|
|
@@ -2847,6 +3008,27 @@ function loadSessionTitles() {
|
|
|
2847
3008
|
return titles;
|
|
2848
3009
|
}
|
|
2849
3010
|
}
|
|
3011
|
+
function loadVisibleSessionIds(registryFile) {
|
|
3012
|
+
try {
|
|
3013
|
+
const file = registryFile ?? path12.join(os.homedir(), ".dsh", "storages", "workspace.json");
|
|
3014
|
+
const j = JSON.parse(fs12.readFileSync(file, "utf8"));
|
|
3015
|
+
const archived = new Set(
|
|
3016
|
+
(Array.isArray(j.global?.archivedSessionIds) ? j.global.archivedSessionIds : []).map((s) => String(s))
|
|
3017
|
+
);
|
|
3018
|
+
const isArchived = (id) => archived.has(id) || archived.has(id.replace(/^session-/, ""));
|
|
3019
|
+
const visible = /* @__PURE__ */ new Set();
|
|
3020
|
+
for (const ws of Object.values(j.tables?.workspaces ?? {})) {
|
|
3021
|
+
const ids = Array.isArray(ws?.sessionIds) ? ws.sessionIds : [];
|
|
3022
|
+
for (const raw of ids) {
|
|
3023
|
+
const id = String(raw);
|
|
3024
|
+
if (!isArchived(id)) visible.add(id);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return { visible, registryFound: true };
|
|
3028
|
+
} catch {
|
|
3029
|
+
return { visible: /* @__PURE__ */ new Set(), registryFound: false };
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
2850
3032
|
function installHeartbeatRpc(ctx, deps) {
|
|
2851
3033
|
ctx.inject(["connection"], (scoped) => {
|
|
2852
3034
|
const remoteCtx = scoped;
|
|
@@ -2904,10 +3086,12 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2904
3086
|
const bindings = loadBindings(guard, paths.settingsDir).bindings;
|
|
2905
3087
|
const home = homeSessionId(paths, guard);
|
|
2906
3088
|
const titles = loadSessionTitles();
|
|
3089
|
+
const { visible, registryFound } = loadVisibleSessionIds();
|
|
2907
3090
|
const out = [];
|
|
2908
3091
|
if (fs12.existsSync(root)) {
|
|
2909
3092
|
for (const slug of fs12.readdirSync(root)) {
|
|
2910
3093
|
for (const id of fs12.readdirSync(path12.join(root, slug))) {
|
|
3094
|
+
if (registryFound && id !== home && !visible.has(id)) continue;
|
|
2911
3095
|
const binding = bindings.find((b) => b.sessionId === id);
|
|
2912
3096
|
out.push({
|
|
2913
3097
|
id,
|