@winmatrix/daemon 1.0.4 → 1.0.6

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.
Files changed (46) hide show
  1. package/bin/winmatrix-daemon.js +3 -1
  2. package/dist/core/AgentProcessManager.d.ts +111 -0
  3. package/dist/core/AgentProcessManager.d.ts.map +1 -0
  4. package/dist/core/AgentProcessManager.js +702 -0
  5. package/dist/core/AgentProcessManager.js.map +1 -0
  6. package/dist/core/ConfigLoader.d.ts +110 -0
  7. package/dist/core/ConfigLoader.d.ts.map +1 -0
  8. package/dist/core/ConfigLoader.js +296 -0
  9. package/dist/core/ConfigLoader.js.map +1 -0
  10. package/dist/core/DaemonClient.d.ts +102 -0
  11. package/dist/core/DaemonClient.d.ts.map +1 -0
  12. package/dist/core/DaemonClient.js +225 -0
  13. package/dist/core/DaemonClient.js.map +1 -0
  14. package/dist/core/DaemonFileLogger.d.ts +34 -0
  15. package/dist/core/DaemonFileLogger.d.ts.map +1 -0
  16. package/dist/core/DaemonFileLogger.js +192 -0
  17. package/dist/core/DaemonFileLogger.js.map +1 -0
  18. package/dist/core/DaemonLifecycle.d.ts +22 -0
  19. package/dist/core/DaemonLifecycle.d.ts.map +1 -0
  20. package/dist/core/DaemonLifecycle.js +157 -0
  21. package/dist/core/DaemonLifecycle.js.map +1 -0
  22. package/dist/core/DiagnosticsServer.d.ts +28 -0
  23. package/dist/core/DiagnosticsServer.d.ts.map +1 -0
  24. package/dist/core/DiagnosticsServer.js +155 -0
  25. package/dist/core/DiagnosticsServer.js.map +1 -0
  26. package/dist/core/InstallationId.d.ts +14 -0
  27. package/dist/core/InstallationId.d.ts.map +1 -0
  28. package/dist/core/InstallationId.js +50 -0
  29. package/dist/core/InstallationId.js.map +1 -0
  30. package/dist/core/RuntimeDoctor.d.ts +13 -0
  31. package/dist/core/RuntimeDoctor.d.ts.map +1 -0
  32. package/dist/core/RuntimeDoctor.js +86 -0
  33. package/dist/core/RuntimeDoctor.js.map +1 -0
  34. package/dist/core/WinMatrixAgentClient.d.ts +199 -0
  35. package/dist/core/WinMatrixAgentClient.d.ts.map +1 -0
  36. package/dist/core/WinMatrixAgentClient.js +569 -0
  37. package/dist/core/WinMatrixAgentClient.js.map +1 -0
  38. package/dist/core/WorkspaceScanner.d.ts +45 -0
  39. package/dist/core/WorkspaceScanner.d.ts.map +1 -0
  40. package/dist/core/WorkspaceScanner.js +170 -0
  41. package/dist/core/WorkspaceScanner.js.map +1 -0
  42. package/dist/index.d.ts +16 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +264 -0
  45. package/dist/index.js.map +1 -0
  46. package/package.json +3 -3
