@chrysb/alphaclaw 0.4.1-beta.0 → 0.4.1-beta.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 (35) hide show
  1. package/lib/public/css/explorer.css +156 -4
  2. package/lib/public/js/components/file-tree.js +533 -48
  3. package/lib/public/js/components/file-viewer/index.js +2 -8
  4. package/lib/public/js/components/google/index.js +83 -48
  5. package/lib/public/js/components/icons.js +26 -0
  6. package/lib/public/js/components/onboarding/use-welcome-codex.js +129 -0
  7. package/lib/public/js/components/onboarding/use-welcome-pairing.js +74 -0
  8. package/lib/public/js/components/onboarding/use-welcome-storage.js +60 -0
  9. package/lib/public/js/components/sidebar.js +1 -1
  10. package/lib/public/js/components/telegram-workspace/onboarding.js +1 -1
  11. package/lib/public/js/components/webhooks.js +2 -2
  12. package/lib/public/js/components/welcome.js +57 -210
  13. package/lib/public/js/lib/api.js +27 -0
  14. package/lib/public/js/lib/browse-file-policies.js +3 -1
  15. package/lib/public/shared/browse-file-policies.json +2 -1
  16. package/lib/server/constants.js +0 -1
  17. package/lib/server/gmail-serve.js +1 -1
  18. package/lib/server/gmail-watch.js +8 -28
  19. package/lib/server/gog-skill.js +169 -0
  20. package/lib/server/onboarding/openclaw.js +9 -1
  21. package/lib/server/routes/browse/index.js +123 -6
  22. package/lib/server/routes/browse/path-utils.js +3 -1
  23. package/lib/server/routes/google.js +4 -0
  24. package/lib/server/routes/webhooks.js +3 -5
  25. package/lib/server/webhooks.js +1 -1
  26. package/lib/server.js +2 -0
  27. package/lib/setup/skills/gog-cli/calendar.md +63 -0
  28. package/lib/setup/skills/gog-cli/contacts.md +30 -0
  29. package/lib/setup/skills/gog-cli/docs.md +46 -0
  30. package/lib/setup/skills/gog-cli/drive.md +48 -0
  31. package/lib/setup/skills/gog-cli/gmail.md +64 -0
  32. package/lib/setup/skills/gog-cli/meet.md +6 -0
  33. package/lib/setup/skills/gog-cli/sheets.md +43 -0
  34. package/lib/setup/skills/gog-cli/tasks.md +32 -0
  35. package/package.json +2 -2
@@ -32,7 +32,7 @@ export const FileViewer = ({
32
32
  onRequestEdit,
33
33
  });
34
34
 
