@particle-academy/agent-integrations 0.3.4 → 0.5.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 (130) hide show
  1. package/README.md +65 -0
  2. package/dist/bridges/charts.d.cts +4 -4
  3. package/dist/bridges/charts.d.ts +4 -4
  4. package/dist/bridges/code.d.cts +4 -4
  5. package/dist/bridges/code.d.ts +4 -4
  6. package/dist/bridges/flow.d.cts +4 -4
  7. package/dist/bridges/flow.d.ts +4 -4
  8. package/dist/bridges/forms.d.cts +4 -4
  9. package/dist/bridges/forms.d.ts +4 -4
  10. package/dist/bridges/scene.d.cts +4 -4
  11. package/dist/bridges/scene.d.ts +4 -4
  12. package/dist/bridges/screens.d.cts +78 -0
  13. package/dist/bridges/screens.d.ts +78 -0
  14. package/dist/bridges/sheets.d.cts +4 -4
  15. package/dist/bridges/sheets.d.ts +4 -4
  16. package/dist/bridges/whiteboard.d.cts +4 -4
  17. package/dist/bridges/whiteboard.d.ts +4 -4
  18. package/dist/bridges-charts.cjs +2 -2
  19. package/dist/bridges-charts.cjs.map +1 -1
  20. package/dist/bridges-charts.js +2 -2
  21. package/dist/bridges-code.cjs +2 -2
  22. package/dist/bridges-code.cjs.map +1 -1
  23. package/dist/bridges-code.js +2 -2
  24. package/dist/bridges-flow.cjs +2 -2
  25. package/dist/bridges-flow.cjs.map +1 -1
  26. package/dist/bridges-flow.js +340 -3
  27. package/dist/bridges-flow.js.map +1 -1
  28. package/dist/bridges-forms.cjs +2 -2
  29. package/dist/bridges-forms.cjs.map +1 -1
  30. package/dist/bridges-forms.js +2 -2
  31. package/dist/bridges-scene.cjs +2 -2
  32. package/dist/bridges-scene.cjs.map +1 -1
  33. package/dist/bridges-scene.js +2 -2
  34. package/dist/bridges-screens.cjs +227 -0
  35. package/dist/bridges-screens.cjs.map +1 -0
  36. package/dist/bridges-screens.js +6 -0
  37. package/dist/bridges-screens.js.map +1 -0
  38. package/dist/bridges-sheets.cjs +2 -2
  39. package/dist/bridges-sheets.cjs.map +1 -1
  40. package/dist/bridges-sheets.js +2 -2
  41. package/dist/bridges-whiteboard.cjs +12 -12
  42. package/dist/bridges-whiteboard.cjs.map +1 -1
  43. package/dist/bridges-whiteboard.js +3 -3
  44. package/dist/{chunk-TBEITXF4.js → chunk-3KSZNGNW.js} +7 -7
  45. package/dist/chunk-3KSZNGNW.js.map +1 -0
  46. package/dist/{chunk-OEIULP2L.js → chunk-4BL5M3U3.js} +5 -5
  47. package/dist/chunk-4BL5M3U3.js.map +1 -0
  48. package/dist/{chunk-QGCF7YKW.js → chunk-4KAIV6OD.js} +40 -12
  49. package/dist/chunk-4KAIV6OD.js.map +1 -0
  50. package/dist/chunk-57ZDHD53.js +180 -0
  51. package/dist/chunk-57ZDHD53.js.map +1 -0
  52. package/dist/chunk-5XELJIJR.js +83 -0
  53. package/dist/chunk-5XELJIJR.js.map +1 -0
  54. package/dist/{chunk-6LTKCNLF.js → chunk-AFUULW5E.js} +3 -34
  55. package/dist/chunk-AFUULW5E.js.map +1 -0
  56. package/dist/chunk-G6N2TQVO.js +34 -0
  57. package/dist/chunk-G6N2TQVO.js.map +1 -0
  58. package/dist/{chunk-ACBENYYO.js → chunk-GQ7XXK7G.js} +12 -12
  59. package/dist/chunk-GQ7XXK7G.js.map +1 -0
  60. package/dist/{chunk-XYYSTJHW.js → chunk-HSTW7ZNO.js} +5 -5
  61. package/dist/chunk-HSTW7ZNO.js.map +1 -0
  62. package/dist/{chunk-PDBF4W7E.js → chunk-IANI25IT.js} +5 -5
  63. package/dist/chunk-IANI25IT.js.map +1 -0
  64. package/dist/chunk-IJ6JX5VC.js +3 -0
  65. package/dist/chunk-IJ6JX5VC.js.map +1 -0
  66. package/dist/{chunk-JMYPUAFH.js → chunk-LVQXIUJH.js} +2 -2
  67. package/dist/{chunk-JMYPUAFH.js.map → chunk-LVQXIUJH.js.map} +1 -1
  68. package/dist/{chunk-PHPXKSWI.js → chunk-NTDZWGYB.js} +5 -5
  69. package/dist/chunk-NTDZWGYB.js.map +1 -0
  70. package/dist/{chunk-DJOWMF6Q.js → chunk-RGO42EQ6.js} +3 -3
  71. package/dist/{chunk-DJOWMF6Q.js.map → chunk-RGO42EQ6.js.map} +1 -1
  72. package/dist/{chunk-QJUTISFC.js → chunk-XRAJSOPS.js} +5 -5
  73. package/dist/chunk-XRAJSOPS.js.map +1 -0
  74. package/dist/chunk-ZHAK2DQR.js +289 -0
  75. package/dist/chunk-ZHAK2DQR.js.map +1 -0
  76. package/dist/components/SharedWhiteboard/index.d.cts +55 -0
  77. package/dist/components/SharedWhiteboard/index.d.ts +55 -0
  78. package/dist/components-shared-whiteboard.cjs +1533 -0
  79. package/dist/components-shared-whiteboard.cjs.map +1 -0
  80. package/dist/components-shared-whiteboard.js +285 -0
  81. package/dist/components-shared-whiteboard.js.map +1 -0
  82. package/dist/index.cjs +618 -1371
  83. package/dist/index.cjs.map +1 -1
  84. package/dist/index.d.cts +11 -59
  85. package/dist/index.d.ts +11 -59
  86. package/dist/index.js +19 -571
  87. package/dist/index.js.map +1 -1
  88. package/dist/mcp/index.d.cts +5 -4
  89. package/dist/mcp/index.d.ts +5 -4
  90. package/dist/mcp.cjs +37 -9
  91. package/dist/mcp.cjs.map +1 -1
  92. package/dist/mcp.js +3 -2
  93. package/dist/presence/index.d.cts +1 -1
  94. package/dist/presence/index.d.ts +1 -1
  95. package/dist/{server-BJu_AMH3.d.ts → server-BsSwfemr.d.cts} +4 -5
  96. package/dist/{server-si-VvFxI.d.cts → server-Du3-IGqM.d.ts} +4 -5
  97. package/dist/sharing/index.d.cts +5 -36
  98. package/dist/sharing/index.d.ts +5 -36
  99. package/dist/sharing.js +2 -1
  100. package/dist/sheets-adapter.cjs +96 -0
  101. package/dist/sheets-adapter.cjs.map +1 -0
  102. package/dist/sheets-adapter.d.cts +119 -0
  103. package/dist/sheets-adapter.d.ts +119 -0
  104. package/dist/sheets-adapter.js +4 -0
  105. package/dist/sheets-adapter.js.map +1 -0
  106. package/dist/token-CrJF76oH.d.cts +34 -0
  107. package/dist/token-CrJF76oH.d.ts +34 -0
  108. package/dist/tool-host-BQuUygLF.d.cts +60 -0
  109. package/dist/tool-host-C8JMMGYq.d.ts +60 -0
  110. package/dist/{types-DXKpLuia.d.ts → types-CCSBGW9T.d.cts} +2 -2
  111. package/dist/{types-Bf1ZoGmI.d.cts → types-DIVNcIQO.d.ts} +2 -2
  112. package/dist/{types-DksGd5Y7.d.cts → types-aOQLTW0E.d.cts} +1 -1
  113. package/dist/{types-DksGd5Y7.d.ts → types-aOQLTW0E.d.ts} +1 -1
  114. package/dist/undo/index.d.cts +4 -4
  115. package/dist/undo/index.d.ts +4 -4
  116. package/dist/undo.cjs +9 -9
  117. package/dist/undo.cjs.map +1 -1
  118. package/dist/undo.js +3 -3
  119. package/package.json +57 -7
  120. package/dist/chunk-4IAVAFUV.js +0 -342
  121. package/dist/chunk-4IAVAFUV.js.map +0 -1
  122. package/dist/chunk-6LTKCNLF.js.map +0 -1
  123. package/dist/chunk-ACBENYYO.js.map +0 -1
  124. package/dist/chunk-OEIULP2L.js.map +0 -1
  125. package/dist/chunk-PDBF4W7E.js.map +0 -1
  126. package/dist/chunk-PHPXKSWI.js.map +0 -1
  127. package/dist/chunk-QGCF7YKW.js.map +0 -1
  128. package/dist/chunk-QJUTISFC.js.map +0 -1
  129. package/dist/chunk-TBEITXF4.js.map +0 -1
  130. package/dist/chunk-XYYSTJHW.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { M as MicroMcpServer } from '../server-BJu_AMH3.js';
