@jxsuite/studio 8.1.0 → 9.0.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 (247) hide show
  1. package/dist/chunks/{2020-dg097k3e.js → 2020-89fz3pfy.js} +3 -3
  2. package/dist/chunks/adapter-ay13gema.js +4 -0
  3. package/dist/chunks/add-new-flow-nmkqpjym.js +4 -0
  4. package/dist/chunks/cf-account-picker-2e06qxey.js +4 -0
  5. package/dist/chunks/cf-settings-5y04810c.js +4 -0
  6. package/dist/chunks/color-04361ttk.js +4 -0
  7. package/dist/chunks/{color-area-vh9cew51.js → color-area-h8dbasp1.js} +2 -2
  8. package/dist/chunks/{color-field-e78f16bs.js → color-field-qv5r2b16.js} +3 -3
  9. package/dist/chunks/{color-slider-fg4yhvk6.js → color-slider-nz2mrzky.js} +2 -2
  10. package/dist/chunks/components-3vf75n83.js +4 -0
  11. package/dist/chunks/content-source-4f2raf4a.js +4 -0
  12. package/dist/chunks/{convert-to-repeater-grnmea6c.js → convert-to-repeater-j9hfcj4f.js} +3 -3
  13. package/dist/chunks/editor-645krvtz.js +4 -0
  14. package/dist/chunks/entry-commands-3qmv8wkr.js +4 -0
  15. package/dist/chunks/{extension-sections-rp7fkega.js → extension-sections-thfke054.js} +3 -3
  16. package/dist/chunks/file-ops-kkhp9ma7.js +4 -0
  17. package/dist/chunks/files-yswdhxrf.js +4 -0
  18. package/dist/chunks/format-host-4g8ex6bz.js +4 -0
  19. package/dist/chunks/fs-events-z9gpvc6t.js +4 -0
  20. package/dist/chunks/grid-open-fm4j183c.js +4 -0
  21. package/dist/chunks/{grid-view-x26rfpz1.js → grid-view-7d2nx83y.js} +10 -10
  22. package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
  23. package/dist/chunks/{iframe-entry-02wzcyvy.js → iframe-entry-btck0e9s.js} +4 -4
  24. package/dist/chunks/iframe-host-q7fedsmr.js +4 -0
  25. package/dist/chunks/{index-203h71wy.js → index-jxmt7vh8.js} +3 -3
  26. package/dist/chunks/index-pt4wh98x.js +4 -0
  27. package/dist/chunks/{javascript-ww5pv5ae.js → javascript-vmqp56ke.js} +2 -2
  28. package/dist/chunks/{jsonMode-vwbdwwq3.js → jsonMode-n3wmpant.js} +6 -6
  29. package/dist/chunks/jx-validate-19y62knt.js +4 -0
  30. package/dist/chunks/layers-panel-1qvc83y9.js +4 -0
  31. package/dist/chunks/layers-qf0bc7ze.js +4 -0
  32. package/dist/chunks/lit-html-wyqw04y2.js +4 -0
  33. package/dist/chunks/media-library-f9d5qcjb.js +4 -0
  34. package/dist/chunks/media-picker-rsb1fmk2.js +4 -0
  35. package/dist/chunks/{monaco-binding-63tqze27.js → monaco-binding-wfhmz89d.js} +3 -3
  36. package/dist/chunks/monaco-buffer-ak0raadw.js +4 -0
  37. package/dist/chunks/{monaco-setup-zyyynnj8.js → monaco-setup-fty7a8vg.js} +3 -3
  38. package/dist/chunks/notify-4qj70q0x.js +4 -0
  39. package/dist/chunks/parse-n8dfxnqf.js +4 -0
  40. package/dist/chunks/platform-ehg6t9vd.js +4 -0
  41. package/dist/chunks/publish-panel-yhmda3cd.js +4 -0
  42. package/dist/chunks/{publish-quick-ajzdhhs6.js → publish-quick-12hh8926.js} +3 -3
  43. package/dist/chunks/publish-workspace-hx8qdv9p.js +4 -0
  44. package/dist/chunks/quick-search-r955m3q1.js +4 -0
  45. package/dist/chunks/references-8gxv0x7t.js +4 -0
  46. package/dist/chunks/right-panel-jarwqjpy.js +4 -0
  47. package/dist/chunks/source-control-84qt25m9.js +4 -0
  48. package/dist/chunks/studio-0nefxhar.js +7 -0
  49. package/dist/chunks/studio-0rg30pvs.js +5 -0
  50. package/dist/chunks/{studio-3q6n9qvq.js → studio-14hh60j8.js} +4 -4
  51. package/dist/chunks/studio-1cfyd1ys.js +5 -0
  52. package/dist/chunks/studio-2bq7hheq.js +5 -0
  53. package/dist/chunks/{studio-mmqr9awv.js → studio-32d8wyxq.js} +33 -33
  54. package/dist/chunks/studio-3hpcawh8.js +4 -0
  55. package/dist/chunks/{studio-zm7dnr9g.js → studio-4j35hgsw.js} +4 -4
  56. package/dist/chunks/{studio-bqgs90r8.js → studio-4qg61g9j.js} +4 -4
  57. package/dist/chunks/studio-4rgem12q.js +5 -0
  58. package/dist/chunks/studio-6k9djnxw.js +5 -0
  59. package/dist/chunks/studio-6rbaedw2.js +9 -0
  60. package/dist/chunks/studio-732mw0g8.js +5 -0
  61. package/dist/chunks/studio-79r8msnr.js +7 -0
  62. package/dist/chunks/studio-7q6jdd4h.js +5 -0
  63. package/dist/chunks/{studio-jqrg9xxg.js → studio-881z1xrx.js} +4 -4
  64. package/dist/chunks/{studio-9wyqyfqy.js → studio-8hf8qcm1.js} +4 -4
  65. package/dist/chunks/{studio-6vrxzkba.js → studio-8pne0rx4.js} +4 -4
  66. package/dist/chunks/{studio-wcakp5bt.js → studio-9179bsm7.js} +7 -7
  67. package/dist/chunks/{studio-xddgbwhe.js → studio-9ff0vqff.js} +3 -3
  68. package/dist/chunks/{studio-25a5by3h.js → studio-9gzvypw4.js} +4 -4
  69. package/dist/chunks/studio-9ndgd55w.js +5 -0
  70. package/dist/chunks/studio-9ra9e7m4.js +5 -0
  71. package/dist/chunks/studio-a6e5fzve.js +5 -0
  72. package/dist/chunks/studio-a9k86nq6.js +5 -0
  73. package/dist/chunks/studio-ae7a69jc.js +6 -0
  74. package/dist/chunks/studio-aghsg0ds.js +7 -0
  75. package/dist/chunks/{studio-2b8443ms.js → studio-ajw4k8hp.js} +3 -3
  76. package/dist/chunks/{studio-957jg8g3.js → studio-apapy17g.js} +4 -4
  77. package/dist/chunks/{studio-hnx1h1pr.js → studio-by8x0jvc.js} +4 -4
  78. package/dist/chunks/{studio-hxq1ebp0.js → studio-c88gq88j.js} +6 -6
  79. package/dist/chunks/{studio-a6x058ew.js → studio-cg54vtet.js} +4 -4
  80. package/dist/chunks/studio-dvhxxxbd.js +5 -0
  81. package/dist/chunks/{studio-1meqbxns.js → studio-e4rq6x0q.js} +4 -4
  82. package/dist/chunks/{studio-v2ek69cb.js → studio-ekzbgnjp.js} +4 -4
  83. package/dist/chunks/{studio-2dtc90zs.js → studio-fg4fj9ec.js} +6 -6
  84. package/dist/chunks/studio-fj6jzgxw.js +5 -0
  85. package/dist/chunks/{studio-txkx6586.js → studio-g45r5vm8.js} +7 -7
  86. package/dist/chunks/{studio-v0gsjav0.js → studio-gczve8zf.js} +4 -4
  87. package/dist/chunks/studio-ge08zwpv.js +5 -0
  88. package/dist/chunks/{studio-1gdfsr49.js → studio-gk9yk8dj.js} +7 -7
  89. package/dist/chunks/{studio-cpdzrdgp.js → studio-gskkgfe3.js} +4 -4
  90. package/dist/chunks/{studio-94ppxeqy.js → studio-h4jbgne2.js} +8 -8
  91. package/dist/chunks/{studio-kdseew9m.js → studio-hcy9zeac.js} +4 -4
  92. package/dist/chunks/studio-ht81xjkr.js +5 -0
  93. package/dist/chunks/{studio-73wz63nn.js → studio-jr30g4ae.js} +4 -4
  94. package/dist/chunks/{studio-fqs1a22a.js → studio-k5n4es74.js} +4 -4
  95. package/dist/chunks/{studio-j5jqvyn3.js → studio-k6hbx2fc.js} +4 -4
  96. package/dist/chunks/{studio-332hesdk.js → studio-kqh7fxn6.js} +4 -4
  97. package/dist/chunks/{studio-8nyw10dp.js → studio-kyw47pet.js} +4 -4
  98. package/dist/chunks/studio-mkw17ngg.js +5 -0
  99. package/dist/chunks/{studio-02phtmqn.js → studio-myb4mf0h.js} +4 -4
  100. package/dist/chunks/{studio-d140sy8e.js → studio-n4qq1n57.js} +4 -4
  101. package/dist/chunks/{studio-bf0rgyf7.js → studio-nfmpmkqb.js} +4 -4
  102. package/dist/chunks/studio-nfwacw31.js +5 -0
  103. package/dist/chunks/{studio-z42kysg7.js → studio-nm86874b.js} +136 -136
  104. package/dist/chunks/studio-nqexty8x.js +16 -0
  105. package/dist/chunks/studio-nr0kcqc6.js +5 -0
  106. package/dist/chunks/{studio-dmn7gpba.js → studio-nvfa137z.js} +5 -5
  107. package/dist/chunks/studio-psh8mwnw.js +4 -0
  108. package/dist/chunks/{studio-4dsdnan2.js → studio-qd0cycm4.js} +5 -5
  109. package/dist/chunks/{studio-zht0x0v3.js → studio-qevf5z9d.js} +4 -4
  110. package/dist/chunks/studio-qs5fmhtj.js +14 -0
  111. package/dist/chunks/studio-ry4789ng.js +7 -0
  112. package/dist/chunks/studio-s4fq9jht.js +5 -0
  113. package/dist/chunks/studio-sc35ttdk.js +5 -0
  114. package/dist/chunks/studio-sm2f0sc6.js +6 -0
  115. package/dist/chunks/{studio-rdnezzkd.js → studio-spza69tb.js} +5 -5
  116. package/dist/chunks/studio-t6dqnnft.js +5 -0
  117. package/dist/chunks/{studio-hw60n5dh.js → studio-tfmhnwyr.js} +3 -3
  118. package/dist/chunks/studio-tn7v8gey.js +6 -0
  119. package/dist/chunks/{studio-nsv7g1qn.js → studio-trcfer2b.js} +4 -4
  120. package/dist/chunks/studio-tyw7tk0x.js +5 -0
  121. package/dist/chunks/{studio-533py3yy.js → studio-tyzgte21.js} +4 -4
  122. package/dist/chunks/studio-vtey9w98.js +5 -0
  123. package/dist/chunks/studio-ws2r1d7e.js +5 -0
  124. package/dist/chunks/studio-x2trkhk5.js +5 -0
  125. package/dist/chunks/{studio-c5dj3dzk.js → studio-xzgp0cp7.js} +4 -4
  126. package/dist/chunks/studio-y47090vq.js +5 -0
  127. package/dist/chunks/{studio-nm9qev4p.js → studio-yjh9gddd.js} +5 -5
  128. package/dist/chunks/studio-ywf9y1e2.js +5 -0
  129. package/dist/chunks/{studio-x3ge4rts.js → studio-z1p91z5a.js} +4 -4
  130. package/dist/chunks/studio-ztppbam0.js +5 -0
  131. package/dist/chunks/{toast-host-1yg5ar8d.js → toast-host-g3r2s0kn.js} +2 -2
  132. package/dist/chunks/toast-w11qpkyz.js +4 -0
  133. package/dist/chunks/{tsMode-vra2hh7y.js → tsMode-byje4a01.js} +5 -5
  134. package/dist/chunks/workspace-7q4arnt3.js +4 -0
  135. package/dist/chunks/workspace-home-ge027kr6.js +4 -0
  136. package/dist/chunks/{ws-client-72x5n99j.js → ws-client-h09h6sqg.js} +3 -3
  137. package/dist/iframe-entry.js +13 -13
  138. package/dist/manifest.json +77 -77
  139. package/dist/studio.js +89 -89
  140. package/package.json +11 -12
  141. package/src/collab/collab-session.ts +6 -2
  142. package/src/commands/context.ts +4 -0
  143. package/src/commands/defaults.ts +2 -0
  144. package/src/commands/live-context.ts +2 -0
  145. package/src/files/files.ts +6 -3
  146. package/src/new-project/new-project-modal.ts +1 -4
  147. package/src/panels/ai-panel.ts +27 -34
  148. package/src/platform.ts +20 -0
  149. package/src/platforms/devserver.ts +2 -120
  150. package/src/services/ai-ask.ts +29 -171
  151. package/src/services/ai-command-tools.ts +40 -50
  152. package/src/services/ai-import-tools.ts +4 -5
  153. package/src/services/ai-project-tools.ts +13 -3
  154. package/src/services/ai-tools.ts +26 -564
  155. package/src/services/ai-writes.ts +84 -2
  156. package/src/services/document-assistant.ts +29 -58
  157. package/src/services/harness/assistant-tools.ts +68 -0
  158. package/src/services/harness/interaction-store.ts +118 -0
  159. package/src/services/harness/studio-documents.ts +105 -0
  160. package/src/services/harness/turn-hooks.ts +1 -6
  161. package/src/services/project-adoption.ts +8 -16
  162. package/src/services/tool-executor.ts +25 -22
  163. package/src/state.ts +3 -13
  164. package/src/surfaces/files-panel.json +12 -0
  165. package/src/surfaces/files-panel.ts +5 -2
  166. package/src/surfaces/welcome.ts +4 -1
  167. package/src/tabs/selection.ts +14 -109
  168. package/src/tabs/tab.ts +10 -1
  169. package/src/tabs/transact.ts +316 -124
  170. package/src/types.ts +13 -8
  171. package/dist/chunks/adapter-tck81zft.js +0 -4
  172. package/dist/chunks/add-new-flow-a1w0bnm6.js +0 -4
  173. package/dist/chunks/cf-account-picker-ce3kp3x1.js +0 -4
  174. package/dist/chunks/cf-settings-7ye6x7g2.js +0 -4
  175. package/dist/chunks/color-4gj49xq6.js +0 -4
  176. package/dist/chunks/components-qce0v93f.js +0 -4
  177. package/dist/chunks/content-source-jjx8kgzr.js +0 -4
  178. package/dist/chunks/editor-bkja5dbv.js +0 -4
  179. package/dist/chunks/entry-commands-1wh0btvg.js +0 -4
  180. package/dist/chunks/file-ops-6pe4z7an.js +0 -4
  181. package/dist/chunks/files-ybpp3j38.js +0 -4
  182. package/dist/chunks/format-host-3mtbhxk1.js +0 -4
  183. package/dist/chunks/fs-events-xfanfxds.js +0 -4
  184. package/dist/chunks/grid-open-dg5qmnaj.js +0 -4
  185. package/dist/chunks/iframe-host-j2bk2xkc.js +0 -4
  186. package/dist/chunks/index-fkrm7pdk.js +0 -4
  187. package/dist/chunks/jx-validate-w7v32f6a.js +0 -4
  188. package/dist/chunks/layers-g7j732cm.js +0 -4
  189. package/dist/chunks/layers-panel-gjrsxcck.js +0 -4
  190. package/dist/chunks/lit-html-jdfz9rhc.js +0 -4
  191. package/dist/chunks/media-library-9j0v4wmk.js +0 -4
  192. package/dist/chunks/media-picker-cpyyt2m0.js +0 -4
  193. package/dist/chunks/monaco-buffer-9wcme176.js +0 -4
  194. package/dist/chunks/notify-rahnnnv4.js +0 -4
  195. package/dist/chunks/parse-v77xtwv7.js +0 -4
  196. package/dist/chunks/platform-b2aqv7v5.js +0 -4
  197. package/dist/chunks/publish-panel-4yk2461b.js +0 -4
  198. package/dist/chunks/publish-workspace-mxestzcf.js +0 -4
  199. package/dist/chunks/quick-search-9g6z65b0.js +0 -4
  200. package/dist/chunks/references-c26enpxs.js +0 -4
  201. package/dist/chunks/right-panel-9d0e0yg7.js +0 -4
  202. package/dist/chunks/source-control-xyn3a8ds.js +0 -4
  203. package/dist/chunks/studio-0khd0xdw.js +0 -5
  204. package/dist/chunks/studio-1fvzq9vs.js +0 -5
  205. package/dist/chunks/studio-1yhz0s8p.js +0 -5
  206. package/dist/chunks/studio-2c2mjyzj.js +0 -6
  207. package/dist/chunks/studio-3pndhat0.js +0 -9
  208. package/dist/chunks/studio-4fjqdzrb.js +0 -6
  209. package/dist/chunks/studio-5abge3wy.js +0 -5
  210. package/dist/chunks/studio-74nzv280.js +0 -5
  211. package/dist/chunks/studio-7cg75gtj.js +0 -14
  212. package/dist/chunks/studio-89cwmnae.js +0 -5
  213. package/dist/chunks/studio-8m1t83kn.js +0 -7
  214. package/dist/chunks/studio-a3evp7f6.js +0 -6
  215. package/dist/chunks/studio-ana67wax.js +0 -5
  216. package/dist/chunks/studio-b4xhpcwy.js +0 -4
  217. package/dist/chunks/studio-c4rcaftm.js +0 -5
  218. package/dist/chunks/studio-cgjt1zce.js +0 -5
  219. package/dist/chunks/studio-cq87hree.js +0 -5
  220. package/dist/chunks/studio-e1g64q43.js +0 -5
  221. package/dist/chunks/studio-e8pzjjhf.js +0 -5
  222. package/dist/chunks/studio-f8kz3krj.js +0 -7
  223. package/dist/chunks/studio-fw3jp9m7.js +0 -5
  224. package/dist/chunks/studio-g6ysm05p.js +0 -7
  225. package/dist/chunks/studio-gmhd0j3f.js +0 -5
  226. package/dist/chunks/studio-gqpty626.js +0 -5
  227. package/dist/chunks/studio-grp684wx.js +0 -5
  228. package/dist/chunks/studio-h98520qr.js +0 -5
  229. package/dist/chunks/studio-hvxt8z79.js +0 -5
  230. package/dist/chunks/studio-j4e627w1.js +0 -5
  231. package/dist/chunks/studio-kt2j51sa.js +0 -5
  232. package/dist/chunks/studio-m4w6sygj.js +0 -16
  233. package/dist/chunks/studio-nqxqf8p2.js +0 -4
  234. package/dist/chunks/studio-p89w12s2.js +0 -7
  235. package/dist/chunks/studio-pbagym4j.js +0 -5
  236. package/dist/chunks/studio-pr23qhrw.js +0 -5
  237. package/dist/chunks/studio-r4ya3sb2.js +0 -5
  238. package/dist/chunks/studio-rm9m3z60.js +0 -5
  239. package/dist/chunks/studio-tpmnbgmt.js +0 -5
  240. package/dist/chunks/studio-ttxhq5yt.js +0 -5
  241. package/dist/chunks/studio-y1x65h5r.js +0 -5
  242. package/dist/chunks/studio-zf188hnd.js +0 -5
  243. package/dist/chunks/toast-1gv0h53h.js +0 -4
  244. package/dist/chunks/workspace-3nt93k48.js +0 -4
  245. package/dist/chunks/workspace-home-zbgyqqws.js +0 -4
  246. package/src/services/directory-picker.ts +0 -187
  247. package/src/services/gated-registry.ts +0 -77
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Ai-ask.ts — the assistant's one way to stop and put a question to the author.
3
3
  *
