@chrysb/alphaclaw 0.9.12 → 0.9.14

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.
@@ -1202,7 +1202,6 @@ import"./chunks/chunk-72ZECFVW.js";var Gr,Pe,Ah,qr,As,kh,Mh,Th,Ph,wd,yd,xd,Rh,Aa
1202
1202
  onpointerdown=${a=>{i.current=a.target===a.currentTarget}}
1203
1203
  onpointerup=${a=>{let l=n&&i.current&&a.target===a.currentTarget;i.current=!1,l&&e?.()}}
1204
1204
  onpointercancel=${()=>{i.current=!1}}
1205
- onclick=${a=>{a.preventDefault()}}
1206
1205
  >
1207
1206
  <div class=${o}>${r}</div>
1208
1207
  </div>
@@ -8107,7 +8106,7 @@ ${O?.grantPublisher||""}`.trim()))}
8107
8106
  </${Zn}>
8108
8107
  `};var kL=R.bind(M),qf=({onRestartRequired:t=()=>{}})=>kL`
8109
8108
  <${V1} onRestartRequired=${t} />
8110
- `;var CL=1e4,j1=({enabled:t=!0}={})=>{let e=Le(async()=>{let n=await ag(),s=Array.isArray(n?.nodes)?n.nodes:[],o=Array.isArray(n?.pending)?n.pending:[];return{nodes:s,pending:o}},CL,{enabled:t,cacheKey:"/api/nodes"});return{nodes:Array.isArray(e.data?.nodes)?e.data.nodes:[],pending:Array.isArray(e.data?.pending)?e.data.pending:[],loading:e.data===null&&!e.error,error:e.error?String(e.error.message||"Could not load nodes"):"",refresh:e.refresh}};var U1=()=>{let t=j1({enabled:!0}),[e,n]=x(!1),[s,o]=x(!1),{data:r,error:i}=it("/api/nodes/connect-info",za,{maxAgeMs:6e4}),a=Array.isArray(t.nodes)?t.nodes.filter(c=>c?.paired!==!1):[];P(()=>{i&&E(i.message||"Could not load node connect command","error")},[i]);let l=G(async()=>{if(!s){o(!0);try{await t.refresh()}finally{o(!1)}}},[t.refresh,s]);return{state:{wizardVisible:e,nodes:a,pending:t.pending,loadingNodes:t.loading,refreshingNodes:s,nodesError:t.error,connectInfo:r},actions:{openWizard:()=>n(!0),closeWizard:()=>n(!1),refreshNodes:l}}};var _L=35e3,AL=1e4,G1="nodesBrowserAttachStateByNode",ML=/selected page has been closed/i,TL=async(t,e=_L)=>{let n=null;try{return await Promise.race([t,new Promise((s,o)=>{n=setTimeout(()=>{o(new Error("Browser check timed out"))},e)})])}finally{n&&clearTimeout(n)}},K1=t=>{let e=Array.isArray(t?.caps)?t.caps:[],n=Array.isArray(t?.commands)?t.commands:[];return e.includes("browser")||n.includes("browser.proxy")},PL=t=>{let e=String(t?.message||"Could not check node browser status").trim();return ML.test(e)?"Selected Chrome page was closed. Click Attach to reconnect.":e},RL=()=>{let e=ze()?.[G1];return!e||typeof e!="object"||Array.isArray(e)?{}:e},LL=(t={})=>{Ml(e=>({...e&&typeof e=="object"?e:{},[G1]:t&&typeof t=="object"?t:{}}))},q1=({nodes:t=[],onRefreshNodes:e=async()=>{}}={})=>{let[n,s]=x({}),[o,r]=x({}),[i,a]=x(""),[l,c]=x(()=>RL()),[d,u]=x(""),[p,f]=x(null),[g,m]=x(""),h=oe(0),b=oe(""),y=async(S,{successMessage:_="Connection command copied",errorMessage:D="Could not copy connection command"}={})=>{if(await Dn(S)){E(_,"success");return}E(D,"error")},v=G(async(S,{silent:_=!1}={})=>{let D=String(S||"").trim();if(!(!D||b.current)){b.current=D,_||a(D),r(I=>({...I,[D]:""}));try{let I=await TL(dg(D,"user")),H=I?.status&&typeof I.status=="object"?I.status:null;s(B=>({...B,[D]:H}))}catch(I){let H=PL(I);s(B=>({...B,[D]:null})),r(B=>({...B,[D]:H})),_||E(H,"error")}finally{b.current="",_||a("")}}},[]),$=G((S,_)=>{let D=String(S||"").trim();D&&c(I=>{let H={...I&&typeof I=="object"?I:{},[D]:_===!0};return LL(H),H})},[]),w=G(async S=>{let _=String(S||"").trim();_&&($(_,!0),await v(_))},[v,$]),k=G(S=>{let _=String(S||"").trim();_&&($(_,!1),s(D=>{let I={...D||{}};return delete I[_],I}),r(D=>{let I={...D||{}};return delete I[_],I}))},[$]),A=G(S=>{let _=String(S||"").trim();_&&u(D=>D===_?"":_)},[]),C=G(async()=>{let S=String(p?.nodeId||"").trim();if(!(!S||g)){m(S);try{await lg(S),k(S),E("Device removed","success"),f(null),u(""),await e()}catch(_){E(_.message||"Could not remove node","error")}finally{m("")}}},[k,e,p,g]);return P(()=>{if(i)return;let S=t.map(_=>({nodeId:String(_?.nodeId||"").trim(),connected:_?.connected===!0,browserCapable:K1(_)})).find(_=>!(!_.nodeId||!_.connected||!_.browserCapable||l?.[_.nodeId]!==!0||n?.[_.nodeId]||o?.[_.nodeId]))?.nodeId;S&&v(S,{silent:!0})},[l,o,n,i,v,t]),P(()=>{if(i)return;let S=t.map(H=>({nodeId:String(H?.nodeId||"").trim(),connected:H?.connected===!0,browserCapable:K1(H),browserRunning:n?.[String(H?.nodeId||"").trim()]?.running===!0})).filter(H=>H.nodeId&&H.connected&&H.browserCapable&&l?.[H.nodeId]===!0&&H.browserRunning).map(H=>H.nodeId);if(!S.length)return;let _=!0,I=setInterval(async()=>{if(!_||b.current)return;let H=h.current%S.length;h.current+=1;let B=S[H];await v(B,{silent:!0})},AL);return()=>{_=!1,clearInterval(I)}},[l,n,i,v,t]),{browserStatusByNodeId:n,browserErrorByNodeId:o,checkingBrowserNodeId:i,browserAttachStateByNodeId:l,menuOpenNodeId:d,removeDialogNode:p,removingNodeId:g,handleCopyText:y,handleCheckNodeBrowser:v,handleAttachNodeBrowser:w,handleDetachNodeBrowser:k,handleOpenNodeMenu:A,handleRemoveNode:C,setMenuOpenNodeId:u,setRemoveDialogNode:f}};var at=R.bind(M),IL=t=>String(t||"").replace(/"/g,'\\"'),J1=({node:t,connectInfo:e,maskToken:n=!1})=>{let s=String(e?.gatewayHost||"").trim()||"localhost",o=Number(e?.gatewayPort)||3e3,r=String(e?.gatewayToken||"").trim(),i=e?.tls===!0?"--tls":"",a=String(t?.displayName||t?.nodeId||"My Node").trim(),l=n?"****":r;return[l?`OPENCLAW_GATEWAY_TOKEN=${l}`:"","openclaw node run",`--host ${s}`,`--port ${o}`,i,`--display-name "${IL(a)}"`].filter(Boolean).join(" ")},EL=t=>t?.connected?at`<${ge} tone="success">Connected</${ge}>`:t?.paired?at`<${ge} tone="warning">Disconnected</${ge}>`:at`<${ge} tone="danger">Pending approval</${ge}>`,DL=t=>{let e=Array.isArray(t?.caps)?t.caps:[],n=Array.isArray(t?.commands)?t.commands:[];return e.includes("browser")||n.includes("browser.proxy")},NL=t=>t.running?"success":"warning",OL=t=>t.running?"Attached":"Not connected",Z1=({nodes:t=[],pending:e=[],loading:n=!1,error:s="",connectInfo:o=null,onRefreshNodes:r=async()=>{}})=>{let i=q1({nodes:t,onRefreshNodes:r}),{browserStatusByNodeId:a,browserErrorByNodeId:l,checkingBrowserNodeId:c,browserAttachStateByNodeId:d,menuOpenNodeId:u,removeDialogNode:p,removingNodeId:f,handleCopyText:g,handleCheckNodeBrowser:m,handleAttachNodeBrowser:h,handleDetachNodeBrowser:b,handleOpenNodeMenu:y,handleRemoveNode:v,setMenuOpenNodeId:$,setRemoveDialogNode:w}=i;return at`
8109
+ `;var CL=1e4,j1=({enabled:t=!0}={})=>{let e=Le(async()=>{let n=await ag(),s=Array.isArray(n?.nodes)?n.nodes:[],o=Array.isArray(n?.pending)?n.pending:[];return{nodes:s,pending:o}},CL,{enabled:t,cacheKey:"/api/nodes",dedupeInFlight:!0});return{nodes:Array.isArray(e.data?.nodes)?e.data.nodes:[],pending:Array.isArray(e.data?.pending)?e.data.pending:[],loading:e.data===null&&!e.error,error:e.error?String(e.error.message||"Could not load nodes"):"",refresh:e.refresh}};var U1=()=>{let t=j1({enabled:!0}),[e,n]=x(!1),[s,o]=x(!1),{data:r,error:i}=it("/api/nodes/connect-info",za,{maxAgeMs:6e4}),a=Array.isArray(t.nodes)?t.nodes.filter(c=>c?.paired!==!1):[];P(()=>{i&&E(i.message||"Could not load node connect command","error")},[i]);let l=G(async()=>{if(!s){o(!0);try{await t.refresh()}finally{o(!1)}}},[t.refresh,s]);return{state:{wizardVisible:e,nodes:a,pending:t.pending,loadingNodes:t.loading,refreshingNodes:s,nodesError:t.error,connectInfo:r},actions:{openWizard:()=>n(!0),closeWizard:()=>n(!1),refreshNodes:l}}};var _L=35e3,AL=1e4,G1="nodesBrowserAttachStateByNode",ML=/selected page has been closed/i,TL=async(t,e=_L)=>{let n=null;try{return await Promise.race([t,new Promise((s,o)=>{n=setTimeout(()=>{o(new Error("Browser check timed out"))},e)})])}finally{n&&clearTimeout(n)}},K1=t=>{let e=Array.isArray(t?.caps)?t.caps:[],n=Array.isArray(t?.commands)?t.commands:[];return e.includes("browser")||n.includes("browser.proxy")},PL=t=>{let e=String(t?.message||"Could not check node browser status").trim();return ML.test(e)?"Selected Chrome page was closed. Click Attach to reconnect.":e},RL=()=>{let e=ze()?.[G1];return!e||typeof e!="object"||Array.isArray(e)?{}:e},LL=(t={})=>{Ml(e=>({...e&&typeof e=="object"?e:{},[G1]:t&&typeof t=="object"?t:{}}))},q1=({nodes:t=[],onRefreshNodes:e=async()=>{}}={})=>{let[n,s]=x({}),[o,r]=x({}),[i,a]=x(""),[l,c]=x(()=>RL()),[d,u]=x(""),[p,f]=x(null),[g,m]=x(""),h=oe(0),b=oe(""),y=async(S,{successMessage:_="Connection command copied",errorMessage:D="Could not copy connection command"}={})=>{if(await Dn(S)){E(_,"success");return}E(D,"error")},v=G(async(S,{silent:_=!1}={})=>{let D=String(S||"").trim();if(!(!D||b.current)){b.current=D,_||a(D),r(I=>({...I,[D]:""}));try{let I=await TL(dg(D,"user")),H=I?.status&&typeof I.status=="object"?I.status:null;s(B=>({...B,[D]:H}))}catch(I){let H=PL(I);s(B=>({...B,[D]:null})),r(B=>({...B,[D]:H})),_||E(H,"error")}finally{b.current="",_||a("")}}},[]),$=G((S,_)=>{let D=String(S||"").trim();D&&c(I=>{let H={...I&&typeof I=="object"?I:{},[D]:_===!0};return LL(H),H})},[]),w=G(async S=>{let _=String(S||"").trim();_&&($(_,!0),await v(_))},[v,$]),k=G(S=>{let _=String(S||"").trim();_&&($(_,!1),s(D=>{let I={...D||{}};return delete I[_],I}),r(D=>{let I={...D||{}};return delete I[_],I}))},[$]),A=G(S=>{let _=String(S||"").trim();_&&u(D=>D===_?"":_)},[]),C=G(async()=>{let S=String(p?.nodeId||"").trim();if(!(!S||g)){m(S);try{await lg(S),k(S),E("Device removed","success"),f(null),u(""),await e()}catch(_){E(_.message||"Could not remove node","error")}finally{m("")}}},[k,e,p,g]);return P(()=>{if(i)return;let S=t.map(_=>({nodeId:String(_?.nodeId||"").trim(),connected:_?.connected===!0,browserCapable:K1(_)})).find(_=>!(!_.nodeId||!_.connected||!_.browserCapable||l?.[_.nodeId]!==!0||n?.[_.nodeId]||o?.[_.nodeId]))?.nodeId;S&&v(S,{silent:!0})},[l,o,n,i,v,t]),P(()=>{if(i)return;let S=t.map(H=>({nodeId:String(H?.nodeId||"").trim(),connected:H?.connected===!0,browserCapable:K1(H),browserRunning:n?.[String(H?.nodeId||"").trim()]?.running===!0})).filter(H=>H.nodeId&&H.connected&&H.browserCapable&&l?.[H.nodeId]===!0&&H.browserRunning).map(H=>H.nodeId);if(!S.length)return;let _=!0,I=setInterval(async()=>{if(!_||b.current)return;let H=h.current%S.length;h.current+=1;let B=S[H];await v(B,{silent:!0})},AL);return()=>{_=!1,clearInterval(I)}},[l,n,i,v,t]),{browserStatusByNodeId:n,browserErrorByNodeId:o,checkingBrowserNodeId:i,browserAttachStateByNodeId:l,menuOpenNodeId:d,removeDialogNode:p,removingNodeId:g,handleCopyText:y,handleCheckNodeBrowser:v,handleAttachNodeBrowser:w,handleDetachNodeBrowser:k,handleOpenNodeMenu:A,handleRemoveNode:C,setMenuOpenNodeId:u,setRemoveDialogNode:f}};var at=R.bind(M),IL=t=>String(t||"").replace(/"/g,'\\"'),J1=({node:t,connectInfo:e,maskToken:n=!1})=>{let s=String(e?.gatewayHost||"").trim()||"localhost",o=Number(e?.gatewayPort)||3e3,r=String(e?.gatewayToken||"").trim(),i=e?.tls===!0?"--tls":"",a=String(t?.displayName||t?.nodeId||"My Node").trim(),l=n?"****":r;return[l?`OPENCLAW_GATEWAY_TOKEN=${l}`:"","openclaw node run",`--host ${s}`,`--port ${o}`,i,`--display-name "${IL(a)}"`].filter(Boolean).join(" ")},EL=t=>t?.connected?at`<${ge} tone="success">Connected</${ge}>`:t?.paired?at`<${ge} tone="warning">Disconnected</${ge}>`:at`<${ge} tone="danger">Pending approval</${ge}>`,DL=t=>{let e=Array.isArray(t?.caps)?t.caps:[],n=Array.isArray(t?.commands)?t.commands:[];return e.includes("browser")||n.includes("browser.proxy")},NL=t=>t.running?"success":"warning",OL=t=>t.running?"Attached":"Not connected",Z1=({nodes:t=[],pending:e=[],loading:n=!1,error:s="",connectInfo:o=null,onRefreshNodes:r=async()=>{}})=>{let i=q1({nodes:t,onRefreshNodes:r}),{browserStatusByNodeId:a,browserErrorByNodeId:l,checkingBrowserNodeId:c,browserAttachStateByNodeId:d,menuOpenNodeId:u,removeDialogNode:p,removingNodeId:f,handleCopyText:g,handleCheckNodeBrowser:m,handleAttachNodeBrowser:h,handleDetachNodeBrowser:b,handleOpenNodeMenu:y,handleRemoveNode:v,setMenuOpenNodeId:$,setRemoveDialogNode:w}=i;return at`
8111
8110
  <div class="space-y-3">
8112
8111
  ${e.length?at`
8113
8112
  <div
@@ -46,9 +46,6 @@ export const ModalShell = ({
46
46
  onpointercancel=${() => {
47
47
  overlayPointerDownRef.current = false;
48
48
  }}
49
- onclick=${(event) => {
50
- event.preventDefault();
51
- }}
52
49
  >
53
50
  <div class=${panelClassName}>${children}</div>
54
51
  </div>
@@ -12,7 +12,7 @@ export const useConnectedNodes = ({ enabled = true } = {}) => {
12
12
  return { nodes, pending };
13
13
  },
14
14
  kNodesPollIntervalMs,
15
- { enabled, cacheKey: "/api/nodes" },
15
+ { enabled, cacheKey: "/api/nodes", dedupeInFlight: true },
16
16
  );
17
17
 
18
18
  return {
@@ -55,6 +55,11 @@ const createCommands = ({ gatewayEnv }) => {
55
55
  stderr: stderr.trim(),
56
56
  code: err?.code,
57
57
  };
58
+ if (err) {
59
+ result.killed = Boolean(err.killed);
60
+ result.signal = err.signal || null;
61
+ result.timedOut = Boolean(err.killed && err.signal === killSignal);
62
+ }
58
63
  if (!quiet && !result.ok) {
59
64
  console.log(`[alphaclaw] Error: ${result.stderr.slice(0, 200)}`);
60
65
  }
@@ -12,12 +12,46 @@ const kAllowedExecAsk = new Set(["off", "on-miss", "always"]);
12
12
  const kSafeNodeIdPattern = /^[\w\-:.]+$/;
13
13
  const kNodeBrowserInvokeTimeoutMs = 30000;
14
14
  const kNodeBrowserCliTimeoutMs = 35000;
15
- const kNodeRouteCliTimeoutMs = 12000;
16
- const kNodesStatusCliTimeoutMs = 5000;
17
- const kNodesPendingCliTimeoutMs = 5000;
15
+ const kDefaultNodeRouteCliTimeoutMs = 12000;
16
+ const kDefaultNodesStatusCliTimeoutMs = 12000;
17
+ const kDefaultNodesPendingCliTimeoutMs = 12000;
18
18
 
19
19
  const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
20
20
 
21
+ const resolveCliTimeoutMs = (envName, fallbackMs, env = process.env) => {
22
+ const parsed = Number(env[envName]);
23
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallbackMs;
24
+ return Math.round(parsed);
25
+ };
26
+
27
+ const resolveNodeCliTimeouts = (env = process.env) => ({
28
+ route: resolveCliTimeoutMs(
29
+ "ALPHACLAW_NODE_ROUTE_TIMEOUT_MS",
30
+ kDefaultNodeRouteCliTimeoutMs,
31
+ env,
32
+ ),
33
+ status: resolveCliTimeoutMs(
34
+ "ALPHACLAW_NODES_STATUS_TIMEOUT_MS",
35
+ kDefaultNodesStatusCliTimeoutMs,
36
+ env,
37
+ ),
38
+ pending: resolveCliTimeoutMs(
39
+ "ALPHACLAW_NODES_PENDING_TIMEOUT_MS",
40
+ kDefaultNodesPendingCliTimeoutMs,
41
+ env,
42
+ ),
43
+ });
44
+
45
+ const isCliTimeoutResult = (result) =>
46
+ Boolean(result?.timedOut || (result?.killed && result?.signal));
47
+
48
+ const formatCliFailure = ({ result, fallback, timeoutLabel, timeoutMs }) => {
49
+ if (isCliTimeoutResult(result)) {
50
+ return `${timeoutLabel} CLI timed out after ${timeoutMs}ms`;
51
+ }
52
+ return String(result?.stderr || "").trim() || fallback;
53
+ };
54
+
21
55
  const normalizeExecAsk = (value) => {
22
56
  const normalized = String(value || "").trim().toLowerCase();
23
57
  if (normalized === "on") return "on-miss";
@@ -159,21 +193,28 @@ const registerNodeRoutes = ({
159
193
  gatewayToken = "",
160
194
  fsModule,
161
195
  }) => {
196
+ const cliTimeouts = resolveNodeCliTimeouts();
197
+
162
198
  app.get("/api/nodes", async (_req, res) => {
163
199
  const statusResult = await clawCmd("nodes status --json", {
164
200
  quiet: true,
165
- timeoutMs: kNodesStatusCliTimeoutMs,
201
+ timeoutMs: cliTimeouts.status,
166
202
  });
167
203
  if (!statusResult.ok) {
168
204
  return res.status(500).json({
169
205
  ok: false,
170
- error: statusResult.stderr || "Could not load nodes status",
206
+ error: formatCliFailure({
207
+ result: statusResult,
208
+ fallback: "Could not load nodes status",
209
+ timeoutLabel: "nodes status",
210
+ timeoutMs: cliTimeouts.status,
211
+ }),
171
212
  });
172
213
  }
173
214
  const status = parseNodesStatus(statusResult.stdout);
174
215
  const pendingResult = await clawCmd("nodes pending --json", {
175
216
  quiet: true,
176
- timeoutMs: kNodesPendingCliTimeoutMs,
217
+ timeoutMs: cliTimeouts.pending,
177
218
  });
178
219
  const pending = pendingResult.ok
179
220
  ? parseNodesPending(pendingResult.stdout)
@@ -220,14 +261,17 @@ const registerNodeRoutes = ({
220
261
  for (const command of commands) {
221
262
  const result = await clawCmd(command, {
222
263
  quiet: true,
223
- timeoutMs: kNodeRouteCliTimeoutMs,
264
+ timeoutMs: cliTimeouts.route,
224
265
  });
225
266
  if (!result.ok) {
226
267
  return res.status(500).json({
227
268
  ok: false,
228
- error:
229
- result.stderr ||
230
- `Could not apply node routing (${command})`,
269
+ error: formatCliFailure({
270
+ result,
271
+ fallback: `Could not apply node routing (${command})`,
272
+ timeoutLabel: "node routing",
273
+ timeoutMs: cliTimeouts.route,
274
+ }),
231
275
  });
232
276
  }
233
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "express": "^4.21.0",
35
35
  "http-proxy": "^1.18.1",
36
- "openclaw": "2026.4.24",
36
+ "openclaw": "2026.5.2",
37
37
  "ws": "^8.19.0"
38
38
  },
39
39
  "devDependencies": {