@particle-academy/agent-integrations 0.28.2 → 0.30.0

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 (213) hide show
  1. package/README.md +38 -0
  2. package/dist/bridges/artboard.d.cts +3 -3
  3. package/dist/bridges/artboard.d.ts +3 -3
  4. package/dist/bridges/catalog.d.cts +3 -3
  5. package/dist/bridges/catalog.d.ts +3 -3
  6. package/dist/bridges/charts.d.cts +3 -3
  7. package/dist/bridges/charts.d.ts +3 -3
  8. package/dist/bridges/cms.d.cts +3 -3
  9. package/dist/bridges/cms.d.ts +3 -3
  10. package/dist/bridges/code.d.cts +3 -3
  11. package/dist/bridges/code.d.ts +3 -3
  12. package/dist/bridges/doc.d.cts +3 -3
  13. package/dist/bridges/doc.d.ts +3 -3
  14. package/dist/bridges/features.d.cts +3 -3
  15. package/dist/bridges/features.d.ts +3 -3
  16. package/dist/bridges/files.d.cts +23 -4
  17. package/dist/bridges/files.d.ts +23 -4
  18. package/dist/bridges/flow.d.cts +3 -3
  19. package/dist/bridges/flow.d.ts +3 -3
  20. package/dist/bridges/forms.d.cts +25 -4
  21. package/dist/bridges/forms.d.ts +25 -4
  22. package/dist/bridges/map.d.cts +3 -3
  23. package/dist/bridges/map.d.ts +3 -3
  24. package/dist/bridges/scene.d.cts +3 -3
  25. package/dist/bridges/scene.d.ts +3 -3
  26. package/dist/bridges/screens.d.cts +3 -3
  27. package/dist/bridges/screens.d.ts +3 -3
  28. package/dist/bridges/sheets.d.cts +3 -3
  29. package/dist/bridges/sheets.d.ts +3 -3
  30. package/dist/bridges/slides.d.cts +3 -3
  31. package/dist/bridges/slides.d.ts +3 -3
  32. package/dist/bridges/terminal.d.cts +24 -4
  33. package/dist/bridges/terminal.d.ts +24 -4
  34. package/dist/bridges/tui.d.cts +45 -0
  35. package/dist/bridges/tui.d.ts +45 -0
  36. package/dist/bridges/whiteboard.d.cts +3 -3
  37. package/dist/bridges/whiteboard.d.ts +3 -3
  38. package/dist/bridges-artboard.cjs +1 -1
  39. package/dist/bridges-artboard.cjs.map +1 -1
  40. package/dist/bridges-artboard.js +3 -3
  41. package/dist/bridges-catalog.cjs +1 -1
  42. package/dist/bridges-catalog.cjs.map +1 -1
  43. package/dist/bridges-catalog.js +4 -4
  44. package/dist/bridges-charts.cjs +1 -1
  45. package/dist/bridges-charts.cjs.map +1 -1
  46. package/dist/bridges-charts.js +4 -4
  47. package/dist/bridges-cms.cjs +1 -1
  48. package/dist/bridges-cms.cjs.map +1 -1
  49. package/dist/bridges-cms.js +5 -5
  50. package/dist/bridges-code.cjs +38 -3
  51. package/dist/bridges-code.cjs.map +1 -1
  52. package/dist/bridges-code.js +4 -4
  53. package/dist/bridges-doc.cjs +1 -1
  54. package/dist/bridges-doc.cjs.map +1 -1
  55. package/dist/bridges-doc.js +4 -4
  56. package/dist/bridges-features.cjs +1 -1
  57. package/dist/bridges-features.cjs.map +1 -1
  58. package/dist/bridges-features.js +4 -4
  59. package/dist/bridges-files.cjs +29 -8
  60. package/dist/bridges-files.cjs.map +1 -1
  61. package/dist/bridges-files.js +4 -4
  62. package/dist/bridges-flow.cjs +1 -1
  63. package/dist/bridges-flow.cjs.map +1 -1
  64. package/dist/bridges-flow.js +3 -3
  65. package/dist/bridges-forms.cjs +12 -2
  66. package/dist/bridges-forms.cjs.map +1 -1
  67. package/dist/bridges-forms.js +4 -4
  68. package/dist/bridges-map.cjs +1 -1
  69. package/dist/bridges-map.cjs.map +1 -1
  70. package/dist/bridges-map.js +4 -4
  71. package/dist/bridges-scene.cjs +1 -1
  72. package/dist/bridges-scene.cjs.map +1 -1
  73. package/dist/bridges-scene.js +4 -4
  74. package/dist/bridges-screens.cjs +1 -1
  75. package/dist/bridges-screens.cjs.map +1 -1
  76. package/dist/bridges-screens.js +4 -4
  77. package/dist/bridges-sheets.cjs +1 -1
  78. package/dist/bridges-sheets.cjs.map +1 -1
  79. package/dist/bridges-sheets.js +4 -4
  80. package/dist/bridges-slides.cjs +1 -1
  81. package/dist/bridges-slides.cjs.map +1 -1
  82. package/dist/bridges-slides.js +4 -4
  83. package/dist/bridges-terminal.cjs +70 -45
  84. package/dist/bridges-terminal.cjs.map +1 -1
  85. package/dist/bridges-terminal.js +4 -4
  86. package/dist/bridges-tui.cjs +114 -0
  87. package/dist/bridges-tui.cjs.map +1 -0
  88. package/dist/bridges-tui.js +7 -0
  89. package/dist/bridges-tui.js.map +1 -0
  90. package/dist/bridges-whiteboard.cjs +1 -1
  91. package/dist/bridges-whiteboard.cjs.map +1 -1
  92. package/dist/bridges-whiteboard.js +4 -4
  93. package/dist/{chunk-AAMQOXZC.js → chunk-2U5JB6ZA.js} +5 -5
  94. package/dist/{chunk-AAMQOXZC.js.map → chunk-2U5JB6ZA.js.map} +1 -1
  95. package/dist/{chunk-ULJL53DL.js → chunk-2VENWGLZ.js} +3 -3
  96. package/dist/chunk-2VENWGLZ.js.map +1 -0
  97. package/dist/{chunk-66WOHZZJ.js → chunk-6YUBWQTS.js} +5 -5
  98. package/dist/{chunk-66WOHZZJ.js.map → chunk-6YUBWQTS.js.map} +1 -1
  99. package/dist/{chunk-ZX6UQURN.js → chunk-BHQJJ2YG.js} +5 -5
  100. package/dist/{chunk-ZX6UQURN.js.map → chunk-BHQJJ2YG.js.map} +1 -1
  101. package/dist/{chunk-GHY3PBPN.js → chunk-BT7HM5I6.js} +3 -3
  102. package/dist/{chunk-GHY3PBPN.js.map → chunk-BT7HM5I6.js.map} +1 -1
  103. package/dist/{chunk-YGD2VTUM.js → chunk-CA7GIJJC.js} +5 -5
  104. package/dist/{chunk-YGD2VTUM.js.map → chunk-CA7GIJJC.js.map} +1 -1
  105. package/dist/{chunk-KJ5AOOV7.js → chunk-CT3LRNH7.js} +3 -3
  106. package/dist/{chunk-KJ5AOOV7.js.map → chunk-CT3LRNH7.js.map} +1 -1
  107. package/dist/{chunk-4KAIV6OD.js → chunk-DF3PLL2E.js} +10 -5
  108. package/dist/chunk-DF3PLL2E.js.map +1 -0
  109. package/dist/{chunk-A57CRGEK.js → chunk-DKSR4R4O.js} +3 -3
  110. package/dist/{chunk-A57CRGEK.js.map → chunk-DKSR4R4O.js.map} +1 -1
  111. package/dist/{chunk-AROVENCE.js → chunk-G5EZNWXJ.js} +5 -5
  112. package/dist/{chunk-AROVENCE.js.map → chunk-G5EZNWXJ.js.map} +1 -1
  113. package/dist/{chunk-OIX2ANFS.js → chunk-GRHPEK6M.js} +45 -9
  114. package/dist/chunk-GRHPEK6M.js.map +1 -0
  115. package/dist/{chunk-XMMCSQ4I.js → chunk-HRHC5GM5.js} +33 -13
  116. package/dist/chunk-HRHC5GM5.js.map +1 -0
  117. package/dist/{chunk-5KLUYF4D.js → chunk-HSI5RVF4.js} +3 -3
  118. package/dist/{chunk-5KLUYF4D.js.map → chunk-HSI5RVF4.js.map} +1 -1
  119. package/dist/{chunk-FF5F526J.js → chunk-LQZKRBWR.js} +5 -5
  120. package/dist/{chunk-FF5F526J.js.map → chunk-LQZKRBWR.js.map} +1 -1
  121. package/dist/chunk-MFMBR6LN.js +100 -0
  122. package/dist/chunk-MFMBR6LN.js.map +1 -0
  123. package/dist/{chunk-KMSRG5KH.js → chunk-N6RLSLOF.js} +7 -7
  124. package/dist/chunk-N6RLSLOF.js.map +1 -0
  125. package/dist/{chunk-VFKO2VUR.js → chunk-OAFJ53X3.js} +5 -5
  126. package/dist/{chunk-VFKO2VUR.js.map → chunk-OAFJ53X3.js.map} +1 -1
  127. package/dist/chunk-PMI4C5S4.js +44 -0
  128. package/dist/chunk-PMI4C5S4.js.map +1 -0
  129. package/dist/{chunk-QDD4WHIV.js → chunk-PR4SXFZH.js} +5 -5
  130. package/dist/{chunk-QDD4WHIV.js.map → chunk-PR4SXFZH.js.map} +1 -1
  131. package/dist/{chunk-NN3ELTVO.js → chunk-RIXYCFDO.js} +42 -7
  132. package/dist/chunk-RIXYCFDO.js.map +1 -0
  133. package/dist/{chunk-PCW2EK5G.js → chunk-USQGA672.js} +16 -6
  134. package/dist/chunk-USQGA672.js.map +1 -0
  135. package/dist/{chunk-HWVVS6HE.js → chunk-UZHNKUER.js} +74 -49
  136. package/dist/chunk-UZHNKUER.js.map +1 -0
  137. package/dist/{chunk-QRDVJW6Q.js → chunk-WCKSDFTM.js} +5 -5
  138. package/dist/{chunk-QRDVJW6Q.js.map → chunk-WCKSDFTM.js.map} +1 -1
  139. package/dist/{chunk-AAD3Z5E7.js → chunk-ZNGTC4CO.js} +5 -5
  140. package/dist/{chunk-AAD3Z5E7.js.map → chunk-ZNGTC4CO.js.map} +1 -1
  141. package/dist/components/SharedWhiteboard/index.d.cts +1 -1
  142. package/dist/components/SharedWhiteboard/index.d.ts +1 -1
  143. package/dist/components-shared-whiteboard.cjs +10 -6
  144. package/dist/components-shared-whiteboard.cjs.map +1 -1
  145. package/dist/components-shared-whiteboard.js +6 -6
  146. package/dist/heuristics.js +1 -1
  147. package/dist/human-plus/file-store.d.cts +17 -0
  148. package/dist/human-plus/file-store.d.ts +17 -0
  149. package/dist/human-plus/index.d.cts +61 -0
  150. package/dist/human-plus/index.d.ts +61 -0
  151. package/dist/human-plus-node.cjs +121 -0
  152. package/dist/human-plus-node.cjs.map +1 -0
  153. package/dist/human-plus-node.js +76 -0
  154. package/dist/human-plus-node.js.map +1 -0
  155. package/dist/human-plus.cjs +46 -0
  156. package/dist/human-plus.cjs.map +1 -0
  157. package/dist/human-plus.js +3 -0
  158. package/dist/human-plus.js.map +1 -0
  159. package/dist/index.cjs +337 -99
  160. package/dist/index.cjs.map +1 -1
  161. package/dist/index.d.cts +11 -9
  162. package/dist/index.d.ts +11 -9
  163. package/dist/index.js +35 -27
  164. package/dist/index.js.map +1 -1
  165. package/dist/mcp/index.d.cts +5 -5
  166. package/dist/mcp/index.d.ts +5 -5
  167. package/dist/mcp.cjs +8 -3
  168. package/dist/mcp.cjs.map +1 -1
  169. package/dist/mcp.js +1 -1
  170. package/dist/presence/index.d.cts +8 -1
  171. package/dist/presence/index.d.ts +8 -1
  172. package/dist/presence.cjs +1 -1
  173. package/dist/presence.cjs.map +1 -1
  174. package/dist/presence.js +1 -1
  175. package/dist/relay-server/index.d.cts +20 -0
  176. package/dist/relay-server/index.d.ts +20 -0
  177. package/dist/relay-server-cli.cjs +53 -9
  178. package/dist/relay-server-cli.cjs.map +1 -1
  179. package/dist/relay-server-cli.js +11 -3
  180. package/dist/relay-server-cli.js.map +1 -1
  181. package/dist/relay-server.cjs +43 -7
  182. package/dist/relay-server.cjs.map +1 -1
  183. package/dist/relay-server.js +1 -1
  184. package/dist/{server-FFZpYPyM.d.ts → server-CDX643Kz.d.ts} +4 -2
  185. package/dist/{server-Dr3j48CU.d.cts → server-DDD3IZ56.d.cts} +4 -2
  186. package/dist/sharing/index.d.cts +4 -4
  187. package/dist/sharing/index.d.ts +4 -4
  188. package/dist/sharing.cjs +5 -5
  189. package/dist/sharing.cjs.map +1 -1
  190. package/dist/sharing.js +1 -1
  191. package/dist/sheets-adapter.d.cts +3 -3
  192. package/dist/sheets-adapter.d.ts +3 -3
  193. package/dist/{token-CrJF76oH.d.cts → token-BZ8doMam.d.cts} +15 -2
  194. package/dist/{token-CrJF76oH.d.ts → token-BZ8doMam.d.ts} +15 -2
  195. package/dist/{tool-host-C_Vh17J_.d.ts → tool-host-CWTOJEm2.d.ts} +1 -1
  196. package/dist/{tool-host-IDecPrF1.d.cts → tool-host-EuYHYSSH.d.cts} +1 -1
  197. package/dist/{types-CBoQhR8L.d.ts → types-BKD6uf86.d.ts} +1 -1
  198. package/dist/{types-BDI_g5UG.d.ts → types-C58ZXs4K.d.cts} +6 -2
  199. package/dist/{types-BDI_g5UG.d.cts → types-C58ZXs4K.d.ts} +6 -2
  200. package/dist/{types-B0rmWyoR.d.cts → types-NFGP769G.d.cts} +1 -1
  201. package/dist/undo/index.d.cts +2 -2
  202. package/dist/undo/index.d.ts +2 -2
  203. package/dist/undo.cjs.map +1 -1
  204. package/dist/undo.js +3 -3
  205. package/package.json +13 -1
  206. package/dist/chunk-4KAIV6OD.js.map +0 -1
  207. package/dist/chunk-HWVVS6HE.js.map +0 -1
  208. package/dist/chunk-KMSRG5KH.js.map +0 -1
  209. package/dist/chunk-NN3ELTVO.js.map +0 -1
  210. package/dist/chunk-OIX2ANFS.js.map +0 -1
  211. package/dist/chunk-PCW2EK5G.js.map +0 -1
  212. package/dist/chunk-ULJL53DL.js.map +0 -1
  213. package/dist/chunk-XMMCSQ4I.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -112,7 +112,7 @@ var MicroMcpServer = class extends ToolRegistry {
112
112
  }
