@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
@@ -1,6 +1,6 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useMemo } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import { useMemo } from "preact/hooks";
3
+ import htm from "htm";
4
4
  import { PageHeader } from "../page-header.js";
5
5
  import { LoadingSpinner } from "../loading-spinner.js";
6
6
  import { ActionButton } from "../action-button.js";
@@ -151,7 +151,7 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
151
151
  if (!ready) {
152
152
  const loadingBody = html`
153
153
  <div class="bg-surface border border-border rounded-xl p-4">
154
- <div class="flex items-center gap-2 text-sm text-gray-400">
154
+ <div class="flex items-center gap-2 text-sm text-fg-muted">
155
155
  <${LoadingSpinner} className="h-4 w-4" />
156
156
  Loading model settings...
157
157
  </div>
@@ -173,7 +173,7 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
173
173
  <h2 class="card-label">Available Models</h2>
174
174
 
175
175
  ${configuredModelEntries.length === 0
176
- ? html`<p class="text-xs text-gray-500">
176
+ ? html`<p class="text-xs text-fg-muted">
177
177
  No models configured. Add a model below.
178
178
  </p>`
179
179
  : html`
@@ -181,10 +181,10 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
181
181
  ${configuredModelEntries.map(
182
182
  (entry) => html`
183
183
  <div
184
- class="flex items-center justify-between py-1.5 px-2 rounded-lg hover:bg-white/5"
184
+ class="flex items-center justify-between py-1.5 px-2 rounded-lg hover:bg-surface"
185
185
  >
186
186
  <div class="flex items-center gap-2 min-w-0">
187
- <span class="text-sm text-gray-200 truncate"
187
+ <span class="text-sm text-body truncate"
188
188
  >${entry.label}</span
189
189
  >
190
190
  ${entry.isPrimary
@@ -193,7 +193,7 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
193
193
  ? html`
194
194
  <button
195
195
  onclick=${() => setPrimaryModel(entry.key)}
196
- class="text-xs px-2 py-0.5 rounded-full text-gray-500 hover:text-gray-300 hover:bg-white/5"
196
+ class="text-xs px-2 py-0.5 rounded-full text-fg-muted hover:text-body hover:bg-surface"
197
197
  >
198
198
  Set primary
199
199
  </button>
@@ -202,7 +202,7 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
202
202
  </div>
203
203
  <button
204
204
  onclick=${() => removeModel(entry.key)}
205
- class="text-xs text-gray-600 hover:text-red-400 shrink-0 px-1"
205
+ class="text-xs text-fg-dim hover:text-status-error-muted shrink-0 px-1"
206
206
  >
207
207
  Remove
208
208
  </button>
@@ -225,11 +225,11 @@ export const Models = ({ onRestartRequired = () => {}, agentId, embedded = false
225
225
  </div>
226
226
 
227
227
  ${loading
228
- ? html`<p class="text-xs text-gray-600">
228
+ ? html`<p class="text-xs text-fg-dim">
229
229
  Loading model catalog...
230
230
  </p>`
231
231
  : error
232
- ? html`<p class="text-xs text-gray-600">${error}</p>`
232
+ ? html`<p class="text-xs text-fg-dim">${error}</p>`
233
233
  : null}
234
234
  </div>
235
235
 
@@ -1,6 +1,6 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useState, useMemo, useRef, useEffect } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import { useState, useMemo, useRef, useEffect } from "preact/hooks";
3
+ import htm from "htm";
4
4
  import {
5
5
  getModelProvider,
6
6
  getAuthProviderFromModelProvider,
@@ -156,7 +156,7 @@ export const SearchableModelPicker = ({
156
156
  setOpen(true);
157
157
  }}
158
158
  onKeyDown=${handleKeyDown}
159
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 disabled:opacity-50 disabled:cursor-not-allowed"
159
+ class="w-full bg-field border border-border rounded-lg px-3 py-2 text-sm text-body outline-none focus:border-fg-muted disabled:opacity-50 disabled:cursor-not-allowed"
160
160
  />
161
161
  ${open && !disabled
162
162
  ? html`
@@ -169,7 +169,7 @@ export const SearchableModelPicker = ({
169
169
  (group, index) => html`
170
170
  <div key=${group.provider}>
171
171
  <div
172
- class=${`sticky top-0 z-10 h-[22px] px-3 text-[12px] font-semibold tracking-wide text-gray-400 bg-[#151922] border-b border-border flex items-center ${
172
+ class=${`sticky top-0 z-10 h-[22px] px-3 text-[12px] font-semibold tracking-wide text-fg-muted bg-[#151922] border-b border-border flex items-center ${
173
173
  index > 0 ? "border-t border-border" : ""
174
174
  }`}
175
175
  >
@@ -182,13 +182,13 @@ export const SearchableModelPicker = ({
182
182
  type="button"
183
183
  onMouseDown=${(event) => event.preventDefault()}
184
184
  onClick=${() => handleSelect(model.key)}
185
- class="w-full text-left px-3 py-2 hover:bg-white/5 border-b border-border last:border-b-0"
185
+ class="w-full text-left px-3 py-2 hover:bg-surface border-b border-border last:border-b-0"
186
186
  >
187
187
  <div class="flex flex-col gap-1">
188
- <div class="text-sm text-gray-200">
188
+ <div class="text-sm text-body">
189
189
  ${getModelDisplayLabel(model)}
190
190
  </div>
191
- <div class="text-xs text-gray-500 font-mono">
191
+ <div class="text-xs text-fg-muted font-mono">
192
192
  ${model.key}
193
193
  </div>
194
194
  </div>
@@ -199,7 +199,7 @@ export const SearchableModelPicker = ({
199
199
  `,
200
200
  )
201
201
  : html`
202
- <div class="px-3 py-3 text-xs text-gray-500">
202
+ <div class="px-3 py-3 text-xs text-fg-muted">
203
203
  No models match that search.
204
204
  </div>
205
205
  `}
@@ -1,6 +1,6 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useState, useRef, useEffect } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import { useState, useRef, useEffect } from "preact/hooks";
3
+ import htm from "htm";
4
4
  import { Badge } from "../badge.js";
