@chrysb/alphaclaw 0.8.1-beta.8 → 0.8.1

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.
@@ -60,26 +60,16 @@ export const BrowserAttachCard = () => {
60
60
  );
61
61
 
62
62
  return html`
63
- <div class="bg-surface border border-border rounded-xl p-4 space-y-3">
64
- <div class="space-y-1">
65
- <h3 class="font-semibold text-sm">Live Chrome Attach (Mac Node)</h3>
66
- <p class="text-xs text-gray-500">
67
- Connect your agent to real Chrome sessions (logged-in tabs, cookies,
68
- and all) using the built-in <code>user</code> profile.
69
- </p>
70
- </div>
71
-
72
- <details class="rounded-lg border border-border bg-black/20 px-3 py-2.5">
73
- <summary
74
- class="cursor-pointer text-xs text-gray-300 hover:text-gray-200"
75
- >
76
- Setup instructions
77
- </summary>
78
- <div
79
- class="pt-3 file-viewer-preview release-notes-preview text-xs leading-5"
80
- dangerouslySetInnerHTML=${{ __html: setupInstructionsHtml }}
81
- ></div>
82
- </details>
83
- </div>
63
+ <details
64
+ class="ac-surface-inset rounded-lg border border-border px-3 py-2.5"
65
+ >
66
+ <summary class="cursor-pointer text-xs text-gray-300 hover:text-gray-200">
67
+ Chrome debugging setup / troubleshooting
68
+ </summary>
69
+ <div
70
+ class="pt-3 px-2 file-viewer-preview release-notes-preview text-xs leading-5"
71
+ dangerouslySetInnerHTML=${{ __html: setupInstructionsHtml }}
72
+ ></div>
73
+ </details>
84
74
  `;
85
75
  };
@@ -9,7 +9,7 @@ import { fetchNodeBrowserStatusForNode, removeNode } from "../../../lib/api.js";
9
9
  import { readUiSettings, updateUiSettings } from "../../../lib/ui-settings.js";
10
10
  import { showToast } from "../../toast.js";
11
11
 
12
- const kBrowserCheckTimeoutMs = 15000;
12
+ const kBrowserCheckTimeoutMs = 35000;
13
13
  const kBrowserPollIntervalMs = 10000;
14
14
  const kBrowserAttachStateByNodeKey = "nodesBrowserAttachStateByNode";
15
15
 
@@ -212,6 +212,32 @@ export const useConnectedNodesCard = ({
212
212
  removingNodeId,
213
213
  ]);
214
214
 
215
+ useEffect(() => {
216
+ if (checkingBrowserNodeId) return;
217
+ const pendingInitialNodeId = nodes
218
+ .map((node) => ({
219
+ nodeId: String(node?.nodeId || "").trim(),
220
+ connected: node?.connected === true,
221
+ browserCapable: isBrowserCapableNode(node),
222
+ }))
223
+ .find((entry) => {
224
+ if (!entry.nodeId || !entry.connected || !entry.browserCapable) return false;
225
+ if (browserAttachStateByNodeId?.[entry.nodeId] !== true) return false;
226
+ if (browserStatusByNodeId?.[entry.nodeId]) return false;
227
+ if (browserErrorByNodeId?.[entry.nodeId]) return false;
228
+ return true;
229
+ })?.nodeId;
230
+ if (!pendingInitialNodeId) return;
231
+ handleCheckNodeBrowser(pendingInitialNodeId, { silent: true });
232
+ }, [
233
+ browserAttachStateByNodeId,
234
+ browserErrorByNodeId,
235
+ browserStatusByNodeId,
236
+ checkingBrowserNodeId,
237
+ handleCheckNodeBrowser,
238
+ nodes,
239
+ ]);
240
+
215
241
  useEffect(() => {
216
242
  if (checkingBrowserNodeId) return;
217
243
  const pollableNodeIds = nodes
@@ -219,13 +245,17 @@ export const useConnectedNodesCard = ({
219
245
  nodeId: String(node?.nodeId || "").trim(),
220
246
  connected: node?.connected === true,
221
247
  browserCapable: isBrowserCapableNode(node),
248
+ browserRunning:
249
+ browserStatusByNodeId?.[String(node?.nodeId || "").trim()]?.running ===
250
+ true,
222
251
  }))
223
252
  .filter(
224
253
  (entry) =>
225
254
  entry.nodeId &&
226
255
  entry.connected &&
227
256
  entry.browserCapable &&
228
- browserAttachStateByNodeId?.[entry.nodeId] === true,
257
+ browserAttachStateByNodeId?.[entry.nodeId] === true &&
258
+ entry.browserRunning,
229
259
  )
230
260
  .map((entry) => entry.nodeId);
231
261
  if (!pollableNodeIds.length) return;
@@ -244,7 +274,13 @@ export const useConnectedNodesCard = ({
244
274
  active = false;
245
275
  clearInterval(timer);
246
276
  };
247
- }, [browserAttachStateByNodeId, checkingBrowserNodeId, handleCheckNodeBrowser, nodes]);
277
+ }, [
278
+ browserAttachStateByNodeId,
279
+ browserStatusByNodeId,
280
+ checkingBrowserNodeId,
281
+ handleCheckNodeBrowser,
282
+ nodes,
283
+ ]);
248
284
 
