@chrysb/alphaclaw 0.8.1-beta.8 → 0.8.2

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 (210) hide show
  1. package/README.md +32 -24
  2. package/bin/alphaclaw.js +13 -2
  3. package/lib/public/css/tailwind.generated.css +1 -0
  4. package/lib/public/css/tailwind.input.css +3 -0
  5. package/lib/public/css/theme.css +28 -0
  6. package/lib/public/css/vendor/xterm.css +218 -0
  7. package/lib/public/dist/app.bundle.js +10514 -0
  8. package/lib/public/dist/chunks/addon-fit-W4YZGRNV.js +1 -0
  9. package/lib/public/dist/chunks/chunk-72ZECFVW.js +1 -0
  10. package/lib/public/dist/chunks/xterm-KOX4YMOF.js +9 -0
  11. package/lib/public/js/app.js +4 -4
  12. package/lib/public/js/components/action-button.js +8 -8
  13. package/lib/public/js/components/add-channel-menu.js +2 -2
  14. package/lib/public/js/components/agent-send-modal.js +6 -6
  15. package/lib/public/js/components/agents-tab/agent-bindings-section/channel-item-trailing.js +7 -7
  16. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +3 -3
  17. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +1 -1
  18. package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +4 -4
  19. package/lib/public/js/components/agents-tab/agent-detail-panel.js +5 -5
  20. package/lib/public/js/components/agents-tab/agent-identity-section.js +18 -18
  21. package/lib/public/js/components/agents-tab/agent-overview/index.js +2 -2
  22. package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +2 -2
  23. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +8 -8
  24. package/lib/public/js/components/agents-tab/agent-overview/tools-card.js +5 -5
  25. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +1 -1
  26. package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +1 -1
  27. package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +4 -4
  28. package/lib/public/js/components/agents-tab/agent-pairing-section.js +4 -4
  29. package/lib/public/js/components/agents-tab/agent-tools/index.js +5 -5
  30. package/lib/public/js/components/agents-tab/agent-tools/use-agent-tools.js +1 -1
  31. package/lib/public/js/components/agents-tab/create-agent-modal.js +13 -13
  32. package/lib/public/js/components/agents-tab/create-channel-modal.js +34 -34
  33. package/lib/public/js/components/agents-tab/delete-agent-dialog.js +3 -3
  34. package/lib/public/js/components/agents-tab/edit-agent-modal.js +9 -9
  35. package/lib/public/js/components/agents-tab/index.js +3 -3
  36. package/lib/public/js/components/agents-tab/use-agents.js +1 -1
  37. package/lib/public/js/components/badge.js +6 -6
  38. package/lib/public/js/components/channel-account-status-badge.js +2 -2
  39. package/lib/public/js/components/channel-operations-panel.js +2 -2
  40. package/lib/public/js/components/channels.js +9 -9
  41. package/lib/public/js/components/confirm-dialog.js +5 -5
  42. package/lib/public/js/components/credentials-modal.js +22 -22
  43. package/lib/public/js/components/cron-tab/cron-calendar.js +6 -6
  44. package/lib/public/js/components/cron-tab/cron-insights-panel.js +10 -10
  45. package/lib/public/js/components/cron-tab/cron-job-detail.js +4 -4
  46. package/lib/public/js/components/cron-tab/cron-job-list.js +4 -4
  47. package/lib/public/js/components/cron-tab/cron-job-settings-card.js +15 -15
  48. package/lib/public/js/components/cron-tab/cron-job-trends-panel.js +5 -5
  49. package/lib/public/js/components/cron-tab/cron-job-usage.js +16 -16
  50. package/lib/public/js/components/cron-tab/cron-overview.js +10 -10
  51. package/lib/public/js/components/cron-tab/cron-prompt-editor.js +3 -3
  52. package/lib/public/js/components/cron-tab/cron-run-history-panel.js +39 -39
  53. package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +4 -4
  54. package/lib/public/js/components/cron-tab/index.js +5 -5
  55. package/lib/public/js/components/cron-tab/use-cron-tab.js +1 -1
  56. package/lib/public/js/components/device-pairings.js +12 -12
  57. package/lib/public/js/components/doctor/findings-list.js +22 -22
  58. package/lib/public/js/components/doctor/fix-card-modal.js +2 -2
  59. package/lib/public/js/components/doctor/general-warning.js +5 -5
  60. package/lib/public/js/components/doctor/index.js +26 -26
  61. package/lib/public/js/components/doctor/summary-cards.js +5 -5
  62. package/lib/public/js/components/envars.js +16 -16
  63. package/lib/public/js/components/features.js +4 -4
  64. package/lib/public/js/components/file-tree.js +4 -4
  65. package/lib/public/js/components/file-viewer/diff-viewer.js +2 -2
  66. package/lib/public/js/components/file-viewer/editor-surface.js +2 -2
  67. package/lib/public/js/components/file-viewer/frontmatter-panel.js +2 -2
  68. package/lib/public/js/components/file-viewer/index.js +3 -3
  69. package/lib/public/js/components/file-viewer/markdown-split-view.js +2 -2
  70. package/lib/public/js/components/file-viewer/media-preview.js +2 -2
  71. package/lib/public/js/components/file-viewer/scroll-sync.js +1 -1
  72. package/lib/public/js/components/file-viewer/sqlite-viewer.js +2 -2
  73. package/lib/public/js/components/file-viewer/status-banners.js +2 -2
  74. package/lib/public/js/components/file-viewer/toolbar.js +2 -2
  75. package/lib/public/js/components/file-viewer/use-editor-line-number-sync.js +1 -1
  76. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +1 -1
  77. package/lib/public/js/components/file-viewer/use-file-diff.js +1 -1
  78. package/lib/public/js/components/file-viewer/use-file-loader.js +1 -1
  79. package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +1 -1
  80. package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +1 -1
  81. package/lib/public/js/components/file-viewer/use-file-viewer.js +2 -2
  82. package/lib/public/js/components/gateway.js +12 -12
  83. package/lib/public/js/components/general/index.js +7 -7
  84. package/lib/public/js/components/general/use-general-tab.js +1 -1
  85. package/lib/public/js/components/global-restart-banner.js +2 -2
  86. package/lib/public/js/components/google/account-row.js +7 -7
  87. package/lib/public/js/components/google/add-account-modal.js +8 -8
  88. package/lib/public/js/components/google/gmail-setup-wizard.js +24 -24
  89. package/lib/public/js/components/google/gmail-watch-toggle.js +5 -5
  90. package/lib/public/js/components/google/index.js +6 -6
  91. package/lib/public/js/components/google/use-gmail-watch.js +1 -1
  92. package/lib/public/js/components/google/use-google-accounts.js +1 -1
  93. package/lib/public/js/components/icons.js +2 -2
  94. package/lib/public/js/components/info-tooltip.js +3 -3
  95. package/lib/public/js/components/loading-spinner.js +2 -2
  96. package/lib/public/js/components/modal-shell.js +5 -5
  97. package/lib/public/js/components/models-tab/index.js +11 -11
  98. package/lib/public/js/components/models-tab/model-picker.js +9 -9
  99. package/lib/public/js/components/models-tab/provider-auth-card.js +12 -12
  100. package/lib/public/js/components/models-tab/use-models.js +1 -1
  101. package/lib/public/js/components/models.js +18 -18
  102. package/lib/public/js/components/nodes-tab/browser-attach/index.js +15 -25
  103. package/lib/public/js/components/nodes-tab/connected-nodes/index.js +32 -32
  104. package/lib/public/js/components/nodes-tab/connected-nodes/use-connected-nodes-card.js +57 -6
  105. package/lib/public/js/components/nodes-tab/exec-allowlist/index.js +10 -10
  106. package/lib/public/js/components/nodes-tab/exec-allowlist/use-exec-allowlist.js +1 -1
  107. package/lib/public/js/components/nodes-tab/exec-config/index.js +13 -13
  108. package/lib/public/js/components/nodes-tab/exec-config/use-exec-config.js +1 -1
  109. package/lib/public/js/components/nodes-tab/index.js +2 -2
  110. package/lib/public/js/components/nodes-tab/setup-wizard/index.js +14 -14
  111. package/lib/public/js/components/nodes-tab/setup-wizard/use-setup-wizard.js +1 -1
  112. package/lib/public/js/components/nodes-tab/use-nodes-tab.js +1 -1
  113. package/lib/public/js/components/onboarding/use-welcome-codex.js +1 -1
  114. package/lib/public/js/components/onboarding/use-welcome-pairing.js +1 -1
  115. package/lib/public/js/components/onboarding/use-welcome-storage.js +1 -1
  116. package/lib/public/js/components/onboarding/welcome-config.js +3 -3
  117. package/lib/public/js/components/onboarding/welcome-form-step.js +34 -34
  118. package/lib/public/js/components/onboarding/welcome-header.js +2 -2
  119. package/lib/public/js/components/onboarding/welcome-import-step.js +22 -22
  120. package/lib/public/js/components/onboarding/welcome-pairing-step.js +15 -15
  121. package/lib/public/js/components/onboarding/welcome-placeholder-review-step.js +7 -7
  122. package/lib/public/js/components/onboarding/welcome-pre-step.js +9 -9
  123. package/lib/public/js/components/onboarding/welcome-secret-review-step.js +15 -15
  124. package/lib/public/js/components/onboarding/welcome-setup-step.js +8 -8
  125. package/lib/public/js/components/overflow-menu.js +3 -3
  126. package/lib/public/js/components/page-header.js +2 -2
  127. package/lib/public/js/components/pairings.js +14 -14
  128. package/lib/public/js/components/pane-shell.js +2 -2
  129. package/lib/public/js/components/pill-tabs.js +4 -4
  130. package/lib/public/js/components/pop-actions.js +3 -3
  131. package/lib/public/js/components/providers.js +17 -17
  132. package/lib/public/js/components/routes/agents-route.js +2 -2
  133. package/lib/public/js/components/routes/browse-route.js +2 -2
  134. package/lib/public/js/components/routes/cron-route.js +2 -2
  135. package/lib/public/js/components/routes/doctor-route.js +2 -2
  136. package/lib/public/js/components/routes/envars-route.js +2 -2
  137. package/lib/public/js/components/routes/general-route.js +2 -2
  138. package/lib/public/js/components/routes/models-route.js +2 -2
  139. package/lib/public/js/components/routes/nodes-route.js +2 -2
  140. package/lib/public/js/components/routes/providers-route.js +2 -2
  141. package/lib/public/js/components/routes/route-redirect.js +2 -2
  142. package/lib/public/js/components/routes/telegram-route.js +2 -2
  143. package/lib/public/js/components/routes/usage-route.js +2 -2
  144. package/lib/public/js/components/routes/watchdog-route.js +2 -2
  145. package/lib/public/js/components/routes/webhooks-route.js +2 -2
  146. package/lib/public/js/components/scope-picker.js +9 -9
  147. package/lib/public/js/components/secret-input.js +5 -5
  148. package/lib/public/js/components/segmented-control.js +2 -2
  149. package/lib/public/js/components/session-select-field.js +7 -7
  150. package/lib/public/js/components/sidebar-git-panel.js +3 -3
  151. package/lib/public/js/components/sidebar.js +3 -3
  152. package/lib/public/js/components/summary-stat-card.js +3 -3
  153. package/lib/public/js/components/telegram-workspace/index.js +10 -10
  154. package/lib/public/js/components/telegram-workspace/manage.js +36 -36
  155. package/lib/public/js/components/telegram-workspace/onboarding.js +73 -73
  156. package/lib/public/js/components/toast.js +8 -8
  157. package/lib/public/js/components/toggle-switch.js +2 -2
  158. package/lib/public/js/components/tooltip.js +5 -5
  159. package/lib/public/js/components/update-action-button.js +2 -2
  160. package/lib/public/js/components/update-modal.js +9 -9
  161. package/lib/public/js/components/usage-tab/constants.js +2 -2
  162. package/lib/public/js/components/usage-tab/index.js +3 -3
  163. package/lib/public/js/components/usage-tab/overview-section.js +15 -15
  164. package/lib/public/js/components/usage-tab/sessions-section.js +19 -19
  165. package/lib/public/js/components/usage-tab/use-usage-tab.js +2 -2
  166. package/lib/public/js/components/watchdog-tab/console/index.js +22 -8
  167. package/lib/public/js/components/watchdog-tab/console/use-console.js +28 -2
  168. package/lib/public/js/components/watchdog-tab/helpers.js +35 -6
  169. package/lib/public/js/components/watchdog-tab/incidents/index.js +6 -6
  170. package/lib/public/js/components/watchdog-tab/incidents/use-incidents.js +1 -1
  171. package/lib/public/js/components/watchdog-tab/index.js +4 -2
  172. package/lib/public/js/components/watchdog-tab/resource-bar.js +5 -5
  173. package/lib/public/js/components/watchdog-tab/resources/index.js +14 -3
  174. package/lib/public/js/components/watchdog-tab/resources/use-resources.js +1 -1
  175. package/lib/public/js/components/watchdog-tab/settings/index.js +97 -30
  176. package/lib/public/js/components/watchdog-tab/settings/use-settings.js +1 -1
  177. package/lib/public/js/components/watchdog-tab/terminal/index.js +3 -3
  178. package/lib/public/js/components/watchdog-tab/terminal/use-terminal.js +41 -5
  179. package/lib/public/js/components/watchdog-tab/use-watchdog-tab.js +2 -0
  180. package/lib/public/js/components/webhooks/create-webhook-modal/index.js +17 -17
  181. package/lib/public/js/components/webhooks/helpers.js +3 -3
  182. package/lib/public/js/components/webhooks/index.js +4 -4
  183. package/lib/public/js/components/webhooks/request-history/index.js +14 -14
  184. package/lib/public/js/components/webhooks/request-history/use-request-history.js +1 -1
  185. package/lib/public/js/components/webhooks/webhook-detail/index.js +41 -41
  186. package/lib/public/js/components/webhooks/webhook-detail/use-webhook-detail.js +1 -1
  187. package/lib/public/js/components/webhooks/webhook-list/index.js +11 -11
  188. package/lib/public/js/components/webhooks/webhook-list/use-webhook-list.js +1 -1
  189. package/lib/public/js/components/welcome/index.js +3 -3
  190. package/lib/public/js/components/welcome/use-welcome.js +10 -10
  191. package/lib/public/js/hooks/use-app-shell-controller.js +22 -4
  192. package/lib/public/js/hooks/use-app-shell-ui.js +1 -1
  193. package/lib/public/js/hooks/use-browse-navigation.js +1 -1
  194. package/lib/public/js/hooks/use-cached-fetch.js +1 -1
  195. package/lib/public/js/hooks/use-destination-session-selection.js +1 -1
  196. package/lib/public/js/hooks/use-hash-location.js +1 -1
  197. package/lib/public/js/hooks/useAgentSessions.js +1 -1
  198. package/lib/public/js/hooks/usePolling.js +1 -1
  199. package/lib/public/js/tailwind-config.js +39 -0
  200. package/lib/public/login.html +3 -18
  201. package/lib/public/setup.html +2 -18
  202. package/lib/server/init/register-server-routes.js +2 -0
  203. package/lib/server/onboarding/github.js +39 -0
  204. package/lib/server/routes/nodes.js +12 -4
  205. package/lib/server/routes/pairings.js +5 -1
  206. package/lib/server/routes/system.js +5 -0
  207. package/lib/server/routes/watchdog.js +15 -0
  208. package/lib/server/watchdog.js +137 -51
  209. package/lib/server.js +2 -0
  210. package/package.json +14 -3
