@particle-academy/agent-integrations 0.29.0 → 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 (138) hide show
  1. package/README.md +30 -1
  2. package/dist/bridges/files.d.cts +20 -1
  3. package/dist/bridges/files.d.ts +20 -1
  4. package/dist/bridges/forms.d.cts +22 -1
  5. package/dist/bridges/forms.d.ts +22 -1
  6. package/dist/bridges/terminal.d.cts +21 -1
  7. package/dist/bridges/terminal.d.ts +21 -1
  8. package/dist/bridges-artboard.cjs +1 -1
  9. package/dist/bridges-artboard.cjs.map +1 -1
  10. package/dist/bridges-artboard.js +1 -1
  11. package/dist/bridges-catalog.cjs +1 -1
  12. package/dist/bridges-catalog.cjs.map +1 -1
  13. package/dist/bridges-catalog.js +2 -2
  14. package/dist/bridges-charts.cjs +1 -1
  15. package/dist/bridges-charts.cjs.map +1 -1
  16. package/dist/bridges-charts.js +2 -2
  17. package/dist/bridges-cms.cjs +1 -1
  18. package/dist/bridges-cms.cjs.map +1 -1
  19. package/dist/bridges-cms.js +3 -3
  20. package/dist/bridges-code.cjs +38 -3
  21. package/dist/bridges-code.cjs.map +1 -1
  22. package/dist/bridges-code.js +2 -2
  23. package/dist/bridges-doc.cjs +1 -1
  24. package/dist/bridges-doc.cjs.map +1 -1
  25. package/dist/bridges-doc.js +2 -2
  26. package/dist/bridges-features.cjs +1 -1
  27. package/dist/bridges-features.cjs.map +1 -1
  28. package/dist/bridges-features.js +2 -2
  29. package/dist/bridges-files.cjs +29 -8
  30. package/dist/bridges-files.cjs.map +1 -1
  31. package/dist/bridges-files.js +2 -2
  32. package/dist/bridges-flow.cjs +1 -1
  33. package/dist/bridges-flow.cjs.map +1 -1
  34. package/dist/bridges-flow.js +1 -1
  35. package/dist/bridges-forms.cjs +12 -2
  36. package/dist/bridges-forms.cjs.map +1 -1
  37. package/dist/bridges-forms.js +2 -2
  38. package/dist/bridges-map.cjs +1 -1
  39. package/dist/bridges-map.cjs.map +1 -1
  40. package/dist/bridges-map.js +2 -2
  41. package/dist/bridges-scene.cjs +1 -1
  42. package/dist/bridges-scene.cjs.map +1 -1
  43. package/dist/bridges-scene.js +2 -2
  44. package/dist/bridges-screens.cjs +1 -1
  45. package/dist/bridges-screens.cjs.map +1 -1
  46. package/dist/bridges-screens.js +2 -2
  47. package/dist/bridges-sheets.cjs +1 -1
  48. package/dist/bridges-sheets.cjs.map +1 -1
  49. package/dist/bridges-sheets.js +2 -2
  50. package/dist/bridges-slides.cjs +1 -1
  51. package/dist/bridges-slides.cjs.map +1 -1
  52. package/dist/bridges-slides.js +2 -2
  53. package/dist/bridges-terminal.cjs +70 -45
  54. package/dist/bridges-terminal.cjs.map +1 -1
  55. package/dist/bridges-terminal.js +2 -2
  56. package/dist/bridges-tui.js +1 -1
  57. package/dist/bridges-whiteboard.cjs +1 -1
  58. package/dist/bridges-whiteboard.cjs.map +1 -1
  59. package/dist/bridges-whiteboard.js +2 -2
  60. package/dist/{chunk-OFCR6JY3.js → chunk-2U5JB6ZA.js} +3 -3
  61. package/dist/{chunk-OFCR6JY3.js.map → chunk-2U5JB6ZA.js.map} +1 -1
  62. package/dist/{chunk-ULJL53DL.js → chunk-2VENWGLZ.js} +3 -3
  63. package/dist/chunk-2VENWGLZ.js.map +1 -0
  64. package/dist/{chunk-BE2XS577.js → chunk-6YUBWQTS.js} +3 -3
  65. package/dist/{chunk-BE2XS577.js.map → chunk-6YUBWQTS.js.map} +1 -1
  66. package/dist/{chunk-OHUDEA6R.js → chunk-BHQJJ2YG.js} +3 -3
  67. package/dist/{chunk-OHUDEA6R.js.map → chunk-BHQJJ2YG.js.map} +1 -1
  68. package/dist/{chunk-LZJI7EKQ.js → chunk-CA7GIJJC.js} +3 -3
  69. package/dist/{chunk-LZJI7EKQ.js.map → chunk-CA7GIJJC.js.map} +1 -1
  70. package/dist/{chunk-7VBB2FJB.js → chunk-DKSR4R4O.js} +3 -3
  71. package/dist/{chunk-7VBB2FJB.js.map → chunk-DKSR4R4O.js.map} +1 -1
  72. package/dist/{chunk-2Y7Y5PTR.js → chunk-G5EZNWXJ.js} +3 -3
  73. package/dist/{chunk-2Y7Y5PTR.js.map → chunk-G5EZNWXJ.js.map} +1 -1
  74. package/dist/{chunk-OIX2ANFS.js → chunk-GRHPEK6M.js} +45 -9
  75. package/dist/chunk-GRHPEK6M.js.map +1 -0
  76. package/dist/{chunk-VMCG3PJD.js → chunk-HRHC5GM5.js} +31 -11
  77. package/dist/chunk-HRHC5GM5.js.map +1 -0
  78. package/dist/{chunk-5KLUYF4D.js → chunk-HSI5RVF4.js} +3 -3
  79. package/dist/{chunk-5KLUYF4D.js.map → chunk-HSI5RVF4.js.map} +1 -1
  80. package/dist/{chunk-AF454WRD.js → chunk-LQZKRBWR.js} +3 -3
  81. package/dist/{chunk-AF454WRD.js.map → chunk-LQZKRBWR.js.map} +1 -1
  82. package/dist/{chunk-KMSRG5KH.js → chunk-N6RLSLOF.js} +7 -7
  83. package/dist/chunk-N6RLSLOF.js.map +1 -0
  84. package/dist/{chunk-NSPL5TVF.js → chunk-OAFJ53X3.js} +3 -3
  85. package/dist/{chunk-NSPL5TVF.js.map → chunk-OAFJ53X3.js.map} +1 -1
  86. package/dist/{chunk-3PKHJ4IV.js → chunk-PR4SXFZH.js} +3 -3
  87. package/dist/{chunk-3PKHJ4IV.js.map → chunk-PR4SXFZH.js.map} +1 -1
  88. package/dist/{chunk-KYMEDV6E.js → chunk-RIXYCFDO.js} +41 -6
  89. package/dist/chunk-RIXYCFDO.js.map +1 -0
  90. package/dist/{chunk-TNU6AWUO.js → chunk-USQGA672.js} +14 -4
  91. package/dist/chunk-USQGA672.js.map +1 -0
  92. package/dist/{chunk-KKGC4ERF.js → chunk-UZHNKUER.js} +72 -47
  93. package/dist/chunk-UZHNKUER.js.map +1 -0
  94. package/dist/{chunk-UTUVN6HQ.js → chunk-WCKSDFTM.js} +3 -3
  95. package/dist/{chunk-UTUVN6HQ.js.map → chunk-WCKSDFTM.js.map} +1 -1
  96. package/dist/{chunk-HFLHLS2N.js → chunk-ZNGTC4CO.js} +3 -3
  97. package/dist/{chunk-HFLHLS2N.js.map → chunk-ZNGTC4CO.js.map} +1 -1
  98. package/dist/components/SharedWhiteboard/index.d.cts +1 -1
  99. package/dist/components/SharedWhiteboard/index.d.ts +1 -1
  100. package/dist/components-shared-whiteboard.cjs +2 -3
  101. package/dist/components-shared-whiteboard.cjs.map +1 -1
  102. package/dist/components-shared-whiteboard.js +4 -4
  103. package/dist/heuristics.js +1 -1
  104. package/dist/index.cjs +156 -60
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.d.cts +2 -2
  107. package/dist/index.d.ts +2 -2
  108. package/dist/index.js +27 -21
  109. package/dist/index.js.map +1 -1
  110. package/dist/presence/index.d.cts +7 -0
  111. package/dist/presence/index.d.ts +7 -0
  112. package/dist/presence.cjs +1 -1
  113. package/dist/presence.cjs.map +1 -1
  114. package/dist/presence.js +1 -1
  115. package/dist/relay-server/index.d.cts +20 -0
  116. package/dist/relay-server/index.d.ts +20 -0
  117. package/dist/relay-server-cli.cjs +53 -9
  118. package/dist/relay-server-cli.cjs.map +1 -1
  119. package/dist/relay-server-cli.js +11 -3
  120. package/dist/relay-server-cli.js.map +1 -1
  121. package/dist/relay-server.cjs +43 -7
  122. package/dist/relay-server.cjs.map +1 -1
  123. package/dist/relay-server.js +1 -1
  124. package/dist/sharing/index.d.cts +1 -1
  125. package/dist/sharing/index.d.ts +1 -1
  126. package/dist/sharing.cjs +5 -5
  127. package/dist/sharing.cjs.map +1 -1
  128. package/dist/sharing.js +1 -1
  129. package/dist/{token-CrJF76oH.d.ts → token-BZ8doMam.d.cts} +15 -2
  130. package/dist/{token-CrJF76oH.d.cts → token-BZ8doMam.d.ts} +15 -2
  131. package/package.json +1 -1
  132. package/dist/chunk-KKGC4ERF.js.map +0 -1
  133. package/dist/chunk-KMSRG5KH.js.map +0 -1
  134. package/dist/chunk-KYMEDV6E.js.map +0 -1
  135. package/dist/chunk-OIX2ANFS.js.map +0 -1
  136. package/dist/chunk-TNU6AWUO.js.map +0 -1
  137. package/dist/chunk-ULJL53DL.js.map +0 -1
  138. package/dist/chunk-VMCG3PJD.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { ShareControls, AgentCursor, AgentActivityHighlight, AgentPanel } from './chunk-5KLUYF4D.js';