4
- * The agent loop `await`s `toolRegistry.execute` (`services/tool-executor.ts`), so a tool that
5
- * returns a pending promise suspends the turn. That is the whole mechanism: `ask_user` registers a
6
- * question, hands back a promise, and the panel resolves it when the human answers. No new chat
7
- * status, no pre-execution hook, and nothing in the published `@jxsuite/ai` changes —
8
- * `finishStream` has already set the state to `"idle"` by the time tools run, so a suspended turn
9
- * is idle by construction and this store is what "waiting" means.
4
+ * `ask_user` declares what it asks (`interaction`) and what its answer means (`settle`), and has no
5
+ * body that waits: the invoker answers `needs_input` before any tool code runs, the turn engine
6
+ * puts the question to the assistant's interaction store (`harness/interaction-store.ts`), and the
7
+ * person's answer comes back through `settle` as the call's result (specs/ai.md §3.4 and §3.11).
10
8
  *
11
9
  * Two decisions are load-bearing and easy to get wrong on a rewrite:
12
10
  *
@@ -17,29 +15,16 @@
17
15
  * between them is a 400. The answer travels as the tool result, and the chat view renders it into
18
16
  * the question's own chip.
19
17
  *
20
- * Exactly one question can be outstanding, because `runAgentLoop` executes a round's tool calls
21
- * serially. The guard below is defensive, not load-bearing.
22
- *
23
18
  * @license MIT