5
5
  import { SecretInput } from "../secret-input.js";
6
6
  import { ActionButton } from "../action-button.js";
@@ -193,7 +193,7 @@ const CodexOAuthSection = ({ codexStatus, onRefreshCodex }) => {
193
193
  return html`
194
194
  <div class="space-y-2">
195
195
  <div class="flex items-center justify-between">
196
- <span class="text-xs text-gray-400">Codex OAuth</span>
196
+ <span class="text-xs text-fg-muted">Codex OAuth</span>
197
197
  ${codexStatus.connected
198
198
  ? html`<${Badge} tone="success">Connected</${Badge}>`
199
199
  : html`<${Badge} tone="warning">Not connected</${Badge}>`}
@@ -226,7 +226,7 @@ const CodexOAuthSection = ({ codexStatus, onRefreshCodex }) => {
226
226
  `
227
227
  : html`
228
228
  <div class="flex items-center justify-between gap-2">
229
- <p class="text-xs text-gray-500">
229
+ <p class="text-xs text-fg-muted">
230
230
  ${authWaiting
231
231
  ? "Complete login in the popup, then paste the redirect URL."
232
232
  : "Paste the redirect URL from your browser to finish connecting."}
@@ -241,9 +241,9 @@ const CodexOAuthSection = ({ codexStatus, onRefreshCodex }) => {
241
241
  `}
