@chrysb/alphaclaw 0.9.12 → 0.9.13

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.
@@ -8107,7 +8107,7 @@ ${O?.grantPublisher||""}`.trim()))}
8107
8107
  </${Zn}>
8108
8108
  `};var kL=R.bind(M),qf=({onRestartRequired:t=()=>{}})=>kL`
8109
8109
  <${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`
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",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
8111
  <div class="space-y-3">
8112
8112
  ${e.length?at`
8113
8113
  <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.13",
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": {