@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.
- package/lib/public/js/components/nodes-tab/browser-attach/index.js +11 -21
- package/lib/public/js/components/nodes-tab/connected-nodes/use-connected-nodes-card.js +39 -3
- package/lib/public/js/hooks/use-app-shell-controller.js +21 -3
- package/lib/server/onboarding/github.js +39 -0
- package/lib/server/routes/nodes.js +2 -2
- package/package.json +1 -1
|
@@ -60,26 +60,16 @@ export const BrowserAttachCard = () => {
|
|
|
60
60
|
);
|
|
61
61
|
|
|
62
62
|
return html`
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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 =
|
|
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
|
-
}, [
|
|
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:
|
|
41
|
+
enabled:
|
|
42
|
+
onboarded === true && !statusStreamConnected && statusPollingGraceElapsed,
|
|
40
43
|
cacheKey: "/api/status",
|
|
41
44
|
});
|
|
42
45
|
const sharedWatchdogPoll = usePolling(fetchWatchdogStatus, statusPollCadenceMs, {
|
|
43
|
-
enabled:
|
|
46
|
+
enabled:
|
|
47
|
+
onboarded === true && !statusStreamConnected && statusPollingGraceElapsed,
|
|
44
48
|
cacheKey: "/api/watchdog/status",
|
|
45
49
|
});
|
|
46
50
|
const sharedDoctorPoll = usePolling(fetchDoctorStatus, statusPollCadenceMs, {
|
|
47
|
-
enabled:
|
|
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 =
|
|
11
|
-
const kNodeBrowserCliTimeoutMs =
|
|
10
|
+
const kNodeBrowserInvokeTimeoutMs = 30000;
|
|
11
|
+
const kNodeBrowserCliTimeoutMs = 35000;
|
|
12
12
|
const kNodeRouteCliTimeoutMs = 12000;
|
|
13
13
|
|
|
14
14
|
const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
|