@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
@@ -11,9 +11,8 @@
11
11
 
12
12
  import { initProjectRepo } from "../files/files";
13
13
  import { getPlatform, hasPlatform } from "../platform";
14
- import { beginBatch, endBatch, isBatching } from "../tabs/transact";
14
+ import { flushOpenBatches } from "../tabs/transact";
15
15
  import { workspace } from "../workspace/workspace";
16
- import type { Tab } from "../tabs/tab";
17
16
 
18
17
  let _adopter: ((root: string) => Promise<void>) | null = null;
19
18
 
@@ -52,8 +51,6 @@ export interface AdoptOutcome {
52
51
 
53
52
  /** The seams {@link adoptCreatedProject} runs through, so each caller keeps the one it already had. */
54
53
  export interface AdoptCreatedOptions {
55
- /** The active tab, read AFTER adoption to re-anchor the agent loop's undo batch. */
56
- getTab: () => Tab | null;
57
54
  /** The project-open flow. Defaults to this module's late-bound {@link adoptProject}. */
58
55
  adopt?: (root: string) => Promise<void>;
59
56
  /** Version-control init. Defaults to `files/files.ts`'s `initProjectRepo`. */
@@ -76,9 +73,8 @@ export interface AdoptCreatedOptions {
76
73
  * - **Verifying.** `openRecentProject` swallows its failures into a status message, so a resolved
77
74
  * promise is not proof of adoption. The caller must be told which of the two happened.
78
75
  *
79
- * The batch dance is the third: the agent loop may hold an undo batch on the pre-adoption tab, and
80
- * adoption replaces every tab in the workspace. Flush it, adopt, re-open one on whatever tab
81
- * adoption left active.
76
+ * The third is the undo batch: the agent loop's lane may hold segments on pre-adoption tabs, and
77
+ * adoption replaces every tab in the workspace, so they are closed first (specs/ai.md §3.3).
82
78
  *
83
79
  * @param {string} root - Absolute root of the project that was just created.
84
80
  * @param {AdoptCreatedOptions} opts
@@ -86,7 +82,7 @@ export interface AdoptCreatedOptions {
86
82
  */
87
83
  export async function adoptCreatedProject(
88
84
  root: string,
89
- { getTab, adopt = adoptProject, initRepo = initProjectRepo }: AdoptCreatedOptions,
85
+ { adopt = adoptProject, initRepo = initProjectRepo }: AdoptCreatedOptions = {},
90
86
  ): Promise<AdoptOutcome> {
91
87
  // Never fails the create: a project that was written stays written (initProjectRepo notifies).
92
88
  await initRepo(root);
@@ -117,19 +113,15 @@ export async function adoptCreatedProject(
117
113
  }
118
114
  }
119
115
 
120
- const wasBatching = isBatching();
121
- if (wasBatching) {
122
- endBatch();
123
- }
116
+ /* The tabs an open lane has segments on are about to be replaced: close each now, against the
117
+ document it was written into. The lane stays open, so the turn's next write opens a segment on
118
+ whatever document it writes in the adopted project. */
119
+ flushOpenBatches();
124
120
  let adoptionError: string | null = null;
125
121
  try {
126
122
  await adopt(root);
127
123
  } catch (error) {
128
124
  adoptionError = error instanceof Error ? error.message : String(error);
129
125
  }
130
- if (wasBatching) {
131
- beginBatch(getTab());
132
- }
133
-
134
126
  return { adopted: workspace.projectRoot === root, error: adoptionError, openedElsewhere };
135
127
  }
@@ -9,17 +9,19 @@
9
9
  * happen (`services/harness/chat-reducer.ts`), and each round's request is projected from it, as
10
10
  * it always was, so the provider sees the conversation the panel shows, with whatever the send
11
11
  * path trimmed or sealed before the turn began.
12
- * - **A batch belongs to the tab it edits** (§3.3). The undo batch opens on the active tab and is
13
- * re-anchored after every call whose tools moved to another document, so a turn that edits two
14
- * documents gives each its own history step and collab publish.
12
+ * - **The batch belongs to the assistant** (§3.3). The turn opens a batch lane owned by its actor,
13
+ * and every write the turn's tools make joins it, one segment per document written: each document
14
+ * gets one history step and one collab publish, whichever tab is active, and a person's edit
15
+ * during the turn closes the segment on that document rather than being folded into it.
15
16
  * - **The ledger is filed under the last message the turn drew** (§3.2), in this module's `finally`,
16
17
  * so the transcript draws the turn's changed-files summary under it.
17
18
  *
18
19
  * @license MIT
19
20
  */
20
21
 
22
+ import type { InteractionStore } from "./harness/interaction-store";
21
23
  import type { createChatState } from "@jxsuite/ai/chat-state";
22
- import { DEFAULT_TURN_POLICY, runTurn } from "@jxsuite/ai/harness";
24
+ import { DEFAULT_TURN_POLICY, assistantActorId, runTurn } from "@jxsuite/ai/harness";
23
25
  import type { TurnInput, TurnRun } from "@jxsuite/ai/harness";
24
26
  import { toChatMessages, toOpenAITools } from "@jxsuite/ai/messages";
25
27
  import type { StreamingClient } from "@jxsuite/ai/streaming-client";
@@ -27,7 +29,7 @@ import { createSessionFacts } from "@jxsuite/ai/tools";
27
29
  import type { SessionFacts, ToolRegistry } from "@jxsuite/ai/tools";
28
30
 
29
31
  import type { Tab } from "../tabs/tab";
30
- import { batchTab, beginBatch, endBatch } from "../tabs/transact";
32
+ import { openBatchLane } from "../tabs/transact";
31
33
  import { estimatePromptTokens } from "./context-manager";
32
34
  import { fileTurn, openTurnLedger, turnAnchor } from "./ai-writes";
33
35
  import { applyHarnessEvent } from "./harness/chat-reducer";
@@ -48,6 +50,8 @@ interface RunAgentLoopOptions {
48
50
  getTab?: () => Tab | null;
49
51
  /** The conversation's facts, which every call's context carries; a fresh set when omitted. */
50
52
  session?: SessionFacts;
53
+ /** Where a call that needs the author waits on them. Without one, such a call is refused. */
54
+ interactions?: InteractionStore;
51
55
  }
52
56
 
53
57
  /**
@@ -62,26 +66,23 @@ export async function runAgentLoop({
62
66
  signal,
63
67
  getTab,
64
68
  session = createSessionFacts(),
69
+ interactions,
65
70
  }: RunAgentLoopOptions): Promise<void> {
66
71
  /* The ledger is filed under the turn's last DRAWN assistant message (see services/ai-writes), so
67
72
  the scan starts at the message this turn answers. */
68
73
  const turnUserId = chatState.messages.findLast((m) => m.role === "user")?.id;
69
74
  const ledger = openTurnLedger(`turn:${chatState.messages.length}`);
70
75
 
71
- // Batch all tool-call mutations into a single undo step, anchored on the tab being edited.
72
- if (getTab) {
73
- beginBatch(getTab());
74
- }
75
- const reanchorBatch = () => {
76
- if (!getTab) {
77
- return;
78
- }
79
- const current = getTab();
80
- if (current !== batchTab()) {
81
- endBatch();
82
- beginBatch(current);
83
- }
84
- };
76
+ /* One undo step per document the turn writes. A loop with no `getTab` has no document of its own
77
+ (the evals), so it gets no lane and each tool is its own entry. The owner's id is the one each
78
+ call's context will carry, so a tool's write joins the lane by naming `ctx.actor`. */
79
+ const lane = getTab
80
+ ? openBatchLane({
81
+ id: assistantActorId(session.sessionId, ledger.turnId),
82
+ kind: "assistant",
83
+ turnId: ledger.turnId,
84
+ })
85
+ : null;
85
86
 
86
87
  /* The placeholder `sendMessage` pushed is round one's message, so the turn answers into it; the
87
88
  conversation the turn continues is everything before it. */
@@ -124,7 +125,6 @@ export async function runAgentLoop({
124
125
  superseded = true;
125
126
  run?.cancel("superseded");
126
127
  },
127
- reanchor: reanchorBatch,
128
128
  }),
129
129
  session,
130
130
  ledger,
@@ -137,13 +137,16 @@ export async function runAgentLoop({
137
137
  if (signal) {
138
138
  input.signal = signal;
139
139
  }
140
+ if (interactions) {
141
+ input.interactions = interactions.strategy;
142
+ }
140
143
  if (placeholder) {
141
144
  input.firstMessageId = placeholder.id;
142
145
  }
143
146
  run = runTurn(input);
144
147
  await run.outcome;
145
148
  } finally {
146
- endBatch();
147
- fileTurn(turnAnchor(chatState.messages, turnUserId) ?? "", ledger.writes);
149
+ const batches = lane?.close() ?? [];
150
+ fileTurn(turnAnchor(chatState.messages, turnUserId) ?? "", ledger.writes, batches);
148
151
  }
149
152
  }
package/src/state.ts CHANGED
@@ -154,19 +154,9 @@ export function pathsEqual(a: JxPath | null, b: JxPath | null) {
154
154
  return a.every((v, i) => v === b[i]);
155
155
  }
156
156
 
157
- /**
158
- * Returns true if `path` is an ancestor of (or equal to) `descendant`.
159
- *
160
- * @param {JxPath} path
161
- * @param {JxPath} descendant
162
- * @returns {boolean}
163
- */
164
- export function isAncestor(path: JxPath, descendant: JxPath) {
165
- if (path.length > descendant.length) {
166
- return false;
167
- }
168
- return path.every((v, i) => v === descendant[i]);
169
- }
157
+ /* Lives beside the doc-op vocabulary (`@jxsuite/schema/doc-ops`), with the other structural-path
158
+ helpers; re-exported for the many callers that import it from here. */
159
+ export { isAncestor } from "@jxsuite/schema/doc-ops";
170
160
 
171
161
  // ─── Tree flattening (for layer panel) ────────────────────────────────────────
172
162
 
@@ -136,6 +136,18 @@
136
136
  }
137
137
  ]
