shadow-claw 1.24.0 → 1.24.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.
Files changed (67) hide show
  1. package/dist/public/AGENTS.md +7 -0
  2. package/dist/public/agent.worker.js +49 -49
  3. package/dist/public/backup-controller-CvzDOq4T.js +1 -0
  4. package/dist/public/{configurePeerJs-CxpZeDJJ.js → configurePeerJs-UfQ6HUlv.js} +1 -1
  5. package/dist/public/docs/example/article/index.html +1 -1
  6. package/dist/public/docs/publishing/index.html +1 -1
  7. package/dist/public/docs/skill-creator/index.html +1 -1
  8. package/dist/public/docs/subsystems/control-plane.md +11 -0
  9. package/dist/public/{downloadGroupFile-By0GR6eV.js → downloadGroupFile-9niHBj9n.js} +1 -1
  10. package/dist/public/{e2e-bridge-IGWsRl4i.js → e2e-bridge-D28tOggc.js} +1 -1
  11. package/dist/public/{file-viewer-D0eflgt6.js → file-viewer-BVg3B_JH.js} +1 -1
  12. package/dist/public/index.html +1 -1
  13. package/dist/public/index.js +1 -1
  14. package/dist/public/initControlPlane-DKNht5ph.js +1 -0
  15. package/dist/public/main/index.html +1 -1
  16. package/dist/public/main/memory/index.html +1 -1
  17. package/dist/public/{markdown-BnpKxSxl.js → markdown-SiFq0PTy.js} +1 -1
  18. package/dist/public/{orchestrator-nJj59RqD.js → orchestrator-8w9-exfN.js} +1 -1
  19. package/dist/public/{orchestrator-3h9xxHUm.js → orchestrator-tHWrqi_F.js} +5 -5
  20. package/dist/public/{peerjs-BP4laMXj.js → peerjs-D8hck8Ih.js} +1 -1
  21. package/dist/public/push-client-D0lkwrK0.js +1 -0
  22. package/dist/public/service-worker/fetch-proxy.js +1 -1
  23. package/dist/public/service-worker.js +1 -1
  24. package/dist/public/{shadow-claw-CbwETBWf.js → shadow-claw-Cs8wbk3d.js} +2 -2
  25. package/dist/public/{shadow-claw-channels-BDWG5Qa3.js → shadow-claw-channels-mY-JMvib.js} +1 -1
  26. package/dist/public/{shadow-claw-chat-Crv0DTCY.js → shadow-claw-chat-DGPKpmU5.js} +1 -1
  27. package/dist/public/{shadow-claw-control-plane-CX1IGbh9.js → shadow-claw-control-plane-B1F0jnPk.js} +2 -2
  28. package/dist/public/{shadow-claw-conversations-_9qAaRXk.js → shadow-claw-conversations-0Q-VC_7N.js} +1 -1
  29. package/dist/public/{shadow-claw-file-viewer-Blf6yGVD.js → shadow-claw-file-viewer-AZfG4D-u.js} +1 -1
  30. package/dist/public/{shadow-claw-files-CYySBDCl.js → shadow-claw-files-Bx4v0Ggp.js} +1 -1
  31. package/dist/public/{shadow-claw-git-v8S0JjuR.js → shadow-claw-git-DD7iQRxq.js} +2 -2
  32. package/dist/public/{shadow-claw-llm-GWgoVfhI.js → shadow-claw-llm-CfeDBZKB.js} +1 -1
  33. package/dist/public/{shadow-claw-networking-qHbP95z9.js → shadow-claw-networking-e0EarDVs.js} +1 -1
  34. package/dist/public/{shadow-claw-notifications-YyHOK2Fr.js → shadow-claw-notifications-BkxuDEQI.js} +3 -3
  35. package/dist/public/{shadow-claw-pages-DFZHhzFZ.js → shadow-claw-pages-BWSE91I1.js} +1 -1
  36. package/dist/public/{shadow-claw-settings-DhxrKIfA.js → shadow-claw-settings-D6lPBn6m.js} +2 -2
  37. package/dist/public/{shadow-claw-storage-BFynbP0j.js → shadow-claw-storage-Crs6LY15.js} +1 -1
  38. package/dist/public/{shadow-claw-task-server-3BIeb5oZ.js → shadow-claw-task-server-CKm_Ennl.js} +1 -1
  39. package/dist/public/{shadow-claw-tasks-Caxc9atL.js → shadow-claw-tasks-Czx6Ucf5.js} +1 -1
  40. package/dist/public/{shadow-claw-terminal-Beb5qF-U.js → shadow-claw-terminal-Dmtr9KY1.js} +1 -1
  41. package/dist/public/{shadow-claw-tools-BfJx3A9W.js → shadow-claw-tools-t3Qq4TDO.js} +1 -1
  42. package/dist/public/{shadow-claw-webvm-BCS8ykeP.js → shadow-claw-webvm-CIuSywD_.js} +1 -1
  43. package/dist/public/{syncWebMcpRegistration-B_M2fCjH.js → syncWebMcpRegistration-DzC80wVG.js} +1 -1
  44. package/dist/server.js +3 -3
  45. package/package.json +1 -1
  46. package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.test.ts +3 -0
  47. package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.ts +43 -9
  48. package/src/core/backup-controller.ts +33 -4
  49. package/src/core/control-plane-client.test.ts +9 -2
  50. package/src/core/control-plane-client.ts +18 -2
  51. package/src/core/orchestrator/utils/operations/task.ts +46 -15
  52. package/src/server/app.ts +2 -0
  53. package/src/server/control-plane.test.ts +36 -0
  54. package/src/server/control-plane.ts +39 -13
  55. package/src/server/middleware/pna.test.ts +15 -3
  56. package/src/server/middleware/pna.ts +6 -2
  57. package/src/server/routes/backup.test.ts +14 -0
  58. package/src/server/routes/backup.ts +44 -15
  59. package/src/server/server.ts +2 -0
  60. package/src/service-worker/fetch-proxy-rules.test.ts +41 -0
  61. package/src/service-worker/fetch-proxy-rules.ts +15 -8
  62. package/src/service-worker/fetch-proxy.ts +18 -2
  63. package/src/subsystems/notifications/push-client.test.ts +5 -1
  64. package/src/subsystems/notifications/push-client.ts +50 -12
  65. package/dist/public/backup-controller-C_s9in2t.js +0 -1
  66. package/dist/public/initControlPlane-C9jpnWev.js +0 -1
  67. package/dist/public/push-client-CrjEXK3U.js +0 -1
