@jxsuite/studio 8.1.0 → 10.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 (266) hide show
  1. package/dist/chunks/{2020-dg097k3e.js → 2020-j56903f1.js} +3 -3
  2. package/dist/chunks/adapter-wc5zvg9a.js +4 -0
  3. package/dist/chunks/add-new-flow-snx1s4b0.js +4 -0
  4. package/dist/chunks/cf-account-picker-xyqw0jex.js +4 -0
  5. package/dist/chunks/cf-settings-hp1pjm0n.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-nkwphrx3.js +4 -0
  11. package/dist/chunks/content-source-6a253wmk.js +4 -0
  12. package/dist/chunks/{convert-to-repeater-grnmea6c.js → convert-to-repeater-fhdwyg14.js} +3 -3
  13. package/dist/chunks/editor-wqrc68ca.js +4 -0
  14. package/dist/chunks/entry-commands-nef0wrmp.js +4 -0
  15. package/dist/chunks/{extension-sections-rp7fkega.js → extension-sections-8ez2rrk8.js} +3 -3
  16. package/dist/chunks/file-ops-y3w71k1n.js +4 -0
  17. package/dist/chunks/files-de90apn9.js +4 -0
  18. package/dist/chunks/format-host-zfjz4rzm.js +4 -0
  19. package/dist/chunks/fs-events-77c0mr0x.js +4 -0
  20. package/dist/chunks/grid-open-4n4fjfpf.js +4 -0
  21. package/dist/chunks/{grid-view-x26rfpz1.js → grid-view-ka55z38f.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-na2mywz4.js +4 -0
  25. package/dist/chunks/index-ewmgdb7c.js +4 -0
  26. package/dist/chunks/{index-203h71wy.js → index-qb98xhse.js} +3 -3
  27. package/dist/chunks/{javascript-ww5pv5ae.js → javascript-ces13wny.js} +2 -2
  28. package/dist/chunks/{jsonMode-vwbdwwq3.js → jsonMode-6md16d4v.js} +6 -6
  29. package/dist/chunks/jx-validate-b309c020.js +4 -0
  30. package/dist/chunks/layers-8rn8y6qv.js +4 -0
  31. package/dist/chunks/layers-panel-eyah7h9z.js +4 -0
  32. package/dist/chunks/lit-html-wyqw04y2.js +4 -0
  33. package/dist/chunks/media-library-gs3np47y.js +4 -0
  34. package/dist/chunks/media-picker-tt7dk4tb.js +4 -0
  35. package/dist/chunks/{monaco-binding-63tqze27.js → monaco-binding-p8j80s4w.js} +3 -3
  36. package/dist/chunks/monaco-buffer-wsf6cmnq.js +4 -0
  37. package/dist/chunks/{monaco-setup-zyyynnj8.js → monaco-setup-wasktzfg.js} +3 -3
  38. package/dist/chunks/notify-qwst108r.js +4 -0
  39. package/dist/chunks/parse-n8dfxnqf.js +4 -0
  40. package/dist/chunks/platform-c16mqvmp.js +4 -0
  41. package/dist/chunks/publish-panel-d34bhvwj.js +4 -0
  42. package/dist/chunks/{publish-quick-ajzdhhs6.js → publish-quick-vr695j26.js} +3 -3
  43. package/dist/chunks/publish-workspace-2yr60mt4.js +4 -0
  44. package/dist/chunks/quick-search-e6xsdv0d.js +4 -0
  45. package/dist/chunks/references-gsj6trdv.js +4 -0
  46. package/dist/chunks/right-panel-33jv1knc.js +4 -0
  47. package/dist/chunks/source-control-bj9bejjv.js +4 -0
  48. package/dist/chunks/studio-03mqh8ax.js +5 -0
  49. package/dist/chunks/{studio-957jg8g3.js → studio-06j4en5h.js} +4 -4
  50. package/dist/chunks/studio-0ydxv2mr.js +7 -0
  51. package/dist/chunks/{studio-v2ek69cb.js → studio-1e8dw2w2.js} +4 -4
  52. package/dist/chunks/studio-2hsqbq7b.js +7 -0
  53. package/dist/chunks/studio-2j86y34y.js +6 -0
  54. package/dist/chunks/studio-2ks6ka4g.js +5 -0
  55. package/dist/chunks/studio-34jrz12k.js +5 -0
  56. package/dist/chunks/{studio-1meqbxns.js → studio-35vrtpdq.js} +4 -4
  57. package/dist/chunks/studio-38g0jdz7.js +5 -0
  58. package/dist/chunks/{studio-25a5by3h.js → studio-396srjbk.js} +4 -4
  59. package/dist/chunks/{studio-zm7dnr9g.js → studio-3c8pp7pb.js} +4 -4
  60. package/dist/chunks/{studio-x3ge4rts.js → studio-3v5cagy9.js} +4 -4
  61. package/dist/chunks/studio-3w35qvay.js +5 -0
  62. package/dist/chunks/studio-3zge48z4.js +7 -0
  63. package/dist/chunks/studio-45gyxpcq.js +5 -0
  64. package/dist/chunks/{studio-hw60n5dh.js → studio-4kzbrr5t.js} +3 -3
  65. package/dist/chunks/{studio-533py3yy.js → studio-5tzgq6fx.js} +4 -4
  66. package/dist/chunks/studio-5wzdy1jc.js +16 -0
  67. package/dist/chunks/studio-732mw0g8.js +5 -0
  68. package/dist/chunks/{studio-3q6n9qvq.js → studio-771k2n12.js} +4 -4
  69. package/dist/chunks/{studio-z42kysg7.js → studio-7dzyz9n8.js} +137 -137
  70. package/dist/chunks/{studio-fqs1a22a.js → studio-7xag4na8.js} +4 -4
  71. package/dist/chunks/{studio-jqrg9xxg.js → studio-881z1xrx.js} +4 -4
  72. package/dist/chunks/studio-88bmt9rh.js +5 -0
  73. package/dist/chunks/{studio-8nyw10dp.js → studio-9b7177jq.js} +4 -4
  74. package/dist/chunks/{studio-nm9qev4p.js → studio-9cxmp61c.js} +5 -5
  75. package/dist/chunks/{studio-4dsdnan2.js → studio-9dz76yqc.js} +5 -5
  76. package/dist/chunks/{studio-a6x058ew.js → studio-agyvzfm4.js} +4 -4
  77. package/dist/chunks/{studio-2b8443ms.js → studio-ajw4k8hp.js} +3 -3
  78. package/dist/chunks/studio-c1pxszat.js +5 -0
  79. package/dist/chunks/studio-cpkamjdn.js +14 -0
  80. package/dist/chunks/studio-d038kqbs.js +5 -0
  81. package/dist/chunks/studio-d83m0wyq.js +5 -0
  82. package/dist/chunks/studio-dbxv7w4s.js +5 -0
  83. package/dist/chunks/studio-de1s2x9v.js +5 -0
  84. package/dist/chunks/{studio-94ppxeqy.js → studio-dx73md4n.js} +8 -8
  85. package/dist/chunks/studio-ekgtrndg.js +5 -0
  86. package/dist/chunks/studio-epenqmvk.js +7 -0
  87. package/dist/chunks/studio-et2q4s8n.js +5 -0
  88. package/dist/chunks/{studio-txkx6586.js → studio-ewarnzm4.js} +7 -7
  89. package/dist/chunks/{studio-bf0rgyf7.js → studio-fy7jzj9j.js} +4 -4
  90. package/dist/chunks/{studio-332hesdk.js → studio-gr36vnte.js} +4 -4
  91. package/dist/chunks/{studio-rdnezzkd.js → studio-gwrhcrss.js} +5 -5
  92. package/dist/chunks/{studio-9wyqyfqy.js → studio-h310mbqm.js} +4 -4
  93. package/dist/chunks/{studio-zht0x0v3.js → studio-h5tpz5ke.js} +4 -4
  94. package/dist/chunks/studio-h7weqf2q.js +5 -0
  95. package/dist/chunks/{studio-1gdfsr49.js → studio-hcsba201.js} +7 -7
  96. package/dist/chunks/studio-hwejq03c.js +5 -0
  97. package/dist/chunks/{studio-wcakp5bt.js → studio-hy4ye0dx.js} +7 -7
  98. package/dist/chunks/{studio-02phtmqn.js → studio-jttyqrmh.js} +4 -4
  99. package/dist/chunks/{studio-j5jqvyn3.js → studio-jw0q5j6k.js} +4 -4
  100. package/dist/chunks/studio-k3a8rp3d.js +5 -0
  101. package/dist/chunks/{studio-hnx1h1pr.js → studio-k3bcy02e.js} +4 -4
  102. package/dist/chunks/studio-m48v13s3.js +4 -0
  103. package/dist/chunks/{studio-bqgs90r8.js → studio-ndskgqef.js} +4 -4
  104. package/dist/chunks/studio-p1n2xjda.js +5 -0
  105. package/dist/chunks/studio-pgm83a2f.js +6 -0
  106. package/dist/chunks/studio-pqqq6v85.js +5 -0
  107. package/dist/chunks/studio-psh8mwnw.js +4 -0
  108. package/dist/chunks/{studio-cpdzrdgp.js → studio-r17asgs0.js} +4 -4
  109. package/dist/chunks/{studio-6vrxzkba.js → studio-ry81j4q1.js} +4 -4
  110. package/dist/chunks/studio-rzg8hnkf.js +5 -0
  111. package/dist/chunks/{studio-nsv7g1qn.js → studio-s0fg2wav.js} +4 -4
  112. package/dist/chunks/{studio-v0gsjav0.js → studio-tj2s0tks.js} +4 -4
  113. package/dist/chunks/studio-tyw7tk0x.js +5 -0
  114. package/dist/chunks/studio-v9ywz2se.js +6 -0
  115. package/dist/chunks/{studio-dmn7gpba.js → studio-vswcpzk7.js} +5 -5
  116. package/dist/chunks/studio-vzgmyt0v.js +5 -0
  117. package/dist/chunks/{studio-2dtc90zs.js → studio-wxdcd42g.js} +6 -6
  118. package/dist/chunks/{studio-c5dj3dzk.js → studio-wxhy1yh7.js} +4 -4
  119. package/dist/chunks/studio-x08a3m0a.js +5 -0
  120. package/dist/chunks/studio-x3mjr5cc.js +5 -0
  121. package/dist/chunks/{studio-xddgbwhe.js → studio-x97ay0v4.js} +4 -4
  122. package/dist/chunks/{studio-mmqr9awv.js → studio-xdpz9rpe.js} +33 -33
  123. package/dist/chunks/studio-xh683qzy.js +5 -0
  124. package/dist/chunks/{studio-hxq1ebp0.js → studio-xkxetm65.js} +6 -6
  125. package/dist/chunks/{studio-73wz63nn.js → studio-y26hx9re.js} +4 -4
  126. package/dist/chunks/{studio-kdseew9m.js → studio-yg3djpnm.js} +4 -4
  127. package/dist/chunks/studio-yx8hzqja.js +5 -0
  128. package/dist/chunks/studio-zg16eeq1.js +5 -0
  129. package/dist/chunks/studio-zgx7svjd.js +9 -0
  130. package/dist/chunks/studio-zkq3r91p.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-h6tkz0ea.js} +5 -5
  134. package/dist/chunks/workspace-home-zkkrxq3r.js +4 -0
  135. package/dist/chunks/workspace-tdqqjqaa.js +4 -0
  136. package/dist/chunks/{ws-client-72x5n99j.js → ws-client-e951my95.js} +3 -3
  137. package/dist/iframe-entry.js +13 -13
  138. package/dist/manifest.json +77 -77
  139. package/dist/studio.js +91 -92
  140. package/package.json +11 -12
  141. package/src/canvas/iframe-editable-root.ts +32 -2
  142. package/src/collab/collab-session.ts +6 -2
  143. package/src/commands/context.ts +4 -0
  144. package/src/commands/defaults.ts +2 -0
  145. package/src/commands/live-context.ts +2 -0
  146. package/src/files/files.ts +6 -3
  147. package/src/format/format-host.ts +1 -1
  148. package/src/github/github-auth-status.ts +2 -2
  149. package/src/media/media-library.ts +73 -3
  150. package/src/new-project/new-project-modal.ts +1 -4
  151. package/src/packages/ensure-deps.ts +5 -5
  152. package/src/packages/pull-package-sync.ts +4 -4
  153. package/src/panels/activity-panel.ts +1 -1
  154. package/src/panels/ai-panel.ts +27 -34
  155. package/src/platform.ts +20 -0
  156. package/src/platforms/cloud.ts +48 -34
  157. package/src/platforms/devserver.ts +2 -120
  158. package/src/services/ai-ask.ts +29 -171
  159. package/src/services/ai-command-tools.ts +40 -50
  160. package/src/services/ai-import-tools.ts +4 -5
  161. package/src/services/ai-project-tools.ts +13 -3
  162. package/src/services/ai-tools.ts +26 -564
  163. package/src/services/ai-writes.ts +84 -2
  164. package/src/services/document-assistant.ts +29 -58
  165. package/src/services/harness/assistant-tools.ts +68 -0
  166. package/src/services/harness/interaction-store.ts +118 -0
  167. package/src/services/harness/studio-documents.ts +105 -0
  168. package/src/services/harness/turn-hooks.ts +1 -6
  169. package/src/services/project-adoption.ts +8 -16
  170. package/src/services/tool-executor.ts +25 -22
  171. package/src/settings/extension-commands.ts +1 -1
  172. package/src/settings/extension-rows.ts +1 -6
  173. package/src/settings/extensions-section.ts +3 -6
  174. package/src/state.ts +3 -13
  175. package/src/surfaces/block-action-bar.json +1 -3
  176. package/src/surfaces/files-panel.json +12 -0
  177. package/src/surfaces/files-panel.ts +5 -2
  178. package/src/surfaces/media-library.json +35 -0
  179. package/src/surfaces/media-library.ts +2 -0
  180. package/src/surfaces/menu.ts +62 -1
  181. package/src/surfaces/settings-packages.ts +1 -1
  182. package/src/surfaces/welcome.ts +4 -1
  183. package/src/tabs/selection.ts +14 -109
  184. package/src/tabs/tab.ts +10 -1
  185. package/src/tabs/transact.ts +316 -124
  186. package/src/types.ts +23 -16
  187. package/src/ui/layers.ts +1 -1
  188. package/src/ui/progress-modal.ts +3 -3
  189. package/dist/chunks/adapter-tck81zft.js +0 -4
  190. package/dist/chunks/add-new-flow-a1w0bnm6.js +0 -4
  191. package/dist/chunks/cf-account-picker-ce3kp3x1.js +0 -4
  192. package/dist/chunks/cf-settings-7ye6x7g2.js +0 -4
  193. package/dist/chunks/color-4gj49xq6.js +0 -4
  194. package/dist/chunks/components-qce0v93f.js +0 -4
  195. package/dist/chunks/content-source-jjx8kgzr.js +0 -4
  196. package/dist/chunks/editor-bkja5dbv.js +0 -4
  197. package/dist/chunks/entry-commands-1wh0btvg.js +0 -4
  198. package/dist/chunks/file-ops-6pe4z7an.js +0 -4
  199. package/dist/chunks/files-ybpp3j38.js +0 -4
  200. package/dist/chunks/format-host-3mtbhxk1.js +0 -4
  201. package/dist/chunks/fs-events-xfanfxds.js +0 -4
  202. package/dist/chunks/grid-open-dg5qmnaj.js +0 -4
  203. package/dist/chunks/iframe-host-j2bk2xkc.js +0 -4
  204. package/dist/chunks/index-fkrm7pdk.js +0 -4
  205. package/dist/chunks/jx-validate-w7v32f6a.js +0 -4
  206. package/dist/chunks/layers-g7j732cm.js +0 -4
  207. package/dist/chunks/layers-panel-gjrsxcck.js +0 -4
  208. package/dist/chunks/lit-html-jdfz9rhc.js +0 -4
  209. package/dist/chunks/media-library-9j0v4wmk.js +0 -4
  210. package/dist/chunks/media-picker-cpyyt2m0.js +0 -4
  211. package/dist/chunks/monaco-buffer-9wcme176.js +0 -4
  212. package/dist/chunks/notify-rahnnnv4.js +0 -4
  213. package/dist/chunks/parse-v77xtwv7.js +0 -4
  214. package/dist/chunks/platform-b2aqv7v5.js +0 -4
  215. package/dist/chunks/publish-panel-4yk2461b.js +0 -4
  216. package/dist/chunks/publish-workspace-mxestzcf.js +0 -4
  217. package/dist/chunks/quick-search-9g6z65b0.js +0 -4
  218. package/dist/chunks/references-c26enpxs.js +0 -4
  219. package/dist/chunks/right-panel-9d0e0yg7.js +0 -4
  220. package/dist/chunks/source-control-xyn3a8ds.js +0 -4
  221. package/dist/chunks/studio-0khd0xdw.js +0 -5
  222. package/dist/chunks/studio-1fvzq9vs.js +0 -5
  223. package/dist/chunks/studio-1yhz0s8p.js +0 -5
  224. package/dist/chunks/studio-2c2mjyzj.js +0 -6
  225. package/dist/chunks/studio-3pndhat0.js +0 -9
  226. package/dist/chunks/studio-4fjqdzrb.js +0 -6
  227. package/dist/chunks/studio-5abge3wy.js +0 -5
  228. package/dist/chunks/studio-74nzv280.js +0 -5
  229. package/dist/chunks/studio-7cg75gtj.js +0 -14
  230. package/dist/chunks/studio-89cwmnae.js +0 -5
  231. package/dist/chunks/studio-8m1t83kn.js +0 -7
  232. package/dist/chunks/studio-a3evp7f6.js +0 -6
  233. package/dist/chunks/studio-ana67wax.js +0 -5
  234. package/dist/chunks/studio-b4xhpcwy.js +0 -4
  235. package/dist/chunks/studio-c4rcaftm.js +0 -5
  236. package/dist/chunks/studio-cgjt1zce.js +0 -5
  237. package/dist/chunks/studio-cq87hree.js +0 -5
  238. package/dist/chunks/studio-d140sy8e.js +0 -5
  239. package/dist/chunks/studio-e1g64q43.js +0 -5
  240. package/dist/chunks/studio-e8pzjjhf.js +0 -5
  241. package/dist/chunks/studio-f8kz3krj.js +0 -7
  242. package/dist/chunks/studio-fw3jp9m7.js +0 -5
  243. package/dist/chunks/studio-g6ysm05p.js +0 -7
  244. package/dist/chunks/studio-gmhd0j3f.js +0 -5
  245. package/dist/chunks/studio-gqpty626.js +0 -5
  246. package/dist/chunks/studio-grp684wx.js +0 -5
  247. package/dist/chunks/studio-h98520qr.js +0 -5
  248. package/dist/chunks/studio-hvxt8z79.js +0 -5
  249. package/dist/chunks/studio-j4e627w1.js +0 -5
  250. package/dist/chunks/studio-kt2j51sa.js +0 -5
  251. package/dist/chunks/studio-m4w6sygj.js +0 -16
  252. package/dist/chunks/studio-nqxqf8p2.js +0 -4
  253. package/dist/chunks/studio-p89w12s2.js +0 -7
  254. package/dist/chunks/studio-pbagym4j.js +0 -5
  255. package/dist/chunks/studio-pr23qhrw.js +0 -5
  256. package/dist/chunks/studio-r4ya3sb2.js +0 -5
  257. package/dist/chunks/studio-rm9m3z60.js +0 -5
  258. package/dist/chunks/studio-tpmnbgmt.js +0 -5
  259. package/dist/chunks/studio-ttxhq5yt.js +0 -5
  260. package/dist/chunks/studio-y1x65h5r.js +0 -5
  261. package/dist/chunks/studio-zf188hnd.js +0 -5
  262. package/dist/chunks/toast-1gv0h53h.js +0 -4
  263. package/dist/chunks/workspace-3nt93k48.js +0 -4
  264. package/dist/chunks/workspace-home-zbgyqqws.js +0 -4
  265. package/src/services/directory-picker.ts +0 -187
  266. package/src/services/gated-registry.ts +0 -77