@@ -0,0 +1,225 @@
1
+ /**
2
+ * WinMatrix Agent SDK - DaemonClient
3
+ *
4
+ * Extends WinMatrixAgentClient for the Computer/Daemon connection model.
5
+ * After WebSocket connection, sends `computer.register` (instead of `register`)
6
+ * with the computer's identity, host info, and detected runtimes.
7
+ *
8
+ * The Daemon connection acts as a multiplexing channel:
9
+ * - computer.heartbeat events keep the Computer alive
10
+ * - agent.create / agent.stop events manage Agent lifecycle on the host
11
+ * - task.* messages are routed by agentId for individual agents
12
+ */
13
+ import { METHODS, EVENTS, parseFrame, isEvent, isRequest, isResponse, } from '@winmatrix/protocol';
14
+ import { WinMatrixAgentClient } from './WinMatrixAgentClient.js';
15
+ /* ── DaemonClient ── */
16
+ export class DaemonClient extends WinMatrixAgentClient {
17
+ daemonOptions;
18
+ serverComputerId = null;
19
+ agentCreateHandler = null;
20
+ agentStopHandler = null;
21
+ profileCollector = null;
22
+ /** 心跳执行锁,防止定时器回调并发 */
23
+ heartbeatInProgress = false;
24
+ constructor(options) {
25
+ super({
26
+ url: options.url,
27
+ apiKey: options.token,
28
+ agentId: `computer:${options.installationId}`,
29
+ name: options.hostname,
30
+ agentType: 'daemon',
31
+ capabilities: { streaming: false, toolCall: false, cancellation: false },
32
+ autoReconnect: options.autoReconnect,
33
+ bridgeHooks: options.bridgeHooks,
34
+ });
35
+ this.daemonOptions = options;
36
+ }
37
+ /** Register handler for daemon-specific events */
38
+ onDaemonEvent(event, handler) {
39
+ if (event === 'agent.create') {
40
+ this.agentCreateHandler = handler;
41
+ }
42
+ else if (event === 'agent.stop') {
43
+ this.agentStopHandler = handler;
44
+ }
45
+ }
46
+ /** Get the server-assigned computer ID (available after successful registration) */
47
+ getComputerId() {
48
+ return this.serverComputerId;
49
+ }
50
+ /** Set the profile collector function (called during heartbeat to attach agentProfiles) */
51
+ setProfileCollector(fn) {
52
+ this.profileCollector = fn;
53
+ }
54
+ /**
55
+ * 通过 daemon 连接发送 agent.error 事件。
56
+ * 替代直接访问私有 ws 属性,用于 agent.create 失败时通知服务端。
57
+ */
58
+ sendAgentError(agentId, error) {
59
+ if (this.getState() !== 'registered')
60
+ return false;
61
+ try {
62
+ this.sendEvent(EVENTS.AGENT_ERROR, { agentId, error });
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ /** Immediately send a heartbeat with agent profiles (used for agent create/destroy/rescan) */
70
+ async sendHeartbeatNow() {
71
+ if (this.getState() !== 'registered')
72
+ return;
73
+ try {
74
+ const agentProfiles = this.profileCollector ? await this.profileCollector() : undefined;
75
+ this.sendEvent(EVENTS.COMPUTER_HEARTBEAT, agentProfiles ? { agentProfiles } : undefined);
76
+ }
77
+ catch {
78
+ // best-effort
79
+ }
80
+ }
81
+ /* ── Protected overrides ── */
82
+ /**
83
+ * Override register() to send computer.register instead of agent register.
84
+ */
85
+ async register() {
86
+ const params = {
87
+ installationId: this.daemonOptions.installationId,
88
+ token: this.daemonOptions.token,
89
+ hostname: this.daemonOptions.hostname,
90
+ os: this.daemonOptions.hostOs,
91
+ arch: this.daemonOptions.hostArch,
92
+ daemonVersion: this.daemonOptions.daemonVersion,
93
+ runtimes: this.daemonOptions.runtimes,
94
+ };
95
+ if (this.daemonOptions.supportedAgentTypes?.length) {
96
+ params.supportedAgentTypes = this.daemonOptions.supportedAgentTypes;
97
+ }
98
+ const response = await this.sendRequest(METHODS.COMPUTER_REGISTER, params);
99
+ if (!response.ok) {
100
+ throw new Error(`Computer registration failed: ${response.error ?? 'unknown error'}`);
101
+ }
102
+ const payload = response.payload;
103
+ if (payload?.computerId) {
104
+ this.serverComputerId = payload.computerId;
105
+ }
106
+ }
107
+ /**
108
+ * Override heartbeat for daemon mode — sends computer.heartbeat events with optional agentProfiles.
109
+ * 使用锁防止定时器回调并发执行,确保上次心跳完成后再调度下次。
110
+ */
111
+ startHeartbeat() {
112
+ const interval = 30000;
113
+ this.heartbeatTimer = setInterval(async () => {
114
+ // 如果上次心跳还在执行,跳过本次
115
+ if (this.heartbeatInProgress)
116
+ return;
117
+ if (this.getState() !== 'registered')
118
+ return;
119
+ this.heartbeatInProgress = true;
120
+ try {
121
+ const agentProfiles = this.profileCollector ? await this.profileCollector() : undefined;
122
+ this.sendEvent(EVENTS.COMPUTER_HEARTBEAT, agentProfiles ? { agentProfiles } : undefined);
123
+ }
124
+ catch {
125
+ // Ignore transient send failures
126
+ }
127
+ finally {
128
+ this.heartbeatInProgress = false;
129
+ }
130
+ }, interval);
131
+ }
132
+ /**
133
+ * Override to extract agentId before parseFrame and intercept daemon events.
134
+ *
135
+ * 注意:方案 B 下 onPassthrough 未设置,passthrough 路由代码(task.assign ACK / wrapper 响应)
136
+ * 当前为 dead code。每个 agent 有独立 WinMatrixAgentClient + WebSocket,消息由
137
+ * AgentProcessManager 分发到各 agent 的 client,不经过 daemon 连接多路复用。
138
+ */
139
+ handleMessage(data) {
140
+ const raw = typeof data === 'string' ? data : data.toString('utf-8');
141
+ // Extract top-level agentId before parseFrame (which drops non-standard fields)
142
+ let agentId = null;
143
+ try {
144
+ const parsed = JSON.parse(raw);
145
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
146
+ if (typeof parsed.agentId === 'string' && parsed.agentId.length > 0) {
147
+ agentId = parsed.agentId;
148
+ }
149
+ }
150
+ }
151
+ catch {
152
+ return;
153
+ }
154
+ // Parse the frame for routing
155
+ const frame = parseFrame(raw);
156
+ if (!frame)
157
+ return;
158
+ // Intercept daemon events BEFORE parent handling
159
+ if (isEvent(frame)) {
160
+ const eventFrame = frame;
161
+ if (this.handleDaemonEvent(eventFrame.event, eventFrame.payload)) {
162
+ return;
163
+ }
164
+ }
165
+ // Pass through agent-level messages to daemon for wrapper forwarding
166
+ if (this.daemonOptions.onPassthrough) {
167
+ if (isRequest(frame)) {
168
+ const reqFrame = frame;
169
+ if (reqFrame.method === METHODS.TASK_ASSIGN) {
170
+ // Ack receipt, then pass through
171
+ this.sendRaw({ type: 'res', id: reqFrame.id, ok: true });
172
+ this.daemonOptions.onPassthrough(frame, raw, agentId);
173
+ return;
174
+ }
175
+ // Other unrecognized requests → pass through
176
+ this.daemonOptions.onPassthrough(frame, raw, agentId);
177
+ return;
178
+ }
179
+ if (isEvent(frame)) {
180
+ const evtFrame = frame;
181
+ // Pass through task/tool events for agent-level routing
182
+ if (evtFrame.event.startsWith('task.') || evtFrame.event.startsWith('tool.')) {
183
+ this.daemonOptions.onPassthrough(frame, raw, agentId);
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ // Pass through response frames for forwarded wrapper requests
189
+ // (IDs starting with 'wrapper-'). Daemon-originated responses (IDs
190
+ // starting with 'sdk-') must be handled by the parent's pendingRequests.
191
+ if (isResponse(frame) && this.daemonOptions.onPassthrough) {
192
+ const resFrame = frame;
193
+ if (typeof resFrame.id === 'string' && resFrame.id.startsWith('wrapper-')) {
194
+ this.daemonOptions.onPassthrough(frame, raw, agentId);
195
+ return;
196
+ }
197
+ }
198
+ // Delegate to parent for standard message handling (responses, ping, etc.)
199
+ super.handleMessage(data);
200
+ }
201
+ /**
202
+ * Handle daemon-specific events. Returns true if the event was handled.
203
+ */
204
+ handleDaemonEvent(event, payload) {
205
+ switch (event) {
206
+ case EVENTS.AGENT_CREATE: {
207
+ const createPayload = payload;
208
+ if (createPayload?.agentId && (createPayload?.runtime || createPayload?.agentType === 'hermes' || createPayload?.agentType === 'openclaw')) {
209
+ this.agentCreateHandler?.(createPayload);
210
+ }
211
+ return true;
212
+ }
213
+ case EVENTS.AGENT_STOP: {
214
+ const stopPayload = payload;
215
+ if (stopPayload?.agentId) {
216
+ this.agentStopHandler?.(stopPayload);
217
+ }
218
+ return true;
219
+ }
220
+ default:
221
+ return false;
222
+ }
223
+ }
224
+ }
225
+ //# sourceMappingURL=DaemonClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DaemonClient.js","sourceRoot":"","sources":["../../src/core/DaemonClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 将 daemon 运行日志写入 ~/.winmatrix/logs/daemon-YYYY-MM-DD.log,
3
+ * 并清理超过保留期的历史文件(默认 3 天)。
4
+ */
5
+ /** 内存中保留供 /api/logs 读取的最近行数 */
6
+ export declare const DEFAULT_LOG_RING_MAX = 800;
7
+ export declare function getLogsDirectory(): string;
8
+ /** 日志行首时间戳:本机本地墙钟,非 UTC(避免 …Z) */
9
+ export declare function formatDaemonLocalTimestamp(d?: Date): string;
10
+ /**
11
+ * 删除日志目录下超过 retentionMs 的 daemon-*.log(按文件名日期;无法解析则按 mtime)。
12
+ */
13
+ export declare function pruneOldDaemonLogs(retentionMs?: number): void;
14
+ export declare class DaemonFileLogger {
15
+ private currentDay;
16
+ private readonly ring;
17
+ private readonly ringMax;
18
+ constructor(ringMax?: number);
19
+ /** 定时清理(例如每 6 小时) */
20
+ startRetentionTimer(intervalMs?: number): ReturnType<typeof setInterval>;
21
+ private rollFileIfNeeded;
22
+ private currentFilePath;
23
+ /**
24
+ * 写入一行带时间戳的文本(无敏感信息过滤;调用方勿记录 token/apiKey 全文)。
25
+ */
26
+ writeLine(level: string, message: string): void;
27
+ getRecentLines(max: number): string[];
28
+ getLogDirectory(): string;
29
+ }
30
+ /**
31
+ * 把 console.log / warn / error 镜像到文件 logger(仍保留原控制台输出)。
32
+ */
33
+ export declare function mirrorConsoleToFile(logger: DaemonFileLogger): void;
34
+ //# sourceMappingURL=DaemonFileLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DaemonFileLogger.d.ts","sourceRoot":"","sources":["../../src/core/DaemonFileLogger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuBH,+BAA+B;AAC/B,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAQxC,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AASD,kCAAkC;AAClC,wBAAgB,0BAA0B,CAAC,CAAC,OAAa,GAAG,MAAM,CASjE;AA4BD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,GAAE,MAA6B,GAAG,IAAI,CAiBnF;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,GAAE,MAA6B;IAOlD,qBAAqB;IACrB,mBAAmB,CAAC,UAAU,GAAE,MAA2B,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC;IAO5F,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAS/C,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAKrC,eAAe,IAAI,MAAM;CAG1B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAoDlE"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * 将 daemon 运行日志写入 ~/.winmatrix/logs/daemon-YYYY-MM-DD.log,
3
+ * 并清理超过保留期的历史文件(默认 3 天)。
4
+ */
5
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, chmodSync, unlinkSync, statSync, } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import util from 'node:util';
9
+ const DAEMON_HOME_DIR = '.winmatrix';
10
+ const LOG_SUBDIR = 'logs';
11
+ const LOG_FILE_PREFIX = 'daemon-';
12
+ const LOG_FILE_SUFFIX = '.log';
13
+ /** 默认保留 3 天 */
14
+ const DEFAULT_RETENTION_MS = 3 * 24 * 60 * 60 * 1000;
15
+ /** 内存中保留供 /api/logs 读取的最近行数 */
16
+ export const DEFAULT_LOG_RING_MAX = 800;
17
+ let consoleStreamErrorGuardsInstalled = false;
18
+ function getDaemonHome() {
19
+ return path.join(homedir(), DAEMON_HOME_DIR);
20
+ }
21
+ export function getLogsDirectory() {
22
+ return path.join(getDaemonHome(), LOG_SUBDIR);
23
+ }
24
+ function localDateStamp(d = new Date()) {
25
+ const y = d.getFullYear();
26
+ const m = String(d.getMonth() + 1).padStart(2, '0');
27
+ const day = String(d.getDate()).padStart(2, '0');
28
+ return `${y}-${m}-${day}`;
29
+ }
30
+ /** 日志行首时间戳:本机本地墙钟,非 UTC(避免 …Z) */
31
+ export function formatDaemonLocalTimestamp(d = new Date()) {
32
+ const y = d.getFullYear();
33
+ const mo = String(d.getMonth() + 1).padStart(2, '0');
34
+ const day = String(d.getDate()).padStart(2, '0');
35
+ const h = String(d.getHours()).padStart(2, '0');
36
+ const min = String(d.getMinutes()).padStart(2, '0');
37
+ const sec = String(d.getSeconds()).padStart(2, '0');
38
+ const ms = String(d.getMilliseconds()).padStart(3, '0');
39
+ return `${y}-${mo}-${day} ${h}:${min}:${sec}.${ms}`;
40
+ }
41
+ function ensureLogsDir() {
42
+ const dir = getLogsDirectory();
43
+ const parent = getDaemonHome();
44
+ if (!existsSync(parent)) {
45
+ mkdirSync(parent, { recursive: true, mode: 0o700 });
46
+ chmodSync(parent, 0o700);
47
+ }
48
+ if (!existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
50
+ chmodSync(dir, 0o700);
51
+ }
52
+ }
53
+ const LOG_NAME_RE = /^daemon-(\d{4})-(\d{2})-(\d{2})\.log$/;
54
+ function parseLogDate(name) {
55
+ const m = LOG_NAME_RE.exec(name);
56
+ if (!m)
57
+ return null;
58
+ const y = Number(m[1]);
59
+ const mo = Number(m[2]) - 1;
60
+ const d = Number(m[3]);
61
+ const dt = new Date(y, mo, d);
62
+ if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d)
63
+ return null;
64
+ return dt;
65
+ }
66
+ /**
67
+ * 删除日志目录下超过 retentionMs 的 daemon-*.log(按文件名日期;无法解析则按 mtime)。
68
+ */
69
+ export function pruneOldDaemonLogs(retentionMs = DEFAULT_RETENTION_MS) {
70
+ const dir = getLogsDirectory();
71
+ if (!existsSync(dir))
72
+ return;
73
+ const cutoff = Date.now() - retentionMs;
74
+ for (const name of readdirSync(dir)) {
75
+ if (!name.startsWith(LOG_FILE_PREFIX) || !name.endsWith(LOG_FILE_SUFFIX))
76
+ continue;
77
+ const full = path.join(dir, name);
78
+ try {
79
+ const fromName = parseLogDate(name);
80
+ const t = fromName ? fromName.getTime() : statSync(full).mtimeMs;
81
+ if (t < cutoff) {
82
+ unlinkSync(full);
83
+ }
84
+ }
85
+ catch {
86
+ /* ignore per-file errors */
87
+ }
88
+ }
89
+ }
90
+ export class DaemonFileLogger {
91
+ currentDay;
92
+ ring = [];
93
+ ringMax;
94
+ constructor(ringMax = DEFAULT_LOG_RING_MAX) {
95
+ ensureLogsDir();
96
+ pruneOldDaemonLogs();
97
+ this.currentDay = localDateStamp();
98
+ this.ringMax = ringMax;
99
+ }
100
+ /** 定时清理(例如每 6 小时) */
101
+ startRetentionTimer(intervalMs = 6 * 60 * 60 * 1000) {
102
+ return setInterval(() => {
103
+ pruneOldDaemonLogs();
104
+ this.rollFileIfNeeded();
105
+ }, intervalMs);
106
+ }
107
+ rollFileIfNeeded() {
108
+ const today = localDateStamp();
109
+ if (today !== this.currentDay) {
110
+ this.currentDay = today;
111
+ }
112
+ }
113
+ currentFilePath() {
114
+ this.rollFileIfNeeded();
115
+ return path.join(getLogsDirectory(), `${LOG_FILE_PREFIX}${this.currentDay}${LOG_FILE_SUFFIX}`);
116
+ }
117
+ /**
118
+ * 写入一行带时间戳的文本(无敏感信息过滤;调用方勿记录 token/apiKey 全文)。
119
+ */
120
+ writeLine(level, message) {
121
+ const line = `${formatDaemonLocalTimestamp()} [${level}] ${message}\n`;
122
+ appendFileSync(this.currentFilePath(), line, { encoding: 'utf-8' });
123
+ this.ring.push(line.replace(/\n$/, ''));
124
+ if (this.ring.length > this.ringMax) {
125
+ this.ring.splice(0, this.ring.length - this.ringMax);
126
+ }
127
+ }
128
+ getRecentLines(max) {
129
+ if (max <= 0)
130
+ return [];
131
+ return this.ring.slice(-max);
132
+ }
133
+ getLogDirectory() {
134
+ return getLogsDirectory();
135
+ }
136
+ }
137
+ /**
138
+ * 把 console.log / warn / error 镜像到文件 logger(仍保留原控制台输出)。
139
+ */
140
+ export function mirrorConsoleToFile(logger) {
141
+ if (!consoleStreamErrorGuardsInstalled) {
142
+ process.stdout.on('error', (err) => {
143
+ if (err.code !== 'EPIPE') {
144
+ throw err;
145
+ }
146
+ });
147
+ process.stderr.on('error', (err) => {
148
+ if (err.code !== 'EPIPE') {
149
+ throw err;
150
+ }
151
+ });
152
+ consoleStreamErrorGuardsInstalled = true;
153
+ }
154
+ const origLog = console.log.bind(console);
155
+ const origWarn = console.warn.bind(console);
156
+ const origError = console.error.bind(console);
157
+ const fmt = (args) => args.map((a) => (typeof a === 'string' ? a : util.inspect(a, { depth: 6, breakLength: 120 }))).join(' ');
158
+ console.log = (...args) => {
159
+ logger.writeLine('log', fmt(args));
160
+ try {
161
+ origLog(...args);
162
+ }
163
+ catch (err) {
164
+ if (err.code !== 'EPIPE') {
165
+ throw err;
166
+ }
167
+ }
168
+ };
169
+ console.warn = (...args) => {
170
+ logger.writeLine('warn', fmt(args));
171
+ try {
172
+ origWarn(...args);
173
+ }
174
+ catch (err) {
175
+ if (err.code !== 'EPIPE') {
176
+ throw err;
177
+ }
178
+ }
179
+ };
180
+ console.error = (...args) => {
181
+ logger.writeLine('error', fmt(args));
182
+ try {
183
+ origError(...args);
184
+ }
185
+ catch (err) {
186
+ if (err.code !== 'EPIPE') {
187
+ throw err;
188
+ }
189
+ }
190
+ };
191
+ }
192
+ //# sourceMappingURL=DaemonFileLogger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DaemonFileLogger.js","sourceRoot":"","sources":["../../src/core/DaemonFileLogger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Daemon 运维:首次目录/init、按 PID 平滑重启。
3
+ */
4
+ /**
5
+ * 守护进程主循环开始时写入(供 restart 查找)。
6
+ */
7
+ export declare function writeDaemonPidFile(): void;
8
+ /**
9
+ * 退出时删除 PID 文件。
10
+ */
11
+ export declare function clearDaemonPidFile(): void;
12
+ /**
13
+ * 创建 ~/.winmatrix、installation、logs;写入或合并 daemon.json(serverUrl / computerToken)。
14
+ * 可选:`winmatrix-daemon setup [--server <ws-url>] [--token <wmc_...>]`
15
+ */
16
+ export declare function runSetup(setupArgv: string[]): void;
17
+ /**
18
+ * 向旧进程发 SIGTERM,再后台拉起新进程(参数为 restart 之后的 argv)。
19
+ */
20
+ export declare function runRestart(forwardArgv: string[]): Promise<void>;
21
+ export declare function printCliHelp(daemonVersion: string): void;
22
+ //# sourceMappingURL=DaemonLifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DaemonLifecycle.d.ts","sourceRoot":"","sources":["../../src/core/DaemonLifecycle.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsCH;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAWzC;AAqBD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAsBlD;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCrE;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CA2BxD"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Daemon 运维:首次目录/init、按 PID 平滑重启。
3
+ */
4
+ import { spawn } from 'node:child_process';
5
+ import { existsSync, writeFileSync, readFileSync, unlinkSync, chmodSync, mkdirSync, } from 'node:fs';
6
+ import path from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { getOrCreateInstallationId } from './InstallationId.js';
10
+ import { DaemonFileLogger, pruneOldDaemonLogs } from './DaemonFileLogger.js';
11
+ import { DEFAULT_DAEMON_PUBLIC_BASE_URL, DEFAULT_DAEMON_SERVER_URL, getDaemonConfigFilePath, mergeDaemonSetupConfigToDisk, normalizeDaemonServerUrl, parseCliArgs, loadDaemonJson, } from './ConfigLoader.js';
12
+ const DAEMON_HOME = path.join(homedir(), '.winmatrix');
13
+ const PID_FILENAME = 'daemon.pid';
14
+ function getPidFilePath() {
15
+ return path.join(DAEMON_HOME, PID_FILENAME);
16
+ }
17
+ function isProbablyOurDaemonPid(pid) {
18
+ return Number.isInteger(pid) && pid > 0;
19
+ }
20
+ /**
21
+ * 守护进程主循环开始时写入(供 restart 查找)。
22
+ */
23
+ export function writeDaemonPidFile() {
24
+ if (!existsSync(DAEMON_HOME)) {
25
+ mkdirSync(DAEMON_HOME, { recursive: true, mode: 0o700 });
26
+ chmodSync(DAEMON_HOME, 0o700);
27
+ }
28
+ writeFileSync(getPidFilePath(), String(process.pid), { encoding: 'utf-8', mode: 0o600 });
29
+ chmodSync(getPidFilePath(), 0o600);
30
+ }
31
+ /**
32
+ * 退出时删除 PID 文件。
33
+ */
34
+ export function clearDaemonPidFile() {
35
+ try {
36
+ if (existsSync(getPidFilePath())) {
37
+ const raw = readFileSync(getPidFilePath(), 'utf-8').trim();
38
+ if (raw === String(process.pid)) {
39
+ unlinkSync(getPidFilePath());
40
+ }
41
+ }
42
+ }
43
+ catch {
44
+ /* ignore */
45
+ }
46
+ }
47
+ function readExistingPid() {
48
+ try {
49
+ if (!existsSync(getPidFilePath()))
50
+ return null;
51
+ const n = Number.parseInt(readFileSync(getPidFilePath(), 'utf-8').trim(), 10);
52
+ return isProbablyOurDaemonPid(n) ? n : null;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function sleep(ms) {
59
+ return new Promise((resolve) => setTimeout(resolve, ms));
60
+ }
61
+ function resolveEntryScriptPath() {
62
+ const here = path.dirname(fileURLToPath(import.meta.url));
63
+ return path.join(here, '..', 'index.js');
64
+ }
65
+ /**
66
+ * 创建 ~/.winmatrix、installation、logs;写入或合并 daemon.json(serverUrl / computerToken)。
67
+ * 可选:`winmatrix-daemon setup [--server <ws-url>] [--token <wmc_...>]`
68
+ */
69
+ export function runSetup(setupArgv) {
70
+ getOrCreateInstallationId();
71
+ new DaemonFileLogger();
72
+ pruneOldDaemonLogs();
73
+ const args = parseCliArgs(setupArgv);
74
+ const file = loadDaemonJson();
75
+ const serverUrl = normalizeDaemonServerUrl(args.server ?? file.serverUrl ?? DEFAULT_DAEMON_SERVER_URL);
76
+ const tokenArg = typeof args.token === 'string' ? args.token.trim() : '';
77
+ mergeDaemonSetupConfigToDisk({ serverUrl, computerToken: tokenArg });
78
+ const cfgPath = getDaemonConfigFilePath();
79
+ console.log(`[daemon] setup: wrote ${cfgPath}`);
80
+ console.log('[daemon] setup: done.');
81
+ if (tokenArg.length === 0) {
82
+ console.log('[daemon] Tip: add Computer token with winmatrix-daemon setup --token wmc_... or edit computerToken in daemon.json');
83
+ }
84
+ console.log('[daemon] Then: winmatrix-daemon or winmatrix-daemon restart');
85
+ console.log('[daemon] Local UI: http://127.0.0.1:18767/ (daemon running)');
86
+ }
87
+ /**
88
+ * 向旧进程发 SIGTERM,再后台拉起新进程(参数为 restart 之后的 argv)。
89
+ */
90
+ export async function runRestart(forwardArgv) {
91
+ const oldPid = readExistingPid();
92
+ if (oldPid !== null) {
93
+ try {
94
+ process.kill(oldPid, 'SIGTERM');
95
+ console.log(`[daemon] restart: sent SIGTERM to pid ${oldPid}`);
96
+ for (let i = 0; i < 50; i++) {
97
+ try {
98
+ process.kill(oldPid, 0);
99
+ }
100
+ catch {
101
+ break;
102
+ }
103
+ await sleep(100);
104
+ }
105
+ }
106
+ catch (e) {
107
+ const msg = e instanceof Error ? e.message : String(e);
108
+ console.log(`[daemon] restart: no process for pid ${oldPid} (${msg}), starting fresh`);
109
+ }
110
+ }
111
+ else {
112
+ console.log('[daemon] restart: no pid file (daemon was not running or first start)');
113
+ }
114
+ const entry = resolveEntryScriptPath();
115
+ if (!existsSync(entry)) {
116
+ console.error(`[daemon] restart: entry not found: ${entry}`);
117
+ console.error('[daemon] restart: run from built package (npm run build in clients/daemon)');
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+ const child = spawn(process.execPath, [entry, ...forwardArgv], {
122
+ detached: true,
123
+ stdio: 'ignore',
124
+ env: process.env,
125
+ });
126
+ child.unref();
127
+ console.log(`[daemon] restart: started new daemon (child pid ${child.pid ?? '?'})`);
128
+ }
129
+ export function printCliHelp(daemonVersion) {
130
+ console.log(`WinMatrix Daemon ${daemonVersion}`);
131
+ console.log(`
132
+ Usage:
133
+ winmatrix-daemon [options] Run daemon (foreground)
134
+ winmatrix-daemon setup [options] Create ~/.winmatrix + write/merge daemon.json
135
+ winmatrix-daemon restart [options] SIGTERM old daemon (from pid file), start again with options
136
+ winmatrix-daemon doctor claude Resolve, verify, and persist Claude Code runtime
137
+ winmatrix-daemon help This help
138
+
139
+ Options (setup / run / restart):
140
+ --server <server-url> Server base URL or External Agent WebSocket URL
141
+ --token <wmc_...> Computer registration token (overrides env + daemon.json)
142
+ --persist-token After resolving token (CLI or env), save serverUrl + computerToken to daemon.json
143
+
144
+ Environment:
145
+ WINMATRIX_TOKEN Computer token (wmc_...)
146
+ WINMATRIX_DAEMON_DIAG_* Local diagnostics HTTP (see DiagnosticsServer)
147
+
148
+ Config file:
149
+ ~/.winmatrix/daemon.json serverUrl, optional computerToken (read on each start; chmod 600)
150
+
151
+ Typical:
152
+ winmatrix-daemon setup --token wmc_...
153
+ winmatrix-daemon setup --token wmc_... --server ${DEFAULT_DAEMON_PUBLIC_BASE_URL}
154
+ winmatrix-daemon restart
155
+ `);
156
+ }
157
+ //# sourceMappingURL=DaemonLifecycle.js.map