@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
@@ -1,5 +1,5 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
  import {
4
4
  formatDurationCompactMs,
5
5
  formatInteger,
@@ -51,14 +51,14 @@ const SessionInlineDetail = ({
51
51
  if (loadingDetail) {
52
52
  return html`
53
53
  <div class="ac-history-body">
54
- <p class="text-xs text-gray-500">Loading session detail...</p>
54
+ <p class="text-xs text-fg-muted">Loading session detail...</p>
55
55
  </div>
56
56
  `;
57
57
  }
58
58
  if (!detail) {
59
59
  return html`
60
60
  <div class="ac-history-body">
61
- <p class="text-xs text-gray-500">Session detail not available.</p>
61
+ <p class="text-xs text-fg-muted">Session detail not available.</p>
62
62
  </div>
63
63
  `;
64
64
  }
@@ -68,20 +68,20 @@ const SessionInlineDetail = ({
68
68
  return html`
69
69
  <div class="ac-history-body space-y-3 border-0 pt-0 mt-0">
70
70
  <div>
71
- <p class="text-[11px] text-gray-500 mb-1">Session key</p>
72
- <p class="text-xs text-gray-300 font-mono break-all">${sessionKeyValue || "n/a"}</p>
71
+ <p class="text-[11px] text-fg-muted mb-1">Session key</p>
72
+ <p class="text-xs text-body font-mono break-all">${sessionKeyValue || "n/a"}</p>
73
73
  </div>
74
74
  <div class="mt-1.5">
75
- <p class="text-[11px] text-gray-500 mb-1">Model breakdown</p>
75
+ <p class="text-[11px] text-fg-muted mb-1">Model breakdown</p>
76
76
  ${(detail.modelBreakdown || []).length === 0
77
- ? html`<p class="text-xs text-gray-500">No model usage recorded.</p>`
77
+ ? html`<p class="text-xs text-fg-muted">No model usage recorded.</p>`
78
78
  : html`
79
79
  <div class="space-y-1.5">
80
80
  ${(detail.modelBreakdown || []).map(
81
81
  (row) => html`
82
- <div class="flex items-center justify-between gap-3 text-xs px-1 py-0.5 rounded hover:bg-white/5 transition-colors">
83
- <span class="text-gray-300 truncate">${row.model || "unknown"}</span>
84
- <span class="inline-flex items-center gap-3 text-gray-500 shrink-0">
82
+ <div class="flex items-center justify-between gap-3 text-xs px-1 py-0.5 rounded hover:bg-surface transition-colors">
83
+ <span class="text-body truncate">${row.model || "unknown"}</span>
84
+ <span class="inline-flex items-center gap-3 text-fg-muted shrink-0">
85
85
  <span>${formatInteger(row.totalTokens)} tok</span>
86
86
  <span>${formatUsd(row.totalCost)}</span>
87
87
  <span>${formatCountLabel(row.turnCount, "turn", "turns")}</span>
@@ -93,16 +93,16 @@ const SessionInlineDetail = ({
93
93
  `}
94
94
  </div>
95
95
  <div>
96
- <p class="text-[11px] text-gray-500 mb-1">Tool usage</p>
96
+ <p class="text-[11px] text-fg-muted mb-1">Tool usage</p>
97
97
  ${(detail.toolUsage || []).length === 0
98
- ? html`<p class="text-xs text-gray-500">No tool calls recorded.</p>`
98
+ ? html`<p class="text-xs text-fg-muted">No tool calls recorded.</p>`
99
99
  : html`
100
100
  <div class="space-y-1.5">
101
101
  ${(detail.toolUsage || []).map(
102
102
  (row) => html`
103
- <div class="flex items-center justify-between gap-3 text-xs px-1 py-0.5 rounded hover:bg-white/5 transition-colors">
104
- <span class="text-gray-300 truncate">${row.toolName}</span>
105
- <span class="inline-flex items-center gap-3 text-gray-500 shrink-0">
103
+ <div class="flex items-center justify-between gap-3 text-xs px-1 py-0.5 rounded hover:bg-surface transition-colors">
104
+ <span class="text-body truncate">${row.toolName}</span>
105
+ <span class="inline-flex items-center gap-3 text-fg-muted shrink-0">
106
106
  <span>${formatCountLabel(row.callCount, "call", "calls")}</span>
107
107
  <span>${(Number(row.errorRate || 0) * 100).toFixed(1)}% err</span>
108
108
  <span>${formatDurationCompactMs(row.avgDurationMs)}</span>
@@ -129,7 +129,7 @@ export const SessionsSection = ({
129
129
  <h2 class="card-label text-xs mb-3">Sessions</h2>
130
130
  <div class="ac-history-list">
131
131
  ${sessions.length === 0
132
- ? html`<p class="text-xs text-gray-500">
132
+ ? html`<p class="text-xs text-fg-muted">
133
133
  ${loadingSessions ? "Loading sessions..." : "No sessions recorded yet."}
134
134
  </p>`
135
135
  : sessions.map(
@@ -143,13 +143,13 @@ export const SessionsSection = ({
143
143
  onToggleSession(itemSessionId, isOpen);
144
144
  }}
145
145
  >
146
- <summary class="ac-history-summary hover:bg-white/5 transition-colors">
146
+ <summary class="ac-history-summary hover:bg-surface transition-colors">
147
147
  <div class="ac-history-summary-row">
148
148
  <span class="inline-flex items-center gap-2 min-w-0">
149
149
  <span class="ac-history-toggle shrink-0" aria-hidden="true">▸</span>
150
150
  <${SessionBadges} session=${item} />
151
151
  </span>
152
- <span class="inline-flex items-center gap-3 shrink-0 text-xs text-gray-500">
152
+ <span class="inline-flex items-center gap-3 shrink-0 text-xs text-fg-muted">
153
153
  <span>${formatInteger(item.totalTokens)} tok</span>
154
154
  <span>${formatUsd(item.totalCost)}</span>
155
155
  <span>
@@ -4,7 +4,8 @@ import {
4
4
  useMemo,
5
5
  useRef,
6
6
  useState,
7
- } from "https://esm.sh/preact/hooks";
7
+ } from "preact/hooks";
8
+ import Chart from "chart.js/auto";
8
9
  import {
9
10
  fetchUsageSessionDetail,
10
11
  fetchUsageSessions,
@@ -233,7 +234,6 @@ export const useUsageTab = ({ sessionId = "" }) => {
233
234
 
234
235
  useEffect(() => {
235
236
  const canvas = overviewCanvasRef.current;
236
- const Chart = window.Chart;
237
237
  if (!canvas || !Chart) return;
238
238
  if (overviewChartRef.current) {
239
239
  overviewChartRef.current.destroy();
@@ -1,5 +1,6 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
+ import { FileCopyLineIcon } from "../../icons.js";
3
4
  import {
4
5
  kWatchdogConsoleTabLogs,
5
6
  kWatchdogConsoleTabTerminal,
@@ -22,26 +23,28 @@ export const WatchdogConsoleCard = ({
22
23
  logsRef = null,
23
24
  logs = "",
24
25
  loadingLogs = true,
26
+ copyingAll = false,
25
27
  terminalPanelRef = null,
26
28
  terminalHostRef = null,
27
29
  terminalInstanceRef = null,
28
30
  logsPanelHeightPx = 320,
31
+ onCopyAll = () => {},
29
32
  }) => html`
30
33
  <div class="bg-surface border border-border rounded-xl p-4">
31
34
  <div class="flex items-center justify-between gap-2 mb-3">
32
35
  <div
33
- class="inline-flex items-center rounded-lg border border-border bg-black/20 p-0.5"
36
+ class="inline-flex items-center rounded-lg border border-border bg-field p-0.5"
34
37
  >
35
38
  <button
36
39
  type="button"
37
- class=${`px-2.5 py-1 text-xs rounded-md ${activeConsoleTab === kWatchdogConsoleTabLogs ? "bg-surface text-gray-100" : "text-gray-400 hover:text-gray-200"}`}
40
+ class=${`px-2.5 py-1 text-xs rounded-md ${activeConsoleTab === kWatchdogConsoleTabLogs ? "bg-surface text-bright" : "text-fg-muted hover:text-body"}`}
38
41
  onClick=${() => onSelectConsoleTab(kWatchdogConsoleTabLogs)}
39
42
  >
40
43
  Logs
41
44
  </button>
42
45
  <button
43
46
  type="button"
44
- class=${`px-2.5 py-1 text-xs rounded-md ${activeConsoleTab === kWatchdogConsoleTabTerminal ? "bg-surface text-gray-100" : "text-gray-400 hover:text-gray-200"}`}
47
+ class=${`px-2.5 py-1 text-xs rounded-md ${activeConsoleTab === kWatchdogConsoleTabTerminal ? "bg-surface text-bright" : "text-fg-muted hover:text-body"}`}
45
48
  onClick=${() => onSelectConsoleTab(kWatchdogConsoleTabTerminal)}
46
49
  >
47
50
  Terminal
@@ -50,7 +53,7 @@ export const WatchdogConsoleCard = ({
50
53
  <div class="flex items-center gap-2">
51
54
  ${activeConsoleTab === kWatchdogConsoleTabLogs
52
55
  ? html`
53
- <label class="inline-flex items-center gap-2 text-xs text-gray-400">
56
+ <label class="inline-flex items-center gap-2 text-xs text-fg-muted">
54
57
  <input
55
58
  type="checkbox"
56
59
  checked=${stickToBottom}
@@ -65,7 +68,7 @@ export const WatchdogConsoleCard = ({
65
68
  ${terminalUiSettling
66
69
  ? null
67
70
  : html`
68
- <span class="text-xs text-gray-500">
71
+ <span class="text-xs text-fg-muted">
69
72
  ${connectingTerminal
70
73
  ? "Connecting..."
71
74
  : terminalEnded
@@ -93,11 +96,22 @@ export const WatchdogConsoleCard = ({
93
96
  <div class=${activeConsoleTab === kWatchdogConsoleTabLogs ? "" : "hidden"}>
94
97
  <pre
95
98
  ref=${logsRef}
96
- class="watchdog-logs-panel bg-black/40 border border-border rounded-lg p-3 overflow-auto text-xs text-gray-300 whitespace-pre-wrap break-words"
99
+ class="watchdog-logs-panel bg-field border border-border rounded-lg p-3 overflow-auto text-xs text-body whitespace-pre-wrap break-words"
97
100
  style=${{ height: `${logsPanelHeightPx}px` }}
98
101
  >
99
102
  ${loadingLogs ? "Loading logs..." : logs || "No logs yet."}</pre
100
103
  >
104
+ <div class="mt-3 flex justify-end">
105
+ <button
106
+ type="button"
107
+ class=${`ac-btn-secondary text-xs px-2.5 py-1 rounded-lg inline-flex items-center gap-1.5 ${copyingAll ? "opacity-50 cursor-not-allowed" : ""}`}
108
+ onClick=${onCopyAll}
109
+ disabled=${copyingAll}
110
+ >
111
+ <${FileCopyLineIcon} className="w-3.5 h-3.5" />
112
+ ${copyingAll ? "Copying..." : "Copy all"}
113
+ </button>
114
+ </div>
101
115
  </div>
102
116
  <div
103
117
  class=${activeConsoleTab === kWatchdogConsoleTabTerminal
@@ -1,8 +1,11 @@
1
- import { useEffect, useRef, useState } from "https://esm.sh/preact/hooks";
1
+ import { useEffect, useRef, useState } from "preact/hooks";
2
2
  import { fetchWatchdogLogs } from "../../../lib/api.js";
3
+ import { copyTextToClipboard } from "../../../lib/clipboard.js";
3
4
  import { readUiSettings, writeUiSettings } from "../../../lib/ui-settings.js";
5
+ import { showToast } from "../../toast.js";
4
6
  import {
5
7
  clampWatchdogLogsPanelHeight,
8
+ formatWatchdogCopyAllText,
6
9
  kWatchdogConsoleTabLogs,
7
10
  kWatchdogConsoleTabTerminal,
8
11
  kWatchdogConsoleTabUiSettingKey,
@@ -12,9 +15,11 @@ import {
12
15
  } from "../helpers.js";
13
16
  import { useWatchdogTerminal } from "../terminal/use-terminal.js";
14
17
 
15
- export const useWatchdogConsole = () => {
18
+ export const useWatchdogConsole = ({
19
+ } = {}) => {
16
20
  const [logs, setLogs] = useState("");
17
21
  const [loadingLogs, setLoadingLogs] = useState(true);
22
+ const [copyingAll, setCopyingAll] = useState(false);
18
23
  const [stickToBottom, setStickToBottom] = useState(true);
19
24
  const [activeConsoleTab, setActiveConsoleTab] = useState(() => {
20
25
  const settings = readUiSettings();
@@ -120,9 +125,29 @@ export const useWatchdogConsole = () => {
120
125
  setActiveConsoleTab(kWatchdogConsoleTabTerminal);
121
126
  };
122
127
 
128
+ const handleCopyAll = async () => {
129
+ if (copyingAll) return;
130
+ setCopyingAll(true);
131
+ try {
132
+ const text = formatWatchdogCopyAllText({
133
+ logs,
134
+ });
135
+ const copied = await copyTextToClipboard(text);
136
+ if (!copied) {
137
+ throw new Error("Could not copy watchdog export");
138
+ }
139
+ showToast("Copied watchdog logs", "success");
140
+ } catch (error) {
141
+ showToast(error.message || "Could not copy watchdog export", "error");
142
+ } finally {
143
+ setCopyingAll(false);
144
+ }
145
+ };
146
+
123
147
  return {
124
148
  logs,
125
149
  loadingLogs,
150
+ copyingAll,
126
151
  stickToBottom,
127
152
  setStickToBottom,
128
153
  activeConsoleTab,
@@ -132,6 +157,7 @@ export const useWatchdogConsole = () => {
132
157
  terminalPanelRef,
133
158
  terminalHostRef,
134
159
  onRestartTerminalSession,
160
+ onCopyAll: handleCopyAll,
135
161
  ...terminal,
136
162
  };
137
163
  };
@@ -4,18 +4,28 @@ export const kWatchdogConsoleTabUiSettingKey = "watchdogConsoleTab";
4
4
  export const kWatchdogLogsPanelHeightUiSettingKey = "watchdogLogsPanelHeightPx";
5
5
  export const kWatchdogLogsPanelDefaultHeightPx = 320;
6
6
  export const kWatchdogLogsPanelMinHeightPx = 160;
7
- export const kXtermCssUrl =
8
- "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css";
7
+ export const kXtermCssUrl = "/css/vendor/xterm.css";
9
8
  export const kWatchdogTerminalWsPath = "/api/watchdog/terminal/ws";
10
9
 
11
10
  let xtermModulesPromise = null;
12
11
 
13
12
  export const loadXtermModules = () => {
14
13
  if (!xtermModulesPromise) {
15
- xtermModulesPromise = Promise.all([
16
- import("https://esm.sh/@xterm/xterm@5.5.0"),
17
- import("https://esm.sh/@xterm/addon-fit@0.10.0"),
18
- ]);
14
+ xtermModulesPromise = Promise.all([import("@xterm/xterm"), import("@xterm/addon-fit")]).then(
15
+ ([xtermModule, fitAddonModule]) => {
16
+ const Terminal =
17
+ xtermModule?.Terminal || xtermModule?.default?.Terminal || null;
18
+ const FitAddon =
19
+ fitAddonModule?.FitAddon || fitAddonModule?.default?.FitAddon || null;
20
+ if (typeof Terminal !== "function") {
21
+ throw new Error("Xterm Terminal export not found");
22
+ }
23
+ if (typeof FitAddon !== "function") {
24
+ throw new Error("Xterm FitAddon export not found");
25
+ }
26
+ return { Terminal, FitAddon };
27
+ },
28
+ );
19
29
  }
20
30
  return xtermModulesPromise;
21
31
  };
@@ -104,3 +114,22 @@ export const getIncidentStatusTone = (event) => {
104
114
  label: "Unknown",
105
115
  };
106
116
  };
117
+
118
+ export const formatWatchdogCopyAllText = ({
119
+ logs = "",
120
+ generatedAt = null,
121
+ } = {}) => {
122
+ const sections = [];
123
+ const generatedAtLabel =
124
+ generatedAt instanceof Date && !Number.isNaN(generatedAt.getTime())
125
+ ? generatedAt.toISOString()
126
+ : new Date().toISOString();
127
+
128
+ sections.push(`# AlphaClaw Watchdog Export`);
129
+ sections.push(`Generated at: ${generatedAtLabel}`);
130
+
131
+ sections.push(`## Gateway Logs`);
132
+ sections.push(String(logs || "").trim() || "No logs yet.");
133
+
134
+ return sections.join("\n\n").trim();
135
+ };
@@ -1,5 +1,5 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
  import { getIncidentStatusTone } from "../helpers.js";
4
4
 
5
5
  const html = htm.bind(h);
@@ -11,13 +11,13 @@ export const WatchdogIncidentsCard = ({
11
11
  <div class="bg-surface border border-border rounded-xl p-4">
12
12
  <div class="flex items-center justify-between gap-2 mb-3">
13
13
  <h2 class="card-label">Recent incidents</h2>
14
- <button class="text-xs text-gray-400 hover:text-gray-200" onclick=${onRefresh}>
14
+ <button class="text-xs text-fg-muted hover:text-body" onclick=${onRefresh}>
15
15
  Refresh
16
16
  </button>
17
17
  </div>
18
18
  <div class="ac-history-list">
19
19
  ${events.length === 0 &&
20
- html`<p class="text-xs text-gray-500">No incidents recorded.</p>`}
20
+ html`<p class="text-xs text-fg-muted">No incidents recorded.</p>`}
21
21
  ${events.map((event) => {
22
22
  const tone = getIncidentStatusTone(event);
23
23
  return html`
@@ -40,9 +40,9 @@ export const WatchdogIncidentsCard = ({
40
40
  ></span>
41
41
  </div>
42
42
  </summary>
43
- <div class="ac-history-body text-xs text-gray-400">
43
+ <div class="ac-history-body text-xs text-fg-muted">
44
44
  <div>Source: ${event.source || "unknown"}</div>
45
- <pre class="mt-2 bg-black/30 rounded p-2 whitespace-pre-wrap break-words">
45
+ <pre class="mt-2 bg-field rounded p-2 whitespace-pre-wrap break-words">
46
46
  ${typeof event.details === "string"
47
47
  ? event.details
48
48
  : JSON.stringify(event.details || {}, null, 2)}</pre
@@ -1,4 +1,4 @@
1
- import { useEffect } from "https://esm.sh/preact/hooks";
1
+ import { useEffect } from "preact/hooks";
2
2
  import { usePolling } from "../../../hooks/usePolling.js";
3
3
  import { fetchWatchdogEvents } from "../../../lib/api.js";
4
4
 
@@ -1,5 +1,5 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
  import { Gateway } from "../gateway.js";
4
4
  import { useWatchdogTab } from "./use-watchdog-tab.js";
5
5
  import { WatchdogResourcesCard } from "./resources/index.js";
@@ -69,10 +69,12 @@ export const WatchdogTab = ({
69
69
  logsRef=${state.logsRef}
70
70
  logs=${state.logs}
71
71
  loadingLogs=${state.loadingLogs}
72
+ copyingAll=${state.copyingAll}
72
73
  terminalPanelRef=${state.terminalPanelRef}
73
74
  terminalHostRef=${state.terminalHostRef}
74
75
  terminalInstanceRef=${state.terminalInstanceRef}
75
76
  logsPanelHeightPx=${state.logsPanelHeightPx}
77
+ onCopyAll=${state.onCopyAll}
76
78
  />
77
79
 
78
80
  <${WatchdogIncidentsCard}
@@ -1,5 +1,5 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
 
4
4
  const html = htm.bind(h);
5
5
 
@@ -21,7 +21,7 @@ export const ResourceBar = ({
21
21
  onclick=${onToggle || undefined}
22
22
  >
23
23
  <span
24
- class=${`text-xs text-gray-400 ${onToggle ? "group-hover:text-gray-200 transition-colors" : ""}`}
24
+ class=${`text-xs text-fg-muted ${onToggle ? "group-hover:text-body transition-colors" : ""}`}
25
25
  >${label}</span
26
26
  >
27
27
  <div
@@ -53,7 +53,7 @@ export const ResourceBar = ({
53
53
  `}
54
54
  </div>
55
55
  <div class="flex flex-wrap items-center gap-x-3 mt-2.5">
56
- <span class="text-xs text-gray-500 font-mono flex-1">${detail}</span>
56
+ <span class="text-xs text-fg-muted font-mono flex-1">${detail}</span>
57
57
  ${expanded &&
58
58
  segments &&
59
59
  segments
@@ -61,7 +61,7 @@ export const ResourceBar = ({
61
61
  .map(
62
62
  (segment) => html`
63
63
  <span
64
- class="inline-flex items-center gap-1 text-xs text-gray-500 font-mono"
64
+ class="inline-flex items-center gap-1 text-xs text-fg-muted font-mono"
65
65
  >
66
66
  <span
67
67
  class="inline-block w-1.5 h-1.5 rounded-full"
@@ -1,7 +1,8 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
  import { formatBytes } from "../helpers.js";
4
4
  import { ResourceBar } from "../resource-bar.js";
5
+ import { Tooltip } from "../../tooltip.js";
5
6
 
6
7
  const html = htm.bind(h);
7
8
 
@@ -11,6 +12,16 @@ export const WatchdogResourcesCard = ({
11
12
  onSetMemoryExpanded = () => {},
12
13
  }) => {
13
14
  if (!resources) return null;
15
+ const diskLabel = resources.disk?.path
16
+ ? html`
17
+ <${Tooltip}
18
+ text=${resources.disk.path}
19
+ widthClass="w-auto max-w-80 whitespace-normal break-all"
20
+ >
21
+ <span class="inline-block cursor-help">Disk</span>
22
+ </${Tooltip}>
23
+ `
24
+ : "Disk";
14
25
  const memorySegments = (() => {
15
26
  const processes = resources.processes;
16
27
  const totalBytes = resources.memory?.totalBytes;
@@ -67,7 +78,7 @@ export const WatchdogResourcesCard = ({
67
78
  onToggle=${() => onSetMemoryExpanded(true)}
68
79
  />
69
80
  <${ResourceBar}
70
- label=${`Disk${resources.disk?.path ? ` (${resources.disk.path})` : ""}`}
81
+ label=${diskLabel}
71
82
  percent=${resources.disk?.percent}
72
83
  detail=${`${formatBytes(resources.disk?.usedBytes)} / ${formatBytes(resources.disk?.totalBytes)}`}
73
84
  />
@@ -1,4 +1,4 @@
1
- import { useState } from "https://esm.sh/preact/hooks";
1
+ import { useState } from "preact/hooks";
2
2
  import { usePolling } from "../../../hooks/usePolling.js";
3
3
  import { fetchWatchdogResources } from "../../../lib/api.js";
4
4
 
@@ -1,7 +1,9 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import { useState } from "preact/hooks";
3
+ import htm from "htm";
3
4
  import { InfoTooltip } from "../../info-tooltip.js";
4
5
  import { ToggleSwitch } from "../../toggle-switch.js";
6
+ import { showToast } from "../../toast.js";
5
7
 
6
8
  const html = htm.bind(h);
7
9
 
@@ -10,35 +12,100 @@ export const WatchdogSettingsCard = ({
10
12
  savingSettings = false,
11
13
  onToggleAutoRepair = () => {},
12
14
  onToggleNotifications = () => {},
13
- }) => html`
14
- <div class="bg-surface border border-border rounded-xl p-4">
15
- <div class="flex items-center justify-between gap-3">
16
- <div class="inline-flex items-center gap-2 text-xs text-gray-400">
17
- <span>Auto-repair</span>
18
- <${InfoTooltip}
19
- text="Automatically runs OpenClaw doctor repair when watchdog detects gateway health failures or crash loops."
15
+ }) => {
16
+ const [testing, setTesting] = useState(false);
17
+ const [testResult, setTestResult] = useState(null);
18
+
19
+ const handleTestNotification = async () => {
20
+ setTesting(true);
21
+ setTestResult(null);
22
+ try {
23
+ const res = await fetch("/api/watchdog/test-notification", { method: "POST" });
24
+ const data = await res.json();
25
+ if (!data?.ok) {
26
+ setTestResult(data);
27
+ return;
28
+ }
29
+
30
+ const channels = data.result?.channels || data.result || {};
31
+ const parts = [];
32
+ for (const channel of ["telegram", "discord", "slack"]) {
33
+ const ch = channels[channel];
34
+ if (!ch || ch.skipped) continue;
35
+ if (ch.sent > 0) parts.push(`${channel}: ${ch.sent} sent`);
36
+ if (ch.failed > 0) parts.push(`${channel}: ${ch.failed} failed`);
37
+ }
38
+
39
+ if (parts.length === 0) {
40
+ showToast("No channels configured", "warning");
41
+ return;
42
+ }
43
+
44
+ const hasFailures = parts.some((part) => part.includes("failed"));
45
+ showToast(
46
+ hasFailures ? parts.join(", ") : `Test notification sent: ${parts.join(", ")}`,
47
+ hasFailures ? "warning" : "success",
48
+ );
49
+ } catch (err) {
50
+ setTestResult({ ok: false, error: err.message });
51
+ } finally {
52
+ setTesting(false);
53
+ }
54
+ };
55
+
56
+ const formatResult = (result) => {
57
+ if (!result) return null;
58
+ return html`<span class="text-status-error-muted text-xs">
59
+ ${result.error || "Failed"}
60
+ </span>`;
61
+ };
62
+
63
+ return html`
64
+ <div class="bg-surface border border-border rounded-xl p-4">
65
+ <div class="flex items-center justify-between gap-3">
66
+ <div class="inline-flex items-center gap-2 text-xs text-fg-muted">
67
+ <span>Auto-repair</span>
68
+ <${InfoTooltip}
69
+ text="Automatically runs OpenClaw doctor repair when watchdog detects gateway health failures or crash loops."
70
+ />
71
+ </div>
72
+ <${ToggleSwitch}
73
+ checked=${!!settings.autoRepair}
74
+ disabled=${savingSettings}
75
+ onChange=${onToggleAutoRepair}
76
+ label=""
20
77
  />
21
78
  </div>
22
- <${ToggleSwitch}
23
- checked=${!!settings.autoRepair}
24
- disabled=${savingSettings}
25
- onChange=${onToggleAutoRepair}
26
- label=""
27
- />
28
- </div>
29
- <div class="flex items-center justify-between gap-3 mt-3">
30
- <div class="inline-flex items-center gap-2 text-xs text-gray-400">
31
- <span>Notifications</span>
32
- <${InfoTooltip}
33
- text="Sends channel notices for watchdog alerts and auto-repair outcomes."
34
- />
79
+ <div class="flex items-center justify-between gap-3 mt-3">
80
+ <div class="inline-flex items-center gap-2 text-xs text-fg-muted">
81
+ <span>Notifications</span>
82
+ <${InfoTooltip}
83
+ text="Sends channel notices for watchdog alerts and auto-repair outcomes."
84
+ />
85
+ </div>
86
+ <div class="flex items-center gap-2">
87
+ <button
88
+ class=${`text-xs px-2 py-1 rounded-lg ac-btn-ghost disabled:opacity-50 disabled:cursor-not-allowed ${
89
+ settings.notificationsEnabled ? "" : "invisible pointer-events-none"
90
+ }`}
91
+ onClick=${handleTestNotification}
92
+ disabled=${testing || savingSettings || !settings.notificationsEnabled}
93
+ aria-hidden=${!settings.notificationsEnabled}
94
+ tabIndex=${settings.notificationsEnabled ? 0 : -1}
95
+ >
96
+ ${testing ? "Sending..." : "Test"}
97
+ </button>
98
+ <${ToggleSwitch}
99
+ checked=${!!settings.notificationsEnabled}
100
+ disabled=${savingSettings}
101
+ onChange=${onToggleNotifications}
102
+ label=""
103
+ />
104
+ </div>
35
105
  </div>
36
- <${ToggleSwitch}
37
- checked=${!!settings.notificationsEnabled}
38
- disabled=${savingSettings}
39
- onChange=${onToggleNotifications}
40
- label=""
41
- />
106
+ ${testResult
107
+ ? html`<div class="mt-2">${formatResult(testResult)}</div>`
108
+ : null}
42
109
  </div>
43
- </div>
44
- `;
110
+ `;
111
+ };
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "https://esm.sh/preact/hooks";
1
+ import { useEffect, useState } from "preact/hooks";
2
2
  import {
3
3
  fetchWatchdogSettings,
4
4
  triggerWatchdogRepair,
@@ -1,5 +1,5 @@
1
- import { h } from "https://esm.sh/preact";
2
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import htm from "htm";
3
3
 
4
4
  const html = htm.bind(h);
5
5
 
@@ -11,7 +11,7 @@ export const WatchdogTerminal = ({
11
11
  }) => html`
12
12
  <div
13
13
  ref=${panelRef}
14
- class="watchdog-logs-panel bg-black/40 border border-border rounded-lg p-3 overflow-hidden"
14
+ class="watchdog-logs-panel bg-field border border-border rounded-lg p-3 overflow-hidden"
15
15
  style=${{ height: `${panelHeightPx}px` }}
16
16
  onClick=${() => terminalInstanceRef?.current?.focus()}
17
17
  >