@@ -10,6 +10,7 @@ const {
10
10
  const kHealthStartupGraceMs = 30 * 1000;
11
11
  const kBootstrapHealthCheckMs = 5 * 1000;
12
12
  const kExpectedRestartWindowMs = 15 * 1000;
13
+ const kGatewayHealthTimeoutMs = 5 * 1000;
13
14
 
14
15
  const isTruthy = (value) =>
15
16
  ["1", "true", "yes", "on"].includes(
@@ -18,32 +19,6 @@ const isTruthy = (value) =>
18
19
  .toLowerCase(),
19
20
  );
20
21
 
21
- const parseHealthResult = (result) => {
22
- if (!result?.ok)
23
- return { ok: false, reason: result?.stderr || "health command failed" };
24
- const raw = String(result.stdout || "").trim();
25
- if (!raw) return { ok: true };
26
- try {
27
- const parsed = JSON.parse(raw);
28
- if (parsed?.ok === false || parsed?.status === "unhealthy") {
29
- return { ok: false, reason: parsed?.error || "gateway unhealthy" };
30
- }
31
- return { ok: true, details: parsed };
32
- } catch {
33
- const firstBrace = raw.indexOf("{");
34
- const lastBrace = raw.lastIndexOf("}");
35
- if (firstBrace >= 0 && lastBrace > firstBrace) {
36
- try {
37
- const parsed = JSON.parse(raw.slice(firstBrace, lastBrace + 1));
38
- if (parsed?.ok === false || parsed?.status === "unhealthy") {
39
- return { ok: false, reason: parsed?.error || "gateway unhealthy" };
40
- }
41
- return { ok: true, details: parsed };
42
- } catch {}
43
- }
44
- }
45
- return { ok: true };
46
- };
47
22
  const isDuplicateGatewayLaunchExit = ({ code, stderrTail = [] } = {}) => {
48
23
  if (code !== 1) return false;
49
24
  const stderrText = (Array.isArray(stderrTail) ? stderrTail : [])
@@ -66,6 +41,7 @@ const createWatchdog = ({
66
41
  writeEnvFile,
67
42
  reloadEnv,
68
43
  resolveSetupUrl,
44
+ resolveGatewayHealthUrl = () => "",
69
45
  }) => {
70
46
  const state = {
71
47
  lifecycle: "stopped",
@@ -85,11 +61,26 @@ const createWatchdog = ({
85
61
  expectedRestartInProgress: false,
86
62
  expectedRestartUntilMs: 0,
87
63
  pendingRecoveryNoticeSource: "",
64
+ awaitingAutoRepairRecovery: false,
88
65
  startupConsecutiveHealthFailures: 0,
89
66
  };
90
67
  let healthTimer = null;
91
68
  let bootstrapHealthTimer = null;
92
69
  let degradedHealthTimer = null;
70
+ let activeIncidentKey = "";
71
+ let sentIncidentNotifications = new Set();
72
+
73
+ const openIncident = (incidentKey = "gateway") => {
74
+ const normalizedKey = String(incidentKey || "gateway");
75
+ if (activeIncidentKey === normalizedKey) return;
76
+ activeIncidentKey = normalizedKey;
77
+ sentIncidentNotifications = new Set();
78
+ };
79
+
80
+ const closeIncident = () => {
81
+ activeIncidentKey = "";
82
+ sentIncidentNotifications = new Set();
83
+ };
93
84
 
94
85
  const clearDegradedHealthCheckTimer = () => {
95
86
  if (!degradedHealthTimer) return;
@@ -209,6 +200,27 @@ const createWatchdog = ({
209
200
  return result;
210
201
  };
211
202
 
203
+ const notifyOncePerIncident = async (
204
+ notificationKey,
205
+ message,
206
+ correlationId = "",
207
+ ) => {
208
+ const key = String(notificationKey || "").trim();
209
+ if (!key) return notify(message, correlationId);
210
+ if (sentIncidentNotifications.has(key)) {
211
+ return {
212
+ ok: false,
213
+ skipped: true,
214
+ reason: "incident_notification_already_sent",
215
+ };
216
+ }
217
+ const result = await notify(message, correlationId);
218
+ if (result?.ok || result?.skipped) {
219
+ sentIncidentNotifications.add(key);
220
+ }
221
+ return result;
222
+ };
223
+
212
224
  const getWatchdogSetupUrl = () => {
213
225
  try {
214
226
  const base =
@@ -241,12 +253,19 @@ const createWatchdog = ({
241
253
  attempts = 0,
242
254
  }) => {
243
255
  if (source === "manual") return;
256
+ openIncident("gateway_recovery");
244
257
  const title = ok
245
258
  ? verifiedHealthy
246
259
  ? "🟢 Auto-repair complete, gateway healthy"
247
260
  : "🟡 Auto-repair started, awaiting health check"
248
261
  : "🔴 Auto-repair failed";
249
- await notify(
262
+ const notificationKey = ok
263
+ ? verifiedHealthy
264
+ ? "auto_repair_complete"
265
+ : "auto_repair_awaiting_health"
266
+ : "auto_repair_failed";
267
+ await notifyOncePerIncident(
268
+ notificationKey,
250
269
  [
251
270
  "🐺 *AlphaClaw Watchdog*",
252
271
  withViewLogsSuffix(title),
@@ -262,6 +281,59 @@ const createWatchdog = ({
262
281
  notificationsEnabled: !state.notificationsDisabled,
263
282
  });
264
283
 
284
+ const probeGatewayHealth = async () => {
285
+ const healthUrl = String(resolveGatewayHealthUrl() || "").trim();
286
+ if (!healthUrl) {
287
+ return {
288
+ ok: false,
289
+ reason: "gateway health URL unavailable",
290
+ };
291
+ }
292
+ const controller = new AbortController();
293
+ const timeoutId = setTimeout(() => controller.abort(), kGatewayHealthTimeoutMs);
294
+ try {
295
+ const response = await fetch(healthUrl, {
296
+ method: "GET",
297
+ headers: { Accept: "application/json" },
298
+ signal: controller.signal,
299
+ });
300
+ const rawBody = await response.text();
301
+ let parsedBody = null;
302
+ try {
303
+ parsedBody = rawBody ? JSON.parse(rawBody) : null;
304
+ } catch {}
305
+ if (!response.ok) {
306
+ return {
307
+ ok: false,
308
+ reason:
309
+ parsedBody?.error ||
310
+ `gateway health returned HTTP ${response.status}`,
311
+ };
312
+ }
313
+ if (parsedBody?.ok === false) {
314
+ return {
315
+ ok: false,
316
+ reason: parsedBody?.error || "gateway unhealthy",
317
+ };
318
+ }
319
+ return {
320
+ ok: true,
321
+ details: parsedBody,
322
+ };
323
+ } catch (error) {
324
+ const message =
325
+ error?.name === "AbortError"
326
+ ? `gateway health timed out after ${kGatewayHealthTimeoutMs}ms`
327
+ : error?.message || "gateway health request failed";
328
+ return {
329
+ ok: false,
330
+ reason: message,
331
+ };
332
+ } finally {
333
+ clearTimeout(timeoutId);
334
+ }
335
+ };
336
+
265
337
  const updateSettings = ({ autoRepair, notificationsEnabled } = {}) => {
266
338
  const hasAutoRepair = typeof autoRepair === "boolean";
267
339
  const hasNotificationsEnabled = typeof notificationsEnabled === "boolean";
@@ -309,6 +381,9 @@ const createWatchdog = ({
309
381
  if (!force && !state.autoRepair) {
310
382
  return { ok: false, skipped: true, reason: "auto_repair_disabled" };
311
383
  }
384
+ if (!force && state.awaitingAutoRepairRecovery) {
385
+ return { ok: false, skipped: true, reason: "awaiting_health_recovery" };
386
+ }
312
387
  if (state.operationInProgress) {
313
388
  return { ok: false, skipped: true, reason: "operation_in_progress" };
314
389
  }
@@ -353,6 +428,7 @@ const createWatchdog = ({
353
428
  state.lifecycle = "running";
354
429
  state.repairAttempts = 0;
355
430
  state.crashTimestamps = [];
431
+ state.awaitingAutoRepairRecovery = false;
356
432
  const verifiedHealthy = await runHealthCheck({
357
433
  allowDuringOperation: true,
358
434
  source: "repair_verify",
@@ -367,8 +443,10 @@ const createWatchdog = ({
367
443
  });
368
444
  if (!verifiedHealthy && source !== "manual") {
369
445
  state.pendingRecoveryNoticeSource = source;
446
+ state.awaitingAutoRepairRecovery = true;
370
447
  } else {
371
448
  state.pendingRecoveryNoticeSource = "";
449
+ state.awaitingAutoRepairRecovery = false;
372
450
  }
373
451
  return { ok: true, verifiedHealthy, launchedGateway, result };
374
452
  }
@@ -413,8 +491,7 @@ const createWatchdog = ({
413
491
  const gatewayStartedAtAtStart = state.gatewayStartedAt;
414
492
  const correlationId = createCorrelationId();
415
493
  state.lastHealthCheckAt = new Date().toISOString();
416
- const result = await clawCmd("health --json", { quiet: true });
417
- const parsed = parseHealthResult(result);
494
+ const parsed = await probeGatewayHealth();
418
495
  const staleAfterRestart =
419
496
  gatewayStartedAtAtStart != null &&
420
497
  state.gatewayStartedAt != null &&
@@ -428,6 +505,11 @@ const createWatchdog = ({
428
505
  if (parsed.ok) {
429
506
  const wasUnhealthy = state.health !== "healthy";
430
507
  const recoveredFromCrashLoop = state.lifecycle === "crash_loop";
508
+ const shouldNotifyRecovery =
509
+ !!activeIncidentKey ||
510
+ recoveredFromCrashLoop ||
511
+ !!state.pendingRecoveryNoticeSource ||
512
+ state.awaitingAutoRepairRecovery;
431
513
  state.startupConsecutiveHealthFailures = 0;
432
514
  clearDegradedHealthCheckTimer();
433
515
  clearExpectedRestartWindow();
@@ -437,29 +519,25 @@ const createWatchdog = ({
437
519
  state.uptimeStartedAt = Date.now();
438
520
  state.repairAttempts = 0;
439
521
  state.crashRecoveryActive = false;
440
- if (recoveredFromCrashLoop) {
522
+ state.awaitingAutoRepairRecovery = false;
523
+ if (shouldNotifyRecovery) {
441
524
  logEvent(
442
525
  "recovery",
443
526
  source,
444
527
  "ok",
445
- {
446
- previousLifecycle: "crash_loop",
447
- health: "healthy",
448
- },
449
- correlationId,
450
- );
451
- await notify(
452
528
  [
453
- "🐺 *AlphaClaw Watchdog*",
454
- withViewLogsSuffix("🟢 Gateway healthy again"),
455
- ].join("\n"),
529
+ {
530
+ previousLifecycle: recoveredFromCrashLoop
531
+ ? "crash_loop"
532
+ : null,
533
+ previousRecoverySource: state.pendingRecoveryNoticeSource || null,
534
+ health: "healthy",
535
+ },
536
+ ][0],
456
537
  correlationId,
457
538
  );
458
- }
459
- if (state.pendingRecoveryNoticeSource) {
460
- const recoverySource = state.pendingRecoveryNoticeSource;
461
- state.pendingRecoveryNoticeSource = "";
462
- await notify(
539
+ await notifyOncePerIncident(
540
+ "gateway_healthy_again",
463
541
  [
464
542
  "🐺 *AlphaClaw Watchdog*",
465
543
  withViewLogsSuffix("🟢 Gateway healthy again"),
@@ -467,11 +545,13 @@ const createWatchdog = ({
467
545
  correlationId,
468
546
  );
469
547
  }
548
+ state.pendingRecoveryNoticeSource = "";
549
+ closeIncident();
470
550
  logEvent(
471
551
  "health_check",
472
552
  source,
473
553
  "ok",
474
- parsed.details || result,
554
+ parsed.details || { ok: true },
475
555
  correlationId,
476
556
  );
477
557
  return true;
@@ -485,7 +565,7 @@ const createWatchdog = ({
485
565
  "ok",
486
566
  {
487
567
  reason: parsed.reason,
488
- result,
568
+ details: parsed.details || null,
489
569
  skipped: true,
490
570
  expectedRestartActive: true,
491
571
  expectedRestartUntilMs: state.expectedRestartUntilMs,
@@ -509,7 +589,7 @@ const createWatchdog = ({
509
589
  "ok",
510
590
  {
511
591
  reason: parsed.reason,
512
- result,
592
+ details: parsed.details || null,
513
593
  skipped: true,
514
594
  startupGraceActive: true,
515
595
  startupGraceMs: kHealthStartupGraceMs,
@@ -531,7 +611,7 @@ const createWatchdog = ({
531
611
  "ok",
532
612
  {
533
613
  reason: parsed.reason,
534
- result,
614
+ details: parsed.details || null,
535
615
  skipped: true,
536
616
  startupFailureRetryActive: true,
537
617
  startupConsecutiveFailures: state.startupConsecutiveHealthFailures,
@@ -551,10 +631,11 @@ const createWatchdog = ({
551
631
  "health_check",
552
632
  source,
553
633
  "failed",
554
- { reason: parsed.reason, result },
634
+ { reason: parsed.reason, details: parsed.details || null },
555
635
  correlationId,
556
636
  );
557
637
  if (!state.autoRepair || !allowAutoRepair) return false;
638
+ if (state.awaitingAutoRepairRecovery) return false;
558
639
  await runRepair({ source, correlationId });
559
640
  return false;
560
641
  };
@@ -658,6 +739,7 @@ const createWatchdog = ({
658
739
 
659
740
  if (state.crashTimestamps.length >= kWatchdogCrashLoopThreshold) {
660
741
  state.lifecycle = "crash_loop";
742
+ openIncident("gateway_recovery");
661
743
  logEvent(
662
744
  "crash_loop",
663
745
  "exit_event",
@@ -668,7 +750,8 @@ const createWatchdog = ({
668
750
  },
669
751
  correlationId,
670
752
  );
671
- void notify(
753
+ void notifyOncePerIncident(
754
+ "crash_loop_detected",
672
755
  [
673
756
  "🐺 *AlphaClaw Watchdog*",
674
757
  withViewLogsSuffix(
@@ -753,6 +836,9 @@ const createWatchdog = ({
753
836
  state.lifecycle = "stopped";
754
837
  state.uptimeStartedAt = null;
755
838
  state.startupConsecutiveHealthFailures = 0;
839
+ state.awaitingAutoRepairRecovery = false;
840
+ state.pendingRecoveryNoticeSource = "";
841
+ closeIncident();
756
842
  };
757
843
 
758
844
  const getStatus = () => {
package/lib/server.js CHANGED
@@ -226,6 +226,7 @@ const watchdog = createWatchdog({
226
226
  writeEnvFile,
227
227
  reloadEnv,
228
228
  resolveSetupUrl,
229
+ resolveGatewayHealthUrl: () => `${getGatewayUrl()}/health`,
229
230
  });
230
231
  const watchdogTerminal = createWatchdogTerminalService({
231
232
  cwd: constants.OPENCLAW_DIR,
@@ -310,6 +311,7 @@ const { isAuthorizedRequest, gmailWatchService } = registerServerRoutes({
310
311
  deleteOauthCallback,
311
312
  markOauthCallbackUsed,
312
313
  watchdog,
314
+ watchdogNotifier,
313
315
  getRecentEvents,
314
316
  readLogTail,
315
317
  watchdogTerminal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.8.1-beta.8",
3
+ "version": "0.8.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,10 +23,12 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "start": "node bin/alphaclaw.js start",
26
+ "build:ui": "node scripts/build-ui.mjs",
26
27
  "test": "vitest run",
27
28
  "test:watch": "vitest",
28
29
  "test:watchdog": "vitest run tests/server/watchdog.test.js tests/server/watchdog-db.test.js tests/server/routes-watchdog.test.js",
29
- "test:coverage": "vitest run --coverage"
30
+ "test:coverage": "vitest run --coverage",
31
+ "prepack": "npm run build:ui"
30
32
  },
31
33
  "dependencies": {
32
34
  "express": "^4.21.0",
@@ -35,9 +37,18 @@
35
37
  "ws": "^8.19.0"
36
38
  },
37
39
  "devDependencies": {
40
+ "@xterm/addon-fit": "^0.10.0",
41
+ "@xterm/xterm": "^5.5.0",
38
42
  "@vitest/coverage-v8": "^4.0.18",
43
+ "chart.js": "^4.5.1",
44
+ "esbuild": "^0.25.9",
45
+ "htm": "^3.1.1",
46
+ "marked": "^16.4.1",
47
+ "preact": "^10.27.2",
39
48
  "supertest": "^7.2.2",
40
- "vitest": "^4.0.18"
49
+ "tailwindcss": "^3.4.17",
50
+ "vitest": "^4.0.18",
51
+ "wouter-preact": "^3.7.1"
41
52
  },
42
53
  "engines": {
43
54
  "node": ">=22.12.0"