@kanadego/dsh-heartbeat 1.6.4 → 1.7.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 +89 -30
- package/client.js +52 -28
- package/dist/{chunk-SJUNS2BE.js → chunk-3QJJRXIR.js} +1 -1
- package/dist/chunk-3QJJRXIR.js.map +1 -0
- package/dist/{chunk-TFMQKETS.js → chunk-ON4MSU6E.js} +16 -1
- package/dist/chunk-ON4MSU6E.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/index.js +257 -118
- package/dist/index.js.map +1 -1
- package/dist/{runtime-YHJT6TXF.js → runtime-W2OWJIHY.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-SJUNS2BE.js.map +0 -1
- package/dist/chunk-TFMQKETS.js.map +0 -1
- /package/dist/{runtime-YHJT6TXF.js.map → runtime-W2OWJIHY.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -21,12 +21,13 @@ import {
|
|
|
21
21
|
runDeterministicAging,
|
|
22
22
|
scanPending,
|
|
23
23
|
sendNewMessageHint,
|
|
24
|
+
updateUserPolicy,
|
|
24
25
|
userPresetRoot
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ON4MSU6E.js";
|
|
26
27
|
import {
|
|
27
28
|
getRuntime,
|
|
28
29
|
setRuntime
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-3QJJRXIR.js";
|
|
30
31
|
import {
|
|
31
32
|
activeSeeds,
|
|
32
33
|
addSeed,
|
|
@@ -851,14 +852,57 @@ defineMethod("transform", [
|
|
|
851
852
|
"preserve"
|
|
852
853
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
853
854
|
|
|
855
|
+
// src/config/ui-config.ts
|
|
856
|
+
import fs from "fs";
|
|
857
|
+
import path from "path";
|
|
858
|
+
var USER_UI_FILE = "ui.json";
|
|
859
|
+
var RANGES = {
|
|
860
|
+
intervalMin: { min: 1, max: 1440 },
|
|
861
|
+
maxDailySend: { min: 1, max: 50 },
|
|
862
|
+
timeInjectMin: { min: 0, max: 1440 }
|
|
863
|
+
};
|
|
864
|
+
function sanitize(raw) {
|
|
865
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
866
|
+
const r = raw;
|
|
867
|
+
const out = {};
|
|
868
|
+
for (const key of ["intervalMin", "maxDailySend", "timeInjectMin"]) {
|
|
869
|
+
const v = r[key];
|
|
870
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= RANGES[key].min && v <= RANGES[key].max) {
|
|
871
|
+
out[key] = Math.floor(v);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (typeof r.statusbar === "boolean") out.statusbar = r.statusbar;
|
|
875
|
+
if (typeof r.idleMode === "boolean") out.idleMode = r.idleMode;
|
|
876
|
+
if (typeof r.tokenSaver === "boolean") out.tokenSaver = r.tokenSaver;
|
|
877
|
+
return out;
|
|
878
|
+
}
|
|
879
|
+
function uiConfigPath(settingsDir) {
|
|
880
|
+
return path.join(settingsDir, USER_UI_FILE);
|
|
881
|
+
}
|
|
882
|
+
function loadUiConfig(guard, settingsDir) {
|
|
883
|
+
try {
|
|
884
|
+
const raw = JSON.parse(fs.readFileSync(guard.assert(uiConfigPath(settingsDir)), "utf8"));
|
|
885
|
+
return sanitize(raw);
|
|
886
|
+
} catch {
|
|
887
|
+
return {};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function saveUiConfig(guard, settingsDir, patch) {
|
|
891
|
+
const merged = sanitize({ ...loadUiConfig(guard, settingsDir), ...patch });
|
|
892
|
+
const file = guard.assert(uiConfigPath(settingsDir));
|
|
893
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
894
|
+
fs.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
895
|
+
return merged;
|
|
896
|
+
}
|
|
897
|
+
|
|
854
898
|
// src/core/orchestrator.ts
|
|
855
899
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
856
|
-
import
|
|
857
|
-
import
|
|
900
|
+
import fs9 from "fs";
|
|
901
|
+
import path9 from "path";
|
|
858
902
|
|
|
859
903
|
// src/env/envpulse.ts
|
|
860
|
-
import
|
|
861
|
-
import
|
|
904
|
+
import fs3 from "fs";
|
|
905
|
+
import path3 from "path";
|
|
862
906
|
import { spawnSync } from "child_process";
|
|
863
907
|
|
|
864
908
|
// src/env/timeflow.ts
|
|
@@ -896,8 +940,8 @@ function timeContext(now = /* @__PURE__ */ new Date()) {
|
|
|
896
940
|
}
|
|
897
941
|
|
|
898
942
|
// src/gate/busy-rules.ts
|
|
899
|
-
import
|
|
900
|
-
import
|
|
943
|
+
import fs2 from "fs";
|
|
944
|
+
import path2 from "path";
|
|
901
945
|
var own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
902
946
|
var FALLBACK_RULES = {
|
|
903
947
|
busy: {},
|
|
@@ -906,7 +950,7 @@ var FALLBACK_RULES = {
|
|
|
906
950
|
};
|
|
907
951
|
function loadBusyRules(configDir) {
|
|
908
952
|
try {
|
|
909
|
-
const raw = JSON.parse(
|
|
953
|
+
const raw = JSON.parse(fs2.readFileSync(path2.join(configDir, "busy-rules.json"), "utf8"));
|
|
910
954
|
if (!raw.busy || !raw.idle) return FALLBACK_RULES;
|
|
911
955
|
return raw;
|
|
912
956
|
} catch {
|
|
@@ -946,21 +990,36 @@ function presenceOf(idleSec, windowClass) {
|
|
|
946
990
|
return windowClass === "busy" ? "active" : "present";
|
|
947
991
|
}
|
|
948
992
|
function probeIdle(guard, paths) {
|
|
949
|
-
const tmp =
|
|
993
|
+
const tmp = path3.join(paths.tmpDir, `idle-${Date.now()}.txt`);
|
|
950
994
|
try {
|
|
951
995
|
const out = guard.assert(tmp);
|
|
952
996
|
const r = spawnSync(
|
|
953
997
|
"powershell.exe",
|
|
954
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
|
|
998
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path3.join(paths.assetsDir, "idle.ps1"), "-out", out],
|
|
955
999
|
{ timeout: 2e4, encoding: "utf8" }
|
|
956
1000
|
);
|
|
957
|
-
if (r.status !== 0 || !
|
|
958
|
-
const v = Number.parseInt(
|
|
1001
|
+
if (r.status !== 0 || !fs3.existsSync(out)) return -1;
|
|
1002
|
+
const v = Number.parseInt(fs3.readFileSync(out, "utf8").trim(), 10);
|
|
959
1003
|
return Number.isNaN(v) ? -1 : v;
|
|
960
1004
|
} catch {
|
|
961
1005
|
return -1;
|
|
962
1006
|
} finally {
|
|
963
|
-
|
|
1007
|
+
fs3.rmSync(tmp, { force: true });
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function probeIdleSeconds(guard, paths) {
|
|
1011
|
+
return probeIdle(guard, paths);
|
|
1012
|
+
}
|
|
1013
|
+
function probeWorkstationLocked() {
|
|
1014
|
+
try {
|
|
1015
|
+
const r = spawnSync(
|
|
1016
|
+
"powershell.exe",
|
|
1017
|
+
["-NoProfile", "-Command", 'if (Get-Process -Name LogonUI -ErrorAction SilentlyContinue) { "locked" } else { "unlocked" }'],
|
|
1018
|
+
{ timeout: 1e4, encoding: "utf8" }
|
|
1019
|
+
);
|
|
1020
|
+
return r.status === 0 && String(r.stdout || "").includes("locked");
|
|
1021
|
+
} catch {
|
|
1022
|
+
return false;
|
|
964
1023
|
}
|
|
965
1024
|
}
|
|
966
1025
|
function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__ */ new Date()) {
|
|
@@ -978,7 +1037,7 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
|
|
|
978
1037
|
festival: t.festival
|
|
979
1038
|
};
|
|
980
1039
|
try {
|
|
981
|
-
|
|
1040
|
+
fs3.writeFileSync(path3.join(paths.dataDir, "envpulse.json"), JSON.stringify(snapshot, null, 1), "utf8");
|
|
982
1041
|
} catch {
|
|
983
1042
|
}
|
|
984
1043
|
writePulseStream(paths, snapshot);
|
|
@@ -986,7 +1045,7 @@ function collectPulse(guard, paths, rules, fgProcess = null, now = /* @__PURE__
|
|
|
986
1045
|
}
|
|
987
1046
|
function writePulseStream(paths, snapshot) {
|
|
988
1047
|
try {
|
|
989
|
-
appendAuditLine(
|
|
1048
|
+
appendAuditLine(path3.join(paths.logsDir, "envpulse.jsonl"), {
|
|
990
1049
|
event: "pulse",
|
|
991
1050
|
takenAt: snapshot.takenAt,
|
|
992
1051
|
idleSeconds: snapshot.idleSeconds,
|
|
@@ -1002,7 +1061,7 @@ function writePulseStream(paths, snapshot) {
|
|
|
1002
1061
|
}
|
|
1003
1062
|
function readPulse(guard, paths) {
|
|
1004
1063
|
try {
|
|
1005
|
-
const raw =
|
|
1064
|
+
const raw = fs3.readFileSync(path3.join(paths.dataDir, "envpulse.json"), "utf8");
|
|
1006
1065
|
return JSON.parse(raw);
|
|
1007
1066
|
} catch {
|
|
1008
1067
|
return null;
|
|
@@ -1010,33 +1069,33 @@ function readPulse(guard, paths) {
|
|
|
1010
1069
|
}
|
|
1011
1070
|
|
|
1012
1071
|
// src/screen/screenpulse.ts
|
|
1013
|
-
import
|
|
1014
|
-
import
|
|
1072
|
+
import fs4 from "fs";
|
|
1073
|
+
import path4 from "path";
|
|
1015
1074
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1016
1075
|
var SCREEN_JSON = "screen.json";
|
|
1017
1076
|
var SCREEN_JPG = "screen.jpg";
|
|
1018
1077
|
function screenJsonPath(paths) {
|
|
1019
|
-
return
|
|
1078
|
+
return path4.join(paths.dataDir, SCREEN_JSON);
|
|
1020
1079
|
}
|
|
1021
1080
|
function screenJpgPath(paths) {
|
|
1022
|
-
return
|
|
1081
|
+
return path4.join(paths.dataDir, SCREEN_JPG);
|
|
1023
1082
|
}
|
|
1024
1083
|
function collectScreen(guard, paths, now = Date.now()) {
|
|
1025
|
-
const rawJson =
|
|
1026
|
-
const rawJpg =
|
|
1084
|
+
const rawJson = path4.join(paths.tmpDir, "screen.raw.json");
|
|
1085
|
+
const rawJpg = path4.join(paths.tmpDir, "screen.raw.jpg");
|
|
1027
1086
|
let encJson = "";
|
|
1028
1087
|
try {
|
|
1029
1088
|
const r = spawnSync2(
|
|
1030
1089
|
"powershell.exe",
|
|
1031
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
|
|
1090
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path4.join(paths.assetsDir, "screenpulse.ps1"), "-outdir", paths.tmpDir],
|
|
1032
1091
|
{ timeout: 45e3, encoding: "utf8" }
|
|
1033
1092
|
);
|
|
1034
1093
|
if (r.status !== 0) {
|
|
1035
1094
|
return { ok: false, capturedAt: null, hasShot: false, visibleCount: 0, error: `collector exit ${r.status}` };
|
|
1036
1095
|
}
|
|
1037
|
-
const raw =
|
|
1096
|
+
const raw = fs4.readFileSync(rawJson, "utf8");
|
|
1038
1097
|
const meta = JSON.parse(raw);
|
|
1039
|
-
if (meta.shot_path &&
|
|
1098
|
+
if (meta.shot_path && fs4.existsSync(meta.shot_path)) {
|
|
1040
1099
|
try {
|
|
1041
1100
|
encryptFile(guard, meta.shot_path, screenJpgPath(paths));
|
|
1042
1101
|
} catch {
|
|
@@ -1044,51 +1103,51 @@ function collectScreen(guard, paths, now = Date.now()) {
|
|
|
1044
1103
|
}
|
|
1045
1104
|
const { shot_path: _drop, ...clean } = meta;
|
|
1046
1105
|
void _drop;
|
|
1047
|
-
encJson =
|
|
1048
|
-
|
|
1106
|
+
encJson = path4.join(paths.tmpDir, `screen.enc.${now}.json`);
|
|
1107
|
+
fs4.writeFileSync(encJson, JSON.stringify(clean, null, 1), "utf8");
|
|
1049
1108
|
encryptFile(guard, encJson, screenJsonPath(paths));
|
|
1050
1109
|
return {
|
|
1051
1110
|
ok: true,
|
|
1052
1111
|
capturedAt: meta.captured_at ?? null,
|
|
1053
|
-
hasShot:
|
|
1112
|
+
hasShot: fs4.existsSync(screenJpgPath(paths)),
|
|
1054
1113
|
visibleCount: Array.isArray(meta.windows) ? meta.windows.length : 0
|
|
1055
1114
|
};
|
|
1056
1115
|
} catch (e) {
|
|
1057
1116
|
return { ok: false, capturedAt: null, hasShot: false, visibleCount: 0, error: String(e) };
|
|
1058
1117
|
} finally {
|
|
1059
1118
|
for (const leftover of [rawJson, rawJpg, encJson]) {
|
|
1060
|
-
|
|
1119
|
+
fs4.rmSync(leftover, { force: true });
|
|
1061
1120
|
}
|
|
1062
1121
|
}
|
|
1063
1122
|
}
|
|
1064
1123
|
function readScreenJson(guard, paths) {
|
|
1065
1124
|
const f = screenJsonPath(paths);
|
|
1066
|
-
if (!
|
|
1067
|
-
const tmp =
|
|
1125
|
+
if (!fs4.existsSync(guard.assert(f))) return null;
|
|
1126
|
+
const tmp = path4.join(paths.tmpDir, `screen.read.${Date.now()}.json`);
|
|
1068
1127
|
try {
|
|
1069
1128
|
decryptFile(guard, f, guard.assert(tmp));
|
|
1070
|
-
return JSON.parse(
|
|
1129
|
+
return JSON.parse(fs4.readFileSync(tmp, "utf8"));
|
|
1071
1130
|
} catch {
|
|
1072
1131
|
return null;
|
|
1073
1132
|
} finally {
|
|
1074
|
-
|
|
1133
|
+
fs4.rmSync(tmp, { force: true });
|
|
1075
1134
|
}
|
|
1076
1135
|
}
|
|
1077
1136
|
function unlockShot(guard, paths) {
|
|
1078
1137
|
const f = screenJpgPath(paths);
|
|
1079
|
-
if (!
|
|
1080
|
-
const tmp =
|
|
1138
|
+
if (!fs4.existsSync(guard.assert(f))) return null;
|
|
1139
|
+
const tmp = path4.join(paths.tmpDir, `screen.view.${Date.now()}.jpg`);
|
|
1081
1140
|
decryptFile(guard, f, guard.assert(tmp));
|
|
1082
1141
|
return tmp;
|
|
1083
1142
|
}
|
|
1084
1143
|
function burnUnlocked(guard, tmpPath) {
|
|
1085
|
-
|
|
1144
|
+
fs4.rmSync(guard.assert(tmpPath), { force: true });
|
|
1086
1145
|
}
|
|
1087
1146
|
|
|
1088
1147
|
// src/screen/vision.ts
|
|
1089
1148
|
import { createRequire } from "module";
|
|
1090
|
-
import
|
|
1091
|
-
import
|
|
1149
|
+
import fs5 from "fs";
|
|
1150
|
+
import path5 from "path";
|
|
1092
1151
|
import { spawn } from "child_process";
|
|
1093
1152
|
var VISION_TIMEOUT_MS = 12e4;
|
|
1094
1153
|
var VISION_MAX_SUMMARY = 300;
|
|
@@ -1096,8 +1155,8 @@ function resolveModlensMain(fromUrl) {
|
|
|
1096
1155
|
try {
|
|
1097
1156
|
const req = createRequire(fromUrl);
|
|
1098
1157
|
const pkgJson = req.resolve("@liustack/modlens/package.json");
|
|
1099
|
-
const main =
|
|
1100
|
-
return
|
|
1158
|
+
const main = path5.join(path5.dirname(pkgJson), "dist", "main.js");
|
|
1159
|
+
return fs5.existsSync(main) ? main : null;
|
|
1101
1160
|
} catch {
|
|
1102
1161
|
return null;
|
|
1103
1162
|
}
|
|
@@ -1135,7 +1194,7 @@ function extractSummary(raw) {
|
|
|
1135
1194
|
var DEFAULT_VISION_PROVIDER = "openai";
|
|
1136
1195
|
function readVisionSettings(guard, paths) {
|
|
1137
1196
|
try {
|
|
1138
|
-
const raw =
|
|
1197
|
+
const raw = fs5.readFileSync(guard.assert(path5.join(paths.settingsDir, "vision.json")), "utf8");
|
|
1139
1198
|
const obj = JSON.parse(raw);
|
|
1140
1199
|
return {
|
|
1141
1200
|
...typeof obj.provider === "string" && obj.provider.trim() ? { provider: obj.provider.trim() } : {},
|
|
@@ -1150,7 +1209,7 @@ async function describeScreenShot(guard, paths, opts) {
|
|
|
1150
1209
|
if (!mainJs) return { ok: false, summary: null, error: "modlens not installed" };
|
|
1151
1210
|
const shot = unlockShot(guard, paths);
|
|
1152
1211
|
if (!shot) return { ok: false, summary: null, error: "no screenshot this beat" };
|
|
1153
|
-
const outJson =
|
|
1212
|
+
const outJson = path5.join(paths.tmpDir, `screen.vision.${Date.now()}.json`);
|
|
1154
1213
|
const spawnCli = opts.spawnCli ?? defaultSpawn;
|
|
1155
1214
|
const timeoutMs = opts.timeoutMs ?? VISION_TIMEOUT_MS;
|
|
1156
1215
|
const settings = opts.settings ?? readVisionSettings(guard, paths);
|
|
@@ -1175,7 +1234,7 @@ async function describeScreenShot(guard, paths, opts) {
|
|
|
1175
1234
|
if (code !== 0) {
|
|
1176
1235
|
return { ok: false, summary: null, error: `modlens exit ${code}${stderr ? `: ${stderr.slice(0, 140)}` : ""}` };
|
|
1177
1236
|
}
|
|
1178
|
-
const raw =
|
|
1237
|
+
const raw = fs5.existsSync(outJson) ? fs5.readFileSync(outJson, "utf8") : "";
|
|
1179
1238
|
const summary = extractSummary(raw);
|
|
1180
1239
|
if (!summary) return { ok: false, summary: null, error: "modlens produced no summary" };
|
|
1181
1240
|
return { ok: true, summary };
|
|
@@ -1183,17 +1242,17 @@ async function describeScreenShot(guard, paths, opts) {
|
|
|
1183
1242
|
return { ok: false, summary: null, error: String(e).slice(0, 160) };
|
|
1184
1243
|
} finally {
|
|
1185
1244
|
burnUnlocked(guard, shot);
|
|
1186
|
-
|
|
1245
|
+
fs5.rmSync(outJson, { force: true });
|
|
1187
1246
|
}
|
|
1188
1247
|
}
|
|
1189
1248
|
|
|
1190
1249
|
// src/gate/gate.ts
|
|
1191
|
-
import
|
|
1250
|
+
import path6 from "path";
|
|
1192
1251
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1193
|
-
import
|
|
1252
|
+
import fs6 from "fs";
|
|
1194
1253
|
var STABLE_WINDOW_MS = 15e3;
|
|
1195
1254
|
function sentFilePath(paths) {
|
|
1196
|
-
return
|
|
1255
|
+
return path6.join(paths.dataDir, "sent.json");
|
|
1197
1256
|
}
|
|
1198
1257
|
function localDay(now) {
|
|
1199
1258
|
const d = new Date(now);
|
|
@@ -1242,20 +1301,20 @@ function evaluateGate(input) {
|
|
|
1242
1301
|
};
|
|
1243
1302
|
}
|
|
1244
1303
|
function probeFrontWindowLive(guard, paths) {
|
|
1245
|
-
const tmp =
|
|
1304
|
+
const tmp = path6.join(paths.tmpDir, `frontwin.${Date.now()}.json`);
|
|
1246
1305
|
try {
|
|
1247
1306
|
const out = guard.assert(tmp);
|
|
1248
1307
|
const r = spawnSync3(
|
|
1249
1308
|
"powershell.exe",
|
|
1250
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
|
|
1309
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path6.join(paths.assetsDir, "frontwin.ps1"), "-out", out],
|
|
1251
1310
|
{ timeout: 1e4, encoding: "utf8" }
|
|
1252
1311
|
);
|
|
1253
|
-
if (r.status !== 0 || !
|
|
1254
|
-
return JSON.parse(
|
|
1312
|
+
if (r.status !== 0 || !fs6.existsSync(out)) return null;
|
|
1313
|
+
return JSON.parse(fs6.readFileSync(out, "utf8"));
|
|
1255
1314
|
} catch {
|
|
1256
1315
|
return null;
|
|
1257
1316
|
} finally {
|
|
1258
|
-
|
|
1317
|
+
fs6.rmSync(tmp, { force: true });
|
|
1259
1318
|
}
|
|
1260
1319
|
}
|
|
1261
1320
|
function probeFrontWindowSnapshot(guard, paths) {
|
|
@@ -1405,8 +1464,8 @@ function buildRuminationPrompt(input) {
|
|
|
1405
1464
|
}
|
|
1406
1465
|
|
|
1407
1466
|
// src/statusbar/store.ts
|
|
1408
|
-
import
|
|
1409
|
-
import
|
|
1467
|
+
import fs7 from "fs";
|
|
1468
|
+
import path7 from "path";
|
|
1410
1469
|
function deriveScene(input) {
|
|
1411
1470
|
if (input.quietHours) return "quiet-hours";
|
|
1412
1471
|
if (input.spokeThisBeat) return "just-spoke";
|
|
@@ -1421,14 +1480,14 @@ function clampNote(note) {
|
|
|
1421
1480
|
return t.length <= 30 ? t : t.slice(0, 30);
|
|
1422
1481
|
}
|
|
1423
1482
|
function statusFilePath(dataDir) {
|
|
1424
|
-
return
|
|
1483
|
+
return path7.join(dataDir, "settings", "status.json");
|
|
1425
1484
|
}
|
|
1426
1485
|
function writeStatus(guard, paths, state) {
|
|
1427
1486
|
atomicWriteJsonSync(guard.assert(statusFilePath(paths.dataDir)), state);
|
|
1428
1487
|
}
|
|
1429
1488
|
function readStatus(guard, paths) {
|
|
1430
1489
|
try {
|
|
1431
|
-
const raw =
|
|
1490
|
+
const raw = fs7.readFileSync(guard.assert(statusFilePath(paths.dataDir)), "utf8");
|
|
1432
1491
|
const parsed = JSON.parse(raw);
|
|
1433
1492
|
if (typeof parsed?.at !== "string" || typeof parsed?.scene !== "string") return null;
|
|
1434
1493
|
return parsed;
|
|
@@ -1444,7 +1503,7 @@ var StatusReader = class {
|
|
|
1444
1503
|
const file = statusFilePath(paths.dataDir);
|
|
1445
1504
|
let st;
|
|
1446
1505
|
try {
|
|
1447
|
-
st =
|
|
1506
|
+
st = fs7.statSync(guard.assert(file));
|
|
1448
1507
|
} catch {
|
|
1449
1508
|
this.mtimeMs = -1;
|
|
1450
1509
|
this.cached = null;
|
|
@@ -1684,16 +1743,16 @@ function buildDigest(guard, paths, policy, input = {}) {
|
|
|
1684
1743
|
}
|
|
1685
1744
|
|
|
1686
1745
|
// src/rhythm/rhythm.ts
|
|
1687
|
-
import
|
|
1688
|
-
import
|
|
1746
|
+
import fs8 from "fs";
|
|
1747
|
+
import path8 from "path";
|
|
1689
1748
|
var DAY_MS = 864e5;
|
|
1690
1749
|
var TAU_DAYS = 10;
|
|
1691
1750
|
function rhythmFilePath(paths) {
|
|
1692
|
-
return
|
|
1751
|
+
return path8.join(paths.dataDir, "profile_rhythm.json");
|
|
1693
1752
|
}
|
|
1694
1753
|
function loadRhythm(paths) {
|
|
1695
1754
|
try {
|
|
1696
|
-
return JSON.parse(
|
|
1755
|
+
return JSON.parse(fs8.readFileSync(rhythmFilePath(paths), "utf8"));
|
|
1697
1756
|
} catch {
|
|
1698
1757
|
return { histogram: {}, days: [], lastDecayAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1699
1758
|
}
|
|
@@ -1764,7 +1823,7 @@ function noteBeat(verdict, detail) {
|
|
|
1764
1823
|
lastBeat = { at: (/* @__PURE__ */ new Date()).toISOString(), verdict, ...detail };
|
|
1765
1824
|
}
|
|
1766
1825
|
function stateFile(paths) {
|
|
1767
|
-
return
|
|
1826
|
+
return path9.join(paths.dataDir, "gate.json");
|
|
1768
1827
|
}
|
|
1769
1828
|
function readBeatState(guard, paths) {
|
|
1770
1829
|
try {
|
|
@@ -1947,7 +2006,9 @@ async function hostUserMessage(text, label) {
|
|
|
1947
2006
|
const { createUserMessage } = await import("@deepseek-ai/dsh-llm");
|
|
1948
2007
|
return createUserMessage({
|
|
1949
2008
|
content: [{ type: "text", text }],
|
|
1950
|
-
|
|
2009
|
+
// 0.1.7 V4: kind:'plugin' is a retired generic wrapper, refused on write
|
|
2010
|
+
// ("producer-owned source kind") — the producer names its own kind.
|
|
2011
|
+
source: { kind: "heartbeat", plugin: "heartbeat", form: "snapshot", sections: [{ name: "heartbeat", text: label }] }
|
|
1951
2012
|
});
|
|
1952
2013
|
}
|
|
1953
2014
|
async function agentTurn(deps, agent, prompt, label, idleWaitMs = IDLE_WAIT_TIMEOUT_MS) {
|
|
@@ -2038,10 +2099,10 @@ async function observeBoundSessions(bc) {
|
|
|
2038
2099
|
const data = loadBindings2(guard, paths.settingsDir);
|
|
2039
2100
|
const targets = observeTargets(data);
|
|
2040
2101
|
if (targets.length === 0) return;
|
|
2041
|
-
const cursorFile =
|
|
2102
|
+
const cursorFile = path9.join(paths.dataDir, "cursors.json");
|
|
2042
2103
|
let cursors = {};
|
|
2043
2104
|
try {
|
|
2044
|
-
cursors = JSON.parse(
|
|
2105
|
+
cursors = JSON.parse(fs9.readFileSync(cursorFile, "utf8"));
|
|
2045
2106
|
} catch {
|
|
2046
2107
|
}
|
|
2047
2108
|
const inboxFile = inboxFilePath2(paths.dataDir);
|
|
@@ -2057,7 +2118,7 @@ async function observeBoundSessions(bc) {
|
|
|
2057
2118
|
const e = events[i];
|
|
2058
2119
|
if (e.type !== "user/message") continue;
|
|
2059
2120
|
const d = e.data;
|
|
2060
|
-
if (d?.source
|
|
2121
|
+
if (d?.source && (d.source.kind === "heartbeat" || d.source.kind === "plugin" || d.source.plugin === "heartbeat")) continue;
|
|
2061
2122
|
const text = (d?.content ?? []).filter((c) => c.type === "text").map((c) => c.text ?? "").join("").trim();
|
|
2062
2123
|
if (!text) continue;
|
|
2063
2124
|
inboxAppend(guard, inboxFile, {
|
|
@@ -2391,6 +2452,13 @@ function scheduleDeferredRetry(deps) {
|
|
|
2391
2452
|
void beat(deps);
|
|
2392
2453
|
}, delayMs);
|
|
2393
2454
|
}
|
|
2455
|
+
var TOKEN_SAVER_IDLE_SECONDS = 1800;
|
|
2456
|
+
function tokenSaverActive(deps) {
|
|
2457
|
+
if (!getRuntime().flags.tokenSaver()) return false;
|
|
2458
|
+
if (probeWorkstationLocked()) return true;
|
|
2459
|
+
const idle = probeIdleSeconds(deps.guard, deps.paths);
|
|
2460
|
+
return idle >= TOKEN_SAVER_IDLE_SECONDS;
|
|
2461
|
+
}
|
|
2394
2462
|
async function beat(deps) {
|
|
2395
2463
|
if (beating) return;
|
|
2396
2464
|
beating = true;
|
|
@@ -2404,18 +2472,23 @@ async function beat(deps) {
|
|
|
2404
2472
|
if (agent) {
|
|
2405
2473
|
deferredRetries = 0;
|
|
2406
2474
|
const bc = { deps, agent, now };
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2475
|
+
if (tokenSaverActive(deps)) {
|
|
2476
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "silent", reason: "token-saver" });
|
|
2477
|
+
noteBeat("silent", { reason: "token-saver\uFF08\u4F60\u4E0D\u5728\uFF0C\u5FC3\u8DF3\u6302\u8D77\uFF09" });
|
|
2478
|
+
} else {
|
|
2479
|
+
await maintenancePhase(bc);
|
|
2480
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "maintenance" });
|
|
2481
|
+
await collectPhase(bc);
|
|
2482
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "collect" });
|
|
2483
|
+
wandered = await wanderPhase(bc);
|
|
2484
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "wander" });
|
|
2485
|
+
const refilled = await refillWanderPhase(bc);
|
|
2486
|
+
if (refilled) {
|
|
2487
|
+
appendAuditLine(paths.logsDir + "/heartbeat.jsonl", { event: "phase_done", phase: "refill_wander" });
|
|
2488
|
+
}
|
|
2489
|
+
wandered = wandered || refilled;
|
|
2490
|
+
await expressionPhases(bc);
|
|
2416
2491
|
}
|
|
2417
|
-
wandered = wandered || refilled;
|
|
2418
|
-
await expressionPhases(bc);
|
|
2419
2492
|
} else {
|
|
2420
2493
|
const bc = { deps, agent: null, now };
|
|
2421
2494
|
await maintenancePhase(bc);
|
|
@@ -2477,26 +2550,26 @@ function startOrchestrator(deps) {
|
|
|
2477
2550
|
|
|
2478
2551
|
// src/rpc.ts
|
|
2479
2552
|
import { spawn as spawn2 } from "child_process";
|
|
2480
|
-
import
|
|
2553
|
+
import fs12 from "fs";
|
|
2481
2554
|
import os from "os";
|
|
2482
|
-
import
|
|
2555
|
+
import path12 from "path";
|
|
2483
2556
|
|
|
2484
2557
|
// src/browse/interests-edit.ts
|
|
2485
|
-
import
|
|
2486
|
-
import
|
|
2558
|
+
import fs10 from "fs";
|
|
2559
|
+
import path10 from "path";
|
|
2487
2560
|
var MAX_INTERESTS = 32;
|
|
2488
2561
|
var MAX_INTEREST_LEN = 60;
|
|
2489
2562
|
var MAX_WINDOWS = 6;
|
|
2490
2563
|
var HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
2491
2564
|
function userInterestsPath(paths) {
|
|
2492
|
-
return
|
|
2565
|
+
return path10.join(paths.settingsDir, "interests.json");
|
|
2493
2566
|
}
|
|
2494
2567
|
function factoryInterestsPath(paths) {
|
|
2495
|
-
return
|
|
2568
|
+
return path10.join(paths.configDir, "interests.json");
|
|
2496
2569
|
}
|
|
2497
2570
|
function readDoc(file) {
|
|
2498
2571
|
try {
|
|
2499
|
-
const raw = JSON.parse(
|
|
2572
|
+
const raw = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
2500
2573
|
if (!Array.isArray(raw.interests)) return null;
|
|
2501
2574
|
return { ...raw, interests: raw.interests.map(String) };
|
|
2502
2575
|
} catch {
|
|
@@ -2514,8 +2587,8 @@ function ensureUserLayer(guard, paths) {
|
|
|
2514
2587
|
return doc;
|
|
2515
2588
|
}
|
|
2516
2589
|
function saveDoc(guard, file, doc) {
|
|
2517
|
-
|
|
2518
|
-
|
|
2590
|
+
fs10.mkdirSync(path10.dirname(file), { recursive: true });
|
|
2591
|
+
fs10.writeFileSync(guard.assert(file), JSON.stringify(doc, null, 2), "utf8");
|
|
2519
2592
|
}
|
|
2520
2593
|
function normalizeInterest(text) {
|
|
2521
2594
|
return text.trim().replace(/\s+/g, " ");
|
|
@@ -2581,8 +2654,8 @@ function setWanderWindows(guard, paths, rawWindows) {
|
|
|
2581
2654
|
}
|
|
2582
2655
|
|
|
2583
2656
|
// src/statusbar/time-inject.ts
|
|
2584
|
-
import
|
|
2585
|
-
import
|
|
2657
|
+
import fs11 from "fs";
|
|
2658
|
+
import path11 from "path";
|
|
2586
2659
|
function shouldInjectTime(input) {
|
|
2587
2660
|
if (input.step !== 1) return false;
|
|
2588
2661
|
if (!input.originIsInboxSplice) return false;
|
|
@@ -2644,11 +2717,11 @@ function lastMessageTime(session) {
|
|
|
2644
2717
|
return void 0;
|
|
2645
2718
|
}
|
|
2646
2719
|
function timeInjectStatePath(dataDir) {
|
|
2647
|
-
return
|
|
2720
|
+
return path11.join(dataDir, "time-inject-state.json");
|
|
2648
2721
|
}
|
|
2649
2722
|
function loadTimeInjectState(guard, dataDir) {
|
|
2650
2723
|
try {
|
|
2651
|
-
const parsed = JSON.parse(
|
|
2724
|
+
const parsed = JSON.parse(fs11.readFileSync(guard.assert(timeInjectStatePath(dataDir)), "utf8"));
|
|
2652
2725
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
2653
2726
|
} catch {
|
|
2654
2727
|
return {};
|
|
@@ -2687,7 +2760,8 @@ ${statusLine}`;
|
|
|
2687
2760
|
}
|
|
2688
2761
|
const message = createUserMessage({
|
|
2689
2762
|
content: [{ type: "text", text }],
|
|
2690
|
-
|
|
2763
|
+
// 0.1.7 V4: producer-owned kind (retired generic 'plugin' is refused).
|
|
2764
|
+
source: { kind: "heartbeat", plugin: "heartbeat", form: "snapshot", sections: [{ name: "heartbeat-time", text }] }
|
|
2691
2765
|
});
|
|
2692
2766
|
state[sessionId] = now;
|
|
2693
2767
|
saveTimeInjectState(guard, dataDir, state);
|
|
@@ -2712,7 +2786,7 @@ var cachedVersion = null;
|
|
|
2712
2786
|
function pluginVersion(paths) {
|
|
2713
2787
|
if (cachedVersion) return cachedVersion;
|
|
2714
2788
|
try {
|
|
2715
|
-
const pkg = JSON.parse(
|
|
2789
|
+
const pkg = JSON.parse(fs12.readFileSync(path12.join(paths.packageRoot, "package.json"), "utf8"));
|
|
2716
2790
|
cachedVersion = typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
2717
2791
|
} catch {
|
|
2718
2792
|
cachedVersion = "unknown";
|
|
@@ -2721,7 +2795,7 @@ function pluginVersion(paths) {
|
|
|
2721
2795
|
}
|
|
2722
2796
|
function homeSessionId(paths, guard) {
|
|
2723
2797
|
try {
|
|
2724
|
-
const raw = loadEncryptedText(guard,
|
|
2798
|
+
const raw = loadEncryptedText(guard, path12.join(paths.dataDir, "gate.json"));
|
|
2725
2799
|
return JSON.parse(raw ?? "{}").sessionId ?? null;
|
|
2726
2800
|
} catch {
|
|
2727
2801
|
return null;
|
|
@@ -2743,13 +2817,13 @@ function loadSessionTitles() {
|
|
|
2743
2817
|
if (row && typeof row.val === "string" && row.val && !titles[id]) titles[id] = row.val;
|
|
2744
2818
|
};
|
|
2745
2819
|
try {
|
|
2746
|
-
const dir =
|
|
2747
|
-
for (const file of
|
|
2820
|
+
const dir = path12.join(os.homedir(), ".dsh", "storages", "session_projcache", "sessions");
|
|
2821
|
+
for (const file of fs12.readdirSync(dir)) {
|
|
2748
2822
|
if (!file.endsWith(".json")) continue;
|
|
2749
2823
|
const id = file.slice(0, -".json".length);
|
|
2750
2824
|
if (!id.startsWith("session-")) continue;
|
|
2751
2825
|
try {
|
|
2752
|
-
const record = JSON.parse(
|
|
2826
|
+
const record = JSON.parse(fs12.readFileSync(path12.join(dir, file), "utf8"));
|
|
2753
2827
|
take(id, record.record);
|
|
2754
2828
|
} catch {
|
|
2755
2829
|
}
|
|
@@ -2757,7 +2831,7 @@ function loadSessionTitles() {
|
|
|
2757
2831
|
} catch {
|
|
2758
2832
|
}
|
|
2759
2833
|
try {
|
|
2760
|
-
const raw = JSON.parse(
|
|
2834
|
+
const raw = JSON.parse(fs12.readFileSync(path12.join(os.homedir(), ".dsh", "storages", "session_projcache.json"), "utf8"));
|
|
2761
2835
|
const walk = (node) => {
|
|
2762
2836
|
if (!node || typeof node !== "object") return;
|
|
2763
2837
|
for (const [key, value] of Object.entries(node)) {
|
|
@@ -2802,7 +2876,7 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2802
2876
|
}
|
|
2803
2877
|
let flags = { statusbarEnabled: true, timeInjectMin: 25, idleMode: false };
|
|
2804
2878
|
try {
|
|
2805
|
-
const { getRuntime: getRuntime2 } = await import("./runtime-
|
|
2879
|
+
const { getRuntime: getRuntime2 } = await import("./runtime-W2OWJIHY.js");
|
|
2806
2880
|
const f = getRuntime2().flags;
|
|
2807
2881
|
flags = { statusbarEnabled: f.statusbarEnabled(), timeInjectMin: f.timeInjectMin(), idleMode: f.idleMode() };
|
|
2808
2882
|
} catch {
|
|
@@ -2826,14 +2900,14 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2826
2900
|
});
|
|
2827
2901
|
}
|
|
2828
2902
|
case "sessions.list": {
|
|
2829
|
-
const root =
|
|
2903
|
+
const root = path12.join(os.homedir(), ".dsh", "sessions");
|
|
2830
2904
|
const bindings = loadBindings(guard, paths.settingsDir).bindings;
|
|
2831
2905
|
const home = homeSessionId(paths, guard);
|
|
2832
2906
|
const titles = loadSessionTitles();
|
|
2833
2907
|
const out = [];
|
|
2834
|
-
if (
|
|
2835
|
-
for (const slug of
|
|
2836
|
-
for (const id of
|
|
2908
|
+
if (fs12.existsSync(root)) {
|
|
2909
|
+
for (const slug of fs12.readdirSync(root)) {
|
|
2910
|
+
for (const id of fs12.readdirSync(path12.join(root, slug))) {
|
|
2837
2911
|
const binding = bindings.find((b) => b.sessionId === id);
|
|
2838
2912
|
out.push({
|
|
2839
2913
|
id,
|
|
@@ -2868,7 +2942,7 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2868
2942
|
let homeReset = false;
|
|
2869
2943
|
if (id === homeSessionId(paths, guard)) {
|
|
2870
2944
|
try {
|
|
2871
|
-
|
|
2945
|
+
fs12.rmSync(guard.assert(path12.join(paths.dataDir, "gate.json")), { force: true });
|
|
2872
2946
|
homeReset = true;
|
|
2873
2947
|
} catch {
|
|
2874
2948
|
}
|
|
@@ -2912,6 +2986,25 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2912
2986
|
auditInterests(paths, "set-windows", r, JSON.stringify(p.windows ?? null));
|
|
2913
2987
|
return r.ok ? ok(r.doc) : err("bad-request", r.reason ?? "setWindows failed");
|
|
2914
2988
|
}
|
|
2989
|
+
case "config.get":
|
|
2990
|
+
return ok(deps.ui.get());
|
|
2991
|
+
case "config.set": {
|
|
2992
|
+
const patch = p;
|
|
2993
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
|
|
2994
|
+
return err("bad-request", "config.set needs a patch object");
|
|
2995
|
+
}
|
|
2996
|
+
const keys = Object.keys(patch).filter(
|
|
2997
|
+
(k) => ["intervalMin", "maxDailySend", "timeInjectMin", "statusbar", "idleMode", "tokenSaver", "psyEnabled"].includes(k)
|
|
2998
|
+
);
|
|
2999
|
+
if (keys.length === 0) return err("bad-request", "config.set has no recognized field");
|
|
3000
|
+
try {
|
|
3001
|
+
deps.ui.set(patch);
|
|
3002
|
+
} catch (e) {
|
|
3003
|
+
return err("bad-request", String(e).slice(0, 120));
|
|
3004
|
+
}
|
|
3005
|
+
appendAuditLine(guard.assert(paths.logsDir + "/heartbeat.jsonl"), { event: "ui_config_set", keys });
|
|
3006
|
+
return ok(deps.ui.get());
|
|
3007
|
+
}
|
|
2915
3008
|
case "profile.digest": {
|
|
2916
3009
|
const d = buildDigest(guard, paths, policy, {});
|
|
2917
3010
|
return ok({
|
|
@@ -2931,13 +3024,13 @@ function installHeartbeatRpc(ctx, deps) {
|
|
|
2931
3024
|
lines.push(`- [${e.topic}/${e.subTopic}] ${e.content} (conf ${e.confidence.toFixed(2)}, ${e.temporal})`);
|
|
2932
3025
|
}
|
|
2933
3026
|
}
|
|
2934
|
-
const out =
|
|
3027
|
+
const out = path12.join(paths.exportsDir, `profile-export-${Date.now()}.md`);
|
|
2935
3028
|
writeText(guard, out, lines.join("\n") + "\n");
|
|
2936
3029
|
return ok({ path: out });
|
|
2937
3030
|
}
|
|
2938
3031
|
case "ledger.open": {
|
|
2939
3032
|
const f = ledgerFilePath(paths.dataDir);
|
|
2940
|
-
if (!
|
|
3033
|
+
if (!fs12.existsSync(guard.assert(f))) fs12.writeFileSync(f, "# \u8D26\u672C\n", "utf8");
|
|
2941
3034
|
spawn2("cmd", ["/c", "start", "", f], { detached: true, stdio: "ignore" }).unref();
|
|
2942
3035
|
return ok({ path: f });
|
|
2943
3036
|
}
|
|
@@ -3100,12 +3193,21 @@ var Config = Schema.object({
|
|
|
3100
3193
|
/** Statusbar master switch (D19). Off = no section/pre-step status; time injection unaffected. */
|
|
3101
3194
|
statusbar: Schema.boolean().default(true),
|
|
3102
3195
|
/** v1.6.3 闲着模式:素材池为空时用画像话题兜底主动搭话(默认关)。 */
|
|
3103
|
-
idleMode: Schema.boolean().default(false)
|
|
3196
|
+
idleMode: Schema.boolean().default(false),
|
|
3197
|
+
/** v1.7.0 节省 token 模式:用户离开(闲置 ≥30 分钟)或锁屏时整跳暂停(默认关)。 */
|
|
3198
|
+
tokenSaver: Schema.boolean().default(false)
|
|
3104
3199
|
});
|
|
3105
3200
|
function apply(ctx, config = {}) {
|
|
3106
3201
|
const paths = initWorkspace(config.dataDir ? { dataDir: config.dataDir } : {});
|
|
3107
3202
|
const guard = createPathGuard(paths.dataDir);
|
|
3108
3203
|
let policy = loadPolicy(guard, paths.configDir, paths.settingsDir);
|
|
3204
|
+
const ui = loadUiConfig(guard, paths.settingsDir);
|
|
3205
|
+
if (ui.intervalMin && ui.intervalMin >= 1) {
|
|
3206
|
+
policy = { ...policy, heartbeat: { ...policy.heartbeat, intervalMin: ui.intervalMin } };
|
|
3207
|
+
}
|
|
3208
|
+
if (ui.maxDailySend && ui.maxDailySend >= 1) {
|
|
3209
|
+
policy = { ...policy, gate: { ...policy.gate, maxDailySend: ui.maxDailySend } };
|
|
3210
|
+
}
|
|
3109
3211
|
if (config.intervalMin && config.intervalMin >= 1) {
|
|
3110
3212
|
policy = { ...policy, heartbeat: { ...policy.heartbeat, intervalMin: config.intervalMin } };
|
|
3111
3213
|
}
|
|
@@ -3120,7 +3222,8 @@ function apply(ctx, config = {}) {
|
|
|
3120
3222
|
flags: {
|
|
3121
3223
|
statusbarEnabled: () => statusbarEnabledRef,
|
|
3122
3224
|
timeInjectMin: () => timeInjectMinRef,
|
|
3123
|
-
idleMode: () => idleModeRef
|
|
3225
|
+
idleMode: () => idleModeRef,
|
|
3226
|
+
tokenSaver: () => tokenSaverRef
|
|
3124
3227
|
}
|
|
3125
3228
|
});
|
|
3126
3229
|
ctx.inject(["agentPresets"], (presetCtx) => {
|
|
@@ -3148,9 +3251,10 @@ function apply(ctx, config = {}) {
|
|
|
3148
3251
|
}
|
|
3149
3252
|
});
|
|
3150
3253
|
let sectionSource = null;
|
|
3151
|
-
let timeInjectMinRef = config.timeInjectMin ?? 25;
|
|
3152
|
-
let statusbarEnabledRef = config.statusbar !== false;
|
|
3153
|
-
let idleModeRef = config.idleMode === true;
|
|
3254
|
+
let timeInjectMinRef = ui.timeInjectMin ?? (config.timeInjectMin ?? 25);
|
|
3255
|
+
let statusbarEnabledRef = ui.statusbar ?? config.statusbar !== false;
|
|
3256
|
+
let idleModeRef = ui.idleMode ?? config.idleMode === true;
|
|
3257
|
+
let tokenSaverRef = ui.tokenSaver ?? config.tokenSaver === true;
|
|
3154
3258
|
const applySettingsOverrides = () => {
|
|
3155
3259
|
try {
|
|
3156
3260
|
const v = sectionSource?.();
|
|
@@ -3171,7 +3275,6 @@ function apply(ctx, config = {}) {
|
|
|
3171
3275
|
void (async () => {
|
|
3172
3276
|
try {
|
|
3173
3277
|
const settingsService = ctx.get("settings");
|
|
3174
|
-
const legacy = await import("@deepseek-ai/dsh-settings");
|
|
3175
3278
|
if (settingsService && typeof settingsService.installSection === "function") {
|
|
3176
3279
|
settingsService.installSection(ctx, "heartbeat", Config, config, {
|
|
3177
3280
|
setSource: (current) => {
|
|
@@ -3181,7 +3284,18 @@ function apply(ctx, config = {}) {
|
|
|
3181
3284
|
applySettingsOverrides();
|
|
3182
3285
|
}
|
|
3183
3286
|
});
|
|
3184
|
-
|
|
3287
|
+
applySettingsOverrides();
|
|
3288
|
+
ctx.logger.info("heartbeat: settings section registered (0.1.5 settings service)");
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
if (settingsService && typeof settingsService.describe === "function") {
|
|
3292
|
+
sectionSource = () => config;
|
|
3293
|
+
applySettingsOverrides();
|
|
3294
|
+
ctx.logger.info("heartbeat: config served by the 0.1.7 profile form (edits reload the plugin)");
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
const legacy = await import("@deepseek-ai/dsh-settings");
|
|
3298
|
+
if (typeof legacy.installSettingsSection === "function" && typeof legacy.settingsNamespace === "function") {
|
|
3185
3299
|
legacy.installSettingsSection(ctx, legacy.settingsNamespace("heartbeat"), Config, config, {
|
|
3186
3300
|
setSource: (current) => {
|
|
3187
3301
|
sectionSource = current;
|
|
@@ -3190,11 +3304,11 @@ function apply(ctx, config = {}) {
|
|
|
3190
3304
|
applySettingsOverrides();
|
|
3191
3305
|
}
|
|
3192
3306
|
});
|
|
3193
|
-
|
|
3194
|
-
|
|
3307
|
+
applySettingsOverrides();
|
|
3308
|
+
ctx.logger.info("heartbeat: settings section registered (legacy 0.1.1 settings)");
|
|
3309
|
+
return;
|
|
3195
3310
|
}
|
|
3196
|
-
|
|
3197
|
-
ctx.logger.info("heartbeat: settings section registered");
|
|
3311
|
+
ctx.logger.warn("heartbeat: no settings integration found; policy file values only");
|
|
3198
3312
|
} catch (e) {
|
|
3199
3313
|
ctx.logger.warn("heartbeat: settings section unavailable (%s)", String(e).slice(0, 120));
|
|
3200
3314
|
}
|
|
@@ -3203,7 +3317,32 @@ function apply(ctx, config = {}) {
|
|
|
3203
3317
|
guard.assert(paths.logsDir + "/heartbeat.jsonl"),
|
|
3204
3318
|
{ event: "plugin_init", dataDir: paths.dataDir }
|
|
3205
3319
|
);
|
|
3206
|
-
|
|
3320
|
+
const uiGet = () => ({
|
|
3321
|
+
intervalMin: getRuntime().policy.heartbeat.intervalMin,
|
|
3322
|
+
maxDailySend: getRuntime().policy.gate.maxDailySend,
|
|
3323
|
+
timeInjectMin: timeInjectMinRef,
|
|
3324
|
+
statusbar: statusbarEnabledRef,
|
|
3325
|
+
idleMode: idleModeRef,
|
|
3326
|
+
tokenSaver: tokenSaverRef,
|
|
3327
|
+
psyEnabled: getRuntime().policy.profile.psyEnabled
|
|
3328
|
+
});
|
|
3329
|
+
const uiSet = (patch) => {
|
|
3330
|
+
if (typeof patch.psyEnabled === "boolean") {
|
|
3331
|
+
updateUserPolicy(guard, paths.settingsDir, { profile: { psyEnabled: patch.psyEnabled } });
|
|
3332
|
+
getRuntime().policy.profile.psyEnabled = patch.psyEnabled;
|
|
3333
|
+
}
|
|
3334
|
+
const merged = saveUiConfig(guard, paths.settingsDir, patch);
|
|
3335
|
+
if (merged.intervalMin && merged.intervalMin >= 1) applyHeartbeatInterval(deps, merged.intervalMin);
|
|
3336
|
+
if (merged.maxDailySend && merged.maxDailySend >= 1) getRuntime().policy.gate.maxDailySend = merged.maxDailySend;
|
|
3337
|
+
if (typeof merged.timeInjectMin === "number") timeInjectMinRef = merged.timeInjectMin;
|
|
3338
|
+
if (typeof merged.statusbar === "boolean") statusbarEnabledRef = merged.statusbar;
|
|
3339
|
+
if (typeof merged.idleMode === "boolean") {
|
|
3340
|
+
idleModeRef = merged.idleMode;
|
|
3341
|
+
getRuntime().policy.heartbeat.idleMode = merged.idleMode;
|
|
3342
|
+
}
|
|
3343
|
+
if (typeof merged.tokenSaver === "boolean") tokenSaverRef = merged.tokenSaver;
|
|
3344
|
+
};
|
|
3345
|
+
installHeartbeatRpc(ctx, { paths, guard, policy, ui: { get: uiGet, set: uiSet } });
|
|
3207
3346
|
startOrchestrator(deps);
|
|
3208
3347
|
const statusReader = new StatusReader();
|
|
3209
3348
|
registerStatusbarSection(ctx, guard, paths, {
|