@@ -64,4 +64,45 @@ describe("shouldBypassFetchProxy", () => {
64
64
  ),
65
65
  ).toBe(true);
66
66
  });
67
+
68
+ it("bypasses loopback endpoints when accessed from GitHub Pages origin", () => {
69
+ const sseUrl = new URL(
70
+ "http://127.0.0.1:8888/api/control/events?clientId=shadow-claw-test",
71
+ );
72
+ expect(
73
+ shouldBypassFetchProxy(sseUrl, "https://xt-ml.github.io/shadow-claw/"),
74
+ ).toBe(true);
75
+
76
+ const messagesUrl = new URL("http://localhost:8888/api/control/messages");
77
+ expect(
78
+ shouldBypassFetchProxy(
79
+ messagesUrl,
80
+ "https://xt-ml.github.io/shadow-claw/",
81
+ ),
82
+ ).toBe(true);
83
+
84
+ const wsUrl = new URL("http://[::1]:8888/ws/control");
85
+ expect(
86
+ shouldBypassFetchProxy(wsUrl, "https://xt-ml.github.io/shadow-claw/"),
87
+ ).toBe(true);
88
+ });
89
+
90
+ it("bypasses private LAN IP endpoints when accessed from GitHub Pages origin", () => {
91
+ const lanUrl = new URL(
92
+ "http://192.168.1.100:8888/api/control/events?clientId=shadow-claw-test",
93
+ );
94
+ expect(
95
+ shouldBypassFetchProxy(lanUrl, "https://xt-ml.github.io/shadow-claw/"),
96
+ ).toBe(true);
97
+
98
+ const classBUrl = new URL("http://172.20.0.5:8888/api/control/events");
99
+ expect(
100
+ shouldBypassFetchProxy(classBUrl, "https://xt-ml.github.io/shadow-claw/"),
101
+ ).toBe(true);
102
+
103
+ const classAUrl = new URL("http://10.0.0.2:8888/api/control/events");
104
+ expect(
105
+ shouldBypassFetchProxy(classAUrl, "https://xt-ml.github.io/shadow-claw/"),
106
+ ).toBe(true);
107
+ });
67
108
  });