138
138
  },
139
+ "address": {
140
+ "tagName": "div",
141
+ "attributes": {
142
+ "part": "empty"
143
+ },
144
+ "children": [
145
+ {
146
+ "tagName": "p",
147
+ "textContent": "Add ?project= and a project folder's absolute path to this page's address to open it."
148
+ }
149
+ ]
150
+ },
139
151
  "welcome": {
140
152
  "tagName": "div",
141
153
  "attributes": {
@@ -81,8 +81,11 @@ export interface FileRowView {
81
81
 
82
82
  /** Everything the document draws, as one value. */
83
83
  export interface FilesPanelValues {
84
- /** Which of three things the panel is: no project, a monorepo root, or a tree. */
85
- view: "none" | "welcome" | "tree";
84
+ /**
85
+ * Which of four things the panel is: no project, a monorepo root on a host that can open a
86
+ * project, the same root on one that opens from the address (`?project=`), or a tree.
87
+ */
88
+ view: "none" | "welcome" | "address" | "tree";
86
89
  headerState: "hidden" | "shown";
87
90
  projectName: string;
88
91
  query: string;
@@ -22,6 +22,7 @@
22
22
  import { getAccountStatus, getBannerNotices, needsAppInstall } from "../account-status";
23
23
  import { runOfferedAction } from "../account/action-flow";
24
24
  import { platformSupportsAddRepo } from "../new-project/add-repo-modal";
25
+ import { canOpenProject, getPlatform } from "../platform";
25
26
  import { getProjectList } from "../project-list";
26
27
  import { reactive } from "../reactivity";
27
28
  import { MINUTE, now } from "../services/clock";
@@ -226,7 +227,9 @@ function project(): Omit<WelcomeScope, "run" | "open" | "remove" | "clear" | "no
226
227
  // One entry, not two: the starter gallery IS the first step of New Project now, so the
227
228
  // Separate "Start from an Example…" button it used to hide behind is gone.
228
229
  { icon: "plus", id: "new", title: "New Project…" },
229
- { icon: "folder-open", id: "open", title: "Open Project…" },
230
+ ...(canOpenProject(getPlatform())
231
+ ? [{ icon: "folder-open", id: "open", title: "Open Project…" } as const]
232
+ : []),
230
233
  ...(platformSupportsClone()
231
234
  ? [{ icon: "download-simple", id: "clone", title: "Clone Git Repository…" } as const]
232
235
  : []),
@@ -42,10 +42,22 @@
42
42
  * standing between a mixed selection and a half-applied, unrecorded delete.
43
43
  */
44
44
 
45
- import { isNumber } from "@jxsuite/schema/guards";
45
+ import { isAncestor } from "@jxsuite/schema/doc-ops";
46
46
 
47
47
  import type { JxPath } from "../state";
48
- import { isAncestor, pathsEqual } from "../state";
48
+ import { pathsEqual } from "../state";
49
+
50
+ /* The structural-path helpers live beside the doc-op vocabulary, so any host that edits a document
51
+ the way Studio does orders and filters a batch the same way. Re-exported here, where every
52
+ Studio caller already imports them. */
53
+ export {
54
+ comparePaths,
55
+ isSpliceablePath,
56
+ spliceCoordinate,
57
+ structuralBatch,
58
+ topLevelSelection,
59
+ } from "@jxsuite/schema/doc-ops";
60
+ export type { SpliceCoordinate } from "@jxsuite/schema/doc-ops";
49
61
 
50
62
  /**
51
63
  * The path single-target surfaces address — the most recently added, or `null` when empty.
@@ -171,113 +183,6 @@ export function pruneSelection(selection: readonly JxPath[], removed: JxPath): J
171
183
  return clonePaths(selection.filter((p) => !isAncestor(removed, p)));
172
184
  }
173
185
 
174
- /**
175
- * Order two paths the way the document reads: parents before children, siblings by index.
176
- *
177
- * Segments are compared pairwise; numbers numerically (so `children/10` follows `children/9`), and
178
- * anything else by string order. A prefix sorts before what extends it.
179
- *
180
- * @param {JxPath} a
181
- * @param {JxPath} b
182
- * @returns {number}
183
- */
184
- export function comparePaths(a: JxPath, b: JxPath): number {
185
- const len = Math.min(a.length, b.length);
186
- for (let i = 0; i < len; i++) {
187
- const x = a[i]!;
188
- const y = b[i]!;
189
- if (x === y) {
190
- continue;
191
- }
192
- if (isNumber(x) && isNumber(y)) {
193
- return x - y;
194
- }
195
- return String(x) < String(y) ? -1 : 1;
196
- }
197
- return a.length - b.length;
198
- }
199
-
200
- /**
201
- * Drop any path whose ancestor is also selected.
202
- *
203
- * Deleting a `<section>` and one of its own paragraphs is one deletion, not two: the paragraph goes
204
- * with its parent, and a second splice into coordinates that no longer exist is how a batch
205
- * corrupts a document.
206
- *
207
- * @param {readonly JxPath[]} selection
208
- * @returns {JxPath[]}
209
- */
210
- export function topLevelSelection(selection: readonly JxPath[]): JxPath[] {
211
- return clonePaths(
212
- selection.filter((p) => !selection.some((q) => !pathsEqual(q, p) && isAncestor(q, p))),
213
- );
214
- }
215
-
216
- /**
217
- * Whether `path` names a position a structural verb can splice — a tail of `"children"` + index.
218
- *
219
- * Every structural mutator (`mutateRemoveNode`, `mutateDuplicateNode`, `mutateWrapNode`) reaches
220
- * its parent with `parentElementPath` — drop the last two segments — and its position with
221
- * `childIndex` — take the last. That produces a splice coordinate for exactly one path shape, and
222
- * the Outline emits three others as first-class rows: the document element (`[]`), a repeater's map
223
- * template (`[…, "children", 1, "map"]`) and a `$switch` case (`[…, "cases", "warn"]`). For the
224
- * template, `parentElementPath` resolves to the enclosing children ARRAY, whose `.children` is
225
- * `undefined`, so the splice throws mid-transaction; for a case it splices at `NaN` and takes the
226
- * wrong child.
227
- *
228
- * Selecting one of those beside an ordinary element is a single ctrl-click, so this is the
229
- * invariant that keeps a batch from mutating the document and then throwing before anything is
230
- * recorded.
231
- *
232
- * @param {JxPath} path
233
- * @returns {boolean}
234
- */
235
- export function isSpliceablePath(path: JxPath): boolean {
236
- return spliceCoordinate(path) !== null;
237
- }
238
-
239
- /** Where a structural verb splices: the parent element's path and the index in its children. */
240
- export interface SpliceCoordinate {
241
- parentPath: JxPath;
242
- index: number;
243
- }
244
-
245
- /**
246
- * The splice coordinate `path` names, or `null` for no path and for every path
247
- * {@link isSpliceablePath} refuses.
248
- *
249
- * @param {JxPath | null | undefined} path
250
- * @returns {SpliceCoordinate | null}
251
- */
252
- export function spliceCoordinate(path: JxPath | null | undefined): SpliceCoordinate | null {
253
- const index = path?.at(-1);
254
- return path && path.at(-2) === "children" && isNumber(index)
255
- ? { index, parentPath: path.slice(0, -2) }
256
- : null;
257
- }
258
-
259
- /**
260
- * The order a structural batch must run in: contained paths dropped, unspliceable paths dropped,
261
- * then LAST node first.
262
- *
263
- * Descending document order is what makes a loop of splices safe. Removing or duplicating at index
264
- * `n` renumbers every sibling after `n`, so a batch that starts at the end never touches a
265
- * coordinate a later step still needs. It is also why duplicating a multi-selection inserts each
266
- * clone directly after its own original instead of walking the whole run forward.
267
- *
268
- * Containment is decided BEFORE spliceability, on the selection the user actually made: a selected
269
- * repeater template still shadows its own descendants, so selecting the template and something
270
- * inside it yields nothing rather than quietly deleting the child the user never pointed at.
271
- *
272
- * @param {readonly JxPath[]} selection
273
- * @returns {JxPath[]} Deepest/last first, every entry a splice coordinate.
274
- */
275
- export function structuralBatch(selection: readonly JxPath[]): JxPath[] {
276
- return topLevelSelection(selection)
277
- .filter((path) => isSpliceablePath(path))
278
- .toSorted((a, b) => comparePaths(b, a));
279
- }
280
-
281
186
  /**
282
187
  * Deduplicate a list of paths, keeping the FIRST occurrence of each.
283
188
  *
package/src/tabs/tab.ts CHANGED
@@ -132,7 +132,7 @@ export interface StoredLivePreview {
132
132
  error: string | null;
133
133
  }
134
134
 
135
- interface HistorySnapshot {
135
+ export interface HistorySnapshot {
136
136
  /** Full document at this state (checkpoint), or null when the ops describe the transition. */
137
137
  document: JxMutableNode | null;
138
138
  /** Selection after this state's transaction — the whole set, so a batch undoes back to it. */
@@ -153,6 +153,13 @@ interface HistorySnapshot {
153
153
  * back through the prose one pause at a time instead of undoing the edit.
154
154
  */
155
155
  coalesceKey?: string | null;
156
+ /**
157
+ * Who made this step, when it is not the person at the keyboard: the id of the actor whose batch
158
+ * lane closed it (specs/ai.md §3.3). Absent on a person's own edits.
159
+ */
160
+ actor?: string;
161
+ /** The turn whose batch this step is, beside {@link HistorySnapshot.actor}. */
162
+ turnId?: string;
156
163
  }
157
164
 
158
165
  /**
@@ -186,6 +193,8 @@ export interface Tab {
186
193
  scope: {
187
194
  stop: () => void;
188
195
  run: <T>(fn: () => T) => T | undefined;
196
+ /** False once {@link disposeTab} stopped it: how a record that outlives the tab can tell. */
197
+ readonly active?: boolean;
189
198
  };
190
199
  doc: {
191
200
  document: JxMutableNode;