mosse-agent-bridge 0.1.9 → 0.1.11
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/dist/drivers/hermes.d.ts +28 -1
- package/dist/drivers/hermes.js +69 -3
- package/dist/drivers/hermes.js.map +1 -1
- package/dist/health.d.ts +44 -0
- package/dist/health.js +107 -0
- package/dist/health.js.map +1 -0
- package/dist/host-paths.d.ts +19 -0
- package/dist/host-paths.js +54 -0
- package/dist/host-paths.js.map +1 -0
- package/dist/host-state.d.ts +36 -0
- package/dist/host-state.js +161 -0
- package/dist/host-state.js.map +1 -0
- package/dist/index.js +272 -4
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +62 -0
- package/dist/install.js +158 -0
- package/dist/install.js.map +1 -0
- package/dist/self-update.d.ts +45 -0
- package/dist/self-update.js +92 -0
- package/dist/self-update.js.map +1 -0
- package/dist/semver.d.ts +4 -0
- package/dist/semver.js +21 -0
- package/dist/semver.js.map +1 -0
- package/dist/service-files.d.ts +42 -0
- package/dist/service-files.js +130 -0
- package/dist/service-files.js.map +1 -0
- package/package.json +1 -1
package/dist/drivers/hermes.d.ts
CHANGED
|
@@ -23,10 +23,37 @@ export interface AgentDirFs {
|
|
|
23
23
|
}): void;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Hermes 全部 16 个内置 auxiliary task(来自 Hermes DEFAULT_CONFIG)。用常量数组生成 overlay,避免手抄漏。
|
|
27
|
+
* 改这里 = 改覆盖范围;顺序不影响语义(Hermes leaf 级 deep-merge)。
|
|
28
|
+
*/
|
|
29
|
+
export declare const HERMES_AUXILIARY_TASKS: readonly ["vision", "web_extract", "compression", "skills_hub", "approval", "mcp", "title_generation", "tts_audio_tags", "triage_specifier", "kanban_decomposer", "profile_describer", "curator", "monitor", "background_review", "moa_reference", "moa_aggregator"];
|
|
30
|
+
/**
|
|
31
|
+
* 生成 HERMES_MANAGED_DIR overlay 的 config.yaml 内容(纯函数,便于单测)。
|
|
32
|
+
*
|
|
33
|
+
* 只含 `auxiliary:` 段,把全部 16 个内置 task 的 `provider` 强制设为 `main`。`main` 由 Hermes 的
|
|
34
|
+
* `agent/auxiliary_client.py:_normalize_aux_provider` 解析成实际主 provider(如 deepseek),专为非聚合
|
|
35
|
+
* provider 设计。这样 auxiliary 侧任务不再因 auto 模式找不到 openrouter/nous(用户没配)而崩(-32603)。
|
|
36
|
+
*
|
|
37
|
+
* Hermes 把本文件 leaf 级 deep-merge 覆盖到用户 `~/.hermes/config.yaml` 之上(managed 胜),只动
|
|
38
|
+
* `auxiliary.*.provider` 这些 leaf,用户其它配置(telegram/whatsapp/model 等)原封不动。
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildAuxiliaryOverlayYaml(): string;
|
|
41
|
+
/**
|
|
42
|
+
* per-agent overlay 目录(放 HERMES_MANAGED_DIR 的 config.yaml)。
|
|
43
|
+
*
|
|
44
|
+
* 刻意放 agentDir 的 `.hermes-managed` 子目录而非 agentDir 本身:agentDir 同时被 TERMINAL_CWD 指着,
|
|
45
|
+
* Hermes 从 cwd 读 AGENTS.md/.hermes.md(人格)、从 HERMES_MANAGED_DIR 读 config.yaml(overlay),两套
|
|
46
|
+
* 机制读不同路径互不干扰;隔到子目录更无歧义。
|
|
47
|
+
*/
|
|
48
|
+
export declare function managedDirFor(agentDir: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* 准备 per-agent 目录(人格 + auxiliary overlay + 工作目录起点,**非** hermes home):
|
|
27
51
|
* 1. mkdir -p(确保存在)。
|
|
28
52
|
* 2. 写 `AGENTS.md` = 人格(systemPromptAppend)。Hermes 把 cwd(我们用 TERMINAL_CWD 指到这里)当前层的
|
|
29
53
|
* AGENTS.md 当 rules 加载。幂等覆盖;无人格写空字符串(清掉上一轮残留,避免串味)。
|
|
54
|
+
* 3. 写 `<agentDir>/.hermes-managed/config.yaml` = HERMES_MANAGED_DIR overlay,把 16 个 auxiliary task 的
|
|
55
|
+
* provider 强制为 `main`(修 DeepSeek 等非聚合 provider 的 -32603)。leaf 级 deep-merge,不碰用户 ~/.hermes。
|
|
56
|
+
* 幂等覆盖(内容固定,二次准备不堆叠)。
|
|
30
57
|
*
|
|
31
58
|
* **绝不写 SOUL.md、绝不 symlink/copy ~/.hermes 的 config.yaml/.env、绝不碰用户真实 home。**
|
|
32
59
|
*/
|
package/dist/drivers/hermes.js
CHANGED
|
@@ -31,11 +31,64 @@ const HERMES_BIN = process.env.MOSSE_HERMES_BIN ?? "hermes";
|
|
|
31
31
|
const NODE_DIR = dirname(process.execPath);
|
|
32
32
|
const DEFAULT_IDLE_MS = Number(process.env.MOSSE_HERMES_IDLE_MS) || 120_000;
|
|
33
33
|
const realFs = { mkdirSync, writeFileSync };
|
|
34
|
+
// ───────────────────── auxiliary overlay(修非聚合 provider 的 -32603 崩)─────────────────────
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
+
* Hermes 全部 16 个内置 auxiliary task(来自 Hermes DEFAULT_CONFIG)。用常量数组生成 overlay,避免手抄漏。
|
|
37
|
+
* 改这里 = 改覆盖范围;顺序不影响语义(Hermes leaf 级 deep-merge)。
|
|
38
|
+
*/
|
|
39
|
+
export const HERMES_AUXILIARY_TASKS = [
|
|
40
|
+
"vision",
|
|
41
|
+
"web_extract",
|
|
42
|
+
"compression",
|
|
43
|
+
"skills_hub",
|
|
44
|
+
"approval",
|
|
45
|
+
"mcp",
|
|
46
|
+
"title_generation",
|
|
47
|
+
"tts_audio_tags",
|
|
48
|
+
"triage_specifier",
|
|
49
|
+
"kanban_decomposer",
|
|
50
|
+
"profile_describer",
|
|
51
|
+
"curator",
|
|
52
|
+
"monitor",
|
|
53
|
+
"background_review",
|
|
54
|
+
"moa_reference",
|
|
55
|
+
"moa_aggregator",
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* 生成 HERMES_MANAGED_DIR overlay 的 config.yaml 内容(纯函数,便于单测)。
|
|
59
|
+
*
|
|
60
|
+
* 只含 `auxiliary:` 段,把全部 16 个内置 task 的 `provider` 强制设为 `main`。`main` 由 Hermes 的
|
|
61
|
+
* `agent/auxiliary_client.py:_normalize_aux_provider` 解析成实际主 provider(如 deepseek),专为非聚合
|
|
62
|
+
* provider 设计。这样 auxiliary 侧任务不再因 auto 模式找不到 openrouter/nous(用户没配)而崩(-32603)。
|
|
63
|
+
*
|
|
64
|
+
* Hermes 把本文件 leaf 级 deep-merge 覆盖到用户 `~/.hermes/config.yaml` 之上(managed 胜),只动
|
|
65
|
+
* `auxiliary.*.provider` 这些 leaf,用户其它配置(telegram/whatsapp/model 等)原封不动。
|
|
66
|
+
*/
|
|
67
|
+
export function buildAuxiliaryOverlayYaml() {
|
|
68
|
+
const lines = ["auxiliary:"];
|
|
69
|
+
for (const task of HERMES_AUXILIARY_TASKS) {
|
|
70
|
+
lines.push(` ${task}: { provider: main }`);
|
|
71
|
+
}
|
|
72
|
+
return `${lines.join("\n")}\n`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* per-agent overlay 目录(放 HERMES_MANAGED_DIR 的 config.yaml)。
|
|
76
|
+
*
|
|
77
|
+
* 刻意放 agentDir 的 `.hermes-managed` 子目录而非 agentDir 本身:agentDir 同时被 TERMINAL_CWD 指着,
|
|
78
|
+
* Hermes 从 cwd 读 AGENTS.md/.hermes.md(人格)、从 HERMES_MANAGED_DIR 读 config.yaml(overlay),两套
|
|
79
|
+
* 机制读不同路径互不干扰;隔到子目录更无歧义。
|
|
80
|
+
*/
|
|
81
|
+
export function managedDirFor(agentDir) {
|
|
82
|
+
return join(agentDir, ".hermes-managed");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 准备 per-agent 目录(人格 + auxiliary overlay + 工作目录起点,**非** hermes home):
|
|
36
86
|
* 1. mkdir -p(确保存在)。
|
|
37
87
|
* 2. 写 `AGENTS.md` = 人格(systemPromptAppend)。Hermes 把 cwd(我们用 TERMINAL_CWD 指到这里)当前层的
|
|
38
88
|
* AGENTS.md 当 rules 加载。幂等覆盖;无人格写空字符串(清掉上一轮残留,避免串味)。
|
|
89
|
+
* 3. 写 `<agentDir>/.hermes-managed/config.yaml` = HERMES_MANAGED_DIR overlay,把 16 个 auxiliary task 的
|
|
90
|
+
* provider 强制为 `main`(修 DeepSeek 等非聚合 provider 的 -32603)。leaf 级 deep-merge,不碰用户 ~/.hermes。
|
|
91
|
+
* 幂等覆盖(内容固定,二次准备不堆叠)。
|
|
39
92
|
*
|
|
40
93
|
* **绝不写 SOUL.md、绝不 symlink/copy ~/.hermes 的 config.yaml/.env、绝不碰用户真实 home。**
|
|
41
94
|
*/
|
|
@@ -45,6 +98,10 @@ export function prepareAgentDir(opts) {
|
|
|
45
98
|
fs.writeFileSync(join(opts.agentDir, "AGENTS.md"), opts.systemPromptAppend ?? "", {
|
|
46
99
|
mode: 0o600,
|
|
47
100
|
});
|
|
101
|
+
// auxiliary overlay:隔到 .hermes-managed 子目录(见 managedDirFor),只覆盖 auxiliary.*.provider leaf。
|
|
102
|
+
const managedDir = managedDirFor(opts.agentDir);
|
|
103
|
+
fs.mkdirSync(managedDir, { recursive: true });
|
|
104
|
+
fs.writeFileSync(join(managedDir, "config.yaml"), buildAuxiliaryOverlayYaml(), { mode: 0o600 });
|
|
48
105
|
}
|
|
49
106
|
/** per-agent 目录默认路径:优先 turn.agentHome(理想来源=daemon 传 ~/.mosse/agents/<agentId>/);否则由 cwd 稳定派生。 */
|
|
50
107
|
function defaultResolveAgentDir(turn) {
|
|
@@ -114,6 +171,10 @@ export function createHermesDriver(deps) {
|
|
|
114
171
|
const env = deps.buildEnv(turn.env);
|
|
115
172
|
env.HERMES_HOME = deps.userHermesDir; // 真实 home:auxiliary/Nous/skills/memory/config/state.db 全活。
|
|
116
173
|
env.TERMINAL_CWD = agentDir; // context-file 的 cwd → 加载 agentDir/AGENTS.md(人格,按 agent 隔离)。
|
|
174
|
+
// HERMES_MANAGED_DIR overlay:把 16 个 auxiliary task 的 provider 强制为 main(leaf 级 deep-merge 到
|
|
175
|
+
// 用户 ~/.hermes 之上)→ 修 DeepSeek 等非聚合 provider 在 auto 模式下找不到 openrouter/nous 的崩(-32603)。
|
|
176
|
+
// 目录的 config.yaml 由 prepareAgentDir 写好(Hermes 要求该路径 is_dir 才生效,prepareAgentDir 已 mkdir)。
|
|
177
|
+
env.HERMES_MANAGED_DIR = managedDirFor(agentDir);
|
|
117
178
|
// 文件工具的工作目录(独立于 TERMINAL_CWD 人格):用户设的工作目录(turn.cwd)或回退 agentDir。
|
|
118
179
|
const sessionCwd = turn.cwd?.trim() || agentDir;
|
|
119
180
|
const child = deps.spawn(bin, ["acp"], { cwd: sessionCwd, env });
|
|
@@ -193,8 +254,13 @@ export function createHermesDriver(deps) {
|
|
|
193
254
|
if (!currentSessionId)
|
|
194
255
|
throw new Error("hermes acp: no session id");
|
|
195
256
|
// D6:Mosse 指定 model 则覆盖会话模型;否则用 Hermes 本地配置默认(~/.hermes/config.yaml)。
|
|
196
|
-
|
|
197
|
-
|
|
257
|
+
// Hermes 的 set-model 要 `provider:model`(冒号);我们存的是 `provider/model`(斜杠,toModelSpec)。
|
|
258
|
+
// 斜杠会被 Hermes 当成 OpenRouter vendor/model slug → 把会话 provider 切去 openrouter(用户多半没 key)
|
|
259
|
+
// → 抛 "No LLM provider configured"(-32603)。转成冒号(只换第一个 /)→ 正确解析 provider,不切走。
|
|
260
|
+
if (turn.model) {
|
|
261
|
+
const modelId = turn.model.includes(":") ? turn.model : turn.model.replace("/", ":");
|
|
262
|
+
await client.setSessionModel(currentSessionId, modelId);
|
|
263
|
+
}
|
|
198
264
|
await client.prompt({
|
|
199
265
|
sessionId: currentSessionId,
|
|
200
266
|
content: [{ type: "text", text: turn.prompt }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermes.js","sourceRoot":"","sources":["../../src/drivers/hermes.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,gGAAgG;AAChG,EAAE;AACF,oEAAoE;AACpE,uFAAuF;AACvF,2FAA2F;AAC3F,mDAAmD;AACnD,oGAAoG;AACpG,wFAAwF;AACxF,kFAAkF;AAClF,2EAA2E;AAC3E,6BAA6B;AAC7B,yFAAyF;AACzF,mDAAmD;AACnD,mFAAmF;AACnF,EAAE;AACF,uBAAuB;AACvB,qFAAqF;AACrF,2FAA2F;AAC3F,iCAAiC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,QAAQ,CAAC;AAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3C,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,OAAO,CAAC;AA4B5E,MAAM,MAAM,GAAe,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;AAExD
|
|
1
|
+
{"version":3,"file":"hermes.js","sourceRoot":"","sources":["../../src/drivers/hermes.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,gGAAgG;AAChG,EAAE;AACF,oEAAoE;AACpE,uFAAuF;AACvF,2FAA2F;AAC3F,mDAAmD;AACnD,oGAAoG;AACpG,wFAAwF;AACxF,kFAAkF;AAClF,2EAA2E;AAC3E,6BAA6B;AAC7B,yFAAyF;AACzF,mDAAmD;AACnD,mFAAmF;AACnF,EAAE;AACF,uBAAuB;AACvB,qFAAqF;AACrF,2FAA2F;AAC3F,iCAAiC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,QAAQ,CAAC;AAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3C,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,OAAO,CAAC;AA4B5E,MAAM,MAAM,GAAe,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;AAExD,yFAAyF;AAEzF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,QAAQ;IACR,aAAa;IACb,aAAa;IACb,YAAY;IACZ,UAAU;IACV,KAAK;IACL,kBAAkB;IAClB,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,SAAS;IACT,SAAS;IACT,mBAAmB;IACnB,eAAe;IACf,gBAAgB;CACR,CAAC;AAEX;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB;IACvC,MAAM,KAAK,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,sBAAsB,EAAE,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,sBAAsB,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,IAI/B;IACC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC;IAC7B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,kBAAkB,IAAI,EAAE,EAAE;QAChF,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,2FAA2F;IAC3F,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,yBAAyB,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAED,iGAAiG;AACjG,SAAS,sBAAsB,CAAC,IAAe;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACxC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACzB,CAAC;AAoBD,SAAS,WAAW;IAClB,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CACzB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QACzF,UAAU,EAAE,GAAG,EAAE,CACf,UAAU,CACR,UAAU,EACV,kBAAkB,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAC5E;QACH,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QACrF,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;QAC1C,eAAe,EAAE,sBAAsB;QACvC,qFAAqF;QACrF,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC;QAC5E,aAAa,EAAE,eAAe;KAC/B,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,SAAS,UAAU,CAAC,KAAkB;IACpC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,IAAI,MAAM,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO;QACnC,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;QACD,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,CAAC;QACT,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,kBAAkB,CAAC,IAAgB;IACjD,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,KAAK,CAAC,MAAM;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9B,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM;YAC7B,4EAA4E;YAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;YAEhF,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,UAAU,CAAC;YAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,2DAA2D;YACjG,GAAG,CAAC,YAAY,GAAG,QAAQ,CAAC,CAAC,6DAA6D;YAC1F,2FAA2F;YAC3F,uFAAuF;YACvF,yFAAyF;YACzF,GAAG,CAAC,kBAAkB,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YAEjD,iEAAiE;YACjE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC;YAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACrD,CAAC;YACD,sBAAsB;YACtB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAU,EAAE,EAAE,CACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CACrD,CAAC;YAEF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACpC,MAAM,UAAU,GAAG,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAEzD,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YAC3B,IAAI,gBAAgB,GAAkB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;YAE7D,sDAAsD;YACtD,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,cAA2B,CAAC;YAChC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAY,CAAC,CAAC,EAAE,EAAE;gBAClD,cAAc,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YACH,IAAI,SAAoD,CAAC;YACzD,SAAS,WAAW;gBAClB,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,IAAI,CAAC;oBACH,IAAI,gBAAgB;wBAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,qBAAqB;gBAC9E,CAAC;gBAAC,MAAM,CAAC;oBACP,aAAa;gBACf,CAAC;gBACD,SAAS,EAAE,CAAC;gBACZ,cAAc,EAAE,CAAC;YACnB,CAAC;YACD,SAAS,SAAS;gBAChB,IAAI,SAAS;oBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;gBACvC,SAAS,GAAG,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;gBACxD,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YACtB,CAAC;YACD,SAAS,EAAE,CAAC;YAEZ,oDAAoD;YACpD,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,SAAS,EAAE,CAAC,CAAC,oBAAoB;gBACjC,MAAM,EAAE,GAAmB,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBACtD,IAAI,CAAC,EAAE;oBAAE,OAAO;gBAChB,IAAI,EAAE,CAAC,IAAI,KAAK,MAAM;oBAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC;gBAC/C,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;YACpC,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,MAAM,CAAC,OAAO;oBAAE,WAAW,EAAE,CAAC;;oBAC7B,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACjE,CAAC;YAED,gBAAgB;YAChB,KAAK,UAAU,QAAQ;gBACrB,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;oBACnB,qEAAqE;oBACrE,IAAI,CAAC;wBACH,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;wBACvF,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC;oBACpC,CAAC;oBAAC,MAAM,CAAC;wBACP,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;wBACnE,gBAAgB,GAAG,CAAC,CAAC,SAAS,CAAC;oBACjC,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;oBACnE,gBAAgB,GAAG,CAAC,CAAC,SAAS,CAAC;gBACjC,CAAC;gBACD,IAAI,CAAC,gBAAgB;oBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBACpE,sEAAsE;gBACtE,oFAAoF;gBACpF,sFAAsF;gBACtF,6EAA6E;gBAC7E,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;oBACf,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBACrF,MAAM,MAAM,CAAC,eAAe,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;gBAC1D,CAAC;gBACD,MAAM,MAAM,CAAC,MAAM,CAAC;oBAClB,SAAS,EAAE,gBAAgB;oBAC3B,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;iBAC/C,CAAC,CAAC;gBACH,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,IAAI,CAAC;gBACH,0CAA0C;gBAC1C,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;gBACjD,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;YAClE,CAAC;oBAAS,CAAC;gBACT,IAAI,SAAS;oBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,MAAM;oBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACzD,SAAS,EAAE,CAAC,CAAC,sBAAsB;YACrC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAc,kBAAkB,CAAC,WAAW,EAAE,CAAC,CAAC"}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 崩溃窗口:此时长内多次未达 healthy 的重启算崩溃循环。 */
|
|
2
|
+
export declare const HEALTH_WINDOW_MS: number;
|
|
3
|
+
/** 崩溃阈值:窗口内达此次数 → 回滚。 */
|
|
4
|
+
export declare const HEALTH_MAX_CRASHES = 3;
|
|
5
|
+
/** 健康判定:启动后稳定运行此时长(且已连上)→ 标记版本健康。 */
|
|
6
|
+
export declare const HEALTHY_AFTER_MS: number;
|
|
7
|
+
export interface LaunchHealth {
|
|
8
|
+
/** 最近一次启动的版本。 */
|
|
9
|
+
version: string;
|
|
10
|
+
/** 该版本崩溃窗口的起点(首次进入未健康态的时刻)。 */
|
|
11
|
+
firstLaunchMs: number;
|
|
12
|
+
/** 该版本窗口内未达 healthy 的重启次数(崩溃计数)。 */
|
|
13
|
+
crashCount: number;
|
|
14
|
+
/** 该版本是否已达稳定。 */
|
|
15
|
+
healthy: boolean;
|
|
16
|
+
/** 已知崩溃循环的坏版本(防回滚循环:不回滚到这里头的)。 */
|
|
17
|
+
badVersions: string[];
|
|
18
|
+
/** previous 也坏 / 无 previous → 停止自动回滚(交人裁定)。 */
|
|
19
|
+
rollbackDisabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface HealthOpts {
|
|
22
|
+
windowMs: number;
|
|
23
|
+
maxCrashes: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const DEFAULT_HEALTH_OPTS: HealthOpts;
|
|
26
|
+
export type BootAction = "ok" | "rollback" | "blocked";
|
|
27
|
+
export interface BootEvaluation {
|
|
28
|
+
/** 落盘的新健康状态。 */
|
|
29
|
+
next: LaunchHealth;
|
|
30
|
+
/** ok=正常起;rollback=切回 previous 并退出;blocked=想回但无安全目标 → 停自动回滚、保持当前。 */
|
|
31
|
+
action: BootAction;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 启动时评估健康(纯函数)。
|
|
35
|
+
* @param prev 上次落盘的健康状态(首次=null)。
|
|
36
|
+
* @param current 本次启动版本(DAEMON_VERSION)。
|
|
37
|
+
* @param now 当前时刻 ms。
|
|
38
|
+
* @param previousVersion `previous` symlink 指向的版本(回滚目标);无则 undefined。
|
|
39
|
+
*/
|
|
40
|
+
export declare function evaluateBoot(prev: LaunchHealth | null, current: string, now: number, previousVersion: string | undefined, opts?: HealthOpts): BootEvaluation;
|
|
41
|
+
/** 标记当前版本健康(稳定运行 + 已连上后调用):清崩溃计数。纯函数。 */
|
|
42
|
+
export declare function markHealthy(prev: LaunchHealth, now: number): LaunchHealth;
|
|
43
|
+
export declare function loadHealth(path: string): LaunchHealth | null;
|
|
44
|
+
export declare function saveHealth(path: string, state: LaunchHealth): void;
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// M3 F3.3:启动健康检查 + 自动回滚(防"自更新到坏版本 → 反复崩 → 永久离线")。
|
|
2
|
+
//
|
|
3
|
+
// 机制(跨进程重启,状态持久在 <dataDir>/host/launch-health.json):
|
|
4
|
+
// - 桥每次启动调 evaluateBoot:若**同一新版本**在 2min 窗口内反复(未达 healthy 就)重启 N 次 → 判定崩溃循环
|
|
5
|
+
// → 回滚:current 切回 previous + 退出(OS 用旧版重启)。
|
|
6
|
+
// - 启动后稳定运行一段(默认 60s,且已连上 server)→ markHealthy:清崩溃计数、标记该版本健康。
|
|
7
|
+
// - 防回滚循环:previous 也在 badVersions(回滚过仍坏)或没有 previous → **不再自动回滚**(rollbackDisabled),
|
|
8
|
+
// 保持当前、明确上报,交人裁定。
|
|
9
|
+
//
|
|
10
|
+
// 纯函数(evaluateBoot/markHealthy)做判定,易单测;loadHealth/saveHealth 是薄 IO。
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname } from "node:path";
|
|
13
|
+
/** 崩溃窗口:此时长内多次未达 healthy 的重启算崩溃循环。 */
|
|
14
|
+
export const HEALTH_WINDOW_MS = 2 * 60 * 1000;
|
|
15
|
+
/** 崩溃阈值:窗口内达此次数 → 回滚。 */
|
|
16
|
+
export const HEALTH_MAX_CRASHES = 3;
|
|
17
|
+
/** 健康判定:启动后稳定运行此时长(且已连上)→ 标记版本健康。 */
|
|
18
|
+
export const HEALTHY_AFTER_MS = 60 * 1000;
|
|
19
|
+
export const DEFAULT_HEALTH_OPTS = {
|
|
20
|
+
windowMs: HEALTH_WINDOW_MS,
|
|
21
|
+
maxCrashes: HEALTH_MAX_CRASHES,
|
|
22
|
+
};
|
|
23
|
+
function addUnique(list, v) {
|
|
24
|
+
return list.includes(v) ? list : [...list, v];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 启动时评估健康(纯函数)。
|
|
28
|
+
* @param prev 上次落盘的健康状态(首次=null)。
|
|
29
|
+
* @param current 本次启动版本(DAEMON_VERSION)。
|
|
30
|
+
* @param now 当前时刻 ms。
|
|
31
|
+
* @param previousVersion `previous` symlink 指向的版本(回滚目标);无则 undefined。
|
|
32
|
+
*/
|
|
33
|
+
export function evaluateBoot(prev, current, now, previousVersion, opts = DEFAULT_HEALTH_OPTS) {
|
|
34
|
+
// 新版本启动(刚切到它,或首次启动)→ 初始化窗口。携带 badVersions/rollbackDisabled(回滚记忆要持久)。
|
|
35
|
+
if (!prev || prev.version !== current) {
|
|
36
|
+
return {
|
|
37
|
+
next: {
|
|
38
|
+
version: current,
|
|
39
|
+
firstLaunchMs: now,
|
|
40
|
+
crashCount: 0,
|
|
41
|
+
healthy: false,
|
|
42
|
+
badVersions: prev?.badVersions ?? [],
|
|
43
|
+
rollbackDisabled: prev?.rollbackDisabled,
|
|
44
|
+
},
|
|
45
|
+
action: "ok",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// 同版本已健康却重启(关机重启 / 手动)→ 重置窗口,保持健康。
|
|
49
|
+
if (prev.healthy) {
|
|
50
|
+
return { next: { ...prev, firstLaunchMs: now, crashCount: 0 }, action: "ok" };
|
|
51
|
+
}
|
|
52
|
+
// 同版本、未健康就重启 → 上次启动崩了。
|
|
53
|
+
const within = now - prev.firstLaunchMs <= opts.windowMs;
|
|
54
|
+
if (!within) {
|
|
55
|
+
// 窗口已过且没循环 → 重开窗口,本次算第 1 次。
|
|
56
|
+
return { next: { ...prev, firstLaunchMs: now, crashCount: 1 }, action: "ok" };
|
|
57
|
+
}
|
|
58
|
+
const crashCount = prev.crashCount + 1;
|
|
59
|
+
if (crashCount < opts.maxCrashes) {
|
|
60
|
+
return { next: { ...prev, crashCount }, action: "ok" };
|
|
61
|
+
}
|
|
62
|
+
// 达阈值 → 崩溃循环。防回滚循环:已禁用 / 无 previous / previous 也是坏版本 → 不回滚,停自动回滚。
|
|
63
|
+
const badVersions = addUnique(prev.badVersions, current);
|
|
64
|
+
if (prev.rollbackDisabled || !previousVersion || prev.badVersions.includes(previousVersion)) {
|
|
65
|
+
return {
|
|
66
|
+
next: { ...prev, crashCount, badVersions, rollbackDisabled: true },
|
|
67
|
+
action: "blocked",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 回滚:把当前坏版本记入 badVersions;切回 previous(由调用方做 symlink + 退出)。
|
|
71
|
+
return { next: { ...prev, crashCount, badVersions }, action: "rollback" };
|
|
72
|
+
}
|
|
73
|
+
/** 标记当前版本健康(稳定运行 + 已连上后调用):清崩溃计数。纯函数。 */
|
|
74
|
+
export function markHealthy(prev, now) {
|
|
75
|
+
return { ...prev, healthy: true, crashCount: 0, firstLaunchMs: now };
|
|
76
|
+
}
|
|
77
|
+
// ───────────────────────── 薄 IO ─────────────────────────
|
|
78
|
+
export function loadHealth(path) {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
81
|
+
if (!parsed || typeof parsed.version !== "string")
|
|
82
|
+
return null;
|
|
83
|
+
return {
|
|
84
|
+
version: parsed.version,
|
|
85
|
+
firstLaunchMs: typeof parsed.firstLaunchMs === "number" ? parsed.firstLaunchMs : Date.now(),
|
|
86
|
+
crashCount: typeof parsed.crashCount === "number" ? parsed.crashCount : 0,
|
|
87
|
+
healthy: parsed.healthy === true,
|
|
88
|
+
badVersions: Array.isArray(parsed.badVersions)
|
|
89
|
+
? parsed.badVersions.filter((v) => typeof v === "string")
|
|
90
|
+
: [],
|
|
91
|
+
rollbackDisabled: parsed.rollbackDisabled === true ? true : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function saveHealth(path, state) {
|
|
99
|
+
try {
|
|
100
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
+
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// 落盘失败不致命(下次再写);绝不因此影响桥运行。
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** npm 包名(self-update 用 `npm install <PACKAGE_NAME>@<v>` 拉版本)。 */
|
|
2
|
+
export declare const PACKAGE_NAME = "mosse-agent-bridge";
|
|
3
|
+
/** launchd/systemd 服务名(mac plist Label / linux unit basename)。 */
|
|
4
|
+
export declare const SERVICE_LABEL = "ai.mosse.host";
|
|
5
|
+
export declare function hostDir(dataDir: string): string;
|
|
6
|
+
export declare function releasesDir(dataDir: string): string;
|
|
7
|
+
/** 某版本的安装目录(npm install --prefix 到这)。 */
|
|
8
|
+
export declare function releaseDir(dataDir: string, version: string): string;
|
|
9
|
+
/** `current` symlink(ExecStart 指它;切版本=改这条 link)。 */
|
|
10
|
+
export declare function currentLink(dataDir: string): string;
|
|
11
|
+
/** `previous` symlink(回滚目标)。 */
|
|
12
|
+
export declare function previousLink(dataDir: string): string;
|
|
13
|
+
export declare function logsDir(dataDir: string): string;
|
|
14
|
+
export declare function configPath(dataDir: string): string;
|
|
15
|
+
export declare function healthPath(dataDir: string): string;
|
|
16
|
+
/** 某版本安装目录里的可执行入口(`npm install --prefix` 的固定布局)。 */
|
|
17
|
+
export declare function releaseEntry(releaseInstallDir: string): string;
|
|
18
|
+
/** `current` symlink 下的可执行入口 —— ExecStart 用这个(OS 重启后自动跑活动版本)。 */
|
|
19
|
+
export declare function currentEntry(dataDir: string): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// M3:host 自更新的目录布局(纯路径计算,无 IO)—— install / self-update / rollback 共用。
|
|
2
|
+
//
|
|
3
|
+
// 布局(全在 <dataDir>/host/ 下,与 credentials.json/cc-sessions.json/agents 等**数据**并列但隔离):
|
|
4
|
+
// <dataDir>/host/
|
|
5
|
+
// releases/<version>/ 每个版本一个目录;`npm install mosse-agent-bridge@<v> --prefix` 装到这
|
|
6
|
+
// node_modules/mosse-agent-bridge/dist/index.js ← 该版本的可执行入口
|
|
7
|
+
// current → symlink 指向 releases/<活动版本> ExecStart 永远跑 current/<entry>
|
|
8
|
+
// previous → symlink 指向上一个版本 回滚时把 current 切回这里
|
|
9
|
+
// logs/ launchd/systemd 的 stdout/stderr
|
|
10
|
+
// config.json server / token / updateMode / channel / installedVersion
|
|
11
|
+
// launch-health.json 回滚用:启动时间戳 + 崩溃计数 + 已知健康/坏版本
|
|
12
|
+
//
|
|
13
|
+
// **只换 host/ 下的代码**,绝不碰 <dataDir> 下的 credentials.json / cc-sessions.json / agents/(数据跨版本保留)。
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** npm 包名(self-update 用 `npm install <PACKAGE_NAME>@<v>` 拉版本)。 */
|
|
16
|
+
export const PACKAGE_NAME = "mosse-agent-bridge";
|
|
17
|
+
/** launchd/systemd 服务名(mac plist Label / linux unit basename)。 */
|
|
18
|
+
export const SERVICE_LABEL = "ai.mosse.host";
|
|
19
|
+
export function hostDir(dataDir) {
|
|
20
|
+
return join(dataDir, "host");
|
|
21
|
+
}
|
|
22
|
+
export function releasesDir(dataDir) {
|
|
23
|
+
return join(hostDir(dataDir), "releases");
|
|
24
|
+
}
|
|
25
|
+
/** 某版本的安装目录(npm install --prefix 到这)。 */
|
|
26
|
+
export function releaseDir(dataDir, version) {
|
|
27
|
+
return join(releasesDir(dataDir), version);
|
|
28
|
+
}
|
|
29
|
+
/** `current` symlink(ExecStart 指它;切版本=改这条 link)。 */
|
|
30
|
+
export function currentLink(dataDir) {
|
|
31
|
+
return join(hostDir(dataDir), "current");
|
|
32
|
+
}
|
|
33
|
+
/** `previous` symlink(回滚目标)。 */
|
|
34
|
+
export function previousLink(dataDir) {
|
|
35
|
+
return join(hostDir(dataDir), "previous");
|
|
36
|
+
}
|
|
37
|
+
export function logsDir(dataDir) {
|
|
38
|
+
return join(hostDir(dataDir), "logs");
|
|
39
|
+
}
|
|
40
|
+
export function configPath(dataDir) {
|
|
41
|
+
return join(hostDir(dataDir), "config.json");
|
|
42
|
+
}
|
|
43
|
+
export function healthPath(dataDir) {
|
|
44
|
+
return join(hostDir(dataDir), "launch-health.json");
|
|
45
|
+
}
|
|
46
|
+
/** 某版本安装目录里的可执行入口(`npm install --prefix` 的固定布局)。 */
|
|
47
|
+
export function releaseEntry(releaseInstallDir) {
|
|
48
|
+
return join(releaseInstallDir, "node_modules", PACKAGE_NAME, "dist", "index.js");
|
|
49
|
+
}
|
|
50
|
+
/** `current` symlink 下的可执行入口 —— ExecStart 用这个(OS 重启后自动跑活动版本)。 */
|
|
51
|
+
export function currentEntry(dataDir) {
|
|
52
|
+
return releaseEntry(currentLink(dataDir));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=host-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-paths.js","sourceRoot":"","sources":["../src/host-paths.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,oFAAoF;AACpF,oBAAoB;AACpB,4FAA4F;AAC5F,oEAAoE;AACpE,gFAAgF;AAChF,mEAAmE;AACnE,kEAAkE;AAClE,2FAA2F;AAC3F,8DAA8D;AAC9D,EAAE;AACF,6FAA6F;AAE7F,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAEjD,kEAAkE;AAClE,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AACD,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC;AACD,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAe;IACzD,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AACD,oDAAoD;AACpD,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AACD,gCAAgC;AAChC,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;AAC/C,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC,CAAC;AACtD,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,YAAY,CAAC,iBAAyB;IACpD,OAAO,IAAI,CAAC,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;AACnF,CAAC;AACD,iEAAiE;AACjE,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type HostStatus = "ready" | "draining" | "updating";
|
|
2
|
+
export declare function trackStart(taskId: string): void;
|
|
3
|
+
export declare function trackEnd(taskId: string): void;
|
|
4
|
+
/** 当前在跑的长任务数(进程级,跨所有连接)。host_status 帧上报用。 */
|
|
5
|
+
export declare function inFlightCount(): number;
|
|
6
|
+
/** 进程空闲(无在跑长任务)= 可安全重启/更新。M2 drain 据此判定。 */
|
|
7
|
+
export declare function isIdle(): boolean;
|
|
8
|
+
export declare function getHostStatus(): HostStatus;
|
|
9
|
+
export declare function getTargetVersion(): string | undefined;
|
|
10
|
+
export declare function getUpdateError(): string | undefined;
|
|
11
|
+
/** 设置(或清空)自更新错误并广播给所有连接。M3 self-update/rollback 失败时调用。 */
|
|
12
|
+
export declare function setUpdateError(msg: string | undefined): void;
|
|
13
|
+
/**
|
|
14
|
+
* 切换进程级 host 状态(drain / updating 用),并通知所有连接广播 host_status。
|
|
15
|
+
*/
|
|
16
|
+
export declare function setHostStatus(status: HostStatus, target?: string): void;
|
|
17
|
+
/** 注册一个状态变化监听(connect() 每条连接注册一个);返回反注册函数(close 时调用)。 */
|
|
18
|
+
export declare function onHostStatusChange(listener: () => void): () => void;
|
|
19
|
+
/** drain 排队是否已超期(久等没空闲)。经 host_status 帧 → server/UI 提示用户"现在升?"。 */
|
|
20
|
+
export declare function isDrainOverdue(): boolean;
|
|
21
|
+
/** 注册"drain 完成可更新"的回调(进程级,index.ts main 注册一次)。本期 log,M3 执行真正更新。 */
|
|
22
|
+
export declare function setReadyToUpdateHook(cb: (version: string) => void): void;
|
|
23
|
+
export type UpdateDecision = "draining" | "deferred" | "ignored";
|
|
24
|
+
/**
|
|
25
|
+
* F2.1:host 收"有新版"信号后的决策(进程级,覆盖所有连接)。
|
|
26
|
+
* - "ignored":已在 drain/update 中(重复信号),或目标版本不高于当前(防误发/降级)。
|
|
27
|
+
* - "deferred":autoUpdate=false,仅记下目标版本、不 drain(M4 手动触发)。
|
|
28
|
+
* - "draining":autoUpdate=true,进 draining(若此刻已空闲则直接 → updating)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function onUpdateAvailable(version: string, opts: {
|
|
31
|
+
autoUpdate: boolean;
|
|
32
|
+
currentVersion: string;
|
|
33
|
+
drainDeadlineMs?: number;
|
|
34
|
+
}): UpdateDecision;
|
|
35
|
+
/** 测试用:重置进程级状态(in-flight + status + drain 编排 + 监听)。生产代码不调用。 */
|
|
36
|
+
export declare function __resetHostStateForTest(): void;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// host 自更新地基(M1 F1.2 + F1.3)+ drain 编排(M2 F2.1):**进程级** in-flight 任务追踪 +
|
|
2
|
+
// host 状态机 + "收到有新版 → 按模式 drain 等空闲 → 标记 updating(占位)" 的决策逻辑。
|
|
3
|
+
//
|
|
4
|
+
// 一个 host 进程为多个 token 各开一条 WS 连接(见 index.ts connect())。in-flight 和 status 都是
|
|
5
|
+
// **整个进程一份**(横跨所有连接),不是每连接一份 —— 任一连接进 draining,所有连接都拒新轮、都广播。
|
|
6
|
+
//
|
|
7
|
+
// M2 只到"决定要更新 → drain 等空闲 → 标记 updating(占位)"。真正的下载/退出/重启在 M3(本模块不执行)。
|
|
8
|
+
import { semverGt } from "./semver.js";
|
|
9
|
+
// ── F1.2 in-flight 追踪 ───────────────────────────────────────────────────────
|
|
10
|
+
// 计入 drain 的"会阻塞重启的在跑任务":**长回合** —— cc_turn(driver.run,可数分钟)+ legacy task。
|
|
11
|
+
// 重启会打断 agent 回合 → 必须等它们跑完(isIdle)才安全更新。
|
|
12
|
+
// 不计入:engine_config_read/write/test、fs_op —— 都是短请求-响应(秒级 RPC),drain 时很快自然清;
|
|
13
|
+
// 把它们计入只会让 isIdle 无谓抖动,且不会真的阻塞一次更新。
|
|
14
|
+
// 键 = server 下发的 taskId(server 端全局唯一:taskSeq / turnId),故跨连接也不会撞键。
|
|
15
|
+
const inFlight = new Set();
|
|
16
|
+
export function trackStart(taskId) {
|
|
17
|
+
inFlight.add(taskId);
|
|
18
|
+
}
|
|
19
|
+
export function trackEnd(taskId) {
|
|
20
|
+
inFlight.delete(taskId);
|
|
21
|
+
// M2:回合结束可能使进程空闲 → 推进 draining → updating(在轮间隙换版本,不打断对话)。
|
|
22
|
+
maybeFinishDrain();
|
|
23
|
+
}
|
|
24
|
+
/** 当前在跑的长任务数(进程级,跨所有连接)。host_status 帧上报用。 */
|
|
25
|
+
export function inFlightCount() {
|
|
26
|
+
return inFlight.size;
|
|
27
|
+
}
|
|
28
|
+
/** 进程空闲(无在跑长任务)= 可安全重启/更新。M2 drain 据此判定。 */
|
|
29
|
+
export function isIdle() {
|
|
30
|
+
return inFlight.size === 0;
|
|
31
|
+
}
|
|
32
|
+
// ── F1.3 host 状态机骨架 ──────────────────────────────────────────────────────
|
|
33
|
+
let hostStatus = "ready";
|
|
34
|
+
let targetVersion;
|
|
35
|
+
// 状态变化监听:每条 WS 连接注册一个,用于把 host_status 帧广播给**所有**连接(进程级状态)。
|
|
36
|
+
const listeners = new Set();
|
|
37
|
+
/** 向所有连接广播一次 host_status(状态/目标版本/drainOverdue 任一变化时调用)。 */
|
|
38
|
+
function broadcast() {
|
|
39
|
+
for (const l of listeners)
|
|
40
|
+
l();
|
|
41
|
+
}
|
|
42
|
+
export function getHostStatus() {
|
|
43
|
+
return hostStatus;
|
|
44
|
+
}
|
|
45
|
+
export function getTargetVersion() {
|
|
46
|
+
return targetVersion;
|
|
47
|
+
}
|
|
48
|
+
// M3:自更新/回滚出错时的最后一条错误(经 host_status 帧上报 server/UI)。状态变 ready 时清。
|
|
49
|
+
let updateError;
|
|
50
|
+
export function getUpdateError() {
|
|
51
|
+
return updateError;
|
|
52
|
+
}
|
|
53
|
+
/** 设置(或清空)自更新错误并广播给所有连接。M3 self-update/rollback 失败时调用。 */
|
|
54
|
+
export function setUpdateError(msg) {
|
|
55
|
+
updateError = msg;
|
|
56
|
+
broadcast();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 切换进程级 host 状态(drain / updating 用),并通知所有连接广播 host_status。
|
|
60
|
+
*/
|
|
61
|
+
export function setHostStatus(status, target) {
|
|
62
|
+
hostStatus = status;
|
|
63
|
+
targetVersion = target;
|
|
64
|
+
if (status === "ready")
|
|
65
|
+
updateError = undefined; // 回 ready(含自更新失败恢复)清掉旧错误。
|
|
66
|
+
broadcast();
|
|
67
|
+
}
|
|
68
|
+
/** 注册一个状态变化监听(connect() 每条连接注册一个);返回反注册函数(close 时调用)。 */
|
|
69
|
+
export function onHostStatusChange(listener) {
|
|
70
|
+
listeners.add(listener);
|
|
71
|
+
return () => {
|
|
72
|
+
listeners.delete(listener);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// ── M2 F2.1 drain 编排 ────────────────────────────────────────────────────────
|
|
76
|
+
// 收到"有新版"信号(server 下发 daemon_update_available)→ 按模式决策:
|
|
77
|
+
// autoUpdate=false → 仅记下目标版本、不 drain(等 M4 手动触发/UI)。
|
|
78
|
+
// autoUpdate=true → 进 draining:拒新轮 + 等当前轮跑完(isIdle)→ 标记 updating(M3 执行真正更新)。
|
|
79
|
+
// 久等不打断:超期(默认 30min)仍没空闲 → 不强杀,只标记 drainOverdue 让 server/UI 提示用户。
|
|
80
|
+
/** 默认 drain 期限:超此仍没空闲 → 标记 drainOverdue 提示用户(**绝不硬杀**)。可经 index.ts 传 env 覆盖。 */
|
|
81
|
+
const DEFAULT_DRAIN_DEADLINE_MS = 30 * 60 * 1000;
|
|
82
|
+
let drainOverdue = false;
|
|
83
|
+
let drainDeadlineTimer;
|
|
84
|
+
// "drain 完成、可以更新了" 的单一回调(index.ts 注册一个;本期只 log,M3 执行下载/退出/重启)。
|
|
85
|
+
let readyToUpdateHook;
|
|
86
|
+
/** drain 排队是否已超期(久等没空闲)。经 host_status 帧 → server/UI 提示用户"现在升?"。 */
|
|
87
|
+
export function isDrainOverdue() {
|
|
88
|
+
return drainOverdue;
|
|
89
|
+
}
|
|
90
|
+
/** 注册"drain 完成可更新"的回调(进程级,index.ts main 注册一次)。本期 log,M3 执行真正更新。 */
|
|
91
|
+
export function setReadyToUpdateHook(cb) {
|
|
92
|
+
readyToUpdateHook = cb;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* F2.1:host 收"有新版"信号后的决策(进程级,覆盖所有连接)。
|
|
96
|
+
* - "ignored":已在 drain/update 中(重复信号),或目标版本不高于当前(防误发/降级)。
|
|
97
|
+
* - "deferred":autoUpdate=false,仅记下目标版本、不 drain(M4 手动触发)。
|
|
98
|
+
* - "draining":autoUpdate=true,进 draining(若此刻已空闲则直接 → updating)。
|
|
99
|
+
*/
|
|
100
|
+
export function onUpdateAvailable(version, opts) {
|
|
101
|
+
// 已在 draining/updating:本期忽略重复信号(已在处理一次更新)。
|
|
102
|
+
if (hostStatus !== "ready")
|
|
103
|
+
return "ignored";
|
|
104
|
+
// 版本守卫:目标不高于当前 → 不更新(防 server 误发 / 降级)。例:0.1.10 才会触发,0.1.9 不会。
|
|
105
|
+
if (!semverGt(version, opts.currentVersion))
|
|
106
|
+
return "ignored";
|
|
107
|
+
if (!opts.autoUpdate) {
|
|
108
|
+
// 仅手动 / 关:记下目标版本供 UI 提示(保持 ready,不 drain),等 M4 手动触发。
|
|
109
|
+
targetVersion = version;
|
|
110
|
+
broadcast();
|
|
111
|
+
return "deferred";
|
|
112
|
+
}
|
|
113
|
+
// 自动:进 draining(进程级)。先清久等标记,再进状态。
|
|
114
|
+
drainOverdue = false;
|
|
115
|
+
setHostStatus("draining", version); // 广播 draining + targetVersion
|
|
116
|
+
maybeFinishDrain(); // 若此刻已空闲 → 直接 updating(无在跑轮)
|
|
117
|
+
// getHostStatus()(而非局部 hostStatus)避开 TS 对前面 `!== "ready"` 守卫的控制流收窄。
|
|
118
|
+
if (getHostStatus() === "draining")
|
|
119
|
+
armDrainDeadline(opts.drainDeadlineMs); // 仍在等 → 起久等期限
|
|
120
|
+
return "draining";
|
|
121
|
+
}
|
|
122
|
+
/** draining 且进程已空闲 → 标记 updating(占位);回调 index.ts(本期 log,M3 执行真正更新)。 */
|
|
123
|
+
function maybeFinishDrain() {
|
|
124
|
+
if (hostStatus !== "draining" || !isIdle())
|
|
125
|
+
return;
|
|
126
|
+
clearDrainDeadline();
|
|
127
|
+
const target = targetVersion;
|
|
128
|
+
setHostStatus("updating", target); // 广播 updating
|
|
129
|
+
if (target)
|
|
130
|
+
readyToUpdateHook?.(target);
|
|
131
|
+
}
|
|
132
|
+
function armDrainDeadline(ms = DEFAULT_DRAIN_DEADLINE_MS) {
|
|
133
|
+
clearDrainDeadline();
|
|
134
|
+
drainDeadlineTimer = setTimeout(() => {
|
|
135
|
+
// 久等不打断:超期仍没空闲 → 标记 drainOverdue 让 server/UI 提示用户裁定,**绝不硬杀长回合**。
|
|
136
|
+
if (hostStatus === "draining" && !isIdle()) {
|
|
137
|
+
drainOverdue = true;
|
|
138
|
+
broadcast();
|
|
139
|
+
}
|
|
140
|
+
}, ms);
|
|
141
|
+
// 别让这个计时器拖住进程退出(它只是提示用途)。
|
|
142
|
+
drainDeadlineTimer.unref?.();
|
|
143
|
+
}
|
|
144
|
+
function clearDrainDeadline() {
|
|
145
|
+
if (drainDeadlineTimer) {
|
|
146
|
+
clearTimeout(drainDeadlineTimer);
|
|
147
|
+
drainDeadlineTimer = undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** 测试用:重置进程级状态(in-flight + status + drain 编排 + 监听)。生产代码不调用。 */
|
|
151
|
+
export function __resetHostStateForTest() {
|
|
152
|
+
inFlight.clear();
|
|
153
|
+
hostStatus = "ready";
|
|
154
|
+
targetVersion = undefined;
|
|
155
|
+
updateError = undefined;
|
|
156
|
+
drainOverdue = false;
|
|
157
|
+
clearDrainDeadline();
|
|
158
|
+
readyToUpdateHook = undefined;
|
|
159
|
+
listeners.clear();
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=host-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-state.js","sourceRoot":"","sources":["../src/host-state.ts"],"names":[],"mappings":"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"}
|