35
- if (!state.hasSelectedPath) {
35
+ if (!state.hasSelectedPath || state.isFolderPath) {
36
36
  return html`
37
37
  <div class="file-viewer-empty">
38
38
  <div class="file-viewer-empty-mark">[ ]</div>
@@ -99,13 +99,7 @@ export const FileViewer = ({
99
99
  `
100
100
  : state.error
101
101
  ? html`<div class="file-viewer-state file-viewer-state-error">${state.error}</div>`
102
- : state.isFolderPath
103
- ? html`
104
- <div class="file-viewer-state">
105
- Folder selected. Choose a file from this folder in the tree.
106
- </div>
107
- `
108
- : state.isImageFile || state.isAudioFile
102
+ : state.isImageFile || state.isAudioFile
109
103
  ? html`
110
104
  <${MediaPreview}
111
105
  isImageFile=${state.isImageFile}
@@ -1,5 +1,10 @@
1
1
  import { h } from "https://esm.sh/preact";
2
- import { useCallback, useEffect, useMemo, useState } from "https://esm.sh/preact/hooks";
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ } from "https://esm.sh/preact/hooks";
3
8
  import htm from "https://esm.sh/htm";
4
9
  import {
5
10
  checkGoogleApis,
@@ -34,12 +39,8 @@ export const Google = ({
34
39
  onRestartRequired = () => {},
35
40
  onOpenGmailWebhook = () => {},
36
41
  }) => {
37
- const {
38
- accounts,
39
- loading,
40
- hasCompanyCredentials,
41
- refreshAccounts,
42
- } = useGoogleAccounts({ gatewayStatus });
42
+ const { accounts, loading, hasCompanyCredentials, refreshAccounts } =
43
+ useGoogleAccounts({ gatewayStatus });
43
44
  const [expandedAccountId, setExpandedAccountId] = useState("");
44
45
  const [scopesByAccountId, setScopesByAccountId] = useState({});
45
46
  const [savedScopesByAccountId, setSavedScopesByAccountId] = useState({});
@@ -90,12 +91,16 @@ export const Google = ({
90
91
  );
91
92
 
92
93
  const ensureScopesForAccount = useCallback((account) => {
93
- const nextScopes = Array.isArray(account.activeScopes) && account.activeScopes.length
94
- ? account.activeScopes
95
- : Array.isArray(account.services) && account.services.length
96
- ? account.services
97
- : getDefaultScopes();
98
- setSavedScopesByAccountId((prev) => ({ ...prev, [account.id]: [...nextScopes] }));
94
+ const nextScopes =
95
+ Array.isArray(account.activeScopes) && account.activeScopes.length
96
+ ? account.activeScopes
97
+ : Array.isArray(account.services) && account.services.length
98
+ ? account.services
99
+ : getDefaultScopes();
100
+ setSavedScopesByAccountId((prev) => ({
101
+ ...prev,
102
+ [account.id]: [...nextScopes],
103
+ }));
99
104
  setScopesByAccountId((prev) => {
100
105
  const current = prev[account.id];
101
106
  if (!current || !hasScopesChanged(current, nextScopes)) {
@@ -125,7 +130,10 @@ export const Google = ({
125
130
  (accountId) => {
126
131
  const account = getAccountById(accountId);
127
132
  if (!account) return;
128
- const scopes = scopesByAccountId[accountId] || account.activeScopes || getDefaultScopes();
133
+ const scopes =
134
+ scopesByAccountId[accountId] ||
135
+ account.activeScopes ||
136
+ getDefaultScopes();
129
137
  if (!scopes.length) {
130
138
  window.alert("Select at least one service");
131
139
  return;
@@ -160,7 +168,10 @@ export const Google = ({
160
168
  try {
161
169
  const data = await checkGoogleApis(accountId);
162
170
  if (data.results) {
163
- setApiStatusByAccountId((prev) => ({ ...prev, [accountId]: data.results }));
171
+ setApiStatusByAccountId((prev) => ({
172
+ ...prev,
173
+ [accountId]: data.results,
174
+ }));
164
175
  }
165
176
  } finally {
166
177
  setCheckingByAccountId((prev) => {
@@ -184,7 +195,10 @@ export const Google = ({
184
195
  await handleCheckApis(accountId);
185
196
  }
186
197
  } else if (event.data?.google === "error") {
187
- showToast(`✗ Google auth failed: ${event.data.message || "unknown"}`, "error");
198
+ showToast(
199
+ `✗ Google auth failed: ${event.data.message || "unknown"}`,
200
+ "error",
201
+ );
188
202
  }
189
203
  };
190
204
  window.addEventListener("message", handler);
@@ -249,6 +263,9 @@ export const Google = ({
249
263
  };
250
264
 
251
265
  const handleCredentialsSaved = async (account) => {
266
+ if (account?.id) {
267
+ setExpandedAccountId(account.id);
268
+ }
252
269
  await refreshAccounts();
253
270
  if (account?.id) startAuth(account.id);
254
271
  };
@@ -267,6 +284,9 @@ export const Google = ({
267
284
  return;
268
285
  }
269
286
  setAddCompanyModalOpen(false);
287
+ if (data.accountId) {
288
+ setExpandedAccountId(data.accountId);
289
+ }
270
290
  await refreshAccounts();
271
291
  if (data.accountId) startAuth(data.accountId);
272
292
  } finally {
@@ -304,7 +324,7 @@ export const Google = ({
304
324
  const account = getAccountById(accountId);
305
325
  if (!account) return;
306
326
  const personal = isPersonalAccount(account);
307
- const client = personal ? "personal" : (account.client || "default");
327
+ const client = personal ? "personal" : account.client || "default";
308
328
  let credentialValues = {};
309
329
  try {
310
330
  const credentialResponse = await fetchGoogleCredentials({
@@ -390,22 +410,23 @@ export const Google = ({
390
410
  };
391
411
 
392
412
  const renderEmptyState = () => html`
393
- <div class="text-center space-y-2 py-1">
413
+ <div class="text-center space-y-2 pt-3">
394
414
  <div class="rounded-lg border border-border bg-black/20 px-3 py-5">
395
- <div class="flex flex-col items-center justify-center gap-1.5">
415
+ <div class="flex flex-col items-center justify-center gap-3">
396
416
  <img
397
417
  src=${kGoogleIconPath}
398
418
  alt="Google logo"
399
- class="h-4 w-4 shrink-0"
419
+ class="h-5 w-5 shrink-0"
400
420
  loading="lazy"
401
421
  decoding="async"
402
422
  />
403
423
  <p class="text-xs text-gray-500">
404
- Connect Gmail, Calendar, Contacts, Drive, Sheets, Tasks, Docs, and Meet.
424
+ Connect Gmail, Calendar, Contacts, Drive, Sheets, Tasks, Docs, and
425
+ Meet.
405
426
  </p>
406
427
  </div>
407
428
  </div>
408
- <div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
429
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-2 pt-2">
409
430
  <${ActionButton}
410
431
  onClick=${handleAddCompanyClick}
411
432
  tone="primary"
@@ -469,34 +490,46 @@ export const Google = ({
469
490
  `}
470
491
  />
471
492
  ${loading
472
- ? html`<div class="text-gray-500 text-sm text-center py-2">Loading...</div>`
493
+ ? html`<div class="text-gray-500 text-sm text-center py-2">
494
+ Loading...
495
+ </div>`
473
496
  : accounts.length
474
497
  ? html`
475
498
  <div class="space-y-2 mt-3">
476
- ${accounts.map((account) =>
477
- html`<${GoogleAccountRow}
478
- key=${account.id}
479
- account=${account}
480
- personal=${isPersonalAccount(account)}
481
- expanded=${expandedAccountId === account.id}
482
- onToggleExpanded=${(accountId) =>
483
- setExpandedAccountId((prev) => (prev === accountId ? "" : accountId))}
484
- scopes=${scopesByAccountId[account.id] || account.activeScopes || getDefaultScopes()}
485
- savedScopes=${savedScopesByAccountId[account.id] || account.activeScopes || getDefaultScopes()}
486
- apiStatus=${apiStatusByAccountId[account.id] || {}}
487
- checkingApis=${expandedAccountId === account.id && Boolean(checkingByAccountId[account.id])}
488
- onToggleScope=${handleToggleScope}
489
- onCheckApis=${handleCheckApis}
490
- onUpdatePermissions=${(accountId) => startAuth(accountId)}
491
- onEditCredentials=${handleEditCredentials}
492
- onDisconnect=${(accountId) => setDisconnectAccountId(accountId)}
493
- gmailWatchStatus=${watchByAccountId.get(account.id) || null}
494
- gmailWatchBusy=${Boolean(busyByAccountId[account.id])}
495
- onEnableGmailWatch=${handleEnableGmailWatch}
496
- onDisableGmailWatch=${handleDisableGmailWatch}
497
- onOpenGmailSetup=${openGmailSetupWizard}
498
- onOpenGmailWebhook=${onOpenGmailWebhook}
499
- />`,
499
+ ${accounts.map(
500
+ (account) =>
501
+ html`<${GoogleAccountRow}
502
+ key=${account.id}
503
+ account=${account}
504
+ personal=${isPersonalAccount(account)}
505
+ expanded=${expandedAccountId === account.id}
506
+ onToggleExpanded=${(accountId) =>
507
+ setExpandedAccountId((prev) =>
508
+ prev === accountId ? "" : accountId,
509
+ )}
510
+ scopes=${scopesByAccountId[account.id] ||
511
+ account.activeScopes ||
512
+ getDefaultScopes()}
513
+ savedScopes=${savedScopesByAccountId[account.id] ||
514
+ account.activeScopes ||
515
+ getDefaultScopes()}
516
+ apiStatus=${apiStatusByAccountId[account.id] || {}}
517
+ checkingApis=${expandedAccountId === account.id &&
518
+ Boolean(checkingByAccountId[account.id])}
519
+ onToggleScope=${handleToggleScope}
520
+ onCheckApis=${handleCheckApis}
521
+ onUpdatePermissions=${(accountId) => startAuth(accountId)}
522
+ onEditCredentials=${handleEditCredentials}
523
+ onDisconnect=${(accountId) =>
524
+ setDisconnectAccountId(accountId)}
525
+ gmailWatchStatus=${watchByAccountId.get(account.id) ||
526
+ null}
527
+ gmailWatchBusy=${Boolean(busyByAccountId[account.id])}
528
+ onEnableGmailWatch=${handleEnableGmailWatch}
529
+ onDisableGmailWatch=${handleDisableGmailWatch}
530
+ onOpenGmailSetup=${openGmailSetupWizard}
531
+ onOpenGmailWebhook=${onOpenGmailWebhook}
532
+ />`,
500
533
  )}
501
534
  </div>
502
535
  `
@@ -528,7 +561,9 @@ export const Google = ({
528
561
  visible=${gmailWizardState.visible}
529
562
  account=${getAccountById(gmailWizardState.accountId)}
530
563
  clientConfig=${clientConfigByClient.get(
531
- String(getAccountById(gmailWizardState.accountId)?.client || "default").trim() || "default",
564
+ String(
565
+ getAccountById(gmailWizardState.accountId)?.client || "default",
566
+ ).trim() || "default",
532
567
  ) || null}
533
568
  saving=${savingClient || gmailLoading}
534
569
  onClose=${closeGmailSetupWizard}
@@ -262,6 +262,32 @@ export const DeleteBinLineIcon = ({ className = "" }) => html`
262
262
  </svg>
263
263
  `;
264
264
 
265
+ export const FileAddLineIcon = ({ className = "" }) => html`
266
+ <svg
267
+ class=${className}
268
+ viewBox="0 0 24 24"
269
+ fill="currentColor"
270
+ aria-hidden="true"
271
+ >
272
+ <path
273
+ d="M15 4H5V20H19V8H15V4ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM11 11V8H13V11H16V13H13V16H11V13H8V11H11Z"
274
+ />
275
+ </svg>
276
+ `;
277
+
278
+ export const FolderAddLineIcon = ({ className = "" }) => html`
279
+ <svg
280
+ class=${className}
281
+ viewBox="0 0 24 24"
282
+ fill="currentColor"
283
+ aria-hidden="true"
284
+ >
285
+ <path
286
+ d="M12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5ZM4 5V19H20V7H11.5858L9.58579 5H4ZM11 12V9H13V12H16V14H13V17H11V14H8V12H11Z"
287
+ />
288
+ </svg>
289
+ `;
290
+
265
291
  export const RestartLineIcon = ({ className = "" }) => html`
266
292
  <svg
267
293
  class=${className}
@@ -0,0 +1,129 @@
1
+ import { useEffect, useRef, useState } from "https://esm.sh/preact/hooks";
2
+ import {
3
+ disconnectCodex,
4
+ exchangeCodexOAuth,
5
+ fetchCodexStatus,
6
+ } from "../../lib/api.js";
7
+
8
+ export const useWelcomeCodex = ({ setFormError } = {}) => {
9
+ const [codexStatus, setCodexStatus] = useState({ connected: false });
10
+ const [codexLoading, setCodexLoading] = useState(true);
11
+ const [codexManualInput, setCodexManualInput] = useState("");
12
+ const [codexExchanging, setCodexExchanging] = useState(false);
13
+ const [codexAuthStarted, setCodexAuthStarted] = useState(false);
14
+ const [codexAuthWaiting, setCodexAuthWaiting] = useState(false);
15
+ const codexPopupPollRef = useRef(null);
16
+
17
+ const refreshCodexStatus = async () => {
18
+ try {
19
+ const status = await fetchCodexStatus();
20
+ setCodexStatus(status);
21
+ if (status?.connected) {
22
+ setCodexAuthStarted(false);
23
+ setCodexAuthWaiting(false);
24
+ }
25
+ } catch {
26
+ setCodexStatus({ connected: false });
27
+ } finally {
28
+ setCodexLoading(false);
29
+ }
30
+ };
31
+
32
+ useEffect(() => {
33
+ refreshCodexStatus();
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ const onMessage = async (e) => {
38
+ if (e.data?.codex === "success") {
39
+ await refreshCodexStatus();
40
+ }
41
+ if (e.data?.codex === "error") {
42
+ setFormError(`Codex auth failed: ${e.data.message || "unknown error"}`);
43
+ }
44
+ };
45
+ window.addEventListener("message", onMessage);
46
+ return () => window.removeEventListener("message", onMessage);
47
+ }, [setFormError]);
48
+
49
+ useEffect(
50
+ () => () => {
51
+ if (codexPopupPollRef.current) {
52
+ clearInterval(codexPopupPollRef.current);
53
+ codexPopupPollRef.current = null;
54
+ }
55
+ },
56
+ [],
57
+ );
58
+
59
+ const startCodexAuth = () => {
60
+ if (codexStatus.connected) return;
61
+ setCodexAuthStarted(true);
62
+ setCodexAuthWaiting(true);
63
+ const authUrl = "/auth/codex/start";
64
+ const popup = window.open(
65
+ authUrl,
66
+ "codex-auth",
67
+ "popup=yes,width=640,height=780",
68
+ );
69
+ if (!popup || popup.closed) {
70
+ setCodexAuthWaiting(false);
71
+ window.location.href = authUrl;
72
+ return;
73
+ }
74
+ if (codexPopupPollRef.current) {
75
+ clearInterval(codexPopupPollRef.current);
76
+ }
77
+ codexPopupPollRef.current = setInterval(() => {
78
+ if (popup.closed) {
79
+ clearInterval(codexPopupPollRef.current);
80
+ codexPopupPollRef.current = null;
81
+ setCodexAuthWaiting(false);
82
+ }
83
+ }, 500);
84
+ };
85
+
86
+ const completeCodexAuth = async () => {
87
+ if (!codexManualInput.trim() || codexExchanging) return;
88
+ setCodexExchanging(true);
89
+ setFormError(null);
90
+ try {
91
+ const result = await exchangeCodexOAuth(codexManualInput.trim());
92
+ if (!result.ok)
93
+ throw new Error(result.error || "Codex OAuth exchange failed");
94
+ setCodexManualInput("");
95
+ setCodexAuthStarted(false);
96
+ setCodexAuthWaiting(false);
97
+ await refreshCodexStatus();
98
+ } catch (err) {
99
+ setFormError(err.message || "Codex OAuth exchange failed");
100
+ } finally {
101
+ setCodexExchanging(false);
102
+ }
103
+ };
104
+
105
+ const handleCodexDisconnect = async () => {
106
+ const result = await disconnectCodex();
107
+ if (!result.ok) {
108
+ setFormError(result.error || "Failed to disconnect Codex");
109
+ return;
110
+ }
111
+ setCodexAuthStarted(false);
112
+ setCodexAuthWaiting(false);
113
+ setCodexManualInput("");
114
+ await refreshCodexStatus();
115
+ };
116
+
117
+ return {
118
+ codexStatus,
119
+ codexLoading,
120
+ codexManualInput,
121
+ setCodexManualInput,
122
+ codexExchanging,
123
+ codexAuthStarted,
124
+ codexAuthWaiting,
125
+ startCodexAuth,
126
+ completeCodexAuth,
127
+ handleCodexDisconnect,
128
+ };
129
+ };
@@ -0,0 +1,74 @@
1
+ import { useEffect, useState } from "https://esm.sh/preact/hooks";
2
+ import { approvePairing, fetchPairings, fetchStatus, rejectPairing } from "../../lib/api.js";
3
+ import { usePolling } from "../../hooks/usePolling.js";
4
+ import { isChannelPaired } from "./pairing-utils.js";
5
+
6
+ export const useWelcomePairing = ({
7
+ isPairingStep = false,
8
+ selectedPairingChannel = "",
9
+ } = {}) => {
10
+ const [pairingError, setPairingError] = useState(null);
11
+ const [pairingComplete, setPairingComplete] = useState(false);
12
+
13
+ const pairingStatusPoll = usePolling(fetchStatus, 3000, {
14
+ enabled: isPairingStep,
15
+ });
16
+ const pairingRequestsPoll = usePolling(
17
+ async () => {
18
+ const payload = await fetchPairings();
19
+ const allPending = payload.pending || [];
20
+ return allPending.filter((p) => p.channel === selectedPairingChannel);
21
+ },
22
+ 1000,
23
+ { enabled: isPairingStep && !!selectedPairingChannel },
24
+ );
25
+ const pairingChannels = pairingStatusPoll.data?.channels || {};
26
+ const canFinishPairing = isChannelPaired(pairingChannels, selectedPairingChannel);
27
+
28
+ useEffect(() => {
29
+ if (isPairingStep && canFinishPairing) {
30
+ setPairingComplete(true);
31
+ }
32
+ }, [isPairingStep, canFinishPairing]);
33
+
34
+ const handlePairingApprove = async (id, channel) => {
35
+ try {
36
+ setPairingError(null);
37
+ const result = await approvePairing(id, channel);
38
+ if (!result.ok) throw new Error(result.error || "Could not approve pairing");
39
+ setPairingComplete(true);
40
+ pairingRequestsPoll.refresh();
41
+ pairingStatusPoll.refresh();
42
+ } catch (err) {
43
+ setPairingError(err.message || "Could not approve pairing");
44
+ }
45
+ };
46
+
47
+ const handlePairingReject = async (id, channel) => {
48
+ try {
49
+ setPairingError(null);
50
+ const result = await rejectPairing(id, channel);
51
+ if (!result.ok) throw new Error(result.error || "Could not reject pairing");
52
+ pairingRequestsPoll.refresh();
53
+ } catch (err) {
54
+ setPairingError(err.message || "Could not reject pairing");
55
+ }
56
+ };
57
+
58
+ const resetPairingState = () => {
59
+ setPairingError(null);
60
+ setPairingComplete(false);
61
+ };
62
+
63
+ return {
64
+ pairingStatusPoll,
65
+ pairingRequestsPoll,
66
+ pairingChannels,
67
+ canFinishPairing,
68
+ pairingError,
69
+ pairingComplete,
70
+ handlePairingApprove,
71
+ handlePairingReject,
72
+ resetPairingState,
73
+ };
74
+ };
@@ -0,0 +1,60 @@
1
+ import { useEffect, useState } from "https://esm.sh/preact/hooks";
2
+
3
+ export const kOnboardingStorageKey = "openclaw_setup";
4
+ export const kOnboardingStepKey = "_step";
5
+ export const kPairingChannelKey = "_pairingChannel";
6
+ export const kOnboardingSetupErrorKey = "_lastSetupError";
7
+
8
+ const loadInitialSetupState = () => {
9
+ try {
10
+ return JSON.parse(localStorage.getItem(kOnboardingStorageKey) || "{}");
11
+ } catch {
12
+ return {};
13
+ }
14
+ };
15
+
16
+ export const useWelcomeStorage = ({
17
+ kSetupStepIndex,
18
+ kPairingStepIndex,
19
+ } = {}) => {
20
+ const [initialSetupState] = useState(loadInitialSetupState);
21
+ const [vals, setVals] = useState(() => ({ ...initialSetupState }));
22
+ const [setupError, setSetupError] = useState(null);
23
+ const initialSetupError = String(
24
+ initialSetupState?.[kOnboardingSetupErrorKey] || "",
25
+ ).trim();
26
+ const shouldRecoverFromSetupState = !!initialSetupError;
27
+ const [step, setStep] = useState(() => {
28
+ const parsedStep = Number.parseInt(
29
+ String(initialSetupState?.[kOnboardingStepKey] || ""),
30
+ 10,
31
+ );
32
+ if (!Number.isFinite(parsedStep)) return 0;
33
+ const clampedStep = Math.max(0, Math.min(kPairingStepIndex, parsedStep));
34
+ if (clampedStep === kSetupStepIndex && shouldRecoverFromSetupState) return 0;
35
+ return clampedStep;
36
+ });
37
+
38
+ useEffect(() => {
39
+ localStorage.setItem(
40
+ kOnboardingStorageKey,
41
+ JSON.stringify({
42
+ ...vals,
43
+ [kOnboardingStepKey]: step,
44
+ ...(setupError ? { [kOnboardingSetupErrorKey]: setupError } : {}),
45
+ }),
46
+ );
47
+ }, [vals, step, setupError]);
48
+
49
+ const setValue = (key, value) => setVals((prev) => ({ ...prev, [key]: value }));
50
+
51
+ return {
52
+ vals,
53
+ setVals,
54
+ setValue,
55
+ step,
56
+ setStep,
57
+ setupError,
58
+ setSetupError,
59
+ };
60
+ };
@@ -12,7 +12,7 @@ const kBrowseBottomPanelUiSettingKey = "browseBottomPanelHeightPx";
12
12
  const kBrowsePanelMinHeightPx = 120;
13
13
  const kBrowseBottomMinHeightPx = 120;
14
14
  const kBrowseResizerHeightPx = 6;
15
- const kDefaultBrowseBottomPanelHeightPx = 160;
15
+ const kDefaultBrowseBottomPanelHeightPx = 260;
16
16
 
17
17
  const readStoredBrowseBottomPanelHeight = () => {
18
18
  try {
@@ -116,7 +116,7 @@ export const CreateGroupStep = ({ onNext, onBack }) => html`
116
116
  <p class="text-xs font-medium text-gray-300">Create the group</p>
117
117
  <ol class="text-xs text-gray-400 space-y-2 list-decimal list-inside">
118
118
  <li>
119
- Open Telegram and create a
119
+ Open Telegram and create a${" "}
120
120
  <span class="text-gray-300">new group</span>
121
121
  </li>
122
122
  <li>
@@ -232,7 +232,7 @@ export const Webhooks = ({
232
232
  selectedWebhook?.fullUrl || `.../hooks/${selectedHookName}`;
233
233
  const webhookUrlWithQueryToken =
234
234
  selectedWebhook?.queryStringUrl ||
235
- `${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}token=<OPENCLAW_HOOKS_TOKEN>`;
235
+ `${webhookUrl}${webhookUrl.includes("?") ? "&" : "?"}token=<WEBHOOK_TOKEN>`;
236
236
  const derivedTokenFromQuery = (() => {
237
237
  try {
238
238
  const parsed = new URL(webhookUrlWithQueryToken);
@@ -245,7 +245,7 @@ export const Webhooks = ({
245
245
  selectedWebhook?.authHeaderValue ||
246
246
  (derivedTokenFromQuery
247
247
  ? `Authorization: Bearer ${derivedTokenFromQuery}`
248
- : "Authorization: Bearer <OPENCLAW_HOOKS_TOKEN>");
248
+ : "Authorization: Bearer <WEBHOOK_TOKEN>");
249
249
  const bearerTokenValue = authHeaderValue.startsWith("Authorization: ")
250
250
  ? authHeaderValue.slice("Authorization: ".length)
251
251
  : authHeaderValue;