isorite 0.6.0 → 0.8.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 (227) hide show
  1. package/README.md +4 -2
  2. package/dist/main.js +615 -96
  3. package/docs/setup-for-agents.md +3 -0
  4. package/docs/setup.md +20 -0
  5. package/docs/terminal.md +15 -0
  6. package/package.json +1 -1
  7. package/plugin/server.ts +4 -2
  8. package/web/_app/immutable/assets/0.CTbO0owi.css +1 -0
  9. package/web/_app/immutable/assets/NoteEditor.8wWtZK_A.css +1 -0
  10. package/web/_app/immutable/chunks/0M1khDs2.js +2 -0
  11. package/web/_app/immutable/chunks/2b4Gb5sE.js +1 -0
  12. package/web/_app/immutable/chunks/84nbDH0I.js +1 -0
  13. package/web/_app/immutable/chunks/8H7HVH4w.js +1 -0
  14. package/web/_app/immutable/chunks/8wVGBFSs.js +45 -0
  15. package/web/_app/immutable/chunks/AF4Hwhju.js +1 -0
  16. package/web/_app/immutable/chunks/B-65IYX-.js +6 -0
  17. package/web/_app/immutable/chunks/B45xgp2S.js +1 -0
  18. package/web/_app/immutable/chunks/B78wSQJQ.js +1 -0
  19. package/web/_app/immutable/chunks/B85D_PlV.js +5 -0
  20. package/web/_app/immutable/chunks/B91efLFT.js +673 -0
  21. package/web/_app/immutable/chunks/B9NhS7L_.js +1 -0
  22. package/web/_app/immutable/chunks/B9YLdnR_.js +1 -0
  23. package/web/_app/immutable/chunks/BEHa2uNW.js +1 -0
  24. package/web/_app/immutable/chunks/BEMVcqjs.js +1 -0
  25. package/web/_app/immutable/chunks/BEjRio4Q.js +1 -0
  26. package/web/_app/immutable/chunks/BEn0Cwpx.js +1 -0
  27. package/web/_app/immutable/chunks/BGbXxNFa.js +2 -0
  28. package/web/_app/immutable/chunks/BGfruO1u.js +1 -0
  29. package/web/_app/immutable/chunks/BHEdbvp9.js +1 -0
  30. package/web/_app/immutable/chunks/BKJx4mf9.js +1 -0
  31. package/web/_app/immutable/chunks/BL3mLym4.js +5 -0
  32. package/web/_app/immutable/chunks/BLp5-jwC.js +1 -0
  33. package/web/_app/immutable/chunks/BN6xhG8e.js +1 -0
  34. package/web/_app/immutable/chunks/BOVgKnng.js +1 -0
  35. package/web/_app/immutable/chunks/BPTmHmyx.js +1 -0
  36. package/web/_app/immutable/chunks/BPW39P52.js +1 -0
  37. package/web/_app/immutable/chunks/BRKS-CCX.js +5 -0
  38. package/web/_app/immutable/chunks/BRsoo5mP.js +1 -0
  39. package/web/_app/immutable/chunks/BSzj6Xyz.js +1 -0
  40. package/web/_app/immutable/chunks/BUZw3wgd.js +1 -0
  41. package/web/_app/immutable/chunks/BV0c4Hdn.js +1 -0
  42. package/web/_app/immutable/chunks/BVIeWmwY.js +1 -0
  43. package/web/_app/immutable/chunks/BWonG38x.js +1 -0
  44. package/web/_app/immutable/chunks/BXxG0Yva.js +1 -0
  45. package/web/_app/immutable/chunks/BZmnMkFw.js +1 -0
  46. package/web/_app/immutable/chunks/BZyAS7I4.js +1 -0
  47. package/web/_app/immutable/chunks/B_1Jd8HM.js +1 -0
  48. package/web/_app/immutable/chunks/Bbt0DTmZ.js +1 -0
  49. package/web/_app/immutable/chunks/Bffqt7tx.js +9 -0
  50. package/web/_app/immutable/chunks/BflJGmDX.js +1 -0
  51. package/web/_app/immutable/chunks/BgczuZKA.js +4 -0
  52. package/web/_app/immutable/chunks/BguXRgvc.js +2 -0
  53. package/web/_app/immutable/chunks/BjPiibIz.js +1 -0
  54. package/web/_app/immutable/chunks/BkSkiI9m.js +1 -0
  55. package/web/_app/immutable/chunks/Bq5hCPe1.js +2 -0
  56. package/web/_app/immutable/chunks/BrGmYbJT.js +2 -0
  57. package/web/_app/immutable/chunks/BrXPCJaM.js +1 -0
  58. package/web/_app/immutable/chunks/BrqJKzQU.js +2 -0
  59. package/web/_app/immutable/chunks/Brxvm_b1.js +4 -0
  60. package/web/_app/immutable/chunks/BsLNwr5A.js +2 -0
  61. package/web/_app/immutable/chunks/Bsdphhtm.js +1 -0
  62. package/web/_app/immutable/chunks/Bt6PBEDR.js +1 -0
  63. package/web/_app/immutable/chunks/BtUtjfIh.js +1 -0
  64. package/web/_app/immutable/chunks/BthdM_4N.js +1 -0
  65. package/web/_app/immutable/chunks/Bu8mWwtz.js +1 -0
  66. package/web/_app/immutable/chunks/BunrC_8P.js +4 -0
  67. package/web/_app/immutable/chunks/BwTct_5T.js +1 -0
  68. package/web/_app/immutable/chunks/BxTFtVqu.js +13 -0
  69. package/web/_app/immutable/chunks/BzI3yqBQ.js +3 -0
  70. package/web/_app/immutable/chunks/C-dfcq9W.js +7 -0
  71. package/web/_app/immutable/chunks/C2OyD2Y6.js +1 -0
  72. package/web/_app/immutable/chunks/C2dAgh7P.js +1 -0
  73. package/web/_app/immutable/chunks/C357ku5U.js +1 -0
  74. package/web/_app/immutable/chunks/C4jVoHG1.js +1 -0
  75. package/web/_app/immutable/chunks/C6Btt2Xs.js +1 -0
  76. package/web/_app/immutable/chunks/C9zpzAZU.js +1 -0
  77. package/web/_app/immutable/chunks/CCiHXe5d.js +1 -0
  78. package/web/_app/immutable/chunks/CDAjAYjS.js +1 -0
  79. package/web/_app/immutable/chunks/CETSRtnV.js +2 -0
  80. package/web/_app/immutable/chunks/CEufJyoj.js +1 -0
  81. package/web/_app/immutable/chunks/CGzfi9Nl.js +1 -0
  82. package/web/_app/immutable/chunks/CHbKgHy3.js +1 -0
  83. package/web/_app/immutable/chunks/CKSRTWxH.js +2 -0
  84. package/web/_app/immutable/chunks/CLVUDRRk.js +2 -0
  85. package/web/_app/immutable/chunks/CLVgA2GD.js +1 -0
  86. package/web/_app/immutable/chunks/CO2Y5y64.js +2 -0
  87. package/web/_app/immutable/chunks/CPF5uNy5.js +1 -0
  88. package/web/_app/immutable/chunks/CQadubS9.js +2 -0
  89. package/web/_app/immutable/chunks/CQfgDRfP.js +1 -0
  90. package/web/_app/immutable/chunks/CRIvE37b.js +2 -0
  91. package/web/_app/immutable/chunks/CRzLMJPS.js +1 -0
  92. package/web/_app/immutable/chunks/CTIncYxj.js +1 -0
  93. package/web/_app/immutable/chunks/CTscJvp6.js +2 -0
  94. package/web/_app/immutable/chunks/CUpZ7dEl.js +1 -0
  95. package/web/_app/immutable/chunks/CX-xckzu.js +4 -0
  96. package/web/_app/immutable/chunks/CXXgtw0p.js +1 -0
  97. package/web/_app/immutable/chunks/CZ8wTb_M.js +8 -0
  98. package/web/_app/immutable/chunks/C_eisQCp.js +1 -0
  99. package/web/_app/immutable/chunks/CdY1YAit.js +2 -0
  100. package/web/_app/immutable/chunks/Ce_hS451.js +1 -0
  101. package/web/_app/immutable/chunks/CgA1WoRv.js +1 -0
  102. package/web/_app/immutable/chunks/CggtIYAM.js +1 -0
  103. package/web/_app/immutable/chunks/ChfNwlCY.js +1 -0
  104. package/web/_app/immutable/chunks/ChtP43wD.js +1 -0
  105. package/web/_app/immutable/chunks/CiVjR8gq.js +3 -0
  106. package/web/_app/immutable/chunks/CjI9Y6RI.js +1 -0
  107. package/web/_app/immutable/chunks/ClOhbFR7.js +1 -0
  108. package/web/_app/immutable/chunks/CnP_Si9g.js +1 -0
  109. package/web/_app/immutable/chunks/CnaYoQib.js +1 -0
  110. package/web/_app/immutable/chunks/Co3PbHdE.js +1 -0
  111. package/web/_app/immutable/chunks/CoyQYage.js +1 -0
  112. package/web/_app/immutable/chunks/Cp5ooBfH.js +1 -0
  113. package/web/_app/immutable/chunks/Cp_JbuGh.js +2 -0
  114. package/web/_app/immutable/chunks/Cq5C893B.js +1 -0
  115. package/web/_app/immutable/chunks/CqHI_q-D.js +1 -0
  116. package/web/_app/immutable/chunks/CqftRsjg.js +1 -0
  117. package/web/_app/immutable/chunks/Cshzl8qu.js +1 -0
  118. package/web/_app/immutable/chunks/Cx3rYX2g.js +1 -0
  119. package/web/_app/immutable/chunks/CxDmU1I4.js +1 -0
  120. package/web/_app/immutable/chunks/CygcOYNo.js +1 -0
  121. package/web/_app/immutable/chunks/CzMBulkp.js +1 -0
  122. package/web/_app/immutable/chunks/D1ij_kNL.js +1 -0
  123. package/web/_app/immutable/chunks/D2Q8cUp1.js +1 -0
  124. package/web/_app/immutable/chunks/D4SKbj9K.js +6 -0
  125. package/web/_app/immutable/chunks/D4c0_ac3.js +1 -0
  126. package/web/_app/immutable/chunks/D4fLEcOQ.js +8 -0
  127. package/web/_app/immutable/chunks/D5EjA2JK.js +1 -0
  128. package/web/_app/immutable/chunks/D5jiJvOU.js +1 -0
  129. package/web/_app/immutable/chunks/D6QVYUu9.js +1 -0
  130. package/web/_app/immutable/chunks/D6RzAl64.js +1 -0
  131. package/web/_app/immutable/chunks/D8WpQPaX.js +1 -0
  132. package/web/_app/immutable/chunks/D9f6rSsU.js +1 -0
  133. package/web/_app/immutable/chunks/D9qYN0V5.js +1 -0
  134. package/web/_app/immutable/chunks/DCl_8MCW.js +1 -0
  135. package/web/_app/immutable/chunks/DECNRCf2.js +1 -0
  136. package/web/_app/immutable/chunks/DHLQUbN7.js +1 -0
  137. package/web/_app/immutable/chunks/DIGvaVgJ.js +2 -0
  138. package/web/_app/immutable/chunks/DKB6_1j6.js +1 -0
  139. package/web/_app/immutable/chunks/DKNOMI4Y.js +1 -0
  140. package/web/_app/immutable/chunks/DLa7NjNO.js +1 -0
  141. package/web/_app/immutable/chunks/DMAYXnpq.js +13 -0
  142. package/web/_app/immutable/chunks/DNzd62Q_.js +1 -0
  143. package/web/_app/immutable/chunks/DPCEkfD_.js +1 -0
  144. package/web/_app/immutable/chunks/DTF72YNO.js +1 -0
  145. package/web/_app/immutable/chunks/DTdlqvEb.js +12 -0
  146. package/web/_app/immutable/chunks/DTszW0OQ.js +1 -0
  147. package/web/_app/immutable/chunks/DWkQyNnU.js +1 -0
  148. package/web/_app/immutable/chunks/DXiLM1-G.js +1 -0
  149. package/web/_app/immutable/chunks/DZYaqCwi.js +1 -0
  150. package/web/_app/immutable/chunks/D_19S_Iu.js +1 -0
  151. package/web/_app/immutable/chunks/D_JZ0swv.js +1 -0
  152. package/web/_app/immutable/chunks/D_aBVMvK.js +11 -0
  153. package/web/_app/immutable/chunks/DciMwFJ1.js +1 -0
  154. package/web/_app/immutable/chunks/De_zI4J0.js +1 -0
  155. package/web/_app/immutable/chunks/DgAKBe0r.js +1 -0
  156. package/web/_app/immutable/chunks/DgZW3JtG.js +1 -0
  157. package/web/_app/immutable/chunks/Di_ac7WN.js +1 -0
  158. package/web/_app/immutable/chunks/DjVOVvNP.js +1 -0
  159. package/web/_app/immutable/chunks/Dp_KdFgX.js +1 -0
  160. package/web/_app/immutable/chunks/Ds8Pkiic.js +1 -0
  161. package/web/_app/immutable/chunks/DtbnBenW.js +1 -0
  162. package/web/_app/immutable/chunks/DuQMFDvy.js +1 -0
  163. package/web/_app/immutable/chunks/DwuoZtxw.js +1 -0
  164. package/web/_app/immutable/chunks/DxLkKbK2.js +5 -0
  165. package/web/_app/immutable/chunks/Dz-mlCAC.js +1 -0
  166. package/web/_app/immutable/chunks/EQVRMnZU.js +2 -0
  167. package/web/_app/immutable/chunks/GwMd3KmG.js +1 -0
  168. package/web/_app/immutable/chunks/K_3X9lyC.js +23 -0
  169. package/web/_app/immutable/chunks/{ZRnI3PpK.js → NmqB5ax3.js} +1 -1
  170. package/web/_app/immutable/chunks/PPSTk1ow.js +1 -0
  171. package/web/_app/immutable/chunks/S7ucR4pR.js +1 -0
  172. package/web/_app/immutable/chunks/TuFg0KqZ.js +4 -0
  173. package/web/_app/immutable/chunks/VlUiK86J.js +1 -0
  174. package/web/_app/immutable/chunks/_rHYGvON.js +1 -0
  175. package/web/_app/immutable/chunks/bcQIrid8.js +1 -0
  176. package/web/_app/immutable/chunks/dvrIP-Hm.js +1 -0
  177. package/web/_app/immutable/chunks/gfMMiTNf.js +1 -0
  178. package/web/_app/immutable/chunks/gpDK5sbt.js +1 -0
  179. package/web/_app/immutable/chunks/hOAYc4Tf.js +1 -0
  180. package/web/_app/immutable/chunks/kS471soi.js +1 -0
  181. package/web/_app/immutable/chunks/mbLqADPr.js +1 -0
  182. package/web/_app/immutable/chunks/nMarIA7J.js +3 -0
  183. package/web/_app/immutable/chunks/oBAE_ms0.js +1 -0
  184. package/web/_app/immutable/chunks/p9eYesGn.js +1 -0
  185. package/web/_app/immutable/chunks/qgSLujhx.js +1 -0
  186. package/web/_app/immutable/chunks/spNKdrPy.js +3 -0
  187. package/web/_app/immutable/chunks/tLWDfEkZ.js +1 -0
  188. package/web/_app/immutable/chunks/u8YMmbU0.js +1 -0
  189. package/web/_app/immutable/chunks/vk5Wkr31.js +1 -0
  190. package/web/_app/immutable/chunks/wCTXGvXm.js +2 -0
  191. package/web/_app/immutable/chunks/wj7G6pU5.js +5 -0
  192. package/web/_app/immutable/chunks/xvONpD0Z.js +1 -0
  193. package/web/_app/immutable/chunks/yxd8jSz3.js +1 -0
  194. package/web/_app/immutable/entry/app.DO9lj7h3.js +2 -0
  195. package/web/_app/immutable/entry/start.Bg5TZs6n.js +1 -0
  196. package/web/_app/immutable/nodes/0.N1e1uAFq.js +26 -0
  197. package/web/_app/immutable/nodes/1.BRTPLKyU.js +1 -0
  198. package/web/_app/immutable/nodes/2.BWh1AYEd.js +1 -0
  199. package/web/_app/immutable/nodes/3.Cgqgn7Zv.js +1 -0
  200. package/web/_app/immutable/nodes/4.DYW7Fxdy.js +2 -0
  201. package/web/_app/immutable/nodes/5.Dkvkif0E.js +5 -0
  202. package/web/_app/immutable/nodes/6.BinB0fuq.js +1 -0
  203. package/web/_app/immutable/nodes/{7.BdTE6U4H.js → 7.BxQaF3lD.js} +1 -1
  204. package/web/_app/version.json +1 -1
  205. package/web/index.html +22 -18
  206. package/web/_app/immutable/assets/0.LEIHnkPh.css +0 -1
  207. package/web/_app/immutable/assets/NoteEditor.BCjOff8L.css +0 -1
  208. package/web/_app/immutable/chunks/2DHY0Dzh.js +0 -666
  209. package/web/_app/immutable/chunks/BQRMn_tl.js +0 -1
  210. package/web/_app/immutable/chunks/C-352d8F.js +0 -1
  211. package/web/_app/immutable/chunks/CHxNLteG.js +0 -1
  212. package/web/_app/immutable/chunks/Cf6h5DpS.js +0 -20
  213. package/web/_app/immutable/chunks/D4A7-5k0.js +0 -1
  214. package/web/_app/immutable/chunks/Dijhflxl.js +0 -1
  215. package/web/_app/immutable/chunks/Ps0IPbdL.js +0 -1
  216. package/web/_app/immutable/chunks/nhmVyQdD.js +0 -3
  217. package/web/_app/immutable/chunks/rTqzqD6j.js +0 -1
  218. package/web/_app/immutable/chunks/y24aTX4O.js +0 -1
  219. package/web/_app/immutable/entry/app.BQ0r6jSu.js +0 -2
  220. package/web/_app/immutable/entry/start.CIQiYrSU.js +0 -1
  221. package/web/_app/immutable/nodes/0.zPrOkn79.js +0 -26
  222. package/web/_app/immutable/nodes/1.DfBkOAo2.js +0 -1
  223. package/web/_app/immutable/nodes/2.fp8Y5joG.js +0 -1
  224. package/web/_app/immutable/nodes/3.Do9cwxjS.js +0 -1
  225. package/web/_app/immutable/nodes/4.CGd6gzGJ.js +0 -2
  226. package/web/_app/immutable/nodes/5.Dtac-HK8.js +0 -5
  227. package/web/_app/immutable/nodes/6._-kZkWYe.js +0 -1