@@ -7,8 +7,9 @@
7
7
  * before Studio boots — while project-less mode (null) powers the /studio welcome screen.
8
8
  *
9
9
  * Cloud omissions (Studio degrades per @jxsuite/protocol's route table): pickDirectory, package
10
- * install/outdated/set-versions (package ops are manifest-only edits), multi-window, gitClone,
11
- * resolveClass, component discovery, code services.
10
+ * install and the install-on-open check (a cloud session has no `node_modules`; add, remove and
11
+ * set-versions update `package.json` and `package-lock.json` through the session's package
12
+ * service), multi-window, gitClone, resolveClass, component discovery, code services.
12
13
  */
13
14
 
14
15
  import { negotiateCollab } from "@jxsuite/collab/negotiate";
@@ -43,6 +44,8 @@ import type {
43
44
  ImportSiteOptions,
44
45
  OfferedAction,
45
46
  PackageInfo,
47
+ PackageOpResult,
48
+ PackageVersionInfo,
46
49
  ProjectListEntry,
47
50
  SiteBuildResult,
48
51
  ProjectSchemasResponse,
@@ -947,17 +950,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
947
950
  }
948
951
  }
949
952
 
950
- async function writePackageJson(pkg: JsonObject): Promise<void> {
951
- const res = await api("/file", {
952
- method: "PUT",
953
- headers: { "Content-Type": "application/json" },
954
- body: JSON.stringify({ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}\n` }),
955
- });
956
- if (!res.ok) {
957
- throw new Error(await errorMessage(res, "Failed to update package.json"));
958
- }
959
- }
960
-
961
953
  // The bound session's /raw tree; see `documentBaseUrl` below for why both members ride together.
962
954
  const repoAssets: Pick<StudioPlatform, "assetSpace" | "documentBaseUrl"> = {};
963
955
  if (base) {
@@ -1284,10 +1276,12 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1284
1276
  *
1285
1277
  * A Worker ships a fixed set of extension packages (specs/extensions.md §5.5), decided by the
1286
1278
  * platform build rather than by this repository's `extensions/` tree, so the gateway is the
1287
- * only thing that knows. Everything it returns is therefore `bundled: true`: enabling one is a
1288
- * `project.json` write alone, and an extension the Worker does not bundle is dropped from the
1289
- * registry before composition — advertising it would promise a toggle that silently does
1290
- * nothing.
1279
+ * only thing that knows. An extension the Worker does not bundle is dropped from the registry
1280
+ * before composition, so advertising it would promise a toggle that silently does nothing.
1281
+ *
1282
+ * What the Worker bundles is what it can EDIT with, not what the project gets: the site is
1283
+ * built in Pages CI from the project's own install, so an entry the project has not declared is
1284
+ * install-then-enable here exactly as on every other backend (specs/extensions.md §3).
1291
1285
  *
1292
1286
  * `installed` here means DECLARED. Nothing resolves a module in a Worker: `addPackage` is a
1293
1287
  * manifest edit and resolution happens later in Pages CI, so the manifest is the only fact this
@@ -1309,7 +1303,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1309
1303
  ...Object.keys(dependencyBlock(pkg, "devDependencies")),
1310
1304
  ]);
1311
1305
  for (const entry of entries) {
1312
- entry.bundled = true;
1313
1306
  entry.installed = declared.has(entry.name);
1314
1307
  }
1315
1308
  return entries;
@@ -1435,28 +1428,49 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1435
1428
  return null;
1436
1429
  },
1437
1430
 
1438
- // ─── Packages (manifest-only edits; resolution happens in Pages CI) ───
1431
+ // ─── Packages (the session's lockfile service; nothing installs) ───
1439
1432
 
1433
+ /* Each mutation runs npm's installer lockfile-only on the platform, which writes
1434
+ `package.json` and `package-lock.json` back into the shared tree as one change, so a commit
1435
+ carries a lockfile a build can install from frozen (server.md §4.1). A failure changes
1436
+ nothing and its message names the cause. */
1440
1437
  async addPackage(name: string) {
1441
- const pkg = await readPackageJson();
1442
- const deps = dependencyBlock(pkg, "dependencies");
1443
- const at = name.lastIndexOf("@");
1444
- const [pkgName, version] =
1445
- at > 0 ? [name.slice(0, at), name.slice(at + 1)] : [name, "latest"];
1446
- deps[pkgName] = version;
1447
- pkg["dependencies"] = deps;
1448
- await writePackageJson(pkg);
1438
+ const res = await api("/packages/add", {
1439
+ body: JSON.stringify({ name }),
1440
+ headers: { "Content-Type": "application/json" },
1441
+ method: "POST",
1442
+ });
1443
+ if (!res.ok) {
1444
+ throw new Error(await errorMessage(res, `Failed to add ${name}`));
1445
+ }
1449
1446
  },
1450
1447
 
1451
1448
  async removePackage(name: string) {
1452
- const pkg = await readPackageJson();
1453
- for (const key of ["dependencies", "devDependencies"]) {
1454
- const deps = pkg[key];
1455
- if (isJsonObject(deps) && name in deps) {
1456
- pkg[key] = Object.fromEntries(Object.entries(deps).filter(([dep]) => dep !== name));
1457
- }
1449
+ const res = await api("/packages/remove", {
1450
+ body: JSON.stringify({ name }),
1451
+ headers: { "Content-Type": "application/json" },
1452
+ method: "POST",
1453
+ });
1454
+ if (!res.ok) {
1455
+ throw new Error(await errorMessage(res, `Failed to remove ${name}`));
1456
+ }
1457
+ },
1458
+
1459
+ async setPackageVersions(updates: { name: string; version: string; dev?: boolean }[]) {
1460
+ const res = await api("/packages/set-versions", {
1461
+ body: JSON.stringify({ updates }),
1462
+ headers: { "Content-Type": "application/json" },
1463
+ method: "POST",
1464
+ });
1465
+ if (!res.ok) {
1466
+ return { log: await errorMessage(res, "Failed to update versions"), ok: false };
1458
1467
  }
1459
- await writePackageJson(pkg);
1468
+ return readJson<PackageOpResult>(res);
1469
+ },
1470
+
1471
+ async packageVersions() {
1472
+ const res = await api("/packages/versions");
1473
+ return res.ok ? readJson<PackageVersionInfo[]>(res) : [];
1460
1474
  },
1461
1475
 
1462
1476
  async listPackages(): Promise<PackageInfo[]> {
@@ -2,18 +2,16 @@
2
2
  * Devserver.js — Dev Server Platform Adapter
3
3
  *
4
4
  * Implements the StudioPlatform interface for the @jxsuite/server development workflow. All file
5
- * I/O goes through /__studio/* REST endpoints. Project opening uses the Chrome File System Access
6
- * API (showDirectoryPicker).
5
+ * I/O goes through /__studio/* REST endpoints. There is no Open Project: a dev-server window opens
6
+ * a project from its `?project=` URL parameter (specs/desktop.md §8.2).
7
7
  *
8
8
  * See spec/desktop.md §8 for the full specification.
9
9
  */
10
10
 
11
11
  import { streamImport } from "../services/import-client";
12
- import { canPickDirectory, pickDirectoryPath } from "../services/directory-picker";
13
12
  import { negotiateCollab } from "@jxsuite/collab/negotiate";
14
13
  import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
15
14
  import type { WsCollabConnection } from "@jxsuite/collab/client";
16
- import { isFunction } from "@jxsuite/schema/guards";
17
15
  import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
18
16
  import type {
19
17
  DataConnectionsResponse,
@@ -80,16 +78,6 @@ interface FsEventBatch {
80
78
  events?: FsEvent[];
81
79
  }
82
80
 
83
- /** `showDirectoryPicker()` (WICG File System Access), which only Chromium-based browsers expose. */
84
- type ShowDirectoryPicker = (options: { mode: string }) => Promise<FileSystemDirectoryHandle>;
85
-
86
- /** True when the window implements the File System Access directory picker. */
87
- function hasDirectoryPicker(
88
- host: Window,
89
- ): host is Window & { showDirectoryPicker: ShowDirectoryPicker } {
90
- return "showDirectoryPicker" in host && isFunction(host.showDirectoryPicker);
91
- }
92
-
93
81
  /** The options object {@link StudioPlatform.createProject} takes. */
94
82
  type CreateProjectOptions = Parameters<StudioPlatform["createProject"]>[0];
95
83
 
@@ -225,36 +213,6 @@ export function createDevServerPlatform() {
225
213
  return p.startsWith(`${_projectRoot}/`) ? p.slice(_projectRoot.length + 1) : p;
226
214
  }
227
215
 
228
- /**
229
- * Native folder chooser for the New Project modal's Browse… button. Present only when the browser
230
- * implements `showDirectoryPicker` (Chromium-based); elsewhere the member stays undefined and the
231
- * modal falls back to a typed Location field.
232
- *
233
- * The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
234
- * folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
235
- * dismisses the chooser; every other failure rejects with a sentence the modal shows under
236
- * Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
237
- * route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
238
- * than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is not
239
- * being served by the dev server at all) propagates as is, and the picker names it.
240
- */
241
- const directoryPicking: Pick<StudioPlatform, "pickDirectory"> = {};
242
- if (canPickDirectory()) {
243
- directoryPicking.pickDirectory = async (): Promise<string | null> =>
244
- pickDirectoryPath(async ({ id, name }) => {
245
- const res = await fetch(
246
- `/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
247
- );
248
- if (!res.ok) {
249
- // A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
250
- const body = await readJson<JsonValue>(res).catch(() => null);
251
- throw new Error(problemMessage(body, res.status));
252
- }
253
- const found = await readJson<{ path?: string | null }>(res);
254
- return found.path ?? null;
255
- });
256
- }
257
-
258
216
  return {
259
217
  id: "devserver",
260
218
 
@@ -293,80 +251,6 @@ export function createDevServerPlatform() {
293
251
  await postActivate(root ?? _projectRoot);
294
252
  },
295
253
 
296
- // ─── Project opening ──────────────────────────────────────────────────
297
-
298
- async openProject() {
299
- // Use Chrome's showDirectoryPicker API
300
- const host = window;
301
- if (!hasDirectoryPicker(host)) {
302
- throw new Error("showDirectoryPicker not available — use a Chromium-based browser");
303
- }
304
-
305
- let dirHandle;
306
- try {
307
- dirHandle = await host.showDirectoryPicker({ mode: "readwrite" });
308
- } catch (error) {
309
- // User cancelled the picker
310
- if (error instanceof Error && error.name === "AbortError") {
311
- return null;
312
- }
313
- throw error;
314
- }
315
-
316
- // Read project.json from the chosen directory
317
- let siteHandle;
318
- try {
319
- siteHandle = await dirHandle.getFileHandle("project.json");
320
- } catch {
321
- throw new Error("No project.json found in selected folder");
322
- }
323
-
324
- const file = await siteHandle.getFile();
325
- /* SAFETY: the user picked this folder as a project, and project.json is that project's own
326
- config file; it is matched below against the server's parse of the same file, and every
327
- adapter hands project.json to Studio as the ProjectConfig the session edits. */
328
- const config = JSON.parse(await file.text()) as ProjectConfig;
329
-
330
- // Resolve server-relative path by matching against known sites
331
- const sitesRes = await fetch("/__studio/sites");
332
- if (!sitesRes.ok) {
333
- throw new Error("Failed to fetch site list from server");
334
- }
335
- const sites = await readJson<SiteEntry[]>(sitesRes);
336
- const match = sites.find(
337
- (s: SiteEntry) => JSON.stringify(s.config) === JSON.stringify(config),
338
- );
339
-
340
- if (!match) {
341
- // Project is outside dev server root — ask the server to find it by directory name
342
- const findRes = await fetch(
343
- `/__studio/find-project?name=${encodeURIComponent(dirHandle.name)}`,
344
- );
345
- if (!findRes.ok) {
346
- throw new Error("Could not locate project on disk");
347
- }
348
- const found = await readJson<{ path?: string }>(findRes);
349
- if (!found.path) {
350
- throw new Error(`Could not find project directory "${dirHandle.name}"`);
351
- }
352
- _projectRoot = found.path;
353
- } else {
354
- _projectRoot = match.path;
355
- }
356
-
357
- // Notify server of active project for static file resolution
358
- await this.activate();
359
-
360
- return {
361
- config,
362
- handle: {
363
- name: config.name || _projectRoot.split("/").pop()!,
364
- projectConfig: config,
365
- root: _projectRoot,
366
- },
367
- };
368
- },
369
-
370
254
  /**
371
255
  * Probe the server root to see if it is itself a site project. Used at startup to auto-detect
372
256
  * projects.
@@ -408,8 +292,6 @@ export function createDevServerPlatform() {
408
292
  return await res.json();
409
293
  },
410
294
 
411
- ...directoryPicking,
412
-
413
295
  /** List starter templates from the dev server. */
414
296
  async listStarters(): Promise<StarterInfo[]> {
415
297
  const res = await fetch("/__studio/starters");
@@ -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
  }