@@ -44,16 +44,23 @@ export function shouldBypassFetchProxy(
44
44
  return true;
45
45
  }
46
46
 
47
- const isLoopback = hostname === "localhost" || hostname === "127.0.0.1";
47
+ const isLoopback =
48
+ hostname === "localhost" ||
49
+ hostname === "127.0.0.1" ||
50
+ hostname === "::1" ||
51
+ hostname === "[::1]";
48
52
 
49
- const isShareTargetPath = requestUrl.pathname.endsWith(
50
- "/share/share-target.html",
53
+ if (isLoopback) {
54
+ return true;
55
+ }
56
+
57
+ const isPrivateIp = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(
58
+ hostname,
51
59
  );
52
60
 
53
- const isProxyPath =
54
- requestUrl.pathname === "/proxy" ||
55
- requestUrl.pathname.startsWith("/git-proxy/") ||
56
- isShareTargetPath;
61
+ if (isPrivateIp) {
62
+ return true;
63
+ }
57
64
 
58
- return isLoopback && isProxyPath;
65
+ return false;
59
66
  }
@@ -282,7 +282,7 @@ self.addEventListener("fetch", (event: FetchEvent) => {
282
282
  "proxy/" +
283
283
  event.request.url;
284
284
 
285
- const proxyResponse = await fetch(targetUrl.href, {
285
+ const proxyFetchOpts: any = {
286
286
  method: "POST",
287
287
  headers: { "Content-Type": "application/json" },
288
288
  body: JSON.stringify({
@@ -291,7 +291,23 @@ self.addEventListener("fetch", (event: FetchEvent) => {
291
291
  headers,
292
292
  body: bodyText,
293
293
  }),
294
- });
294
+ };
295
+
296
+ const targetHostname = targetUrl.hostname.toLowerCase();
297
+ if (
298
+ targetHostname === "localhost" ||
299
+ targetHostname === "127.0.0.1" ||
300
+ targetHostname === "::1" ||
301
+ targetHostname === "[::1]"
302
+ ) {
303
+ proxyFetchOpts.targetAddressSpace = "loopback";
304
+ } else if (
305
+ /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(targetHostname)
306
+ ) {
307
+ proxyFetchOpts.targetAddressSpace = "private";
308
+ }
309
+
310
+ const proxyResponse = await fetch(targetUrl.href, proxyFetchOpts);
295
311
 
296
312
  return proxyResponse;
297
313
  } catch (err) {
@@ -88,7 +88,9 @@ describe("push-client", () => {
88
88
 
89
89
  const key = await getVapidPublicKey();
90
90
  expect(key).toBe("test-vapid-key");
91
- expect(fetch).toHaveBeenCalledWith("/push/vapid-public-key");
91
+ expect(fetch).toHaveBeenCalledWith("/push/vapid-public-key", {
92
+ targetAddressSpace: "loopback",
93
+ });
92
94
  });
93
95
  });
94
96
 
@@ -113,6 +115,7 @@ describe("push-client", () => {
113
115
  const postCall = (fetch as any).mock.calls[1];
114
116
  expect(postCall[0]).toBe("/push/subscribe");
115
117
  expect(postCall[1].method).toBe("POST");
118
+ expect(postCall[1].targetAddressSpace).toBe("loopback");
116
119
  });
117
120
  });
118
121
 
@@ -128,6 +131,7 @@ describe("push-client", () => {
128
131
  method: "DELETE",
129
132
  headers: { "Content-Type": "application/json" },
130
133
  body: JSON.stringify({ endpoint: mockSubscription.endpoint }),
134
+ targetAddressSpace: "loopback",
131
135
  });
132
136
  });
133
137
 
@@ -12,7 +12,13 @@ import { getConfig } from "../../db/getConfig.js";
12
12
  */