2
- import { createSessionDescriptor, attachSseRelay } from './chunk-KMSRG5KH.js';
1
+ import { ShareControls, AgentCursor, AgentActivityHighlight, AgentPanel } from './chunk-HSI5RVF4.js';
2
+ import { createSessionDescriptor, attachSseRelay } from './chunk-N6RLSLOF.js';
3
3
  import { attachInProcess } from './chunk-AFUULW5E.js';
4
- import { registerWhiteboardBridge } from './chunk-OFCR6JY3.js';
4
+ import { registerWhiteboardBridge } from './chunk-2U5JB6ZA.js';
5
5
  import './chunk-CT3LRNH7.js';
6
- import './chunk-ULJL53DL.js';
6
+ import './chunk-2VENWGLZ.js';
7
7
  import './chunk-C3TYI5TJ.js';
8
8
  import { MicroMcpServer } from './chunk-DF3PLL2E.js';
9
9
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
@@ -1,6 +1,6 @@
1
1
  export { attachHeuristicsSink, mapActivityToEvent } from './chunk-7R6RMROL.js';
2
2
  import './chunk-UCKJAUBY.js';
3
- import './chunk-ULJL53DL.js';
3
+ import './chunk-2VENWGLZ.js';
4
4
  import './chunk-C3TYI5TJ.js';