package/dist/main.js CHANGED
@@ -1622,7 +1622,7 @@ function locate$1(note, p) {
1622
1622
  }
1623
1623
  /** The note with a proposal written in. For a rewrite, `region` writes that one alone; without it, every region
1624
1624
  * still open. Null when the place (or, for a rewrite, any of the places asked for) cannot be found. */
1625
- function written(note, p, region) {
1625
+ function written$1(note, p, region) {
1626
1626
  if (p.form === "rewrite") {
1627
1627
  const places = (region !== void 0 ? [region] : p.open ?? p.regions.map((_, k) => k)).map((k) => ({
1628
1628
  k,
@@ -2665,6 +2665,12 @@ something at one place in the note they have open.`;
2665
2665
  - Maths is LaTeX between dollar signs: $p(x)$ in a line, and a displayed
2666
2666
  equation as a paragraph of its own, with $$ alone on the line before it
2667
2667
  and $$ alone on the line after.
2668
+ - Code is a fenced block named with its language (\`\`\`python), so it is
2669
+ coloured by it.
2670
+ - A diagram is a fenced block in the language mermaid (Mermaid's syntax), and
2671
+ is drawn as the diagram. Draw one where a picture of how things stand to
2672
+ each other helps, or when asked; not by habit. Maths in a label goes
2673
+ between $$ and $$.
2668
2674
  - Ordinary Markdown otherwise. No HTML.`;
2669
2675
  FORMAT_RULES = FORMAT;
2670
2676
  LOOKUP = (rounds, meaning) => `# Looking things up
@@ -2698,6 +2704,43 @@ say plainly what you would need to look up to say more.`;
2698
2704
  }));
2699
2705
  //#endregion
2700
2706
  //#region packages/engine/src/lookup.ts
2707
+ /** Which base a call names, and its arguments for that base: `base`, or a note's id written `memory:tools/x`. */
2708
+ function baseOf(args) {
2709
+ const { base, ...rest } = args;
2710
+ let named = typeof base === "string" ? base.trim() : "";
2711
+ for (const k of ["id", "note"]) {
2712
+ const m = typeof rest[k] === "string" ? /^([a-z0-9][a-z0-9-]{0,39}):(?!\/\/)(.+)$/.exec(rest[k]) : null;
2713
+ if (m) {
2714
+ named ||= m[1];
2715
+ if (m[1] === named) rest[k] = m[2];
2716
+ }
2717
+ }
2718
+ return {
2719
+ base: named,
2720
+ args: rest
2721
+ };
2722
+ }
2723
+ /** The tools' definitions with `base`, for a request that has other bases lent to it. */
2724
+ function withBase(defs, names, always = false) {
2725
+ if (!names.length && !always) return defs;
2726
+ const base = {
2727
+ type: "string",
2728
+ description: `Another knowledge base lent to you, by name: ${names.length ? names.join(", ") : "one brief_base names"}. This base when not given. A note there may also be named <base>:folder/name.`
2729
+ };
2730
+ return defs.map((d) => ({
2731
+ ...d,
2732
+ function: {
2733
+ ...d.function,
2734
+ parameters: {
2735
+ ...d.function.parameters,
2736
+ properties: {
2737
+ ...d.function.parameters.properties,
2738
+ base
2739
+ }
2740
+ }
2741
+ }
2742
+ }));
2743
+ }
2701
2744
  /** A note's id from what a model writes for one: /basics/entropy.md, basics/entropy, [x](/basics/entropy.md). */
2702
2745
  function noteId(b, raw) {
2703
2746
  const id = String(raw ?? "").trim().replace(/^.*\]\(/, "").replace(/\)$/, "").replace(/\s+"[^"]*"$/, "").split("#")[0].replace(/^\/+/, "").replace(/\.md$/i, "");
@@ -2773,17 +2816,19 @@ var init_lookup = __esmMin((() => {
2773
2816
  named = /* @__PURE__ */ new Map();
2774
2817
  b;
2775
2818
  similar;
2776
- constructor(b, note, similar = null) {
2819
+ away;
2820
+ constructor(b, note, similar = null, away = null) {
2777
2821
  this.b = b;
2778
2822
  this.held = [note];
2779
2823
  this.similar = similar;
2824
+ this.away = away;
2780
2825
  }
2781
2826
  defs() {
2782
- return this.similar ? [
2827
+ return withBase(this.similar ? [
2783
2828
  TOOLS[0],
2784
2829
  FIND_SIMILAR,
2785
2830
  ...TOOLS.slice(1)
2786
- ] : TOOLS;
2831
+ ] : TOOLS, this.away?.lent().map((l) => l.name) ?? []);
2787
2832
  }
2788
2833
  /** The notes opened, each once, in order. */
2789
2834
  read() {
@@ -2803,6 +2848,19 @@ var init_lookup = __esmMin((() => {
2803
2848
  notes: []
2804
2849
  };
2805
2850
  let text;
2851
+ const there = baseOf(args);
2852
+ if (there.base) {
2853
+ try {
2854
+ text = await this.elsewhere(name, there.base, there.args, step);
2855
+ } catch (err) {
2856
+ text = this.fail(step, `That could not be looked up: ${err.message}`);
2857
+ }
2858
+ this.steps.push(step);
2859
+ return {
2860
+ text,
2861
+ step
2862
+ };
2863
+ }
2806
2864
  try {
2807
2865
  text = name === "search_notes" ? this.search(args, step) : name === "find_similar" ? await this.meaning(args, step) : name === "outline_note" ? this.outline(args, step) : name === "read_note" ? this.section(args, step) : this.fail(step, `There is no tool ${name}.`);
2808
2866
  } catch (err) {
@@ -2814,6 +2872,22 @@ var init_lookup = __esmMin((() => {
2814
2872
  step
2815
2873
  };
2816
2874
  }
2875
+ /** A look in another base lent to the request: what that base gives, said as being from there. */
2876
+ async elsewhere(name, base, args, step) {
2877
+ const lent = this.away?.lent().map((l) => l.name) ?? [];
2878
+ if (!lent.includes(base)) return this.fail(step, lent.length ? `There is no base ${base} lent here: the bases are ${lent.join(", ")}. Leave base out for this one.` : `There is no base ${base} lent here: leave base out.`);
2879
+ const note = typeof args.id === "string" ? args.id.replace(/^\/+/, "").replace(/\.md$/i, "") : void 0;
2880
+ step.how = name === "search_notes" ? "keyword" : name === "find_similar" ? "meaning" : "read";
2881
+ step.away = {
2882
+ base,
2883
+ ...note ? { note } : {},
2884
+ ...typeof args.section === "string" && args.section ? { section: args.section } : {}
2885
+ };
2886
+ const query = String(args.query ?? args.text ?? "").trim();
2887
+ if (query) step.query = query;
2888
+ step.said = note ? `Read ${note} in ${base}` : query ? `Searched ${base} for "${query}"` : `Looked in ${base}`;
2889
+ return this.away.look(base, name, args);
2890
+ }
2817
2891
  fail(step, why) {
2818
2892
  step.failed = true;
2819
2893
  step.said = why;
@@ -3141,6 +3215,7 @@ function turnMessages(a) {
3141
3215
  const held = reads.length ? `# Notes you read earlier in this conversation\n\n${reads.map(sentText).join("\n\n")}` : "";
3142
3216
  const aside = a.aside?.length ? `# Read earlier, set aside\n\n${a.aside.map((n) => `- ${n.title} (/${n.id}.md)`).join("\n")}` : "";
3143
3217
  const agents = a.agents?.trim() ? `# Their instructions for this knowledge base (AGENTS.md)\n\n${a.agents.trim()}` : "";
3218
+ const lent = a.lookup && a.lent?.length ? `# Other knowledge bases lent to you\n\nYou may read these with the same look-up tools, giving their base; you change notes only in this one. Look in them where what is asked may be there: how the person works, their tools, what was settled before.\n\n${a.lent.map((l) => `- ${l.name}: ${l.about}`).join("\n")}` : "";
3144
3219
  const known = understood.length ? `# Their understanding, as you have recorded it\n\n${understood.map(sentText).join("\n\n")}` : "";
3145
3220
  const broken = a.broken?.length ? `# Code links that no longer resolve\n\nSince you were last told, these links in their notes point to files no longer in their repository. Mention it only where it bears on what they ask.\n\n${a.broken.map((b) => `- in ${b.title} (/${b.id}.md): ${b.href}`).join("\n")}` : "";
3146
3221
  const request = `# What they ask now\n\n${a.asked}`;
@@ -3155,6 +3230,7 @@ function turnMessages(a) {
3155
3230
  role: "user",
3156
3231
  content: [
3157
3232
  agents,
3233
+ lent,
3158
3234
  earlier,
3159
3235
  known,
3160
3236
  gave,
@@ -3168,7 +3244,7 @@ function turnMessages(a) {
3168
3244
  {
3169
3245
  name: "instructions and tools",
3170
3246
  detail: `${a.mode} mode${checking ? ", checking" : ""}${a.lookup ? "" : ", no look-ups"}`,
3171
- tokens: estimate(system) + (a.lookup ? 500 : 0) + turnTools(a.mode, a.sent).length * 120
3247
+ tokens: estimate(system) + estimate(lent) + (a.lookup ? 500 : 0) + turnTools(a.mode, a.sent).length * 120
3172
3248
  },
3173
3249
  {
3174
3250
  name: "earlier in this conversation",
@@ -3743,7 +3819,7 @@ function keepDone(before, next) {
3743
3819
  return next;
3744
3820
  }
3745
3821
  }
3746
- var SIGN_IN_MS, REPLY_TOKENS, NOTE_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
3822
+ var SIGN_IN_MS, fromBase, REPLY_TOKENS, NOTE_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
3747
3823
  var init_engine = __esmMin((() => {
3748
3824
  init_src$1();
3749
3825
  init_api();
@@ -3756,6 +3832,7 @@ var init_engine = __esmMin((() => {
3756
3832
  init_meaning();
3757
3833
  init_provider();
3758
3834
  SIGN_IN_MS = 6e5;
3835
+ fromBase = (base, text) => `(From the base ${base}: name its notes ${base}:<id>, or give base "${base}".)\n${text}`;
3759
3836
  REPLY_TOKENS = 2500;
3760
3837
  NOTE_TOKENS = 6e3;
3761
3838
  DRAFT_PREFIX = "@draft/";
@@ -4017,7 +4094,7 @@ var init_engine = __esmMin((() => {
4017
4094
  }));
4018
4095
  }
4019
4096
  async agentTools() {
4020
- return [
4097
+ const tools = [
4021
4098
  BRIEF,
4022
4099
  TOOLS[0],
4023
4100
  ...this.similar ? [FIND_SIMILAR] : [],
@@ -4025,9 +4102,48 @@ var init_engine = __esmMin((() => {
4025
4102
  EDIT_NOTE,
4026
4103
  WRITE_NOTE
4027
4104
  ];
4105
+ return this.opts.elsewhere ? withBase(tools, this.lent().map((l) => l.name), true) : tools;
4106
+ }
4107
+ /** The bases lent to this one's sessions, not this one. */
4108
+ lent() {
4109
+ return (this.opts.elsewhere?.lent() ?? []).filter((l) => l.name !== this.opts.name);
4110
+ }
4111
+ /** Another base lent here, by name, or why there is none of that name. */
4112
+ other(base) {
4113
+ const lent = this.lent();
4114
+ const engine = lent.some((l) => l.name === base) ? this.opts.elsewhere?.base(base) : null;
4115
+ if (!engine) throw new EngineError(lent.length ? `there is no base ${base} lent here: the bases are ${lent.map((l) => l.name).join(", ")}; leave base out for ${this.opts.name}` : `there is no base ${base} lent here: leave base out`, 404);
4116
+ return engine;
4117
+ }
4118
+ /** For Iso: a look in the other bases lent here, as their own look-ups give it, drawn on no Atlas. */
4119
+ get away() {
4120
+ if (!this.opts.elsewhere) return null;
4121
+ return {
4122
+ lent: () => this.lent(),
4123
+ look: async (base, tool, args) => fromBase(base, (await new Lookup(this.other(base).bundle, "", this.other(base).similar).call(tool, JSON.stringify(args))).text)
4124
+ };
4125
+ }
4126
+ /** What this base is, in a line: its AGENTS.md's first line of text, else its name and size. */
4127
+ async about() {
4128
+ return ((await this.agents()).text ?? "").split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l && !l.startsWith("---")) ?? `${this.bundle.concepts.size} notes`;
4028
4129
  }
4029
4130
  async agentLook({ agent, session, tool, args, asked = "" }) {
4030
- if (tool === BRIEF.function.name) return { text: this.brief() };
4131
+ const there = baseOf(args);
4132
+ if (there.base && there.base !== this.opts.name) {
4133
+ const done = await this.other(there.base).agentLook({
4134
+ agent,
4135
+ session,
4136
+ tool,
4137
+ args: there.args,
4138
+ asked
4139
+ });
4140
+ return { text: fromBase(there.base, done.text) };
4141
+ }
4142
+ args = there.args;
4143
+ if (tool === BRIEF.function.name) {
4144
+ const lent = this.lent();
4145
+ return { text: this.brief() + (lent.length ? `\n\nOther knowledge bases lent to you, each with its own practice (brief_base with its base, before you use it):\n${lent.map((l) => `- ${l.name}: ${l.about}`).join("\n")}\nPass base to any tool to look or write there.` : "") };
4146
+ }
4031
4147
  if (tool === EDIT_NOTE.function.name || tool === WRITE_NOTE.function.name) {
4032
4148
  const str = (v, name) => {
4033
4149
  if (typeof v !== "string") throw new EngineError(`${tool} needs ${name}, as text`, 400);
@@ -4069,6 +4185,7 @@ var init_engine = __esmMin((() => {
4069
4185
  session
4070
4186
  });
4071
4187
  }
4188
+ if (!busy) for (const l of this.lent()) await this.opts.elsewhere?.base(l.name)?.agentStatus(agent, session, false);
4072
4189
  }
4073
4190
  async agentWrite({ agent, session, note, asked, edit }) {
4074
4191
  const who = `agent:${agent}`;
@@ -4594,7 +4711,7 @@ var init_engine = __esmMin((() => {
4594
4711
  tier: req.tier,
4595
4712
  place
4596
4713
  });
4597
- const lookup = new Lookup(this.bundle, req.note, this.similar);
4714
+ const lookup = new Lookup(this.bundle, req.note, this.similar, this.away);
4598
4715
  (async () => {
4599
4716
  let ran = null, offered = false;
4600
4717
  try {
@@ -5197,6 +5314,7 @@ var init_engine = __esmMin((() => {
5197
5314
  sent: [...sent, ...reads],
5198
5315
  aside,
5199
5316
  ...agentsText ? { agents: agentsText } : {},
5317
+ ...this.lent().length ? { lent: this.lent() } : {},
5200
5318
  asked: req.asked ?? ""
5201
5319
  });
5202
5320
  const ratio = hand.ratio ?? 1, scaled = (n) => Math.round(n * ratio);
@@ -5545,6 +5663,7 @@ var init_engine = __esmMin((() => {
5545
5663
  sent,
5546
5664
  aside: w.aside,
5547
5665
  ...w.agentsText ? { agents: w.agentsText } : {},
5666
+ ...this.lent().length ? { lent: this.lent() } : {},
5548
5667
  asked: req.asked,
5549
5668
  broken: await this.brokenNews("iso")
5550
5669
  });
@@ -5594,7 +5713,7 @@ var init_engine = __esmMin((() => {
5594
5713
  });
5595
5714
  };
5596
5715
  const who = `iso:${this.provider.name}/${this.provider.tiers[req.tier]}`;
5597
- const lookup = new Lookup(this.bundle, req.given.find((g) => g.kind !== "folder")?.id ?? "", this.similar);
5716
+ const lookup = new Lookup(this.bundle, req.given.find((g) => g.kind !== "folder")?.id ?? "", this.similar, this.away);
5598
5717
  const looks = new Set(lookup.defs().map((d) => d.function.name));
5599
5718
  const tools = {
5600
5719
  defs: () => [...lookupOn ? lookup.defs() : [], ...own],
@@ -5605,7 +5724,7 @@ var init_engine = __esmMin((() => {
5605
5724
  try {
5606
5725
  args = JSON.parse(raw || "{}");
5607
5726
  } catch {}
5608
- const id = noteId(this.bundle, args.id), want = typeof args.section === "string" ? args.section.replace(/^#+\s*/, "").trim() : "";
5727
+ const id = args.base ? null : noteId(this.bundle, args.id), want = typeof args.section === "string" ? args.section.replace(/^#+\s*/, "").trim() : "";
5609
5728
  const held = id ? inHand.get(id) : void 0;
5610
5729
  if (id && held && (held === "whole" || want && held.includes(want))) {
5611
5730
  const step = {
@@ -5629,6 +5748,8 @@ var init_engine = __esmMin((() => {
5629
5748
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) args = parsed;
5630
5749
  } catch {}
5631
5750
  if (!own.some((d) => d.function.name === name)) return { text: `There is no tool ${name} in ${mode} mode.` };
5751
+ const there = baseOf(args);
5752
+ if (there.base && there.base !== this.opts.name) return { text: `Notes are changed only in ${this.opts.name}, the base you are in: ${there.base} is lent to you to read.` };
5632
5753
  return { text: await this.turnTool(name, args, {
5633
5754
  turn,
5634
5755
  conversation,
@@ -6218,10 +6339,10 @@ var init_engine = __esmMin((() => {
6218
6339
  let text;
6219
6340
  if (p.form === "rewrite") {
6220
6341
  if (region !== void 0 && !p.open?.includes(region)) throw new EngineError("that change is no longer waiting: it was kept or removed", 409);
6221
- text = written(now, p, region);
6342
+ text = written$1(now, p, region);
6222
6343
  if (text === null) throw new EngineError(region === void 0 ? "a passage this would rewrite is no longer in the note as it was. Keep the others one by one." : "the passage this would rewrite is no longer in the note as it was", 409);
6223
6344
  } else {
6224
- text = written(now, p) ?? written(now, {
6345
+ text = written$1(now, p) ?? written$1(now, {
6225
6346
  ...p,
6226
6347
  after: null,
6227
6348
  before: null
@@ -6774,6 +6895,62 @@ function gatewayTlsFrom(config, dir, env = process.env) {
6774
6895
  async function hasModel$1(config = configPath()) {
6775
6896
  return await new ConfigSecrets(config).get("provider.key") !== null || providerConfig(config).url !== OPENROUTER.url;
6776
6897
  }
6898
+ const BASE_NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
6899
+ const expand = (p) => p === "~" ? homedir() : p.startsWith("~/") ? join(homedir(), p.slice(2)) : resolve(p);
6900
+ /** A path as written in the file: under the home folder, with ~. */
6901
+ const written = (p) => {
6902
+ const home = homedir();
6903
+ return p === home ? "~" : p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
6904
+ };
6905
+ /** Where the bases are listed, beside the configuration. */
6906
+ const basesPath = (config = configPath()) => join(dirname(config), "bases.toml");
6907
+ /** The bases listed, in the order written. A row without a path, or with a name isorite
6908
+ * would not give one, is passed over. */
6909
+ function knownBases(config = configPath()) {
6910
+ return Object.entries(readConfig(basesPath(config))).flatMap(([name, v]) => {
6911
+ const row = table(v);
6912
+ return BASE_NAME.test(name) && typeof row.path === "string" && row.path ? [{
6913
+ name,
6914
+ path: expand(row.path),
6915
+ lend: row.lend === true
6916
+ }] : [];
6917
+ });
6918
+ }
6919
+ /** A name for a base from its folder: small letters, digits and dashes. */
6920
+ function baseName(folder) {
6921
+ return basename(folder).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "base";
6922
+ }
6923
+ /** The base at `folder` by its name in the configuration, listed first if it is not: under its folder's name, or
6924
+ * that name with -2, -3… where another folder has it. `as` names it, and `lend` lends it. */
6925
+ function rememberBase(folder, { path = configPath(), as, lend } = {}) {
6926
+ const at = resolve(folder), had = knownBases(path).find((b) => b.path === at);
6927
+ if (had && lend === void 0) return had.name;
6928
+ const bases = readConfig(basesPath(path));
6929
+ let name = had?.name;
6930
+ if (!name) {
6931
+ const stem = as && BASE_NAME.test(as) ? as : baseName(at), taken = new Set(Object.keys(bases));
6932
+ name = stem;
6933
+ for (let n = 2; taken.has(name); n++) name = `${stem.slice(0, 36)}-${n}`;
6934
+ }
6935
+ bases[name] = {
6936
+ ...table(bases[name]),
6937
+ path: written(at),
6938
+ ...lend === void 0 ? {} : { lend }
6939
+ };
6940
+ writeConfig(bases, basesPath(path));
6941
+ return name;
6942
+ }
6943
+ /** Lend a known base to every session, or stop lending it. False when there is no base of that name. */
6944
+ function lendBase(name, lend, path = configPath()) {
6945
+ const bases = readConfig(basesPath(path));
6946
+ if (!BASE_NAME.test(name) || !bases[name]) return false;
6947
+ bases[name] = {
6948
+ ...table(bases[name]),
6949
+ lend
6950
+ };
6951
+ writeConfig(bases, basesPath(path));
6952
+ return true;
6953
+ }
6777
6954
  //#endregion
6778
6955
  //#region packages/server/src/model.ts
6779
6956
  /** Where the server looks for the model, in order, and why each place is there. */
@@ -6815,7 +6992,7 @@ function modelInfo(dir) {
6815
6992
  } catch {}
6816
6993
  const graph = sha(readFileSync(join(dir, "model.onnx"))), granite = graph === FILES[0][2];
6817
6994
  const pooling = own.pooling === "mean" ? "mean" : "cls";
6818
- const name = typeof own.name === "string" && own.name ? own.name : granite ? NAME$1 : basename(dir);
6995
+ const name = typeof own.name === "string" && own.name ? own.name : granite ? NAME$2 : basename(dir);
6819
6996
  return {
6820
6997
  dir,
6821
6998
  name,
@@ -6997,11 +7174,11 @@ async function modelCommand(from, say = console.log, env = process.env, sources
6997
7174
  return 1;
6998
7175
  }
6999
7176
  }
7000
- var NAME$1, MODEL, RELEASE, PACKAGE, HUB, FILES, sha, userModelsDir, userModelDir, hasModel, ModelFetchError, modelFiles;
7177
+ var NAME$2, MODEL, RELEASE, PACKAGE, HUB, FILES, sha, userModelsDir, userModelDir, hasModel, ModelFetchError, modelFiles;
7001
7178
  var init_model = __esmMin((() => {
7002
7179
  init_src();
7003
- NAME$1 = "granite-embedding-small-english-r2";
7004
- MODEL = `${NAME$1}-q8`;
7180
+ NAME$2 = "granite-embedding-small-english-r2";
7181
+ MODEL = `${NAME$2}-q8`;
7005
7182
  RELEASE = "https://github.com/lachlanjs/isorite/releases/download/model-granite-1.0.0/isorite-model-granite-1.0.0.tgz";
7006
7183
  PACKAGE = "isorite-model-granite@1.0.0";
7007
7184
  HUB = `https://huggingface.co/onnx-community/granite-embedding-small-english-r2-ONNX/resolve/1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8`;
@@ -7029,7 +7206,7 @@ var init_model = __esmMin((() => {
7029
7206
  ];
7030
7207
  sha = (buf) => createHash("sha256").update(buf).digest("hex");
7031
7208
  userModelsDir = (env = process.env) => join(env.XDG_DATA_HOME || join(homedir(), ".local", "share"), "isorite", "models");
7032
- userModelDir = (env = process.env) => join(userModelsDir(env), NAME$1);
7209
+ userModelDir = (env = process.env) => join(userModelsDir(env), NAME$2);
7033
7210
  hasModel = (dir) => existsSync(join(dir, "model.onnx")) && existsSync(join(dir, "tokenizer.json"));
7034
7211
  ModelFetchError = class extends Error {
7035
7212
  tried;
@@ -7217,7 +7394,132 @@ function relay(live, key) {
7217
7394
  };
7218
7395
  }
7219
7396
  //#endregion
7397
+ //#region packages/server/src/bases.ts
7398
+ const NAME$1 = /^[a-z0-9][a-z0-9-]{0,39}$/;
7399
+ var Bases = class {
7400
+ first;
7401
+ #config;
7402
+ #make;
7403
+ #open = /* @__PURE__ */ new Map();
7404
+ #ready = /* @__PURE__ */ new Map();
7405
+ #about = /* @__PURE__ */ new Map();
7406
+ #holds = 0;
7407
+ #idle;
7408
+ /** `make` opens a base; `idle` is called when the last hold is let go (a server isorite term started). */
7409
+ constructor({ config, first, make, idle }) {
7410
+ this.#config = config;
7411
+ this.first = first;
7412
+ this.#make = make;
7413
+ this.#idle = idle;
7414
+ }
7415
+ /** The first base, and every lent one. */
7416
+ async start() {
7417
+ await this.get(this.first);
7418
+ await Promise.all(knownBases(this.#config).filter((b) => b.lend).map((b) => this.get(b.name)?.catch((e) => console.error(`isorite: ${b.name} (${b.path}) could not be opened: ${e.message}`))));
7419
+ }
7420
+ /** A known base, opened if it is not yet; null where none has that name. */
7421
+ get(name) {
7422
+ const had = this.#open.get(name);
7423
+ if (had) return had;
7424
+ const known = NAME$1.test(name) ? knownBases(this.#config).find((b) => b.name === name) : void 0;
7425
+ if (!known) return null;
7426
+ const opening = this.#make(known).then(async (opened) => {
7427
+ this.#ready.set(name, opened);
7428
+ this.#about.set(name, await opened.engine.about());
7429
+ opened.engine.events((e) => {
7430
+ if (e.type === "note-changed") opened.engine.about().then((a) => this.#about.set(name, a));
7431
+ });
7432
+ return opened;
7433
+ });
7434
+ opening.catch(() => this.#open.delete(name));
7435
+ this.#open.set(name, opening);
7436
+ return opening;
7437
+ }
7438
+ /** Every open base, for what is kept into use in all of them (the provider). */
7439
+ each(fn) {
7440
+ for (const o of this.#ready.values()) fn(o.engine);
7441
+ }
7442
+ lent() {
7443
+ return knownBases(this.#config).filter((b) => b.lend && this.#ready.has(b.name)).map((b) => ({
7444
+ name: b.name,
7445
+ about: this.#about.get(b.name) ?? ""
7446
+ }));
7447
+ }
7448
+ base(name) {
7449
+ return this.#ready.get(name)?.engine ?? null;
7450
+ }
7451
+ list() {
7452
+ return knownBases(this.#config).map((b) => ({
7453
+ name: b.name,
7454
+ path: b.path,
7455
+ lend: b.lend,
7456
+ open: this.#ready.has(b.name)
7457
+ }));
7458
+ }
7459
+ async lend(name, on) {
7460
+ if (!lendBase(name, on, this.#config)) return false;
7461
+ if (on) await this.get(name);
7462
+ return true;
7463
+ }
7464
+ async open(path) {
7465
+ const name = rememberBase(resolve(path), { path: this.#config });
7466
+ await this.get(name);
7467
+ return {
7468
+ name,
7469
+ first: name === this.first
7470
+ };
7471
+ }
7472
+ hold() {
7473
+ this.#holds++;
7474
+ let let_go = false;
7475
+ return () => {
7476
+ if (let_go) return;
7477
+ let_go = true;
7478
+ if (--this.#holds === 0) setTimeout(() => {
7479
+ if (this.#holds === 0) this.#idle?.();
7480
+ }, 1e3).unref();
7481
+ };
7482
+ }
7483
+ /** A request to the server: to a base's API by its address, /b/<name>/api/…, or to the first. A base's page is
7484
+ * the app's own page with ?base=<name> (SvelteKit's hash router needs its page at the root it was built for):
7485
+ * /b/<name> is sent there. */
7486
+ async fetch(req, env) {
7487
+ const url = new URL(req.url);
7488
+ const m = /^\/b\/([^/]+)(\/.*)?$/.exec(url.pathname);
7489
+ if (!m || !NAME$1.test(m[1])) return (await this.get(this.first)).fetch(req, env);
7490
+ const name = m[1];
7491
+ if (!m[2]?.startsWith("/api/")) return new Response(null, {
7492
+ status: 302,
7493
+ headers: { Location: `/?base=${name}${m[2]?.startsWith("/#") ? m[2].slice(1) : ""}` }
7494
+ });
7495
+ const opening = this.get(name);
7496
+ if (!opening) return new Response(JSON.stringify({ error: `no base ${name} is known here: isorite bases lists them` }), {
7497
+ status: 404,
7498
+ headers: { "Content-Type": "application/json" }
7499
+ });
7500
+ let opened;
7501
+ try {
7502
+ opened = await opening;
7503
+ } catch (e) {
7504
+ return new Response(JSON.stringify({ error: `${name} could not be opened: ${e.message}` }), {
7505
+ status: 500,
7506
+ headers: { "Content-Type": "application/json" }
7507
+ });
7508
+ }
7509
+ url.pathname = m[2];
7510
+ const body = req.method === "GET" || req.method === "HEAD" ? null : req.body;
7511
+ return opened.fetch(new Request(url, {
7512
+ method: req.method,
7513
+ headers: req.headers,
7514
+ body,
7515
+ signal: req.signal,
7516
+ ...body ? { duplex: "half" } : {}
7517
+ }), env);
7518
+ }
7519
+ };
7520
+ //#endregion
7220
7521
  //#region packages/server/src/terminal.ts
7522
+ init_src();
7221
7523
  const OUTPUT = 4e3;
7222
7524
  const LOOKUPS = /* @__PURE__ */ new Set([
7223
7525
  "search_notes",
@@ -7228,6 +7530,7 @@ const LOOKUPS = /* @__PURE__ */ new Set([
7228
7530
  ]);
7229
7531
  /** isorite's tools that change a note: their line opens it, where the agent's words are marked. */
7230
7532
  const CHANGES = /* @__PURE__ */ new Set(["edit_note", "write_note"]);
7533
+ const isorite = (name) => LOOKUPS.has(name) || CHANGES.has(name);
7231
7534
  /** What a tool call acted on, in a line. */
7232
7535
  function titleOf(name, input = {}) {
7233
7536
  const s = (k) => typeof input[k] === "string" ? input[k] : "";
@@ -7275,16 +7578,21 @@ function toTurns(messages, { live = /* @__PURE__ */ new Map(), ours = /* @__PURE
7275
7578
  const input = c.state.input ?? {};
7276
7579
  const output = (c.state.content ?? []).map((x) => x.text ?? "").join("").slice(0, OUTPUT);
7277
7580
  const status = c.state.status === "completed" ? "done" : /error|fail/.test(c.state.status) ? "failed" : "running";
7581
+ const { base, args } = isorite(c.name) ? baseOf(input) : {
7582
+ base: "",
7583
+ args: input
7584
+ };
7278
7585
  const item = {
7279
7586
  kind: "tool",
7280
7587
  id: c.id,
7281
7588
  name: c.name,
7282
- title: titleOf(c.name, input),
7589
+ title: (base ? `${base}: ` : "") + titleOf(c.name, args),
7283
7590
  status,
7284
7591
  output
7285
7592
  };
7286
- const named = LOOKUPS.has(c.name) ? input.id : CHANGES.has(c.name) ? input.note : void 0;
7593
+ const named = LOOKUPS.has(c.name) ? args.id : CHANGES.has(c.name) ? args.note : void 0;
7287
7594
  if (typeof named === "string" && named) item.note = named.replace(/^\//, "").replace(/\.md$/, "");
7595
+ if (base) item.base = base;
7288
7596
  const diff = diffOf(c.name, input);
7289
7597
  if (diff) item.diff = diff;
7290
7598
  turn.items.push(item);
@@ -7716,7 +8024,7 @@ const AgentStatus = z.object({
7716
8024
  session: z.string().min(1).max(200),
7717
8025
  busy: z.boolean()
7718
8026
  }).strict();
7719
- function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal, gateway, setup }) {
8027
+ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal, gateway, setup, bases, base }) {
7720
8028
  const app = new Hono();
7721
8029
  const allowed = new Set([...allowHosts].map((h) => h.toLowerCase()));
7722
8030
  const reply = (c, status, value) => c.body(JSON.stringify(value), status, {
@@ -7919,9 +8227,52 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
7919
8227
  post("/api/settings/provider/models", z.object({ draft: SetupDraft.nullable() }).strict(), async (c, b) => ({ models: await setup.models(local(c) ? b.draft : null) }));
7920
8228
  post("/api/settings/report", z.object({}).strict(), async () => ({ text: await setup.report() }));
7921
8229
  }
8230
+ if (bases) {
8231
+ app.get("/api/bases", (c) => answer(c, async () => ({
8232
+ here: base ?? "",
8233
+ local: local(c),
8234
+ bases: bases.list()
8235
+ })));
8236
+ const mine = (c) => {
8237
+ if (!local(c)) throw new EngineError("the bases are changed only from this machine", 403);
8238
+ };
8239
+ post("/api/bases/lend", z.object({
8240
+ name: z.string().max(40),
8241
+ lend: z.boolean()
8242
+ }).strict(), async (c, b) => {
8243
+ mine(c);
8244
+ if (!await bases.lend(b.name, b.lend)) throw new EngineError(`no base ${b.name} is known here`, 404);
8245
+ return { bases: bases.list() };
8246
+ });
8247
+ post("/api/bases/open", z.object({ path: z.string().min(1).max(4e3) }).strict(), async (c, b) => {
8248
+ mine(c);
8249
+ if (!isAbsolute(b.path) || !existsSync(b.path) || !statSync(b.path).isDirectory()) throw new EngineError(`no folder at ${b.path}`, 404);
8250
+ return bases.open(b.path);
8251
+ });
8252
+ app.get("/api/server/hold", (c) => {
8253
+ if (!local(c)) return refuse(c, 403, "held only from this machine");
8254
+ return streamSSE(c, async (stream) => {
8255
+ const release = bases.hold();
8256
+ const beat = setInterval(() => {
8257
+ stream.write(": held\n\n");
8258
+ }, 25e3);
8259
+ await stream.writeSSE({
8260
+ event: "held",
8261
+ data: "{}"
8262
+ });
8263
+ await new Promise((done) => stream.onAbort(done));
8264
+ clearInterval(beat);
8265
+ release();
8266
+ });
8267
+ });
8268
+ }
7922
8269
  app.get("/api/agent/tools", (c) => answer(c, () => api.agentTools()));
7923
8270
  post("/api/agent/look", AgentLook, (_c, b) => api.agentLook(b));
7924
- app.get("/api/agent/base", (c) => answer(c, async () => ({ notes: notes ?? null })));
8271
+ app.get("/api/agent/base", (c) => answer(c, async () => ({
8272
+ notes: notes ?? null,
8273
+ name: base ?? null,
8274
+ lent: (bases?.list() ?? []).filter((b) => b.lend && b.name !== base).map((b) => b.name)
8275
+ })));
7925
8276
  post("/api/agent/write", AgentChange, (_c, b) => api.agentWrite(b));
7926
8277
  post("/api/agent/send", z.object({
7927
8278
  note: z.string().max(400),
@@ -8362,7 +8713,7 @@ var Setup = class {
8362
8713
  };
8363
8714
  //#endregion
8364
8715
  //#region packages/server/src/version.ts
8365
- const VERSION = "0.6.0";
8716
+ const VERSION = "0.8.0";
8366
8717
  //#endregion
8367
8718
  //#region packages/server/src/studybase.ts
8368
8719
  init_src$1();
@@ -8433,15 +8784,16 @@ function newBase(path) {
8433
8784
  makeOwn(at);
8434
8785
  return at;
8435
8786
  }
8436
- async function openEngine({ notes }, device = deviceId(), config = configPath(), http, model = modelDir(), secrets = new ConfigSecrets(config)) {
8787
+ async function openEngine({ notes }, device = deviceId(), config = configPath(), http, model = modelDir(), secrets = new ConfigSecrets(config), extras = {}) {
8437
8788
  const provider = providerConfig(config);
8438
8789
  const own = makeOwn(notes);
8790
+ const embedder = extras.embedder !== void 0 ? extras.embedder : model ? nodeEmbedder(model) : null;
8439
8791
  return Engine.open({
8440
8792
  notes: new NodeFiles(notes),
8441
8793
  own: new NodeFiles(own),
8442
8794
  clock: systemClock,
8443
8795
  device,
8444
- name: basename(notes),
8796
+ name: extras.name ?? basename(notes),
8445
8797
  http: http ?? gatewayHttp(provider, gatewayTls(config)),
8446
8798
  secrets,
8447
8799
  provider,
@@ -8449,13 +8801,14 @@ async function openEngine({ notes }, device = deviceId(), config = configPath(),
8449
8801
  const dir = resolve(notes, path);
8450
8802
  return existsSync(dir) && statSync(dir).isDirectory() ? new NodeFiles(dir) : null;
8451
8803
  },
8452
- ...model ? {
8453
- embedder: nodeEmbedder(model),
8804
+ ...embedder ? {
8805
+ embedder,
8454
8806
  cache: {
8455
8807
  files: new NodeFiles(cacheDir()),
8456
8808
  name: `vectors-${createHash("sha256").update(notes).digest("hex").slice(0, 16)}.json`
8457
8809
  }
8458
- } : {}
8810
+ } : {},
8811
+ ...extras.elsewhere ? { elsewhere: extras.elsewhere } : {}
8459
8812
  });
8460
8813
  }
8461
8814
  /** The guide's finder, as a provider: no key, no sign-in, and a name that says what it is. */
@@ -8480,16 +8833,49 @@ async function liveProvider(config, stand) {
8480
8833
  }
8481
8834
  /** A link most terminals let you click (OSC 8), when the output is a terminal; the address as it is otherwise. */
8482
8835
  const link$1 = (url, tty = Boolean(process.stdout.isTTY)) => tty ? `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\` : url;
8483
- /** `stand`: in place of the provider, an answerer of its own, as the guide's finder is when no model is set up
8484
- * (M16). It is asked while there is no model here (no key, no provider of your own), and needs no key. */
8485
- async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [], stand } = {}) {
8836
+ /** The server's bases, opened: the one named (remembered in bases.toml, M20) and every lent one, each with its
8837
+ * engine and app, and the server's answer to a request for any of them. `stand`: in place of the provider, an
8838
+ * answerer of its own, as the guide's finder is when no model is set up (M16), asked while there is no model here.
8839
+ * `idle`: what to do once the last of those holding the server lets it go. */
8840
+ async function openServer(path, { host = "127.0.0.1", port = 4767, allowHosts = [], stand, idle, config = configPath(), model = modelDir() } = {}) {
8486
8841
  const opened = locate(path);
8487
- const config = configPath();
8488
8842
  const current = () => liveProvider(config, stand);
8489
8843
  let live = await current();
8490
- const secrets = new ConfigSecrets(config);
8491
- const api = await openEngine(opened, deviceId(config), config, live.http, modelDir(), secrets);
8492
- api.setProvider(live.provider, live.http);
8844
+ const secrets = new ConfigSecrets(config), device = deviceId(config);
8845
+ const first = rememberBase(opened.notes, { path: config });
8846
+ const embedder = model ? nodeEmbedder(model) : null;
8847
+ const token = randomBytes(24).toString("base64url"), loopback = LOOPBACK.has(host);
8848
+ const gateway = loopback ? relay(() => live, () => secrets.get(KEY)) : void 0;
8849
+ const web = existsSync(join(WEB_DIR, "index.html")) ? WEB_DIR : null;
8850
+ const bases = new Bases({
8851
+ config,
8852
+ first,
8853
+ ...idle ? { idle } : {},
8854
+ make: async (known) => {
8855
+ const api = await openEngine({ notes: known.path }, device, config, live.http, model, secrets, {
8856
+ name: known.name,
8857
+ elsewhere: bases,
8858
+ embedder
8859
+ });
8860
+ api.setProvider(live.provider, live.http);
8861
+ return {
8862
+ engine: api,
8863
+ fetch: createApp({
8864
+ api,
8865
+ token,
8866
+ loopback,
8867
+ web,
8868
+ allowHosts,
8869
+ notes: known.path,
8870
+ terminal: new Terminal(),
8871
+ gateway,
8872
+ setup,
8873
+ bases,
8874
+ base: known.name
8875
+ }).fetch
8876
+ };
8877
+ }
8878
+ });
8493
8879
  const setup = new Setup({
8494
8880
  config,
8495
8881
  notes: opened.notes,
@@ -8497,25 +8883,29 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
8497
8883
  port,
8498
8884
  kept: async () => {
8499
8885
  live = await current();
8500
- api.setProvider(live.provider, live.http);
8886
+ bases.each((api) => api.setProvider(live.provider, live.http));
8501
8887
  }
8502
8888
  });
8503
- const gateway = LOOPBACK.has(host) ? relay(() => live, () => secrets.get(KEY)) : void 0;
8504
- const web = existsSync(join(WEB_DIR, "index.html")) ? WEB_DIR : null;
8505
- const terminal = new Terminal();
8506
- const app = createApp({
8507
- api,
8508
- token: randomBytes(24).toString("base64url"),
8509
- loopback: LOOPBACK.has(host),
8510
- web,
8511
- allowHosts,
8889
+ await bases.start();
8890
+ return {
8512
8891
  notes: opened.notes,
8513
- terminal,
8514
- gateway,
8515
- setup
8892
+ bases,
8893
+ embedder: Boolean(embedder),
8894
+ fetch: (req, env) => bases.fetch(req, env)
8895
+ };
8896
+ }
8897
+ /** `held`: started by isorite term or mcp, it stops once the last of them lets it go. */
8898
+ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [], stand, held = false } = {}) {
8899
+ let stop = () => void 0;
8900
+ const { notes, bases, embedder, fetch } = await openServer(path, {
8901
+ host,
8902
+ port,
8903
+ allowHosts,
8904
+ ...stand ? { stand } : {},
8905
+ ...held ? { idle: () => stop() } : {}
8516
8906
  });
8517
8907
  const server = serve({
8518
- fetch: app.fetch,
8908
+ fetch,
8519
8909
  hostname: host,
8520
8910
  port
8521
8911
  });
@@ -8523,11 +8913,13 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
8523
8913
  console.error(e.code === "EADDRINUSE" ? `port ${port} is in use (isorite serve --port <n>)` : e.message);
8524
8914
  process.exit(1);
8525
8915
  });
8526
- console.log(`isorite: ${opened.notes}`);
8527
- console.log(api.meaning ? " search by meaning: on" : " search by meaning: off (isorite model fetches its model)");
8528
- console.log(web ? ` ${link$1(`http://localhost:${port}/`)}` : ` the app is not built (npm run build); the engine alone is at http://localhost:${port}/api/`);
8916
+ console.log(`isorite: ${notes}`);
8917
+ console.log(embedder ? " search by meaning: on" : " search by meaning: off (isorite model fetches its model)");
8918
+ const lent = bases.lent().filter((l) => l.name !== bases.first).map((l) => l.name);
8919
+ if (lent.length) console.log(` lent to every session: ${lent.join(", ")} (isorite bases)`);
8920
+ console.log(existsSync(join(WEB_DIR, "index.html")) ? ` ${link$1(`http://localhost:${port}/`)}` : ` the app is not built (npm run build); the engine alone is at http://localhost:${port}/api/`);
8529
8921
  if (process.env.SSH_CONNECTION && LOOPBACK.has(host)) console.log(` over SSH, the link opens on this machine; from your own, first run: ssh -L ${port}:localhost:${port} ${hostname()}`);
8530
- const stop = () => {
8922
+ stop = () => {
8531
8923
  server.close();
8532
8924
  process.exit(0);
8533
8925
  };
@@ -8563,49 +8955,77 @@ async function serving(url) {
8563
8955
  return null;
8564
8956
  }
8565
8957
  }
8566
- /** The isorite server on the port, and how to let it go: one already running for this base (or for any, when
8567
- * no base is named), or one started for the base, its output kept in a log, stopped when let go. */
8958
+ /** The isorite server on the port, and how to let it go: one already running (for this base, or holding it as
8959
+ * well from now on, M20; or for any, when no base is named), or one started for the base, its output kept in a
8960
+ * log. `url` is the base's own address there. A server this started stops once the last of those using it lets it
8961
+ * go; one you started yourself goes on. */
8568
8962
  async function ensureServer(path, { port = 4767, env = process.env, wait = 12e4 } = {}) {
8569
- const url = `http://127.0.0.1:${port}`;
8963
+ const root = `http://127.0.0.1:${port}`;
8570
8964
  const opened = path === void 0 ? null : locate(path);
8571
- const name = await serving(url);
8572
- if (name !== null) {
8573
- if (opened && name !== basename(opened.notes)) throw new StudyError(`port ${port} is serving ${name}, not ${basename(opened.notes)}: stop it, or use another port (--port <n>)`);
8574
- return {
8575
- url,
8576
- release: () => void 0
8577
- };
8578
- }
8579
- if (!opened) throw new StudyError(`nothing is serving on port ${port}: name a base`);
8580
- const log = join(tmpdir(), `isorite-term-${port}.log`);
8581
- const out = openSync(log, "w");
8582
- const server = spawn(process.execPath, [
8583
- MAIN,
8584
- "serve",
8585
- path,
8586
- "--port",
8587
- String(port)
8588
- ], {
8589
- stdio: [
8590
- "ignore",
8591
- out,
8592
- out
8593
- ],
8594
- env
8595
- });
8596
- const exited = new Promise((done) => server.once("exit", () => done()));
8597
- const started = Date.now();
8598
- while (await serving(url) === null) {
8599
- if (server.exitCode !== null || Date.now() - started > wait) {
8600
- server.kill();
8601
- throw new StudyError(`the isorite server did not start; its output is in ${log}`);
8965
+ let name = await serving(root), started = null;
8966
+ if (name === null) {
8967
+ if (!opened) throw new StudyError(`nothing is serving on port ${port}: name a base`);
8968
+ const log = join(tmpdir(), `isorite-term-${port}.log`);
8969
+ const out = openSync(log, "w");
8970
+ started = spawn(process.execPath, [
8971
+ MAIN,
8972
+ "serve",
8973
+ path,
8974
+ "--port",
8975
+ String(port),
8976
+ "--held"
8977
+ ], {
8978
+ stdio: [
8979
+ "ignore",
8980
+ out,
8981
+ out
8982
+ ],
8983
+ env,
8984
+ detached: true
8985
+ });
8986
+ const exited = new Promise((done) => started.once("exit", () => done()));
8987
+ const begun = Date.now();
8988
+ while ((name = await serving(root)) === null) {
8989
+ if (started.exitCode !== null || Date.now() - begun > wait) {
8990
+ started.kill();
8991
+ throw new StudyError(`the isorite server did not start; its output is in ${log}`);
8992
+ }
8993
+ await Promise.race([exited, new Promise((r) => setTimeout(r, 250))]);
8602
8994
  }
8603
- await Promise.race([exited, new Promise((r) => setTimeout(r, 250))]);
8995
+ started.unref();
8604
8996
  }
8997
+ let url = root;
8998
+ if (opened) {
8999
+ const at = await fetch(`${root}/api/bases/open`, {
9000
+ method: "POST",
9001
+ headers: {
9002
+ "Content-Type": "application/json",
9003
+ "x-isorite-token": await tokenOf(root),
9004
+ Origin: root
9005
+ },
9006
+ body: JSON.stringify({ path: opened.notes })
9007
+ }).catch(() => null);
9008
+ if (at?.ok) {
9009
+ const base = await at.json();
9010
+ if (!base.first) url = `${root}/b/${base.name}`;
9011
+ } else if (name !== basename(opened.notes)) throw new StudyError(`port ${port} is serving ${name}, not ${basename(opened.notes)}: stop it, or use another port (--port <n>)`);
9012
+ }
9013
+ const holding = new AbortController();
9014
+ const held = fetch(`${root}/api/server/hold`, { signal: holding.signal }).then((r) => {
9015
+ if (r.ok && r.body) (async () => {
9016
+ try {
9017
+ for await (const _ of r.body);
9018
+ } catch {}
9019
+ })();
9020
+ return r.ok;
9021
+ }, () => false);
8605
9022
  return {
8606
9023
  url,
8607
9024
  release: () => {
8608
- server.kill();
9025
+ holding.abort();
9026
+ held.then((ok) => {
9027
+ if (!ok && started) started.kill();
9028
+ });
8609
9029
  }
8610
9030
  };
8611
9031
  }
@@ -8626,7 +9046,7 @@ async function tell(url, path, body) {
8626
9046
  headers: {
8627
9047
  "Content-Type": "application/json",
8628
9048
  "x-isorite-token": token,
8629
- Origin: url
9049
+ Origin: new URL(url).origin
8630
9050
  },
8631
9051
  body: JSON.stringify(body)
8632
9052
  });
@@ -8717,9 +9137,9 @@ async function modelsFor(env, server) {
8717
9137
  }
8718
9138
  /** What `isorite term` says before OpenCode takes the screen, and again when it gives it back: where isorite is in
8719
9139
  * the browser, which models the terminal uses and where they come from, and, over SSH, how to reach the browser. */
8720
- function where(port, models, env, tty) {
9140
+ function where(port, models, env, tty, page = "/") {
8721
9141
  const names = models.gateway?.models.join(", ");
8722
- const lines = [`isorite: ${link$1(`http://localhost:${port}/`, tty)}`, models.from === "environment" ? ` the terminal's models: ${names}, at ${models.gateway.url} (ISORITE_GATEWAY_URL)` : models.from === "config" ? ` the terminal's models: ${names}, as config.toml names them, through the isorite server` : " the terminal's models: OpenCode's own, as no key or certificate is named in config.toml"];
9142
+ const lines = [`isorite: ${link$1(`http://localhost:${port}${page}`, tty)}`, models.from === "environment" ? ` the terminal's models: ${names}, at ${models.gateway.url} (ISORITE_GATEWAY_URL)` : models.from === "config" ? ` the terminal's models: ${names}, as config.toml names them, through the isorite server` : " the terminal's models: OpenCode's own, as no key or certificate is named in config.toml"];
8723
9143
  if (env.SSH_CONNECTION) lines.push(` over SSH, the link opens on this machine; from your own, first run: ssh -L ${port}:localhost:${port} ${hostname()}`);
8724
9144
  return lines;
8725
9145
  }
@@ -8746,7 +9166,7 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
8746
9166
  throw e;
8747
9167
  }
8748
9168
  const { gateway, env } = models;
8749
- const said = where(port, models, given);
9169
+ const said = where(port, models, given, void 0, new URL(server.url).pathname.replace(/\/?$/, "/"));
8750
9170
  for (const line of said) console.log(line);
8751
9171
  const agents = path === void 0 ? null : join(locate(path).notes, AGENTS_FILE);
8752
9172
  const home = writeHome(defaultHome(env), {
@@ -9219,7 +9639,7 @@ async function mcp(path, { port = 4767, agent = "mcp", quiet = QUIET_MS, input =
9219
9639
  headers: {
9220
9640
  "Content-Type": "application/json",
9221
9641
  [TOKEN_HEADER]: await token,
9222
- Origin: url
9642
+ Origin: new URL(url).origin
9223
9643
  },
9224
9644
  body: JSON.stringify(body)
9225
9645
  });
@@ -9249,7 +9669,7 @@ async function mcp(path, { port = 4767, agent = "mcp", quiet = QUIET_MS, input =
9249
9669
  name: "isorite",
9250
9670
  version: "0.1.0"
9251
9671
  },
9252
- instructions: "A knowledge base of notes. Call brief_base first, then look things up with search_notes, outline_note and read_note, following a note's links before searching again. Cite a note by its title and section."
9672
+ instructions: "A knowledge base of notes. Call brief_base first, then look things up with search_notes, outline_note and read_note, following a note's links before searching again. Cite a note by its title and section. Other knowledge bases lent to you (the agent's memory among them) are named by brief_base: pass base to the same tools to look or write there, and follow the practice each one's brief gives."
9253
9673
  };
9254
9674
  if (rpc.method === "ping") return {};
9255
9675
  if (rpc.method === "tools/list") return { tools: (await get("/api/agent/tools")).map(({ function: t }) => ({
@@ -10485,6 +10905,98 @@ async function writeGeometry(folder, now = Date.now()) {
10485
10905
  return { notes };
10486
10906
  }
10487
10907
  //#endregion
10908
+ //#region packages/server/src/memory.ts
10909
+ init_src$1();
10910
+ const MEMORY_FOLDER = join(homedir(), "isorite-memory");
10911
+ /** The memory's practice, as the agent is to follow it: read with brief_base, whichever base a session is for. */
10912
+ const MEMORY_AGENTS = `# What you keep across projects: the person, their tools, and what you have learned working with them.
10913
+
10914
+ This knowledge base is your memory. It is lent to every session, whatever project the session is for. Look in it,
10915
+ and keep it, with the knowledge base tools, giving \`base: "memory"\`.
10916
+
10917
+ ## When to look
10918
+
10919
+ - At the start of a task: search for the person, the project's tools and the kind of work.
10920
+ - Before asking the person something they may have told you before.
10921
+
10922
+ ## What to keep
10923
+
10924
+ Keep what will be useful in another project, or in a later session of this one:
10925
+ - **you/**: how the person likes to work, what they have corrected you on or confirmed, and why.
10926
+ - **tools/**: tools, services and machines, and how they are set up and reached.
10927
+ - **practice/**: ways of working that went well or badly, and mistakes not to repeat.
10928
+ - **projects/**: what is going on in each project that its own files don't say: goals, decisions, constraints,
10929
+ with dates as dates.
10930
+
10931
+ Don't keep what a project's own files, notes or history already say, what matters only to this conversation, or
10932
+ anything secret: keys, passwords, tokens.
10933
+
10934
+ ## How to keep it
10935
+
10936
+ - One note for each thing worth keeping: its title says it, its first lines give the fact, then **Why:** and
10937
+ **When it applies:**.
10938
+ - Link it to the notes it touches.
10939
+ - Update the note that is there rather than writing a second one, and delete a note that turns out to be wrong.
10940
+ - Write as you learn, not at the end: the person sees each note you write marked as yours, and can undo it.
10941
+ `;
10942
+ /** Make the memory, or say where it is: the folder (as isorite new makes it), a git repository of its own, its
10943
+ * practice, and its row in bases.toml, lent. */
10944
+ function memory(folder = MEMORY_FOLDER, { config = configPath(), out = console.log } = {}) {
10945
+ const at = resolve(folder), known = knownBases(config);
10946
+ const had = known.find((b) => b.path === at) ?? (folder === MEMORY_FOLDER ? known.find((b) => b.name === "memory") : void 0);
10947
+ if (had && existsSync(join(had.path, "AGENTS.md"))) {
10948
+ out(`the memory is ${had.name}, at ${had.path}: ${had.lend ? "lent to every session" : `not lent (isorite bases lend ${had.name})`}`);
10949
+ return 0;
10950
+ }
10951
+ if (!existsSync(join(at, "AGENTS.md"))) {
10952
+ if (existsSync(at) && readdirSync(at).some((f) => f !== ".isorite" && f !== ".git")) throw new StudyError(`${at} has files in it already, and no ${AGENTS_FILE}: name another folder, isorite memory <folder>`);
10953
+ newBase(at);
10954
+ writeFileSync(join(at, AGENTS_FILE), MEMORY_AGENTS);
10955
+ try {
10956
+ if (!existsSync(join(at, ".git"))) execFileSync("git", ["init", "--quiet"], {
10957
+ cwd: at,
10958
+ stdio: "ignore"
10959
+ });
10960
+ } catch {
10961
+ out(" (git is not here: the memory is not a repository yet)");
10962
+ }
10963
+ out(`isorite memory: made at ${at}, a private repository of its own: push it where you like`);
10964
+ }
10965
+ const name = rememberBase(at, {
10966
+ path: config,
10967
+ as: "memory",
10968
+ lend: true
10969
+ });
10970
+ out(` ${name}: lent to every session's agent and Iso, as listed in ${basesPath(config)}`);
10971
+ out(` watch it learn: isorite serve ${at}, or switch to ${name} in the page of a running server`);
10972
+ return 0;
10973
+ }
10974
+ /** isorite bases: the bases this device knows; `lend <name>` and `keep <name>` (lend it no longer). */
10975
+ function bases(args, { config = configPath(), out = console.log } = {}) {
10976
+ const [verb, name] = args;
10977
+ if (verb === "lend" || verb === "keep") {
10978
+ const known = knownBases(config).find((b) => b.name === name);
10979
+ if (!known) {
10980
+ out(`no base ${name ?? ""} is known: isorite bases lists them`);
10981
+ return 2;
10982
+ }
10983
+ rememberBase(known.path, {
10984
+ path: config,
10985
+ lend: verb === "lend"
10986
+ });
10987
+ out(`${known.name}: ${verb === "lend" ? "lent to every session" : "not lent"}`);
10988
+ return 0;
10989
+ }
10990
+ const known = knownBases(config);
10991
+ if (!known.length) {
10992
+ out("no bases yet: each one you open is listed here (isorite serve, term, mcp); isorite memory makes the agent's memory");
10993
+ return 0;
10994
+ }
10995
+ const width = Math.max(...known.map((b) => b.name.length));
10996
+ for (const b of known) out(`${b.name.padEnd(width)} ${b.lend ? "lent " : " "} ${b.path}${existsSync(b.path) ? "" : " (not there)"}`);
10997
+ return 0;
10998
+ }
10999
+ //#endregion
10488
11000
  //#region packages/server/src/main.ts
10489
11001
  init_src$1();
10490
11002
  init_model();
@@ -10513,6 +11025,10 @@ const USAGE = `isorite: an agentic harness for understanding
10513
11025
  provider (asked a few words, a few hundred tokens), the terminal and
10514
11026
  the notes; --report gives it as one block, secrets hidden, to paste
10515
11027
  to someone (or an AI) helping
11028
+ isorite memory [<folder>] the agent's memory: a knowledge base (~/isorite-memory unless named),
11029
+ a git repository of its own, lent to every session's agent and Iso
11030
+ isorite bases [lend|keep <name>] the bases this device knows, each listed as it is opened; lend one to
11031
+ every session, or keep it to itself
10516
11032
  isorite help setup how isorite is set up, every setting and failure: written for an AI
10517
11033
  helping you set it up (docs/setup.md is for people)
10518
11034
  isorite guide [<folder>] [--port <n>] the guide: a knowledge base about isorite, with things to do in it,
@@ -10551,7 +11067,8 @@ function main(argv) {
10551
11067
  const allowHosts = rest.flatMap((a, i) => a === "--allow-host" && rest[i + 1] ? [rest[i + 1]] : []);
10552
11068
  return serve$1(rest[0], {
10553
11069
  ...port === void 0 ? {} : { port },
10554
- allowHosts
11070
+ allowHosts,
11071
+ held: rest.includes("--held")
10555
11072
  }).then(() => new Promise(() => void 0), (e) => {
10556
11073
  if (e instanceof StudyError) {
10557
11074
  console.error(e.message);
@@ -10607,6 +11124,8 @@ function main(argv) {
10607
11124
  }
10608
11125
  return modelCommand(rest.includes("--from") ? from : void 0);
10609
11126
  }
11127
+ if (command === "memory") return memory(rest.find((a) => !a.startsWith("--")));
11128
+ if (command === "bases") return bases(rest);
10610
11129
  if (command === "--version" || command === "-v") {
10611
11130
  console.log(VERSION);
10612
11131
  return 0;