242
242
  ${!codexStatus.connected && authStarted
243
243
  ? html`
244
- <p class="text-xs text-gray-500">
244
+ <p class="text-xs text-fg-muted">
245
245
  After login, copy the full redirect URL (starts with
246
- <code class="text-xs bg-black/30 px-1 rounded"
246
+ <code class="text-xs bg-field px-1 rounded"
247
247
  >http://localhost:1455/auth/callback</code
248
248
  >) and paste it here.
249
249
  </p>
@@ -252,7 +252,7 @@ const CodexOAuthSection = ({ codexStatus, onRefreshCodex }) => {
252
252
  value=${manualInput}
253
253
  onInput=${(e) => setManualInput(e.target.value)}
254
254
  placeholder="http://localhost:1455/auth/callback?code=...&state=..."
255
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-xs text-gray-200 outline-none focus:border-gray-500"
255
+ class="w-full bg-field border border-border rounded-lg px-3 py-2 text-xs text-body outline-none focus:border-fg-muted"
256
256
  />
257
257
  <${ActionButton}
258
258
  onClick=${completeAuth}
@@ -340,7 +340,7 @@ export const ProviderAuthCard = ({
340
340
  return html`
341
341
  <div class="space-y-1.5">
342
342
  <div class="flex items-center gap-2">
343
- <label class="text-xs font-medium text-gray-400"
343
+ <label class="text-xs font-medium text-fg-muted"
344
344
  >${mode.label}</label
345
345
  >
346
346
  ${hasMultipleModes && isActive
@@ -349,7 +349,7 @@ export const ProviderAuthCard = ({
349
349
  ${hasMultipleModes && !isActive && fieldValue
350
350
  ? html`<button
351
351
  onclick=${() => handleSetActive(mode)}
352
- class="text-xs px-1.5 py-0.5 rounded-full text-gray-500 hover:text-gray-300 hover:bg-white/5"
352
+ class="text-xs px-1.5 py-0.5 rounded-full text-fg-muted hover:text-body hover:bg-surface"
353
353
  >
354
354
  Set primary
355
355
  </button>`
@@ -388,10 +388,10 @@ export const ProviderAuthCard = ({
388
388
  }}
389
389
  placeholder=${mode.placeholder || ""}
390
390
  isSecret=${true}
391
- inputClass="flex-1 w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
391
+ inputClass="flex-1 w-full bg-field border border-border rounded-lg px-3 py-2 text-sm text-body outline-none focus:border-fg-muted font-mono"
392
392
  />
393
393
  ${mode.hint
394
- ? html`<p class="text-xs text-gray-600">${mode.hint}</p>`
394
+ ? html`<p class="text-xs text-fg-dim">${mode.hint}</p>`
395
395
  : null}
396
396
  </div>
397
397
  `;
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef, useCallback } from "https://esm.sh/preact/hooks";
1
+ import { useState, useEffect, useRef, useCallback } from "preact/hooks";
2
2
  import {
3
3
  fetchModels,
4
4
  fetchModelsConfig,
@@ -1,6 +1,6 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useEffect, useRef, useState } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
1
+ import { h } from "preact";
2
+ import { useEffect, useRef, useState } from "preact/hooks";
3
+ import htm from "htm";
4
4
  import {
5
5
  fetchEnvVars,
6
6
  saveEnvVars,
@@ -275,15 +275,15 @@ export const Models = () => {
275
275
 
276
276
  const renderCredentialField = (field) => html`
277
277
  <div class="space-y-1">
278
- <label class="text-xs font-medium text-gray-400">${field.label}</label>
278
+ <label class="text-xs font-medium text-fg-muted">${field.label}</label>
279
279
  <${SecretInput}
280
280
  value=${getKeyVal(envVars, field.key)}
281
281
  onInput=${(e) => setEnvValue(field.key, e.target.value)}
282
282
  placeholder=${field.placeholder || ""}
283
283
  isSecret=${!field.isText}
284
- inputClass="flex-1 w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
284
+ inputClass="flex-1 w-full bg-field border border-border rounded-lg px-3 py-2 text-sm text-body outline-none focus:border-fg-muted font-mono"
285
285
  />
286
- <p class="text-xs text-gray-600">${field.hint}</p>
286
+ <p class="text-xs text-fg-dim">${field.hint}</p>
287
287
  </div>
288
288
  `;
289
289
 
@@ -296,7 +296,7 @@ export const Models = () => {
296
296
  html`
297
297
  <div class="border border-border rounded-lg p-3 space-y-2">
298
298
  <div class="flex items-center justify-between">
299
- <span class="text-xs text-gray-400">Codex OAuth</span>
299
+ <span class="text-xs text-fg-muted">Codex OAuth</span>
300
300
  ${codexStatus.connected
301
301
  ? html`<${Badge} tone="success">Connected</${Badge}>`
302
302
  : html`<${Badge} tone="warning">Not connected</${Badge}>`}
@@ -329,7 +329,7 @@ export const Models = () => {
329
329
  `
330
330
  : html`
331
331
  <div class="flex items-center justify-between gap-2">
332
- <p class="text-xs text-gray-500">
332
+ <p class="text-xs text-fg-muted">
333
333
  ${codexAuthWaiting
334
334
  ? "Complete login in the popup, then paste the redirect URL."
335
335
  : "Paste the redirect URL from your browser to finish connecting."}
@@ -344,9 +344,9 @@ export const Models = () => {
344
344
  `}
345
345
  ${!codexStatus.connected && codexAuthStarted
346
346
  ? html`
347
- <p class="text-xs text-gray-500">
347
+ <p class="text-xs text-fg-muted">
348
348
  After login, copy the full redirect URL (starts with
349
- <code class="text-xs bg-black/30 px-1 rounded">http://localhost:1455/auth/callback</code>)
349
+ <code class="text-xs bg-field px-1 rounded">http://localhost:1455/auth/callback</code>)
350
350
  and paste it here.
351
351
  </p>
352
352
  <input
@@ -354,7 +354,7 @@ export const Models = () => {
354
354
  value=${codexManualInput}
355
355
  onInput=${(e) => setCodexManualInput(e.target.value)}
356
356
  placeholder="http://localhost:1455/auth/callback?code=...&state=..."
357
- class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-xs text-gray-200 outline-none focus:border-gray-500"
357
+ class="w-full bg-field border border-border rounded-lg px-3 py-2 text-xs text-body outline-none focus:border-fg-muted"
358
358
  />
359
359
  <${ActionButton}
360
360
  onClick=${completeCodexAuth}
@@ -376,7 +376,7 @@ export const Models = () => {
376
376
  if (!ready) {
377
377
  return html`
378
378
  <div class="bg-surface border border-border rounded-xl p-4">
379
- <div class="flex items-center gap-2 text-sm text-gray-400">
379
+ <div class="flex items-center gap-2 text-sm text-fg-muted">
380
380
  <${LoadingSpinner} className="h-4 w-4" />
381
381
  Loading model settings...
382
382
  </div>
@@ -396,14 +396,14 @@ export const Models = () => {
396
396
  setModelDirty(next !== savedModel);
397
397
  kModelsTabCache = { ...(kModelsTabCache || {}), selectedModel: next };
398
398
  }}
399
- class="w-full bg-black/30 border border-border rounded-lg pl-3 pr-8 py-2 text-sm text-gray-200 outline-none focus:border-gray-500"
399
+ class="w-full bg-field border border-border rounded-lg pl-3 pr-8 py-2 text-sm text-body outline-none focus:border-fg-muted"
400
400
  >
401
401
  <option value="">Select a model</option>
402
402
  ${modelOptions.map(
403
403
  (model) => html`<option value=${model.key}>${model.label || model.key}</option>`,
404
404
  )}
405
405
  </select>
406
- <p class="text-xs text-gray-600">
406
+ <p class="text-xs text-fg-dim">
407
407
  ${modelsLoading ? "Loading model catalog..." : modelsError ? modelsError : ""}
408
408
  </p>
409
409
  ${canToggleFullCatalog
@@ -417,7 +417,7 @@ export const Models = () => {
417
417
  kModelsTabCache = { ...(kModelsTabCache || {}), showAllModels: next };
418
418
  return next;
419
419
  })}
420
- class="text-xs text-gray-500 hover:text-gray-300"
420
+ class="text-xs text-fg-muted hover:text-body"
421
421
  >
422
422
  ${showAllModels ? "Show recommended models" : "Show full model catalog"}
423
423
  </button>
@@ -433,8 +433,8 @@ export const Models = () => {
433
433
  <h2 class="font-semibold text-sm">Other Providers</h2>
434
434
  ${otherProviders.map(
435
435
  (provider) => html`
436
- <div class="bg-black/20 border border-border rounded-lg p-3 space-y-3">
437
- <h3 class="text-xs font-semibold text-gray-300">${kProviderLabels[provider] || provider}</h3>
436
+ <div class="bg-field border border-border rounded-lg p-3 space-y-3">
437
+ <h3 class="text-xs font-semibold text-body">${kProviderLabels[provider] || provider}</h3>
438
438
  ${renderProviderContent(provider)}
439
439
  </div>
440
440
  `,
@@ -453,7 +453,7 @@ export const Models = () => {
453
453
  />
454
454
  ${modelDirty && !hasSelectedProviderAuth
455
455
  ? html`
456
- <p class="text-xs text-yellow-500">
456
+ <p class="text-xs text-status-warning-muted">
457
457
  Set credentials for the selected provider before saving this model change.
458
458
  </p>
459
459
  `
@@ -1,7 +1,7 @@
1
- import { h } from "https://esm.sh/preact";
2
- import { useMemo } from "https://esm.sh/preact/hooks";
3
- import htm from "https://esm.sh/htm";
4
- import { marked } from "https://esm.sh/marked";
1
+ import { h } from "preact";
2
+ import { useMemo } from "preact/hooks";
3
+ import htm from "htm";
4
+ import { marked } from "marked";
5
5
 
6
6
  const html = htm.bind(h);
7
7
 
@@ -63,7 +63,7 @@ export const BrowserAttachCard = () => {
63
63
  <details
64
64
  class="ac-surface-inset rounded-lg border border-border px-3 py-2.5"
65
65
  >
66
- <summary class="cursor-pointer text-xs text-gray-300 hover:text-gray-200">
66
+ <summary class="cursor-pointer text-xs text-body hover:text-body">
67
67
  Chrome debugging setup / troubleshooting
68
68
  </summary>
69
69
  <div
@@ -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 { ActionButton } from "../../action-button.js";
4
4
  import { Badge } from "../../badge.js";
5
5
  import { ConfirmDialog } from "../../confirm-dialog.js";
@@ -92,7 +92,7 @@ export const ConnectedNodesCard = ({
92
92
  ${pending.length
93
93
  ? html`
94
94
  <div
95
- class="bg-surface border border-yellow-500/40 rounded-xl px-4 py-3 text-xs text-yellow-300"
95
+ class="bg-surface border border-yellow-500/40 rounded-xl px-4 py-3 text-xs text-status-warning"
96
96
  >
97
97
  ${pending.length} pending node${pending.length === 1 ? "" : "s"}
98
98
  waiting for approval.
@@ -102,7 +102,7 @@ export const ConnectedNodesCard = ({
102
102
  ${loading
103
103
  ? html`
104
104
  <div class="bg-surface border border-border rounded-xl p-4">
105
- <div class="flex items-center gap-3 text-sm text-gray-400">
105
+ <div class="flex items-center gap-3 text-sm text-fg-muted">
106
106
  <${LoadingSpinner} className="h-4 w-4" />
107
107
  <span>Loading nodes...</span>
108
108
  </div>
@@ -111,7 +111,7 @@ export const ConnectedNodesCard = ({
111
111
  : error
112
112
  ? html`
113
113
  <div
114
- class="bg-surface border border-border rounded-xl p-4 text-xs text-red-400"
114
+ class="bg-surface border border-border rounded-xl p-4 text-xs text-status-error-muted"
115
115
  >
116
116
  ${error}
117
117
  </div>
@@ -124,10 +124,10 @@ export const ConnectedNodesCard = ({
124
124
  <div class="max-w-md w-full flex flex-col items-center gap-4">
125
125
  <${ComputerLineIcon} className="h-12 w-12 text-cyan-400" />
126
126
  <div class="space-y-2">
127
- <h2 class="font-semibold text-lg text-gray-100">
127
+ <h2 class="font-semibold text-lg text-bright">
128
128
  No connected nodes yet
129
129
  </h2>
130
- <p class="text-xs text-gray-400 leading-5">
130
+ <p class="text-xs text-fg-muted leading-5">
131
131
  Connect a Mac, iOS, Android, or headless node to run
132
132
  system and browser commands through this gateway.
133
133
  </p>
@@ -175,7 +175,7 @@ export const ConnectedNodesCard = ({
175
175
  ? html`
176
176
  <button
177
177
  type="button"
178
- class="shrink-0 inline-flex items-center gap-1 text-[11px] text-gray-500 hover:text-gray-300"
178
+ class="shrink-0 inline-flex items-center gap-1 text-[11px] text-fg-muted hover:text-body"
179
179
  onclick=${() =>
180
180
  handleCopyText(nodeId, {
181
181
  successMessage: "Device ID copied",
@@ -204,7 +204,7 @@ export const ConnectedNodesCard = ({
204
204
  onToggle=${() => handleOpenNodeMenu(nodeId)}
205
205
  >
206
206
  <${OverflowMenuItem}
207
- className="text-red-300 hover:text-red-200"
207
+ className="text-status-error hover:text-status-error"
208
208
  onClick=${() => {
209
209
  setMenuOpenNodeId("");
210
210
  setRemoveDialogNode(node);
@@ -219,15 +219,15 @@ export const ConnectedNodesCard = ({
219
219
  </div>
220
220
  <div class="flex flex-wrap gap-2 text-[11px]">
221
221
  <div class="ac-surface-inset rounded-lg px-2.5 py-1">
222
- <span class="text-gray-500">platform: </span>
222
+ <span class="text-fg-muted">platform: </span>
223
223
  <code>${node?.platform || "unknown"}</code>
224
224
  </div>
225
225
  <div class="ac-surface-inset rounded-lg px-2.5 py-1">
226
- <span class="text-gray-500">version: </span>
226
+ <span class="text-fg-muted">version: </span>
227
227
  <code>${node?.version || "unknown"}</code>
228
228
  </div>
229
229
  <div class="ac-surface-inset rounded-lg px-2.5 py-1">
230
- <span class="text-gray-500">capabilities: </span>
230
+ <span class="text-fg-muted">capabilities: </span>
231
231
  <code
232
232
  >${Array.isArray(node?.caps)
233
233
  ? node.caps.join(", ")
@@ -251,14 +251,14 @@ export const ConnectedNodesCard = ({
251
251
  ${browserAttachEnabled
252
252
  ? html`
253
253
  <div
254
- class="text-[11px] text-gray-500"
254
+ class="text-[11px] text-fg-muted"
255
255
  >
256
256
  profile: <code>user</code>
257
257
  </div>
258
258
  `
259
259
  : html`
260
260
  <div
261
- class="text-[11px] text-gray-500"
261
+ class="text-[11px] text-fg-muted"
262
262
  >
263
263
  Attach is disabled until you click
264
264
  ${" "}
@@ -322,7 +322,7 @@ export const ConnectedNodesCard = ({
322
322
  class="flex items-center justify-between gap-2"
323
323
  >
324
324
  <div
325
- class="flex flex-wrap gap-2 text-[11px] text-gray-500"
325
+ class="flex flex-wrap gap-2 text-[11px] text-fg-muted"
326
326
  >
327
327
  <span
328
328
  >driver:
@@ -339,30 +339,32 @@ export const ConnectedNodesCard = ({
339
339
  ></span
340
340
  >
341
341
  </div>
342
- ${browserAttachEnabled
343
- ? html`
344
- <button
345
- type="button"
346
- onclick=${() =>
347
- handleDetachNodeBrowser(
348
- nodeId,
349
- )}
350
- class="shrink-0 text-[11px] text-gray-500 hover:text-gray-300"
351
- >
352
- Detach
353
- </button>
354
- `
355
- : null}
356
342
  </div>
357
343
  `
358
344
  : null}
359
345
  ${browserError
360
346
  ? html`<div
361
- class="text-[11px] text-red-400"
347
+ class="text-[11px] text-status-error-muted"
362
348
  >
363
349
  ${browserError}
364
350
  </div>`
365
351
  : null}
352
+ ${canCheckBrowser &&
353
+ browserAttachEnabled &&
354
+ !checkingBrowser
355
+ ? html`
356
+ <div class="flex justify-end pt-1">
357
+ <button
358
+ type="button"
359
+ onclick=${() =>
360
+ handleDetachNodeBrowser(nodeId)}
361
+ class="shrink-0 text-[11px] text-fg-muted hover:text-body"
362
+ >
363
+ Detach
364
+ </button>
365
+ </div>
366
+ `
367
+ : null}
366
368
  </div>
367
369
  </div>
368
370
  `
@@ -372,7 +374,7 @@ export const ConnectedNodesCard = ({
372
374
  <div
373
375
  class="border-t border-border pt-2 space-y-2"
374
376
  >
375
- <div class="text-[11px] text-gray-500">
377
+ <div class="text-[11px] text-fg-muted">
376
378
  Reconnect command
377
379
  </div>
378
380
  <div class="flex items-center gap-2">
@@ -384,7 +386,7 @@ export const ConnectedNodesCard = ({
384
386
  connectInfo,
385
387
  maskToken: true,
386
388
  })}
387
- class="flex-1 min-w-0 bg-black/30 border border-border rounded-lg px-2 py-1.5 text-[11px] font-mono text-gray-300"
389
+ class="flex-1 min-w-0 bg-field border border-border rounded-lg px-2 py-1.5 text-[11px] font-mono text-body"
388
390
  />
389
391
  <${ActionButton}
390
392
  onClick=${() =>
@@ -3,7 +3,7 @@ import {
3
3
  useEffect,
4
4
  useRef,
5
5
  useState,
6
- } from "https://esm.sh/preact/hooks";
6
+ } from "preact/hooks";
7
7
  import { copyTextToClipboard } from "../../../lib/clipboard.js";
8
8
  import { fetchNodeBrowserStatusForNode, removeNode } from "../../../lib/api.js";
9
9
  import { readUiSettings, updateUiSettings } from "../../../lib/ui-settings.js";
@@ -12,6 +12,7 @@ import { showToast } from "../../toast.js";
12
12
  const kBrowserCheckTimeoutMs = 35000;
13
13
  const kBrowserPollIntervalMs = 10000;
14
14
  const kBrowserAttachStateByNodeKey = "nodesBrowserAttachStateByNode";
15
+ const kBrowserClosedPageErrorPattern = /selected page has been closed/i;
15
16
 
16
17
  const withTimeout = async (promise, timeoutMs = kBrowserCheckTimeoutMs) => {
17
18
  let timeoutId = null;
@@ -37,6 +38,16 @@ const isBrowserCapableNode = (node) => {
37
38
  return caps.includes("browser") || commands.includes("browser.proxy");
38
39
  };
39
40
 
41
+ const normalizeBrowserStatusError = (error) => {
42
+ const rawMessage = String(
43
+ error?.message || "Could not check node browser status",
44
+ ).trim();
45
+ if (kBrowserClosedPageErrorPattern.test(rawMessage)) {
46
+ return "Selected Chrome page was closed. Click Attach to reconnect.";
47
+ }
48
+ return rawMessage;
49
+ };
50
+
40
51
  const readBrowserAttachStateByNode = () => {
41
52
  const uiSettings = readUiSettings();
42
53
  const attachState = uiSettings?.[kBrowserAttachStateByNodeKey];
@@ -120,7 +131,12 @@ export const useConnectedNodesCard = ({
120
131
  [normalizedNodeId]: status,
121
132
  }));
122
133
  } catch (error) {
123
- const message = error.message || "Could not check node browser status";
134
+ const message = normalizeBrowserStatusError(error);
135
+ // Stop poll loops after failures so we do not keep retrying a stale browser session.
136
+ setBrowserStatusByNodeId((prev) => ({
137
+ ...prev,
138
+ [normalizedNodeId]: null,
139
+ }));
124
140
  setBrowserErrorByNodeId((prev) => ({
125
141
  ...prev,
126
142
  [normalizedNodeId]: message,
@@ -268,7 +284,6 @@ export const useConnectedNodesCard = ({
268
284
  const nextNodeId = pollableNodeIds[pollIndex];
269
285
  await handleCheckNodeBrowser(nextNodeId, { silent: true });
270
286
  };
271
- poll();
272
287
  const timer = setInterval(poll, kBrowserPollIntervalMs);
273
288
  return () => {
274
289
  active = false;