opencami 1.8.9 → 1.8.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.
Files changed (102) hide show
  1. package/dist/client/assets/{CSPContext-DI-5GAnQ.js → CSPContext-DATmz4sz.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-CrIsc5n9.js → DirectionContext-FHFKdJq_.js} +1 -1
  3. package/dist/client/assets/_sessionKey-D2ZMdqJ0.js +23 -0
  4. package/dist/client/assets/agents-9A0buuwT.js +2 -0
  5. package/dist/client/assets/agents-screen-C58VLjES.js +1 -0
  6. package/dist/client/assets/bots-Lqt92ma0.js +2 -0
  7. package/dist/client/assets/{bots-screen-BTKCOohV.js → bots-screen-B8h4Y1VL.js} +1 -1
  8. package/dist/client/assets/{button-8ab4wOwy.js → button-BcUmP7JL.js} +1 -1
  9. package/dist/client/assets/{composite-B2qsrzf3.js → composite--BBbKdxm.js} +1 -1
  10. package/dist/client/assets/{connect-B3_p7C4I.js → connect-BmwKnEFP.js} +1 -1
  11. package/dist/client/assets/{dashboard-BtClHYpn.js → dashboard-BWQl_-2-.js} +1 -1
  12. package/dist/client/assets/event-BxzjTPjG.js +1 -0
  13. package/dist/client/assets/file-explorer-screen-BAbbb_R4.js +1 -0
  14. package/dist/client/assets/files-DvIFms9G.js +2 -0
  15. package/dist/client/assets/follow-up-suggestions-Bq-xR5GV.js +5 -0
  16. package/dist/client/assets/{index-BXiha-Vz.js → index-BTtT4Vbb.js} +1 -1
  17. package/dist/client/assets/{index-CtlYu8Ug.js → index-rOr2i8gW.js} +1 -1
  18. package/dist/client/assets/keyboard-shortcuts-dialog-BA8cBwWm.js +1 -0
  19. package/dist/client/assets/main-_1IghVZn.js +212 -0
  20. package/dist/client/assets/markdown-CqIcraPO.js +87 -0
  21. package/dist/client/assets/memory-B3LyVZwO.js +2 -0
  22. package/dist/client/assets/memory-screen-GBzowsnE.js +1 -0
  23. package/dist/client/assets/menu--FXN_JCl.js +1 -0
  24. package/dist/client/assets/{opencami-logo-BSed2Wez.js → opencami-logo-BFo87CeM.js} +1 -1
  25. package/dist/client/assets/proxy-CJ6-TEKD.js +9 -0
  26. package/dist/client/assets/{react-WkSlhZJd.js → react-BRYbnjMp.js} +1 -1
  27. package/dist/client/assets/search-dialog-CJS2GUnE.js +1 -0
  28. package/dist/client/assets/{search-sources-badge-Du8KpUEb.js → search-sources-badge-D0YArqfp.js} +1 -1
  29. package/dist/client/assets/session-export-dialog-Bc9YEPLs.js +1 -0
  30. package/dist/client/assets/settings-dialog-SDzqDXsx.js +1 -0
  31. package/dist/client/assets/skills-BVLjvrPU.js +2 -0
  32. package/dist/client/assets/{skills-panel-CVh1I-7D.js → skills-panel-DIalYvpW.js} +1 -1
  33. package/dist/client/assets/styles-b2XYKZYx.css +1 -0
  34. package/dist/client/assets/switch-BwmDNR0d.js +1 -0
  35. package/dist/client/assets/tabs-T1fJbUsm.js +1 -0
  36. package/dist/client/assets/thinking-DUdE6Iej.js +1 -0
  37. package/dist/client/assets/tooltip-DexBPu8g.js +1 -0
  38. package/dist/client/assets/use-file-explorer-state-eXs92yKN.js +12 -0
  39. package/dist/client/assets/{useBaseUiId-DiwX_3so.js → useBaseUiId-DN9cOflS.js} +1 -1
  40. package/dist/client/assets/{useCompositeItem-UPIPwR9H.js → useCompositeItem-B_J-cnZX.js} +1 -1
  41. package/dist/client/assets/{useControlled-CT2hRlcU.js → useControlled-DeDLOsPR.js} +1 -1
  42. package/dist/client/assets/{useMutation-rx8UH99I.js → useMutation-TzgejsE_.js} +1 -1
  43. package/dist/client/assets/useOnFirstRender-S4-xcV93.js +1 -0
  44. package/dist/client/assets/{useQuery-Boaa6oF3.js → useQuery-BdjM5kzn.js} +1 -1
  45. package/dist/server/assets/{_sessionKey-B6iYeyCS.js → _sessionKey-DBzlGMo3.js} +147 -129
  46. package/dist/server/assets/{_tanstack-start-manifest_v-C9chPgNH.js → _tanstack-start-manifest_v-CX4xb59U.js} +1 -1
  47. package/dist/server/assets/{agents-CmQ4vvXm.js → agents-BuE0Yum3.js} +1 -1
  48. package/dist/server/assets/{agents-screen-bmrIyFbk.js → agents-screen-CEQhbEwf.js} +3 -3
  49. package/dist/server/assets/{bots-Byt6jv0a.js → bots-BDHeSvSQ.js} +1 -1
  50. package/dist/server/assets/{bots-screen-C2TGFv42.js → bots-screen-C0NRS526.js} +2 -2
  51. package/dist/server/assets/{button-CwY2OHFj.js → button-kI8fEIZQ.js} +1 -1
  52. package/dist/server/assets/{connect-BNabuqpW.js → connect-CTVBm0Vc.js} +2 -2
  53. package/dist/server/assets/{file-explorer-screen-DH4UFK03.js → file-explorer-screen-FU_NhZmS.js} +4 -4
  54. package/dist/server/assets/{files-DYdXlQDr.js → files-DLxqp-h5.js} +1 -1
  55. package/dist/server/assets/{follow-up-suggestions-mzRQIB0k.js → follow-up-suggestions-BmSFSY87.js} +8 -8
  56. package/dist/server/assets/{index-BEWnDAH6.js → index-B_F4DTUu.js} +1 -1
  57. package/dist/server/assets/{index-COElhwGA.js → index-C1dylvu-.js} +1 -1
  58. package/dist/server/assets/{keyboard-shortcuts-dialog-Cr6fOqHz.js → keyboard-shortcuts-dialog-Cp3ECNNi.js} +2 -2
  59. package/dist/server/assets/{markdown-DoX5Q7qh.js → markdown-CFdYXCRQ.js} +3 -3
  60. package/dist/server/assets/{memory-Cxu7i8ej.js → memory-rBB015W-.js} +1 -1
  61. package/dist/server/assets/{memory-screen-B5l1NZRY.js → memory-screen-vqXczcVo.js} +4 -4
  62. package/dist/server/assets/{menu-D90CDTi2.js → menu-D8cKTpmN.js} +1 -1
  63. package/dist/server/assets/{router-BqLGFd4L.js → router-CL6A11qi.js} +305 -85
  64. package/dist/server/assets/{search-dialog-CmI7naPN.js → search-dialog-D8DvpPZw.js} +8 -8
  65. package/dist/server/assets/{search-sources-badge-B0rAEDs_.js → search-sources-badge-B0t8Qffy.js} +1 -1
  66. package/dist/server/assets/{session-export-dialog-C53RRAah.js → session-export-dialog-CgtlOnwf.js} +2 -2
  67. package/dist/server/assets/{settings-dialog-BZ67gr9N.js → settings-dialog-CeP-J_NJ.js} +35 -18
  68. package/dist/server/assets/{skills-Cy8xclXY.js → skills-BXUivxuo.js} +1 -1
  69. package/dist/server/assets/{skills-panel-BnRNb7u9.js → skills-panel-CDUp4jvw.js} +2 -2
  70. package/dist/server/assets/{switch-BbkUeVDV.js → switch-BZzwkgAQ.js} +1 -1
  71. package/dist/server/assets/{tabs-DDFZob0m.js → tabs-CWbp3mT4.js} +1 -1
  72. package/dist/server/assets/{thinking-CA8PSwKJ.js → thinking-ClIWjSbx.js} +8 -8
  73. package/dist/server/assets/{tooltip-DgsSPocE.js → tooltip-DOvOrSSS.js} +1 -1
  74. package/dist/server/assets/{use-file-explorer-state-s7CS50ho.js → use-file-explorer-state-E6cUvMva.js} +1 -1
  75. package/dist/server/server.js +195 -38
  76. package/package.json +1 -1
  77. package/dist/client/assets/_sessionKey-B4NZmxf3.js +0 -21
  78. package/dist/client/assets/agents-bptidK8z.js +0 -2
  79. package/dist/client/assets/agents-screen-6qdnPmx2.js +0 -1
  80. package/dist/client/assets/bots-BWpbaQ-E.js +0 -2
  81. package/dist/client/assets/event-DG3RKJz8.js +0 -1
  82. package/dist/client/assets/file-explorer-screen-Djl8x-8P.js +0 -1
  83. package/dist/client/assets/files-CjbCJDgC.js +0 -2
  84. package/dist/client/assets/follow-up-suggestions-BSCMXRXh.js +0 -5
  85. package/dist/client/assets/keyboard-shortcuts-dialog-HAufCn9C.js +0 -1
  86. package/dist/client/assets/main-CQKtcNr3.js +0 -210
  87. package/dist/client/assets/markdown-DFJF-FsV.js +0 -87
  88. package/dist/client/assets/memory-DnJOmcwU.js +0 -2
  89. package/dist/client/assets/memory-screen-Bm4NMAnU.js +0 -1
  90. package/dist/client/assets/menu-D26Vmgxl.js +0 -1
  91. package/dist/client/assets/popupStateMapping-DkI2OCkW.js +0 -1
  92. package/dist/client/assets/proxy-CHQ-VCN1.js +0 -9
  93. package/dist/client/assets/search-dialog-CCl4d0Pi.js +0 -1
  94. package/dist/client/assets/session-export-dialog-io9FvLKq.js +0 -1
  95. package/dist/client/assets/settings-dialog-B93qswor.js +0 -1
  96. package/dist/client/assets/skills-BNDGnHwM.js +0 -2
  97. package/dist/client/assets/styles-Ce2xZzc4.css +0 -1
  98. package/dist/client/assets/switch-CSnzINDW.js +0 -1
  99. package/dist/client/assets/tabs-CWfn44FL.js +0 -1
  100. package/dist/client/assets/thinking-BmoLlbFC.js +0 -1
  101. package/dist/client/assets/tooltip-CSGMH2t4.js +0 -1
  102. package/dist/client/assets/use-file-explorer-state-BYVzjwPA.js +0 -12