2
- import '../types-DksGd5Y7.js';
1
+ import { T as ToolHost } from '../tool-host-C8JMMGYq.js';
2
+ import '../types-aOQLTW0E.js';
3
3
 
4
4
  /**
5
5
  * Generic undo/redo stack keyed by `agentId`. Each entry holds:
@@ -48,12 +48,12 @@ type UndoToolsOptions = {
48
48
  * ensureUndoToolsRegistered — bridges call this on construction. Safe to
49
49
  * call repeatedly with the same server; subsequent calls are no-ops.
50
50
  */
51
- declare function ensureUndoToolsRegistered(server: MicroMcpServer, options?: UndoToolsOptions): void;
51
+ declare function ensureUndoToolsRegistered(host: ToolHost, options?: UndoToolsOptions): void;
52
52
  /**
53
53
  * registerUndoTools — add agent_undo / agent_redo / agent_history to the
54
54
  * server. Returns a disposer that unregisters all three.
55
55
  */
56
- declare function registerUndoTools(server: MicroMcpServer, options?: UndoToolsOptions): () => void;
56
+ declare function registerUndoTools(host: ToolHost, options?: UndoToolsOptions): () => void;
57
57
 
58
58
  /**
59
59
  * useUndoStack — minimal React snapshot of an agent's history. Polls every
package/dist/undo.cjs CHANGED
@@ -57,18 +57,18 @@ function errorResult(text) {
57
57
  }
58
58
 
59
59
  // src/undo/undo-tools.ts
60
- var installedServers = /* @__PURE__ */ new WeakSet();
61
- function ensureUndoToolsRegistered(server, options = {}) {
62
- if (installedServers.has(server)) return;
63
- installedServers.add(server);
64
- registerUndoTools(server, options);
60
+ var installedHosts = /* @__PURE__ */ new WeakSet();
61
+ function ensureUndoToolsRegistered(host, options = {}) {
62
+ if (installedHosts.has(host)) return;
63
+ installedHosts.add(host);
64
+ registerUndoTools(host, options);
65
65
  }
66
- function registerUndoTools(server, options = {}) {
66
+ function registerUndoTools(host, options = {}) {
67
67
  const defaultAgent = options.defaultAgentId ?? "agent";
68
68
  const disposers = [];
69
69
  const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
70
70
  disposers.push(
71
- server.registerTool(
71
+ host.registerTool(
72
72
  {
73
73
  name: "agent_undo",
74
74
  description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
@@ -86,7 +86,7 @@ function registerUndoTools(server, options = {}) {
86
86
  )
87
87
  );
88
88
  disposers.push(
89
- server.registerTool(
89
+ host.registerTool(
90
90
  {
91
91
  name: "agent_redo",
92
92
  description: "Redo the most recently undone action.",
@@ -104,7 +104,7 @@ function registerUndoTools(server, options = {}) {
104
104
  )
105
105
  );
106
106
  disposers.push(
107
- server.registerTool(
107
+ host.registerTool(
108
108
  {
109
109
  name: "agent_history",
110
110
  description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
package/dist/undo.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/undo/undo-stack.ts","../src/mcp/server.ts","../src/undo/undo-tools.ts","../src/undo/use-undo-stack.ts"],"names":["useState","useEffect","useCallback"],"mappings":";;;;;AA8BA,IAAM,MAAA,uBAAa,GAAA,EAAmB;AACtC,IAAM,GAAA,GAAM,GAAA;AAEZ,SAAS,SAAS,OAAA,EAAwB;AACxC,EAAA,IAAI,CAAA,GAAI,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AAC1B,EAAA,IAAI,CAAC,CAAA,EAAG;AACN,IAAA,CAAA,GAAI,EAAE,IAAA,EAAM,EAAC,EAAG,MAAA,EAAQ,EAAC,EAAE;AAC3B,IAAA,MAAA,CAAO,GAAA,CAAI,SAAS,CAAC,CAAA;AAAA,EACvB;AACA,EAAA,OAAO,CAAA;AACT;AAGO,SAAS,aAAA,CAAc,SAAiB,KAAA,EAAwB;AACrE,EAAA,MAAM,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1B,EAAA,CAAA,CAAE,IAAA,CAAK,KAAK,KAAK,CAAA;AACjB,EAAA,IAAI,CAAA,CAAE,IAAA,CAAK,MAAA,GAAS,GAAA,EAAK,CAAA,CAAE,IAAA,CAAK,MAAA,CAAO,CAAA,EAAG,CAAA,CAAE,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AAC7D,EAAA,CAAA,CAAE,OAAO,MAAA,GAAS,CAAA;AACpB;AAGA,eAAsB,QAAQ,OAAA,EAA4C;AACxE,EAAA,MAAM,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,IAAA,CAAK,GAAA,EAAI;AACzB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,MAAM,IAAA,EAAK;AACjB,EAAA,CAAA,CAAE,MAAA,CAAO,KAAK,KAAK,CAAA;AACnB,EAAA,OAAO,KAAA;AACT;AAGA,eAAsB,QAAQ,OAAA,EAA4C;AACxE,EAAA,MAAM,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,MAAA,CAAO,GAAA,EAAI;AAC3B,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,MAAM,IAAA,EAAK;AACjB,EAAA,CAAA,CAAE,IAAA,CAAK,KAAK,KAAK,CAAA;AACjB,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,YAAY,OAAA,EAA8B;AACxD,EAAA,OAAO,QAAA,CAAS,OAAO,CAAA,CAAE,IAAA,CAAK,KAAA,EAAM;AACtC;AAGO,SAAS,WAAW,OAAA,EAAuB;AAChD,EAAA,MAAA,CAAO,OAAO,OAAO,CAAA;AACvB;AAGO,SAAS,kBAAA,GAA2B;AACzC,EAAA,MAAA,CAAO,KAAA,EAAM;AACf;;;ACkGO,SAAS,UAAA,CAAW,MAAc,UAAA,EAAkC;AACzE,EAAA,OAAO;AAAA,IACL,SAAS,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,CAAA;AAAA,IAChC,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,iBAAA,EAAmB,UAAA,KAAe;AAAC,GACtE;AACF;AAEO,SAAS,YAAY,IAAA,EAA8B;AACxD,EAAA,OAAO,EAAE,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,QAAQ,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,IAAA,EAAK;AAC5D;;;ACjLA,IAAM,gBAAA,uBAAuB,OAAA,EAAwB;AAM9C,SAAS,yBAAA,CAA0B,MAAA,EAAwB,OAAA,GAA4B,EAAC,EAAS;AACtG,EAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,MAAM,CAAA,EAAG;AAClC,EAAA,gBAAA,CAAiB,IAAI,MAAM,CAAA;AAC3B,EAAA,iBAAA,CAAkB,QAAQ,OAAO,CAAA;AACnC;AAMO,SAAS,iBAAA,CAAkB,MAAA,EAAwB,OAAA,GAA4B,EAAC,EAAe;AACpG,EAAA,MAAM,YAAA,GAAe,QAAQ,cAAA,IAAkB,OAAA;AAC/C,EAAA,MAAM,YAA+B,EAAC;AACtC,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KACf,OAAO,MAAM,OAAA,KAAY,QAAA,GAAW,KAAK,OAAA,GAAU,YAAA;AAErD,EAAA,SAAA,CAAU,IAAA;AAAA,IACR,MAAA,CAAO,YAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,YAAA;AAAA,QACN,WAAA,EAAa,8FAAA;AAAA,QACb,WAAA,EAAa;AAAA,UACX,IAAA,EAAM,QAAA;AAAA,UACN,YAAY,EAAE,OAAA,EAAS,EAAE,IAAA,EAAM,UAAS,EAAE;AAAA,UAC1C,oBAAA,EAAsB;AAAA;AACxB,OACF;AAAA,MACA,OAAO,IAAA,KAAS;AACd,QAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAC,CAAA;AACzC,QAAA,IAAI,CAAC,KAAA,EAAO,OAAO,WAAA,CAAY,kBAAkB,CAAA;AACjD,QAAA,OAAO,UAAA,CAAW,CAAA,OAAA,EAAU,KAAA,CAAM,KAAK,CAAA,CAAA,EAAI,EAAE,KAAA,EAAO,SAAA,CAAU,KAAK,CAAA,EAAG,CAAA;AAAA,MACxE;AAAA;AACF,GACF;AAEA,EAAA,SAAA,CAAU,IAAA;AAAA,IACR,MAAA,CAAO,YAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,YAAA;AAAA,QACN,WAAA,EAAa,uCAAA;AAAA,QACb,WAAA,EAAa;AAAA,UACX,IAAA,EAAM,QAAA;AAAA,UACN,YAAY,EAAE,OAAA,EAAS,EAAE,IAAA,EAAM,UAAS,EAAE;AAAA,UAC1C,oBAAA,EAAsB;AAAA;AACxB,OACF;AAAA,MACA,OAAO,IAAA,KAAS;AACd,QAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAC,CAAA;AACzC,QAAA,IAAI,CAAC,KAAA,EAAO,OAAO,WAAA,CAAY,kBAAkB,CAAA;AACjD,QAAA,OAAO,UAAA,CAAW,CAAA,OAAA,EAAU,KAAA,CAAM,KAAK,CAAA,CAAA,EAAI,EAAE,KAAA,EAAO,SAAA,CAAU,KAAK,CAAA,EAAG,CAAA;AAAA,MACxE;AAAA;AACF,GACF;AAEA,EAAA,SAAA,CAAU,IAAA;AAAA,IACR,MAAA,CAAO,YAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,eAAA;AAAA,QACN,WAAA,EAAa,yFAAA;AAAA,QACb,WAAA,EAAa;AAAA,UACX,IAAA,EAAM,QAAA;AAAA,UACN,YAAY,EAAE,OAAA,EAAS,EAAE,IAAA,EAAM,UAAS,EAAE;AAAA,UAC1C,oBAAA,EAAsB;AAAA;AACxB,OACF;AAAA,MACA,OAAO,IAAA,KAAS;AACd,QAAA,MAAM,UAAU,WAAA,CAAY,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAE,IAAI,SAAS,CAAA;AACxD,QAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,GAAG,IAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CAAE,WAAA,EAAa,CAAA,CAAA,EAAI,CAAA,CAAE,QAAQ,CAAA,CAAA,EAAI,CAAA,CAAE,MAAM,CAAA,EAAA,EAAK,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACzH,QAAA,OAAO,UAAA,CAAW,IAAA,IAAQ,SAAA,EAAW,OAAO,CAAA;AAAA,MAC9C;AAAA;AACF,GACF;AAEA,EAAA,OAAO,MAAM,SAAA,CAAU,OAAA,CAAQ,CAAC,CAAA,KAAM,GAAG,CAAA;AAC3C;AAEA,SAAS,UAAU,KAAA,EAAyC;AAC1D,EAAA,OAAO;AAAA,IACL,WAAW,KAAA,CAAM,SAAA;AAAA,IACjB,UAAU,KAAA,CAAM,QAAA;AAAA,IAChB,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,OAAO,KAAA,CAAM;AAAA,GACf;AACF;AC7FO,SAAS,YAAA,CAAa,OAAA,EAAiB,UAAA,GAAa,GAAA,EAAK;AAC9D,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAIA,eAAS,MAAM,WAAA,CAAY,OAAO,CAAC,CAAA;AAEjE,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,MAAM,OAAO,MAAM;AACjB,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,UAAA,CAAW,WAAA,CAAY,OAAO,CAAC,CAAA;AAAA,IACjC,CAAA;AACA,IAAA,MAAM,EAAA,GAAK,WAAA,CAAY,IAAA,EAAM,UAAU,CAAA;AACvC,IAAA,IAAA,EAAK;AACL,IAAA,OAAO,MAAM;AAAE,MAAA,SAAA,GAAY,IAAA;AAAM,MAAA,aAAA,CAAc,EAAE,CAAA;AAAA,IAAG,CAAA;AAAA,EACtD,CAAA,EAAG,CAAC,OAAA,EAAS,UAAU,CAAC,CAAA;AAExB,EAAA,MAAM,OAAA,GAAUC,iBAAA,CAAY,MAAM,UAAA,CAAW,WAAA,CAAY,OAAO,CAAC,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAC7E,EAAA,OAAO,EAAE,SAAS,OAAA,EAAQ;AAC5B","file":"undo.cjs","sourcesContent":["/**\n * Generic undo/redo stack keyed by `agentId`. Each entry holds:\n * - `do` — re-applies the action (for redo)\n * - `undo` — reverses it\n * - `label` — human-readable summary surfaced in agent_history\n *\n * Bridges register entries by calling `pushUndoEntry` after a successful\n * mutation. The corresponding MCP tools (`agent_undo`, `agent_redo`,\n * `agent_history`) are registered once per server via `registerUndoTools`.\n *\n * Stacks are per-agent so multiple agents can rewind independently.\n */\n\nexport type UndoEntry = {\n /** Wall-clock ms. */\n timestamp: number;\n /** Bridge id (e.g. \"whiteboard\", \"form:signup\"). */\n bridgeId: string;\n /** Tool name that produced the entry. */\n action: string;\n /** Short human label, e.g. `Added sticky n_abc`. */\n label: string;\n /** Reverse the action. */\n undo: () => void | Promise<void>;\n /** Re-apply the action (used when redoing after an undo). */\n redo: () => void | Promise<void>;\n};\n\ntype Stack = { past: UndoEntry[]; future: UndoEntry[] };\n\nconst stacks = new Map<string, Stack>();\nconst CAP = 200;\n\nfunction getStack(agentId: string): Stack {\n let s = stacks.get(agentId);\n if (!s) {\n s = { past: [], future: [] };\n stacks.set(agentId, s);\n }\n return s;\n}\n\n/** Push a new undo entry on the agent's stack. Clears the redo (future) stack. */\nexport function pushUndoEntry(agentId: string, entry: UndoEntry): void {\n const s = getStack(agentId);\n s.past.push(entry);\n if (s.past.length > CAP) s.past.splice(0, s.past.length - CAP);\n s.future.length = 0;\n}\n\n/** Pop and undo the most recent entry. Returns the entry that ran, or null. */\nexport async function undoOne(agentId: string): Promise<UndoEntry | null> {\n const s = getStack(agentId);\n const entry = s.past.pop();\n if (!entry) return null;\n await entry.undo();\n s.future.push(entry);\n return entry;\n}\n\n/** Re-apply the most recently undone entry. Returns it, or null if no future. */\nexport async function redoOne(agentId: string): Promise<UndoEntry | null> {\n const s = getStack(agentId);\n const entry = s.future.pop();\n if (!entry) return null;\n await entry.redo();\n s.past.push(entry);\n return entry;\n}\n\n/** Read the past stack (oldest first). */\nexport function readHistory(agentId: string): UndoEntry[] {\n return getStack(agentId).past.slice();\n}\n\n/** Wipe an agent's stacks. */\nexport function clearStack(agentId: string): void {\n stacks.delete(agentId);\n}\n\n/** Test/teardown helper. */\nexport function resetAllUndoStacks(): void {\n stacks.clear();\n}\n","import {\n type CallToolResult,\n type JsonObject,\n type JsonRpcMessage,\n type JsonRpcRequest,\n type JsonRpcId,\n type RegisteredTool,\n type ServerCapabilities,\n type ServerInfo,\n type ToolDefinition,\n type ToolHandler,\n JSONRPC_INTERNAL_ERROR,\n JSONRPC_INVALID_PARAMS,\n JSONRPC_METHOD_NOT_FOUND,\n MCP_PROTOCOL_VERSION,\n} from \"./types\";\n\nexport type McpServerOptions = {\n info: ServerInfo;\n /** Defaults to { tools: { listChanged: true } } */\n capabilities?: ServerCapabilities;\n /** Free-text instructions surfaced to clients during initialize. */\n instructions?: string;\n};\n\nexport type Transport = {\n /** Called by the server when it has a message to deliver to the client. */\n send: (message: JsonRpcMessage) => void;\n /** Called by the server when it's torn down so the transport can clean up. */\n close?: () => void;\n};\n\n/**\n * MicroMcpServer — protocol-level MCP server, transport-agnostic.\n *\n * Use it like:\n *\n * const server = new MicroMcpServer({ info: { name: \"session\", version: \"0.1\" } });\n * server.registerTool({ name: \"...\", inputSchema: { type: \"object\" } }, async (args) => ({...}));\n * const transport = new InProcessTransport();\n * server.attach(transport);\n * transport.deliver({ ... }); // client → server frames\n *\n * The same server can serve multiple transports (e.g. an in-process agent\n * AND a relayed external client) by attaching each one.\n */\nexport class MicroMcpServer {\n private tools = new Map<string, RegisteredTool>();\n private transports = new Set<Transport>();\n private notifyListChangedScheduled = false;\n\n readonly info: ServerInfo;\n readonly capabilities: ServerCapabilities;\n readonly instructions?: string;\n\n constructor(options: McpServerOptions) {\n this.info = options.info;\n this.capabilities = options.capabilities ?? { tools: { listChanged: true } };\n this.instructions = options.instructions;\n }\n\n attach(transport: Transport): () => void {\n this.transports.add(transport);\n return () => this.detach(transport);\n }\n\n detach(transport: Transport): void {\n if (this.transports.delete(transport)) {\n transport.close?.();\n }\n }\n\n registerTool(definition: ToolDefinition, handler: ToolHandler): () => void {\n this.tools.set(definition.name, { definition, handler });\n this.scheduleListChangedNotification();\n return () => this.unregisterTool(definition.name);\n }\n\n unregisterTool(name: string): void {\n if (this.tools.delete(name)) {\n this.scheduleListChangedNotification();\n }\n }\n\n listTools(): ToolDefinition[] {\n return Array.from(this.tools.values()).map((t) => t.definition);\n }\n\n /**\n * Receive a JSON-RPC frame from a client (called by the transport).\n * The transport is responsible for sending the response back.\n */\n async receive(transport: Transport, message: JsonRpcMessage): Promise<void> {\n if (!(\"method\" in message)) return; // It's a response, not a request — ignore.\n\n const isNotification = !(\"id\" in message);\n if (isNotification) {\n // Notifications are fire-and-forget. We ignore unknown methods.\n return;\n }\n\n const request = message as JsonRpcRequest;\n try {\n const result = await this.handle(request);\n transport.send({ jsonrpc: \"2.0\", id: request.id, result });\n } catch (err) {\n transport.send({\n jsonrpc: \"2.0\",\n id: request.id,\n error: this.toRpcError(err),\n });\n }\n }\n\n private async handle(request: JsonRpcRequest): Promise<any> {\n const { method, params } = request;\n switch (method) {\n case \"initialize\":\n return {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities: this.capabilities,\n serverInfo: this.info,\n ...(this.instructions ? { instructions: this.instructions } : {}),\n };\n\n case \"tools/list\":\n return { tools: this.listTools() };\n\n case \"tools/call\": {\n const name = params?.name;\n const args = (params?.arguments ?? {}) as JsonObject;\n if (typeof name !== \"string\") {\n throw rpcError(JSONRPC_INVALID_PARAMS, \"tools/call requires `name`\");\n }\n const tool = this.tools.get(name);\n if (!tool) {\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unknown tool: ${name}`);\n }\n const result = await tool.handler(args);\n return result satisfies CallToolResult;\n }\n\n case \"ping\":\n return {};\n\n default:\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);\n }\n }\n\n private scheduleListChangedNotification(): void {\n if (this.notifyListChangedScheduled) return;\n this.notifyListChangedScheduled = true;\n queueMicrotask(() => {\n this.notifyListChangedScheduled = false;\n this.broadcast({ jsonrpc: \"2.0\", method: \"notifications/tools/list_changed\" });\n });\n }\n\n private broadcast(message: JsonRpcMessage): void {\n for (const t of this.transports) t.send(message);\n }\n\n private toRpcError(err: unknown): { code: number; message: string; data?: any } {\n if (err && typeof err === \"object\" && \"code\" in err && \"message\" in err) {\n return err as any;\n }\n return {\n code: JSONRPC_INTERNAL_ERROR,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport function rpcError(code: number, message: string, data?: any) {\n return { code, message, ...(data !== undefined ? { data } : {}) };\n}\n\n/**\n * Helper to build a CallToolResult from a string or structured value.\n */\nexport function textResult(text: string, structured?: any): CallToolResult {\n return {\n content: [{ type: \"text\", text }],\n ...(structured !== undefined ? { structuredContent: structured } : {}),\n };\n}\n\nexport function errorResult(text: string): CallToolResult {\n return { content: [{ type: \"text\", text }], isError: true };\n}\n\n// Internal helper so the JsonRpcId import isn't dropped by tsup\ntype _KeepIdImport = JsonRpcId;\n","import { textResult, errorResult } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport { readHistory, redoOne, undoOne } from \"./undo-stack\";\n\nexport type UndoToolsOptions = {\n /** Default agent id when the caller doesn't pass one. */\n defaultAgentId?: string;\n};\n\n/**\n * Idempotent tracker so multiple bridges on the same server only register\n * agent_undo / agent_redo / agent_history once.\n */\nconst installedServers = new WeakSet<MicroMcpServer>();\n\n/**\n * ensureUndoToolsRegistered — bridges call this on construction. Safe to\n * call repeatedly with the same server; subsequent calls are no-ops.\n */\nexport function ensureUndoToolsRegistered(server: MicroMcpServer, options: UndoToolsOptions = {}): void {\n if (installedServers.has(server)) return;\n installedServers.add(server);\n registerUndoTools(server, options);\n}\n\n/**\n * registerUndoTools — add agent_undo / agent_redo / agent_history to the\n * server. Returns a disposer that unregisters all three.\n */\nexport function registerUndoTools(server: MicroMcpServer, options: UndoToolsOptions = {}): () => void {\n const defaultAgent = options.defaultAgentId ?? \"agent\";\n const disposers: Array<() => void> = [];\n const agentOf = (args: any): string =>\n typeof args?.agentId === \"string\" ? args.agentId : defaultAgent;\n\n disposers.push(\n server.registerTool(\n {\n name: \"agent_undo\",\n description: \"Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const entry = await undoOne(agentOf(args));\n if (!entry) return errorResult(\"Nothing to undo.\");\n return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });\n },\n ),\n );\n\n disposers.push(\n server.registerTool(\n {\n name: \"agent_redo\",\n description: \"Redo the most recently undone action.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const entry = await redoOne(agentOf(args));\n if (!entry) return errorResult(\"Nothing to redo.\");\n return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });\n },\n ),\n );\n\n disposers.push(\n server.registerTool(\n {\n name: \"agent_history\",\n description: \"List the agent's undo stack (oldest first). Useful for understanding what's reversible.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const history = readHistory(agentOf(args)).map(serialize);\n const text = history.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join(\"\\n\");\n return textResult(text || \"(empty)\", history);\n },\n ),\n );\n\n return () => disposers.forEach((d) => d());\n}\n\nfunction serialize(entry: import(\"./undo-stack\").UndoEntry) {\n return {\n timestamp: entry.timestamp,\n bridgeId: entry.bridgeId,\n action: entry.action,\n label: entry.label,\n };\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { readHistory } from \"./undo-stack\";\n\n/**\n * useUndoStack — minimal React snapshot of an agent's history. Polls every\n * `intervalMs` (default 500). Use this to render an inline \"agent timeline\"\n * in a sidebar or activity panel. No subscription model in v1 — keeping it\n * simple; bridge mutations are infrequent enough that polling is fine.\n */\nexport function useUndoStack(agentId: string, intervalMs = 500) {\n const [history, setHistory] = useState(() => readHistory(agentId));\n\n useEffect(() => {\n let cancelled = false;\n const tick = () => {\n if (cancelled) return;\n setHistory(readHistory(agentId));\n };\n const id = setInterval(tick, intervalMs);\n tick();\n return () => { cancelled = true; clearInterval(id); };\n }, [agentId, intervalMs]);\n\n const refresh = useCallback(() => setHistory(readHistory(agentId)), [agentId]);\n return { history, refresh };\n}\n"]}
1
+ {"version":3,"sources":["../src/undo/undo-stack.ts","../src/mcp/server.ts","../src/undo/undo-tools.ts","../src/undo/use-undo-stack.ts"],"names":["useState","useEffect","useCallback"],"mappings":"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","file":"undo.cjs","sourcesContent":["/**\n * Generic undo/redo stack keyed by `agentId`. Each entry holds:\n * - `do` — re-applies the action (for redo)\n * - `undo` — reverses it\n * - `label` — human-readable summary surfaced in agent_history\n *\n * Bridges register entries by calling `pushUndoEntry` after a successful\n * mutation. The corresponding MCP tools (`agent_undo`, `agent_redo`,\n * `agent_history`) are registered once per server via `registerUndoTools`.\n *\n * Stacks are per-agent so multiple agents can rewind independently.\n */\n\nexport type UndoEntry = {\n /** Wall-clock ms. */\n timestamp: number;\n /** Bridge id (e.g. \"whiteboard\", \"form:signup\"). */\n bridgeId: string;\n /** Tool name that produced the entry. */\n action: string;\n /** Short human label, e.g. `Added sticky n_abc`. */\n label: string;\n /** Reverse the action. */\n undo: () => void | Promise<void>;\n /** Re-apply the action (used when redoing after an undo). */\n redo: () => void | Promise<void>;\n};\n\ntype Stack = { past: UndoEntry[]; future: UndoEntry[] };\n\nconst stacks = new Map<string, Stack>();\nconst CAP = 200;\n\nfunction getStack(agentId: string): Stack {\n let s = stacks.get(agentId);\n if (!s) {\n s = { past: [], future: [] };\n stacks.set(agentId, s);\n }\n return s;\n}\n\n/** Push a new undo entry on the agent's stack. Clears the redo (future) stack. */\nexport function pushUndoEntry(agentId: string, entry: UndoEntry): void {\n const s = getStack(agentId);\n s.past.push(entry);\n if (s.past.length > CAP) s.past.splice(0, s.past.length - CAP);\n s.future.length = 0;\n}\n\n/** Pop and undo the most recent entry. Returns the entry that ran, or null. */\nexport async function undoOne(agentId: string): Promise<UndoEntry | null> {\n const s = getStack(agentId);\n const entry = s.past.pop();\n if (!entry) return null;\n await entry.undo();\n s.future.push(entry);\n return entry;\n}\n\n/** Re-apply the most recently undone entry. Returns it, or null if no future. */\nexport async function redoOne(agentId: string): Promise<UndoEntry | null> {\n const s = getStack(agentId);\n const entry = s.future.pop();\n if (!entry) return null;\n await entry.redo();\n s.past.push(entry);\n return entry;\n}\n\n/** Read the past stack (oldest first). */\nexport function readHistory(agentId: string): UndoEntry[] {\n return getStack(agentId).past.slice();\n}\n\n/** Wipe an agent's stacks. */\nexport function clearStack(agentId: string): void {\n stacks.delete(agentId);\n}\n\n/** Test/teardown helper. */\nexport function resetAllUndoStacks(): void {\n stacks.clear();\n}\n","import {\n type CallToolResult,\n type JsonObject,\n type JsonRpcMessage,\n type JsonRpcRequest,\n type JsonRpcId,\n type RegisteredTool,\n type ServerCapabilities,\n type ServerInfo,\n type ToolDefinition,\n type ToolHandler,\n JSONRPC_INTERNAL_ERROR,\n JSONRPC_INVALID_PARAMS,\n JSONRPC_METHOD_NOT_FOUND,\n MCP_PROTOCOL_VERSION,\n} from \"./types\";\nimport { ToolRegistry } from \"./tool-host\";\n\nexport type McpServerOptions = {\n info: ServerInfo;\n /** Defaults to { tools: { listChanged: true } } */\n capabilities?: ServerCapabilities;\n /** Free-text instructions surfaced to clients during initialize. */\n instructions?: string;\n};\n\nexport type Transport = {\n /** Called by the server when it has a message to deliver to the client. */\n send: (message: JsonRpcMessage) => void;\n /** Called by the server when it's torn down so the transport can clean up. */\n close?: () => void;\n};\n\n/**\n * MicroMcpServer — protocol-level MCP server, transport-agnostic.\n *\n * Use it like:\n *\n * const server = new MicroMcpServer({ info: { name: \"session\", version: \"0.1\" } });\n * server.registerTool({ name: \"...\", inputSchema: { type: \"object\" } }, async (args) => ({...}));\n * const transport = new InProcessTransport();\n * server.attach(transport);\n * transport.deliver({ ... }); // client → server frames\n *\n * The same server can serve multiple transports (e.g. an in-process agent\n * AND a relayed external client) by attaching each one.\n */\nexport class MicroMcpServer extends ToolRegistry {\n private transports = new Set<Transport>();\n private notifyListChangedScheduled = false;\n\n readonly info: ServerInfo;\n readonly capabilities: ServerCapabilities;\n readonly instructions?: string;\n\n constructor(options: McpServerOptions) {\n super();\n this.info = options.info;\n this.capabilities = options.capabilities ?? { tools: { listChanged: true } };\n this.instructions = options.instructions;\n }\n\n attach(transport: Transport): () => void {\n this.transports.add(transport);\n return () => this.detach(transport);\n }\n\n detach(transport: Transport): void {\n if (this.transports.delete(transport)) {\n transport.close?.();\n }\n }\n\n unregisterTool(name: string): void {\n if (this.tools.delete(name)) {\n this.scheduleListChangedNotification();\n }\n }\n\n protected onToolsChanged(): void {\n this.scheduleListChangedNotification();\n }\n\n /**\n * Receive a JSON-RPC frame from a client (called by the transport).\n * The transport is responsible for sending the response back.\n */\n async receive(transport: Transport, message: JsonRpcMessage): Promise<void> {\n if (!(\"method\" in message)) return; // It's a response, not a request — ignore.\n\n const isNotification = !(\"id\" in message);\n if (isNotification) {\n // Notifications are fire-and-forget. We ignore unknown methods.\n return;\n }\n\n const request = message as JsonRpcRequest;\n try {\n const result = await this.handle(request);\n transport.send({ jsonrpc: \"2.0\", id: request.id, result });\n } catch (err) {\n transport.send({\n jsonrpc: \"2.0\",\n id: request.id,\n error: this.toRpcError(err),\n });\n }\n }\n\n private async handle(request: JsonRpcRequest): Promise<any> {\n const { method, params } = request;\n switch (method) {\n case \"initialize\":\n return {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities: this.capabilities,\n serverInfo: this.info,\n ...(this.instructions ? { instructions: this.instructions } : {}),\n };\n\n case \"tools/list\":\n return { tools: this.listTools() };\n\n case \"tools/call\": {\n const name = params?.name;\n const args = (params?.arguments ?? {}) as JsonObject;\n if (typeof name !== \"string\") {\n throw rpcError(JSONRPC_INVALID_PARAMS, \"tools/call requires `name`\");\n }\n const tool = this.tools.get(name);\n if (!tool) {\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unknown tool: ${name}`);\n }\n const result = await tool.handler(args);\n return result satisfies CallToolResult;\n }\n\n case \"ping\":\n return {};\n\n default:\n throw rpcError(JSONRPC_METHOD_NOT_FOUND, `Unsupported method: ${method}`);\n }\n }\n\n private scheduleListChangedNotification(): void {\n if (this.notifyListChangedScheduled) return;\n this.notifyListChangedScheduled = true;\n queueMicrotask(() => {\n this.notifyListChangedScheduled = false;\n this.broadcast({ jsonrpc: \"2.0\", method: \"notifications/tools/list_changed\" });\n });\n }\n\n private broadcast(message: JsonRpcMessage): void {\n for (const t of this.transports) t.send(message);\n }\n\n private toRpcError(err: unknown): { code: number; message: string; data?: any } {\n if (err && typeof err === \"object\" && \"code\" in err && \"message\" in err) {\n return err as any;\n }\n return {\n code: JSONRPC_INTERNAL_ERROR,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport function rpcError(code: number, message: string, data?: any) {\n return { code, message, ...(data !== undefined ? { data } : {}) };\n}\n\n/**\n * Helper to build a CallToolResult from a string or structured value.\n */\nexport function textResult(text: string, structured?: any): CallToolResult {\n return {\n content: [{ type: \"text\", text }],\n ...(structured !== undefined ? { structuredContent: structured } : {}),\n };\n}\n\nexport function errorResult(text: string): CallToolResult {\n return { content: [{ type: \"text\", text }], isError: true };\n}\n\n// Internal helper so the JsonRpcId import isn't dropped by tsup\ntype _KeepIdImport = JsonRpcId;\n","import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport { readHistory, redoOne, undoOne } from \"./undo-stack\";\n\nexport type UndoToolsOptions = {\n /** Default agent id when the caller doesn't pass one. */\n defaultAgentId?: string;\n};\n\n/**\n * Idempotent tracker so multiple bridges on the same server only register\n * agent_undo / agent_redo / agent_history once.\n */\nconst installedHosts = new WeakSet<ToolHost>();\n\n/**\n * ensureUndoToolsRegistered — bridges call this on construction. Safe to\n * call repeatedly with the same server; subsequent calls are no-ops.\n */\nexport function ensureUndoToolsRegistered(host: ToolHost, options: UndoToolsOptions = {}): void {\n if (installedHosts.has(host)) return;\n installedHosts.add(host);\n registerUndoTools(host, options);\n}\n\n/**\n * registerUndoTools — add agent_undo / agent_redo / agent_history to the\n * server. Returns a disposer that unregisters all three.\n */\nexport function registerUndoTools(host: ToolHost, options: UndoToolsOptions = {}): () => void {\n const defaultAgent = options.defaultAgentId ?? \"agent\";\n const disposers: Array<() => void> = [];\n const agentOf = (args: any): string =>\n typeof args?.agentId === \"string\" ? args.agentId : defaultAgent;\n\n disposers.push(\n host.registerTool(\n {\n name: \"agent_undo\",\n description: \"Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const entry = await undoOne(agentOf(args));\n if (!entry) return errorResult(\"Nothing to undo.\");\n return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });\n },\n ),\n );\n\n disposers.push(\n host.registerTool(\n {\n name: \"agent_redo\",\n description: \"Redo the most recently undone action.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const entry = await redoOne(agentOf(args));\n if (!entry) return errorResult(\"Nothing to redo.\");\n return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });\n },\n ),\n );\n\n disposers.push(\n host.registerTool(\n {\n name: \"agent_history\",\n description: \"List the agent's undo stack (oldest first). Useful for understanding what's reversible.\",\n inputSchema: {\n type: \"object\",\n properties: { agentId: { type: \"string\" } },\n additionalProperties: false,\n },\n },\n async (args) => {\n const history = readHistory(agentOf(args)).map(serialize);\n const text = history.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join(\"\\n\");\n return textResult(text || \"(empty)\", history);\n },\n ),\n );\n\n return () => disposers.forEach((d) => d());\n}\n\nfunction serialize(entry: import(\"./undo-stack\").UndoEntry) {\n return {\n timestamp: entry.timestamp,\n bridgeId: entry.bridgeId,\n action: entry.action,\n label: entry.label,\n };\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport { readHistory } from \"./undo-stack\";\n\n/**\n * useUndoStack — minimal React snapshot of an agent's history. Polls every\n * `intervalMs` (default 500). Use this to render an inline \"agent timeline\"\n * in a sidebar or activity panel. No subscription model in v1 — keeping it\n * simple; bridge mutations are infrequent enough that polling is fine.\n */\nexport function useUndoStack(agentId: string, intervalMs = 500) {\n const [history, setHistory] = useState(() => readHistory(agentId));\n\n useEffect(() => {\n let cancelled = false;\n const tick = () => {\n if (cancelled) return;\n setHistory(readHistory(agentId));\n };\n const id = setInterval(tick, intervalMs);\n tick();\n return () => { cancelled = true; clearInterval(id); };\n }, [agentId, intervalMs]);\n\n const refresh = useCallback(() => setHistory(readHistory(agentId)), [agentId]);\n return { history, refresh };\n}\n"]}
package/dist/undo.js CHANGED
@@ -1,5 +1,5 @@
1
- export { useUndoStack } from './chunk-DJOWMF6Q.js';
2
- export { clearStack, ensureUndoToolsRegistered, pushUndoEntry, readHistory, redoOne, registerUndoTools, resetAllUndoStacks, undoOne } from './chunk-ACBENYYO.js';
3
- import './chunk-QGCF7YKW.js';
1
+ export { useUndoStack } from './chunk-RGO42EQ6.js';
2
+ export { clearStack, ensureUndoToolsRegistered, pushUndoEntry, readHistory, redoOne, registerUndoTools, resetAllUndoStacks, undoOne } from './chunk-GQ7XXK7G.js';
3
+ import './chunk-4KAIV6OD.js';
4
4
  //# sourceMappingURL=undo.js.map
5
5
  //# sourceMappingURL=undo.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/agent-integrations",
3
- "version": "0.3.4",
3
+ "version": "0.5.0",
4
4
  "description": "MCP-driven agent presence in collab sessions: per-session micro-MCP server, pluggable bridges to fancy-* packages, and agent UX components (panel + on-canvas cursor).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,12 +18,60 @@
18
18
  "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
19
19
  },
20
20
  "./mcp": {
21
- "import": { "types": "./dist/mcp.d.ts", "default": "./dist/mcp.js" },
22
- "require": { "types": "./dist/mcp.d.cts", "default": "./dist/mcp.cjs" }
21
+ "import": { "types": "./dist/mcp/index.d.ts", "default": "./dist/mcp.js" },
22
+ "require": { "types": "./dist/mcp/index.d.cts", "default": "./dist/mcp.cjs" }
23
23
  },
24
24
  "./bridges/whiteboard": {
25
- "import": { "types": "./dist/bridges-whiteboard.d.ts", "default": "./dist/bridges-whiteboard.js" },
26
- "require": { "types": "./dist/bridges-whiteboard.d.cts", "default": "./dist/bridges-whiteboard.cjs" }
25
+ "import": { "types": "./dist/bridges/whiteboard.d.ts", "default": "./dist/bridges-whiteboard.js" },
26
+ "require": { "types": "./dist/bridges/whiteboard.d.cts", "default": "./dist/bridges-whiteboard.cjs" }
27
+ },
28
+ "./bridges/flow": {
29
+ "import": { "types": "./dist/bridges/flow.d.ts", "default": "./dist/bridges-flow.js" },
30
+ "require": { "types": "./dist/bridges/flow.d.cts", "default": "./dist/bridges-flow.cjs" }
31
+ },
32
+ "./bridges/forms": {
33
+ "import": { "types": "./dist/bridges/forms.d.ts", "default": "./dist/bridges-forms.js" },
34
+ "require": { "types": "./dist/bridges/forms.d.cts", "default": "./dist/bridges-forms.cjs" }
35
+ },
36
+ "./bridges/sheets": {
37
+ "import": { "types": "./dist/bridges/sheets.d.ts", "default": "./dist/bridges-sheets.js" },
38
+ "require": { "types": "./dist/bridges/sheets.d.cts", "default": "./dist/bridges-sheets.cjs" }
39
+ },
40
+ "./bridges/code": {
41
+ "import": { "types": "./dist/bridges/code.d.ts", "default": "./dist/bridges-code.js" },
42
+ "require": { "types": "./dist/bridges/code.d.cts", "default": "./dist/bridges-code.cjs" }
43
+ },
44
+ "./bridges/charts": {
45
+ "import": { "types": "./dist/bridges/charts.d.ts", "default": "./dist/bridges-charts.js" },
46
+ "require": { "types": "./dist/bridges/charts.d.cts", "default": "./dist/bridges-charts.cjs" }
47
+ },
48
+ "./bridges/scene": {
49
+ "import": { "types": "./dist/bridges/scene.d.ts", "default": "./dist/bridges-scene.js" },
50
+ "require": { "types": "./dist/bridges/scene.d.cts", "default": "./dist/bridges-scene.cjs" }
51
+ },
52
+ "./bridges/screens": {
53
+ "import": { "types": "./dist/bridges/screens.d.ts", "default": "./dist/bridges-screens.js" },
54
+ "require": { "types": "./dist/bridges/screens.d.cts", "default": "./dist/bridges-screens.cjs" }
55
+ },
56
+ "./sheets-adapter": {
57
+ "import": { "types": "./dist/sheets-adapter.d.ts", "default": "./dist/sheets-adapter.js" },
58
+ "require": { "types": "./dist/sheets-adapter.d.cts", "default": "./dist/sheets-adapter.cjs" }
59
+ },
60
+ "./components/shared-whiteboard": {
61
+ "import": { "types": "./dist/components/SharedWhiteboard/index.d.ts", "default": "./dist/components-shared-whiteboard.js" },
62
+ "require": { "types": "./dist/components/SharedWhiteboard/index.d.cts", "default": "./dist/components-shared-whiteboard.cjs" }
63
+ },
64
+ "./presence": {
65
+ "import": { "types": "./dist/presence/index.d.ts", "default": "./dist/presence.js" },
66
+ "require": { "types": "./dist/presence/index.d.cts", "default": "./dist/presence.cjs" }
67
+ },
68
+ "./undo": {
69
+ "import": { "types": "./dist/undo/index.d.ts", "default": "./dist/undo.js" },
70
+ "require": { "types": "./dist/undo/index.d.cts", "default": "./dist/undo.cjs" }
71
+ },
72
+ "./sharing": {
73
+ "import": { "types": "./dist/sharing/index.d.ts", "default": "./dist/sharing.js" },
74
+ "require": { "types": "./dist/sharing/index.d.cts", "default": "./dist/sharing.cjs" }
27
75
  },
28
76
  "./styles.css": "./dist/styles.css"
29
77
  },
@@ -40,11 +88,13 @@
40
88
  "react": "^18.0.0 || ^19.0.0",
41
89
  "react-dom": "^18.0.0 || ^19.0.0",
42
90
  "@particle-academy/fancy-whiteboard": "^0.1.0",
43
- "@particle-academy/fancy-flow": "^0.2.0"
91
+ "@particle-academy/fancy-flow": "^0.2.0",
92
+ "@particle-academy/fancy-sheets": "^0.1.0"
44
93
  },
45
94
  "peerDependenciesMeta": {
46
95
  "@particle-academy/fancy-whiteboard": { "optional": true },
47
- "@particle-academy/fancy-flow": { "optional": true }
96
+ "@particle-academy/fancy-flow": { "optional": true },
97
+ "@particle-academy/fancy-sheets": { "optional": true }
48
98
  },
49
99
  "devDependencies": {
50
100
  "@particle-academy/fancy-whiteboard": "^0.1.5",
@@ -1,342 +0,0 @@
1
- import { wrapToolWithActivity } from './chunk-52S7XYZK.js';
2
- import { errorResult, textResult } from './chunk-QGCF7YKW.js';
3
-
4
- // src/bridges/flow.ts
5
- var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
6
- var num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : 0;
7
- var str = (v, fallback = "") => typeof v === "string" ? v : fallback;
8
- var newId = (prefix) => `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
9
- function registerFlowBridge(server, options) {
10
- const { adapter } = options;
11
- const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
12
- const disposers = [];
13
- const flTarget = (args, result) => ({
14
- kind: "flow",
15
- elementId: result?.structuredContent?.id ?? args?.id
16
- });
17
- const reg = (name, description, properties, required, handler, resolveTarget) => {
18
- const wrapped = async (args) => {
19
- try {
20
- return await handler(args);
21
- } catch (e) {
22
- return errorResult(e instanceof Error ? e.message : String(e));
23
- }
24
- };
25
- const final = resolveTarget ? wrapToolWithActivity(wrapped, {
26
- toolName: name,
27
- agent: { id: agent.id, name: agent.name, color: agent.color },
28
- kind: "flow",
29
- resolveTarget: ({ args, result }) => resolveTarget(args, result)
30
- }) : wrapped;
31
- disposers.push(
32
- server.registerTool(
33
- {
34
- name,
35
- description,
36
- inputSchema: { type: "object", properties, required, additionalProperties: false }
37
- },
38
- final
39
- )
40
- );
41
- };
42
- reg("flow_get_state", "Get the full graph: nodes + edges.", {}, [], () => {
43
- const state = { nodes: adapter.getNodes(), edges: adapter.getEdges() };
44
- return textResult(JSON.stringify(state, null, 2), state);
45
- });
46
- reg("flow_list_nodes", "Summarise every node: id, kind, label, position, status.", {}, [], () => {
47
- const items = adapter.getNodes().map((n) => ({
48
- id: n.id,
49
- kind: n.type,
50
- label: n.data?.label,
51
- x: Math.round(n.position.x),
52
- y: Math.round(n.position.y),
53
- status: n.data?.status ?? "idle"
54
- }));
55
- const text = items.map((i) => `${i.kind} ${i.id}: "${i.label}" @(${i.x},${i.y}) [${i.status}]`).join("\n") || "(empty graph)";
56
- return textResult(text, items);
57
- });
58
- reg(
59
- "flow_get_node",
60
- "Get a single node's full record by id.",
61
- { id: { type: "string" } },
62
- ["id"],
63
- (args) => {
64
- const id = str(args.id);
65
- const node = adapter.getNodes().find((n) => n.id === id);
66
- if (!node) return errorResult(`No node with id ${id}`);
67
- return textResult(JSON.stringify(node, null, 2), node);
68
- }
69
- );
70
- reg(
71
- "flow_list_node_kinds",
72
- "List every node kind registered in fancy-flow's registry. Use this to discover what's authorable before adding nodes.",
73
- { category: { type: "string", description: "Optional category filter: trigger | logic | data | ai | io | human | output | custom." } },
74
- [],
75
- async () => {
76
- try {
77
- const { listNodeKinds } = await import('@particle-academy/fancy-flow');
78
- const cat = adapter ? void 0 : void 0;
79
- const all = (cat ? listNodeKinds(cat) : listNodeKinds()).map((k) => ({
80
- name: k.name,
81
- category: k.category,
82
- label: k.label,
83
- description: k.description,
84
- icon: k.icon,
85
- accent: k.accent,
86
- inputs: k.inputs ?? [],
87
- outputs: k.outputs ?? [],
88
- configFields: (k.configSchema ?? []).map((f) => ({ key: f.key, type: f.type, label: f.label, required: !!f.required }))
89
- }));
90
- const text = all.map((k) => `${k.category}/${k.name}: ${k.label}${k.description ? " \u2014 " + k.description : ""}`).join("\n");
91
- return textResult(text || "(no kinds registered)", all);
92
- } catch (e) {
93
- return errorResult(`fancy-flow registry not available: ${e instanceof Error ? e.message : String(e)}`);
94
- }
95
- }
96
- );
97
- reg(
98
- "flow_get_node_schema",
99
- "Get the full configSchema + ports for a node kind. Use to know exactly what fields a kind accepts before calling flow_add_node.",
100
- { name: { type: "string" } },
101
- ["name"],
102
- async (args) => {
103
- try {
104
- const { getNodeKind } = await import('@particle-academy/fancy-flow');
105
- const k = getNodeKind(str(args.name));
106
- if (!k) return errorResult(`No kind registered: ${args.name}`);
107
- const summary = {
108
- name: k.name,
109
- category: k.category,
110
- label: k.label,
111
- description: k.description,
112
- inputs: k.inputs ?? [],
113
- outputs: k.outputs ?? [],
114
- configSchema: k.configSchema ?? [],
115
- defaultConfig: k.defaultConfig ?? null
116
- };
117
- return textResult(JSON.stringify(summary, null, 2), summary);
118
- } catch (e) {
119
- return errorResult(`fancy-flow registry not available: ${e instanceof Error ? e.message : String(e)}`);
120
- }
121
- }
122
- );
123
- reg("flow_list_edges", "Summarise every edge.", {}, [], () => {
124
- const items = adapter.getEdges().map((e) => ({
125
- id: e.id,
126
- from: `${e.source}${e.sourceHandle ? `:${e.sourceHandle}` : ""}`,
127
- to: `${e.target}${e.targetHandle ? `:${e.targetHandle}` : ""}`
128
- }));
129
- return textResult(items.map((i) => `${i.id}: ${i.from} \u2192 ${i.to}`).join("\n") || "(no edges)", items);
130
- });
131
- reg(
132
- "flow_add_node",
133
- "Add a node of any kind registered in fancy-flow's registry. Call flow_list_node_kinds first to discover what's available.",
134
- {
135
- kind: { type: "string", description: "Registry kind name (e.g. memory_store, llm_call, branch)." },
136
- label: { type: "string" },
137
- x: { type: "number" },
138
- y: { type: "number" },
139
- description: { type: "string" },
140
- config: { type: "object", description: "Config fields per the kind's configSchema." },
141
- body: { type: "string", description: "Note kinds only \u2014 body text." }
142
- },
143
- ["kind", "label", "x", "y"],
144
- async (args) => {
145
- const kindName = str(args.kind);
146
- let kindDef = null;
147
- try {
148
- const { getNodeKind, defaultConfigFor } = await import('@particle-academy/fancy-flow');
149
- kindDef = getNodeKind(kindName);
150
- var defaults = kindDef ? defaultConfigFor(kindDef) : {};
151
- } catch {
152
- var defaults = {};
153
- }
154
- const isLegacy = ["trigger", "action", "decision", "output", "note", "subgraph"].includes(kindName);
155
- if (!kindDef && !isLegacy) {
156
- return errorResult(`Unknown kind: ${kindName} \u2014 call flow_list_node_kinds for the registry.`);
157
- }
158
- const id = newId("n");
159
- const config = { ...defaults, ...args.config && typeof args.config === "object" ? args.config : {} };
160
- const node = {
161
- id,
162
- type: kindName,
163
- position: { x: num(args.x), y: num(args.y) },
164
- data: {
165
- kind: kindName,
166
- label: str(args.label),
167
- ...args.description ? { description: str(args.description) } : {},
168
- config,
169
- ...kindName === "note" && args.body ? { body: str(args.body) } : {}
170
- }
171
- };
172
- adapter.setNodes((all) => [...all, node]);
173
- return textResult(`Added ${kindName} ${id} ("${str(args.label)}")`, node);
174
- },
175
- flTarget
176
- );
177
- reg(
178
- "flow_update_node",
179
- "Update fields on a node. Only provided fields change.",
180
- {
181
- id: { type: "string" },
182
- label: { type: "string" },
183
- x: { type: "number" },
184
- y: { type: "number" },
185
- description: { type: "string" },
186
- config: { type: "object" }
187
- },
188
- ["id"],
189
- (args) => {
190
- const id = str(args.id);
191
- let updated = null;
192
- adapter.setNodes(
193
- (all) => all.map((n) => {
194
- if (n.id !== id) return n;
195
- updated = {
196
- ...n,
197
- position: {
198
- x: args.x !== void 0 ? num(args.x) : n.position.x,
199
- y: args.y !== void 0 ? num(args.y) : n.position.y
200
- },
201
- data: {
202
- ...n.data,
203
- ...args.label !== void 0 ? { label: str(args.label) } : {},
204
- ...args.description !== void 0 ? { description: str(args.description) } : {},
205
- ...args.config && typeof args.config === "object" ? { config: { ...n.data.config ?? {}, ...args.config } } : {}
206
- }
207
- };
208
- return updated;
209
- })
210
- );
211
- if (!updated) return errorResult(`No node with id ${id}`);
212
- return textResult(`Updated node ${id}`, updated);
213
- },
214
- flTarget
215
- );
216
- reg(
217
- "flow_delete_node",
218
- "Remove a node by id (also removes any connected edges).",
219
- { id: { type: "string" } },
220
- ["id"],
221
- (args) => {
222
- const id = str(args.id);
223
- if (!adapter.getNodes().some((n) => n.id === id)) {
224
- return errorResult(`No node with id ${id}`);
225
- }
226
- adapter.setNodes((all) => all.filter((n) => n.id !== id));
227
- adapter.setEdges((all) => all.filter((e) => e.source !== id && e.target !== id));
228
- return textResult(`Deleted node ${id}`);
229
- },
230
- flTarget
231
- );
232
- reg(
233
- "flow_connect",
234
- "Create an edge between two nodes (optionally specifying handle ids).",
235
- {
236
- source: { type: "string" },
237
- target: { type: "string" },
238
- sourceHandle: { type: "string" },
239
- targetHandle: { type: "string" },
240
- label: { type: "string" }
241
- },
242
- ["source", "target"],
243
- (args) => {
244
- const source = str(args.source);
245
- const target = str(args.target);
246
- const all = adapter.getNodes();
247
- if (!all.find((n) => n.id === source)) return errorResult(`No source node ${source}`);
248
- if (!all.find((n) => n.id === target)) return errorResult(`No target node ${target}`);
249
- const edge = {
250
- id: newId("e"),
251
- source,
252
- target,
253
- ...args.sourceHandle ? { sourceHandle: str(args.sourceHandle) } : {},
254
- ...args.targetHandle ? { targetHandle: str(args.targetHandle) } : {},
255
- ...args.label ? { label: str(args.label) } : {}
256
- };
257
- adapter.setEdges((existing) => [...existing, edge]);
258
- return textResult(`Connected ${source}${edge.sourceHandle ? `:${edge.sourceHandle}` : ""} \u2192 ${target}${edge.targetHandle ? `:${edge.targetHandle}` : ""}`, edge);
259
- },
260
- flTarget
261
- );
262
- reg(
263
- "flow_disconnect",
264
- "Remove an edge by id.",
265
- { id: { type: "string" } },
266
- ["id"],
267
- (args) => {
268
- const id = str(args.id);
269
- if (!adapter.getEdges().some((e) => e.id === id)) {
270
- return errorResult(`No edge ${id}`);
271
- }
272
- adapter.setEdges((all) => all.filter((e) => e.id !== id));
273
- return textResult(`Disconnected ${id}`);
274
- },
275
- flTarget
276
- );
277
- reg(
278
- "flow_set_node_status",
279
- "Manually set a node's status badge (idle | queued | running | done | error) and optional text. Useful for narration outside a run.",
280
- {
281
- id: { type: "string" },
282
- status: { type: "string", enum: ["idle", "queued", "running", "done", "error"] },
283
- text: { type: "string" }
284
- },
285
- ["id", "status"],
286
- (args) => {
287
- const id = str(args.id);
288
- const status = str(args.status);
289
- const text = args.text !== void 0 ? str(args.text) : void 0;
290
- if (adapter.setNodeStatus) {
291
- adapter.setNodeStatus(id, status, text);
292
- } else {
293
- let found = false;
294
- adapter.setNodes(
295
- (all) => all.map((n) => {
296
- if (n.id !== id) return n;
297
- found = true;
298
- return { ...n, data: { ...n.data, status, statusText: text } };
299
- })
300
- );
301
- if (!found) return errorResult(`No node with id ${id}`);
302
- }
303
- return textResult(`${id} \u2192 ${status}${text ? ` (${text})` : ""}`);
304
- },
305
- flTarget
306
- );
307
- reg(
308
- "flow_run",
309
- "Trigger a run of the current graph. Returns the topological result. Requires the host to have wired `run` into the adapter.",
310
- {},
311
- [],
312
- async () => {
313
- if (!adapter.run) return errorResult("Host did not provide a run handler.");
314
- const result = await adapter.run();
315
- return textResult(result.ok ? "Run complete" : `Run failed: ${result.error ?? "unknown"}`, result);
316
- },
317
- flTarget
318
- );
319
- reg(
320
- "flow_cancel",
321
- "Cancel an in-flight run.",
322
- {},
323
- [],
324
- () => {
325
- if (!adapter.cancel) return errorResult("Host did not provide a cancel handler.");
326
- adapter.cancel();
327
- return textResult("Run cancelled");
328
- },
329
- flTarget
330
- );
331
- return {
332
- id: "flow",
333
- title: "Flow",
334
- dispose: () => {
335
- for (const d of disposers) d();
336
- }
337
- };
338
- }
339
-
340
- export { registerFlowBridge };
341
- //# sourceMappingURL=chunk-4IAVAFUV.js.map
342
- //# sourceMappingURL=chunk-4IAVAFUV.js.map