249
285
  return {
250
286
  browserStatusByNodeId,
@@ -18,6 +18,7 @@ import { usePolling } from "./usePolling.js";
18
18
  import { showToast } from "../components/toast.js";
19
19
 
20
20
  export const useAppShellController = ({ location = "" } = {}) => {
21
+ const kInitialStatusPollDelayMs = 5000;
21
22
  const [onboarded, setOnboarded] = useState(null);
22
23
  const [authEnabled, setAuthEnabled] = useState(false);
23
24
  const [acVersion, setAcVersion] = useState(null);
@@ -29,6 +30,7 @@ export const useAppShellController = ({ location = "" } = {}) => {
29
30
  const [restartingGateway, setRestartingGateway] = useState(false);
30
31
  const [gatewayRestartSignal, setGatewayRestartSignal] = useState(0);
31
32
  const [statusPollCadenceMs, setStatusPollCadenceMs] = useState(15000);
33
+ const [statusPollingGraceElapsed, setStatusPollingGraceElapsed] = useState(false);
32
34
  const [openclawUpdateInProgress, setOpenclawUpdateInProgress] = useState(false);
33
35
  const [statusStreamConnected, setStatusStreamConnected] = useState(false);
34
36
  const [statusStreamStatus, setStatusStreamStatus] = useState(null);
@@ -36,15 +38,18 @@ export const useAppShellController = ({ location = "" } = {}) => {
36
38
  const [statusStreamDoctor, setStatusStreamDoctor] = useState(null);
37
39
 
38
40
  const sharedStatusPoll = usePolling(fetchStatus, statusPollCadenceMs, {
39
- enabled: onboarded === true && !statusStreamConnected,
41
+ enabled:
42
+ onboarded === true && !statusStreamConnected && statusPollingGraceElapsed,
40
43
  cacheKey: "/api/status",
41
44
  });
42
45
  const sharedWatchdogPoll = usePolling(fetchWatchdogStatus, statusPollCadenceMs, {
43
- enabled: onboarded === true && !statusStreamConnected,
46
+ enabled:
47
+ onboarded === true && !statusStreamConnected && statusPollingGraceElapsed,
44
48
  cacheKey: "/api/watchdog/status",
45
49
  });
46
50
  const sharedDoctorPoll = usePolling(fetchDoctorStatus, statusPollCadenceMs, {
47
- enabled: onboarded === true && !statusStreamConnected,
51
+ enabled:
52
+ onboarded === true && !statusStreamConnected && statusPollingGraceElapsed,
48
53
  cacheKey: "/api/doctor/status",
49
54
  });
50
55
  const sharedStatus = statusStreamStatus || sharedStatusPoll.data || null;
@@ -69,6 +74,19 @@ export const useAppShellController = ({ location = "" } = {}) => {
69
74
  .catch(() => {});
70
75
  }, []);
71
76
 
77
+ useEffect(() => {
78
+ if (onboarded !== true) {
79
+ setStatusPollingGraceElapsed(false);
80
+ return () => {};
81
+ }
82
+ const timerId = setTimeout(() => {
83
+ setStatusPollingGraceElapsed(true);
84
+ }, kInitialStatusPollDelayMs);
85
+ return () => {
86
+ clearTimeout(timerId);
87
+ };
88
+ }, [onboarded]);
89
+
72
90
  useEffect(() => {
73
91
  if (onboarded !== true) return;
74
92
  let disposed = false;
@@ -31,6 +31,38 @@ const parseGithubErrorMessage = async (response) => {
31
31
  return response.statusText || `HTTP ${response.status}`;
32
32
  };
33
33
 
34
+ // Files GitHub may auto-create when initializing a repo — a repo containing
35
+ // only these is treated as empty for onboarding purposes.
36
+ const kBoilerplateNames = new Set([
37
+ "readme",
38
+ "readme.md",
39
+ "readme.txt",
40
+ "readme.rst",
41
+ "license",
42
+ "license.md",
43
+ "license.txt",
44
+ ".gitignore",
45
+ ".gitattributes",
46
+ ]);
47
+
48
+ const repoContainsOnlyBoilerplate = async (repoUrl, ghHeaders) => {
49
+ try {
50
+ const res = await fetch(
51
+ `https://api.github.com/repos/${repoUrl}/contents/`,
52
+ { headers: ghHeaders },
53
+ );
54
+ if (!res.ok) return false;
55
+ const entries = await res.json();
56
+ if (!Array.isArray(entries)) return false;
57
+ if (entries.length === 0) return true;
58
+ return entries.every(
59
+ (e) => e.type === "file" && kBoilerplateNames.has(e.name.toLowerCase()),
60
+ );
61
+ } catch {
62
+ return false;
63
+ }
64
+ };
65
+
34
66
  const isClassicPat = (token) => String(token || "").startsWith("ghp_");
35
67
  const isFineGrainedPat = (token) =>
36
68
  String(token || "").startsWith("github_pat_");
@@ -98,6 +130,13 @@ const verifyGithubRepoForOnboarding = async ({
98
130
  return { ok: true, repoExists: true, repoIsEmpty: true };
99
131
  }
100
132
  if (commitsRes.ok) {
133
+ const onlyBoilerplate = await repoContainsOnlyBoilerplate(
134
+ repoUrl,
135
+ ghHeaders,
136
+ );
137
+ if (onlyBoilerplate) {
138
+ return { ok: true, repoExists: true, repoIsEmpty: true };
139
+ }
101
140
  if (isExisting) {
102
141
  return { ok: true, repoExists: true, repoIsEmpty: false };
103
142
  }
@@ -7,8 +7,8 @@ const kAllowedExecHosts = new Set(["gateway", "node"]);
7
7
  const kAllowedExecSecurity = new Set(["deny", "allowlist", "full"]);
8
8
  const kAllowedExecAsk = new Set(["off", "on-miss", "always"]);
9
9
  const kSafeNodeIdPattern = /^[\w\-:.]+$/;
10
- const kNodeBrowserInvokeTimeoutMs = 15000;
11
- const kNodeBrowserCliTimeoutMs = 18000;
10
+ const kNodeBrowserInvokeTimeoutMs = 30000;
11
+ const kNodeBrowserCliTimeoutMs = 35000;
12
12
  const kNodeRouteCliTimeoutMs = 12000;
13
13
 
14
14
  const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.8.1-beta.8",
3
+ "version": "0.8.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },