@chrysb/alphaclaw 0.8.1 → 0.8.3-beta.0

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 (219) hide show
  1. package/README.md +33 -24
  2. package/bin/alphaclaw.js +13 -2
  3. package/lib/public/css/chat.css +426 -0
  4. package/lib/public/css/explorer.css +101 -0
  5. package/lib/public/css/tailwind.generated.css +1 -0
  6. package/lib/public/css/tailwind.input.css +3 -0
  7. package/lib/public/css/theme.css +28 -0
  8. package/lib/public/css/vendor/xterm.css +218 -0
  9. package/lib/public/dist/app.bundle.js +10706 -0
  10. package/lib/public/dist/chunks/addon-fit-W4YZGRNV.js +1 -0
  11. package/lib/public/dist/chunks/chunk-72ZECFVW.js +1 -0
  12. package/lib/public/dist/chunks/xterm-KOX4YMOF.js +9 -0
  13. package/lib/public/js/app.js +38 -4
  14. package/lib/public/js/components/action-button.js +8 -8
  15. package/lib/public/js/components/add-channel-menu.js +2 -2
  16. package/lib/public/js/components/agent-send-modal.js +6 -6
  17. package/lib/public/js/components/agents-tab/agent-bindings-section/channel-item-trailing.js +7 -7
  18. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +3 -3
  19. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +1 -1
  20. package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +4 -4
  21. package/lib/public/js/components/agents-tab/agent-detail-panel.js +5 -5
  22. package/lib/public/js/components/agents-tab/agent-identity-section.js +18 -18
  23. package/lib/public/js/components/agents-tab/agent-overview/index.js +2 -2
  24. package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +2 -2
  25. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +8 -8
  26. package/lib/public/js/components/agents-tab/agent-overview/tools-card.js +5 -5
  27. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +1 -1
  28. package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +1 -1
  29. package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +4 -4
  30. package/lib/public/js/components/agents-tab/agent-pairing-section.js +4 -4
  31. package/lib/public/js/components/agents-tab/agent-tools/index.js +5 -5
  32. package/lib/public/js/components/agents-tab/agent-tools/use-agent-tools.js +1 -1
  33. package/lib/public/js/components/agents-tab/create-agent-modal.js +13 -13
  34. package/lib/public/js/components/agents-tab/create-channel-modal.js +34 -34
  35. package/lib/public/js/components/agents-tab/delete-agent-dialog.js +3 -3
  36. package/lib/public/js/components/agents-tab/edit-agent-modal.js +9 -9
  37. package/lib/public/js/components/agents-tab/index.js +3 -3
  38. package/lib/public/js/components/agents-tab/use-agents.js +1 -1
  39. package/lib/public/js/components/badge.js +6 -6
  40. package/lib/public/js/components/channel-account-status-badge.js +2 -2
  41. package/lib/public/js/components/channel-operations-panel.js +2 -2
  42. package/lib/public/js/components/channels.js +9 -9
  43. package/lib/public/js/components/confirm-dialog.js +5 -5
  44. package/lib/public/js/components/credentials-modal.js +22 -22
  45. package/lib/public/js/components/cron-tab/cron-calendar.js +6 -6
  46. package/lib/public/js/components/cron-tab/cron-insights-panel.js +10 -10
  47. package/lib/public/js/components/cron-tab/cron-job-detail.js +4 -4
  48. package/lib/public/js/components/cron-tab/cron-job-list.js +4 -4
  49. package/lib/public/js/components/cron-tab/cron-job-settings-card.js +15 -15
  50. package/lib/public/js/components/cron-tab/cron-job-trends-panel.js +5 -5
  51. package/lib/public/js/components/cron-tab/cron-job-usage.js +16 -16
  52. package/lib/public/js/components/cron-tab/cron-overview.js +10 -10
  53. package/lib/public/js/components/cron-tab/cron-prompt-editor.js +3 -3
  54. package/lib/public/js/components/cron-tab/cron-run-history-panel.js +39 -39
  55. package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +4 -4
  56. package/lib/public/js/components/cron-tab/index.js +5 -5
  57. package/lib/public/js/components/cron-tab/use-cron-tab.js +1 -1
  58. package/lib/public/js/components/device-pairings.js +12 -12
  59. package/lib/public/js/components/doctor/findings-list.js +22 -22
  60. package/lib/public/js/components/doctor/fix-card-modal.js +2 -2
  61. package/lib/public/js/components/doctor/general-warning.js +5 -5
  62. package/lib/public/js/components/doctor/index.js +26 -26
  63. package/lib/public/js/components/doctor/summary-cards.js +5 -5
  64. package/lib/public/js/components/envars.js +16 -16
  65. package/lib/public/js/components/features.js +4 -4
  66. package/lib/public/js/components/file-tree.js +4 -4
  67. package/lib/public/js/components/file-viewer/diff-viewer.js +2 -2
  68. package/lib/public/js/components/file-viewer/editor-surface.js +2 -2
  69. package/lib/public/js/components/file-viewer/frontmatter-panel.js +2 -2
  70. package/lib/public/js/components/file-viewer/index.js +3 -3
  71. package/lib/public/js/components/file-viewer/markdown-split-view.js +2 -2
  72. package/lib/public/js/components/file-viewer/media-preview.js +2 -2
  73. package/lib/public/js/components/file-viewer/scroll-sync.js +1 -1
  74. package/lib/public/js/components/file-viewer/sqlite-viewer.js +2 -2
  75. package/lib/public/js/components/file-viewer/status-banners.js +2 -2
  76. package/lib/public/js/components/file-viewer/toolbar.js +2 -2
  77. package/lib/public/js/components/file-viewer/use-editor-line-number-sync.js +1 -1
  78. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +1 -1
  79. package/lib/public/js/components/file-viewer/use-file-diff.js +1 -1
  80. package/lib/public/js/components/file-viewer/use-file-loader.js +1 -1
  81. package/lib/public/js/components/file-viewer/use-file-viewer-draft-sync.js +1 -1
  82. package/lib/public/js/components/file-viewer/use-file-viewer-hotkeys.js +1 -1
  83. package/lib/public/js/components/file-viewer/use-file-viewer.js +2 -2
  84. package/lib/public/js/components/gateway.js +12 -12
  85. package/lib/public/js/components/general/index.js +7 -7
  86. package/lib/public/js/components/general/use-general-tab.js +1 -1
  87. package/lib/public/js/components/global-restart-banner.js +2 -2
  88. package/lib/public/js/components/google/account-row.js +7 -7
  89. package/lib/public/js/components/google/add-account-modal.js +8 -8
  90. package/lib/public/js/components/google/gmail-setup-wizard.js +24 -24
  91. package/lib/public/js/components/google/gmail-watch-toggle.js +5 -5
  92. package/lib/public/js/components/google/index.js +6 -6
  93. package/lib/public/js/components/google/use-gmail-watch.js +1 -1
  94. package/lib/public/js/components/google/use-google-accounts.js +1 -1
  95. package/lib/public/js/components/icons.js +2 -2
  96. package/lib/public/js/components/info-tooltip.js +3 -3
  97. package/lib/public/js/components/loading-spinner.js +2 -2
  98. package/lib/public/js/components/modal-shell.js +5 -5
  99. package/lib/public/js/components/models-tab/index.js +11 -11
  100. package/lib/public/js/components/models-tab/model-picker.js +9 -9
  101. package/lib/public/js/components/models-tab/provider-auth-card.js +12 -12
  102. package/lib/public/js/components/models-tab/use-models.js +1 -1
  103. package/lib/public/js/components/models.js +18 -18
  104. package/lib/public/js/components/nodes-tab/browser-attach/index.js +5 -5
  105. package/lib/public/js/components/nodes-tab/connected-nodes/index.js +34 -32
  106. package/lib/public/js/components/nodes-tab/connected-nodes/use-connected-nodes-card.js +18 -3
  107. package/lib/public/js/components/nodes-tab/exec-allowlist/index.js +10 -10
  108. package/lib/public/js/components/nodes-tab/exec-allowlist/use-exec-allowlist.js +1 -1
  109. package/lib/public/js/components/nodes-tab/exec-config/index.js +13 -13
  110. package/lib/public/js/components/nodes-tab/exec-config/use-exec-config.js +1 -1
  111. package/lib/public/js/components/nodes-tab/index.js +2 -2
  112. package/lib/public/js/components/nodes-tab/setup-wizard/index.js +14 -14
  113. package/lib/public/js/components/nodes-tab/setup-wizard/use-setup-wizard.js +1 -1
  114. package/lib/public/js/components/nodes-tab/use-nodes-tab.js +1 -1
  115. package/lib/public/js/components/onboarding/use-welcome-codex.js +1 -1
  116. package/lib/public/js/components/onboarding/use-welcome-pairing.js +1 -1
  117. package/lib/public/js/components/onboarding/use-welcome-storage.js +1 -1
  118. package/lib/public/js/components/onboarding/welcome-config.js +3 -3
  119. package/lib/public/js/components/onboarding/welcome-form-step.js +34 -34
  120. package/lib/public/js/components/onboarding/welcome-header.js +2 -2
  121. package/lib/public/js/components/onboarding/welcome-import-step.js +22 -22
  122. package/lib/public/js/components/onboarding/welcome-pairing-step.js +15 -15
  123. package/lib/public/js/components/onboarding/welcome-placeholder-review-step.js +7 -7
  124. package/lib/public/js/components/onboarding/welcome-pre-step.js +9 -9
  125. package/lib/public/js/components/onboarding/welcome-secret-review-step.js +15 -15
  126. package/lib/public/js/components/onboarding/welcome-setup-step.js +8 -8
  127. package/lib/public/js/components/overflow-menu.js +3 -3
  128. package/lib/public/js/components/page-header.js +2 -2
  129. package/lib/public/js/components/pairings.js +14 -14
  130. package/lib/public/js/components/pane-shell.js +2 -2
  131. package/lib/public/js/components/pill-tabs.js +4 -4
  132. package/lib/public/js/components/pop-actions.js +3 -3
  133. package/lib/public/js/components/providers.js +17 -17
  134. package/lib/public/js/components/routes/agents-route.js +2 -2
  135. package/lib/public/js/components/routes/browse-route.js +2 -2
  136. package/lib/public/js/components/routes/chat-route.js +1094 -0
  137. package/lib/public/js/components/routes/cron-route.js +2 -2
  138. package/lib/public/js/components/routes/doctor-route.js +2 -2
  139. package/lib/public/js/components/routes/envars-route.js +2 -2
  140. package/lib/public/js/components/routes/general-route.js +2 -2
  141. package/lib/public/js/components/routes/index.js +1 -0
  142. package/lib/public/js/components/routes/models-route.js +2 -2
  143. package/lib/public/js/components/routes/nodes-route.js +2 -2
  144. package/lib/public/js/components/routes/providers-route.js +2 -2
  145. package/lib/public/js/components/routes/route-redirect.js +2 -2
  146. package/lib/public/js/components/routes/telegram-route.js +2 -2
  147. package/lib/public/js/components/routes/usage-route.js +2 -2
  148. package/lib/public/js/components/routes/watchdog-route.js +2 -2
  149. package/lib/public/js/components/routes/webhooks-route.js +2 -2
  150. package/lib/public/js/components/scope-picker.js +9 -9
  151. package/lib/public/js/components/secret-input.js +5 -5
  152. package/lib/public/js/components/segmented-control.js +2 -2
  153. package/lib/public/js/components/session-select-field.js +7 -7
  154. package/lib/public/js/components/sidebar-git-panel.js +3 -3
  155. package/lib/public/js/components/sidebar.js +55 -3
  156. package/lib/public/js/components/summary-stat-card.js +3 -3
  157. package/lib/public/js/components/telegram-workspace/index.js +10 -10
  158. package/lib/public/js/components/telegram-workspace/manage.js +36 -36
  159. package/lib/public/js/components/telegram-workspace/onboarding.js +73 -73
  160. package/lib/public/js/components/toast.js +8 -8
  161. package/lib/public/js/components/toggle-switch.js +2 -2
  162. package/lib/public/js/components/tooltip.js +5 -5
  163. package/lib/public/js/components/update-action-button.js +2 -2
  164. package/lib/public/js/components/update-modal.js +9 -9
  165. package/lib/public/js/components/usage-tab/constants.js +2 -2
  166. package/lib/public/js/components/usage-tab/index.js +3 -3
  167. package/lib/public/js/components/usage-tab/overview-section.js +15 -15
  168. package/lib/public/js/components/usage-tab/sessions-section.js +19 -19
  169. package/lib/public/js/components/usage-tab/use-usage-tab.js +2 -2
  170. package/lib/public/js/components/watchdog-tab/console/index.js +22 -8
  171. package/lib/public/js/components/watchdog-tab/console/use-console.js +28 -2
  172. package/lib/public/js/components/watchdog-tab/helpers.js +35 -6
  173. package/lib/public/js/components/watchdog-tab/incidents/index.js +6 -6
  174. package/lib/public/js/components/watchdog-tab/incidents/use-incidents.js +1 -1
  175. package/lib/public/js/components/watchdog-tab/index.js +4 -2
  176. package/lib/public/js/components/watchdog-tab/resource-bar.js +5 -5
  177. package/lib/public/js/components/watchdog-tab/resources/index.js +14 -3
  178. package/lib/public/js/components/watchdog-tab/resources/use-resources.js +1 -1
  179. package/lib/public/js/components/watchdog-tab/settings/index.js +97 -30
  180. package/lib/public/js/components/watchdog-tab/settings/use-settings.js +1 -1
  181. package/lib/public/js/components/watchdog-tab/terminal/index.js +3 -3
  182. package/lib/public/js/components/watchdog-tab/terminal/use-terminal.js +41 -5
  183. package/lib/public/js/components/watchdog-tab/use-watchdog-tab.js +2 -0
  184. package/lib/public/js/components/webhooks/create-webhook-modal/index.js +17 -17
  185. package/lib/public/js/components/webhooks/helpers.js +3 -3
  186. package/lib/public/js/components/webhooks/index.js +4 -4
  187. package/lib/public/js/components/webhooks/request-history/index.js +14 -14
  188. package/lib/public/js/components/webhooks/request-history/use-request-history.js +1 -1
  189. package/lib/public/js/components/webhooks/webhook-detail/index.js +41 -41
  190. package/lib/public/js/components/webhooks/webhook-detail/use-webhook-detail.js +1 -1
  191. package/lib/public/js/components/webhooks/webhook-list/index.js +11 -11
  192. package/lib/public/js/components/webhooks/webhook-list/use-webhook-list.js +1 -1
  193. package/lib/public/js/components/welcome/index.js +3 -3
  194. package/lib/public/js/components/welcome/use-welcome.js +10 -10
  195. package/lib/public/js/hooks/use-app-shell-controller.js +1 -1
  196. package/lib/public/js/hooks/use-app-shell-ui.js +1 -1
  197. package/lib/public/js/hooks/use-browse-navigation.js +14 -3
  198. package/lib/public/js/hooks/use-cached-fetch.js +1 -1
  199. package/lib/public/js/hooks/use-destination-session-selection.js +1 -1
  200. package/lib/public/js/hooks/use-hash-location.js +1 -1
  201. package/lib/public/js/hooks/useAgentSessions.js +1 -1
  202. package/lib/public/js/hooks/usePolling.js +1 -1
  203. package/lib/public/js/lib/app-navigation.js +1 -0
  204. package/lib/public/js/lib/storage-keys.js +3 -0
  205. package/lib/public/js/tailwind-config.js +39 -0
  206. package/lib/public/login.html +3 -18
  207. package/lib/public/setup.html +3 -18
  208. package/lib/server/auth-profiles.js +1 -1
  209. package/lib/server/chat-ws.js +835 -0
  210. package/lib/server/init/register-server-routes.js +2 -0
  211. package/lib/server/onboarding/validation.js +15 -10
  212. package/lib/server/routes/nodes.js +10 -2
  213. package/lib/server/routes/pairings.js +5 -1
  214. package/lib/server/routes/system.js +5 -0
  215. package/lib/server/routes/watchdog.js +15 -0
  216. package/lib/server/watchdog-terminal-ws.js +14 -1
  217. package/lib/server/watchdog.js +137 -51
  218. package/lib/server.js +38 -0
  219. package/package.json +14 -3