13
13
  export async function getPushUrl(path: string): Promise<string> {
14
14
  const db = await getDb();
15
- const proxyUrl = await getConfig(db, CONFIG_KEYS.PUSH_PROXY_URL);
15
+ let proxyUrl = await getConfig(db, CONFIG_KEYS.PUSH_PROXY_URL);
16
+
17
+ if (!proxyUrl && typeof localStorage !== "undefined") {
18
+ try {
19
+ proxyUrl = localStorage.getItem(CONFIG_KEYS.PUSH_PROXY_URL) || undefined;
20
+ } catch (_) {}
21
+ }
16
22
 
17
23
  if (!proxyUrl) {
18
24
  return path;
@@ -28,6 +34,32 @@ export async function getPushUrl(path: string): Promise<string> {
28
34
  return `${normalizedProxy}${normalizedPath}`;
29
35
  }
30
36
 
37
+ export function getPushFetchOptions(
38
+ url: string,
39
+ baseOptions: RequestInit = {},
40
+ ): RequestInit {
41
+ const opts: any = { ...baseOptions };
42
+ try {
43
+ const locOrigin =
44
+ typeof location !== "undefined"
45
+ ? location.origin
46
+ : "http://127.0.0.1:8888";
47
+ const u = new URL(url, locOrigin);
48
+ const host = u.hostname.toLowerCase();
49
+ if (
50
+ host === "localhost" ||
51
+ host === "127.0.0.1" ||
52
+ host === "::1" ||
53
+ host === "[::1]"
54
+ ) {
55
+ opts.targetAddressSpace = "loopback";
56
+ } else if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(host)) {
57
+ opts.targetAddressSpace = "private";
58
+ }
59
+ } catch (_) {}
60
+ return opts;
61
+ }
62
+
31
63
  /**
32
64
  * Convert a URL-safe base64 string to a Uint8Array.
33
65
  * Required for PushManager.subscribe() applicationServerKey.
@@ -45,7 +77,7 @@ export function urlBase64ToUint8Array(base64String: string): Uint8Array {
45
77
  */
46
78
  export async function getVapidPublicKey(): Promise<string> {
47
79
  const url = await getPushUrl("/push/vapid-public-key");
48
- const res = await fetch(url);
80
+ const res = await fetch(url, getPushFetchOptions(url));
49
81
  const data = await res.json();
50
82
 
51
83
  return data.publicKey;
@@ -65,11 +97,14 @@ export async function subscribeToPush(): Promise<PushSubscription> {
65
97
  });
66
98
 
67
99
  const url = await getPushUrl("/push/subscribe");
68
- await fetch(url, {
69
- method: "POST",
70
- headers: { "Content-Type": "application/json" },
71
- body: JSON.stringify(subscription),
72
- });
100
+ await fetch(
101
+ url,
102
+ getPushFetchOptions(url, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify(subscription),
106
+ }),
107
+ );
73
108
 
74
109
  return subscription;
75
110
  }
@@ -90,11 +125,14 @@ export async function unsubscribeFromPush(): Promise<void> {
90
125
  await subscription.unsubscribe();
91
126
 
92
127
  const url = await getPushUrl("/push/subscribe");
93
- await fetch(url, {
94
- method: "DELETE",
95
- headers: { "Content-Type": "application/json" },
96
- body: JSON.stringify({ endpoint }),
97
- });
128
+ await fetch(
129
+ url,
130
+ getPushFetchOptions(url, {
131
+ method: "DELETE",
132
+ headers: { "Content-Type": "application/json" },
133
+ body: JSON.stringify({ endpoint }),
134
+ }),
135
+ );
98
136
  }
99
137
 