@@ -12,7 +12,7 @@ import { execFile, execSync } from "node:child_process";
12
12
  import { promisify } from "node:util";
13
13
  import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
14
14
  import { posix } from "path";
15
- const appCss = "/assets/styles-Ce2xZzc4.css";
15
+ const appCss = "/assets/styles-b2XYKZYx.css";
16
16
  const swRegisterScript = `
17
17
  (() => {
18
18
  // Skip PWA service worker inside Capacitor native shell — they conflict
@@ -157,20 +157,22 @@ const accentColorScript = `
157
157
  try {
158
158
  const stored = localStorage.getItem('opencami-accent-color')
159
159
  const map = {
160
- green: { accent: '#22c55e', hover: '#16a34a', light: 'rgba(34, 197, 94, 0.10)' },
161
- blue: { accent: '#3b82f6', hover: '#2563eb', light: 'rgba(59, 130, 246, 0.10)' },
162
- purple: { accent: '#8b5cf6', hover: '#7c3aed', light: 'rgba(139, 92, 246, 0.10)' },
163
- orange: { accent: '#f97316', hover: '#ea580c', light: 'rgba(249, 115, 22, 0.10)' },
164
- pink: { accent: '#ec4899', hover: '#db2777', light: 'rgba(236, 72, 153, 0.10)' },
165
- red: { accent: '#ef4444', hover: '#dc2626', light: 'rgba(239, 68, 68, 0.10)' },
166
- cyan: { accent: '#06b6d4', hover: '#0891b2', light: 'rgba(6, 182, 212, 0.10)' },
167
- yellow: { accent: '#eab308', hover: '#ca8a04', light: 'rgba(234, 179, 8, 0.10)' },
160
+ green: { accent: '#22c55e', hover: '#16a34a', light: 'rgba(34, 197, 94, 0.10)', fg: '#ffffff' },
161
+ blue: { accent: '#3b82f6', hover: '#2563eb', light: 'rgba(59, 130, 246, 0.10)', fg: '#ffffff' },
162
+ purple: { accent: '#8b5cf6', hover: '#7c3aed', light: 'rgba(139, 92, 246, 0.10)', fg: '#ffffff' },
163
+ orange: { accent: '#f97316', hover: '#ea580c', light: 'rgba(249, 115, 22, 0.10)', fg: '#ffffff' },
164
+ pink: { accent: '#ec4899', hover: '#db2777', light: 'rgba(236, 72, 153, 0.10)', fg: '#ffffff' },
165
+ red: { accent: '#ef4444', hover: '#dc2626', light: 'rgba(239, 68, 68, 0.10)', fg: '#ffffff' },
166
+ cyan: { accent: '#06b6d4', hover: '#0891b2', light: 'rgba(6, 182, 212, 0.10)', fg: '#ffffff' },
167
+ yellow: { accent: '#eab308', hover: '#ca8a04', light: 'rgba(234, 179, 8, 0.10)', fg: '#ffffff' },
168
+ white: { accent: '#ffffff', hover: '#e5e5e5', light: 'rgba(255, 255, 255, 0.10)', fg: '#1a1a1a' },
168
169
  }
169
170
  const selected = map[stored] || map.green
170
171
  const root = document.documentElement
171
172
  root.style.setProperty('--opencami-accent', selected.accent)
172
173
  root.style.setProperty('--opencami-accent-hover', selected.hover)
173
174
  root.style.setProperty('--opencami-accent-light', selected.light)
175
+ root.style.setProperty('--opencami-accent-fg', selected.fg)
174
176
  } catch {}
