@codedeck/codedeck 2026.3.1-4.63
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 +38 -0
- package/config/default.yaml +51 -0
- package/dist/agent/brain-dispatcher.d.ts +67 -0
- package/dist/agent/brain-dispatcher.d.ts.map +1 -0
- package/dist/agent/brain-dispatcher.js +136 -0
- package/dist/agent/brain-dispatcher.js.map +1 -0
- package/dist/agent/detect.d.ts +20 -0
- package/dist/agent/detect.d.ts.map +1 -0
- package/dist/agent/detect.js +187 -0
- package/dist/agent/detect.js.map +1 -0
- package/dist/agent/drivers/base.d.ts +57 -0
- package/dist/agent/drivers/base.d.ts.map +1 -0
- package/dist/agent/drivers/base.js +3 -0
- package/dist/agent/drivers/base.js.map +1 -0
- package/dist/agent/drivers/claude-code.d.ts +14 -0
- package/dist/agent/drivers/claude-code.d.ts.map +1 -0
- package/dist/agent/drivers/claude-code.js +112 -0
- package/dist/agent/drivers/claude-code.js.map +1 -0
- package/dist/agent/drivers/codex.d.ts +14 -0
- package/dist/agent/drivers/codex.d.ts.map +1 -0
- package/dist/agent/drivers/codex.js +77 -0
- package/dist/agent/drivers/codex.js.map +1 -0
- package/dist/agent/drivers/gemini.d.ts +24 -0
- package/dist/agent/drivers/gemini.d.ts.map +1 -0
- package/dist/agent/drivers/gemini.js +142 -0
- package/dist/agent/drivers/gemini.js.map +1 -0
- package/dist/agent/drivers/opencode.d.ts +18 -0
- package/dist/agent/drivers/opencode.d.ts.map +1 -0
- package/dist/agent/drivers/opencode.js +50 -0
- package/dist/agent/drivers/opencode.js.map +1 -0
- package/dist/agent/drivers/shell.d.ts +13 -0
- package/dist/agent/drivers/shell.d.ts.map +1 -0
- package/dist/agent/drivers/shell.js +30 -0
- package/dist/agent/drivers/shell.js.map +1 -0
- package/dist/agent/env-isolation.d.ts +26 -0
- package/dist/agent/env-isolation.d.ts.map +1 -0
- package/dist/agent/env-isolation.js +103 -0
- package/dist/agent/env-isolation.js.map +1 -0
- package/dist/agent/notify-setup.d.ts +18 -0
- package/dist/agent/notify-setup.d.ts.map +1 -0
- package/dist/agent/notify-setup.js +68 -0
- package/dist/agent/notify-setup.js.map +1 -0
- package/dist/agent/session-manager.d.ts +75 -0
- package/dist/agent/session-manager.d.ts.map +1 -0
- package/dist/agent/session-manager.js +407 -0
- package/dist/agent/session-manager.js.map +1 -0
- package/dist/agent/signal.d.ts +32 -0
- package/dist/agent/signal.d.ts.map +1 -0
- package/dist/agent/signal.js +199 -0
- package/dist/agent/signal.js.map +1 -0
- package/dist/agent/status-poller.d.ts +27 -0
- package/dist/agent/status-poller.d.ts.map +1 -0
- package/dist/agent/status-poller.js +76 -0
- package/dist/agent/status-poller.js.map +1 -0
- package/dist/agent/templates/brain-prompt.d.ts +14 -0
- package/dist/agent/templates/brain-prompt.d.ts.map +1 -0
- package/dist/agent/templates/brain-prompt.js +57 -0
- package/dist/agent/templates/brain-prompt.js.map +1 -0
- package/dist/agent/templates/identity.d.ts +19 -0
- package/dist/agent/templates/identity.d.ts.map +1 -0
- package/dist/agent/templates/identity.js +97 -0
- package/dist/agent/templates/identity.js.map +1 -0
- package/dist/agent/tmux.d.ts +90 -0
- package/dist/agent/tmux.d.ts.map +1 -0
- package/dist/agent/tmux.js +386 -0
- package/dist/agent/tmux.js.map +1 -0
- package/dist/autofix/audit-engine.d.ts +35 -0
- package/dist/autofix/audit-engine.d.ts.map +1 -0
- package/dist/autofix/audit-engine.js +144 -0
- package/dist/autofix/audit-engine.js.map +1 -0
- package/dist/autofix/branch-manager.d.ts +44 -0
- package/dist/autofix/branch-manager.d.ts.map +1 -0
- package/dist/autofix/branch-manager.js +97 -0
- package/dist/autofix/branch-manager.js.map +1 -0
- package/dist/autofix/decision-engine.d.ts +38 -0
- package/dist/autofix/decision-engine.d.ts.map +1 -0
- package/dist/autofix/decision-engine.js +115 -0
- package/dist/autofix/decision-engine.js.map +1 -0
- package/dist/autofix/index.d.ts +23 -0
- package/dist/autofix/index.d.ts.map +1 -0
- package/dist/autofix/index.js +192 -0
- package/dist/autofix/index.js.map +1 -0
- package/dist/autofix/prompt-builder.d.ts +25 -0
- package/dist/autofix/prompt-builder.d.ts.map +1 -0
- package/dist/autofix/prompt-builder.js +137 -0
- package/dist/autofix/prompt-builder.js.map +1 -0
- package/dist/autofix/report-parser.d.ts +18 -0
- package/dist/autofix/report-parser.d.ts.map +1 -0
- package/dist/autofix/report-parser.js +74 -0
- package/dist/autofix/report-parser.js.map +1 -0
- package/dist/autofix/state-machine.d.ts +40 -0
- package/dist/autofix/state-machine.d.ts.map +1 -0
- package/dist/autofix/state-machine.js +76 -0
- package/dist/autofix/state-machine.js.map +1 -0
- package/dist/bind/bind-flow.d.ts +15 -0
- package/dist/bind/bind-flow.d.ts.map +1 -0
- package/dist/bind/bind-flow.js +198 -0
- package/dist/bind/bind-flow.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +89 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/codex-watcher.d.ts +46 -0
- package/dist/daemon/codex-watcher.d.ts.map +1 -0
- package/dist/daemon/codex-watcher.js +533 -0
- package/dist/daemon/codex-watcher.js.map +1 -0
- package/dist/daemon/command-handler.d.ts +6 -0
- package/dist/daemon/command-handler.d.ts.map +1 -0
- package/dist/daemon/command-handler.js +770 -0
- package/dist/daemon/command-handler.js.map +1 -0
- package/dist/daemon/discussion-orchestrator.d.ts +63 -0
- package/dist/daemon/discussion-orchestrator.d.ts.map +1 -0
- package/dist/daemon/discussion-orchestrator.js +482 -0
- package/dist/daemon/discussion-orchestrator.js.map +1 -0
- package/dist/daemon/gemini-watcher.d.ts +42 -0
- package/dist/daemon/gemini-watcher.d.ts.map +1 -0
- package/dist/daemon/gemini-watcher.js +463 -0
- package/dist/daemon/gemini-watcher.js.map +1 -0
- package/dist/daemon/hook-server.d.ts +42 -0
- package/dist/daemon/hook-server.d.ts.map +1 -0
- package/dist/daemon/hook-server.js +160 -0
- package/dist/daemon/hook-server.js.map +1 -0
- package/dist/daemon/jsonl-watcher.d.ts +35 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -0
- package/dist/daemon/jsonl-watcher.js +635 -0
- package/dist/daemon/jsonl-watcher.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +20 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +331 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/server-link.d.ts +44 -0
- package/dist/daemon/server-link.d.ts.map +1 -0
- package/dist/daemon/server-link.js +232 -0
- package/dist/daemon/server-link.js.map +1 -0
- package/dist/daemon/subsession-manager.d.ts +37 -0
- package/dist/daemon/subsession-manager.d.ts.map +1 -0
- package/dist/daemon/subsession-manager.js +240 -0
- package/dist/daemon/subsession-manager.js.map +1 -0
- package/dist/daemon/terminal-parser.d.ts +42 -0
- package/dist/daemon/terminal-parser.d.ts.map +1 -0
- package/dist/daemon/terminal-parser.js +278 -0
- package/dist/daemon/terminal-parser.js.map +1 -0
- package/dist/daemon/terminal-streamer.d.ts +93 -0
- package/dist/daemon/terminal-streamer.d.ts.map +1 -0
- package/dist/daemon/terminal-streamer.js +451 -0
- package/dist/daemon/terminal-streamer.js.map +1 -0
- package/dist/daemon/timeline-emitter.d.ts +32 -0
- package/dist/daemon/timeline-emitter.d.ts.map +1 -0
- package/dist/daemon/timeline-emitter.js +97 -0
- package/dist/daemon/timeline-emitter.js.map +1 -0
- package/dist/daemon/timeline-event.d.ts +23 -0
- package/dist/daemon/timeline-event.d.ts.map +1 -0
- package/dist/daemon/timeline-event.js +7 -0
- package/dist/daemon/timeline-event.js.map +1 -0
- package/dist/daemon/timeline-store.d.ts +40 -0
- package/dist/daemon/timeline-store.d.ts.map +1 -0
- package/dist/daemon/timeline-store.js +153 -0
- package/dist/daemon/timeline-store.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/claude-mem.d.ts +9 -0
- package/dist/memory/claude-mem.d.ts.map +1 -0
- package/dist/memory/claude-mem.js +58 -0
- package/dist/memory/claude-mem.js.map +1 -0
- package/dist/memory/context-builder.d.ts +4 -0
- package/dist/memory/context-builder.d.ts.map +1 -0
- package/dist/memory/context-builder.js +35 -0
- package/dist/memory/context-builder.js.map +1 -0
- package/dist/memory/detector.d.ts +7 -0
- package/dist/memory/detector.d.ts.map +1 -0
- package/dist/memory/detector.js +17 -0
- package/dist/memory/detector.js.map +1 -0
- package/dist/memory/extractor.d.ts +21 -0
- package/dist/memory/extractor.d.ts.map +1 -0
- package/dist/memory/extractor.js +83 -0
- package/dist/memory/extractor.js.map +1 -0
- package/dist/memory/injector.d.ts +7 -0
- package/dist/memory/injector.d.ts.map +1 -0
- package/dist/memory/injector.js +18 -0
- package/dist/memory/injector.js.map +1 -0
- package/dist/memory/interface.d.ts +25 -0
- package/dist/memory/interface.d.ts.map +1 -0
- package/dist/memory/interface.js +3 -0
- package/dist/memory/interface.js.map +1 -0
- package/dist/memory/mem0.d.ts +12 -0
- package/dist/memory/mem0.d.ts.map +1 -0
- package/dist/memory/mem0.js +93 -0
- package/dist/memory/mem0.js.map +1 -0
- package/dist/router/command-parser.d.ts +33 -0
- package/dist/router/command-parser.d.ts.map +1 -0
- package/dist/router/command-parser.js +66 -0
- package/dist/router/command-parser.js.map +1 -0
- package/dist/router/message-router.d.ts +42 -0
- package/dist/router/message-router.d.ts.map +1 -0
- package/dist/router/message-router.js +222 -0
- package/dist/router/message-router.js.map +1 -0
- package/dist/router/response-collector.d.ts +28 -0
- package/dist/router/response-collector.d.ts.map +1 -0
- package/dist/router/response-collector.js +164 -0
- package/dist/router/response-collector.js.map +1 -0
- package/dist/store/project-store.d.ts +37 -0
- package/dist/store/project-store.d.ts.map +1 -0
- package/dist/store/project-store.js +70 -0
- package/dist/store/project-store.js.map +1 -0
- package/dist/store/session-store.d.ts +32 -0
- package/dist/store/session-store.d.ts.map +1 -0
- package/dist/store/session-store.js +67 -0
- package/dist/store/session-store.js.map +1 -0
- package/dist/tracker/branch.d.ts +24 -0
- package/dist/tracker/branch.d.ts.map +1 -0
- package/dist/tracker/branch.js +55 -0
- package/dist/tracker/branch.js.map +1 -0
- package/dist/tracker/github.d.ts +31 -0
- package/dist/tracker/github.d.ts.map +1 -0
- package/dist/tracker/github.js +117 -0
- package/dist/tracker/github.js.map +1 -0
- package/dist/tracker/gitlab.d.ts +31 -0
- package/dist/tracker/gitlab.d.ts.map +1 -0
- package/dist/tracker/gitlab.js +116 -0
- package/dist/tracker/gitlab.js.map +1 -0
- package/dist/tracker/index.d.ts +9 -0
- package/dist/tracker/index.d.ts.map +1 -0
- package/dist/tracker/index.js +28 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/tracker/interface.d.ts +39 -0
- package/dist/tracker/interface.d.ts.map +1 -0
- package/dist/tracker/interface.js +7 -0
- package/dist/tracker/interface.js.map +1 -0
- package/dist/tracker/priority.d.ts +19 -0
- package/dist/tracker/priority.d.ts.map +1 -0
- package/dist/tracker/priority.js +40 -0
- package/dist/tracker/priority.js.map +1 -0
- package/dist/util/logger.d.ts +4 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +14 -0
- package/dist/util/logger.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ServerLink } from './server-link.js';
|
|
2
|
+
import { type Config } from '../config.js';
|
|
3
|
+
import type { MemoryBackend } from '../memory/interface.js';
|
|
4
|
+
export interface DaemonContext {
|
|
5
|
+
config: Config;
|
|
6
|
+
memory: MemoryBackend | null;
|
|
7
|
+
serverLink: ServerLink | null;
|
|
8
|
+
/** Persist a channel binding to D1 via CF Worker API. Returns false if not connected or request fails. */
|
|
9
|
+
persistBinding(platform: string, channelId: string, botId: string, bindingType: string, target: string): Promise<boolean>;
|
|
10
|
+
/** Remove a channel binding from D1 via CF Worker API. Returns false if not connected or request fails. */
|
|
11
|
+
removeBinding(platform: string, channelId: string, botId: string): Promise<boolean>;
|
|
12
|
+
/** Send a session event (started/stopped/error) to the CF Worker for relay to browsers. */
|
|
13
|
+
sendSessionEvent(event: 'started' | 'stopped' | 'error', session: string, state: string): void;
|
|
14
|
+
}
|
|
15
|
+
/** Startup sequence: config → store → memory → sessions → server link */
|
|
16
|
+
export declare function startup(): Promise<DaemonContext>;
|
|
17
|
+
/** Shutdown sequence: flush store, disconnect WS, exit cleanly */
|
|
18
|
+
export declare function shutdown(exitCode?: number): Promise<void>;
|
|
19
|
+
export declare function getDaemonContext(): DaemonContext;
|
|
20
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO9C,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAIvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG5D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,0GAA0G;IAC1G,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1H,2GAA2G;IAC3G,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,2FAA2F;IAC3F,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChG;AA6ED,yEAAyE;AACzE,wBAAsB,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,CAyLtD;AAED,kEAAkE;AAClE,wBAAsB,QAAQ,CAAC,QAAQ,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1D;AAmCD,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startup = startup;
|
|
7
|
+
exports.shutdown = shutdown;
|
|
8
|
+
exports.getDaemonContext = getDaemonContext;
|
|
9
|
+
const session_store_js_1 = require("../store/session-store.js");
|
|
10
|
+
const session_manager_js_1 = require("../agent/session-manager.js");
|
|
11
|
+
const tmux_js_1 = require("../agent/tmux.js");
|
|
12
|
+
const detector_js_1 = require("../memory/detector.js");
|
|
13
|
+
const server_link_js_1 = require("./server-link.js");
|
|
14
|
+
const command_handler_js_1 = require("./command-handler.js");
|
|
15
|
+
const timeline_emitter_js_1 = require("./timeline-emitter.js");
|
|
16
|
+
const timeline_store_js_1 = require("./timeline-store.js");
|
|
17
|
+
const hook_server_js_1 = require("./hook-server.js");
|
|
18
|
+
const signal_js_1 = require("../agent/signal.js");
|
|
19
|
+
const config_js_1 = require("../config.js");
|
|
20
|
+
const bind_flow_js_1 = require("../bind/bind-flow.js");
|
|
21
|
+
const tmux_js_2 = require("../agent/tmux.js");
|
|
22
|
+
const logger_js_1 = __importDefault(require("../util/logger.js"));
|
|
23
|
+
let ctx = null;
|
|
24
|
+
// ── Worker session sync helpers ────────────────────────────────────────────
|
|
25
|
+
async function persistSessionToWorker(workerUrl, serverId, token, name, record) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${workerUrl}/api/server/${serverId}/sessions/${encodeURIComponent(name)}`, {
|
|
28
|
+
method: 'PUT',
|
|
29
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'X-Server-Id': serverId },
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
projectName: record.projectName,
|
|
32
|
+
projectRole: record.role,
|
|
33
|
+
agentType: record.agentType,
|
|
34
|
+
projectDir: record.projectDir,
|
|
35
|
+
state: record.state,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
logger_js_1.default.warn({ status: res.status, name }, 'persistSessionToWorker: non-ok response');
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
logger_js_1.default.warn({ err: e, name }, 'persistSessionToWorker: fetch failed');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function deleteSessionFromWorker(workerUrl, serverId, token, name) {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${workerUrl}/api/server/${serverId}/sessions/${encodeURIComponent(name)}`, {
|
|
48
|
+
method: 'DELETE',
|
|
49
|
+
headers: { Authorization: `Bearer ${token}`, 'X-Server-Id': serverId },
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
logger_js_1.default.warn({ status: res.status, name }, 'deleteSessionFromWorker: non-ok response');
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
logger_js_1.default.warn({ err: e, name }, 'deleteSessionFromWorker: fetch failed');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** On startup: pull sessions from D1 and populate the local store so restoreFromStore can rebuild tmux. */
|
|
59
|
+
async function syncSessionsFromWorker(workerUrl, serverId, token) {
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch(`${workerUrl}/api/server/${serverId}/sessions`, {
|
|
62
|
+
headers: { Authorization: `Bearer ${token}`, 'X-Server-Id': serverId },
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
logger_js_1.default.warn({ status: res.status }, 'syncSessionsFromWorker: non-ok response');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (const s of data.sessions) {
|
|
71
|
+
if (s.state === 'stopped')
|
|
72
|
+
continue; // skip stopped sessions
|
|
73
|
+
(0, session_store_js_1.upsertSession)({
|
|
74
|
+
name: s.name,
|
|
75
|
+
projectName: s.project_name,
|
|
76
|
+
role: s.role,
|
|
77
|
+
agentType: s.agent_type,
|
|
78
|
+
projectDir: s.project_dir,
|
|
79
|
+
state: s.state,
|
|
80
|
+
restarts: 0,
|
|
81
|
+
restartTimestamps: [],
|
|
82
|
+
createdAt: Date.now(),
|
|
83
|
+
updatedAt: Date.now(),
|
|
84
|
+
});
|
|
85
|
+
count++;
|
|
86
|
+
}
|
|
87
|
+
logger_js_1.default.info({ count }, 'Sessions synced from D1');
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
logger_js_1.default.warn({ err: e }, 'syncSessionsFromWorker: fetch failed');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Startup sequence: config → store → memory → sessions → server link */
|
|
94
|
+
async function startup() {
|
|
95
|
+
logger_js_1.default.info('Daemon starting');
|
|
96
|
+
const config = await (0, config_js_1.loadConfig)();
|
|
97
|
+
logger_js_1.default.info({ config: config.daemon }, 'Config loaded');
|
|
98
|
+
await (0, session_store_js_1.loadStore)();
|
|
99
|
+
logger_js_1.default.info('Session store loaded');
|
|
100
|
+
await (0, session_manager_js_1.initOnStartup)();
|
|
101
|
+
logger_js_1.default.info('Startup cleanup done');
|
|
102
|
+
// Clean up old timeline files (>7 days)
|
|
103
|
+
timeline_store_js_1.timelineStore.cleanup();
|
|
104
|
+
const { backend: memory, mode } = await (0, detector_js_1.detectMemoryBackend)();
|
|
105
|
+
logger_js_1.default.info({ mode }, 'Memory backend selected');
|
|
106
|
+
const creds = await (0, bind_flow_js_1.loadCredentials)();
|
|
107
|
+
// No fallback: a valid serverId is required for the WS endpoint (/api/server/:id/ws)
|
|
108
|
+
// and for the auth handshake. Without stored credentials from `codedeck bind`, we
|
|
109
|
+
// cannot connect to the CF Worker.
|
|
110
|
+
const workerUrl = creds?.workerUrl;
|
|
111
|
+
const serverId = creds?.serverId ?? '';
|
|
112
|
+
const token = creds?.token ?? '';
|
|
113
|
+
// Sync sessions from D1 before restoring tmux sessions
|
|
114
|
+
if (creds) {
|
|
115
|
+
await syncSessionsFromWorker(workerUrl, serverId, token);
|
|
116
|
+
}
|
|
117
|
+
await (0, session_manager_js_1.restoreFromStore)();
|
|
118
|
+
logger_js_1.default.info('Sessions reconciled');
|
|
119
|
+
let serverLink = null;
|
|
120
|
+
if (creds) {
|
|
121
|
+
serverLink = new server_link_js_1.ServerLink({ workerUrl: workerUrl, serverId, token });
|
|
122
|
+
serverLink.onMessage((msg) => {
|
|
123
|
+
(0, command_handler_js_1.handleWebCommand)(msg, serverLink);
|
|
124
|
+
});
|
|
125
|
+
serverLink.connect();
|
|
126
|
+
}
|
|
127
|
+
// Wire session events → ServerLink so the browser sees them
|
|
128
|
+
(0, session_manager_js_1.setSessionEventCallback)((event, session, state) => {
|
|
129
|
+
if (!serverLink)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
serverLink.send({ type: 'session_event', event, session, state });
|
|
133
|
+
}
|
|
134
|
+
catch { /* not connected */ }
|
|
135
|
+
});
|
|
136
|
+
// Wire session persist → D1 via Worker API
|
|
137
|
+
if (creds) {
|
|
138
|
+
(0, session_manager_js_1.setSessionPersistCallback)(async (record, name) => {
|
|
139
|
+
if (record) {
|
|
140
|
+
await persistSessionToWorker(workerUrl, serverId, token, name, record);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await deleteSessionFromWorker(workerUrl, serverId, token, name);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// Push all active sessions from local store to DB on startup.
|
|
147
|
+
// Covers the case where DB was cleared while the daemon was running
|
|
148
|
+
// (or route was misconfigured and persists silently failed).
|
|
149
|
+
const localSessions = (0, session_store_js_1.listSessions)();
|
|
150
|
+
for (const s of localSessions) {
|
|
151
|
+
if (s.state !== 'stopped') {
|
|
152
|
+
await persistSessionToWorker(workerUrl, serverId, token, s.name, s);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (localSessions.filter((s) => s.state !== 'stopped').length > 0) {
|
|
156
|
+
logger_js_1.default.info({ count: localSessions.filter((s) => s.state !== 'stopped').length }, 'Pushed local sessions to server DB on startup');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function persistBinding(platform, channelId, botId, bindingType, target) {
|
|
160
|
+
if (!workerUrl || !serverId || !token)
|
|
161
|
+
return false;
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch(`${workerUrl}/api/server/${serverId}/bindings`, {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
166
|
+
body: JSON.stringify({ platform, channelId, botId, bindingType, target }),
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
logger_js_1.default.warn({ status: res.status, platform, channelId }, 'persistBinding: worker returned error');
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
logger_js_1.default.warn({ err: e, platform, channelId }, 'persistBinding: fetch failed');
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function removeBinding(platform, channelId, botId) {
|
|
180
|
+
if (!workerUrl || !serverId || !token)
|
|
181
|
+
return false;
|
|
182
|
+
try {
|
|
183
|
+
const res = await fetch(`${workerUrl}/api/server/${serverId}/bindings`, {
|
|
184
|
+
method: 'DELETE',
|
|
185
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
186
|
+
body: JSON.stringify({ platform, channelId, botId }),
|
|
187
|
+
});
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
logger_js_1.default.warn({ status: res.status, platform, channelId }, 'removeBinding: worker returned error');
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
logger_js_1.default.warn({ err: e, platform, channelId }, 'removeBinding: fetch failed');
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function sendSessionEvent(event, session, state) {
|
|
200
|
+
if (!serverLink)
|
|
201
|
+
return;
|
|
202
|
+
try {
|
|
203
|
+
serverLink.send({ type: 'session_event', event, session, state });
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
logger_js_1.default.warn({ err: e, event, session }, 'Failed to send session event');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Forward all timeline events to connected browsers via ServerLink
|
|
210
|
+
if (serverLink) {
|
|
211
|
+
timeline_emitter_js_1.timelineEmitter.on((event) => {
|
|
212
|
+
serverLink.sendTimelineEvent(event);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Set up router context so inbound chat messages can be dispatched to routeMessage
|
|
216
|
+
if (serverLink) {
|
|
217
|
+
(0, command_handler_js_1.setRouterContext)({
|
|
218
|
+
sendOutbound: async (channelId, platform, botId, content) => {
|
|
219
|
+
if (!workerUrl || !token) {
|
|
220
|
+
logger_js_1.default.warn({ platform, channelId }, 'sendOutbound: no worker credentials');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const res = await fetch(`${workerUrl}/api/outbound`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
227
|
+
body: JSON.stringify({ platform, botId, channelId, content }),
|
|
228
|
+
});
|
|
229
|
+
if (!res.ok) {
|
|
230
|
+
logger_js_1.default.warn({ status: res.status, platform, channelId }, 'sendOutbound: worker returned error');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
logger_js_1.default.warn({ err: e, platform, channelId }, 'sendOutbound failed');
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
sendToSession: async (sessionName, text) => {
|
|
238
|
+
await (0, tmux_js_2.sendKeys)(sessionName, text);
|
|
239
|
+
},
|
|
240
|
+
persistBinding,
|
|
241
|
+
removeBinding,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Start local hook server — agents POST here via CC hooks / notify plugins.
|
|
245
|
+
// Port is auto-discovered (saved across restarts); hook scripts are rewritten with actual port.
|
|
246
|
+
const hookResult = await (0, hook_server_js_1.startHookServer)((payload) => {
|
|
247
|
+
if (!serverLink)
|
|
248
|
+
return;
|
|
249
|
+
try {
|
|
250
|
+
const record = (0, session_store_js_1.listSessions)().find((s) => s.name === payload.session);
|
|
251
|
+
const projectName = record?.projectName ?? payload.session;
|
|
252
|
+
if (payload.event === 'idle') {
|
|
253
|
+
serverLink.send({ type: 'session.idle', session: payload.session, project: projectName, agentType: payload.agentType });
|
|
254
|
+
}
|
|
255
|
+
else if (payload.event === 'notification') {
|
|
256
|
+
serverLink.send({ type: 'session.notification', session: payload.session, project: projectName, title: payload.title, message: payload.message });
|
|
257
|
+
}
|
|
258
|
+
else if (payload.event === 'tool_start') {
|
|
259
|
+
serverLink.send({ type: 'session.tool', session: payload.session, tool: payload.tool });
|
|
260
|
+
}
|
|
261
|
+
else if (payload.event === 'tool_end') {
|
|
262
|
+
serverLink.send({ type: 'session.tool', session: payload.session, tool: null });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch { /* not connected */ }
|
|
266
|
+
});
|
|
267
|
+
hookServer = hookResult.server;
|
|
268
|
+
// Rewrite all CC hook scripts with the actual port (may differ from last run)
|
|
269
|
+
await (0, signal_js_1.setupCCHooks)().catch((e) => logger_js_1.default.warn({ err: e }, 'CC hook setup failed'));
|
|
270
|
+
ctx = { config, memory, serverLink, persistBinding, removeBinding, sendSessionEvent };
|
|
271
|
+
setupSignalHandlers();
|
|
272
|
+
startHealthPoller();
|
|
273
|
+
logger_js_1.default.info('Daemon started');
|
|
274
|
+
return ctx;
|
|
275
|
+
}
|
|
276
|
+
/** Shutdown sequence: flush store, disconnect WS, exit cleanly */
|
|
277
|
+
async function shutdown(exitCode = 0) {
|
|
278
|
+
logger_js_1.default.info('Daemon shutting down');
|
|
279
|
+
try {
|
|
280
|
+
if (healthTimer)
|
|
281
|
+
clearInterval(healthTimer);
|
|
282
|
+
hookServer?.close();
|
|
283
|
+
ctx?.serverLink?.disconnect();
|
|
284
|
+
await (0, session_store_js_1.flushStore)();
|
|
285
|
+
logger_js_1.default.info('Store flushed');
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
logger_js_1.default.error({ err: e }, 'Error during shutdown');
|
|
289
|
+
}
|
|
290
|
+
// tmux sessions are intentionally NOT killed — they keep running
|
|
291
|
+
logger_js_1.default.info('Daemon stopped (tmux sessions left running)');
|
|
292
|
+
process.exit(exitCode);
|
|
293
|
+
}
|
|
294
|
+
const HEALTH_POLL_MS = 30_000;
|
|
295
|
+
let healthTimer = null;
|
|
296
|
+
let hookServer = null;
|
|
297
|
+
/** Periodically check all running sessions; restart any that have disappeared. */
|
|
298
|
+
function startHealthPoller() {
|
|
299
|
+
healthTimer = setInterval(async () => {
|
|
300
|
+
const sessions = (0, session_store_js_1.listSessions)();
|
|
301
|
+
for (const s of sessions) {
|
|
302
|
+
if (s.state === 'stopped' || s.state === 'error')
|
|
303
|
+
continue;
|
|
304
|
+
try {
|
|
305
|
+
const alive = await (0, tmux_js_1.sessionExists)(s.name);
|
|
306
|
+
if (!alive) {
|
|
307
|
+
logger_js_1.default.warn({ session: s.name }, 'Session missing, attempting restart');
|
|
308
|
+
await (0, session_manager_js_1.restartSession)(s);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
logger_js_1.default.warn({ session: s.name, err }, 'Health check error');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}, HEALTH_POLL_MS);
|
|
316
|
+
}
|
|
317
|
+
function setupSignalHandlers() {
|
|
318
|
+
const handler = () => shutdown(0);
|
|
319
|
+
process.on('SIGTERM', handler);
|
|
320
|
+
process.on('SIGINT', handler);
|
|
321
|
+
process.on('uncaughtException', (err) => {
|
|
322
|
+
logger_js_1.default.error({ err }, 'Uncaught exception');
|
|
323
|
+
shutdown(1);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function getDaemonContext() {
|
|
327
|
+
if (!ctx)
|
|
328
|
+
throw new Error('Daemon not started');
|
|
329
|
+
return ctx;
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/daemon/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TimelineEvent } from './timeline-event.js';
|
|
2
|
+
export interface ServerLinkOpts {
|
|
3
|
+
workerUrl: string;
|
|
4
|
+
serverId: string;
|
|
5
|
+
token: string;
|
|
6
|
+
}
|
|
7
|
+
export type MessageHandler = (msg: unknown) => void;
|
|
8
|
+
export declare class ServerLink {
|
|
9
|
+
private ws;
|
|
10
|
+
private handlers;
|
|
11
|
+
private heartbeatTimer?;
|
|
12
|
+
private statsTimer?;
|
|
13
|
+
private reconnectTimer?;
|
|
14
|
+
private watchdogTimer?;
|
|
15
|
+
private pongTimer?;
|
|
16
|
+
private backoffMs;
|
|
17
|
+
private stopping;
|
|
18
|
+
private reconnecting;
|
|
19
|
+
private lastPong;
|
|
20
|
+
private seq;
|
|
21
|
+
private readonly workerUrl;
|
|
22
|
+
private readonly serverId;
|
|
23
|
+
private readonly token;
|
|
24
|
+
constructor(opts: ServerLinkOpts);
|
|
25
|
+
connect(): void;
|
|
26
|
+
send(msg: unknown): void;
|
|
27
|
+
/** Send a binary WebSocket frame (raw PTY data). Best-effort: no throw on disconnect. */
|
|
28
|
+
sendBinary(data: Buffer): void;
|
|
29
|
+
/** Send a timeline event to connected browsers via the server relay. */
|
|
30
|
+
sendTimelineEvent(event: TimelineEvent): void;
|
|
31
|
+
onMessage(handler: MessageHandler): void;
|
|
32
|
+
disconnect(): void;
|
|
33
|
+
private startHeartbeat;
|
|
34
|
+
private stopHeartbeat;
|
|
35
|
+
/** Watchdog: periodically verifies the connection is truly alive.
|
|
36
|
+
* If no message received within PONG_TIMEOUT after a heartbeat ping,
|
|
37
|
+
* the connection is considered dead and forcibly recycled. */
|
|
38
|
+
private startWatchdog;
|
|
39
|
+
private stopWatchdog;
|
|
40
|
+
/** Kill current connection and force immediate reconnect */
|
|
41
|
+
private forceReconnect;
|
|
42
|
+
private scheduleReconnect;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=server-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-link.d.ts","sourceRoot":"","sources":["../../src/daemon/server-link.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAqBzD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAEpD,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,cAAc,CAAC,CAAiC;IACxD,OAAO,CAAC,UAAU,CAAC,CAAiC;IACpD,OAAO,CAAC,cAAc,CAAC,CAAgC;IACvD,OAAO,CAAC,aAAa,CAAC,CAAiC;IACvD,OAAO,CAAC,SAAS,CAAC,CAAgC;IAClD,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,EAAE,cAAc;IAMhC,OAAO,IAAI,IAAI;IA2Df,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAQxB,yFAAyF;IACzF,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK9B,wEAAwE;IACxE,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAQ7C,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAIxC,UAAU,IAAI,IAAI;IAUlB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,aAAa;IAKrB;;mEAE+D;IAC/D,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,YAAY;IAIpB,4DAA4D;IAC5D,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,iBAAiB;CAW1B"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ServerLink = void 0;
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
8
|
+
const logger_js_1 = __importDefault(require("../util/logger.js"));
|
|
9
|
+
/** Collect lightweight system stats for daemon.stats messages. */
|
|
10
|
+
function collectSystemStats() {
|
|
11
|
+
const memTotal = node_os_1.default.totalmem();
|
|
12
|
+
const memFree = node_os_1.default.freemem();
|
|
13
|
+
const [load1, load5, load15] = node_os_1.default.loadavg();
|
|
14
|
+
// CPU usage: approximate from load average vs CPU count
|
|
15
|
+
const cpuCount = node_os_1.default.cpus().length;
|
|
16
|
+
const cpu = Math.min(100, Math.round((load1 / cpuCount) * 100));
|
|
17
|
+
return { cpu, memUsed: memTotal - memFree, memTotal, load1: +load1.toFixed(2), load5: +load5.toFixed(2), load15: +load15.toFixed(2), uptime: node_os_1.default.uptime() };
|
|
18
|
+
}
|
|
19
|
+
const HEARTBEAT_MS = 30_000;
|
|
20
|
+
const STATS_MS = 5_000; // daemon.stats update interval (separate from heartbeat)
|
|
21
|
+
const INITIAL_BACKOFF_MS = 1_000;
|
|
22
|
+
const MAX_BACKOFF_MS = 60_000;
|
|
23
|
+
const WATCHDOG_MS = 15_000; // check connection health every 15s
|
|
24
|
+
const PONG_TIMEOUT_MS = 10_000; // if no pong within 10s, connection is dead
|
|
25
|
+
class ServerLink {
|
|
26
|
+
ws = null;
|
|
27
|
+
handlers = [];
|
|
28
|
+
heartbeatTimer;
|
|
29
|
+
statsTimer;
|
|
30
|
+
reconnectTimer;
|
|
31
|
+
watchdogTimer;
|
|
32
|
+
pongTimer;
|
|
33
|
+
backoffMs = INITIAL_BACKOFF_MS;
|
|
34
|
+
stopping = false;
|
|
35
|
+
reconnecting = false;
|
|
36
|
+
lastPong = 0; // timestamp of last received message (any message counts as proof of life)
|
|
37
|
+
seq = 0;
|
|
38
|
+
workerUrl;
|
|
39
|
+
serverId;
|
|
40
|
+
token;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
this.workerUrl = opts.workerUrl;
|
|
43
|
+
this.serverId = opts.serverId;
|
|
44
|
+
this.token = opts.token;
|
|
45
|
+
}
|
|
46
|
+
connect() {
|
|
47
|
+
// Clean up previous connection if any
|
|
48
|
+
this.stopHeartbeat();
|
|
49
|
+
this.stopWatchdog();
|
|
50
|
+
if (this.pongTimer) {
|
|
51
|
+
clearTimeout(this.pongTimer);
|
|
52
|
+
this.pongTimer = undefined;
|
|
53
|
+
}
|
|
54
|
+
const wsUrl = this.workerUrl.replace(/^http/, 'ws') + `/api/server/${this.serverId}/ws`;
|
|
55
|
+
logger_js_1.default.info({ url: wsUrl }, 'ServerLink: connecting');
|
|
56
|
+
this.reconnecting = false;
|
|
57
|
+
const ws = new WebSocket(wsUrl);
|
|
58
|
+
this.ws = ws;
|
|
59
|
+
ws.addEventListener('open', () => {
|
|
60
|
+
if (this.ws !== ws)
|
|
61
|
+
return; // replaced before open
|
|
62
|
+
logger_js_1.default.info('ServerLink: connected');
|
|
63
|
+
this.backoffMs = INITIAL_BACKOFF_MS;
|
|
64
|
+
this.lastPong = Date.now();
|
|
65
|
+
// Send auth handshake immediately — server closes the socket if this is not
|
|
66
|
+
// the first message or if credentials are invalid (5s timeout enforced server-side).
|
|
67
|
+
ws.send(JSON.stringify({ type: 'auth', serverId: this.serverId, token: this.token }));
|
|
68
|
+
this.startHeartbeat();
|
|
69
|
+
this.startWatchdog();
|
|
70
|
+
});
|
|
71
|
+
ws.addEventListener('error', (event) => {
|
|
72
|
+
if (this.ws !== ws)
|
|
73
|
+
return; // stale socket — a newer connection already took over
|
|
74
|
+
logger_js_1.default.warn({ error: event.message ?? 'unknown' }, 'ServerLink: error');
|
|
75
|
+
// Close event *should* fire after error, but in edge cases (non-101 response,
|
|
76
|
+
// DNS failure) it may not. Schedule reconnect as a safety net — scheduleReconnect()
|
|
77
|
+
// is idempotent (guards with `this.reconnecting`), so no double-reconnect risk
|
|
78
|
+
// when close does fire.
|
|
79
|
+
if (!this.stopping)
|
|
80
|
+
this.scheduleReconnect();
|
|
81
|
+
});
|
|
82
|
+
ws.addEventListener('message', (event) => {
|
|
83
|
+
if (this.ws !== ws)
|
|
84
|
+
return; // stale socket
|
|
85
|
+
this.lastPong = Date.now();
|
|
86
|
+
const raw = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
87
|
+
try {
|
|
88
|
+
const msg = JSON.parse(raw);
|
|
89
|
+
for (const h of this.handlers)
|
|
90
|
+
h(msg);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// ignore parse errors
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
ws.addEventListener('close', (event) => {
|
|
97
|
+
// If this.ws has already been replaced by a newer socket (e.g. because we called
|
|
98
|
+
// connect() again while this socket was still in-flight), the server will close
|
|
99
|
+
// this one with 1001 "replaced" — that's expected and we must NOT reconnect,
|
|
100
|
+
// otherwise the newer connection gets kicked and we loop forever.
|
|
101
|
+
if (this.ws !== ws)
|
|
102
|
+
return;
|
|
103
|
+
logger_js_1.default.info({ code: event.code, reason: event.reason }, 'ServerLink: closed');
|
|
104
|
+
this.stopHeartbeat();
|
|
105
|
+
this.stopWatchdog();
|
|
106
|
+
if (!this.stopping)
|
|
107
|
+
this.scheduleReconnect();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
send(msg) {
|
|
111
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
112
|
+
throw new Error('ServerLink: not connected');
|
|
113
|
+
}
|
|
114
|
+
this.seq++;
|
|
115
|
+
this.ws.send(JSON.stringify({ ...(msg ?? {}), seq: this.seq }));
|
|
116
|
+
}
|
|
117
|
+
/** Send a binary WebSocket frame (raw PTY data). Best-effort: no throw on disconnect. */
|
|
118
|
+
sendBinary(data) {
|
|
119
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
120
|
+
return;
|
|
121
|
+
this.ws.send(data);
|
|
122
|
+
}
|
|
123
|
+
/** Send a timeline event to connected browsers via the server relay. */
|
|
124
|
+
sendTimelineEvent(event) {
|
|
125
|
+
try {
|
|
126
|
+
this.send({ type: 'timeline.event', event });
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Not connected — timeline events are best-effort
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
onMessage(handler) {
|
|
133
|
+
this.handlers.push(handler);
|
|
134
|
+
}
|
|
135
|
+
disconnect() {
|
|
136
|
+
this.stopping = true;
|
|
137
|
+
this.stopHeartbeat();
|
|
138
|
+
this.stopWatchdog();
|
|
139
|
+
if (this.pongTimer)
|
|
140
|
+
clearTimeout(this.pongTimer);
|
|
141
|
+
if (this.reconnectTimer)
|
|
142
|
+
clearTimeout(this.reconnectTimer);
|
|
143
|
+
this.ws?.close();
|
|
144
|
+
this.ws = null;
|
|
145
|
+
}
|
|
146
|
+
startHeartbeat() {
|
|
147
|
+
this.heartbeatTimer = setInterval(() => {
|
|
148
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
149
|
+
this.send({ type: 'heartbeat', ...collectSystemStats() });
|
|
150
|
+
}
|
|
151
|
+
}, HEARTBEAT_MS);
|
|
152
|
+
// Stats updates more frequently than heartbeat
|
|
153
|
+
this.statsTimer = setInterval(() => {
|
|
154
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
155
|
+
this.send({ type: 'daemon.stats', ...collectSystemStats() });
|
|
156
|
+
}
|
|
157
|
+
}, STATS_MS);
|
|
158
|
+
}
|
|
159
|
+
stopHeartbeat() {
|
|
160
|
+
if (this.heartbeatTimer) {
|
|
161
|
+
clearInterval(this.heartbeatTimer);
|
|
162
|
+
this.heartbeatTimer = undefined;
|
|
163
|
+
}
|
|
164
|
+
if (this.statsTimer) {
|
|
165
|
+
clearInterval(this.statsTimer);
|
|
166
|
+
this.statsTimer = undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Watchdog: periodically verifies the connection is truly alive.
|
|
170
|
+
* If no message received within PONG_TIMEOUT after a heartbeat ping,
|
|
171
|
+
* the connection is considered dead and forcibly recycled. */
|
|
172
|
+
startWatchdog() {
|
|
173
|
+
this.watchdogTimer = setInterval(() => {
|
|
174
|
+
if (this.stopping)
|
|
175
|
+
return;
|
|
176
|
+
const ws = this.ws;
|
|
177
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
178
|
+
// Not connected — force reconnect if not already scheduled
|
|
179
|
+
logger_js_1.default.warn('ServerLink watchdog: not connected, forcing reconnect');
|
|
180
|
+
this.forceReconnect();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const silenceMs = Date.now() - this.lastPong;
|
|
184
|
+
if (silenceMs > HEARTBEAT_MS + PONG_TIMEOUT_MS) {
|
|
185
|
+
// Haven't received anything for heartbeat interval + timeout — dead connection
|
|
186
|
+
logger_js_1.default.warn({ silenceMs }, 'ServerLink watchdog: connection silent, forcing reconnect');
|
|
187
|
+
this.forceReconnect();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}, WATCHDOG_MS);
|
|
191
|
+
}
|
|
192
|
+
stopWatchdog() {
|
|
193
|
+
if (this.watchdogTimer) {
|
|
194
|
+
clearInterval(this.watchdogTimer);
|
|
195
|
+
this.watchdogTimer = undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Kill current connection and force immediate reconnect */
|
|
199
|
+
forceReconnect() {
|
|
200
|
+
this.stopHeartbeat();
|
|
201
|
+
this.stopWatchdog();
|
|
202
|
+
if (this.reconnectTimer) {
|
|
203
|
+
clearTimeout(this.reconnectTimer);
|
|
204
|
+
this.reconnectTimer = undefined;
|
|
205
|
+
}
|
|
206
|
+
this.reconnecting = false;
|
|
207
|
+
// Force-close existing socket (will trigger close event, but we handle reconnect ourselves)
|
|
208
|
+
try {
|
|
209
|
+
this.ws?.close();
|
|
210
|
+
}
|
|
211
|
+
catch { /* ignore */ }
|
|
212
|
+
this.ws = null;
|
|
213
|
+
// Reset backoff for forced reconnects — we want to come back fast
|
|
214
|
+
this.backoffMs = INITIAL_BACKOFF_MS;
|
|
215
|
+
this.scheduleReconnect();
|
|
216
|
+
}
|
|
217
|
+
scheduleReconnect() {
|
|
218
|
+
// Prevent double scheduling from error+close firing in sequence
|
|
219
|
+
if (this.reconnecting)
|
|
220
|
+
return;
|
|
221
|
+
this.reconnecting = true;
|
|
222
|
+
logger_js_1.default.info({ backoffMs: this.backoffMs }, 'ServerLink: scheduling reconnect');
|
|
223
|
+
if (this.reconnectTimer)
|
|
224
|
+
clearTimeout(this.reconnectTimer);
|
|
225
|
+
this.reconnectTimer = setTimeout(() => {
|
|
226
|
+
this.connect();
|
|
227
|
+
this.backoffMs = Math.min(this.backoffMs * 2, MAX_BACKOFF_MS);
|
|
228
|
+
}, this.backoffMs);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.ServerLink = ServerLink;
|
|
232
|
+
//# sourceMappingURL=server-link.js.map
|