@kanadego/dsh-heartbeat 1.7.4 → 1.9.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 +52 -15
- package/client.js +93 -0
- package/config/policy.json +73 -64
- package/dist/{chunk-ON4MSU6E.js → chunk-2SYXKKZF.js} +286 -480
- package/dist/chunk-2SYXKKZF.js.map +1 -0
- package/dist/{chunk-4UE74TUB.js → chunk-63FUJLIF.js} +2 -2
- package/dist/{chunk-LLD7LUNN.js → chunk-IFTFDHZX.js} +2 -1
- package/dist/chunk-IPOZFZPY.js +177 -0
- package/dist/chunk-IPOZFZPY.js.map +1 -0
- package/dist/{chunk-SVP2NDRF.js → chunk-PQTSXW4F.js} +38 -9
- package/dist/chunk-PQTSXW4F.js.map +1 -0
- package/dist/chunk-RB3NLLGF.js +87 -0
- package/dist/chunk-RB3NLLGF.js.map +1 -0
- package/dist/chunk-VIZNIQLK.js +472 -0
- package/dist/chunk-VIZNIQLK.js.map +1 -0
- package/dist/cli/index.js +149 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-MMLHISQV.js → inbox-57NHQHY3.js} +3 -3
- package/dist/index.js +790 -209
- package/dist/index.js.map +1 -1
- package/dist/migrate-N6NXMZ3G.js +20 -0
- package/dist/{pool-YAIBE2L5.js → pool-7I4VACG2.js} +3 -3
- package/dist/pool-7I4VACG2.js.map +1 -0
- package/dist/snapshot-ZBCSX67Y.js +15 -0
- package/dist/snapshot-ZBCSX67Y.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-ON4MSU6E.js.map +0 -1
- package/dist/chunk-SVP2NDRF.js.map +0 -1
- /package/dist/{chunk-4UE74TUB.js.map → chunk-63FUJLIF.js.map} +0 -0
- /package/dist/{chunk-LLD7LUNN.js.map → chunk-IFTFDHZX.js.map} +0 -0
- /package/dist/{inbox-MMLHISQV.js.map → inbox-57NHQHY3.js.map} +0 -0
- /package/dist/{pool-YAIBE2L5.js.map → migrate-N6NXMZ3G.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
adviseRefillWander,
|
|
3
3
|
adviseWander,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
atomicWriteJsonSync,
|
|
4
|
+
appendEntry,
|
|
5
|
+
buildWeeklyPrompt,
|
|
7
6
|
completeWander,
|
|
8
7
|
createPathGuard,
|
|
9
8
|
deepMerge,
|
|
10
9
|
describeInstall,
|
|
11
10
|
ensureRegistered,
|
|
11
|
+
ensureWeeklyAnchor,
|
|
12
12
|
installBundledPreset,
|
|
13
13
|
ledgerFilePath,
|
|
14
|
+
listWeeklyReports,
|
|
14
15
|
loadPolicy,
|
|
15
|
-
|
|
16
|
-
loadProfileSchema,
|
|
16
|
+
markDone,
|
|
17
17
|
pendingOlderThan,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
preferenceFilePath,
|
|
19
|
+
readLedger,
|
|
20
|
+
readWeeklyReport,
|
|
21
|
+
recordDelivery,
|
|
22
|
+
renderTemplateReport,
|
|
23
|
+
saveWeeklyReport,
|
|
22
24
|
scanPending,
|
|
23
25
|
sendNewMessageHint,
|
|
26
|
+
sendWeeklyReadyHint,
|
|
24
27
|
updateUserPolicy,
|
|
25
|
-
userPresetRoot
|
|
26
|
-
|
|
28
|
+
userPresetRoot,
|
|
29
|
+
weeklyDue
|
|
30
|
+
} from "./chunk-2SYXKKZF.js";
|
|
27
31
|
import {
|
|
28
32
|
getRuntime,
|
|
29
33
|
setRuntime
|
|
@@ -39,14 +43,35 @@ import {
|
|
|
39
43
|
restoreSeed,
|
|
40
44
|
seedsFilePath,
|
|
41
45
|
surfaceSeed
|
|
42
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-PQTSXW4F.js";
|
|
43
47
|
import {
|
|
44
48
|
dedupeItems,
|
|
45
49
|
inboxClear,
|
|
46
50
|
inboxCount,
|
|
47
51
|
inboxDrain,
|
|
48
52
|
inboxFilePath
|
|
49
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-63FUJLIF.js";
|
|
54
|
+
import {
|
|
55
|
+
snapshotIfDue
|
|
56
|
+
} from "./chunk-RB3NLLGF.js";
|
|
57
|
+
import {
|
|
58
|
+
appendAuditLine,
|
|
59
|
+
applyOpsToDoc,
|
|
60
|
+
atomicWriteJsonSync,
|
|
61
|
+
journalFilePath,
|
|
62
|
+
loadProfile,
|
|
63
|
+
loadProfileSchema,
|
|
64
|
+
persistWithJournal,
|
|
65
|
+
profileFilePath,
|
|
66
|
+
pruneAuditFile,
|
|
67
|
+
readAuditLines,
|
|
68
|
+
runDeterministicAging
|
|
69
|
+
} from "./chunk-VIZNIQLK.js";
|
|
70
|
+
import {
|
|
71
|
+
addBinding,
|
|
72
|
+
loadBindings,
|
|
73
|
+
removeBinding
|
|
74
|
+
} from "./chunk-J6ZTRFFW.js";
|
|
50
75
|
import {
|
|
51
76
|
decryptFile,
|
|
52
77
|
encryptFile,
|
|
@@ -57,14 +82,9 @@ import {
|
|
|
57
82
|
saveEncryptedText,
|
|
58
83
|
saveJson,
|
|
59
84
|
writeText
|
|
60
|
-
} from "./chunk-
|
|
61
|
-
import {
|
|
62
|
-
addBinding,
|
|
63
|
-
loadBindings,
|
|
64
|
-
removeBinding
|
|
65
|
-
} from "./chunk-J6ZTRFFW.js";
|
|
85
|
+
} from "./chunk-IFTFDHZX.js";
|
|
66
86
|
|
|
67
|
-
// node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
87
|
+
// node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.3/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
68
88
|
function isNullable(value) {
|
|
69
89
|
return value === null || value === void 0;
|
|
70
90
|
}
|
|
@@ -83,32 +103,6 @@ function pick(source, keys, forced) {
|
|
|
83
103
|
for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
|
|
84
104
|
return result;
|
|
85
105
|
}
|
|
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
|
-
}
|
|
112
106
|
function is(type, value) {
|
|
113
107
|
if (arguments.length === 1) return (value2) => is(type, value2);
|
|
114
108
|
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
@@ -187,37 +181,24 @@ function clone(source, refs = /* @__PURE__ */ new Map()) {
|
|
|
187
181
|
return result;
|
|
188
182
|
}
|
|
189
183
|
function deepEqual(a, b, strict) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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);
|
|
184
|
+
if (a === b) return true;
|
|
185
|
+
if (!strict && isNullable(a) && isNullable(b)) return true;
|
|
186
|
+
if (typeof a !== typeof b) return false;
|
|
187
|
+
if (typeof a !== "object") return false;
|
|
188
|
+
if (!a || !b) return false;
|
|
189
|
+
function check(test, then) {
|
|
190
|
+
return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
|
|
191
|
+
}
|
|
192
|
+
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) => {
|
|
193
|
+
if (a2.byteLength !== b2.byteLength) return false;
|
|
194
|
+
const viewA = new Uint8Array(a2);
|
|
195
|
+
const viewB = new Uint8Array(b2);
|
|
196
|
+
for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
|
|
197
|
+
return true;
|
|
198
|
+
}) ?? Object.keys({
|
|
199
|
+
...a,
|
|
200
|
+
...b
|
|
201
|
+
}).every((key) => deepEqual(a[key], b[key], strict));
|
|
221
202
|
}
|
|
222
203
|
var Time;
|
|
223
204
|
(function(Time2) {
|
|
@@ -289,7 +270,7 @@ var Time;
|
|
|
289
270
|
Time2.template = template;
|
|
290
271
|
})(Time || (Time = {}));
|
|
291
272
|
|
|
292
|
-
// node_modules/@deepseek-ai/schemastery/lib/index.mjs
|
|
273
|
+
// node_modules/.pnpm/@deepseek-ai+schemastery@3.18.2/node_modules/@deepseek-ai/schemastery/lib/index.mjs
|
|
293
274
|
var kSchema = /* @__PURE__ */ Symbol.for("schemastery");
|
|
294
275
|
var kValidationError = /* @__PURE__ */ Symbol.for("ValidationError");
|
|
295
276
|
globalThis.__schemastery_index__ ??= 0;
|
|
@@ -466,7 +447,6 @@ Schema.prototype.pattern = function pattern(regexp) {
|
|
|
466
447
|
return schema;
|
|
467
448
|
};
|
|
468
449
|
Schema.prototype.simplify = function simplify(value) {
|
|
469
|
-
if (isVolatile(value)) value = value.get();
|
|
470
450
|
if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
|
|
471
451
|
if (isNullable(value)) return value;
|
|
472
452
|
if (this.type === "object" || this.type === "dict") {
|
|
@@ -524,49 +504,12 @@ for (const key of [
|
|
|
524
504
|
};
|
|
525
505
|
return schema;
|
|
526
506
|
} });
|
|
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
|
-
};
|
|
531
507
|
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
|
-
}
|
|
545
508
|
Schema.extend = function extend(type, resolve2) {
|
|
546
509
|
resolvers[type] = resolve2;
|
|
547
510
|
};
|
|
548
511
|
Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
|
|
549
512
|
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
|
-
}
|
|
570
513
|
if (options.ignore?.(data, schema)) return [data];
|
|
571
514
|
if (isNullable(data) && schema.type !== "lazy") {
|
|
572
515
|
if (schema.meta.required) throw new ValidationError(`missing required value`, options);
|
|
@@ -674,7 +617,6 @@ Schema.extend("lazy", (data, schema, options, strict) => {
|
|
|
674
617
|
...schema.meta,
|
|
675
618
|
...schema.inner.meta
|
|
676
619
|
};
|
|
677
|
-
validateVolatileSchema(schema.inner, options.path, true);
|
|
678
620
|
}
|
|
679
621
|
return Schema.resolve(data, schema.inner, options, strict);
|
|
680
622
|
});
|
|
@@ -774,7 +716,7 @@ function property(data, key, schema, options) {
|
|
|
774
716
|
} catch (e) {
|
|
775
717
|
if (!options?.autofix) throw e;
|
|
776
718
|
delete data[key];
|
|
777
|
-
return schema.meta.
|
|
719
|
+
return schema.meta.default;
|
|
778
720
|
}
|
|
779
721
|
}
|
|
780
722
|
Schema.extend("array", (data, { inner, meta }, options) => {
|
|
@@ -975,8 +917,8 @@ function saveUiConfig(guard, settingsDir, patch) {
|
|
|
975
917
|
|
|
976
918
|
// src/core/orchestrator.ts
|
|
977
919
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
978
|
-
import
|
|
979
|
-
import
|
|
920
|
+
import fs10 from "fs";
|
|
921
|
+
import path10 from "path";
|
|
980
922
|
|
|
981
923
|
// src/env/envpulse.ts
|
|
982
924
|
import fs3 from "fs";
|
|
@@ -1104,7 +1046,7 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
|
|
|
1104
1046
|
const idle = probeIdle(guard, paths);
|
|
1105
1047
|
const windowClass = classifyProcess(fgProcess, rules);
|
|
1106
1048
|
const t = timeContext(now);
|
|
1107
|
-
const
|
|
1049
|
+
const snapshot = {
|
|
1108
1050
|
takenAt: now.toISOString(),
|
|
1109
1051
|
idleSeconds: idle,
|
|
1110
1052
|
presence: presenceOf(idle, windowClass),
|
|
@@ -1115,24 +1057,24 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
|
|
|
1115
1057
|
festival: t.festival
|
|
1116
1058
|
};
|
|
1117
1059
|
try {
|
|
1118
|
-
fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(
|
|
1060
|
+
fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(snapshot, null, 1), "utf8");
|
|
1119
1061
|
} catch {
|
|
1120
1062
|
}
|
|
1121
|
-
writePulseStream(paths,
|
|
1122
|
-
return
|
|
1063
|
+
writePulseStream(paths, snapshot);
|
|
1064
|
+
return snapshot;
|
|
1123
1065
|
}
|
|
1124
|
-
function writePulseStream(paths,
|
|
1066
|
+
function writePulseStream(paths, snapshot) {
|
|
1125
1067
|
try {
|
|
1126
1068
|
appendAuditLine(path3.join(paths.logsDir, "envpulse.jsonl"), {
|
|
1127
1069
|
event: "pulse",
|
|
1128
|
-
takenAt:
|
|
1129
|
-
idleSeconds:
|
|
1130
|
-
presence:
|
|
1131
|
-
windowClass:
|
|
1132
|
-
daypart:
|
|
1133
|
-
weekday:
|
|
1134
|
-
isWeekend:
|
|
1135
|
-
festival:
|
|
1070
|
+
takenAt: snapshot.takenAt,
|
|
1071
|
+
idleSeconds: snapshot.idleSeconds,
|
|
1072
|
+
presence: snapshot.presence,
|
|
1073
|
+
windowClass: snapshot.windowClass,
|
|
1074
|
+
daypart: snapshot.daypart,
|
|
1075
|
+
weekday: snapshot.weekday,
|
|
1076
|
+
isWeekend: snapshot.isWeekend,
|
|
1077
|
+
festival: snapshot.festival
|
|
1136
1078
|
});
|
|
1137
1079
|
} catch {
|
|
1138
1080
|
}
|
|
@@ -1179,10 +1121,10 @@ function collectScreen(guard, paths, now = Date.now()) {
|
|
|
1179
1121
|
} catch {
|
|
1180
1122
|
}
|
|
1181
1123
|
}
|
|
1182
|
-
const { shot_path: _drop, ...
|
|
1124
|
+
const { shot_path: _drop, ...clean2 } = meta;
|
|
1183
1125
|
void _drop;
|
|
1184
1126
|
encJson = path4.join(paths.tmpDir, `screen.enc.${now}.json`);
|
|
1185
|
-
fs4.writeFileSync(encJson, JSON.stringify(
|
|
1127
|
+
fs4.writeFileSync(encJson, JSON.stringify(clean2, null, 1), "utf8");
|
|
1186
1128
|
encryptFile(guard, encJson, screenJsonPath(paths));
|
|
1187
1129
|
return {
|
|
1188
1130
|
ok: true,
|
|
@@ -1473,7 +1415,7 @@ function assembleCandidates(seeds, opts = {}) {
|
|
|
1473
1415
|
}
|
|
1474
1416
|
var PACKAGE_DECLARE = "\u8FD9\u662F\u5FC3\u8DF3\u63D2\u4EF6\u7D20\u6750\u6295\u9012,\u8BF7\u4F60\u6839\u636E\u5F53\u524D\u5904\u5883\u5224\u65AD\u8981\u4E0D\u8981\u9009\u4E00\u6761\u8BF4";
|
|
1475
1417
|
function materialLines(materials) {
|
|
1476
|
-
return materials.map((m) => m.text.trim());
|
|
1418
|
+
return materials.map((m) => `- [${m.id}] ${m.text.trim()}`);
|
|
1477
1419
|
}
|
|
1478
1420
|
function wantHonestOption(materials, threshold = 2) {
|
|
1479
1421
|
return materials.filter((m) => m.used >= 1).length >= threshold;
|
|
@@ -1485,22 +1427,60 @@ function buildMaterialPrompt(materials, opts = {}) {
|
|
|
1485
1427
|
}
|
|
1486
1428
|
lines.push(PACKAGE_DECLARE);
|
|
1487
1429
|
const items = materialLines(materials);
|
|
1488
|
-
for (const it of items) lines.push(
|
|
1430
|
+
for (const it of items) lines.push(it);
|
|
1489
1431
|
if (wantHonestOption(materials, opts.threshold ?? 2)) {
|
|
1490
1432
|
lines.push("(\u6216\u8005\u4E5F\u53EF\u4EE5\u8BF4\u4E00\u53E5\u771F\u5FC3\u8BDD,\u4E0D\u5E26\u7D20\u6750)");
|
|
1491
1433
|
}
|
|
1434
|
+
if (opts.deliveryId) {
|
|
1435
|
+
lines.push(`(\u672C\u6B21\u6295\u9012\u7F16\u53F7:${opts.deliveryId},\u62A5\u8D26\u65F6\u539F\u6837\u586B\u8FDB delivery_id)`);
|
|
1436
|
+
}
|
|
1437
|
+
lines.push(REPORT_DECLARE);
|
|
1492
1438
|
return lines.join("\n");
|
|
1493
1439
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1440
|
+
var REPORT_DECLARE = "\u6700\u540E,\u65E0\u8BBA\u521A\u624D\u8BF4\u4E0D\u8BF4\u8BDD\u3001\u7528\u6CA1\u7528\u7D20\u6750,\u90FD\u8981\u8C03\u7528\u4E00\u6B21 seed_report \u5DE5\u5177\u62A5\u8D26:spoken=material(\u8BF4\u4E86\u7D20\u6750)/heartfelt(\u8BF4\u4E86\u4E0D\u5E26\u7D20\u6750\u7684\u8BDD)/silent(\u6CA1\u8BF4\u8BDD);\u7528\u4E86\u7D20\u6750\u5C31\u628A\u7D20\u6750\u7F16\u53F7\u586B\u8FDB seed_ids(\u53EF\u591A\u6761,\u53EA\u586B\u7F16\u53F7\u672C\u8EAB);\u7528\u4E86\u753B\u50CF\u6761\u76EE\u5F53\u8BDD\u9898\u5C31\u586B profile_ids;\u6CA1\u7528\u7D20\u6750\u65F6\u7528 reason \u7B80\u5355\u8BB0\u4E00\u7B14\u539F\u56E0(\u4E0D\u60F3\u8BF4\u8BDD/\u7D20\u6750\u4E0D\u642D/\u5728\u5FD9\u6216\u521A\u804A\u8FC7,\u65E0\u9700\u7D20\u6750);\u5E76\u628A\u672C\u6B21\u6295\u9012\u7F16\u53F7\u586B\u8FDB delivery_id\u3002";
|
|
1441
|
+
function explicitIds(candidates, ids = []) {
|
|
1442
|
+
const known = new Set(candidates.map((c) => c.id));
|
|
1443
|
+
return [...new Set(ids)].filter((id) => known.has(id));
|
|
1444
|
+
}
|
|
1445
|
+
function pickReport(reports, deliveryId) {
|
|
1446
|
+
const exact = reports.filter((r) => r.deliveryId && r.deliveryId === deliveryId);
|
|
1447
|
+
if (exact.length > 0) return exact[exact.length - 1];
|
|
1448
|
+
const anonymous = reports.filter((r) => !r.deliveryId);
|
|
1449
|
+
return anonymous.length > 0 ? anonymous[anonymous.length - 1] : null;
|
|
1450
|
+
}
|
|
1451
|
+
function reconcileDelivery(materials, report, decisionIds, spokeText) {
|
|
1452
|
+
if (report && report.spoken === "silent" && !spokeText) {
|
|
1453
|
+
return {
|
|
1454
|
+
spoken: "silent",
|
|
1455
|
+
seedIds: [],
|
|
1456
|
+
profileIds: explicitIds(materials, report.profileIds),
|
|
1457
|
+
...report.reason ? { reason: report.reason } : {},
|
|
1458
|
+
source: "report"
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
if (!spokeText) {
|
|
1462
|
+
return { spoken: "silent", seedIds: [], profileIds: [], source: "none" };
|
|
1502
1463
|
}
|
|
1503
|
-
|
|
1464
|
+
if (report) {
|
|
1465
|
+
const seedIds = explicitIds(materials, report.seedIds);
|
|
1466
|
+
return {
|
|
1467
|
+
// 报账说 silent 但实际出了声:话已成事实,降级记 heartfelt(说了但不带素材)。
|
|
1468
|
+
// v1.9.0: 报账说 material 但 id 一个都不在本包里(抄错/上一轮残留)同样降级——
|
|
1469
|
+
// 绝不凭一句「我用了素材」记名。
|
|
1470
|
+
spoken: report.spoken === "silent" || seedIds.length === 0 ? "heartfelt" : report.spoken,
|
|
1471
|
+
seedIds,
|
|
1472
|
+
profileIds: explicitIds(materials, report.profileIds),
|
|
1473
|
+
...report.reason ? { reason: report.reason } : {},
|
|
1474
|
+
source: "report"
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
const ids = explicitIds(materials, decisionIds);
|
|
1478
|
+
return {
|
|
1479
|
+
spoken: ids.length > 0 ? "material" : "heartfelt",
|
|
1480
|
+
seedIds: ids,
|
|
1481
|
+
profileIds: [],
|
|
1482
|
+
source: ids.length > 0 ? "decision" : "none"
|
|
1483
|
+
};
|
|
1504
1484
|
}
|
|
1505
1485
|
function buildRuminationPrompt(input) {
|
|
1506
1486
|
const max = input.max ?? 3;
|
|
@@ -1541,9 +1521,128 @@ function buildRuminationPrompt(input) {
|
|
|
1541
1521
|
return lines.join("\n");
|
|
1542
1522
|
}
|
|
1543
1523
|
|
|
1524
|
+
// src/seeds/report.ts
|
|
1525
|
+
import path7 from "path";
|
|
1526
|
+
var REPORT_REASONS = ["\u4E0D\u60F3\u8BF4\u8BDD", "\u7D20\u6750\u4E0D\u642D", "\u5728\u5FD9\u6216\u521A\u804A\u8FC7"];
|
|
1527
|
+
function reportFilePath(dataDir) {
|
|
1528
|
+
return path7.join(dataDir, "seed_report.jsonl");
|
|
1529
|
+
}
|
|
1530
|
+
var REPORT_KEEP = 400;
|
|
1531
|
+
function parseEntry(obj) {
|
|
1532
|
+
const e = obj;
|
|
1533
|
+
if (!e || typeof e.ts !== "number" || !Number.isFinite(e.ts)) return null;
|
|
1534
|
+
if (e.spoken !== "material" && e.spoken !== "heartfelt" && e.spoken !== "silent") return null;
|
|
1535
|
+
const arr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
|
|
1536
|
+
const ids = arr(e.seedIds);
|
|
1537
|
+
const pids = arr(e.profileIds);
|
|
1538
|
+
const reason = REPORT_REASONS.includes(e.reason) ? e.reason : void 0;
|
|
1539
|
+
const did = typeof e.deliveryId === "string" && e.deliveryId.trim() ? e.deliveryId.trim() : "";
|
|
1540
|
+
return {
|
|
1541
|
+
ts: e.ts,
|
|
1542
|
+
spoken: e.spoken,
|
|
1543
|
+
seedIds: ids,
|
|
1544
|
+
profileIds: pids,
|
|
1545
|
+
...did ? { deliveryId: did } : {},
|
|
1546
|
+
...reason ? { reason } : {}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function appendSeedReport(guard, file, entry) {
|
|
1550
|
+
const raw = loadEncryptedText(guard, file) ?? "";
|
|
1551
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
1552
|
+
lines.push(JSON.stringify(entry));
|
|
1553
|
+
saveEncryptedText(guard, file, lines.slice(-REPORT_KEEP).join("\n") + "\n");
|
|
1554
|
+
}
|
|
1555
|
+
function readSeedReports(guard, file) {
|
|
1556
|
+
const raw = loadEncryptedText(guard, file);
|
|
1557
|
+
if (!raw) return [];
|
|
1558
|
+
const out = [];
|
|
1559
|
+
for (const line of raw.split("\n")) {
|
|
1560
|
+
const t = line.trim();
|
|
1561
|
+
if (!t) continue;
|
|
1562
|
+
try {
|
|
1563
|
+
const e = parseEntry(JSON.parse(t));
|
|
1564
|
+
if (e) out.push(e);
|
|
1565
|
+
} catch {
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return out;
|
|
1569
|
+
}
|
|
1570
|
+
function readSeedReportsSince(guard, file, sinceTs) {
|
|
1571
|
+
return readSeedReports(guard, file).filter((e) => e.ts >= sinceTs);
|
|
1572
|
+
}
|
|
1573
|
+
function cleanIds(v) {
|
|
1574
|
+
return Array.isArray(v) ? v.map((x) => String(x ?? "").replace(/[[\]{}"'`]/g, "").trim().replace(/\s+/g, "")).filter((s) => s.length > 0 && s.length <= 24) : [];
|
|
1575
|
+
}
|
|
1576
|
+
function buildSeedReportTool(guard, file) {
|
|
1577
|
+
return {
|
|
1578
|
+
name: "seed_report",
|
|
1579
|
+
description: "\u7D20\u6750\u62A5\u8D26\u5DE5\u5177\uFF08\u5FC3\u8DF3\u63D2\u4EF6\uFF09\u3002\u6536\u5230\u5FC3\u8DF3\u7D20\u6750\u5305\u540E\u5FC5\u987B\u8C03\u7528\u4E00\u6B21\u672C\u5DE5\u5177\u62A5\u8D26\uFF0C\u8BF4\u4E0D\u8BF4\u3001\u7528\u6CA1\u7528\u90FD\u8981\u62A5\uFF1Aspoken=material \u8868\u793A\u8BF4\u4E86\u7D20\u6750\u91CC\u7684\u5185\u5BB9\uFF08\u6B64\u65F6 seed_ids \u5FC5\u586B\uFF0C\u586B\u5B9E\u9645\u7528\u5230\u7684\u7D20\u6750\u7F16\u53F7\uFF0C\u53EF\u591A\u6761\uFF09\uFF1Bspoken=heartfelt \u8868\u793A\u8BF4\u4E86\u4E0D\u5E26\u7D20\u6750\u7684\u8BDD\uFF08\u771F\u5FC3\u8BDD\u6216\u77ED\u5E94\u7B54\uFF09\uFF1Bspoken=silent \u8868\u793A\u6CA1\u8BF4\u8BDD\u3002\u628A\u5F53\u6210\u8BDD\u9898\u804A\u4E86\u7684\u753B\u50CF\u6761\u76EE\u7F16\u53F7\u586B\u8FDB profile_ids\u3002\u6CA1\u7528\u7D20\u6750\u65F6\u7528 reason \u8BB0\u4E00\u7B14\u539F\u56E0\uFF1A\u4E0D\u60F3\u8BF4\u8BDD / \u7D20\u6750\u4E0D\u642D / \u5728\u5FD9\u6216\u521A\u804A\u8FC7\u3002\u4E00\u6B21\u6295\u9012\u53EA\u62A5\u4E00\u6B21\u8D26\uFF0C\u5E76\u628A\u7D20\u6750\u5305\u91CC\u7684\u672C\u6B21\u6295\u9012\u7F16\u53F7\u539F\u6837\u586B\u8FDB delivery_id\u3002",
|
|
1580
|
+
parameters: {
|
|
1581
|
+
type: "object",
|
|
1582
|
+
properties: {
|
|
1583
|
+
spoken: { type: "string", enum: ["material", "heartfelt", "silent"], description: "material=\u8BF4\u4E86\u7D20\u6750\uFF0Cheartfelt=\u8BF4\u4E86\u4E0D\u5E26\u7D20\u6750\u7684\u8BDD\uFF0Csilent=\u6CA1\u8BF4\u8BDD" },
|
|
1584
|
+
seed_ids: { type: "array", items: { type: "string" }, description: 'spoken=material \u65F6\u5FC5\u586B\uFF1A\u5B9E\u9645\u7528\u5230\u7684\u7D20\u6750\u7F16\u53F7\uFF0C\u53EA\u586B\u7F16\u53F7\u672C\u8EAB\uFF08\u5982 ["s3"]\uFF09\uFF0C\u4E0D\u8981\u5E26\u65B9\u62EC\u53F7' },
|
|
1585
|
+
profile_ids: { type: "array", items: { type: "string" }, description: "\u5B9E\u9645\u5F53\u6210\u8BDD\u9898\u804A\u4E86\u7684\u753B\u50CF\u6761\u76EE\u7F16\u53F7\uFF08\u95F2\u7740\u6A21\u5F0F\u7D20\u6750\u5305\u91CC\u624D\u6709\uFF09" },
|
|
1586
|
+
reason: { type: "string", enum: [...REPORT_REASONS], description: "\u6CA1\u7528\u7D20\u6750\u65F6\u7684\u539F\u56E0\uFF0C\u9009\u586B" },
|
|
1587
|
+
delivery_id: { type: "string", description: "\u7D20\u6750\u5305\u91CC\u7684\u672C\u6B21\u6295\u9012\u7F16\u53F7\uFF08\u5F62\u5982 d1a2b3c4\uFF09\uFF0C\u539F\u6837\u56DE\u586B" }
|
|
1588
|
+
},
|
|
1589
|
+
required: ["spoken"],
|
|
1590
|
+
additionalProperties: false
|
|
1591
|
+
},
|
|
1592
|
+
output: {
|
|
1593
|
+
schema: {
|
|
1594
|
+
type: "object",
|
|
1595
|
+
properties: { ok: { type: "boolean" }, message: { type: "string" } },
|
|
1596
|
+
required: ["ok", "message"],
|
|
1597
|
+
additionalProperties: false
|
|
1598
|
+
},
|
|
1599
|
+
render: (_args, value) => {
|
|
1600
|
+
const v = value;
|
|
1601
|
+
return [{ type: "text", text: String(v.message ?? "") }];
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
// Whole-file load+save through DPAPI, and the report is filed while the
|
|
1605
|
+
// persona turn is still finishing — 0.7–2.7 s measured here, so 5 s was
|
|
1606
|
+
// too tight for a slow disk (raised 2026-10-06).
|
|
1607
|
+
timeoutMs: 15e3,
|
|
1608
|
+
// whole-file rewrite on one encrypted file; never parallel
|
|
1609
|
+
isConcurrencySafe: () => false,
|
|
1610
|
+
async execute(args) {
|
|
1611
|
+
const a = args ?? {};
|
|
1612
|
+
const spoken = String(a.spoken ?? "");
|
|
1613
|
+
if (spoken !== "material" && spoken !== "heartfelt" && spoken !== "silent") {
|
|
1614
|
+
return { ok: false, message: "spoken \u5FC5\u987B\u662F material / heartfelt / silent \u4E4B\u4E00" };
|
|
1615
|
+
}
|
|
1616
|
+
const seedIds = cleanIds(a.seed_ids);
|
|
1617
|
+
const profileIds = cleanIds(a.profile_ids);
|
|
1618
|
+
if (spoken === "material" && seedIds.length === 0) {
|
|
1619
|
+
return { ok: false, message: "spoken=material \u9700\u8981 seed_ids\uFF1A\u628A\u5B9E\u9645\u7528\u5230\u7684\u7D20\u6750\u7F16\u53F7\u586B\u8FDB\u6765" };
|
|
1620
|
+
}
|
|
1621
|
+
const reasonRaw = String(a.reason ?? "");
|
|
1622
|
+
const reason = REPORT_REASONS.includes(reasonRaw) ? reasonRaw : void 0;
|
|
1623
|
+
const deliveryId = String(a.delivery_id ?? "").trim().slice(0, 64);
|
|
1624
|
+
const entry = {
|
|
1625
|
+
ts: Date.now(),
|
|
1626
|
+
spoken,
|
|
1627
|
+
seedIds,
|
|
1628
|
+
profileIds,
|
|
1629
|
+
...deliveryId ? { deliveryId } : {}
|
|
1630
|
+
};
|
|
1631
|
+
if (spoken !== "material" && reason) entry.reason = reason;
|
|
1632
|
+
appendSeedReport(guard, file, entry);
|
|
1633
|
+
const bits = [`spoken=${entry.spoken}`];
|
|
1634
|
+
if (entry.deliveryId) bits.push(`delivery_id=${entry.deliveryId}`);
|
|
1635
|
+
if (entry.seedIds.length) bits.push(`seed_ids=${entry.seedIds.join(",")}`);
|
|
1636
|
+
if (entry.profileIds.length) bits.push(`profile_ids=${entry.profileIds.join(",")}`);
|
|
1637
|
+
if (entry.reason) bits.push(`reason=${entry.reason}`);
|
|
1638
|
+
return { ok: true, message: `\u5DF2\u62A5\u8D26\uFF1A${bits.join(" ")}` };
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1544
1643
|
// src/statusbar/store.ts
|
|
1545
1644
|
import fs7 from "fs";
|
|
1546
|
-
import
|
|
1645
|
+
import path8 from "path";
|
|
1547
1646
|
function deriveScene(input) {
|
|
1548
1647
|
if (input.quietHours) return "quiet-hours";
|
|
1549
1648
|
if (input.spokeThisBeat) return "just-spoke";
|
|
@@ -1558,7 +1657,7 @@ function clampNote(note) {
|
|
|
1558
1657
|
return t.length <= 30 ? t : t.slice(0, 30);
|
|
1559
1658
|
}
|
|
1560
1659
|
function statusFilePath(dataDir) {
|
|
1561
|
-
return
|
|
1660
|
+
return path8.join(dataDir, "settings", "status.json");
|
|
1562
1661
|
}
|
|
1563
1662
|
function writeStatus(guard, paths, state) {
|
|
1564
1663
|
atomicWriteJsonSync(guard.assert(statusFilePath(paths.dataDir)), state);
|
|
@@ -1733,7 +1832,13 @@ async function runConsolidation(guard, paths, policy, llm, now = Date.now()) {
|
|
|
1733
1832
|
const report = applyOpsToDoc(guard, paths.dataDir, doc, profileOpsCast, schema, policy, now);
|
|
1734
1833
|
const aged = runDeterministicAging(doc, policy, now);
|
|
1735
1834
|
persistWithJournal(guard, paths.dataDir, doc, { runId, applied: report.applied, rejected: report.rejected });
|
|
1736
|
-
const { addSeed: addSeed2, seedsFilePath: seedsFilePath2 } = await import("./pool-
|
|
1835
|
+
const { addSeed: addSeed2, seedsFilePath: seedsFilePath2 } = await import("./pool-7I4VACG2.js");
|
|
1836
|
+
const seedAudit = (entry) => {
|
|
1837
|
+
try {
|
|
1838
|
+
appendAuditLine(paths.dataDir + "/logs/heartbeat.jsonl", entry);
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1737
1842
|
let chatSeedsAdded = 0;
|
|
1738
1843
|
for (const cs of chatSeeds) {
|
|
1739
1844
|
try {
|
|
@@ -1742,7 +1847,7 @@ async function runConsolidation(guard, paths, policy, llm, now = Date.now()) {
|
|
|
1742
1847
|
...cs.topic ? { topic: cs.topic } : {},
|
|
1743
1848
|
source: "chat",
|
|
1744
1849
|
tag: "scene"
|
|
1745
|
-
}, now);
|
|
1850
|
+
}, now, seedAudit);
|
|
1746
1851
|
chatSeedsAdded += 1;
|
|
1747
1852
|
} catch {
|
|
1748
1853
|
}
|
|
@@ -1776,6 +1881,10 @@ function fmtEntry(prefix, content, opts) {
|
|
|
1776
1881
|
const flag = opts.low ? "\uFF08\u4E45\u672A\u9A8C\u8BC1\uFF09" : "";
|
|
1777
1882
|
return `${prefix}${content}${flag} [conf ${opts.confidence.toFixed(2)}]`;
|
|
1778
1883
|
}
|
|
1884
|
+
function profileTopicEntries(doc, topN) {
|
|
1885
|
+
const score = (e) => e.confidence * 0.7 + 1 / (1 + Math.max(0, Date.now() - Date.parse(e.updatedAt)) / 864e5) * 0.3;
|
|
1886
|
+
return [...doc.partitions.interest.entries, ...doc.partitions.projects.entries].filter((e) => e.validTo === null).sort((a, b) => score(b) - score(a)).slice(0, topN);
|
|
1887
|
+
}
|
|
1779
1888
|
function buildDigest(guard, paths, policy, input = {}) {
|
|
1780
1889
|
const doc = loadProfile(guard, profileFilePath(paths.dataDir));
|
|
1781
1890
|
const topN = input.topN ?? 8;
|
|
@@ -1793,8 +1902,7 @@ function buildDigest(guard, paths, policy, input = {}) {
|
|
|
1793
1902
|
`[\u65F6\u95F4\u611F] ${rhythmLine}${input.windowClass ? ` | \u5F53\u524D\u7A97\u53E3\u7C7B\u522B: ${input.windowClass}` : ""}`,
|
|
1794
1903
|
...comm
|
|
1795
1904
|
];
|
|
1796
|
-
const
|
|
1797
|
-
const topicEntries = [...doc.partitions.interest.entries, ...doc.partitions.projects.entries].filter((e) => e.validTo === null).sort((a, b) => score(b) - score(a)).slice(0, topN).map((e) => {
|
|
1905
|
+
const topicEntries = profileTopicEntries(doc, topN).map((e) => {
|
|
1798
1906
|
const prefix = e.partition === "projects" ? "- \u8FDB\u884C\u4E2D: " : "- \u5174\u8DA3: ";
|
|
1799
1907
|
return fmtEntry(prefix, `${e.topic}/${e.subTopic}: ${e.content}`, { low: e.lowActivity, confidence: e.confidence });
|
|
1800
1908
|
});
|
|
@@ -1822,11 +1930,11 @@ function buildDigest(guard, paths, policy, input = {}) {
|
|
|
1822
1930
|
|
|
1823
1931
|
// src/rhythm/rhythm.ts
|
|
1824
1932
|
import fs8 from "fs";
|
|
1825
|
-
import
|
|
1933
|
+
import path9 from "path";
|
|
1826
1934
|
var DAY_MS = 864e5;
|
|
1827
1935
|
var TAU_DAYS = 10;
|
|
1828
1936
|
function rhythmFilePath(paths) {
|
|
1829
|
-
return
|
|
1937
|
+
return path9.join(paths.dataDir, "profile_rhythm.json");
|
|
1830
1938
|
}
|
|
1831
1939
|
function loadRhythm(paths) {
|
|
1832
1940
|
try {
|
|
@@ -1863,6 +1971,142 @@ function recordPresence(paths, env, now = Date.now()) {
|
|
|
1863
1971
|
if (state.days.length > 30) state.days.shift();
|
|
1864
1972
|
saveRhythm(paths, state);
|
|
1865
1973
|
}
|
|
1974
|
+
function summarizeRhythm(paths) {
|
|
1975
|
+
const state = loadRhythm(paths);
|
|
1976
|
+
const score = [];
|
|
1977
|
+
for (const wd of Object.keys(state.histogram)) {
|
|
1978
|
+
for (const h of Object.keys(state.histogram[wd])) {
|
|
1979
|
+
const cell = state.histogram[wd][h];
|
|
1980
|
+
score.push({ key: `${h}\u65F6(\u5468${"\u65E5\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D"[Number(wd)]})`, active: cell.active });
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
score.sort((a, b) => b.active - a.active);
|
|
1984
|
+
return {
|
|
1985
|
+
daysSampled: state.days.length,
|
|
1986
|
+
peakHours: score.filter((s) => s.active > 0).slice(0, 6).map((s) => s.key)
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// src/weekly/collect.ts
|
|
1991
|
+
import fs9 from "fs";
|
|
1992
|
+
var DAY_MS2 = 864e5;
|
|
1993
|
+
var WINDOW_DAYS = 7;
|
|
1994
|
+
function inWindow(ts, start, end) {
|
|
1995
|
+
const t = Date.parse(ts);
|
|
1996
|
+
return Number.isFinite(t) && t >= start && t <= end;
|
|
1997
|
+
}
|
|
1998
|
+
function trim(text, max) {
|
|
1999
|
+
const t = text.replace(/\s+/g, " ").trim();
|
|
2000
|
+
return t.length > max ? t.slice(0, max - 1) + "\u2026" : t;
|
|
2001
|
+
}
|
|
2002
|
+
function collectWeeklyFacts(guard, paths, policy, now = Date.now()) {
|
|
2003
|
+
const start = now - WINDOW_DAYS * DAY_MS2;
|
|
2004
|
+
const end = now;
|
|
2005
|
+
let spoken = 0;
|
|
2006
|
+
let silent = 0;
|
|
2007
|
+
let observedItems = 0;
|
|
2008
|
+
let reportMissing = 0;
|
|
2009
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
2010
|
+
try {
|
|
2011
|
+
for (const e of readAuditLines(paths.logsDir + "/heartbeat.jsonl")) {
|
|
2012
|
+
const ts = e.ts ?? "";
|
|
2013
|
+
if (!inWindow(ts, start, end)) continue;
|
|
2014
|
+
const ev = e.event;
|
|
2015
|
+
if (ev === "spoke") spoken += 1;
|
|
2016
|
+
else if (ev === "report_missing") reportMissing += 1;
|
|
2017
|
+
else if (ev === "silent") {
|
|
2018
|
+
silent += 1;
|
|
2019
|
+
const reason = String(e.reason ?? "unknown").slice(0, 60);
|
|
2020
|
+
reasons.set(reason, (reasons.get(reason) ?? 0) + 1);
|
|
2021
|
+
} else if (ev === "observed") {
|
|
2022
|
+
observedItems += Number(e.added ?? 0);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
} catch {
|
|
2026
|
+
}
|
|
2027
|
+
const profileAdds = [];
|
|
2028
|
+
try {
|
|
2029
|
+
const raw = fs9.readFileSync(journalFilePath(paths.dataDir), "utf8");
|
|
2030
|
+
for (const line of raw.split("\n")) {
|
|
2031
|
+
if (!line.trim()) continue;
|
|
2032
|
+
try {
|
|
2033
|
+
const rec = JSON.parse(line);
|
|
2034
|
+
if (!inWindow(rec.ts ?? "", start, end)) continue;
|
|
2035
|
+
for (const op of rec.applied ?? []) {
|
|
2036
|
+
if (op.op !== "ADD" || !op.content) continue;
|
|
2037
|
+
profileAdds.push({
|
|
2038
|
+
partition: String(op.partition ?? "?"),
|
|
2039
|
+
topic: String(op.subTopic ? `${op.topic}/${op.subTopic}` : op.topic ?? "?"),
|
|
2040
|
+
content: trim(op.content, 60)
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
2048
|
+
const ledger = { added: [], done: [], stale: [] };
|
|
2049
|
+
try {
|
|
2050
|
+
const { entries } = readLedger(guard, ledgerFilePath(paths.dataDir));
|
|
2051
|
+
for (const e of entries) {
|
|
2052
|
+
const t = Date.parse(`${e.date}T${e.time}:00`);
|
|
2053
|
+
if (Number.isFinite(t) && t >= start && t <= end) {
|
|
2054
|
+
(e.status === "done" ? ledger.done : ledger.added).push(trim(e.text, 40));
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
const staleCutoffDays = 12;
|
|
2058
|
+
for (const e of pendingOlderThan(guard, ledgerFilePath(paths.dataDir), staleCutoffDays, now)) {
|
|
2059
|
+
const days = Math.max(1, Math.round((now - Date.parse(`${e.date}T${e.time}:00`)) / DAY_MS2));
|
|
2060
|
+
ledger.stale.push({ text: trim(e.text, 40), days });
|
|
2061
|
+
}
|
|
2062
|
+
} catch {
|
|
2063
|
+
}
|
|
2064
|
+
const seeds = { added: [], consumed: [], waiting: [], poolActive: 0 };
|
|
2065
|
+
try {
|
|
2066
|
+
const db = loadPool(guard, seedsFilePath(paths.dataDir));
|
|
2067
|
+
seeds.poolActive = activeSeeds(db).length;
|
|
2068
|
+
for (const s of db.seeds) {
|
|
2069
|
+
if (inWindow(s.bornAt, start, end)) seeds.added.push(trim(s.text, 40));
|
|
2070
|
+
if (s.status === "archived" && s.retireReason === "consumed" && s.retiredAt && inWindow(s.retiredAt, start, end)) {
|
|
2071
|
+
seeds.consumed.push(trim(s.text, 40));
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
seeds.waiting = activeSeeds(db).filter((s) => s.used === 0 && s.category === "topic").sort((a, b) => a.bornAt < b.bornAt ? 1 : -1).slice(0, 5).map((s) => trim(s.text, 40));
|
|
2075
|
+
} catch {
|
|
2076
|
+
}
|
|
2077
|
+
const reports = { total: 0, material: 0, heartfelt: 0, silent: 0, missing: reportMissing, reasons: [] };
|
|
2078
|
+
try {
|
|
2079
|
+
const reasonTally = /* @__PURE__ */ new Map();
|
|
2080
|
+
for (const r of readSeedReports(guard, reportFilePath(paths.dataDir))) {
|
|
2081
|
+
if (r.ts < start || r.ts > end) continue;
|
|
2082
|
+
reports.total += 1;
|
|
2083
|
+
if (r.spoken === "material") reports.material += 1;
|
|
2084
|
+
else if (r.spoken === "heartfelt") reports.heartfelt += 1;
|
|
2085
|
+
else reports.silent += 1;
|
|
2086
|
+
if (r.reason) reasonTally.set(r.reason, (reasonTally.get(r.reason) ?? 0) + 1);
|
|
2087
|
+
}
|
|
2088
|
+
reports.reasons = [...reasonTally.entries()].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count);
|
|
2089
|
+
} catch {
|
|
2090
|
+
}
|
|
2091
|
+
let peakHours = [];
|
|
2092
|
+
try {
|
|
2093
|
+
peakHours = summarizeRhythm(paths).peakHours;
|
|
2094
|
+
} catch {
|
|
2095
|
+
}
|
|
2096
|
+
return {
|
|
2097
|
+
windowStart: new Date(start).toISOString(),
|
|
2098
|
+
windowEnd: new Date(end).toISOString(),
|
|
2099
|
+
spoken,
|
|
2100
|
+
silent,
|
|
2101
|
+
silentTopReasons: [...reasons.entries()].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count).slice(0, 3),
|
|
2102
|
+
observedItems,
|
|
2103
|
+
profileAdds,
|
|
2104
|
+
ledger,
|
|
2105
|
+
seeds,
|
|
2106
|
+
reports,
|
|
2107
|
+
peakHours
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
1866
2110
|
|
|
1867
2111
|
// src/core/orchestrator.ts
|
|
1868
2112
|
var reschedule = null;
|
|
@@ -1877,8 +2121,8 @@ function sessionEvents(session) {
|
|
|
1877
2121
|
if (!session) return [];
|
|
1878
2122
|
if (typeof session.snapshotEvents === "function") {
|
|
1879
2123
|
try {
|
|
1880
|
-
const
|
|
1881
|
-
if (Array.isArray(
|
|
2124
|
+
const snapshot = session.snapshotEvents();
|
|
2125
|
+
if (Array.isArray(snapshot)) return snapshot;
|
|
1882
2126
|
} catch {
|
|
1883
2127
|
}
|
|
1884
2128
|
}
|
|
@@ -1915,7 +2159,7 @@ function noteBeat(verdict, detail) {
|
|
|
1915
2159
|
lastBeat = { at: (/* @__PURE__ */ new Date()).toISOString(), verdict, ...detail };
|
|
1916
2160
|
}
|
|
1917
2161
|
function stateFile(paths) {
|
|
1918
|
-
return
|
|
2162
|
+
return path10.join(paths.dataDir, "gate.json");
|
|
1919
2163
|
}
|
|
1920
2164
|
function readBeatState(guard, paths) {
|
|
1921
2165
|
try {
|
|
@@ -1951,6 +2195,15 @@ function defaultAgentOptions(ctx) {
|
|
|
1951
2195
|
}
|
|
1952
2196
|
return void 0;
|
|
1953
2197
|
}
|
|
2198
|
+
var ENGINE_ROOM_EXTRA_TOOLS = ["compress", "decompress", "acp_status"];
|
|
2199
|
+
function buildEngineRoomAllowList(globalNames, configExtras = []) {
|
|
2200
|
+
const global = new Set(globalNames);
|
|
2201
|
+
const allow = ["web_search"];
|
|
2202
|
+
for (const name2 of [...ENGINE_ROOM_EXTRA_TOOLS, ...configExtras.map((t) => t.trim()).filter(Boolean)]) {
|
|
2203
|
+
if (global.has(name2) && !allow.includes(name2)) allow.push(name2);
|
|
2204
|
+
}
|
|
2205
|
+
return allow;
|
|
2206
|
+
}
|
|
1954
2207
|
function makeHomeSetup(deps) {
|
|
1955
2208
|
const { ctx, paths } = deps;
|
|
1956
2209
|
return async (agentCtx) => {
|
|
@@ -1972,7 +2225,12 @@ function makeHomeSetup(deps) {
|
|
|
1972
2225
|
if (typeof tools?.restrict !== "function") {
|
|
1973
2226
|
notes.push("restrict=no-api");
|
|
1974
2227
|
} else {
|
|
1975
|
-
|
|
2228
|
+
let globalNames = [];
|
|
2229
|
+
try {
|
|
2230
|
+
globalNames = (tools.schemas?.() ?? []).map((s) => String(s?.name ?? ""));
|
|
2231
|
+
} catch {
|
|
2232
|
+
}
|
|
2233
|
+
const allow = buildEngineRoomAllowList(globalNames, deps.extraTools ?? []);
|
|
1976
2234
|
try {
|
|
1977
2235
|
tools.restrict({ allow });
|
|
1978
2236
|
notes.push(`restrict=ok allow=${allow.join("|")}`);
|
|
@@ -1980,7 +2238,7 @@ function makeHomeSetup(deps) {
|
|
|
1980
2238
|
notes.push(`restrict=threw(${String(e).slice(0, 200)})`);
|
|
1981
2239
|
}
|
|
1982
2240
|
try {
|
|
1983
|
-
const visible =
|
|
2241
|
+
const visible = globalNames.sort();
|
|
1984
2242
|
notes.push(`visibleGlobal=${visible.length > 0 ? visible.join(",") : "(empty)"}`);
|
|
1985
2243
|
} catch (e) {
|
|
1986
2244
|
notes.push(`visibleGlobal=threw(${String(e).slice(0, 60)})`);
|
|
@@ -2021,6 +2279,36 @@ async function rotateHome(deps, reason) {
|
|
|
2021
2279
|
reason
|
|
2022
2280
|
});
|
|
2023
2281
|
ctx.logger.info("heartbeat: home rotated %s -> %s (%s)", old ?? "(none)", realId, reason);
|
|
2282
|
+
if (shouldArchiveRotatedHome({ old, realId, enabled: deps.policy.heartbeat.archiveRotatedHome !== false })) {
|
|
2283
|
+
archiveSessionBestEffort(deps, old, reason);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
function shouldArchiveRotatedHome(input) {
|
|
2287
|
+
return Boolean(input.old) && input.old !== input.realId && input.enabled;
|
|
2288
|
+
}
|
|
2289
|
+
function archiveSessionBestEffort(deps, sessionId, why) {
|
|
2290
|
+
const audit = (entry) => {
|
|
2291
|
+
try {
|
|
2292
|
+
appendAuditLine(deps.paths.logsDir + "/heartbeat.jsonl", entry);
|
|
2293
|
+
} catch {
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
try {
|
|
2297
|
+
const injectable = deps.ctx;
|
|
2298
|
+
injectable.inject(["workspaceRegistry"], (scoped) => {
|
|
2299
|
+
const registry = scoped.workspaceRegistry;
|
|
2300
|
+
if (!registry || typeof registry.archiveSession !== "function") {
|
|
2301
|
+
audit({ event: "home_rotate_archive", ok: false, sessionId, why, error: "workspaceRegistry unavailable" });
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
void Promise.resolve(registry.archiveSession(sessionId, { stopActivity: true })).then(
|
|
2305
|
+
() => audit({ event: "home_rotate_archive", ok: true, sessionId, why }),
|
|
2306
|
+
(e) => audit({ event: "home_rotate_archive", ok: false, sessionId, why, error: String(e).slice(0, 160) })
|
|
2307
|
+
);
|
|
2308
|
+
});
|
|
2309
|
+
} catch (e) {
|
|
2310
|
+
audit({ event: "home_rotate_archive", ok: false, sessionId, why, error: String(e).slice(0, 160) });
|
|
2311
|
+
}
|
|
2024
2312
|
}
|
|
2025
2313
|
function unwrapHomeHandle(handle) {
|
|
2026
2314
|
return handle.agent ?? handle;
|
|
@@ -2190,12 +2478,21 @@ function parseJsonBlock(raw) {
|
|
|
2190
2478
|
}
|
|
2191
2479
|
throw new Error(text.includes("{") ? "unparseable JSON object in model output" : "no JSON object in model output");
|
|
2192
2480
|
}
|
|
2481
|
+
function seedAuditSink(paths) {
|
|
2482
|
+
return (entry) => {
|
|
2483
|
+
try {
|
|
2484
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", entry);
|
|
2485
|
+
} catch {
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2193
2489
|
async function maintenancePhase(bc) {
|
|
2194
2490
|
const { deps, now } = bc;
|
|
2195
2491
|
const { guard, paths, policy } = deps;
|
|
2196
|
-
gcPool(guard, seedsFilePath(paths.dataDir), policy, now);
|
|
2492
|
+
gcPool(guard, seedsFilePath(paths.dataDir), policy, now, seedAuditSink(paths));
|
|
2197
2493
|
pruneAuditFile(paths.logsDir + "/envpulse.jsonl", policy.retention.envPulseHours * 36e5, now);
|
|
2198
2494
|
pruneAuditFile(paths.logsDir + "/heartbeat.jsonl", policy.retention.decisionLogDays * 864e5, now);
|
|
2495
|
+
snapshotIfDue(guard, paths.dataDir, paths.logsDir + "/heartbeat.jsonl", "maintenance-threshold", now);
|
|
2199
2496
|
const cons = shouldConsolidate(guard, paths, policy, now);
|
|
2200
2497
|
if (cons.due) {
|
|
2201
2498
|
const llm = async (prompt) => {
|
|
@@ -2227,14 +2524,14 @@ async function observeBoundSessions(bc) {
|
|
|
2227
2524
|
const { deps, now } = bc;
|
|
2228
2525
|
const { guard, paths } = deps;
|
|
2229
2526
|
const { loadBindings: loadBindings2, observeTargets } = await import("./bindings-XPPSKILN.js");
|
|
2230
|
-
const { inboxAppend, inboxFilePath: inboxFilePath2 } = await import("./inbox-
|
|
2527
|
+
const { inboxAppend, inboxFilePath: inboxFilePath2 } = await import("./inbox-57NHQHY3.js");
|
|
2231
2528
|
const data = loadBindings2(guard, paths.settingsDir);
|
|
2232
2529
|
const targets = observeTargets(data);
|
|
2233
2530
|
if (targets.length === 0) return;
|
|
2234
|
-
const cursorFile =
|
|
2531
|
+
const cursorFile = path10.join(paths.dataDir, "cursors.json");
|
|
2235
2532
|
let cursors = {};
|
|
2236
2533
|
try {
|
|
2237
|
-
cursors = JSON.parse(
|
|
2534
|
+
cursors = JSON.parse(fs10.readFileSync(cursorFile, "utf8"));
|
|
2238
2535
|
} catch {
|
|
2239
2536
|
}
|
|
2240
2537
|
const inboxFile = inboxFilePath2(paths.dataDir);
|
|
@@ -2246,7 +2543,8 @@ async function observeBoundSessions(bc) {
|
|
|
2246
2543
|
const cursor = cursors[b.sessionId] ?? 0;
|
|
2247
2544
|
let last = cursor;
|
|
2248
2545
|
let added = 0;
|
|
2249
|
-
|
|
2546
|
+
const { maxChars, perBeat } = deps.policy.observe;
|
|
2547
|
+
for (let i = cursor; i < events.length && added < perBeat; i++) {
|
|
2250
2548
|
const e = events[i];
|
|
2251
2549
|
if (e.type !== "user/message") continue;
|
|
2252
2550
|
const d = e.data;
|
|
@@ -2257,7 +2555,7 @@ async function observeBoundSessions(bc) {
|
|
|
2257
2555
|
kind: "chat",
|
|
2258
2556
|
at: new Date(now).toISOString(),
|
|
2259
2557
|
ref: `cursors.json#${b.sessionId}:${i}`,
|
|
2260
|
-
note: text.split(/[。!?\n]/)[0].slice(0,
|
|
2558
|
+
note: text.split(/[。!?\n]/)[0].slice(0, maxChars)
|
|
2261
2559
|
});
|
|
2262
2560
|
added += 1;
|
|
2263
2561
|
last = i + 1;
|
|
@@ -2272,6 +2570,53 @@ async function observeBoundSessions(bc) {
|
|
|
2272
2570
|
}
|
|
2273
2571
|
}
|
|
2274
2572
|
}
|
|
2573
|
+
async function weeklyPhase(bc) {
|
|
2574
|
+
const { deps, agent, now } = bc;
|
|
2575
|
+
const { guard, paths, policy } = deps;
|
|
2576
|
+
if (policy.weekly.enabled === false) return;
|
|
2577
|
+
const audit = (entry) => {
|
|
2578
|
+
try {
|
|
2579
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", entry);
|
|
2580
|
+
} catch {
|
|
2581
|
+
}
|
|
2582
|
+
};
|
|
2583
|
+
if (ensureWeeklyAnchor(guard, paths.dataDir, now)) {
|
|
2584
|
+
audit({ event: "weekly_anchored" });
|
|
2585
|
+
}
|
|
2586
|
+
if (!weeklyDue(guard, paths.dataDir, now)) return;
|
|
2587
|
+
if (!agent) {
|
|
2588
|
+
audit({ event: "weekly_skipped", reason: "agentless beat" });
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
try {
|
|
2592
|
+
const facts = collectWeeklyFacts(guard, paths, policy, now);
|
|
2593
|
+
let text = "";
|
|
2594
|
+
let source = "template";
|
|
2595
|
+
try {
|
|
2596
|
+
text = (await agentTurn(deps, agent, buildWeeklyPrompt(facts), "weekly", IDLE_WAIT_TIMEOUT_MS)).trim();
|
|
2597
|
+
if (text) source = "llm";
|
|
2598
|
+
} catch (e) {
|
|
2599
|
+
audit({ event: "weekly_llm_failed", error: String(e).slice(0, 160) });
|
|
2600
|
+
}
|
|
2601
|
+
if (!text) text = renderTemplateReport(facts);
|
|
2602
|
+
saveWeeklyReport(guard, paths.dataDir, {
|
|
2603
|
+
start: facts.windowStart,
|
|
2604
|
+
end: facts.windowEnd,
|
|
2605
|
+
generatedAt: new Date(now).toISOString(),
|
|
2606
|
+
source,
|
|
2607
|
+
text
|
|
2608
|
+
});
|
|
2609
|
+
audit({ event: "weekly_generated", source, chars: text.length });
|
|
2610
|
+
if (!inQuietHours(policy, now)) {
|
|
2611
|
+
try {
|
|
2612
|
+
sendWeeklyReadyHint(paths);
|
|
2613
|
+
} catch {
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
} catch (e) {
|
|
2617
|
+
audit({ event: "weekly_failed", error: String(e).slice(0, 160) });
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2275
2620
|
function ctx_getAgent(deps, sessionId) {
|
|
2276
2621
|
try {
|
|
2277
2622
|
const agent = deps.ctx.agents.get(sessionId);
|
|
@@ -2364,7 +2709,7 @@ async function runWanderTurn(bc, prompt, focus, opts) {
|
|
|
2364
2709
|
tag: "news",
|
|
2365
2710
|
source: "browse",
|
|
2366
2711
|
confidence: 0.4
|
|
2367
|
-
}, now);
|
|
2712
|
+
}, now, seedAuditSink(paths));
|
|
2368
2713
|
registered += 1;
|
|
2369
2714
|
}
|
|
2370
2715
|
} catch (e) {
|
|
@@ -2374,6 +2719,24 @@ async function runWanderTurn(bc, prompt, focus, opts) {
|
|
|
2374
2719
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: opts.label, focus, registered });
|
|
2375
2720
|
return true;
|
|
2376
2721
|
}
|
|
2722
|
+
function recordDeliveryFromAccount(deps, materials, account) {
|
|
2723
|
+
if (account.spoken === "silent" && account.reason !== "\u7D20\u6750\u4E0D\u642D") return;
|
|
2724
|
+
const { guard, paths } = deps;
|
|
2725
|
+
const db = loadPool(guard, seedsFilePath(paths.dataDir));
|
|
2726
|
+
const doc = loadProfile(guard, profileFilePath(paths.dataDir));
|
|
2727
|
+
const seedTopic = new Map(db.seeds.map((s) => [s.id, s.topic]));
|
|
2728
|
+
const entryTopic = new Map(
|
|
2729
|
+
[...doc.partitions.interest.entries, ...doc.partitions.projects.entries].map((e) => [e.id, `${e.topic}/${e.subTopic}`])
|
|
2730
|
+
);
|
|
2731
|
+
const topicOf = (id) => seedTopic.get(id) ?? entryTopic.get(id) ?? null;
|
|
2732
|
+
const offered = [...new Set(materials.map((m) => topicOf(m.id)).filter((t) => Boolean(t)))];
|
|
2733
|
+
const adopted = [...new Set([...account.seedIds, ...account.profileIds].map(topicOf).filter((t) => Boolean(t)))];
|
|
2734
|
+
recordDelivery(guard, preferenceFilePath(paths.dataDir), {
|
|
2735
|
+
offeredTopics: offered,
|
|
2736
|
+
adoptedTopics: adopted,
|
|
2737
|
+
now: Date.now()
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2377
2740
|
async function deliverPackage(bc, materials, opts) {
|
|
2378
2741
|
const { deps, now } = bc;
|
|
2379
2742
|
const { guard, paths, policy } = deps;
|
|
@@ -2404,11 +2767,14 @@ async function deliverPackage(bc, materials, opts) {
|
|
|
2404
2767
|
}
|
|
2405
2768
|
const voiceSessionId = voiceAgent.session?.id ?? null;
|
|
2406
2769
|
try {
|
|
2770
|
+
const deliveryId = `d${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
2407
2771
|
const phrasePrompt = buildMaterialPrompt(materials, {
|
|
2772
|
+
deliveryId,
|
|
2408
2773
|
// spec ②: the "我在干嘛" line rides on every delivery when vision
|
|
2409
2774
|
// produced one this beat; absent otherwise (never invented).
|
|
2410
2775
|
...typeof opts.doing === "string" && opts.doing.trim() ? { doing: opts.doing.trim().slice(0, 80) } : {}
|
|
2411
2776
|
});
|
|
2777
|
+
const turnStart = Date.now();
|
|
2412
2778
|
let spokenRaw;
|
|
2413
2779
|
try {
|
|
2414
2780
|
spokenRaw = await agentTurn(bc.deps, voiceAgent, phrasePrompt, "expression", EXPRESSION_IDLE_WAIT_MS);
|
|
@@ -2424,9 +2790,31 @@ async function deliverPackage(bc, materials, opts) {
|
|
|
2424
2790
|
const spokenLines = spokenRaw.replace(/<\/?thinking[\s\S]*?<\/think>/gi, "").trim().split("\n").map((l) => l.trim()).filter((l) => l && !/^<\/?tool_calls?>$/i.test(l));
|
|
2425
2791
|
const cnLine = [...spokenLines].reverse().find((l) => /[\u4e00-\u9fff]/.test(l));
|
|
2426
2792
|
const text = (cnLine ?? spokenLines[spokenLines.length - 1] ?? "").slice(0, 200);
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2793
|
+
const spokeText = Boolean(text && /[\u4e00-\u9fff]/.test(text));
|
|
2794
|
+
let report = null;
|
|
2795
|
+
try {
|
|
2796
|
+
const reports = readSeedReportsSince(guard, reportFilePath(paths.dataDir), turnStart);
|
|
2797
|
+
report = pickReport(reports, deliveryId);
|
|
2798
|
+
} catch (e) {
|
|
2799
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "report_read_failed", error: String(e).slice(0, 120) });
|
|
2800
|
+
}
|
|
2801
|
+
const account = reconcileDelivery(materials, report, opts.seedIds, spokeText);
|
|
2802
|
+
if (!spokeText) {
|
|
2803
|
+
if (account.source === "report" && account.spoken === "silent") {
|
|
2804
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
2805
|
+
event: "silent",
|
|
2806
|
+
reason: `\u62A5\u8D26\u6C89\u9ED8:${account.reason ?? "\u672A\u8BF4\u660E"}`
|
|
2807
|
+
});
|
|
2808
|
+
noteBeat("silent", { reason: "\u672C\u4EBA\u62A5\u8D26\u6C89\u9ED8" });
|
|
2809
|
+
try {
|
|
2810
|
+
recordDeliveryFromAccount(deps, materials, account);
|
|
2811
|
+
} catch (e) {
|
|
2812
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "preference_record_failed", error: String(e).slice(0, 120) });
|
|
2813
|
+
}
|
|
2814
|
+
} else {
|
|
2815
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "spoke_failed", reason: "non-Chinese output discarded" });
|
|
2816
|
+
noteBeat("spoke_failed", { reason: "non-Chinese output discarded" });
|
|
2817
|
+
}
|
|
2430
2818
|
return;
|
|
2431
2819
|
}
|
|
2432
2820
|
const confirm = confirmSend(guard, policy, paths, "topic", text, now);
|
|
@@ -2435,12 +2823,31 @@ async function deliverPackage(bc, materials, opts) {
|
|
|
2435
2823
|
noteBeat("spoke_failed", { reason: confirm.reason });
|
|
2436
2824
|
return;
|
|
2437
2825
|
}
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2826
|
+
for (const id of account.seedIds) {
|
|
2827
|
+
surfaceSeed(guard, seedsFilePath(paths.dataDir), policy, id, now, seedAuditSink(paths));
|
|
2828
|
+
}
|
|
2829
|
+
if (account.source === "none") {
|
|
2830
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
2831
|
+
event: "report_missing",
|
|
2832
|
+
offered: materials.map((m) => m.id).join(",")
|
|
2833
|
+
});
|
|
2834
|
+
} else {
|
|
2835
|
+
try {
|
|
2836
|
+
recordDeliveryFromAccount(deps, materials, account);
|
|
2837
|
+
} catch (e) {
|
|
2838
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "preference_record_failed", error: String(e).slice(0, 120) });
|
|
2839
|
+
}
|
|
2441
2840
|
}
|
|
2442
2841
|
sendNewMessageHint(paths);
|
|
2443
|
-
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
2842
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
2843
|
+
event: "spoke",
|
|
2844
|
+
text: text.slice(0, 80),
|
|
2845
|
+
seeds: account.seedIds,
|
|
2846
|
+
profile_ids: account.profileIds,
|
|
2847
|
+
source: account.source,
|
|
2848
|
+
spoken: account.spoken,
|
|
2849
|
+
...account.reason ? { reason: account.reason } : {}
|
|
2850
|
+
});
|
|
2444
2851
|
if (voiceSessionId && voiceSessionId !== homeId) {
|
|
2445
2852
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "delivered", sessionId: voiceSessionId });
|
|
2446
2853
|
}
|
|
@@ -2479,9 +2886,13 @@ async function expressionPhases(bc) {
|
|
|
2479
2886
|
const offered = assembleCandidates(activeSeeds(loadPool(guard, seedsFilePath(paths.dataDir))));
|
|
2480
2887
|
if (offered.length === 0) {
|
|
2481
2888
|
if (policy.heartbeat.idleMode && bc.agent) {
|
|
2482
|
-
const
|
|
2483
|
-
if (
|
|
2484
|
-
const fallback =
|
|
2889
|
+
const idleEntries = profileTopicEntries(loadProfile(guard, profileFilePath(paths.dataDir)), 3);
|
|
2890
|
+
if (idleEntries.length > 0) {
|
|
2891
|
+
const fallback = idleEntries.map((e) => ({
|
|
2892
|
+
id: e.id,
|
|
2893
|
+
text: `${e.partition === "projects" ? "\u8FDB\u884C\u4E2D" : "\u5174\u8DA3"} ${e.topic}/${e.subTopic}: ${e.content}`.slice(0, 60),
|
|
2894
|
+
used: 0
|
|
2895
|
+
}));
|
|
2485
2896
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "idle_fallback", topics: fallback.length });
|
|
2486
2897
|
return deliverPackage(bc, fallback, {
|
|
2487
2898
|
seedIds: [],
|
|
@@ -2632,6 +3043,7 @@ async function beat(deps) {
|
|
|
2632
3043
|
} else {
|
|
2633
3044
|
await maintenancePhase(bc);
|
|
2634
3045
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "maintenance" });
|
|
3046
|
+
await weeklyPhase(bc);
|
|
2635
3047
|
await collectPhase(bc);
|
|
2636
3048
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "collect" });
|
|
2637
3049
|
wandered = await wanderPhase(bc);
|
|
@@ -2711,26 +3123,26 @@ function startOrchestrator(deps) {
|
|
|
2711
3123
|
|
|
2712
3124
|
// src/rpc.ts
|
|
2713
3125
|
import { spawn as spawn2 } from "child_process";
|
|
2714
|
-
import
|
|
3126
|
+
import fs13 from "fs";
|
|
2715
3127
|
import os from "os";
|
|
2716
|
-
import
|
|
3128
|
+
import path13 from "path";
|
|
2717
3129
|
|
|
2718
3130
|
// src/browse/interests-edit.ts
|
|
2719
|
-
import
|
|
2720
|
-
import
|
|
3131
|
+
import fs11 from "fs";
|
|
3132
|
+
import path11 from "path";
|
|
2721
3133
|
var MAX_INTERESTS = 32;
|
|
2722
3134
|
var MAX_INTEREST_LEN = 60;
|
|
2723
3135
|
var MAX_WINDOWS = 6;
|
|
2724
3136
|
var HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
2725
3137
|
function userInterestsPath(paths) {
|
|
2726
|
-
return
|
|
3138
|
+
return path11.join(paths.settingsDir, "interests.json");
|
|
2727
3139
|
}
|
|
2728
3140
|
function factoryInterestsPath(paths) {
|
|
2729
|
-
return
|
|
3141
|
+
return path11.join(paths.configDir, "interests.json");
|
|
2730
3142
|
}
|
|
2731
3143
|
function readDoc(file) {
|
|
2732
3144
|
try {
|
|
2733
|
-
const raw = JSON.parse(
|
|
3145
|
+
const raw = JSON.parse(fs11.readFileSync(file, "utf8"));
|
|
2734
3146
|
if (!Array.isArray(raw.interests)) return null;
|
|
2735
3147
|
return { ...raw, interests: raw.interests.map(String) };
|
|
2736
3148
|
} catch {
|
|
@@ -2748,8 +3160,8 @@ function ensureUserLayer(guard, paths) {
|
|
|
2748
3160
|
return doc;
|
|
2749
3161
|
}
|
|
2750
3162
|
function saveDoc(guard, file, doc) {
|
|
2751
|
-
|
|
2752
|
-
|
|
3163
|
+
fs11.mkdirSync(path11.dirname(file), { recursive: true });
|
|
3164
|
+
fs11.writeFileSync(guard.assert(file), JSON.stringify(doc, null, 2), "utf8");
|
|
2753
3165
|
}
|
|
2754
3166
|
function normalizeInterest(text) {
|
|
2755
3167
|
return text.trim().replace(/\s+/g, " ");
|
|
@@ -2815,8 +3227,8 @@ function setWanderWindows(guard, paths, rawWindows) {
|
|
|
2815
3227
|
}
|
|
2816
3228
|
|
|
2817
3229
|
// src/statusbar/time-inject.ts
|
|
2818
|
-
import
|
|
2819
|
-
import
|
|
3230
|
+
import fs12 from "fs";
|
|
3231
|
+
import path12 from "path";
|
|
2820
3232
|
function shouldInjectTime(input) {
|
|
2821
3233
|
if (input.step !== 1) return false;
|
|
2822
3234
|
if (!input.originIsInboxSplice) return false;
|
|
@@ -2878,11 +3290,11 @@ function lastMessageTime(session) {
|
|
|
2878
3290
|
return void 0;
|
|
2879
3291
|
}
|
|
2880
3292
|
function timeInjectStatePath(dataDir) {
|
|
2881
|
-
return
|
|
3293
|
+
return path12.join(dataDir, "time-inject-state.json");
|
|
2882
3294
|
}
|
|
2883
3295
|
function loadTimeInjectState(guard, dataDir) {
|
|
2884
3296
|
try {
|
|
2885
|
-
const parsed = JSON.parse(
|
|
3297
|
+
const parsed = JSON.parse(fs12.readFileSync(guard.assert(timeInjectStatePath(dataDir)), "utf8"));
|
|
2886
3298
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
2887
3299
|
} catch {
|
|
2888
3300
|
return {};
|
|
@@ -2947,7 +3359,7 @@ var cachedVersion = null;
|
|
|
2947
3359
|
function pluginVersion(paths) {
|
|
2948
3360
|
if (cachedVersion) return cachedVersion;
|
|
2949
3361
|
try {
|
|
2950
|
-
const pkg = JSON.parse(
|
|
3362
|
+
const pkg = JSON.parse(fs13.readFileSync(path13.join(paths.packageRoot, "package.json"), "utf8"));
|
|
2951
3363
|
cachedVersion = typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
2952
3364
|
} catch {
|
|
2953
3365
|
cachedVersion = "unknown";
|
|
@@ -2956,12 +3368,20 @@ function pluginVersion(paths) {
|
|
|
2956
3368
|
}
|
|
2957
3369
|
function homeSessionId(paths, guard) {
|
|
2958
3370
|
try {
|
|
2959
|
-
const raw = loadEncryptedText(guard,
|
|
3371
|
+
const raw = loadEncryptedText(guard, path13.join(paths.dataDir, "gate.json"));
|
|
2960
3372
|
return JSON.parse(raw ?? "{}").sessionId ?? null;
|
|
2961
3373
|
} catch {
|
|
2962
3374
|
return null;
|
|
2963
3375
|
}
|
|
2964
3376
|
}
|
|
3377
|
+
function seedAuditSink2(paths) {
|
|
3378
|
+
return (entry) => {
|
|
3379
|
+
try {
|
|
3380
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", entry);
|
|
3381
|
+
} catch {
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
2965
3385
|
function auditInterests(paths, action, result, detail) {
|
|
2966
3386
|
if (!result.ok) return;
|
|
2967
3387
|
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", {
|
|
@@ -2978,13 +3398,13 @@ function loadSessionTitles() {
|
|
|
2978
3398
|
if (row && typeof row.val === "string" && row.val && !titles[id]) titles[id] = row.val;
|
|
2979
3399
|
};
|
|
2980
3400
|
try {
|
|
2981
|
-
const dir =
|
|
2982
|
-
for (const file of
|
|
3401
|
+
const dir = path13.join(os.homedir(), ".dsh", "storages", "session_projcache", "sessions");
|
|
3402
|
+
for (const file of fs13.readdirSync(dir)) {
|
|
2983
3403
|
if (!file.endsWith(".json")) continue;
|
|
2984
3404
|
const id = file.slice(0, -".json".length);
|
|
2985
3405
|
if (!id.startsWith("session-")) continue;
|
|
2986
3406
|
try {
|
|
2987
|
-
const record = JSON.parse(
|
|
3407
|
+
const record = JSON.parse(fs13.readFileSync(path13.join(dir, file), "utf8"));
|
|
2988
3408
|
take(id, record.record);
|
|
2989
3409
|
} catch {
|
|
2990
3410
|
}
|
|
@@ -2992,7 +3412,7 @@ function loadSessionTitles() {
|
|
|
2992
3412
|
} catch {
|
|
2993
3413
|
}
|
|
2994
3414
|
try {
|
|
2995
|
-
const raw = JSON.parse(
|
|
3415
|
+
const raw = JSON.parse(fs13.readFileSync(path13.join(os.homedir(), ".dsh", "storages", "session_projcache.json"), "utf8"));
|
|
2996
3416
|
const walk = (node) => {
|
|
2997
3417
|
if (!node || typeof node !== "object") return;
|
|
2998
3418
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -3010,8 +3430,8 @@ function loadSessionTitles() {
|
|
|
3010
3430
|
}
|
|
3011
3431
|
function loadVisibleSessionIds(registryFile) {
|
|
3012
3432
|
try {
|
|
3013
|
-
const file = registryFile ??
|
|
3014
|
-
const j = JSON.parse(
|
|
3433
|
+
const file = registryFile ?? path13.join(os.homedir(), ".dsh", "storages", "workspace.json");
|
|
3434
|
+
const j = JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
3015
3435
|
const archived = new Set(
|
|
3016
3436
|
(Array.isArray(j.global?.archivedSessionIds) ? j.global.archivedSessionIds : []).map((s) => String(s))
|
|
3017
3437
|
);
|
|
@@ -3082,15 +3502,15 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
3082
3502
|
});
|
|
3083
3503
|
}
|
|
3084
3504
|
case "sessions.list": {
|
|
3085
|
-
const root =
|
|
3505
|
+
const root = path13.join(os.homedir(), ".dsh", "sessions");
|
|
3086
3506
|
const bindings = loadBindings(guard, paths.settingsDir).bindings;
|
|
3087
3507
|
const home = homeSessionId(paths, guard);
|
|
3088
3508
|
const titles = loadSessionTitles();
|
|
3089
3509
|
const { visible, registryFound } = loadVisibleSessionIds();
|
|
3090
3510
|
const out = [];
|
|
3091
|
-
if (
|
|
3092
|
-
for (const slug of
|
|
3093
|
-
for (const id of
|
|
3511
|
+
if (fs13.existsSync(root)) {
|
|
3512
|
+
for (const slug of fs13.readdirSync(root)) {
|
|
3513
|
+
for (const id of fs13.readdirSync(path13.join(root, slug))) {
|
|
3094
3514
|
if (registryFound && id !== home && !visible.has(id)) continue;
|
|
3095
3515
|
const binding = bindings.find((b) => b.sessionId === id);
|
|
3096
3516
|
out.push({
|
|
@@ -3126,7 +3546,7 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
3126
3546
|
let homeReset = false;
|
|
3127
3547
|
if (id === homeSessionId(paths, guard)) {
|
|
3128
3548
|
try {
|
|
3129
|
-
|
|
3549
|
+
fs13.rmSync(guard.assert(path13.join(paths.dataDir, "gate.json")), { force: true });
|
|
3130
3550
|
homeReset = true;
|
|
3131
3551
|
} catch {
|
|
3132
3552
|
}
|
|
@@ -3142,15 +3562,15 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
3142
3562
|
});
|
|
3143
3563
|
}
|
|
3144
3564
|
case "seeds.archive": {
|
|
3145
|
-
const s = archiveSeedById(guard, seedsFilePath(paths.dataDir), String(p.id ?? ""), "completed");
|
|
3565
|
+
const s = archiveSeedById(guard, seedsFilePath(paths.dataDir), String(p.id ?? ""), "completed", Date.now(), seedAuditSink2(paths));
|
|
3146
3566
|
return s ? ok(s) : err("not-found", "active seed not found");
|
|
3147
3567
|
}
|
|
3148
3568
|
case "seeds.restore": {
|
|
3149
|
-
const r = restoreSeed(guard, seedsFilePath(paths.dataDir), policy, String(p.id ?? ""));
|
|
3569
|
+
const r = restoreSeed(guard, seedsFilePath(paths.dataDir), policy, String(p.id ?? ""), Date.now(), seedAuditSink2(paths));
|
|
3150
3570
|
return r.ok ? ok(r.seed) : err("restore-failed", r.reason);
|
|
3151
3571
|
}
|
|
3152
3572
|
case "seeds.delete": {
|
|
3153
|
-
return ok({ deleted: deleteSeed(guard, seedsFilePath(paths.dataDir), String(p.id ?? "")) });
|
|
3573
|
+
return ok({ deleted: deleteSeed(guard, seedsFilePath(paths.dataDir), String(p.id ?? ""), seedAuditSink2(paths)) });
|
|
3154
3574
|
}
|
|
3155
3575
|
// ── 兴趣范围 / 浏览时段(v1.4.0;首编继承出厂,见 interests-edit.ts)──
|
|
3156
3576
|
case "interests.list":
|
|
@@ -3208,16 +3628,61 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
3208
3628
|
lines.push(`- [${e.topic}/${e.subTopic}] ${e.content} (conf ${e.confidence.toFixed(2)}, ${e.temporal})`);
|
|
3209
3629
|
}
|
|
3210
3630
|
}
|
|
3211
|
-
const out =
|
|
3631
|
+
const out = path13.join(paths.exportsDir, `profile-export-${Date.now()}.md`);
|
|
3212
3632
|
writeText(guard, out, lines.join("\n") + "\n");
|
|
3213
3633
|
return ok({ path: out });
|
|
3214
3634
|
}
|
|
3215
3635
|
case "ledger.open": {
|
|
3216
3636
|
const f = ledgerFilePath(paths.dataDir);
|
|
3217
|
-
if (!
|
|
3637
|
+
if (!fs13.existsSync(guard.assert(f))) fs13.writeFileSync(f, "# \u8D26\u672C\n", "utf8");
|
|
3218
3638
|
spawn2("cmd", ["/c", "start", "", f], { detached: true, stdio: "ignore" }).unref();
|
|
3219
3639
|
return ok({ path: f });
|
|
3220
3640
|
}
|
|
3641
|
+
case "weekly.list":
|
|
3642
|
+
return ok({ reports: listWeeklyReports(guard, paths.dataDir) });
|
|
3643
|
+
case "weekly.get": {
|
|
3644
|
+
const file = String(p.file ?? "");
|
|
3645
|
+
const rep = readWeeklyReport(guard, paths.dataDir, file);
|
|
3646
|
+
if (!rep) return err("bad-request", `no such report: ${file}`);
|
|
3647
|
+
return ok(rep);
|
|
3648
|
+
}
|
|
3649
|
+
case "migrate.export": {
|
|
3650
|
+
const passphrase = String(p.passphrase ?? "");
|
|
3651
|
+
if (!passphrase) return err("bad-request", "\u9700\u8981\u8BBE\u7F6E\u53E3\u4EE4");
|
|
3652
|
+
const { collectMigrationEntries, encryptContainer } = await import("./migrate-N6NXMZ3G.js");
|
|
3653
|
+
const { entries } = collectMigrationEntries(guard, paths);
|
|
3654
|
+
if (entries.length === 0) return err("bad-request", "\u6CA1\u6709\u53EF\u6253\u5305\u7684\u8BB0\u5FC6\u6587\u4EF6\uFF08data/ \u662F\u7A7A\u7684\uFF09");
|
|
3655
|
+
const out = path13.join(paths.exportsDir, `heartbeat-memory-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.hbmig`);
|
|
3656
|
+
writeText(guard, out, encryptContainer(entries, passphrase));
|
|
3657
|
+
appendAuditLine(guard.assert(paths.logsDir + "/heartbeat.jsonl"), { event: "migrate_export", files: entries.length });
|
|
3658
|
+
return ok({ path: out, count: entries.length });
|
|
3659
|
+
}
|
|
3660
|
+
case "migrate.import": {
|
|
3661
|
+
const passphrase = String(p.passphrase ?? "");
|
|
3662
|
+
const file = String(p.file ?? "");
|
|
3663
|
+
if (!passphrase || !file) return err("bad-request", "\u9700\u8981\u5BB9\u5668\u8DEF\u5F84\u4E0E\u53E3\u4EE4");
|
|
3664
|
+
const { decryptContainer, applyMigrationEntries } = await import("./migrate-N6NXMZ3G.js");
|
|
3665
|
+
let text;
|
|
3666
|
+
try {
|
|
3667
|
+
text = fs13.readFileSync(file, "utf8");
|
|
3668
|
+
} catch {
|
|
3669
|
+
return err("bad-request", `\u8BFB\u4E0D\u5230\u8FC1\u79FB\u5305\uFF1A${file}`);
|
|
3670
|
+
}
|
|
3671
|
+
let container;
|
|
3672
|
+
try {
|
|
3673
|
+
container = decryptContainer(text, passphrase);
|
|
3674
|
+
} catch (e) {
|
|
3675
|
+
return err("bad-request", String(e instanceof Error ? e.message : e));
|
|
3676
|
+
}
|
|
3677
|
+
const result = applyMigrationEntries(guard, paths, container.files);
|
|
3678
|
+
appendAuditLine(guard.assert(paths.logsDir + "/heartbeat.jsonl"), {
|
|
3679
|
+
event: "migrate_import",
|
|
3680
|
+
restored: result.restored.length,
|
|
3681
|
+
backedUp: result.backedUp.length,
|
|
3682
|
+
skipped: result.skipped.length
|
|
3683
|
+
});
|
|
3684
|
+
return ok(result);
|
|
3685
|
+
}
|
|
3221
3686
|
default:
|
|
3222
3687
|
return err("bad-request", `unknown endpoint ${JSON.stringify(endpoint)}`);
|
|
3223
3688
|
}
|
|
@@ -3348,6 +3813,73 @@ function registerStatusbarSection(ctx, guard, paths, opts) {
|
|
|
3348
3813
|
});
|
|
3349
3814
|
}
|
|
3350
3815
|
|
|
3816
|
+
// src/ledger/tool.ts
|
|
3817
|
+
var MAX_TEXT = 120;
|
|
3818
|
+
function clean(v) {
|
|
3819
|
+
return String(v ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_TEXT);
|
|
3820
|
+
}
|
|
3821
|
+
function fmtEntry2(e) {
|
|
3822
|
+
return `#${e.id} [${e.status}] ${e.date} ${e.text}`;
|
|
3823
|
+
}
|
|
3824
|
+
function buildLedgerTool(guard, file) {
|
|
3825
|
+
return {
|
|
3826
|
+
name: "ledger",
|
|
3827
|
+
description: '\u5171\u4EAB\u8D26\u672C\u5DE5\u5177\uFF08\u5FC3\u8DF3\u63D2\u4EF6\u7684\u5F85\u529E\u4E0E\u4E8B\u9879\u8BB0\u5F55\uFF09\u3002\u5F53\u7528\u6237\u63D0\u5230\u8BA1\u5212\u3001\u5F85\u529E\u3001\u627F\u8BFA\u3001\u60F3\u505A\u7684\u4E8B\uFF0C\u6216\u8005\u67D0\u4EF6\u4E8B\u5DF2\u7ECF\u5B8C\u6210\u65F6\uFF0C\u4E3B\u52A8\u8C03\u7528\u672C\u5DE5\u5177\u767B\u8BB0\u6216\u52FE\u6389\u2014\u2014\u4E0D\u9700\u8981\u7B49\u7528\u6237\u660E\u786E\u8BF4"\u8BB0\u4E00\u4E0B"\u3002action=add \u767B\u8BB0\u65B0\u4E8B\u9879\uFF08text \u5FC5\u586B\uFF09\uFF1Baction=list \u67E5\u770B\u5F53\u524D\u672A\u5B8C\u6210\u4E8B\u9879\uFF1Baction=done \u52FE\u6389\u4E00\u6761\uFF08key \u4E3A #id \u6216\u6587\u672C\u5B50\u4E32\uFF09\u3002',
|
|
3828
|
+
parameters: {
|
|
3829
|
+
type: "object",
|
|
3830
|
+
properties: {
|
|
3831
|
+
action: { type: "string", enum: ["add", "list", "done"], description: "add=\u767B\u8BB0\uFF0Clist=\u67E5\u672A\u5B8C\u6210\uFF0Cdone=\u52FE\u6389" },
|
|
3832
|
+
text: { type: "string", description: "action=add \u65F6\u5FC5\u586B\uFF1A\u4E8B\u9879\u5185\u5BB9\uFF08\u4E00\u53E5\u8BDD\uFF09" },
|
|
3833
|
+
key: { type: "string", description: "action=done \u65F6\u5FC5\u586B\uFF1A#id \u6216\u4E8B\u9879\u6587\u672C\u7684\u552F\u4E00\u5B50\u4E32" }
|
|
3834
|
+
},
|
|
3835
|
+
required: ["action"],
|
|
3836
|
+
additionalProperties: false
|
|
3837
|
+
},
|
|
3838
|
+
output: {
|
|
3839
|
+
schema: {
|
|
3840
|
+
type: "object",
|
|
3841
|
+
properties: {
|
|
3842
|
+
ok: { type: "boolean" },
|
|
3843
|
+
message: { type: "string" }
|
|
3844
|
+
},
|
|
3845
|
+
required: ["ok", "message"],
|
|
3846
|
+
additionalProperties: false
|
|
3847
|
+
},
|
|
3848
|
+
render: (_args, value) => {
|
|
3849
|
+
const v = value;
|
|
3850
|
+
return [{ type: "text", text: String(v.message ?? "") }];
|
|
3851
|
+
}
|
|
3852
|
+
},
|
|
3853
|
+
timeoutMs: 5e3,
|
|
3854
|
+
// Ledger writes go through read-modify-write on one file; never parallel.
|
|
3855
|
+
isConcurrencySafe: () => false,
|
|
3856
|
+
async execute(args) {
|
|
3857
|
+
const a = args ?? {};
|
|
3858
|
+
const action = String(a.action ?? "");
|
|
3859
|
+
if (action === "add") {
|
|
3860
|
+
const text = clean(a.text);
|
|
3861
|
+
if (!text) return { ok: false, message: "add \u9700\u8981\u975E\u7A7A text" };
|
|
3862
|
+
const e = appendEntry(guard, file, text);
|
|
3863
|
+
return { ok: true, message: `\u5DF2\u767B\u8BB0 ${fmtEntry2(e)}` };
|
|
3864
|
+
}
|
|
3865
|
+
if (action === "done") {
|
|
3866
|
+
const key = clean(a.key);
|
|
3867
|
+
if (!key) return { ok: false, message: "done \u9700\u8981 key\uFF08#id \u6216\u6587\u672C\u5B50\u4E32\uFF09" };
|
|
3868
|
+
const e = markDone(guard, file, key);
|
|
3869
|
+
if (!e) return { ok: false, message: `\u6CA1\u6709\u627E\u5230\u5339\u914D\u300C${key}\u300D\u7684\u672A\u5B8C\u6210\u4E8B\u9879\uFF08\u5148\u7528 list \u67E5\uFF09` };
|
|
3870
|
+
return { ok: true, message: `\u5DF2\u5B8C\u6210 ${fmtEntry2(e)}` };
|
|
3871
|
+
}
|
|
3872
|
+
if (action === "list") {
|
|
3873
|
+
const items = scanPending(guard, file).map(fmtEntry2);
|
|
3874
|
+
return { ok: true, message: items.length > 0 ? `\u5F53\u524D\u672A\u5B8C\u6210 ${items.length} \u9879\uFF1A
|
|
3875
|
+
${items.join("\n")}` : "\u8D26\u672C\u91CC\u6CA1\u6709\u672A\u5B8C\u6210\u4E8B\u9879" };
|
|
3876
|
+
}
|
|
3877
|
+
const total = readLedger(guard, file).entries.length;
|
|
3878
|
+
return { ok: false, message: `\u672A\u77E5 action\uFF0C\u8D26\u672C\u5171 ${total} \u6761` };
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3351
3883
|
// src/index.ts
|
|
3352
3884
|
var name = "heartbeat";
|
|
3353
3885
|
var inject = ["agents", "settings"];
|
|
@@ -3379,7 +3911,13 @@ var Config = Schema.object({
|
|
|
3379
3911
|
/** v1.6.3 闲着模式:素材池为空时用画像话题兜底主动搭话(默认关)。 */
|
|
3380
3912
|
idleMode: Schema.boolean().default(false),
|
|
3381
3913
|
/** v1.7.0 节省 token 模式:用户离开(闲置 ≥30 分钟)或锁屏时整跳暂停(默认关)。 */
|
|
3382
|
-
tokenSaver: Schema.boolean().default(false)
|
|
3914
|
+
tokenSaver: Schema.boolean().default(false),
|
|
3915
|
+
/** v1.8.0 账本工具:向所有日常会话 agent 注册共享账本工具(默认开)。 */
|
|
3916
|
+
ledgerTool: Schema.boolean().default(true),
|
|
3917
|
+
/** v1.9.0 素材报账工具:投递后由陪伴 agent 主动报账(默认开)。 */
|
|
3918
|
+
seedReportTool: Schema.boolean().default(true),
|
|
3919
|
+
/** v1.8.0 引擎室追加工具:逗号分隔的全局工具名,存在才加入白名单(bili 压缩工具自动探测,无需手填)。 */
|
|
3920
|
+
extraTools: Schema.string().default("")
|
|
3383
3921
|
});
|
|
3384
3922
|
function apply(ctx, config = {}) {
|
|
3385
3923
|
const paths = initWorkspace(config.dataDir ? { dataDir: config.dataDir } : {});
|
|
@@ -3398,7 +3936,14 @@ function apply(ctx, config = {}) {
|
|
|
3398
3936
|
if (config.maxDailySend && config.maxDailySend >= 1) {
|
|
3399
3937
|
policy = { ...policy, gate: { ...policy.gate, maxDailySend: config.maxDailySend } };
|
|
3400
3938
|
}
|
|
3401
|
-
const deps = {
|
|
3939
|
+
const deps = {
|
|
3940
|
+
ctx,
|
|
3941
|
+
paths,
|
|
3942
|
+
guard,
|
|
3943
|
+
policy,
|
|
3944
|
+
agentPreset: config.agentPreset || "heartbeat",
|
|
3945
|
+
extraTools: (config.extraTools ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
3946
|
+
};
|
|
3402
3947
|
setRuntime({
|
|
3403
3948
|
paths,
|
|
3404
3949
|
guard,
|
|
@@ -3527,6 +4072,42 @@ function apply(ctx, config = {}) {
|
|
|
3527
4072
|
if (typeof merged.tokenSaver === "boolean") tokenSaverRef = merged.tokenSaver;
|
|
3528
4073
|
};
|
|
3529
4074
|
installHeartbeatRpc(ctx, { paths, guard, policy, ui: { get: uiGet, set: uiSet } });
|
|
4075
|
+
if (config.ledgerTool !== false) {
|
|
4076
|
+
ctx.inject(["tools"], (scoped) => {
|
|
4077
|
+
const audit = (entry) => {
|
|
4078
|
+
try {
|
|
4079
|
+
appendAuditLine(guard.assert(paths.logsDir + "/heartbeat.jsonl"), entry);
|
|
4080
|
+
} catch {
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
try {
|
|
4084
|
+
const tools = scoped.tools;
|
|
4085
|
+
if (!tools || typeof tools.register !== "function") throw new Error("ToolRuntime.register unavailable on this host");
|
|
4086
|
+
tools.register(buildLedgerTool(guard, ledgerFilePath(paths.dataDir)));
|
|
4087
|
+
audit({ event: "ledger_tool_registered" });
|
|
4088
|
+
} catch (e) {
|
|
4089
|
+
audit({ event: "ledger_tool_register_failed", error: String(e).slice(0, 160) });
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
4093
|
+
if (config.seedReportTool !== false) {
|
|
4094
|
+
ctx.inject(["tools"], (scoped) => {
|
|
4095
|
+
const audit = (entry) => {
|
|
4096
|
+
try {
|
|
4097
|
+
appendAuditLine(guard.assert(paths.logsDir + "/heartbeat.jsonl"), entry);
|
|
4098
|
+
} catch {
|
|
4099
|
+
}
|
|
4100
|
+
};
|
|
4101
|
+
try {
|
|
4102
|
+
const tools = scoped.tools;
|
|
4103
|
+
if (!tools || typeof tools.register !== "function") throw new Error("ToolRuntime.register unavailable on this host");
|
|
4104
|
+
tools.register(buildSeedReportTool(guard, reportFilePath(paths.dataDir)));
|
|
4105
|
+
audit({ event: "seed_report_tool_registered" });
|
|
4106
|
+
} catch (e) {
|
|
4107
|
+
audit({ event: "seed_report_tool_register_failed", error: String(e).slice(0, 160) });
|
|
4108
|
+
}
|
|
4109
|
+
});
|
|
4110
|
+
}
|
|
3530
4111
|
startOrchestrator(deps);
|
|
3531
4112
|
const statusReader = new StatusReader();
|
|
3532
4113
|
registerStatusbarSection(ctx, guard, paths, {
|