@chrysb/alphaclaw 0.5.5 → 0.5.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.
- package/lib/public/js/components/sidebar.js +1 -13
- package/lib/server/gmail-serve.js +2 -12
- package/lib/server/onboarding/workspace.js +1 -0
- package/lib/server/startup.js +23 -0
- package/lib/server/watchdog.js +112 -26
- package/lib/server.js +19 -20
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ export const AppSidebar = ({
|
|
|
199
199
|
`,
|
|
200
200
|
)}
|
|
201
201
|
<div class="sidebar-footer">
|
|
202
|
-
${acHasUpdate && acLatest
|
|
202
|
+
${acHasUpdate && acLatest && selectedNavId === "general"
|
|
203
203
|
? html`
|
|
204
204
|
<${UpdateActionButton}
|
|
205
205
|
onClick=${onAcUpdate}
|
|
@@ -250,18 +250,6 @@ export const AppSidebar = ({
|
|
|
250
250
|
onSelectFile=${onSelectBrowseFile}
|
|
251
251
|
isActive=${sidebarTab === "browse"}
|
|
252
252
|
/>
|
|
253
|
-
${acHasUpdate && acLatest
|
|
254
|
-
? html`
|
|
255
|
-
<${UpdateActionButton}
|
|
256
|
-
onClick=${onAcUpdate}
|
|
257
|
-
loading=${acUpdating}
|
|
258
|
-
warning=${true}
|
|
259
|
-
idleLabel=${`Update to v${acLatest}`}
|
|
260
|
-
loadingLabel="Updating..."
|
|
261
|
-
className="w-full justify-center"
|
|
262
|
-
/>
|
|
263
|
-
`
|
|
264
|
-
: null}
|
|
265
253
|
</div>
|
|
266
254
|
</div>
|
|
267
255
|
</div>
|
|
@@ -124,18 +124,8 @@ const createGmailServeManager = ({
|
|
|
124
124
|
stdio: ["ignore", "pipe", "pipe"],
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
child.stdout.on("data", (
|
|
128
|
-
|
|
129
|
-
if (line) {
|
|
130
|
-
console.log(`[alphaclaw] gmail watch serve (${email}): ${line}`);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
child.stderr.on("data", (chunk) => {
|
|
134
|
-
const line = String(chunk || "").trim();
|
|
135
|
-
if (line) {
|
|
136
|
-
console.log(`[alphaclaw] gmail watch serve stderr (${email}): ${line}`);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
127
|
+
child.stdout.on("data", () => {});
|
|
128
|
+
child.stderr.on("data", () => {});
|
|
139
129
|
|
|
140
130
|
const nextEntry = {
|
|
141
131
|
accountId,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const runOnboardedBootSequence = ({
|
|
2
|
+
doSyncPromptFiles,
|
|
3
|
+
reloadEnv,
|
|
4
|
+
syncChannelConfig,
|
|
5
|
+
readEnvFile,
|
|
6
|
+
ensureGatewayProxyConfig,
|
|
7
|
+
resolveSetupUrl,
|
|
8
|
+
startGateway,
|
|
9
|
+
watchdog,
|
|
10
|
+
gmailWatchService,
|
|
11
|
+
}) => {
|
|
12
|
+
doSyncPromptFiles();
|
|
13
|
+
reloadEnv();
|
|
14
|
+
syncChannelConfig(readEnvFile());
|
|
15
|
+
ensureGatewayProxyConfig(resolveSetupUrl());
|
|
16
|
+
startGateway();
|
|
17
|
+
watchdog.start();
|
|
18
|
+
gmailWatchService.start();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
runOnboardedBootSequence,
|
|
23
|
+
};
|
package/lib/server/watchdog.js
CHANGED
|
@@ -12,10 +12,15 @@ const kBootstrapHealthCheckMs = 5 * 1000;
|
|
|
12
12
|
const kExpectedRestartWindowMs = 15 * 1000;
|
|
13
13
|
|
|
14
14
|
const isTruthy = (value) =>
|
|
15
|
-
["1", "true", "yes", "on"].includes(
|
|
15
|
+
["1", "true", "yes", "on"].includes(
|
|
16
|
+
String(value || "")
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase(),
|
|
19
|
+
);
|
|
16
20
|
|
|
17
21
|
const parseHealthResult = (result) => {
|
|
18
|
-
if (!result?.ok)
|
|
22
|
+
if (!result?.ok)
|
|
23
|
+
return { ok: false, reason: result?.stderr || "health command failed" };
|
|
19
24
|
const raw = String(result.stdout || "").trim();
|
|
20
25
|
if (!raw) return { ok: true };
|
|
21
26
|
try {
|
|
@@ -58,7 +63,9 @@ const createWatchdog = ({
|
|
|
58
63
|
repairAttempts: 0,
|
|
59
64
|
crashTimestamps: [],
|
|
60
65
|
autoRepair: isTruthy(process.env.WATCHDOG_AUTO_REPAIR),
|
|
61
|
-
notificationsDisabled: isTruthy(
|
|
66
|
+
notificationsDisabled: isTruthy(
|
|
67
|
+
process.env.WATCHDOG_NOTIFICATIONS_DISABLED,
|
|
68
|
+
),
|
|
62
69
|
operationInProgress: false,
|
|
63
70
|
gatewayStartedAt: null,
|
|
64
71
|
gatewayPid: null,
|
|
@@ -92,7 +99,8 @@ const createWatchdog = ({
|
|
|
92
99
|
scheduleDegradedHealthCheck();
|
|
93
100
|
}
|
|
94
101
|
}, kWatchdogDegradedCheckIntervalMs);
|
|
95
|
-
if (typeof degradedHealthTimer.unref === "function")
|
|
102
|
+
if (typeof degradedHealthTimer.unref === "function")
|
|
103
|
+
degradedHealthTimer.unref();
|
|
96
104
|
};
|
|
97
105
|
|
|
98
106
|
const clearExpectedRestartWindow = () => {
|
|
@@ -101,7 +109,10 @@ const createWatchdog = ({
|
|
|
101
109
|
};
|
|
102
110
|
|
|
103
111
|
const markExpectedRestartWindow = (durationMs = kExpectedRestartWindowMs) => {
|
|
104
|
-
const safeDuration = Math.max(
|
|
112
|
+
const safeDuration = Math.max(
|
|
113
|
+
5000,
|
|
114
|
+
Number(durationMs) || kExpectedRestartWindowMs,
|
|
115
|
+
);
|
|
105
116
|
state.expectedRestartInProgress = true;
|
|
106
117
|
state.expectedRestartUntilMs = Date.now() + safeDuration;
|
|
107
118
|
};
|
|
@@ -142,13 +153,21 @@ const createWatchdog = ({
|
|
|
142
153
|
|
|
143
154
|
const trimCrashWindow = () => {
|
|
144
155
|
const threshold = Date.now() - kWatchdogCrashLoopWindowMs;
|
|
145
|
-
state.crashTimestamps = state.crashTimestamps.filter(
|
|
156
|
+
state.crashTimestamps = state.crashTimestamps.filter(
|
|
157
|
+
(ts) => ts >= threshold,
|
|
158
|
+
);
|
|
146
159
|
};
|
|
147
160
|
|
|
148
161
|
const createCorrelationId = () =>
|
|
149
162
|
`${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
150
163
|
|
|
151
|
-
const logEvent = (
|
|
164
|
+
const logEvent = (
|
|
165
|
+
eventType,
|
|
166
|
+
source,
|
|
167
|
+
status,
|
|
168
|
+
details = null,
|
|
169
|
+
correlationId = "",
|
|
170
|
+
) => {
|
|
152
171
|
try {
|
|
153
172
|
insertWatchdogEvent({
|
|
154
173
|
eventType,
|
|
@@ -168,16 +187,25 @@ const createWatchdog = ({
|
|
|
168
187
|
}
|
|
169
188
|
if (!notifier?.notify) return { ok: false, reason: "notifier_unavailable" };
|
|
170
189
|
const result = await notifier.notify(message);
|
|
171
|
-
logEvent(
|
|
190
|
+
logEvent(
|
|
191
|
+
"notification",
|
|
192
|
+
"watchdog",
|
|
193
|
+
result.ok ? "ok" : "failed",
|
|
194
|
+
result,
|
|
195
|
+
correlationId,
|
|
196
|
+
);
|
|
172
197
|
return result;
|
|
173
198
|
};
|
|
174
199
|
|
|
175
200
|
const getWatchdogSetupUrl = () => {
|
|
176
201
|
try {
|
|
177
202
|
const base =
|
|
178
|
-
typeof resolveSetupUrl === "function"
|
|
203
|
+
typeof resolveSetupUrl === "function"
|
|
204
|
+
? String(resolveSetupUrl() || "")
|
|
205
|
+
: "";
|
|
179
206
|
if (base) return `${base.replace(/\/+$/, "")}/#/watchdog`;
|
|
180
|
-
const fallbackPort =
|
|
207
|
+
const fallbackPort =
|
|
208
|
+
Number.parseInt(String(process.env.PORT || "3000"), 10) || 3000;
|
|
181
209
|
return `http://localhost:${fallbackPort}/#/watchdog`;
|
|
182
210
|
} catch {
|
|
183
211
|
return "";
|
|
@@ -190,7 +218,8 @@ const createWatchdog = ({
|
|
|
190
218
|
return `${line} - [View logs](${setupUrl})`;
|
|
191
219
|
};
|
|
192
220
|
|
|
193
|
-
const asInlineCode = (value) =>
|
|
221
|
+
const asInlineCode = (value) =>
|
|
222
|
+
`\`${String(value || "").replace(/`/g, "")}\``;
|
|
194
223
|
|
|
195
224
|
const notifyAutoRepairOutcome = async ({
|
|
196
225
|
source,
|
|
@@ -225,11 +254,15 @@ const createWatchdog = ({
|
|
|
225
254
|
const hasAutoRepair = typeof autoRepair === "boolean";
|
|
226
255
|
const hasNotificationsEnabled = typeof notificationsEnabled === "boolean";
|
|
227
256
|
if (!hasAutoRepair && !hasNotificationsEnabled) {
|
|
228
|
-
throw new Error(
|
|
257
|
+
throw new Error(
|
|
258
|
+
"Expected autoRepair and/or notificationsEnabled boolean",
|
|
259
|
+
);
|
|
229
260
|
}
|
|
230
261
|
const envVars = readEnvFile();
|
|
231
262
|
if (hasAutoRepair) {
|
|
232
|
-
const existingIdx = envVars.findIndex(
|
|
263
|
+
const existingIdx = envVars.findIndex(
|
|
264
|
+
(item) => item.key === "WATCHDOG_AUTO_REPAIR",
|
|
265
|
+
);
|
|
233
266
|
const nextValue = autoRepair ? "true" : "false";
|
|
234
267
|
if (existingIdx >= 0) {
|
|
235
268
|
envVars[existingIdx] = { ...envVars[existingIdx], value: nextValue };
|
|
@@ -254,7 +287,9 @@ const createWatchdog = ({
|
|
|
254
287
|
writeEnvFile(envVars);
|
|
255
288
|
reloadEnv();
|
|
256
289
|
state.autoRepair = isTruthy(process.env.WATCHDOG_AUTO_REPAIR);
|
|
257
|
-
state.notificationsDisabled = isTruthy(
|
|
290
|
+
state.notificationsDisabled = isTruthy(
|
|
291
|
+
process.env.WATCHDOG_NOTIFICATIONS_DISABLED,
|
|
292
|
+
);
|
|
258
293
|
return getSettings();
|
|
259
294
|
};
|
|
260
295
|
|
|
@@ -277,7 +312,13 @@ const createWatchdog = ({
|
|
|
277
312
|
const child = launchGatewayProcess();
|
|
278
313
|
launchedGateway = !!child;
|
|
279
314
|
if (launchedGateway) {
|
|
280
|
-
logEvent(
|
|
315
|
+
logEvent(
|
|
316
|
+
"restart",
|
|
317
|
+
"repair",
|
|
318
|
+
"ok",
|
|
319
|
+
{ pid: child.pid },
|
|
320
|
+
correlationId,
|
|
321
|
+
);
|
|
281
322
|
} else {
|
|
282
323
|
logEvent(
|
|
283
324
|
"restart",
|
|
@@ -288,7 +329,13 @@ const createWatchdog = ({
|
|
|
288
329
|
);
|
|
289
330
|
}
|
|
290
331
|
} catch (err) {
|
|
291
|
-
logEvent(
|
|
332
|
+
logEvent(
|
|
333
|
+
"restart",
|
|
334
|
+
"repair",
|
|
335
|
+
"failed",
|
|
336
|
+
{ error: err.message },
|
|
337
|
+
correlationId,
|
|
338
|
+
);
|
|
292
339
|
}
|
|
293
340
|
state.health = "unknown";
|
|
294
341
|
state.lifecycle = "running";
|
|
@@ -344,7 +391,10 @@ const createWatchdog = ({
|
|
|
344
391
|
source = "health_timer",
|
|
345
392
|
allowAutoRepair = true,
|
|
346
393
|
} = {}) => {
|
|
347
|
-
if (
|
|
394
|
+
if (
|
|
395
|
+
state.expectedRestartInProgress &&
|
|
396
|
+
Date.now() >= state.expectedRestartUntilMs
|
|
397
|
+
) {
|
|
348
398
|
clearExpectedRestartWindow();
|
|
349
399
|
}
|
|
350
400
|
if (state.operationInProgress && !allowDuringOperation) return false;
|
|
@@ -358,7 +408,8 @@ const createWatchdog = ({
|
|
|
358
408
|
state.gatewayStartedAt != null &&
|
|
359
409
|
state.gatewayStartedAt !== gatewayStartedAtAtStart;
|
|
360
410
|
const restartWindowActive =
|
|
361
|
-
state.expectedRestartInProgress &&
|
|
411
|
+
state.expectedRestartInProgress &&
|
|
412
|
+
Date.now() < state.expectedRestartUntilMs;
|
|
362
413
|
if (staleAfterRestart) {
|
|
363
414
|
return false;
|
|
364
415
|
}
|
|
@@ -370,7 +421,8 @@ const createWatchdog = ({
|
|
|
370
421
|
clearExpectedRestartWindow();
|
|
371
422
|
state.health = "healthy";
|
|
372
423
|
state.lifecycle = "running";
|
|
373
|
-
if (!state.uptimeStartedAt || wasUnhealthy)
|
|
424
|
+
if (!state.uptimeStartedAt || wasUnhealthy)
|
|
425
|
+
state.uptimeStartedAt = Date.now();
|
|
374
426
|
state.repairAttempts = 0;
|
|
375
427
|
state.crashRecoveryActive = false;
|
|
376
428
|
if (recoveredFromCrashLoop) {
|
|
@@ -384,6 +436,13 @@ const createWatchdog = ({
|
|
|
384
436
|
},
|
|
385
437
|
correlationId,
|
|
386
438
|
);
|
|
439
|
+
await notify(
|
|
440
|
+
[
|
|
441
|
+
"🐺 *AlphaClaw Watchdog*",
|
|
442
|
+
withViewLogsSuffix("🟢 Gateway healthy again"),
|
|
443
|
+
].join("\n"),
|
|
444
|
+
correlationId,
|
|
445
|
+
);
|
|
387
446
|
}
|
|
388
447
|
if (state.pendingRecoveryNoticeSource) {
|
|
389
448
|
const recoverySource = state.pendingRecoveryNoticeSource;
|
|
@@ -392,12 +451,17 @@ const createWatchdog = ({
|
|
|
392
451
|
[
|
|
393
452
|
"🐺 *AlphaClaw Watchdog*",
|
|
394
453
|
withViewLogsSuffix("🟢 Gateway healthy again"),
|
|
395
|
-
`Trigger: ${asInlineCode(recoverySource)}`,
|
|
396
454
|
].join("\n"),
|
|
397
455
|
correlationId,
|
|
398
456
|
);
|
|
399
457
|
}
|
|
400
|
-
logEvent(
|
|
458
|
+
logEvent(
|
|
459
|
+
"health_check",
|
|
460
|
+
source,
|
|
461
|
+
"ok",
|
|
462
|
+
parsed.details || result,
|
|
463
|
+
correlationId,
|
|
464
|
+
);
|
|
401
465
|
return true;
|
|
402
466
|
}
|
|
403
467
|
if (restartWindowActive) {
|
|
@@ -445,7 +509,10 @@ const createWatchdog = ({
|
|
|
445
509
|
|
|
446
510
|
if (state.health === "unknown" && state.lifecycle === "running") {
|
|
447
511
|
state.startupConsecutiveHealthFailures += 1;
|
|
448
|
-
if (
|
|
512
|
+
if (
|
|
513
|
+
state.startupConsecutiveHealthFailures <
|
|
514
|
+
kWatchdogStartupFailureThreshold
|
|
515
|
+
) {
|
|
449
516
|
logEvent(
|
|
450
517
|
"health_check",
|
|
451
518
|
source,
|
|
@@ -486,7 +553,13 @@ const createWatchdog = ({
|
|
|
486
553
|
try {
|
|
487
554
|
const child = launchGatewayProcess();
|
|
488
555
|
if (child) {
|
|
489
|
-
logEvent(
|
|
556
|
+
logEvent(
|
|
557
|
+
"restart",
|
|
558
|
+
"exit_event",
|
|
559
|
+
"ok",
|
|
560
|
+
{ pid: child.pid },
|
|
561
|
+
correlationId,
|
|
562
|
+
);
|
|
490
563
|
} else {
|
|
491
564
|
logEvent(
|
|
492
565
|
"restart",
|
|
@@ -497,13 +570,24 @@ const createWatchdog = ({
|
|
|
497
570
|
);
|
|
498
571
|
}
|
|
499
572
|
} catch (err) {
|
|
500
|
-
logEvent(
|
|
573
|
+
logEvent(
|
|
574
|
+
"restart",
|
|
575
|
+
"exit_event",
|
|
576
|
+
"failed",
|
|
577
|
+
{ error: err.message },
|
|
578
|
+
correlationId,
|
|
579
|
+
);
|
|
501
580
|
} finally {
|
|
502
581
|
state.operationInProgress = false;
|
|
503
582
|
}
|
|
504
583
|
};
|
|
505
584
|
|
|
506
|
-
const onGatewayExit = ({
|
|
585
|
+
const onGatewayExit = ({
|
|
586
|
+
code,
|
|
587
|
+
signal,
|
|
588
|
+
expectedExit = false,
|
|
589
|
+
stderrTail = [],
|
|
590
|
+
} = {}) => {
|
|
507
591
|
const correlationId = createCorrelationId();
|
|
508
592
|
clearDegradedHealthCheckTimer();
|
|
509
593
|
if (expectedExit && (code == null || code === 0)) {
|
|
@@ -557,7 +641,9 @@ const createWatchdog = ({
|
|
|
557
641
|
),
|
|
558
642
|
`Crashes: ${state.crashTimestamps.length} in the last ${Math.floor(kWatchdogCrashLoopWindowMs / 1000)}s`,
|
|
559
643
|
`Last exit code: ${code ?? "unknown"}`,
|
|
560
|
-
...(state.autoRepair
|
|
644
|
+
...(state.autoRepair
|
|
645
|
+
? []
|
|
646
|
+
: ["Auto-restart paused; manual action required."]),
|
|
561
647
|
].join("\n"),
|
|
562
648
|
correlationId,
|
|
563
649
|
);
|
package/lib/server.js
CHANGED
|
@@ -91,6 +91,7 @@ const {
|
|
|
91
91
|
const {
|
|
92
92
|
ensureOpenclawRuntimeArtifacts,
|
|
93
93
|
installControlUiSkill,
|
|
94
|
+
resolveSetupUiUrl,
|
|
94
95
|
syncBootstrapPromptFiles,
|
|
95
96
|
} = require("./server/onboarding/workspace");
|
|
96
97
|
const {
|
|
@@ -105,6 +106,7 @@ const { createDiscordApi } = require("./server/discord-api");
|
|
|
105
106
|
const { createWatchdogNotifier } = require("./server/watchdog-notify");
|
|
106
107
|
const { createWatchdog } = require("./server/watchdog");
|
|
107
108
|
const { createDoctorService } = require("./server/doctor/service");
|
|
109
|
+
const { runOnboardedBootSequence } = require("./server/startup");
|
|
108
110
|
|
|
109
111
|
const { registerAuthRoutes } = require("./server/routes/auth");
|
|
110
112
|
const { registerPageRoutes } = require("./server/routes/pages");
|
|
@@ -154,20 +156,13 @@ proxy.on("error", (err, req, res) => {
|
|
|
154
156
|
const authProfiles = createAuthProfiles();
|
|
155
157
|
const loginThrottle = { ...createLoginThrottle(), getClientKey };
|
|
156
158
|
const { shellCmd, clawCmd, gogCmd } = createCommands({ gatewayEnv });
|
|
157
|
-
const resolveSetupUrl = () =>
|
|
158
|
-
|
|
159
|
+
const resolveSetupUrl = () =>
|
|
160
|
+
resolveSetupUiUrl(
|
|
159
161
|
process.env.ALPHACLAW_SETUP_URL ||
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (process.env.RAILWAY_STATIC_URL) {
|
|
165
|
-
const domain = String(process.env.RAILWAY_STATIC_URL).trim();
|
|
166
|
-
if (!domain) return "";
|
|
167
|
-
return domain.startsWith("http") ? domain : `https://${domain}`;
|
|
168
|
-
}
|
|
169
|
-
return "";
|
|
170
|
-
};
|
|
162
|
+
process.env.ALPHACLAW_BASE_URL ||
|
|
163
|
+
process.env.RENDER_EXTERNAL_URL ||
|
|
164
|
+
process.env.URL,
|
|
165
|
+
);
|
|
171
166
|
const restartGateway = () => restartGatewayWithReload(reloadEnv);
|
|
172
167
|
const openclawVersionService = createOpenclawVersionService({
|
|
173
168
|
gatewayEnv,
|
|
@@ -415,13 +410,17 @@ server.on("upgrade", (req, socket, head) => {
|
|
|
415
410
|
server.listen(PORT, "0.0.0.0", () => {
|
|
416
411
|
console.log(`[alphaclaw] Express listening on :${PORT}`);
|
|
417
412
|
if (isOnboarded()) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
413
|
+
runOnboardedBootSequence({
|
|
414
|
+
doSyncPromptFiles,
|
|
415
|
+
reloadEnv,
|
|
416
|
+
syncChannelConfig,
|
|
417
|
+
readEnvFile,
|
|
418
|
+
ensureGatewayProxyConfig,
|
|
419
|
+
resolveSetupUrl,
|
|
420
|
+
startGateway,
|
|
421
|
+
watchdog,
|
|
422
|
+
gmailWatchService,
|
|
423
|
+
});
|
|
425
424
|
} else {
|
|
426
425
|
console.log("[alphaclaw] Awaiting onboarding via Setup UI");
|
|
427
426
|
}
|