@@ -68,6 +68,7 @@ const registerServerRoutes = ({
68
68
  deleteOauthCallback,
69
69
  markOauthCallbackUsed,
70
70
  watchdog,
71
+ watchdogNotifier,
71
72
  getRecentEvents,
72
73
  readLogTail,
73
74
  watchdogTerminal,
@@ -212,6 +213,7 @@ const registerServerRoutes = ({
212
213
  app,
213
214
  requireAuth,
214
215
  watchdog,
216
+ watchdogNotifier,
215
217
  getRecentEvents,
216
218
  readLogTail,
217
219
  watchdogTerminal,
@@ -1,3 +1,5 @@
1
+ const { getEnvVarForApiKeyProvider } = require("../auth-profiles");
2
+
1
3
  const kAnthropicSetupTokenPrefix = "sk-ant-oat01-";
2
4
  const kAnthropicApiKeyPrefix = "sk-ant-api";
3
5
 
@@ -71,12 +73,6 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
71
73
  const githubRepoInput = String(varMap.GITHUB_WORKSPACE_REPO || "").trim();
72
74
  const selectedProvider = resolveModelProvider(modelKey);
73
75
  const hasCodexOauth = hasCodexOauthProfile();
74
- const hasAiByProvider = {
75
- anthropic: !!(varMap.ANTHROPIC_API_KEY || varMap.ANTHROPIC_TOKEN),
76
- openai: !!varMap.OPENAI_API_KEY,
77
- "openai-codex": !!hasCodexOauth,
78
- google: !!varMap.GEMINI_API_KEY,
79
- };
80
76
  const hasAnyAi = !!(
81
77
  varMap.ANTHROPIC_API_KEY ||
82
78
  varMap.ANTHROPIC_TOKEN ||
@@ -84,10 +80,19 @@ const validateOnboardingInput = ({ vars, modelKey, resolveModelProvider, hasCode
84
80
  varMap.GEMINI_API_KEY ||
85
81
  hasCodexOauth
86
82
  );
87
- const hasAi =
88
- selectedProvider in hasAiByProvider
89
- ? hasAiByProvider[selectedProvider]
90
- : hasAnyAi;
83
+ const hasAi = (() => {
84
+ if (selectedProvider === "openai-codex") {
85
+ return hasCodexOauth;
86
+ }
87
+ if (selectedProvider === "anthropic") {
88
+ return !!(varMap.ANTHROPIC_API_KEY || varMap.ANTHROPIC_TOKEN);
89
+ }
90
+ const envKey = getEnvVarForApiKeyProvider(selectedProvider);
91
+ if (envKey) {
92
+ return !!String(varMap[envKey] || "").trim();
93
+ }
94
+ return hasAnyAi;
95
+ })();
91
96
  const hasGithub = !!(githubToken && githubRepoInput);
92
97
  const hasChannel = !!(varMap.TELEGRAM_BOT_TOKEN || varMap.DISCORD_BOT_TOKEN || (varMap.SLACK_BOT_TOKEN && varMap.SLACK_APP_TOKEN));
93
98
 
@@ -10,6 +10,8 @@ const kSafeNodeIdPattern = /^[\w\-:.]+$/;
10
10
  const kNodeBrowserInvokeTimeoutMs = 30000;
11
11
  const kNodeBrowserCliTimeoutMs = 35000;
12
12
  const kNodeRouteCliTimeoutMs = 12000;
13
+ const kNodesStatusCliTimeoutMs = 5000;
14
+ const kNodesPendingCliTimeoutMs = 5000;
13
15
 
14
16
  const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
15
17
 
@@ -172,7 +174,10 @@ const registerNodeRoutes = ({
172
174
  fsModule,
173
175
  }) => {
174
176
  app.get("/api/nodes", async (_req, res) => {
175
- const statusResult = await clawCmd("nodes status --json", { quiet: true });
177
+ const statusResult = await clawCmd("nodes status --json", {
178
+ quiet: true,
179
+ timeoutMs: kNodesStatusCliTimeoutMs,
180
+ });
176
181
  if (!statusResult.ok) {
177
182
  return res.status(500).json({
178
183
  ok: false,
@@ -180,7 +185,10 @@ const registerNodeRoutes = ({
180
185
  });
181
186
  }
182
187
  const status = parseNodesStatus(statusResult.stdout);
183
- const pendingResult = await clawCmd("nodes pending --json", { quiet: true });
188
+ const pendingResult = await clawCmd("nodes pending --json", {
189
+ quiet: true,
190
+ timeoutMs: kNodesPendingCliTimeoutMs,
191
+ });
184
192
  const pending = pendingResult.ok
185
193
  ? parseNodesPending(pendingResult.stdout)
186
194
  : status.pending;
@@ -7,6 +7,7 @@ const { quoteShellArg } = require("../utils/shell");
7
7
 
8
8
  const kAllowedPairingChannels = new Set(["telegram", "discord", "slack"]);
9
9
  const kSafePairingArgPattern = /^[\w\-:.]+$/;
10
+ const kDevicesListCliTimeoutMs = 5000;
10
11
  const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
11
12
 
12
13
  const resolvePairingStorePath = ({ openclawDir, channel }) =>
@@ -208,7 +209,10 @@ const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openc
208
209
  cliAutoApproveComplete: devicePairingCache.cliAutoApproveComplete,
209
210
  });
210
211
  }
211
- const result = await clawCmd("devices list --json", { quiet: true });
212
+ const result = await clawCmd("devices list --json", {
213
+ quiet: true,
214
+ timeoutMs: kDevicesListCliTimeoutMs,
215
+ });
212
216
  if (!result.ok) {
213
217
  return res.json({ pending: [], cliAutoApproveComplete: hasCliAutoApproveMarker() });
214
218
  }
@@ -532,6 +532,10 @@ const registerSystemRoutes = ({
532
532
  const running = await isGatewayRunning();
533
533
  const repo = process.env.GITHUB_WORKSPACE_REPO || "";
534
534
  const openclawVersion = openclawVersionService.readOpenclawVersion();
535
+ const alphaclawVersion =
536
+ typeof alphaclawVersionService?.readAlphaclawVersion === "function"
537
+ ? alphaclawVersionService.readAlphaclawVersion()
538
+ : null;
535
539
  return {
536
540
  gateway: running
537
541
  ? "running"
@@ -542,6 +546,7 @@ const registerSystemRoutes = ({
542
546
  channels: getChannelStatus(),
543
547
  repo,
544
548
  openclawVersion,
549
+ alphaclawVersion,
545
550
  syncCron: getSystemCronStatus(),
546
551
  };
547
552
  };
@@ -4,6 +4,7 @@ const registerWatchdogRoutes = ({
4
4
  app,
5
5
  requireAuth,
6
6
  watchdog,
7
+ watchdogNotifier,
7
8
  getRecentEvents,
8
9
  readLogTail,
9
10
  watchdogTerminal,
@@ -76,6 +77,20 @@ const registerWatchdogRoutes = ({
76
77
  }
77
78
  });
78
79
 
80
+ app.post("/api/watchdog/test-notification", requireAuth, async (req, res) => {
81
+ try {
82
+ if (!watchdogNotifier?.notify) {
83
+ return res.status(503).json({ ok: false, error: "Notifier not available" });
84
+ }
85
+ const result = await watchdogNotifier.notify(
86
+ "*AlphaClaw test notification* — your watchdog alerts are working.",
87
+ );
88
+ res.json({ ok: true, result });
89
+ } catch (err) {
90
+ res.status(500).json({ ok: false, error: err.message });
91
+ }
92
+ });
93
+
79
94
  app.post("/api/watchdog/terminal/session", requireAuth, (req, res) => {
80
95
  try {
81
96
  const terminalSession = watchdogTerminal.createOrReuseSession();
@@ -8,6 +8,7 @@ const createWatchdogTerminalWsBridge = ({
8
8
  getGatewayUrl,
9
9
  isAuthorizedRequest,
10
10
  watchdogTerminal,
11
+ chatWsService = null,
11
12
  }) => {
12
13
  const watchdogTerminalWss = new WebSocketServer({ noServer: true });
13
14
 
@@ -84,7 +85,8 @@ const createWatchdogTerminalWsBridge = ({
84
85
  );
85
86
  if (
86
87
  requestUrl.pathname.startsWith("/openclaw") ||
87
- requestUrl.pathname === kWatchdogTerminalWsPath
88
+ requestUrl.pathname === kWatchdogTerminalWsPath ||
89
+ requestUrl.pathname === "/api/ws/chat"
88
90
  ) {
89
91
  const upgradeReq = {
90
92
  headers: req.headers,
@@ -105,6 +107,17 @@ const createWatchdogTerminalWsBridge = ({
105
107
  });
106
108
  return;
107
109
  }
110
+ if (requestUrl.pathname === "/api/ws/chat") {
111
+ if (!chatWsService || typeof chatWsService.handleUpgrade !== "function") {
112
+ socket.write(
113
+ "HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nChat websocket unavailable",
114
+ );
115
+ socket.destroy();
116
+ return;
117
+ }
118
+ chatWsService.handleUpgrade(req, socket, head);
119
+ return;
120
+ }
108
121
  proxy.ws(req, socket, head, { target: getGatewayUrl() });
109
122
  });
110
123
  };
@@ -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
@@ -74,6 +74,7 @@ const {
74
74
  } = require("./server/env");
75
75
  const {
76
76
  gatewayEnv,
77
+ getGatewayPort,
77
78
  getGatewayUrl,
78
79
  isOnboarded,
79
80
  isGatewayRunning,
@@ -120,6 +121,7 @@ const {
120
121
  const { createDoctorService } = require("./server/doctor/service");
121
122
  const { createAgentsService } = require("./server/agents/service");
122
123
  const { createOperationEventsService } = require("./server/operation-events");
124
+ const { createChatWsService } = require("./server/chat-ws");
123
125
  const { runOnboardedBootSequence } = require("./server/startup");
124
126
  const { createCronService } = require("./server/cron-service");
125
127
  const {
@@ -194,6 +196,11 @@ const openclawVersionService = createOpenclawVersionService({
194
196
  const alphaclawVersionService = createAlphaclawVersionService();
195
197
  const restartRequiredState = createRestartRequiredState({ isGatewayRunning });
196
198
  const operationEvents = createOperationEventsService();
199
+ const chatWsService = createChatWsService({
200
+ fs,
201
+ openclawDir: constants.OPENCLAW_DIR,
202
+ getGatewayPort,
203
+ });
197
204
  const cronService = createCronService({
198
205
  clawCmd,
199
206
  OPENCLAW_DIR: constants.OPENCLAW_DIR,
@@ -226,6 +233,7 @@ const watchdog = createWatchdog({
226
233
  writeEnvFile,
227
234
  reloadEnv,
228
235
  resolveSetupUrl,
236
+ resolveGatewayHealthUrl: () => `${getGatewayUrl()}/health`,
229
237
  });
230
238
  const watchdogTerminal = createWatchdogTerminalService({
231
239
  cwd: constants.OPENCLAW_DIR,
@@ -310,6 +318,7 @@ const { isAuthorizedRequest, gmailWatchService } = registerServerRoutes({
310
318
  deleteOauthCallback,
311
319
  markOauthCallbackUsed,
312
320
  watchdog,
321
+ watchdogNotifier,
313
322
  getRecentEvents,
314
323
  readLogTail,
315
324
  watchdogTerminal,
@@ -326,6 +335,34 @@ const { isAuthorizedRequest, gmailWatchService } = registerServerRoutes({
326
335
  SETUP_API_PREFIXES,
327
336
  webhookMiddleware,
328
337
  });
338
+ app.get("/api/chat/history", async (req, res) => {
339
+ const upgradeReq = {
340
+ headers: req.headers,
341
+ path: req.path,
342
+ query: req.query || {},
343
+ };
344
+ if (!isAuthorizedRequest(upgradeReq)) {
345
+ return res.status(401).json({ ok: false, error: "Unauthorized" });
346
+ }
347
+ const sessionKey = String(req.query?.sessionKey || "").trim();
348
+ if (!sessionKey) {
349
+ return res.status(400).json({ ok: false, error: "sessionKey is required" });
350
+ }
351
+ try {
352
+ const { messages, rawHistory } = await chatWsService.fetchHistory(sessionKey);
353
+ return res.json({
354
+ ok: true,
355
+ sessionKey,
356
+ messages,
357
+ rawHistory,
358
+ });
359
+ } catch (err) {
360
+ return res.status(502).json({
361
+ ok: false,
362
+ error: err?.message || "Could not load chat history",
363
+ });
364
+ }
365
+ });
329
366
 
330
367
  const server = http.createServer(app);
331
368
  createWatchdogTerminalWsBridge({
@@ -334,6 +371,7 @@ createWatchdogTerminalWsBridge({
334
371
  getGatewayUrl,
335
372
  isAuthorizedRequest,
336
373
  watchdogTerminal,
374
+ chatWsService,
337
375
  });
338
376
 
339
377
  startServerLifecycle({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.8.1",
3
+ "version": "0.8.3-beta.0",
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"