24
19
  */
25
20
 
21
+ import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
26
22
  import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
27
23
  import { isString } from "@jxsuite/schema/guards";
28
- import { reactive } from "../reactivity";
29
-
30
- import type { ToolRegistry } from "@jxsuite/ai/tools";
31
24
 
32
- /** A question the assistant is waiting on. `id` is the tool-call id, so the chip and this agree. */
33
- export interface PendingAsk {
34
- id: string;
35
- question: string;
36
- /** Short choices rendered as buttons. Free text is always allowed alongside them. */
37
- options: string[];
38
- /** One line of why-I'm-asking, rendered under the question. */
39
- context: string;
40
- }
25
+ import type { InteractionResponse, ToolRegistry } from "@jxsuite/ai/tools";
41
26
 
42
- /** How a question was settled. `answer: null` with `skipped` is the author saying "you decide". */
27
+ /** What a settled question's result carries: the answer, or `null` with `skipped` for "you decide". */
43
28
  export interface AskAnswer {
44
29
  answer: string | null;
45
30
  skipped: boolean;
@@ -48,112 +33,6 @@ export interface AskAnswer {
48
33
  /** Max choices rendered as buttons; beyond this the question wants free text, not a menu. */
49
34
  const MAX_OPTIONS = 6;
50
35
 
51
- const state = reactive<{ pending: PendingAsk | null }>({ pending: null });
52
-
53
- /** The resolver for {@link state.pending}, kept out of the reactive object (it is not render data). */
54
- let settle: ((answer: AskAnswer) => void) | null = null;
55
-
56
- /** Torn down with the question so a stopped turn does not leave a listener on a dead signal. */
57
- let detachAbort: (() => void) | null = null;
58
-
59
- function clear() {
60
- state.pending = null;
61
- settle = null;
62
- detachAbort?.();
63
- detachAbort = null;
64
- }
65
-
66
- /**
67
- * The outstanding question, or null. Reactive — read it inside an `effect()` to repaint on it.
68
- *
69
- * @returns {PendingAsk | null}
70
- */
71
- export function pendingAsk(): PendingAsk | null {
72
- return state.pending;
73
- }
74
-
75
- /** Whether a turn is suspended on the author. The composer's Send becomes Answer on this. */
76
- export function isAwaitingAnswer(): boolean {
77
- return state.pending !== null;
78
- }
79
-
80
- /**
81
- * Register a question and suspend until it is settled.
82
- *
83
- * @param {PendingAsk} ask
84
- * @param {AbortSignal} [signal] - The asking call's signal. Without one the question can still be
85
- * settled by {@link cancelAsk}, but a Stop would leave the turn awaiting a promise nothing could
86
- * resolve.
87
- * @returns {Promise<AskAnswer>} Resolves on an answer, a skip, or the turn being stopped (`{
88
- * answer: null, skipped: false }` — the shape the tool reports as a failure).
89
- */
90
- export function askUser(ask: PendingAsk, signal?: AbortSignal): Promise<AskAnswer> {
91
- return new Promise<AskAnswer>((resolve) => {
92
- state.pending = ask;
93
- settle = resolve;
94
-
95
- if (signal?.aborted) {
96
- clear();
97
- resolve({ answer: null, skipped: false });
98
- return;
99
- }
100
- if (signal) {
101
- const onAbort = () => {
102
- cancelAsk();
103
- };
104
- signal.addEventListener("abort", onAbort, { once: true });
105
- detachAbort = () => signal.removeEventListener("abort", onAbort);
106
- }
107
- });
108
- }
109
-
110
- /**
111
- * Answer the outstanding question.
112
- *
113
- * @param {string} text
114
- * @returns {boolean} False when nothing was pending, so a caller can fall through to a normal send.
115
- */
116
- export function answerAsk(text: string): boolean {
117
- if (!settle) {
118
- return false;
119
- }
120
- const resolve = settle;
121
- clear();
122
- resolve({ answer: text, skipped: false });
123
- return true;
124
- }
125
-
126
- /**
127
- * Decline to answer — "you decide". Settles as a success, so the turn carries on.
128
- *
129
- * @returns {boolean} False when nothing was pending.
130
- */
131
- export function skipAsk(): boolean {
132
- if (!settle) {
133
- return false;
134
- }
135
- const resolve = settle;
136
- clear();
137
- resolve({ answer: null, skipped: true });
138
- return true;
139
- }
140
-
141
- /** Settle an outstanding question as unanswered. Called from `stop()` and from the turn's abort. */
142
- export function cancelAsk(): void {
143
- if (!settle) {
144
- return;
145
- }
146
- const resolve = settle;
147
- clear();
148
- resolve({ answer: null, skipped: false });
149
- }
150
-
151
- /** Drop any outstanding question without settling a turn. For New Chat and for tests. */
152
- export function resetAsk(): void {
153
- cancelAsk();
154
- clear();
155
- }
156
-
157
36
  /**
158
37
  * Register `ask_user` into a tool registry.
159
38
  *
@@ -163,24 +42,12 @@ export function resetAsk(): void {
163
42
  *
164
43
  * @param {Pick<ToolRegistry, "register">} registry
165
44
  */
166
- /** What the host that registers the question tool may hook. */
167
- export interface AskToolOptions {
168
- /**
169
- * Called once a question has been put to the author, before the turn waits on the answer. A host
170
- * saves the conversation here: a turn suspended on a person may wait for as long as they like,
171
- * and a reload in the meantime must still find the question to restore as open (`specs/ai.md`
172
- * §3.4).
173
- */
174
- onPending?: () => void;
175
- }
176
-
177
- export function registerAskTool(
178
- registry: Pick<ToolRegistry, "register">,
179
- { onPending }: AskToolOptions = {},
180
- ): void {
45
+ export function registerAskTool(registry: Pick<ToolRegistry, "register">): void {
181
46
  registry.register(
182
47
  createToolDefinition({
183
48
  name: "ask_user",
49
+ title: "Ask the user",
50
+ annotations: JX_TOOL_FACTS.ask_user.annotations,
184
51
  // It suspends the turn on the author, so a round that only asked spends no work budget.
185
52
  interactive: true,
186
53
  description:
@@ -212,43 +79,32 @@ export function registerAskTool(
212
79
  },
213
80
  required: ["question"],
214
81
  },
215
- execute: async (args, ctx) => {
82
+ interaction(args) {
216
83
  /* Every field is any JSON value, narrowed by a guard rather than by `?.`. The registry's
217
- `validate()` is "a lightweight structural check, not a JSON Schema validator",
218
- `strict: false` skips it entirely, and this tool may be registered into someone else's
219
- registry — so an argument that is a number is a case, not an impossibility. Optional
220
- chaining guards null, not type: `context?.trim()` threw on one. */
84
+ `validate()` is "a lightweight structural check, not a JSON Schema validator", and this
85
+ tool may be registered into someone else's registry, so an argument that is a number is
86
+ a case, not an impossibility. Optional chaining guards null, not type: `context?.trim()`
87
+ threw on one. */
221
88
  const { question, options, context } = args;
222
89
  if (!isString(question) || !question.trim()) {
223
- return toolError('Pass "question" — the question to put to the user.');
224
- }
225
- if (isAwaitingAnswer()) {
226
- return toolError("A question is already waiting for the user.");
90
+ return { refusal: 'Pass "question" — the question to put to the user.' };
227
91
  }
228
92
  const choices = Array.isArray(options)
229
93
  ? options
230
94
  .filter((o): o is string => isString(o) && o.trim().length > 0)
231
95
  .slice(0, MAX_OPTIONS)
232
96
  : [];
233
-
234
- const answering = askUser(
235
- {
236
- context: isString(context) ? context.trim() : "",
237
- /* The tool-call id, so the chip the transcript draws and the question this registers
238
- are the same thing. Without it a restored, permanently unanswered question would
239
- render identically to the live one. */
240
- id: ctx.callId,
241
- options: choices,
242
- question: question.trim(),
243
- },
244
- ctx.signal,
245
- );
246
- // The question is published by now (`askUser` sets it synchronously); the answer is not.
247
- if (isAwaitingAnswer()) {
248
- onPending?.();
249
- }
250
- const { answer, skipped } = await answering;
251
-
97
+ return {
98
+ context: isString(context) ? context.trim() : "",
99
+ kind: "question",
100
+ options: choices,
101
+ question: question.trim(),
102
+ };
103
+ },
104
+ settle(_args, response) {
105
+ /* SAFETY: `ask_user` only ever asks a question, so the response the invoker hands back is
106
+ the answer to one. */
107
+ const { answer, skipped } = response as Extract<InteractionResponse, { kind: "question" }>;
252
108
  if (skipped) {
253
109
  /* A success: the model asked, and "you decide" is an answer. Rendering it as a failure
254
110
  would have the model apologise for having asked a fair question. */
@@ -262,6 +118,8 @@ export function registerAskTool(
262
118
  }
263
119
  return toolSuccess({ answer, skipped: false }, `The user answered: "${answer}"`);
264
120
  },
121
+ // Never reached through the invoker, which settles an interaction instead of executing it.
122
+ execute: () => toolError("ask_user needs a person, and none is available here."),
265
123
  }),
266
124
  );
267
125
  }
@@ -60,6 +60,7 @@
60
60
  * @license MIT
61
61
  */
62
62
 
63
+ import { toolFacts } from "@jxsuite/ai/jx-tools";
63
64
  import { createToolContext, toolError } from "@jxsuite/ai/tools";
64
65
  import type {
65
66
  ToolArguments,
@@ -82,6 +83,7 @@ import type { WriteReportDeps, WriteSnapshot } from "./ai-write-report";
82
83
  import type { CommandContext } from "../commands/context";
83
84
  import type { AiToolReport, AnyCommand, CommandRegistry } from "../commands/registry";
84
85
  import type { Tab } from "../tabs/tab";
86
+ import { runAsActor } from "../tabs/transact";
85
87
 
86
88
  /**
87
89
  * The record a selection-level projection composes through.
@@ -214,8 +216,9 @@ function definitionOf(
214
216
  } else {
215
217
  parameters = command.args ?? EMPTY_ARGS;
216
218
  }
217
- return {
219
+ const definition: ToolDefinition = {
218
220
  name: tool.name,
221
+ title: command.title,
219
222
  description: `${tool.description}${undoSuffix(command)}`,
220
223
  /* SAFETY: `parameters` is an `argsSchema()` object (or the selector's composed from one), a
221
224
  JSON Schema object whose only departures from `JSONSchema` are readonly arrays and a
@@ -227,6 +230,13 @@ function definitionOf(
227
230
  llmStrict: false,
228
231
  execute: (args, ctx) => execute(tool.name, args, ctx),
229
232
  };
233
+ /* The facts are the table's, by tool name (`@jxsuite/ai/jx-tools`). A record with none projects a
234
+ tool without them, which the catalog refuses: that is the test that keeps the table whole. */
235
+ const facts = toolFacts(tool.name);
236
+ if (facts) {
237
+ definition.annotations = facts.annotations;
238
+ }
239
+ return definition;
230
240
  }
231
241
 
232
242
  /**
@@ -347,13 +357,21 @@ function fileLedger(
347
357
  }
348
358
  }
349
359
 
360
+ /** The command view, which also answers what the catalog asks of it. */
361
+ export interface CommandToolRegistry extends ToolRegistry {
362
+ /** Every record's projection, offered or not: the catalog's members from this side. */
363
+ readonly members: () => ToolDefinition[];
364
+ /** Whether the record projecting `name` is offered this round (its gate, and a non-empty enum). */
365
+ readonly advertises: (name: string) => boolean;
366
+ }
367
+
350
368
  /**
351
369
  * A ToolRegistry whose tools are the active registry's `aiTool` records. Nothing is registered.
352
370
  *
353
371
  * @param {CommandToolDeps} deps
354
- * @returns {ToolRegistry}
372
+ * @returns {CommandToolRegistry}
355
373
  */
356
- export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
374
+ export function createCommandToolRegistry(deps: CommandToolDeps): CommandToolRegistry {
357
375
  /** The record projecting `name`, enabled or not — so `execute` can reach a refusal. */
358
376
  function commandFor(name: string): AnyCommand | undefined {
359
377
  return activeRegistry()
@@ -408,10 +426,10 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
408
426
  const { paths, ...rest } = args;
409
427
  await registry.run(SELECTOR, { paths });
410
428
  before = registry.context();
411
- await registry.run(command.id, rest);
429
+ await runAsActor(ctx.actor, () => registry.run(command.id, rest));
412
430
  } else {
413
431
  before = registry.context();
414
- await registry.run(command.id, args);
432
+ await runAsActor(ctx.actor, () => registry.run(command.id, args));
415
433
  }
416
434
  } catch (error) {
417
435
  return { error: error instanceof Error ? error.message : String(error), success: false };
@@ -507,52 +525,24 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
507
525
  return { valid: true };
508
526
  },
509
527
  execute,
510
- };
511
- }
512
-
513
- /**
514
- * Union of two registries for the loop: hand tools gated by tier, command tools gated by the
515
- * record.
516
- *
517
- * `list`/`listForLLM` concatenate (hand first); `getDefinition`/`validate`/`execute` go to
518
- * whichever side has the definition, hand first; an unknown name answers `Unknown tool: "<name>"`
519
- * as `@jxsuite/ai` does; `register` goes to `hand`. A name on both sides is a defect the parity
520
- * test in `tests/ai-system-prompt.test.ts` catches; the composite's hand-first order is not a
521
- * feature anyone may rely on.
522
- *
523
- * @param {ToolRegistry} hand
524
- * @param {ToolRegistry} commands
525
- * @returns {ToolRegistry}
526
- */
527
- export function composeToolRegistries(hand: ToolRegistry, commands: ToolRegistry): ToolRegistry {
528
- function sideOf(name: string): ToolRegistry | undefined {
529
- if (hand.getDefinition(name)) {
530
- return hand;
531
- }
532
- return commands.getDefinition(name) ? commands : undefined;
533
- }
534
- return {
535
- register(tool) {
536
- hand.register(tool);
537
- },
538
- list() {
539
- return [...hand.list(), ...commands.list()];
540
- },
541
- listForLLM() {
542
- return [...hand.listForLLM(), ...commands.listForLLM()];
543
- },
544
- getDefinition(name) {
545
- return sideOf(name)?.getDefinition(name);
546
- },
547
- validate(name, args) {
548
- const side = sideOf(name);
549
- return side
550
- ? side.validate(name, args)
551
- : { valid: false, errors: [`Unknown tool: "${name}"`] };
528
+ members() {
529
+ const registry = activeRegistry();
530
+ if (!registry) {
531
+ return [];
532
+ }
533
+ const definitions: ToolDefinition[] = [];
534
+ for (const command of projected(registry)) {
535
+ const definition = definitionOf(registry, command, execute);
536
+ if (definition) {
537
+ definitions.push(definition);
538
+ }
539
+ }
540
+ return definitions;
552
541
  },
553
- async execute(name, args, ctx) {
554
- const side = sideOf(name);
555
- return side ? side.execute(name, args, ctx) : toolError(`Unknown tool: "${name}"`);
542
+ advertises(name) {
543
+ const registry = activeRegistry();
544
+ const command = commandFor(name);
545
+ return registry !== null && command !== undefined && advertised(registry, command);
556
546
  },
557
547
  };
558
548
  }
@@ -21,6 +21,7 @@
21
21
  * @license MIT
22
22
  */
23
23
 
24
+ import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
24
25
  import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
25
26
  import { IMPORT_MAX_PAGES } from "@jxsuite/protocol";
26
27
  import { isBoolean, isNumber, isString } from "@jxsuite/schema/guards";
@@ -35,7 +36,6 @@ import { adoptCreatedProject } from "./project-adoption";
35
36
  import type { AdoptCreatedOptions, AdoptOutcome } from "./project-adoption";
36
37
 
37
38
  import type { JsonValue, ToolRegistry } from "@jxsuite/ai/tools";
38
- import type { Tab } from "../tabs/tab";
39
39
  import type {
40
40
  ImportBreakpointPolicy,
41
41
  ImportReadyEvent,
@@ -62,8 +62,6 @@ const DEFAULT_MIN_FIDELITY = 25;
62
62
  const MAX_FIDELITY = 100;
63
63
 
64
64
  export interface ImportToolsCtx {
65
- /** The active tab, read AFTER adoption to re-anchor the agent loop's undo batch. */
66
- getTab: () => Tab | null;
67
65
  /** Open the imported project in this window. Defaults to the late-bound adopter. */
68
66
  adoptProject?: (root: string) => Promise<void>;
69
67
  /** Fired once adoption is VERIFIED — the session store re-keys the live chat here. */
@@ -269,7 +267,6 @@ function describeRun(id: string, root: string, summary?: ImportSiteSummary): str
269
267
  export function registerImportTools(
270
268
  registry: Pick<ToolRegistry, "register">,
271
269
  {
272
- getTab,
273
270
  adoptProject,
274
271
  onProjectAdopted,
275
272
  brief: briefStore = { clear: clearPendingImportBrief, read: pendingImportBrief },
@@ -278,6 +275,8 @@ export function registerImportTools(
278
275
  registry.register(
279
276
  createToolDefinition({
280
277
  name: "import_site",
278
+ title: "Import a site",
279
+ annotations: JX_TOOL_FACTS.import_site.annotations,
281
280
  description:
282
281
  "Clone a live website into a new Jx project and open it in the studio: pages, styles, " +
283
282
  "assets, a shared layout and recurring components. Only available while no project is " +
@@ -454,7 +453,7 @@ export function registerImportTools(
454
453
  * failure here is not the run's failure: the import continues either way, and the terminal
455
454
  * path below reports honestly on whichever state the window ended up in.
456
455
  */
457
- const adoptOptions: AdoptCreatedOptions = { getTab };
456
+ const adoptOptions: AdoptCreatedOptions = {};
458
457
  if (adoptProject) {
459
458
  adoptOptions.adopt = adoptProject;
460
459
  }
@@ -14,6 +14,7 @@
14
14
  * @license MIT
15
15
  */
16
16
 
17
+ import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
17
18
  import { createToolDefinition } from "@jxsuite/ai/tools";
18
19
  import { isJsonObject, isNumber } from "@jxsuite/schema/guards";
19
20
  import type { CreateProjectDestination, StudioPlatform } from "../types";
@@ -64,7 +65,6 @@ interface ListedEntry {
64
65
  }
65
66
 
66
67
  export interface ProjectToolsCtx {
67
- getTab: () => Tab | null;
68
68
  validate?: (doc: CandidateDocument) => Promise<string[]>;
69
69
  /**
70
70
  * Gate for `project.json` writes — the per-project entry document, not the document schema. It
@@ -113,7 +113,6 @@ export interface ProjectToolsCtx {
113
113
  export function registerProjectTools(
114
114
  registry: Pick<ToolRegistry, "register">,
115
115
  {
116
- getTab,
117
116
  validate = validateDoc,
118
117
  validateProject = validateProjectConfig,
119
118
  renderCheck,
@@ -130,6 +129,8 @@ export function registerProjectTools(
130
129
  registry.register(
131
130
  createToolDefinition({
132
131
  name: "list_files",
132
+ title: "List files",
133
+ annotations: JX_TOOL_FACTS.list_files.annotations,
133
134
  description:
134
135
  "List the project's files recursively (paths relative to the project root, with type and " +
135
136
  "size). Use this to discover pages, layouts, components, content, data, and styles before " +
@@ -199,6 +200,8 @@ export function registerProjectTools(
199
200
  registry.register(
200
201
  createToolDefinition({
201
202
  name: "read_file",
203
+ title: "Read a file",
204
+ annotations: JX_TOOL_FACTS.read_file.annotations,
202
205
  description:
203
206
  "Read a project file's content by its project-relative path. Works for any text file " +
204
207
  "(Jx documents, markdown, CSS, data). Large files are truncated.",
@@ -247,6 +250,8 @@ export function registerProjectTools(
247
250
  registry.register(
248
251
  createToolDefinition({
249
252
  name: "write_file",
253
+ title: "Write a file",
254
+ annotations: JX_TOOL_FACTS.write_file.annotations,
250
255
  description:
251
256
  "Write a project file (create or overwrite) at a project-relative path. Jx documents " +
252
257
  "(.json under pages/, layouts/, components/) are schema-validated and render-checked " +
@@ -422,6 +427,8 @@ export function registerProjectTools(
422
427
  registry.register(
423
428
  createToolDefinition({
424
429
  name: "search_files",
430
+ title: "Search files",
431
+ annotations: JX_TOOL_FACTS.search_files.annotations,
425
432
  description:
426
433
  "Search project files by file NAME (not content). Returns matching paths. Use list_files " +
427
434
  "for a directory overview and read_file to inspect contents.",
@@ -465,6 +472,8 @@ export function registerProjectTools(
465
472
  registry.register(
466
473
  createToolDefinition({
467
474
  name: "create_project",
475
+ title: "Create a project",
476
+ annotations: JX_TOOL_FACTS.create_project.annotations,
468
477
  description:
469
478
  "Create a new Jx project (project.json, conventional directories, starter pages) and open " +
470
479
  "it in the studio. Only available while no project is open. After it succeeds, the file " +
@@ -625,7 +634,6 @@ export function registerProjectTools(
625
634
  */
626
635
  const { adopted, error: adoptionError } = await adoptCreatedProject(result.root, {
627
636
  adopt: adoptProject,
628
- getTab,
629
637
  });
630
638
  if (adopted) {
631
639
  onProjectAdopted?.(result.root);
@@ -653,6 +661,8 @@ export function registerProjectTools(
653
661
  registry.register(
654
662
  createToolDefinition({
655
663
  name: "list_starters",
664
+ title: "List starters",
665
+ annotations: JX_TOOL_FACTS.list_starters.annotations,
656
666
  description:
657
667
  "List the starter templates available for new projects (id, name, description). " +
658
668
  "Currently informational — create_project scaffolds from built-in template variants.",