175
177
  })()
176
178
  `;
@@ -329,7 +331,7 @@ function RootDocument({ children }) {
329
331
  ] })
330
332
  ] });
331
333
  }
332
- const $$splitComponentImporter$9 = () => import("./skills-Cy8xclXY.js");
334
+ const $$splitComponentImporter$9 = () => import("./skills-BXUivxuo.js");
333
335
  const Route$A = createFileRoute("/skills")({
334
336
  component: lazyRouteComponent($$splitComponentImporter$9, "component")
335
337
  });
@@ -346,11 +348,11 @@ const Route$z = createFileRoute("/new")({
346
348
  },
347
349
  component: lazyRouteComponent($$splitComponentImporter$8, "component")
348
350
  });
349
- const $$splitComponentImporter$7 = () => import("./memory-Cxu7i8ej.js");
351
+ const $$splitComponentImporter$7 = () => import("./memory-rBB015W-.js");
350
352
  const Route$y = createFileRoute("/memory")({
351
353
  component: lazyRouteComponent($$splitComponentImporter$7, "component")
352
354
  });
353
- const $$splitComponentImporter$6 = () => import("./files-DYdXlQDr.js");
355
+ const $$splitComponentImporter$6 = () => import("./files-DLxqp-h5.js");
354
356
  const Route$x = createFileRoute("/files")({
355
357
  component: lazyRouteComponent($$splitComponentImporter$6, "component")
356
358
  });
@@ -358,23 +360,23 @@ const $$splitComponentImporter$5 = () => import("./dashboard-UYRCu_mQ.js");
358
360
  const Route$w = createFileRoute("/dashboard")({
359
361
  component: lazyRouteComponent($$splitComponentImporter$5, "component")
360
362
  });
361
- const $$splitComponentImporter$4 = () => import("./connect-BNabuqpW.js");
363
+ const $$splitComponentImporter$4 = () => import("./connect-CTVBm0Vc.js");
362
364
  const Route$v = createFileRoute("/connect")({
363
365
  component: lazyRouteComponent($$splitComponentImporter$4, "component")
364
366
  });
365
- const $$splitComponentImporter$3 = () => import("./bots-Byt6jv0a.js");
367
+ const $$splitComponentImporter$3 = () => import("./bots-BDHeSvSQ.js");
366
368
  const Route$u = createFileRoute("/bots")({
367
369
  component: lazyRouteComponent($$splitComponentImporter$3, "component")
368
370
  });
369
- const $$splitComponentImporter$2 = () => import("./agents-CmQ4vvXm.js");
371
+ const $$splitComponentImporter$2 = () => import("./agents-BuE0Yum3.js");
370
372
  const Route$t = createFileRoute("/agents")({
371
373
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
372
374
  });
373
- const $$splitComponentImporter$1 = () => import("./index-COElhwGA.js");
375
+ const $$splitComponentImporter$1 = () => import("./index-C1dylvu-.js");
374
376
  const Route$s = createFileRoute("/")({
375
377
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
376
378
  });
377
- const $$splitComponentImporter = () => import("./_sessionKey-B6iYeyCS.js").then((n) => n.$);
379
+ const $$splitComponentImporter = () => import("./_sessionKey-DBzlGMo3.js").then((n) => n.$);
378
380
  const Route$r = createFileRoute("/chat/$sessionKey")({
379
381
  component: lazyRouteComponent($$splitComponentImporter, "component")
380
382
  });
@@ -558,7 +560,7 @@ function buildConnectParams(url, token, password, nonce) {
558
560
  mode: clientMode,
559
561
  instanceId: loadOrCreateInstanceId()
560
562
  },
561
- caps: [],
563
+ caps: ["tool-events"],
562
564
  auth: {
563
565
  token: token || void 0,
564
566
  password: password || void 0,
@@ -729,7 +731,7 @@ class PersistentGatewayConnection {
729
731
  mode: "webchat",
730
732
  instanceId: loadOrCreateInstanceId()
731
733
  },
732
- caps: [],
734
+ caps: ["tool-events"],
733
735
  auth: {
734
736
  token: token || void 0,
735
737
  password: password || void 0
@@ -921,14 +923,240 @@ function getPersistentConnection() {
921
923
  }
922
924
  return _proc.__opencamiGatewayInstance;
923
925
  }
926
+ const sharedGatewayClients = /* @__PURE__ */ new Map();
927
+ function createGatewayClient() {
928
+ const pendingRpcs = /* @__PURE__ */ new Map();
929
+ const callbacks = /* @__PURE__ */ new Set();
930
+ let ws = null;
931
+ let closed = false;
932
+ let connected = false;
933
+ let connectPromise = null;
934
+ function rejectAll(err) {
935
+ for (const [, pending] of pendingRpcs) {
936
+ clearTimeout(pending.timer);
937
+ pending.reject(err);
938
+ }
939
+ pendingRpcs.clear();
940
+ }
941
+ function waitForRes(id, timeoutMs = 3e4) {
942
+ return new Promise((resolve2, reject) => {
943
+ const timer = setTimeout(() => {
944
+ pendingRpcs.delete(id);
945
+ reject(new Error(`RPC timeout waiting for ${id}`));
946
+ }, timeoutMs);
947
+ pendingRpcs.set(id, { resolve: resolve2, reject, timer });
948
+ });
949
+ }
950
+ function emitError(error) {
951
+ for (const callback of callbacks) {
952
+ try {
953
+ callback.onError?.(error);
954
+ } catch {
955
+ }
956
+ }
957
+ }
958
+ function emitEvent(event) {
959
+ for (const callback of callbacks) {
960
+ try {
961
+ callback.onEvent?.(event);
962
+ } catch {
963
+ }
964
+ }
965
+ }
966
+ function handleMessage(data) {
967
+ try {
968
+ const str = typeof data === "string" ? data : data.toString();
969
+ const parsed = JSON.parse(str);
970
+ if (parsed.type === "res") {
971
+ const pending = pendingRpcs.get(parsed.id);
972
+ if (!pending) return;
973
+ pendingRpcs.delete(parsed.id);
974
+ clearTimeout(pending.timer);
975
+ if (parsed.ok) pending.resolve(parsed.payload);
976
+ else pending.reject(new GatewayResponseError(parsed.error?.message ?? "gateway error", parsed.error?.code));
977
+ return;
978
+ }
979
+ if (parsed.type === "event") {
980
+ emitEvent({
981
+ event: parsed.event,
982
+ payload: parsed.payload ?? {},
983
+ seq: parsed.seq
984
+ });
985
+ }
986
+ } catch {
987
+ }
988
+ }
989
+ function handleClose() {
990
+ const wasConnected = connected;
991
+ connected = false;
992
+ ws = null;
993
+ rejectAll(new Error("Connection closed"));
994
+ if (!closed && wasConnected) {
995
+ emitError(new Error("Gateway client connection closed"));
996
+ }
997
+ }
998
+ async function connect() {
999
+ if (closed) throw new Error("Gateway client closed");
1000
+ if (connected && ws?.readyState === WebSocket.OPEN) return;
1001
+ if (connectPromise) return connectPromise;
1002
+ connectPromise = (async () => {
1003
+ const { url, token, password } = getGatewayConfig();
1004
+ const origin = process.env.OPENCAMI_ORIGIN?.trim();
1005
+ const nextWs = origin ? new WebSocket(url, { headers: { Origin: origin } }) : new WebSocket(url);
1006
+ ws = nextWs;
1007
+ await new Promise((resolve2, reject) => {
1008
+ const onOpen = () => {
1009
+ cleanup();
1010
+ resolve2();
1011
+ };
1012
+ const onError = (err) => {
1013
+ cleanup();
1014
+ reject(new Error(`WS open error: ${err.message}`));
1015
+ };
1016
+ const cleanup = () => {
1017
+ nextWs.off("open", onOpen);
1018
+ nextWs.off("error", onError);
1019
+ };
1020
+ nextWs.on("open", onOpen);
1021
+ nextWs.on("error", onError);
1022
+ });
1023
+ const nonce = await new Promise((resolve2) => {
1024
+ let done = false;
1025
+ const timer = setTimeout(() => {
1026
+ if (done) return;
1027
+ done = true;
1028
+ resolve2("");
1029
+ }, 3e3);
1030
+ const onMessage = (data) => {
1031
+ try {
1032
+ const str = typeof data === "string" ? data : data.toString();
1033
+ const parsed = JSON.parse(str);
1034
+ if (parsed.type === "event" && parsed.event === "connect.challenge") {
1035
+ const n = parsed.payload?.nonce;
1036
+ if (typeof n === "string" && n.length > 0) {
1037
+ clearTimeout(timer);
1038
+ nextWs.off("message", onMessage);
1039
+ if (done) return;
1040
+ done = true;
1041
+ resolve2(n);
1042
+ }
1043
+ }
1044
+ } catch {
1045
+ }
1046
+ };
1047
+ nextWs.on("message", onMessage);
1048
+ });
1049
+ nextWs.on("message", handleMessage);
1050
+ nextWs.on("close", handleClose);
1051
+ nextWs.on("error", (err) => {
1052
+ emitError(new Error(`Gateway client error: ${err.message}`));
1053
+ });
1054
+ const connectId = randomUUID();
1055
+ const connectParams = buildConnectParams(url, token, password, nonce);
1056
+ nextWs.send(JSON.stringify({
1057
+ type: "req",
1058
+ id: connectId,
1059
+ method: "connect",
1060
+ params: connectParams
1061
+ }));
1062
+ const hello = await waitForRes(connectId, 1e4);
1063
+ if (hello?.auth?.deviceToken) {
1064
+ const identity = loadOrCreateDeviceIdentity();
1065
+ storeDeviceToken(identity.deviceId, url, hello.auth.deviceToken);
1066
+ }
1067
+ connected = true;
1068
+ })();
1069
+ try {
1070
+ await connectPromise;
1071
+ } finally {
1072
+ connectPromise = null;
1073
+ }
1074
+ }
1075
+ async function sendReq(method, params) {
1076
+ await connect();
1077
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
1078
+ throw new Error("Gateway client not connected");
1079
+ }
1080
+ const id = randomUUID();
1081
+ ws.send(JSON.stringify({ type: "req", id, method, params }));
1082
+ return await waitForRes(id);
1083
+ }
1084
+ function close() {
1085
+ if (closed) return;
1086
+ closed = true;
1087
+ connected = false;
1088
+ rejectAll(new Error("Gateway client closed"));
1089
+ const currentWs = ws;
1090
+ ws = null;
1091
+ if (currentWs) {
1092
+ currentWs.off("message", handleMessage);
1093
+ currentWs.off("close", handleClose);
1094
+ currentWs.close();
1095
+ }
1096
+ callbacks.clear();
1097
+ }
1098
+ function isClosed() {
1099
+ return closed;
1100
+ }
1101
+ function addCallbacks(callbacksToAdd) {
1102
+ if (!callbacksToAdd?.onEvent && !callbacksToAdd?.onError) {
1103
+ return () => {
1104
+ };
1105
+ }
1106
+ callbacks.add(callbacksToAdd);
1107
+ return () => {
1108
+ callbacks.delete(callbacksToAdd);
1109
+ };
1110
+ }
1111
+ return { connect, sendReq, close, isClosed, addCallbacks };
1112
+ }
1113
+ function releaseGatewayClient(key) {
1114
+ const entry = sharedGatewayClients.get(key);
1115
+ if (!entry) return;
1116
+ entry.refs -= 1;
1117
+ if (entry.refs > 0) return;
1118
+ entry.client.close();
1119
+ sharedGatewayClients.delete(key);
1120
+ }
1121
+ async function acquireGatewayClient(key, callbacks) {
1122
+ const existing = sharedGatewayClients.get(key);
1123
+ if (existing && !existing.client.isClosed()) {
1124
+ existing.refs += 1;
1125
+ const removeCallbacks2 = existing.client.addCallbacks(callbacks);
1126
+ return {
1127
+ client: existing.client,
1128
+ release: () => {
1129
+ removeCallbacks2();
1130
+ releaseGatewayClient(key);
1131
+ }
1132
+ };
1133
+ }
1134
+ const client = createGatewayClient();
1135
+ const removeCallbacks = client.addCallbacks(callbacks);
1136
+ await client.connect();
1137
+ sharedGatewayClients.set(key, { refs: 1, client });
1138
+ return {
1139
+ client,
1140
+ release: () => {
1141
+ removeCallbacks();
1142
+ releaseGatewayClient(key);
1143
+ }
1144
+ };
1145
+ }
1146
+ async function gatewayRpcShared(method, params, key) {
1147
+ if (!key) {
1148
+ return gatewayRpc(method, params);
1149
+ }
1150
+ const existing = sharedGatewayClients.get(key);
1151
+ if (!existing || existing.client.isClosed()) {
1152
+ return gatewayRpc(method, params);
1153
+ }
1154
+ return existing.client.sendReq(method, params);
1155
+ }
924
1156
  async function gatewayRpc(method, params) {
925
1157
  const conn = getPersistentConnection();
926
1158
  return conn.rpc(method, params);
927
1159
  }
928
- function subscribeGatewayEvents(sessionKey, listener) {
929
- const conn = getPersistentConnection();
930
- return conn.subscribe(sessionKey, listener);
931
- }
932
1160
  function getDeviceStatus() {
933
1161
  const conn = getPersistentConnection();
934
1162
  return conn.getDeviceStatus();
@@ -1286,91 +1514,83 @@ const Route$o = createFileRoute("/api/stream")({
1286
1514
  handlers: {
1287
1515
  GET: async ({ request }) => {
1288
1516
  const url = new URL(request.url);
1289
- const sessionKey = url.searchParams.get("sessionKey");
1290
- if (!sessionKey) {
1517
+ const sessionKey = url.searchParams.get("sessionKey")?.trim() || "";
1518
+ const friendlyId = url.searchParams.get("friendlyId")?.trim() || "";
1519
+ const key = sessionKey || friendlyId;
1520
+ if (!key) {
1291
1521
  return new Response(
1292
- JSON.stringify({ ok: false, error: "sessionKey required" }),
1522
+ JSON.stringify({ ok: false, error: "sessionKey or friendlyId required" }),
1293
1523
  { status: 400, headers: { "content-type": "application/json" } }
1294
1524
  );
1295
1525
  }
1296
1526
  const pass = new PassThrough();
1297
1527
  const encoder = new TextEncoder();
1298
1528
  let closed = false;
1299
- let unsubscribe = null;
1300
- function sendSSE(event, data) {
1529
+ let heartbeat = null;
1530
+ let releaseClient = null;
1531
+ function writeChunk(chunk) {
1301
1532
  if (closed) return;
1302
1533
  try {
1303
- pass.write(encoder.encode(`event: ${event}
1304
- data: ${JSON.stringify(data)}
1305
-
1306
- `));
1534
+ pass.write(encoder.encode(chunk));
1307
1535
  } catch {
1536
+ cleanup();
1308
1537
  }
1309
1538
  }
1539
+ function send(data) {
1540
+ writeChunk(`data: ${JSON.stringify(data)}
1541
+
1542
+ `);
1543
+ }
1310
1544
  function cleanup() {
1311
1545
  if (closed) return;
1312
1546
  closed = true;
1313
- if (unsubscribe) {
1314
- unsubscribe();
1315
- unsubscribe = null;
1547
+ if (heartbeat) {
1548
+ clearInterval(heartbeat);
1549
+ heartbeat = null;
1550
+ }
1551
+ if (releaseClient) {
1552
+ releaseClient();
1553
+ releaseClient = null;
1316
1554
  }
1317
1555
  try {
1318
1556
  pass.end();
1319
1557
  } catch {
1320
1558
  }
1321
1559
  }
1322
- pass.write(encoder.encode(": connected\n\n"));
1323
- let gotAgentStream = false;
1324
- unsubscribe = subscribeGatewayEvents(sessionKey, (evt) => {
1325
- if (evt.event === "agent") {
1326
- const payload = evt.payload;
1327
- const agentStream = payload.stream;
1328
- if (agentStream === "assistant") {
1329
- gotAgentStream = true;
1330
- const data = payload.data ?? payload;
1331
- const text = typeof data.delta === "string" ? data.delta : typeof data.text === "string" ? data.text : typeof payload.text === "string" ? payload.text : typeof payload.delta === "string" ? payload.delta : "";
1332
- if (text) {
1333
- sendSSE("delta", { text, sessionKey });
1560
+ writeChunk(": connected\n\n");
1561
+ heartbeat = setInterval(() => {
1562
+ writeChunk("event: ping\ndata: {}\n\n");
1563
+ }, 15e3);
1564
+ try {
1565
+ const handle = await acquireGatewayClient(key, {
1566
+ onEvent(event) {
1567
+ const p = event.payload;
1568
+ const eventSessionKey = typeof p?.sessionKey === "string" ? p.sessionKey : "";
1569
+ if (eventSessionKey && !eventSessionKey.includes(key)) {
1570
+ return;
1334
1571
  }
1335
- } else if (agentStream === "tool") {
1336
- gotAgentStream = true;
1337
- const tdata = payload.data ?? payload;
1338
- sendSSE("tool", {
1339
- name: tdata.name ?? tdata.toolName ?? payload.name ?? "",
1340
- status: tdata.phase ?? tdata.status ?? payload.phase ?? "running",
1341
- id: tdata.id ?? tdata.toolCallId ?? payload.id ?? "",
1342
- sessionKey
1572
+ send({
1573
+ event: event.event,
1574
+ payload: event.payload,
1575
+ seq: event.seq,
1576
+ stateVersion: event.stateVersion
1343
1577
  });
1344
- } else if (agentStream === "lifecycle") {
1345
- const ldata = payload.data ?? payload;
1346
- const phase = ldata.phase ?? payload.phase;
1347
- if (phase === "end" || phase === "error") {
1348
- sendSSE("done", {
1349
- sessionKey,
1350
- status: phase,
1351
- error: phase === "error" ? payload.error : void 0
1352
- });
1353
- cleanup();
1354
- }
1355
- }
1356
- } else if (evt.event === "chat") {
1357
- const payload = evt.payload;
1358
- const state = payload.state ?? payload.kind;
1359
- const msg = payload.message;
1360
- if (state === "delta" && !gotAgentStream) {
1361
- const content = Array.isArray(msg?.content) ? msg.content : [];
1362
- const firstBlock = content[0];
1363
- const text = typeof firstBlock?.text === "string" ? firstBlock.text : typeof payload.text === "string" ? payload.text : typeof payload.delta === "string" ? payload.delta : "";
1364
- if (text) {
1365
- sendSSE("delta", { text, sessionKey });
1366
- }
1367
- } else if (state === "final") {
1368
- sendSSE("done", { sessionKey, status: "end" });
1369
- cleanup();
1578
+ },
1579
+ onError(error) {
1580
+ send({ event: "error", payload: error.message });
1370
1581
  }
1582
+ });
1583
+ if (closed) {
1584
+ handle.release();
1585
+ } else {
1586
+ releaseClient = handle.release;
1371
1587
  }
1372
- });
1373
- request.signal?.addEventListener("abort", cleanup);
1588
+ } catch (error) {
1589
+ const message = error instanceof Error ? error.message : String(error);
1590
+ send({ event: "error", payload: message });
1591
+ cleanup();
1592
+ }
1593
+ request.signal.addEventListener("abort", cleanup, { once: true });
1374
1594
  const webStream = Readable.toWeb(pass);
1375
1595
  return new Response(webStream, {
1376
1596
  headers: {
@@ -1772,7 +1992,7 @@ const Route$l = createFileRoute("/api/send")({
1772
1992
  if (sessionKey.length === 0) {
1773
1993
  sessionKey = "main";
1774
1994
  }
1775
- const res = await gatewayRpc("chat.send", {
1995
+ const res = await gatewayRpcShared("chat.send", {
1776
1996
  sessionKey,
1777
1997
  message,
1778
1998
  thinking,
@@ -1781,7 +2001,7 @@ const Route$l = createFileRoute("/api/send")({
1781
2001
  deliver: false,
1782
2002
  timeoutMs: 12e4,
1783
2003
  idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : randomUUID()
1784
- });
2004
+ }, sessionKey);
1785
2005
  return json({ ok: true, ...res, sessionKey });
1786
2006
  } catch (err) {
1787
2007
  return json(
@@ -3,30 +3,30 @@ import { useState, useRef, useEffect, useCallback, useMemo } from "react";
3
3
  import { useNavigate } from "@tanstack/react-router";
4
4
  import { HugeiconsIcon } from "@hugeicons/react";
5
5
  import { Search01Icon, Cancel01Icon, Loading03Icon } from "@hugeicons/core-free-icons";
6
- import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-s7CS50ho.js";
6
+ import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-E6cUvMva.js";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
- import { c as chatQueryKeys } from "./_sessionKey-B6iYeyCS.js";
9
- import { c as cn } from "./button-CwY2OHFj.js";
8
+ import { c as chatQueryKeys } from "./_sessionKey-DBzlGMo3.js";
9
+ import { c as cn } from "./button-kI8fEIZQ.js";
10
10
  import "@base-ui/react/dialog";
11
11
  import "zustand";
12
- import "./tooltip-DgsSPocE.js";
12
+ import "./tooltip-DOvOrSSS.js";
13
13
  import "@base-ui/react/tooltip";
14
14
  import "motion/react";
15
15
  import "@base-ui/react/alert-dialog";
16
16
  import "@base-ui/react/collapsible";
17
17
  import "@base-ui/react/scroll-area";
18
- import "./menu-D90CDTi2.js";
18
+ import "./menu-D8cKTpmN.js";
19
19
  import "@base-ui/react/menu";
20
20
  import "./opencami-logo-C-43FL3R.js";
21
- import "./markdown-DoX5Q7qh.js";
21
+ import "./markdown-CFdYXCRQ.js";
22
22
  import "marked";
23
23
  import "react-markdown";
24
24
  import "remark-breaks";
25
25
  import "remark-gfm";
26
- import "./index-BEWnDAH6.js";
26
+ import "./index-B_F4DTUu.js";
27
27
  import "zustand/middleware";
28
28
  import "react-dom";
29
- import "./router-BqLGFd4L.js";
29
+ import "./router-CL6A11qi.js";
30
30
  import "node:crypto";
31
31
  import "node:fs";
32
32
  import "node:os";
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { HugeiconsIcon } from "@hugeicons/react";
4
4
  import { Search01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons";
5
- import { c as cn } from "./button-CwY2OHFj.js";
5
+ import { c as cn } from "./button-kI8fEIZQ.js";
6
6
  import "@base-ui/react/merge-props";
7
7
  import "@base-ui/react/use-render";
8
8
  import "class-variance-authority";
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
4
- import { B as Button } from "./button-CwY2OHFj.js";
3
+ import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-E6cUvMva.js";
4
+ import { B as Button } from "./button-kI8fEIZQ.js";
5
5
  import "@base-ui/react/dialog";
6
6
  import "zustand";
7
7
  import "@base-ui/react/merge-props";