5
5
  //# sourceMappingURL=heuristics.js.map
6
6
  //# sourceMappingURL=heuristics.js.map
package/dist/index.cjs CHANGED
@@ -321,7 +321,7 @@ function wrapToolWithActivity(handler, options) {
321
321
  target: { ...target, kind: target.kind ?? options.kind, screenId: target.screenId ?? options.screenId },
322
322
  action: options.toolName,
323
323
  timestamp: Date.now(),
324
- meta: extractMeta(result),
324
+ meta: options.buildMeta ? options.buildMeta(result) : extractMeta(result),
325
325
  ttlMs: options.ttlMs
326
326
  });
327
327
  return result;
@@ -543,6 +543,7 @@ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
543
543
  function registerFormBridge(host, options) {
544
544
  const { adapter } = options;
545
545
  const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
546
+ const pendingMode = options.pendingMode ?? true;
546
547
  const disposers = [];
547
548
  ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
548
549
  const formId = adapter.id;
@@ -671,11 +672,20 @@ function registerFormBridge(host, options) {
671
672
  );
672
673
  reg(
673
674
  "form_submit",
674
- "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.",
675
676
  {},
676
677
  [],
677
678
  async () => {
678
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
+ }
679
689
  const result = await adapter.submit();
680
690
  if (!result.ok) return errorResult(result.error ?? "Submit failed");
681
691
  return textResult("Submitted", { values: result.values });
@@ -1039,6 +1049,7 @@ function useSheetsActivityHighlights(options = {}) {
1039
1049
  }
1040
1050
 
1041
1051
  // src/bridges/code.ts
1052
+ var MAX_STREAM_CHARS = 1e4;
1042
1053
  var DEFAULT_AGENT3 = { id: "agent", name: "Agent", color: "#a855f7" };
1043
1054
  function registerCodeBridge(host, options) {
1044
1055
  const { adapter } = options;
@@ -1051,6 +1062,16 @@ function registerCodeBridge(host, options) {
1051
1062
  elementId: adapter.id,
1052
1063
  label: adapter.title ?? adapter.id
1053
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
+ };
1054
1075
  const reg = (name, description, properties, required, handler, isMutation) => {
1055
1076
  const wrapped = async (args) => {
1056
1077
  try {
@@ -1120,7 +1141,9 @@ function registerCodeBridge(host, options) {
1120
1141
  ["value"],
1121
1142
  (args) => {
1122
1143
  const value = String(args.value ?? "");
1144
+ const prev = adapter.getValue();
1123
1145
  adapter.setValue(value);
1146
+ recordUndo("code_set_value", "Replace document", prev, value);
1124
1147
  return textResult(`Replaced document (${value.length} chars)`, { length: value.length });
1125
1148
  },
1126
1149
  true
@@ -1132,8 +1155,10 @@ function registerCodeBridge(host, options) {
1132
1155
  ["text"],
1133
1156
  (args) => {
1134
1157
  const text = String(args.text ?? "");
1135
- const next = adapter.getValue() + text;
1158
+ const prev = adapter.getValue();
1159
+ const next = prev + text;
1136
1160
  adapter.setValue(next);
1161
+ recordUndo("code_append", `Append ${text.length} chars`, prev, next);
1137
1162
  return textResult(`Appended ${text.length} chars`, { length: next.length });
1138
1163
  },
1139
1164
  true
@@ -1148,6 +1173,11 @@ function registerCodeBridge(host, options) {
1148
1173
  ["text"],
1149
1174
  async (args) => {
1150
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
+ }
1151
1181
  const cps = Math.max(1, Number(args.cps ?? 25));
1152
1182
  const interval = Math.max(8, Math.round(1e3 / cps));
1153
1183
  const start = adapter.getValue();
@@ -1155,6 +1185,7 @@ function registerCodeBridge(host, options) {
1155
1185
  adapter.setValue(start + text.slice(0, i));
1156
1186
  if (i < text.length) await new Promise((r) => setTimeout(r, interval));
1157
1187
  }
1188
+ recordUndo("code_stream_append", `Append ${text.length} chars`, start, start + text);
1158
1189
  return textResult(`Streamed ${text.length} chars`, { length: text.length });
1159
1190
  },
1160
1191
  true
@@ -1166,7 +1197,9 @@ function registerCodeBridge(host, options) {
1166
1197
  ["text"],
1167
1198
  (args) => {
1168
1199
  if (!adapter.replaceSelection) return errorResult("Host did not provide replaceSelection.");
1200
+ const prev = adapter.getValue();
1169
1201
  adapter.replaceSelection(String(args.text ?? ""));
1202
+ recordUndo("code_replace_selection", "Replace selection", prev, adapter.getValue());
1170
1203
  return textResult("Selection replaced", {});
1171
1204
  },
1172
1205
  true
@@ -1179,7 +1212,18 @@ function registerCodeBridge(host, options) {
1179
1212
  (args) => {
1180
1213
  if (!adapter.setLanguage) return errorResult("Host did not provide setLanguage.");
1181
1214
  const lang = String(args.language ?? "");
1215
+ const prevLang = adapter.getLanguage?.();
1182
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
+ }
1183
1227
  return textResult(`Language \u2192 ${lang}`, { language: lang });
1184
1228
  },
1185
1229
  true
@@ -1194,7 +1238,8 @@ function registerCodeBridge(host, options) {
1194
1238
  adapter.focus();
1195
1239
  return textResult("Focused", {});
1196
1240
  },
1197
- true
1241
+ // Focus is not a document mutation — don't broadcast it as one or log undo.
1242
+ false
1198
1243
  );
1199
1244
  return {
1200
1245
  id: `code:${adapter.id}`,
@@ -2465,6 +2510,22 @@ function registerMapBridge(host, options) {
2465
2510
 
2466
2511
  // src/bridges/files.ts
2467
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
+ }
2468
2529
  var str3 = (v, fallback = "") => typeof v === "string" ? v : fallback;
2469
2530
  var num3 = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : 0;
2470
2531
  var asPaths = (v) => {
@@ -2480,6 +2541,10 @@ function registerFilesBridge(host, options) {
2480
2541
  const { adapter } = options;
2481
2542
  const agent = { ...DEFAULT_AGENT9, ...options.agent ?? {} };
2482
2543
  const disposers = [];
2544
+ const guard = (path) => {
2545
+ if (options.root !== void 0) assertPathWithinRoot(options.root, path);
2546
+ return path;
2547
+ };
2483
2548
  ensureUndoToolsRegistered(host, { defaultAgentId: agent.id });
2484
2549
  const fTarget = (args, _result) => ({
2485
2550
  kind: "files",
@@ -2535,7 +2600,7 @@ function registerFilesBridge(host, options) {
2535
2600
  { path: { type: "string", description: "Folder path to list; defaults to the current directory." } },
2536
2601
  [],
2537
2602
  async (args) => {
2538
- const path = args.path !== void 0 ? str3(args.path) : adapter.getPath();
2603
+ const path = guard(args.path !== void 0 ? str3(args.path) : adapter.getPath());
2539
2604
  const entries = await adapter.listChildren(path);
2540
2605
  const summary = entries.map((e) => ({
2541
2606
  path: e.path,
@@ -2554,7 +2619,7 @@ function registerFilesBridge(host, options) {
2554
2619
  { path: { type: "string" } },
2555
2620
  ["path"],
2556
2621
  (args) => {
2557
- const path = str3(args.path);
2622
+ const path = guard(str3(args.path));
2558
2623
  const prev = adapter.getPath();
2559
2624
  adapter.setPath(path);
2560
2625
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -2575,7 +2640,7 @@ function registerFilesBridge(host, options) {
2575
2640
  { path: { type: "string" } },
2576
2641
  ["path"],
2577
2642
  (args) => {
2578
- const path = str3(args.path);
2643
+ const path = guard(str3(args.path));
2579
2644
  if (adapter.getExpanded().includes(path)) {
2580
2645
  return textResult(`${path} already expanded`, { path, expanded: true });
2581
2646
  }
@@ -2598,7 +2663,7 @@ function registerFilesBridge(host, options) {
2598
2663
  { path: { type: "string" } },
2599
2664
  ["path"],
2600
2665
  (args) => {
2601
- const path = str3(args.path);
2666
+ const path = guard(str3(args.path));
2602
2667
  if (!adapter.getExpanded().includes(path)) {
2603
2668
  return textResult(`${path} already collapsed`, { path, expanded: false });
2604
2669
  }
@@ -2624,7 +2689,7 @@ function registerFilesBridge(host, options) {
2624
2689
  },
2625
2690
  [],
2626
2691
  (args) => {
2627
- 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);
2628
2693
  const prev = adapter.getSelection();
2629
2694
  adapter.setSelection(next);
2630
2695
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -2653,8 +2718,8 @@ function registerFilesBridge(host, options) {
2653
2718
  if (!adapter.requestSnapshot) {
2654
2719
  return errorResult("Host did not wire requestSnapshot (snapshot mode not enabled).");
2655
2720
  }
2656
- const path = args.path !== void 0 ? str3(args.path) : adapter.getPath();
2657
- 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;
2658
2723
  const tree = await adapter.requestSnapshot(path, depth);
2659
2724
  const count = Array.isArray(tree) ? tree.length : 0;
2660
2725
  return textResult(`Snapshot of ${path}: ${count} top-level entr${count === 1 ? "y" : "ies"}.`, {
@@ -2678,16 +2743,15 @@ var DEFAULT_AGENT10 = { id: "agent", name: "Agent", color: "#a855f7" };
2678
2743
  var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
2679
2744
  function registerTerminalBridge(host, options) {
2680
2745
  const agent = { ...DEFAULT_AGENT10, ...options.agent ?? {} };
2681
- const pendingMode = options.pendingMode ?? false;
2746
+ const pendingMode = options.pendingMode ?? true;
2682
2747
  const screenId = options.screenId ?? options.adapter?.screenId;
2683
2748
  const disposers = [];
2684
2749
  const staged = /* @__PURE__ */ new Map();
2685
2750
  let seq = 0;
2686
2751
  ensureUndoToolsRegistered(host);
2687
2752
  const listTerminals = () => {
2688
- if (options.terminals) return options.terminals();
2689
- if (options.adapter) return [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }];
2690
- 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;
2691
2755
  };
2692
2756
  const resolve = (id) => {
2693
2757
  const list = listTerminals();
@@ -2704,6 +2768,17 @@ function registerTerminalBridge(host, options) {
2704
2768
  elementId: terminalId ?? screenId ?? "terminal",
2705
2769
  label: label ?? "terminal"
2706
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
+ };
2707
2782
  const TERMINAL_ARG = {
2708
2783
  terminal: {
2709
2784
  type: "string",
@@ -2723,7 +2798,12 @@ function registerTerminalBridge(host, options) {
2723
2798
  agent,
2724
2799
  kind: "terminal",
2725
2800
  screenId,
2726
- 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
2727
2807
  }) : wrapped;
2728
2808
  disposers.push(
2729
2809
  host.registerTool(
@@ -2760,6 +2840,7 @@ function registerTerminalBridge(host, options) {
2760
2840
  return textResult(`${kind === "run" ? "ran" : "wrote"} on ${t.id}: ${truncate(data)}`, {
2761
2841
  kind,
2762
2842
  data,
2843
+ length: data.length,
2763
2844
  terminal: t.id,
2764
2845
  executed: true
2765
2846
  });
@@ -2770,7 +2851,7 @@ function registerTerminalBridge(host, options) {
2770
2851
  options.onPending?.(entry);
2771
2852
  return textResult(
2772
2853
  `Staged ${kind} on ${t.id} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2773
- { ...entry, pending: true }
2854
+ { ...entry, length: data.length, pending: true }
2774
2855
  );
2775
2856
  }
2776
2857
  reg(
@@ -2815,12 +2896,13 @@ function registerTerminalBridge(host, options) {
2815
2896
  );
2816
2897
  reg(
2817
2898
  "terminal_write",
2818
- "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.",
2819
2900
  { ...TERMINAL_ARG, data: { type: "string", description: "Raw bytes to write." } },
2820
2901
  ["data"],
2821
2902
  (args) => stageOrExec(need(args), "write", String(args.data)),
2822
2903
  true,
2823
- (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)
2824
2906
  );
2825
2907
  reg(
2826
2908
  "terminal_run",
@@ -2829,41 +2911,47 @@ function registerTerminalBridge(host, options) {
2829
2911
  ["command"],
2830
2912
  (args) => stageOrExec(need(args), "run", String(args.command)),
2831
2913
  true,
2832
- (args) => target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2833
- );
2834
- reg(
2835
- "terminal_confirm",
2836
- "Confirm + execute a staged command by id (pendingMode).",
2837
- { id: { type: "string" } },
2838
- ["id"],
2839
- async (args) => {
2840
- const id = String(args.id);
2841
- const entry = staged.get(id);
2842
- if (!entry) return errorResult(`No staged command ${id}`);
2843
- const t = resolve(entry.terminalId);
2844
- if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
2845
- staged.delete(id);
2846
- await exec(t, entry.kind, entry.data);
2847
- return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });
2848
- },
2849
- true,
2850
- (args) => {
2851
- const e = staged.get(String(args.id));
2852
- return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
2853
- }
2854
- );
2855
- reg(
2856
- "terminal_reject",
2857
- "Drop a staged command by id without executing it.",
2858
- { id: { type: "string" } },
2859
- ["id"],
2860
- (args) => {
2861
- const id = String(args.id);
2862
- if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
2863
- return textResult(`Rejected ${id}`, { id, rejected: true });
2864
- },
2865
- false
2914
+ (args, result) => result?.structuredContent?.pending ? null : target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2866
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
+ }
2867
2955
  if (canClear) {
2868
2956
  reg(
2869
2957
  "terminal_clear",
@@ -2908,8 +2996,10 @@ function registerTerminalBridge(host, options) {
2908
2996
  if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);
2909
2997
  const id = String(args.id);
2910
2998
  const shells = t.listShells?.();
2911
- if (shells && shells.length && !shells.some((s) => s.id === id)) {
2912
- 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
+ );
2913
3003
  }
2914
3004
  await t.setShell(id);
2915
3005
  return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
@@ -3050,6 +3140,12 @@ function registerNavigationBridge(host, options) {
3050
3140
  if (stale) return stale;
3051
3141
  const url = String(args.url ?? "");
3052
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
+ }
3053
3149
  const from = adapter.getLocation().url;
3054
3150
  await adapter.visit(url);
3055
3151
  fancyAutoCommon.pushUndoEntry(agent.id, {
@@ -4034,8 +4130,7 @@ function describeSession(id, token) {
4034
4130
  }
4035
4131
  function buildShareUrl(descriptor, baseUrl = typeof window !== "undefined" ? window.location.href.split("?")[0] : "") {
4036
4132
  const u = new URL(baseUrl);
4037
- u.searchParams.set("session", descriptor.id);
4038
- u.searchParams.set("token", descriptor.token);
4133
+ u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();
4039
4134
  return u.toString();
4040
4135
  }
4041
4136
  function buildShareConfig(descriptor, transport = "broadcast-channel") {
@@ -4050,9 +4145,10 @@ function buildShareConfig(descriptor, transport = "broadcast-channel") {
4050
4145
  }
4051
4146
  function readSessionFromUrl() {
4052
4147
  if (typeof window === "undefined") return null;
4053
- const params = new URL(window.location.href).searchParams;
4054
- const id = params.get("session");
4055
- 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");
4056
4152
  if (!id || !token) return null;
4057
4153
  return describeSession(id, token);
4058
4154
  }