113
113
  const request = message;
114
114
  try {
115
- const result = await this.handle(request);
115
+ const result = await this.handle(request, transport);
116
116
  transport.send({ jsonrpc: "2.0", id: request.id, result });
117
117
  } catch (err) {
118
118
  transport.send({
@@ -122,7 +122,7 @@ var MicroMcpServer = class extends ToolRegistry {
122
122
  });
123
123
  }
124
124
  }
125
- async handle(request) {
125
+ async handle(request, transport) {
126
126
  const { method, params } = request;
127
127
  switch (method) {
128
128
  case "initialize":
@@ -144,7 +144,7 @@ var MicroMcpServer = class extends ToolRegistry {
144
144
  if (!tool) {
145
145
  throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unknown tool: ${name}`);
146
146
  }
147
- const result = await tool.handler(args);
147
+ const result = await tool.handler(args, { transport });
148
148
  return result;
149
149
  }
150
150
  case "ping":
@@ -161,6 +161,11 @@ var MicroMcpServer = class extends ToolRegistry {
161
161
  this.broadcast({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
162
162
  });
163
163
  }
164
+ /** Send an application notification to every client, or one attached transport. */
165
+ notify(message, transport) {
166
+ if (transport) transport.send(message);
167
+ else this.broadcast(message);
168
+ }
164
169
  broadcast(message) {
165
170
  for (const t of this.transports) t.send(message);
166
171
  }
@@ -252,6 +257,50 @@ function attachRelay(server, channel) {
252
257
  return transport;
253
258
  }
254
259
 
260
+ // src/human-plus/events.ts
261
+ var MemoryHumanPlusEventStore = class {
262
+ constructor() {
263
+ this.events = [];
264
+ this.acknowledgements = /* @__PURE__ */ new Map();
265
+ this.listeners = /* @__PURE__ */ new Set();
266
+ this.nextSequence = 1;
267
+ }
268
+ async append(input) {
269
+ const event = { ...input, id: input.id ?? globalThis.crypto.randomUUID(), timestamp: input.timestamp ?? Date.now(), sequence: this.nextSequence++ };
270
+ this.events.push(event);
271
+ this.listeners.forEach((listener) => listener(event));
272
+ return event;
273
+ }
274
+ async list(query) {
275
+ const acknowledged = this.acknowledgements.get(query.consumerId);
276
+ const events = this.events.filter((event) => event.sequence > (query.after ?? 0) && (!query.unacknowledgedOnly || !acknowledged?.has(event.id)) && (!query.types?.length || query.types.includes(event.type)) && (!query.surfaces?.length || query.surfaces.includes(event.surfaceId))).slice(0, Math.max(1, Math.min(500, query.limit ?? 50)));
277
+ return { events, nextCursor: events.at(-1)?.sequence ?? query.after ?? 0 };
278
+ }
279
+ async acknowledge(consumerId, eventIds, disposition) {
280
+ let values = this.acknowledgements.get(consumerId);
281
+ if (!values) {
282
+ values = /* @__PURE__ */ new Map();
283
+ this.acknowledgements.set(consumerId, values);
284
+ }
285
+ eventIds.forEach((id) => values.set(id, disposition));
286
+ }
287
+ subscribe(listener) {
288
+ this.listeners.add(listener);
289
+ return () => this.listeners.delete(listener);
290
+ }
291
+ async compact(options = {}) {
292
+ const cutoff = options.before ?? Number.POSITIVE_INFINITY;
293
+ const keep = options.keepLast ?? 1e3;
294
+ const removable = Math.max(0, this.events.length - keep);
295
+ this.events = this.events.filter((event, index) => index >= removable || event.timestamp >= cutoff);
296
+ const ids = new Set(this.events.map((event) => event.id));
297
+ for (const values of this.acknowledgements.values()) for (const id of values.keys()) if (!ids.has(id)) values.delete(id);
298
+ }
299
+ };
300
+
301
+ // src/presence/index.ts
302
+ init_registry();
303
+
255
304
  // src/presence/wrap-tool-with-activity.ts
256
305
  init_registry();
257
306
  function wrapToolWithActivity(handler, options) {
@@ -272,7 +321,7 @@ function wrapToolWithActivity(handler, options) {
272
321
  target: { ...target, kind: target.kind ?? options.kind, screenId: target.screenId ?? options.screenId },
273
322
  action: options.toolName,
274
323
  timestamp: Date.now(),
275
- meta: extractMeta(result),
324
+ meta: options.buildMeta ? options.buildMeta(result) : extractMeta(result),
276
325
  ttlMs: options.ttlMs
277
326
  });
278
327
  return result;
@@ -286,6 +335,133 @@ function extractMeta(result) {
286
335
  return void 0;
287
336
  }
288
337
 
338
+ // src/presence/use-agent-activity.ts
339
+ init_registry();
340
+ function useAgentActivity(filter, options = {}) {
341
+ const cap = options.capacity ?? 50;
342
+ const [events, setEvents] = react.useState(() => fancyAutoCommon.readActivityHistory(filter).slice(-cap));
343
+ react.useEffect(() => {
344
+ setEvents(fancyAutoCommon.readActivityHistory(filter).slice(-cap));
345
+ return fancyAutoCommon.onActivity((event) => {
346
+ setEvents((prev) => {
347
+ const next = prev.length >= cap ? prev.slice(prev.length - cap + 1) : prev.slice();
348
+ next.push(event);
349
+ return next;
350
+ });
351
+ }, filter);
352
+ }, [filter?.agentId, filter?.screenId, filter?.kind, cap]);
353
+ return { events, latest: events.length > 0 ? events[events.length - 1] : null };
354
+ }
355
+ function useAgentActivityForScreen(screenId, options = {}) {
356
+ const { events, latest } = useAgentActivity({ screenId }, options);
357
+ const fadeAfter = latest?.ttlMs ?? 1500;
358
+ const [isAgentActive, setActive] = react.useState(false);
359
+ react.useEffect(() => {
360
+ if (!latest) {
361
+ setActive(false);
362
+ return;
363
+ }
364
+ setActive(true);
365
+ const timer = setTimeout(() => setActive(false), fadeAfter);
366
+ return () => clearTimeout(timer);
367
+ }, [latest, fadeAfter]);
368
+ return { events, latest, isAgentActive };
369
+ }
370
+
371
+ // src/bridges/tui.ts
372
+ function registerTuiBridge(host, options) {
373
+ const disposers = [];
374
+ const subscriptions = /* @__PURE__ */ new Map();
375
+ const pending = /* @__PURE__ */ new Map();
376
+ const defaultActor = options.actor ?? { kind: "agent", id: "agent", name: "Agent" };
377
+ const serverCapabilities = host.capabilities;
378
+ if (serverCapabilities) serverCapabilities.experimental = { ...serverCapabilities.experimental, "io.particle-academy/human-plus": { version: "1", push: true, durableCursor: true, stagedActions: true, wakesAgent: false } };
379
+ const register = (name, description, properties, required, handler) => {
380
+ disposers.push(host.registerTool({ name, description, inputSchema: { type: "object", properties, required } }, async (args, context) => {
381
+ try {
382
+ const result = await handler(args, context);
383
+ return textResult(typeof result === "string" ? result : JSON.stringify(result), result);
384
+ } catch (error) {
385
+ return errorResult(error instanceof Error ? error.message : String(error));
386
+ }
387
+ }));
388
+ };
389
+ const publish = async (surfaceId, type, payload, actor = defaultActor, priority = "normal") => options.eventStore.append({ appId: options.appId, sessionId: options.sessionId, surfaceId, type, payload, actor, priority });
390
+ register("tui_surfaces_list", "List addressable Fancy TUI surfaces and commands.", {}, [], () => options.registry.list().map((surface) => ({ id: surface.id, kind: surface.kind, label: surface.label, commands: surface.commands?.map((command) => ({ name: command.name, description: command.description, policy: command.policy ?? "execute", inputSchema: command.inputSchema })) ?? [] })));
391
+ register("tui_surface_read", "Read a Fancy TUI surface by stable id.", { id: { type: "string" } }, ["id"], (args) => {
392
+ const surface = options.registry.get(String(args.id));
393
+ if (!surface) throw new Error(`Unknown TUI surface: ${String(args.id)}`);
394
+ return { id: surface.id, kind: surface.kind, state: surface.read() };
395
+ });
396
+ register("tui_action_invoke", "Invoke or propose an action on a Fancy TUI surface.", { surfaceId: { type: "string" }, command: { type: "string" }, input: { type: "object" } }, ["surfaceId", "command"], async (args) => {
397
+ const surfaceId = String(args.surfaceId);
398
+ const commandName = String(args.command);
399
+ const surface = options.registry.get(surfaceId);
400
+ if (!surface) throw new Error(`Unknown TUI surface: ${surfaceId}`);
401
+ const command = surface.commands?.find((value) => value.name === commandName);
402
+ if (!command) throw new Error(`Unknown command '${commandName}' on ${surfaceId}`);
403
+ const policy = command.policy ?? "execute";
404
+ if (policy === "observe" || policy === "human-only") throw new Error(`Action ${surfaceId}.${commandName} is ${policy}`);
405
+ const input = args.input && typeof args.input === "object" && !Array.isArray(args.input) ? args.input : {};
406
+ if (policy === "propose" || policy === "confirm") {
407
+ const id = globalThis.crypto.randomUUID();
408
+ pending.set(id, { id, surfaceId, command: commandName, input, actor: defaultActor });
409
+ await publish(surfaceId, "action.proposed", { id, command: commandName, input }, defaultActor, "attention");
410
+ return { pending: true, id };
411
+ }
412
+ const result = await command.invoke(input);
413
+ await publish(surfaceId, "action.executed", { command: commandName, input, result });
414
+ fancyAutoCommon.emitActivity({ agentId: defaultActor.id, agentName: defaultActor.name, target: { kind: "custom", elementId: surfaceId }, action: `tui_${commandName}`, timestamp: Date.now(), meta: { surfaceId } });
415
+ return { executed: true, result };
416
+ });
417
+ register("tui_action_confirm", "Confirm and execute a pending TUI action.", { id: { type: "string" } }, ["id"], async (args) => {
418
+ const id = String(args.id);
419
+ const value = pending.get(id);
420
+ if (!value) throw new Error(`Unknown pending action: ${id}`);
421
+ const surface = options.registry.get(value.surfaceId);
422
+ const command = surface?.commands?.find((x) => x.name === value.command);
423
+ if (!command) throw new Error("Pending action target is no longer available");
424
+ pending.delete(id);
425
+ const result = await command.invoke(value.input);
426
+ await publish(value.surfaceId, "action.confirmed", { id, command: value.command, result }, { kind: "human", id: "human" });
427
+ return { executed: true, result };
428
+ });
429
+ register("tui_action_reject", "Reject a pending TUI action.", { id: { type: "string" } }, ["id"], async (args) => {
430
+ const id = String(args.id);
431
+ const value = pending.get(id);
432
+ if (!value) throw new Error(`Unknown pending action: ${id}`);
433
+ pending.delete(id);
434
+ await publish(value.surfaceId, "action.rejected", { id, command: value.command }, { kind: "human", id: "human" });
435
+ return { rejected: true };
436
+ });
437
+ register("human_plus_events_list", "Pull durable Human+ events for a consumer.", { consumerId: { type: "string" }, after: { type: "number" }, limit: { type: "number" }, unacknowledgedOnly: { type: "boolean" } }, ["consumerId"], (args) => options.eventStore.list({ consumerId: String(args.consumerId), after: typeof args.after === "number" ? args.after : void 0, limit: typeof args.limit === "number" ? args.limit : void 0, unacknowledgedOnly: args.unacknowledgedOnly === true }));
438
+ register("human_plus_events_ack", "Acknowledge durable Human+ events.", { consumerId: { type: "string" }, eventIds: { type: "array", items: { type: "string" } }, disposition: { type: "string" } }, ["consumerId", "eventIds"], async (args) => {
439
+ const ids = Array.isArray(args.eventIds) ? args.eventIds.map(String) : [];
440
+ await options.eventStore.acknowledge(String(args.consumerId), ids, ["seen", "handled", "rejected", "ignored"].includes(String(args.disposition)) ? String(args.disposition) : "handled");
441
+ return { acknowledged: ids.length };
442
+ });
443
+ register("human_plus_events_subscribe", "Push matching Human+ events on this MCP transport while retaining them in the inbox.", { consumerId: { type: "string" }, types: { type: "array" }, surfaces: { type: "array" } }, ["consumerId"], (args, context) => {
444
+ if (!context?.transport) throw new Error("Subscriptions require an attached MCP transport");
445
+ subscriptions.set(context.transport, { consumerId: String(args.consumerId), types: Array.isArray(args.types) ? args.types.map(String) : void 0, surfaces: Array.isArray(args.surfaces) ? args.surfaces.map(String) : void 0 });
446
+ return { subscribed: true, delivery: "at-least-once", wakesAgent: false };
447
+ });
448
+ const unsubscribeStore = options.eventStore.subscribe((event) => {
449
+ const server = host;
450
+ if (typeof server.notify !== "function") return;
451
+ for (const [transport, subscription] of subscriptions) {
452
+ if (subscription.types?.length && !subscription.types.includes(event.type)) continue;
453
+ if (subscription.surfaces?.length && !subscription.surfaces.includes(event.surfaceId)) continue;
454
+ server.notify({ jsonrpc: "2.0", method: "notifications/human_plus/event", params: event }, transport);
455
+ }
456
+ });
457
+ return { id: "tui", title: "Fancy TUI", pending: () => [...pending.values()], dispose: () => {
458
+ disposers.forEach((dispose) => dispose());
459
+ unsubscribeStore();
460
+ subscriptions.clear();
461
+ pending.clear();
462
+ } };
463
+ }
464
+
289
465
  // src/undo/undo-tools.ts
290
466
  var installedHosts = /* @__PURE__ */ new WeakSet();
291
467
  function ensureUndoToolsRegistered(host, options = {}) {
@@ -367,6 +543,7 @@ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
367
543
  function registerFormBridge(host, options) {
368
544
  const { adapter } = options;
369
545
  const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
546
+ const pendingMode = options.pendingMode ?? true;
370
547
  const disposers = [];
371
548
  ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
372
549
  const formId = adapter.id;
@@ -495,11 +672,20 @@ function registerFormBridge(host, options) {
495
672
  );
496
673
  reg(
497
674
  "form_submit",
498
- "Submit the form. Host returns ok + values (or an error).",
675
+ "Submit the form. Host returns ok + values (or an error). In pendingMode this is staged for the human to confirm first.",
499
676
  {},
500
677
  [],
501
678
  async () => {
502
679
  if (!adapter.submit) return errorResult("Host did not provide a submit implementation.");
680
+ if (pendingMode && adapter.confirm) {
681
+ const ok = await adapter.confirm({
682
+ action: "submit",
683
+ form: formId,
684
+ title: adapter.title,
685
+ values: adapter.getValues()
686
+ });
687
+ if (!ok) return errorResult("Declined by user");
688
+ }
503
689
  const result = await adapter.submit();
504
690
  if (!result.ok) return errorResult(result.error ?? "Submit failed");
505
691
  return textResult("Submitted", { values: result.values });
@@ -863,6 +1049,7 @@ function useSheetsActivityHighlights(options = {}) {
863
1049
  }
864
1050
 
865
1051
  // src/bridges/code.ts
1052
+ var MAX_STREAM_CHARS = 1e4;
866
1053
  var DEFAULT_AGENT3 = { id: "agent", name: "Agent", color: "#a855f7" };
867
1054
  function registerCodeBridge(host, options) {
868
1055
  const { adapter } = options;
@@ -875,6 +1062,16 @@ function registerCodeBridge(host, options) {
875
1062
  elementId: adapter.id,
876
1063
  label: adapter.title ?? adapter.id
877
1064
  };
1065
+ const recordUndo = (action, label, prevValue, nextValue) => {
1066
+ fancyAutoCommon.pushUndoEntry(agent.id, {
1067
+ timestamp: Date.now(),
1068
+ bridgeId: "code",
1069
+ action,
1070
+ label,
1071
+ undo: () => adapter.setValue(prevValue),
1072
+ redo: () => adapter.setValue(nextValue)
1073
+ });
1074
+ };
878
1075
  const reg = (name, description, properties, required, handler, isMutation) => {
879
1076
  const wrapped = async (args) => {
880
1077
  try {
@@ -944,7 +1141,9 @@ function registerCodeBridge(host, options) {
944
1141
  ["value"],
945
1142
  (args) => {
946
1143
  const value = String(args.value ?? "");
1144
+ const prev = adapter.getValue();
947
1145
  adapter.setValue(value);
1146
+ recordUndo("code_set_value", "Replace document", prev, value);
948
1147
  return textResult(`Replaced document (${value.length} chars)`, { length: value.length });
949
1148
  },
950
1149
  true
@@ -956,8 +1155,10 @@ function registerCodeBridge(host, options) {
956
1155
  ["text"],
957
1156
  (args) => {
958
1157
  const text = String(args.text ?? "");
959
- const next = adapter.getValue() + text;
1158
+ const prev = adapter.getValue();
1159
+ const next = prev + text;
960
1160
  adapter.setValue(next);
1161
+ recordUndo("code_append", `Append ${text.length} chars`, prev, next);
961
1162
  return textResult(`Appended ${text.length} chars`, { length: next.length });
962
1163
  },
963
1164
  true
@@ -972,6 +1173,11 @@ function registerCodeBridge(host, options) {
972
1173
  ["text"],
973
1174
  async (args) => {
974
1175
  const text = String(args.text ?? "");
1176
+ if (text.length > MAX_STREAM_CHARS) {
1177
+ return errorResult(
1178
+ `Text too long to stream (${text.length} > ${MAX_STREAM_CHARS}). Use code_set_value / code_append for large content.`
1179
+ );
1180
+ }
975
1181
  const cps = Math.max(1, Number(args.cps ?? 25));
976
1182
  const interval = Math.max(8, Math.round(1e3 / cps));
977
1183
  const start = adapter.getValue();
@@ -979,6 +1185,7 @@ function registerCodeBridge(host, options) {
979
1185
  adapter.setValue(start + text.slice(0, i));
980
1186
  if (i < text.length) await new Promise((r) => setTimeout(r, interval));
981
1187
  }
1188
+ recordUndo("code_stream_append", `Append ${text.length} chars`, start, start + text);
982
1189
  return textResult(`Streamed ${text.length} chars`, { length: text.length });
983
1190
  },
984
1191
  true
@@ -990,7 +1197,9 @@ function registerCodeBridge(host, options) {
990
1197
  ["text"],
991
1198
  (args) => {
992
1199
  if (!adapter.replaceSelection) return errorResult("Host did not provide replaceSelection.");
1200
+ const prev = adapter.getValue();
993
1201
  adapter.replaceSelection(String(args.text ?? ""));
1202
+ recordUndo("code_replace_selection", "Replace selection", prev, adapter.getValue());
994
1203
  return textResult("Selection replaced", {});
995
1204
  },
996
1205
  true
@@ -1003,7 +1212,18 @@ function registerCodeBridge(host, options) {
1003
1212
  (args) => {
1004
1213
  if (!adapter.setLanguage) return errorResult("Host did not provide setLanguage.");
1005
1214
  const lang = String(args.language ?? "");
1215
+ const prevLang = adapter.getLanguage?.();
1006
1216
  adapter.setLanguage(lang);
1217
+ if (prevLang !== void 0) {
1218
+ fancyAutoCommon.pushUndoEntry(agent.id, {
1219
+ timestamp: Date.now(),
1220
+ bridgeId: "code",
1221
+ action: "code_set_language",
1222
+ label: `Language \u2192 ${lang}`,
1223
+ undo: () => adapter.setLanguage?.(prevLang),
1224
+ redo: () => adapter.setLanguage?.(lang)
1225
+ });
1226
+ }
1007
1227
  return textResult(`Language \u2192 ${lang}`, { language: lang });
1008
1228
  },
1009
1229
  true
@@ -1018,7 +1238,8 @@ function registerCodeBridge(host, options) {
1018
1238
  adapter.focus();
1019
1239
  return textResult("Focused", {});
1020
1240
  },
1021
- true
1241
+ // Focus is not a document mutation — don't broadcast it as one or log undo.
1242
+ false
1022
1243
  );
1023
1244
  return {
1024
1245
  id: `code:${adapter.id}`,
@@ -2289,6 +2510,22 @@ function registerMapBridge(host, options) {
2289
2510
 
2290
2511
  // src/bridges/files.ts
2291
2512
  var DEFAULT_AGENT9 = { id: "agent", name: "Agent", color: "#a855f7" };
2513
+ var MAX_SNAPSHOT_DEPTH = 8;
2514
+ function assertPathWithinRoot(root, path) {
2515
+ const segments = path.replace(/[\\/]+/g, "/").split("/");
2516
+ if (segments.includes("..")) {
2517
+ throw new Error(`Path may not contain '..': ${path}`);
2518
+ }
2519
+ const isAbsolute = /^([A-Za-z]:)?[\\/]/.test(path) || /^[A-Za-z]:/.test(path);
2520
+ if (isAbsolute) {
2521
+ const norm = (p2) => p2.replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
2522
+ const r = norm(root);
2523
+ const p = norm(path);
2524
+ if (p !== r && !p.startsWith(r + "/")) {
2525
+ throw new Error(`Path escapes the configured root: ${path}`);
2526
+ }
2527
+ }
2528
+ }
2292
2529
  var str3 = (v, fallback = "") => typeof v === "string" ? v : fallback;
2293
2530
  var num3 = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : 0;
2294
2531
  var asPaths = (v) => {
@@ -2304,6 +2541,10 @@ function registerFilesBridge(host, options) {
2304
2541
  const { adapter } = options;
2305
2542
  const agent = { ...DEFAULT_AGENT9, ...options.agent ?? {} };
2306
2543
  const disposers = [];
2544
+ const guard = (path) => {
2545
+ if (options.root !== void 0) assertPathWithinRoot(options.root, path);
2546
+ return path;
2547
+ };
2307
2548
  ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
2308
2549
  const fTarget = (args, _result) => ({
2309
2550
  kind: "files",
@@ -2359,7 +2600,7 @@ function registerFilesBridge(host, options) {
2359
2600
  { path: { type: "string", description: "Folder path to list; defaults to the current directory." } },
2360
2601
  [],
2361
2602
  async (args) => {
2362
- const path = args.path !== void 0 ? str3(args.path) : adapter.getPath();
2603
+ const path = guard(args.path !== void 0 ? str3(args.path) : adapter.getPath());
2363
2604
  const entries = await adapter.listChildren(path);
2364
2605
  const summary = entries.map((e) => ({
2365
2606
  path: e.path,
@@ -2378,7 +2619,7 @@ function registerFilesBridge(host, options) {
2378
2619
  { path: { type: "string" } },
2379
2620
  ["path"],
2380
2621
  (args) => {
2381
- const path = str3(args.path);
2622
+ const path = guard(str3(args.path));
2382
2623
  const prev = adapter.getPath();
2383
2624
  adapter.setPath(path);
2384
2625
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -2399,7 +2640,7 @@ function registerFilesBridge(host, options) {
2399
2640
  { path: { type: "string" } },
2400
2641
  ["path"],
2401
2642
  (args) => {
2402
- const path = str3(args.path);
2643
+ const path = guard(str3(args.path));
2403
2644
  if (adapter.getExpanded().includes(path)) {
2404
2645
  return textResult(`${path} already expanded`, { path, expanded: true });
2405
2646
  }
@@ -2422,7 +2663,7 @@ function registerFilesBridge(host, options) {
2422
2663
  { path: { type: "string" } },
2423
2664
  ["path"],
2424
2665
  (args) => {
2425
- const path = str3(args.path);
2666
+ const path = guard(str3(args.path));
2426
2667
  if (!adapter.getExpanded().includes(path)) {
2427
2668
  return textResult(`${path} already collapsed`, { path, expanded: false });
2428
2669
  }
@@ -2448,7 +2689,7 @@ function registerFilesBridge(host, options) {
2448
2689
  },
2449
2690
  [],
2450
2691
  (args) => {
2451
- const next = args.paths !== void 0 ? asPaths(args.paths) : asPaths(args.path);
2692
+ const next = (args.paths !== void 0 ? asPaths(args.paths) : asPaths(args.path)).map(guard);
2452
2693
  const prev = adapter.getSelection();
2453
2694
  adapter.setSelection(next);
2454
2695
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -2477,8 +2718,8 @@ function registerFilesBridge(host, options) {
2477
2718
  if (!adapter.requestSnapshot) {
2478
2719
  return errorResult("Host did not wire requestSnapshot (snapshot mode not enabled).");
2479
2720
  }
2480
- const path = args.path !== void 0 ? str3(args.path) : adapter.getPath();
2481
- const depth = args.depth !== void 0 ? num3(args.depth) : void 0;
2721
+ const path = guard(args.path !== void 0 ? str3(args.path) : adapter.getPath());
2722
+ const depth = args.depth !== void 0 ? Math.max(0, Math.min(MAX_SNAPSHOT_DEPTH, num3(args.depth))) : void 0;
2482
2723
  const tree = await adapter.requestSnapshot(path, depth);
2483
2724
  const count = Array.isArray(tree) ? tree.length : 0;
2484
2725
  return textResult(`Snapshot of ${path}: ${count} top-level entr${count === 1 ? "y" : "ies"}.`, {
@@ -2502,16 +2743,15 @@ var DEFAULT_AGENT10 = { id: "agent", name: "Agent", color: "#a855f7" };
2502
2743
  var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
2503
2744
  function registerTerminalBridge(host, options) {
2504
2745
  const agent = { ...DEFAULT_AGENT10, ...options.agent ?? {} };
2505
- const pendingMode = options.pendingMode ?? false;
2746
+ const pendingMode = options.pendingMode ?? true;
2506
2747
  const screenId = options.screenId ?? options.adapter?.screenId;
2507
2748
  const disposers = [];
2508
2749
  const staged = /* @__PURE__ */ new Map();
2509
2750
  let seq = 0;
2510
2751
  ensureUndoToolsRegistered(host);
2511
2752
  const listTerminals = () => {
2512
- if (options.terminals) return options.terminals();
2513
- if (options.adapter) return [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }];
2514
- return [];
2753
+ const all = options.terminals ? options.terminals() : options.adapter ? [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }] : [];
2754
+ return options.canAccess ? all.filter((t) => options.canAccess(t.id)) : all;
2515
2755
  };
2516
2756
  const resolve = (id) => {
2517
2757
  const list = listTerminals();
@@ -2528,6 +2768,17 @@ function registerTerminalBridge(host, options) {
2528
2768
  elementId: terminalId ?? screenId ?? "terminal",
2529
2769
  label: label ?? "terminal"
2530
2770
  });
2771
+ const redactMeta = (result) => {
2772
+ const sc = result.structuredContent;
2773
+ if (!sc || typeof sc !== "object") return void 0;
2774
+ return {
2775
+ terminal: sc.terminal,
2776
+ kind: sc.kind,
2777
+ length: typeof sc.data === "string" ? sc.data.length : void 0,
2778
+ executed: sc.executed,
2779
+ pending: sc.pending
2780
+ };
2781
+ };
2531
2782
  const TERMINAL_ARG = {
2532
2783
  terminal: {
2533
2784
  type: "string",
@@ -2547,7 +2798,12 @@ function registerTerminalBridge(host, options) {
2547
2798
  agent,
2548
2799
  kind: "terminal",
2549
2800
  screenId,
2550
- resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
2801
+ // A per-call resolver may return null to SKIP the broadcast (e.g. a
2802
+ // merely-staged command); only fall back to the default target when no
2803
+ // resolver was supplied.
2804
+ resolveTarget: ({ args, result }) => resolveTarget ? resolveTarget(args, result) : target(),
2805
+ // Never fan raw command/keystroke bytes to peers — broadcast shape only.
2806
+ buildMeta: redactMeta
2551
2807
  }) : wrapped;
2552
2808
  disposers.push(
2553
2809
  host.registerTool(
@@ -2584,6 +2840,7 @@ function registerTerminalBridge(host, options) {
2584
2840
  return textResult(`${kind === "run" ? "ran" : "wrote"} on ${t.id}: ${truncate(data)}`, {
2585
2841
  kind,
2586
2842
  data,
2843
+ length: data.length,
2587
2844
  terminal: t.id,
2588
2845
  executed: true
2589
2846
  });
@@ -2594,7 +2851,7 @@ function registerTerminalBridge(host, options) {
2594
2851
  options.onPending?.(entry);
2595
2852
  return textResult(
2596
2853
  `Staged ${kind} on ${t.id} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2597
- { ...entry, pending: true }
2854
+ { ...entry, length: data.length, pending: true }
2598
2855
  );
2599
2856
  }
2600
2857
  reg(
@@ -2639,12 +2896,13 @@ function registerTerminalBridge(host, options) {
2639
2896
  );
2640
2897
  reg(
2641
2898
  "terminal_write",
2642
- "Write raw data / keystrokes to a terminal (input, control chars, ANSI). Pass `terminal` to target a specific one. In pendingMode this stages instead of executing.",
2899
+ "Write raw data / keystrokes to a terminal (input, control chars, ANSI). Pass `terminal` to target a specific one. In pendingMode this stages instead of executing. NOTE: bytes are written verbatim \u2014 an embedded CR runs a command, and ANSI/OSC sequences reach the terminal; treat this exactly like terminal_run for gating.",
2643
2900
  { ...TERMINAL_ARG, data: { type: "string", description: "Raw bytes to write." } },
2644
2901
  ["data"],
2645
2902
  (args) => stageOrExec(need(args), "write", String(args.data)),
2646
2903
  true,
2647
- (args) => target(`write:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2904
+ // Skip the peer broadcast while only staged; emit once it actually executes.
2905
+ (args, result) => result?.structuredContent?.pending ? null : target(`write:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2648
2906
  );
2649
2907
  reg(
2650
2908
  "terminal_run",
@@ -2653,41 +2911,47 @@ function registerTerminalBridge(host, options) {
2653
2911
  ["command"],
2654
2912
  (args) => stageOrExec(need(args), "run", String(args.command)),
2655
2913
  true,
2656
- (args) => target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2657
- );
2658
- reg(
2659
- "terminal_confirm",
2660
- "Confirm + execute a staged command by id (pendingMode).",
2661
- { id: { type: "string" } },
2662
- ["id"],
2663
- async (args) => {
2664
- const id = String(args.id);
2665
- const entry = staged.get(id);
2666
- if (!entry) return errorResult(`No staged command ${id}`);
2667
- const t = resolve(entry.terminalId);
2668
- if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
2669
- staged.delete(id);
2670
- await exec(t, entry.kind, entry.data);
2671
- return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });
2672
- },
2673
- true,
2674
- (args) => {
2675
- const e = staged.get(String(args.id));
2676
- return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
2677
- }
2678
- );
2679
- reg(
2680
- "terminal_reject",
2681
- "Drop a staged command by id without executing it.",
2682
- { id: { type: "string" } },
2683
- ["id"],
2684
- (args) => {
2685
- const id = String(args.id);
2686
- if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
2687
- return textResult(`Rejected ${id}`, { id, rejected: true });
2688
- },
2689
- false
2914
+ (args, result) => result?.structuredContent?.pending ? null : target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2690
2915
  );
2916
+ if (options.allowAgentConfirm) {
2917
+ reg(
2918
+ "terminal_confirm",
2919
+ "Confirm + execute a staged command by id (pendingMode).",
2920
+ { id: { type: "string" } },
2921
+ ["id"],
2922
+ async (args) => {
2923
+ const id = String(args.id);
2924
+ const entry = staged.get(id);
2925
+ if (!entry) return errorResult(`No staged command ${id}`);
2926
+ const t = resolve(entry.terminalId);
2927
+ if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
2928
+ staged.delete(id);
2929
+ await exec(t, entry.kind, entry.data);
2930
+ return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, {
2931
+ ...entry,
2932
+ length: entry.data.length,
2933
+ executed: true
2934
+ });
2935
+ },
2936
+ true,
2937
+ (args) => {
2938
+ const e = staged.get(String(args.id));
2939
+ return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
2940
+ }
2941
+ );
2942
+ reg(
2943
+ "terminal_reject",
2944
+ "Drop a staged command by id without executing it.",
2945
+ { id: { type: "string" } },
2946
+ ["id"],
2947
+ (args) => {
2948
+ const id = String(args.id);
2949
+ if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
2950
+ return textResult(`Rejected ${id}`, { id, rejected: true });
2951
+ },
2952
+ false
2953
+ );
2954
+ }
2691
2955
  if (canClear) {
2692
2956
  reg(
2693
2957
  "terminal_clear",
@@ -2732,8 +2996,10 @@ function registerTerminalBridge(host, options) {
2732
2996
  if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);
2733
2997
  const id = String(args.id);
2734
2998
  const shells = t.listShells?.();
2735
- if (shells && shells.length && !shells.some((s) => s.id === id)) {
2736
- return errorResult(`Unknown shell '${id}' for ${t.id}. Use terminal_list_shells for valid ids.`);
2999
+ if (!shells || !shells.some((s) => s.id === id)) {
3000
+ return errorResult(
3001
+ `Unknown or unlisted shell '${id}' for ${t.id}. Call terminal_list_shells for valid ids.`
3002
+ );
2737
3003
  }
2738
3004
  await t.setShell(id);
2739
3005
  return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
@@ -2874,6 +3140,12 @@ function registerNavigationBridge(host, options) {
2874
3140
  if (stale) return stale;
2875
3141
  const url = String(args.url ?? "");
2876
3142
  if (!url) return errorResult("url is required");
3143
+ const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url)?.[1]?.toLowerCase();
3144
+ if (scheme && scheme !== "http" && scheme !== "https") {
3145
+ return errorResult(
3146
+ `Refusing to navigate to a '${scheme}:' URL. Only http(s) URLs or same-site paths are allowed.`
3147
+ );
3148
+ }
2877
3149
  const from = adapter.getLocation().url;
2878
3150
  await adapter.visit(url);
2879
3151
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -3858,8 +4130,7 @@ function describeSession(id, token) {
3858
4130
  }
3859
4131
  function buildShareUrl(descriptor, baseUrl = typeof window !== "undefined" ? window.location.href.split("?")[0] : "") {
3860
4132
  const u = new URL(baseUrl);
3861
- u.searchParams.set("session", descriptor.id);
3862
- u.searchParams.set("token", descriptor.token);
4133
+ u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();
3863
4134
  return u.toString();
3864
4135
  }
3865
4136
  function buildShareConfig(descriptor, transport = "broadcast-channel") {
@@ -3874,9 +4145,10 @@ function buildShareConfig(descriptor, transport = "broadcast-channel") {
3874
4145
  }
3875
4146
  function readSessionFromUrl() {
3876
4147
  if (typeof window === "undefined") return null;
3877
- const params = new URL(window.location.href).searchParams;
3878
- const id = params.get("session");
3879
- const token = params.get("token");
4148
+ const url = new URL(window.location.href);
4149
+ const frag = new URLSearchParams(url.hash.replace(/^#/, ""));
4150
+ const id = frag.get("session") ?? url.searchParams.get("session");
4151
+ const token = frag.get("token") ?? url.searchParams.get("token");
3880
4152
  if (!id || !token) return null;
3881
4153
  return describeSession(id, token);
3882
4154
  }
@@ -4568,39 +4840,6 @@ function buildCurlRecipe(session) {
4568
4840
  ].join("\n");
4569
4841
  }
4570
4842
 
4571
- // src/presence/use-agent-activity.ts
4572
- init_registry();
4573
- function useAgentActivity(filter, options = {}) {
4574
- const cap = options.capacity ?? 50;
4575
- const [events, setEvents] = react.useState(() => fancyAutoCommon.readActivityHistory(filter).slice(-cap));
4576
- react.useEffect(() => {
4577
- setEvents(fancyAutoCommon.readActivityHistory(filter).slice(-cap));
4578
- return fancyAutoCommon.onActivity((event) => {
4579
- setEvents((prev) => {
4580
- const next = prev.length >= cap ? prev.slice(prev.length - cap + 1) : prev.slice();
4581
- next.push(event);
4582
- return next;
4583
- });
4584
- }, filter);
4585
- }, [filter?.agentId, filter?.screenId, filter?.kind, cap]);
4586
- return { events, latest: events.length > 0 ? events[events.length - 1] : null };
4587
- }
4588
- function useAgentActivityForScreen(screenId, options = {}) {
4589
- const { events, latest } = useAgentActivity({ screenId }, options);
4590
- const fadeAfter = latest?.ttlMs ?? 1500;
4591
- const [isAgentActive, setActive] = react.useState(false);
4592
- react.useEffect(() => {
4593
- if (!latest) {
4594
- setActive(false);
4595
- return;
4596
- }
4597
- setActive(true);
4598
- const timer = setTimeout(() => setActive(false), fadeAfter);
4599
- return () => clearTimeout(timer);
4600
- }, [latest, fadeAfter]);
4601
- return { events, latest, isAgentActive };
4602
- }
4603
-
4604
4843
  // src/connectors/targets.ts
4605
4844
  var CLAUDE_CONNECTORS_URL = "https://claude.ai/settings/connectors";
4606
4845
  function encodeBase64Json(value) {
@@ -5059,9 +5298,6 @@ function SimulateUsersButton({
5059
5298
  );
5060
5299
  }
5061
5300
 
5062
- // src/presence/index.ts
5063
- init_registry();
5064
-
5065
5301
  // src/heuristics/sink.ts
5066
5302
  function numericMeta(meta, key) {
5067
5303
  const v = meta?.[key];
@@ -5302,6 +5538,7 @@ exports.InProcessTransport = InProcessTransport;
5302
5538
  exports.MCPB_MANIFEST_VERSION = MCPB_MANIFEST_VERSION;
5303
5539
  exports.MCPB_MIN_NODE = MCPB_MIN_NODE;
5304
5540
  exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
5541
+ exports.MemoryHumanPlusEventStore = MemoryHumanPlusEventStore;
5305
5542
  exports.MicroMcpServer = MicroMcpServer;
5306
5543
  exports.RelayTransport = RelayTransport;
5307
5544
  exports.ScreensActivityBridge = ScreensActivityBridge;
@@ -5348,6 +5585,7 @@ exports.registerScreensBridge = registerScreensBridge;
5348
5585
  exports.registerSheetsBridge = registerSheetsBridge;
5349
5586
  exports.registerSlidesBridge = registerSlidesBridge;
5350
5587
  exports.registerTerminalBridge = registerTerminalBridge;
5588
+ exports.registerTuiBridge = registerTuiBridge;
5351
5589
  exports.registerUndoTools = registerUndoTools;
5352
5590
  exports.rpcError = rpcError;
5353
5591
  exports.slugifyServerName = slugifyServerName;