100
138
  /**
@@ -1 +0,0 @@
1
- import{t as e}from"./ulid-BY7rQVLN.js";import{t}from"./buffer-9oRIc-5Z.js";var n=t(),r=class{_clientId;_token;_serverBaseUrl;_fileEnumerator;_fileReader;_onProgress;constructor(e){this._clientId=e.clientId,this._token=e.token,this._serverBaseUrl=e.serverBaseUrl||``,this._fileEnumerator=e.fileEnumerator||(async()=>[]),this._fileReader=e.fileReader||(async()=>null),this._onProgress=e.onProgress}async initiate(){let t=e(),r=await this._fileEnumerator(),i=0,a=0,o={"Content-Type":`application/json`};this._token&&(o[`x-control-token`]=this._token);for(let e=0;e<r.length;e++){let s=r[e],c=await this._fileReader(s);if(c==null)continue;let l,u=`utf-8`;typeof c==`string`?(l=c,i+=n.Buffer.from(c,`utf-8`).length):(l=n.Buffer.from(c).toString(`base64`),u=`base64`,i+=c.byteLength||c.length||0);let d=`${this._serverBaseUrl}/api/backup/upload`,f=await fetch(d,{method:`POST`,headers:o,body:JSON.stringify({clientId:this._clientId,backupId:t,path:s,content:l,encoding:u})});if(!f.ok){let e=await f.text().catch(()=>f.statusText);throw Error(`Upload failed for ${s} (${f.status}): ${e}`)}a++,this._onProgress?.({current:a,total:r.length,file:s})}let s=`${this._serverBaseUrl}/api/backup/complete`,c=await fetch(s,{method:`POST`,headers:o,body:JSON.stringify({clientId:this._clientId,backupId:t,fileCount:a,totalBytes:i})});if(!c.ok){let e=await c.text().catch(()=>c.statusText);throw Error(`Failed to complete backup: ${e}`)}return{success:!0,backupId:t,fileCount:a,totalBytes:i}}};export{r as BackupController};
@@ -1 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-aKtaBQYM.js";import{r as t}from"./config-CrHchneq.js";import{i as n}from"./app-routes-Bs-riIvr.js";import{n as r}from"./txPromise-EBECky1b.js";import{t as i}from"./ulid-BY7rQVLN.js";import{t as a}from"./browser-nBz_r6l4.js";import{t as o}from"./getAllTasks-vpBlgGsc.js";var s=e(a(),1),c=class{_transport;_url;_httpUrl;_token;_clientId;_peerId;_deviceLabel;_capabilities;_version;_heartbeatIntervalMs;_reconnectDelayMs;_maxReconnectDelayMs;_handlers;_WebSocketClass;_EventSourceClass;_fetchFn;_ws=null;_es=null;_heartbeatTimer=null;_reconnectTimer=null;_currentReconnectDelay;_isDestroyed=!1;_state=`disconnected`;constructor(e){if(this._transport=e.transport||`sse`,this._clientId=e.clientId,this._peerId=e.peerId,this._deviceLabel=e.deviceLabel||`ShadowClaw Browser Client`,this._capabilities=e.capabilities||[`webmcp`,`opfs`,`push`],this._version=e.version||`1.23.4`,this._token=e.token,this._heartbeatIntervalMs=e.heartbeatIntervalMs||1e4,this._reconnectDelayMs=e.reconnectDelayMs||1e3,this._maxReconnectDelayMs=e.maxReconnectDelayMs||3e4,this._currentReconnectDelay=this._reconnectDelayMs,this._handlers=new Map,this._WebSocketClass=e.WebSocketClass||(typeof WebSocket<`u`?WebSocket:null),this._EventSourceClass=e.EventSourceClass||(typeof EventSource<`u`?EventSource:null),this._fetchFn=e.fetchFn||(typeof fetch<`u`?fetch.bind(globalThis):(()=>Promise.reject(Error(`fetch not available`)))),e.handlers)for(let[t,n]of Object.entries(e.handlers))this.registerHandler(t,n);let t=typeof location<`u`?location:null,n=t?`${t.protocol}//${t.host}`:`http://127.0.0.1:8888`,r=t?`${t.protocol===`https:`?`wss:`:`ws:`}//${t.host}`:`ws://127.0.0.1:8888`;this._url=e.url||`${r}/ws/control`,this._httpUrl=e.httpUrl||n}get transport(){return this._transport}registerHandler(e,t){this._handlers.set(e,t)}unregisterHandler(e){this._handlers.delete(e)}getState(){return this._state}connect(){this._isDestroyed||this._state===`connected`||this._state===`connecting`||(this._transport===`websocket`?this._connectWebSocket():this._connectSse())}_connectWebSocket(){if(!this._WebSocketClass){console.warn(`[ControlPlaneClient] WebSocket not supported in this environment`);return}this._state=`connecting`;let e=new URL(this._url,`ws://127.0.0.1:8888`);this._token&&e.searchParams.set(`token`,this._token);try{let t=new this._WebSocketClass(e.toString());this._ws=t,t.onopen=()=>{this._state=`connected`,this._currentReconnectDelay=this._reconnectDelayMs,this._sendRegister(),this._startHeartbeat()},t.onmessage=e=>{this._handleMessage(e.data)},t.onclose=()=>{this._cleanupConnection(),this._isDestroyed||this._scheduleReconnect()},t.onerror=e=>{console.warn(`[ControlPlaneClient] WebSocket error:`,e)}}catch(e){console.warn(`[ControlPlaneClient] Failed to establish WebSocket connection:`,e),this._cleanupConnection(),this._scheduleReconnect()}}_connectSse(){if(!this._EventSourceClass){(s.default===void 0||!s.default.env?.JEST_WORKER_ID)&&console.warn(`[ControlPlaneClient] EventSource not supported in this environment`);return}this._state=`connecting`;let e=new URL(`/api/control/events`,this._httpUrl);e.searchParams.set(`clientId`,this._clientId),this._token&&e.searchParams.set(`token`,this._token);try{let t=new this._EventSourceClass(e.toString());this._es=t,t.onopen=()=>{this._state=`connected`,this._currentReconnectDelay=this._reconnectDelayMs,this._sendRegister(),this._startHeartbeat()},typeof t.addEventListener==`function`&&t.addEventListener(`command:execute`,e=>{this._handleMessage(e.data)}),t.onmessage=e=>{this._handleMessage(e.data)},t.onerror=e=>{console.warn(`[ControlPlaneClient] SSE connection error:`,e),this._cleanupConnection(),this._isDestroyed||this._scheduleReconnect()}}catch(e){console.warn(`[ControlPlaneClient] Failed to establish SSE connection:`,e),this._cleanupConnection(),this._scheduleReconnect()}}disconnect(){this._isDestroyed=!0,this._cleanupConnection(),this._reconnectTimer&&=(clearTimeout(this._reconnectTimer),null)}_cleanupConnection(){if(this._state=`disconnected`,this._stopHeartbeat(),this._ws){try{this._ws.close()}catch{}this._ws=null}if(this._es){try{this._es.close()}catch{}this._es=null}}_scheduleReconnect(){this._reconnectTimer||this._isDestroyed||(this._reconnectTimer=setTimeout(()=>{this._reconnectTimer=null,this._isDestroyed||this.connect()},this._currentReconnectDelay),this._currentReconnectDelay=Math.min(this._currentReconnectDelay*1.5,this._maxReconnectDelayMs))}_startHeartbeat(){this._stopHeartbeat(),this._heartbeatTimer=setInterval(()=>{this._sendHeartbeat()},this._heartbeatIntervalMs)}_stopHeartbeat(){this._heartbeatTimer&&=(clearInterval(this._heartbeatTimer),null)}_sendRegister(){let e={clientId:this._clientId,deviceLabel:this._deviceLabel,capabilities:this._capabilities,version:this._version,transport:this._transport,peerId:this._peerId};this._sendMessage({id:i(),type:`client:register`,payload:e})}_sendHeartbeat(){let e={clientId:this._clientId,timestamp:Date.now()};this._sendMessage({id:i(),type:`client:heartbeat`,payload:e})}_sendMessage(e){if(this._transport===`websocket`){this._ws&&(this._ws.readyState===1||this._ws.readyState===this._WebSocketClass?.OPEN)&&this._ws.send(JSON.stringify(e));return}let t=new URL(`/api/control/messages`,this._httpUrl),n={"Content-Type":`application/json`};this._token&&(n[`x-control-token`]=this._token,n.Authorization=`Bearer ${this._token}`),this._fetchFn(t.toString(),{method:`POST`,headers:n,body:JSON.stringify(e)}).catch(e=>{console.warn(`[ControlPlaneClient] Failed to send message via HTTP POST:`,e)})}async _handleMessage(e){if(!e||typeof e!=`string`)return;let t;try{t=JSON.parse(e)}catch{return}if(t.type===`command:execute`){let{commandId:e,action:n,args:r}=t.payload,a=this._handlers.get(n);if(!a){this._sendMessage({id:i(),type:`command:result`,replyTo:t.id,payload:{commandId:e,success:!1,error:`Unknown action: ${n}`}});return}try{let n=await a(r);this._sendMessage({id:i(),type:`command:result`,replyTo:t.id,payload:{commandId:e,success:!0,data:n}})}catch(n){this._sendMessage({id:i(),type:`command:result`,replyTo:t.id,payload:{commandId:e,success:!1,error:n.message||String(n)}})}}}};function l(){if(typeof localStorage<`u`)try{let e=localStorage.getItem(t.CONTROL_PLANE_CLIENT_ID);if(e&&e.trim())return e.trim()}catch{}let e=n(),r=e?`${e}-${i().toLowerCase()}`:`client-${i().toLowerCase()}`;if(typeof localStorage<`u`)try{localStorage.setItem(t.CONTROL_PLANE_CLIENT_ID,r)}catch{}return r}function u(){if(typeof localStorage<`u`)try{let e=localStorage.getItem(t.CONTROL_PLANE_ENABLED);if(e!==null)return e===`true`||e===`1`}catch{}return!1}async function d(e){if(typeof localStorage<`u`)try{let e=localStorage.getItem(t.CONTROL_PLANE_ENABLED);if(e!==null)return e===`true`||e===`1`}catch{}if(e)try{let{getConfig:n}=await import(`./getConfig-D89uJgo5.js`).then(e=>e.n),r=await n(e,t.CONTROL_PLANE_ENABLED);if(r!=null)return r===`true`||r===`1`}catch{}return!1}function f(e){if(typeof EventSource>`u`&&typeof WebSocket>`u`)return!1;let t=e||(typeof location<`u`?location:null);return!(!t||t.protocol===`file:`||typeof document<`u`&&document.querySelector(`meta[name="shadowclaw-static-only"]`))}function p(e){let n=``;if(typeof localStorage<`u`)try{n=(localStorage.getItem(t.CONTROL_PLANE_URL)||``).trim()}catch{}let r=e||(typeof location<`u`?location:null),i=r&&(r.protocol===`file:`||r.hostname&&(r.hostname.endsWith(`.github.io`)||r.hostname.endsWith(`.pages.dev`))),a=n;a||=i?`http://127.0.0.1:8888`:r&&r.host?`${r.protocol}//${r.host}`:`http://127.0.0.1:8888`,a=a.replace(/\/+$/,``);let o=a.replace(/^https?:/,e=>e===`https:`?`wss:`:`ws:`);return{httpUrl:a,wsUrl:`${o}/ws/control`}}function m(){let e=[];return typeof navigator<`u`&&(navigator.storage&&typeof navigator.storage.getDirectory==`function`&&e.push(`opfs`),`serviceWorker`in navigator&&typeof window<`u`&&`PushManager`in window&&e.push(`push`),(typeof document<`u`&&document.modelContext||navigator&&navigator.modelContext)&&e.push(`webmcp`)),typeof RTCPeerConnection<`u`&&e.push(`peerjs`),e}function h(){if(typeof navigator>`u`)return`ShadowClaw Node`;let e=navigator.userAgent,t=`Desktop`;/iPad/i.test(e)?t=`iPad`:/iPhone/i.test(e)?t=`iPhone`:/Android/i.test(e)?t=`Android`:/Macintosh/i.test(e)?t=`Mac`:/Windows/i.test(e)?t=`Windows`:/Linux/i.test(e)&&(t=`Linux`);let n=`Browser`;return/Chrome/i.test(e)&&!/Edg/i.test(e)?n=`Chrome`:/Safari/i.test(e)&&!/Chrome/i.test(e)?n=`Safari`:/Firefox/i.test(e)?n=`Firefox`:/Edg/i.test(e)&&(n=`Edge`),`${t} ${n}`}let g=null;async function _(e,t={},n={}){let i=l(),a=h(),s=m();switch(e){case`send-message`:{if(!t.text)throw Error(`Missing text parameter`);let{orchestratorStore:e}=await import(`./orchestrator-3h9xxHUm.js`).then(e=>e.n),r=n.orchestrator||e.orchestrator,i=t.groupId||e.activeGroupId||`br:main`;return r?.browserChat?.submit?(r.browserChat.submit(t.text,i),{queued:!0,groupId:i}):r?.submitMessage?(r.submitMessage(t.text,i),{queued:!0,groupId:i}):e.sendMessage?(e.sendMessage(t.text),{queued:!0,groupId:i}):{queued:!1,error:`Orchestrator not ready`}}case`list-tasks`:{let e=await o(await r());return t?.groupId&&(e=e.filter(e=>e.groupId===t.groupId)),{tasks:e}}case`read-state`:{let{orchestratorStore:e}=await import(`./orchestrator-3h9xxHUm.js`).then(e=>e.n),t=n.orchestrator||e.orchestrator;return{clientId:i,deviceLabel:a,capabilities:s,activeGroupId:n.orchestrator?.activeGroupId||e.activeGroupId||t?.activeGroupId||`br:main`,state:n.orchestrator?.state||e.state||t?.state||`idle`}}case`invoke-tool`:if(!t.toolName)throw Error(`Missing toolName parameter`);return{result:await(typeof document<`u`&&document.modelContext||typeof navigator<`u`&&navigator.modelContext).invoke(t.toolName,t.input)};case`trigger-backup`:{let{orchestratorStore:e}=await import(`./orchestrator-3h9xxHUm.js`).then(e=>e.n),a=t.groupId||n.orchestrator?.activeGroupId||e.activeGroupId||`br:main`,o=await r(),{getGroupDir:s}=await import(`./getGroupDir-P1h9wl6S.js`).then(e=>e.n),c=await s(o,a);async function l(e,t=``){let n=[];for await(let[r,i]of e.entries()){let e=t?`${t}/${r}`:r;if(i.kind===`directory`){let t=await l(i,e);n.push(...t)}else n.push(e)}return n}async function u(e){try{let t=e.split(`/`),n=c;for(let e=0;e<t.length-1;e++)n=await n.getDirectoryHandle(t[e]);let r=await(await(await n.getFileHandle(t[t.length-1])).getFile()).arrayBuffer();return new Uint8Array(r)}catch{return null}}let{BackupController:d}=await import(`./backup-controller-C_s9in2t.js`);return await new d({clientId:i,token:t.token,fileEnumerator:()=>l(c),fileReader:e=>u(e)}).initiate()}default:throw Error(`Unknown action '${e}'`)}}function v(e={}){if(g){try{g.disconnect()}catch{}g=null}let n=e.clientId&&e.clientId.trim()||l(),r=m(),i=h(),a=null;if(typeof localStorage<`u`)try{let e=localStorage.getItem(t.CONTROL_PLANE_TRANSPORT);(e===`sse`||e===`websocket`)&&(a=e)}catch{}let o=e.transport||a||`sse`,s;if(typeof localStorage<`u`)try{s=localStorage.getItem(t.PEERJS_MY_PEER_ID)||void 0}catch{}let{httpUrl:u,wsUrl:d}=p(),v=new c({clientId:n,deviceLabel:i,capabilities:r,peerId:s,transport:o,httpUrl:u,url:d,handlers:{"send-message":t=>_(`send-message`,t,e),"list-tasks":t=>_(`list-tasks`,t,e),"read-state":t=>_(`read-state`,t,e),"invoke-tool":t=>_(`invoke-tool`,t,e),"trigger-backup":t=>_(`trigger-backup`,t,e)}});return e.autoConnect!==!1&&f()&&v.connect(),g=v,v}function y(){if(g){try{g.disconnect()}catch{}g=null}}export{v as createDefaultControlPlaneClient,_ as executeClientControlCommand,d as isControlPlaneEnabled,u as isControlPlaneEnabledSync,f as shouldConnectControlPlane,y as stopControlPlaneClient};
@@ -1 +0,0 @@
1
- import{r as e}from"./config-CrHchneq.js";import{n as t}from"./txPromise-EBECky1b.js";import{t as n}from"./getConfig-D89uJgo5.js";async function r(r){let i=await n(await t(),e.PUSH_PROXY_URL);if(!i)return r;let a=r.startsWith(`/`)?r:`/${r}`;return`${i.endsWith(`/`)?i.slice(0,-1):i}${a}`}function i(e){let t=(e+`=`.repeat((4-e.length%4)%4)).replace(/-/g,`+`).replace(/_/g,`/`),n=atob(t);return Uint8Array.from([...n].map(e=>e.charCodeAt(0)))}async function a(){let e=await r(`/push/vapid-public-key`);return(await(await fetch(e)).json()).publicKey}async function o(){let e=await navigator.serviceWorker.ready,t=await a(),n=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:i(t)}),o=await r(`/push/subscribe`);return await fetch(o,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(n)}),n}async function s(){let e=await(await navigator.serviceWorker.ready).pushManager.getSubscription();if(!e)return;let{endpoint:t}=e;await e.unsubscribe();let n=await r(`/push/subscribe`);await fetch(n,{method:`DELETE`,headers:{"Content-Type":`application/json`},body:JSON.stringify({endpoint:t})})}async function c(){return(await navigator.serviceWorker.ready).pushManager.getSubscription()}export{s as i,r as n,o as r,c as t};