@alisio/server 0.1.0-alpha.7 → 0.1.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 (178) hide show
  1. package/dist/bridges/interaction-bridge.js +13 -4
  2. package/dist/host/goal.d.ts +36 -0
  3. package/dist/host/goal.js +172 -0
  4. package/dist/host/plan.d.ts +10 -0
  5. package/dist/host/plan.js +45 -0
  6. package/dist/host/presets.d.ts +1 -1
  7. package/dist/host/presets.js +13 -4
  8. package/dist/host/run-scheduler.d.ts +25 -2
  9. package/dist/host/run-scheduler.js +18 -3
  10. package/dist/host/sessions.d.ts +10 -1
  11. package/dist/host/sessions.js +26 -5
  12. package/dist/host/workspace-host.d.ts +19 -4
  13. package/dist/host/workspace-host.js +63 -7
  14. package/dist/http/errors.js +7 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.js +178 -10
  17. package/dist/routes/changelog.d.ts +9 -0
  18. package/dist/routes/changelog.js +32 -0
  19. package/dist/routes/commands.d.ts +8 -0
  20. package/dist/routes/commands.js +137 -12
  21. package/dist/routes/goal.d.ts +21 -0
  22. package/dist/routes/goal.js +121 -0
  23. package/dist/routes/management.d.ts +12 -2
  24. package/dist/routes/management.js +49 -6
  25. package/dist/routes/plugin-views.d.ts +22 -0
  26. package/dist/routes/plugin-views.js +96 -0
  27. package/dist/routes/sessions.js +2 -0
  28. package/dist/routes/tasks.d.ts +5 -0
  29. package/dist/routes/tasks.js +70 -0
  30. package/dist/web/assets/AgentPicker-CQGC_GFI.js +1 -0
  31. package/dist/web/assets/AgentsModal-DUUOJ0cR.js +2 -0
  32. package/dist/web/assets/ArtifactPanel-OVoYusLj.js +2 -0
  33. package/dist/web/assets/BtwPanel-jdZGkYfl.js +1 -0
  34. package/dist/web/assets/ChangelogDialog-2oHdLrua.js +1 -0
  35. package/dist/web/assets/ChangelogDialog-BicA9ah6.css +1 -0
  36. package/dist/web/assets/CopyButton-BuTEL_32.js +1 -0
  37. package/dist/web/assets/GoalBar-CHV3_kfx.css +1 -0
  38. package/dist/web/assets/GoalBar-CIWauuxF.js +1 -0
  39. package/dist/web/assets/MemoryTab-2v0jYGnY.js +2 -0
  40. package/dist/web/assets/MemoryTab-BMdPbUkv.css +1 -0
  41. package/dist/web/assets/PermissionsMenu-BKjKYqlX.js +1 -0
  42. package/dist/web/assets/PlanReviewPanel-C5VRDn0N.js +1 -0
  43. package/dist/web/assets/PlanReviewPanel-DIB4EV2K.css +1 -0
  44. package/dist/web/assets/SettingsModal-Bz9ltMDh.js +1 -0
  45. package/dist/web/assets/SpreadsheetPanel-DK7YBb9S.js +1 -0
  46. package/dist/web/assets/TasksTab-lCCQl7WO.js +1 -0
  47. package/dist/web/assets/TasksTab-xvZOft9V.css +1 -0
  48. package/dist/web/assets/TrajectoryTab-PJHG3yJv.css +1 -0
  49. package/dist/web/assets/TrajectoryTab-qSV6_FbA.js +1 -0
  50. package/dist/web/assets/{abnfDiagram-VCTEODGH-BNuKZo1p.js → abnfDiagram-VCTEODGH-B8huo-Kk.js} +1 -1
  51. package/dist/web/assets/{agents.module-D1wKvaD-.js → agents.module-CdjwYuBf.js} +2 -2
  52. package/dist/web/assets/app-DC-_N2rl.js +3 -0
  53. package/dist/web/assets/architecture-7GRP2DOG-DnGQjmPz.js +1 -0
  54. package/dist/web/assets/{architectureDiagram-5GKGNRK7-Dt_V8yqa.js → architectureDiagram-5GKGNRK7-BqGd8I5j.js} +1 -1
  55. package/dist/web/assets/{blockDiagram-I7D4REHJ-NxVOhPmn.js → blockDiagram-I7D4REHJ-DtQSxJUd.js} +1 -1
  56. package/dist/web/assets/{c4Diagram-7LVT6UL2-DRi00rDC.js → c4Diagram-7LVT6UL2-BuNBp5yh.js} +1 -1
  57. package/dist/web/assets/channel-CEI3etVU.js +1 -0
  58. package/dist/web/assets/{chunk-4HAMMTFA-DmLMKdyN.js → chunk-4HAMMTFA-390Mn_il.js} +1 -1
  59. package/dist/web/assets/{chunk-75Z2AOVW-BR8-_T1K.js → chunk-75Z2AOVW-Chht6Xhu.js} +1 -1
  60. package/dist/web/assets/chunk-DU6HZSFF-CMLEkaIG.js +125 -0
  61. package/dist/web/assets/{chunk-F27PBJKO-CEslv91G.js → chunk-F27PBJKO-SKMSua5B.js} +1 -1
  62. package/dist/web/assets/{chunk-GMAD6QVW-ajMpCrE1.js → chunk-GMAD6QVW-Df-qsmXX.js} +1 -1
  63. package/dist/web/assets/{chunk-GVQU2GXP-Cxv3ClOW.js → chunk-GVQU2GXP-7PgapOOA.js} +1 -1
  64. package/dist/web/assets/{chunk-IMKFNOWR-BrT27IpP.js → chunk-IMKFNOWR-DRbZAlkh.js} +1 -1
  65. package/dist/web/assets/{chunk-L3NEJ4N5-IF_NnfIM.js → chunk-L3NEJ4N5-3E0ssqUP.js} +1 -1
  66. package/dist/web/assets/{chunk-OSK3NFVY-CTNQzsaZ.js → chunk-OSK3NFVY-B1GP22y4.js} +1 -1
  67. package/dist/web/assets/{chunk-P2QGCYS3-C8Mv-VXk.js → chunk-P2QGCYS3-T_PO2L1c.js} +1 -1
  68. package/dist/web/assets/{chunk-POPQ4Y6H-DEyZliGx.js → chunk-POPQ4Y6H-DiREf_on.js} +1 -1
  69. package/dist/web/assets/{chunk-PWAF6VOD-DYoILyGR.js → chunk-PWAF6VOD-yOR4r5Wg.js} +1 -1
  70. package/dist/web/assets/{chunk-SHT3W25Y-BPA5CAVG.js → chunk-SHT3W25Y-LqpptdEy.js} +1 -1
  71. package/dist/web/assets/{chunk-SVP7TREG-DuiGU0RH.js → chunk-SVP7TREG-BkiLiJog.js} +1 -1
  72. package/dist/web/assets/{chunk-TICWLB2K-CPsLIDl-.js → chunk-TICWLB2K-F9gzgGhl.js} +1 -1
  73. package/dist/web/assets/classDiagram-ZZMXUADV-B7WS1Q75.js +1 -0
  74. package/dist/web/assets/classDiagram-v2-VYDZK3BY-B7WS1Q75.js +1 -0
  75. package/dist/web/assets/{cynefin-OW5HDTMX-CfgsLEx3.js → cynefin-OW5HDTMX-B_NT3eSJ.js} +1 -1
  76. package/dist/web/assets/{cynefinDiagram-5FMLGOSQ-CF2Eh0TH.js → cynefinDiagram-5FMLGOSQ-DpNbosPc.js} +1 -1
  77. package/dist/web/assets/{dagre-GXQ25YYZ-DYyidWnQ.js → dagre-GXQ25YYZ-BgaTchbz.js} +1 -1
  78. package/dist/web/assets/{diagram-S7CK7UJ4-DSBPkUpu.js → diagram-S7CK7UJ4-BC0d5yAz.js} +1 -1
  79. package/dist/web/assets/{diagram-UQ7AKVKN-V5xEYTHc.js → diagram-UQ7AKVKN-Bjm2wIY2.js} +1 -1
  80. package/dist/web/assets/{diagram-VSXAHHWV-BECaFuc3.js → diagram-VSXAHHWV-BcCWYw5W.js} +1 -1
  81. package/dist/web/assets/{diagram-VX7I27RA-21c8snpi.js → diagram-VX7I27RA-CzGzOQ_X.js} +1 -1
  82. package/dist/web/assets/{diagram-Z3DM3KII-C35HmHNH.js → diagram-Z3DM3KII-CJZ8iKLl.js} +1 -1
  83. package/dist/web/assets/{ebnfDiagram-PWID7BFC-CITGP-Q9.js → ebnfDiagram-PWID7BFC-DKTo52mJ.js} +1 -1
  84. package/dist/web/assets/{engine-DUxyrccI.js → engine-DE1KkajI.js} +4 -4
  85. package/dist/web/assets/{erDiagram-RLTQ6QDP-BP6F-Cwl.js → erDiagram-RLTQ6QDP-Cssct0LD.js} +1 -1
  86. package/dist/web/assets/es-ra-yzfB-.js +3 -0
  87. package/dist/web/assets/eventmodeling-NTZA5JFV-Cro5xJnm.js +1 -0
  88. package/dist/web/assets/flowDiagram-HODETNUW-BiyE5zC5.js +1 -0
  89. package/dist/web/assets/{ganttDiagram-EL5Y4UJY-Cakc1RCe.js → ganttDiagram-EL5Y4UJY-DO1wNnfK.js} +1 -1
  90. package/dist/web/assets/{gitGraph-4MIJSDKK-CHB16fZf.js → gitGraph-4MIJSDKK-CjTm6VaR.js} +1 -1
  91. package/dist/web/assets/{gitGraphDiagram-WWUBYQGX-CTd8KVPV.js → gitGraphDiagram-WWUBYQGX-DoNpr-oE.js} +1 -1
  92. package/dist/web/assets/goal-Cjg5BNyW.js +1 -0
  93. package/dist/web/assets/{highlight-DJEaplpK.js → highlight-C4F9bqUR.js} +1 -1
  94. package/dist/web/assets/i18n-BX-1yz6D.js +3 -0
  95. package/dist/web/assets/{icons-dCLC5DFa.js → icons-CMUG5AC2.js} +1 -1
  96. package/dist/web/assets/index-BcfooQoH.js +74 -0
  97. package/dist/web/assets/index-CKBbtY1d.css +1 -0
  98. package/dist/web/assets/{info-A6RAGUB7-BCmSXR4J.js → info-A6RAGUB7-D0RLRe9r.js} +1 -1
  99. package/dist/web/assets/{infoDiagram-27XIBGKW-kmLXHL0B.js → infoDiagram-27XIBGKW-Xvh_yq1U.js} +1 -1
  100. package/dist/web/assets/{ishikawaDiagram-5VMMS53U-T3d1OORv.js → ishikawaDiagram-5VMMS53U-CKMcZ3YE.js} +1 -1
  101. package/dist/web/assets/{journeyDiagram-3NMN7TZE-IRnndJxa.js → journeyDiagram-3NMN7TZE-CH4U_78p.js} +1 -1
  102. package/dist/web/assets/jsxRuntime-B6PrrToB.js +1 -0
  103. package/dist/web/assets/{kanban-definition-UXKFOSKX-D81-nCTe.js → kanban-definition-UXKFOSKX-YDba8pTP.js} +1 -1
  104. package/dist/web/assets/{line-BBeV3vfV.js → line-5g6gTA1Q.js} +1 -1
  105. package/dist/web/assets/{mermaid-parser.core-xZ2WHlRI.js → mermaid-parser.core-DgCR7nMS.js} +3 -3
  106. package/dist/web/assets/{mindmap-definition-YA3MSWOX-Ho6e6jVr.js → mindmap-definition-YA3MSWOX-BFhIglWE.js} +1 -1
  107. package/dist/web/assets/{packet-AYTQ26CC-B9pSVRWc.js → packet-AYTQ26CC-Bx6BMrJt.js} +1 -1
  108. package/dist/web/assets/{pegDiagram-XKGWAZYB-CTXTBWF0.js → pegDiagram-XKGWAZYB-DfFISLjH.js} +1 -1
  109. package/dist/web/assets/{pie-WAS4IAKB-Bga9YnnI.js → pie-WAS4IAKB-LV0HnA-W.js} +1 -1
  110. package/dist/web/assets/{pieDiagram-E7YTZNPT-DVVSLq99.js → pieDiagram-E7YTZNPT-CepfvSOf.js} +1 -1
  111. package/dist/web/assets/preload-helper-B3nfOi5I.js +1 -0
  112. package/dist/web/assets/{quadrantDiagram-AXDQQJYC-B83E3PUh.js → quadrantDiagram-AXDQQJYC-xPm2UUev.js} +1 -1
  113. package/dist/web/assets/{radar-RG4KPBEZ-u8zRwuLC.js → radar-RG4KPBEZ-V5htrwTl.js} +1 -1
  114. package/dist/web/assets/{railroad-74A4TZTK-D9v6vBft.js → railroad-74A4TZTK-b1KQjCKl.js} +1 -1
  115. package/dist/web/assets/railroad-abnf-HS5TGJTU-DGo9EEg0.js +1 -0
  116. package/dist/web/assets/railroad-ebnf-LZEXJU2U-pz5FmZJB.js +1 -0
  117. package/dist/web/assets/railroad-peg-WCYAUIDC-CaGbhEvZ.js +1 -0
  118. package/dist/web/assets/{railroadDiagram-O6MQD6OU-qU8L_T9G.js → railroadDiagram-O6MQD6OU-C7eNLTRf.js} +1 -1
  119. package/dist/web/assets/{requirementDiagram-BXWQKSXE-DbGVadzP.js → requirementDiagram-BXWQKSXE-CDkBY9hC.js} +1 -1
  120. package/dist/web/assets/{sankeyDiagram-P5KCCOFB-DjF_6wxm.js → sankeyDiagram-P5KCCOFB-D3kcagKm.js} +1 -1
  121. package/dist/web/assets/{sequenceDiagram-WJ2MYXX4-ACVlOZ_E.js → sequenceDiagram-WJ2MYXX4-4bVDlu88.js} +1 -1
  122. package/dist/web/assets/settings.module-DueWybFY.js +1 -0
  123. package/dist/web/assets/shared-EK9okQVL.js +1 -0
  124. package/dist/web/assets/{stateDiagram-D77RDMKH-gO6dN_6c.js → stateDiagram-D77RDMKH-ByCmUnvJ.js} +1 -1
  125. package/dist/web/assets/stateDiagram-v2-MP3YSRHH-CRiIHTdb.js +1 -0
  126. package/dist/web/assets/{swimlanes-42K2YHIH-DChgukPo.js → swimlanes-42K2YHIH-C836H1FT.js} +1 -1
  127. package/dist/web/assets/swimlanesDiagram-VR7AAH4N-CK7iFVSI.js +8 -0
  128. package/dist/web/assets/tasks-kigp6weV.js +1 -0
  129. package/dist/web/assets/{timeline-definition-24CTP7MA-BOJKAXQm.js → timeline-definition-24CTP7MA-C4pJeiqC.js} +1 -1
  130. package/dist/web/assets/{treeView-Q6P3EWNA-DDk_V5T-.js → treeView-Q6P3EWNA-LZesboxH.js} +1 -1
  131. package/dist/web/assets/{treemap-WGGIJYW6-gSJ9NeJF.js → treemap-WGGIJYW6-Cl6Q6_3w.js} +1 -1
  132. package/dist/web/assets/{vennDiagram-4TSXK5OY-pztSs7q3.js → vennDiagram-4TSXK5OY-Beflgatq.js} +1 -1
  133. package/dist/web/assets/{view-wUX2CVZB.js → view-B1m43xQK.js} +2 -2
  134. package/dist/web/assets/view-BkwTQ5Tx.js +1 -0
  135. package/dist/web/assets/{view-Lqn7oIjv.js → view-BlfB-4-z.js} +1 -1
  136. package/dist/web/assets/view-BqWt6DTt.js +1 -0
  137. package/dist/web/assets/view-CoT7YOtS.js +1 -0
  138. package/dist/web/assets/view-Covt-3G_.js +6 -0
  139. package/dist/web/assets/view-Cvijx5Ut.js +5 -0
  140. package/dist/web/assets/view-OIR43-Re.js +1 -0
  141. package/dist/web/assets/view-lXxKlpOm.js +2 -0
  142. package/dist/web/assets/visible-DLChI0F-.js +1 -0
  143. package/dist/web/assets/{wardley-WFR3VGLG-CZ8Fuv6x.js → wardley-WFR3VGLG-BPBKdRq1.js} +1 -1
  144. package/dist/web/assets/{wardleyDiagram-VM6X3IG4-BPDY9aCw.js → wardleyDiagram-VM6X3IG4-BoH1N_OA.js} +1 -1
  145. package/dist/web/assets/{xychartDiagram-S5SC5T6Z-DTGZgbYf.js → xychartDiagram-S5SC5T6Z-B9h0fFPk.js} +1 -1
  146. package/dist/web/index.html +9 -5
  147. package/package.json +4 -4
  148. package/dist/web/assets/AgentPicker-DB8mxbbL.js +0 -1
  149. package/dist/web/assets/AgentsModal-RvCfWb-2.js +0 -2
  150. package/dist/web/assets/ArtifactPanel-BO4wIe9q.js +0 -2
  151. package/dist/web/assets/BtwPanel-BQN5XxKF.js +0 -1
  152. package/dist/web/assets/SettingsModal--RT4zb3v.js +0 -1
  153. package/dist/web/assets/SpreadsheetPanel-C2XdUSSC.js +0 -1
  154. package/dist/web/assets/app-DP2KtZ7m.js +0 -2
  155. package/dist/web/assets/architecture-7GRP2DOG-Bxp-PFvl.js +0 -1
  156. package/dist/web/assets/channel-CNDX-cbg.js +0 -1
  157. package/dist/web/assets/chunk-DU6HZSFF-CgtANb4X.js +0 -125
  158. package/dist/web/assets/classDiagram-ZZMXUADV-BfcenY86.js +0 -1
  159. package/dist/web/assets/classDiagram-v2-VYDZK3BY-BfcenY86.js +0 -1
  160. package/dist/web/assets/eventmodeling-NTZA5JFV-50pr2V7m.js +0 -1
  161. package/dist/web/assets/flowDiagram-HODETNUW-ftQQEWxR.js +0 -1
  162. package/dist/web/assets/index-CRb-14T4.css +0 -1
  163. package/dist/web/assets/index-lCAk_Rn7.js +0 -74
  164. package/dist/web/assets/jsxRuntime-DJRCfs_u.js +0 -5
  165. package/dist/web/assets/railroad-abnf-HS5TGJTU-DkFG-50O.js +0 -1
  166. package/dist/web/assets/railroad-ebnf-LZEXJU2U-CBTuWFTA.js +0 -1
  167. package/dist/web/assets/railroad-peg-WCYAUIDC-CdSnH_fA.js +0 -1
  168. package/dist/web/assets/shared-BaCxhGEp.js +0 -1
  169. package/dist/web/assets/stateDiagram-v2-MP3YSRHH-Bmk6g5Wx.js +0 -1
  170. package/dist/web/assets/swimlanesDiagram-VR7AAH4N-CPdgOMx5.js +0 -8
  171. package/dist/web/assets/view-BByXcA5W.js +0 -1
  172. package/dist/web/assets/view-BOCBCjVE.js +0 -2
  173. package/dist/web/assets/view-CuotKs3K.js +0 -1
  174. package/dist/web/assets/view-D1FOOnH2.js +0 -5
  175. package/dist/web/assets/view-DL9H44DY.js +0 -1
  176. package/dist/web/assets/view-DzGOmq2Y.js +0 -6
  177. package/dist/web/assets/view-xJC-nbhd.js +0 -1
  178. /package/dist/web/assets/{shared-DuS6eVxB.css → settings-DuS6eVxB.css} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { stat } from "node:fs/promises";
2
- import { createApplication, findWorkspace, resolveTrust, workspaceKey, } from "@alisio/core";
2
+ import { createApplication, findWorkspace, ReloadFailedError, ReloadRefusedError, reloadApplication, resolveTrust, validateReloadConfig, workspaceKey, } from "@alisio/core";
3
3
  import { HttpError } from "../http/errors.js";
4
4
  const MISSING_ERRNO = new Set(["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP"]);
5
5
  const missing = (path) => new HttpError("workspace_missing", `Workspace folder not found: ${path}`, { path });
@@ -76,22 +76,25 @@ export class WorkspaceHost {
76
76
  this.opening.set(id, promise);
77
77
  return promise;
78
78
  }
79
- async create(id, path) {
79
+ /**
80
+ * Builds the `Application` of a workspace: launch flags are explicit, one-run trust like in the
81
+ * terminal; otherwise the trust store decides and a workspace that would need a prompt opens
82
+ * untrusted.
83
+ */
84
+ async build(id, path) {
80
85
  const base = this.options.base;
81
- // Launch flags are explicit, one-run trust like in the terminal; otherwise the trust store
82
- // decides and a workspace that would need a prompt opens untrusted.
83
86
  const explicit = !!base.trustProject || !!base.config;
84
87
  const trust = explicit ? undefined : await resolveTrust(path);
85
88
  const trusted = explicit || !!trust?.trusted;
86
- let app;
87
89
  try {
88
- app = await (this.options.create ?? createApplication)({
90
+ const app = await (this.options.create ?? createApplication)({
89
91
  ...base,
90
92
  trustProject: explicit ? base.trustProject : trusted,
91
93
  cwd: path,
92
94
  approvalSource: "web",
93
95
  ...(this.options.wire?.(id, path) ?? {}),
94
96
  });
97
+ return { app, trusted, untrustedResources: !trusted && !!trust?.hasProjectResources };
95
98
  }
96
99
  catch (error) {
97
100
  // The folder vanished between the check and the open (the core canonicalizes it).
@@ -100,12 +103,15 @@ export class WorkspaceHost {
100
103
  throw missing(path);
101
104
  throw error;
102
105
  }
106
+ }
107
+ async create(id, path) {
108
+ const { app, trusted, untrustedResources } = await this.build(id, path);
103
109
  const entry = {
104
110
  id,
105
111
  path,
106
112
  app,
107
113
  trusted,
108
- untrustedResources: !trusted && !!trust?.hasProjectResources,
114
+ untrustedResources,
109
115
  lastUsed: this.now(),
110
116
  };
111
117
  if (this.closed) {
@@ -163,6 +169,56 @@ export class WorkspaceHost {
163
169
  await this.close(id);
164
170
  return this.openPath(entry.path);
165
171
  }
172
+ /**
173
+ * `/reload` for one open workspace: validate, build the new app NEXT TO the current one and
174
+ * only then swap, so a broken configuration leaves the workspace and its sessions untouched
175
+ * (unlike `recycle`, which closes first). The caller refuses while runs are active (`busy`).
176
+ * SSE subscribers are unaffected: they read the shared store.
177
+ */
178
+ async reload(id, guard = {}) {
179
+ const entry = this.open.get(id);
180
+ if (!entry)
181
+ throw new HttpError("not_found", "Workspace is not open");
182
+ const base = this.options.base;
183
+ let built;
184
+ try {
185
+ return await reloadApplication({
186
+ current: entry.app,
187
+ ...(guard.busy ? { busy: guard.busy } : {}),
188
+ validate: async () => {
189
+ const explicit = !!base.trustProject || !!base.config;
190
+ const trust = explicit ? undefined : await resolveTrust(entry.path);
191
+ await validateReloadConfig({
192
+ ...base,
193
+ cwd: entry.path,
194
+ trustProject: explicit ? base.trustProject : !!trust?.trusted,
195
+ });
196
+ },
197
+ create: async () => {
198
+ const next = await this.build(id, entry.path);
199
+ built = next;
200
+ return next.app;
201
+ },
202
+ swap: (next) => {
203
+ if (this.closed || this.open.get(id) !== entry)
204
+ throw new Error("The workspace was closed while reloading");
205
+ this.options.onClose?.(entry);
206
+ entry.app = next;
207
+ entry.trusted = built?.trusted ?? entry.trusted;
208
+ entry.untrustedResources = built?.untrustedResources ?? entry.untrustedResources;
209
+ entry.lastUsed = this.now();
210
+ this.options.onOpen?.(entry);
211
+ },
212
+ });
213
+ }
214
+ catch (error) {
215
+ if (error instanceof ReloadRefusedError)
216
+ throw new HttpError("runs_active", error.message);
217
+ if (error instanceof ReloadFailedError)
218
+ throw new HttpError("validation_failed", `Reload failed; the current session is unchanged: ${error.message}`, { stage: error.stage });
219
+ throw error;
220
+ }
221
+ }
166
222
  /** Closes every app in parallel (each close is capped by the core's teardown timeouts). */
167
223
  async closeAll() {
168
224
  this.closed = true;
@@ -34,6 +34,13 @@ export const STATUS = {
34
34
  dataset_unsupported: 415,
35
35
  query_rejected: 400,
36
36
  query_timeout: 408,
37
+ view_failed: 502,
38
+ view_timeout: 504,
39
+ view_too_large: 502,
40
+ task_not_found: 404,
41
+ goal_not_found: 404,
42
+ goal_conflict: 409,
43
+ goal_disabled: 409,
37
44
  internal: 500,
38
45
  };
39
46
  /** A typed API failure; routes throw it and the server renders it as an `ApiError` body. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type ServerResponse } from "node:http";
2
+ import { type AppOptions } from "@alisio/core";
2
3
  import { type FolderPicker } from "./host/folder-picker.ts";
3
4
  import { type ServerAppOptions } from "./host/workspace-host.ts";
4
5
  import { type Logger } from "./log.ts";
@@ -53,6 +54,13 @@ export interface ServerOptions {
53
54
  * tests. Always off when the server is bound for remote access.
54
55
  */
55
56
  folderPicker?: FolderPicker;
57
+ /** Limits of plugin data views (`GET /api/sessions/:sid/views/…`): 5 s and 1 MiB by default. */
58
+ views?: {
59
+ timeoutMs?: number;
60
+ maxBytes?: number;
61
+ };
62
+ /** Background-task runtime knobs (notification timing, kill grace, limits): tests tune them. */
63
+ tasks?: Pick<NonNullable<AppOptions["tasks"]>, "timing" | "sweepDelayMs" | "killGraceMs" | "maxPerProcess">;
56
64
  }
57
65
  export interface RunningServer {
58
66
  host: string;
package/dist/index.js CHANGED
@@ -5,13 +5,15 @@
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { createServer } from "node:http";
7
7
  import { dirname, join } from "node:path";
8
- import { AnalysisJobs, ArtifactStore, BlobStore, CapabilityGrants, SQLiteStore, stateHome, } from "@alisio/core";
8
+ import { AnalysisJobs, ArtifactStore, BackgroundTaskStore, BlobStore, CapabilityGrants, GoalStore, SQLiteStore, stateHome, } from "@alisio/core";
9
9
  import { AuthGuard, isLoopbackHost } from "./auth/guard.js";
10
10
  import { newSecret } from "./auth/token.js";
11
11
  import { redactViewPath } from "./auth/view-token.js";
12
12
  import { ApprovalBridge } from "./bridges/approval-bridge.js";
13
13
  import { InteractionBridge } from "./bridges/interaction-bridge.js";
14
14
  import { createNativePicker } from "./host/folder-picker.js";
15
+ import { GoalDriver } from "./host/goal.js";
16
+ import { followUpPlanRun } from "./host/plan.js";
15
17
  import { RunScheduler } from "./host/run-scheduler.js";
16
18
  import { SessionService } from "./host/sessions.js";
17
19
  import { WorkspaceHost, workspaceId } from "./host/workspace-host.js";
@@ -25,18 +27,22 @@ import { registerArtifactViewRoutes, VIEW_PREFIX, viewerBaseHeaders, } from "./r
25
27
  import { registerArtifactRoutes } from "./routes/artifacts.js";
26
28
  import { registerBlobRoutes } from "./routes/blobs.js";
27
29
  import { registerCapabilityRoutes } from "./routes/capabilities.js";
30
+ import { registerChangelogRoutes } from "./routes/changelog.js";
28
31
  import { registerCommandRoutes } from "./routes/commands.js";
29
32
  import { registerDatasetRoutes } from "./routes/datasets.js";
30
33
  import { registerEventRoutes } from "./routes/events.js";
31
34
  import { registerFileRoutes } from "./routes/files.js";
32
35
  import { registerFolderRoutes } from "./routes/folders.js";
36
+ import { registerGoalRoutes } from "./routes/goal.js";
33
37
  import { registerHealthRoutes } from "./routes/health.js";
34
38
  import { registerManagementRoutes, WorkspaceRecycler } from "./routes/management.js";
39
+ import { registerPluginViewRoutes } from "./routes/plugin-views.js";
35
40
  import { registerPromptRoutes } from "./routes/prompts.js";
36
41
  import { registerProviderRoutes } from "./routes/providers.js";
37
42
  import { registerSessionViewRoutes } from "./routes/session-views.js";
38
43
  import { registerSessionRoutes } from "./routes/sessions.js";
39
44
  import { registerSideQuestionRoutes } from "./routes/side-questions.js";
45
+ import { registerTaskRoutes } from "./routes/tasks.js";
40
46
  import { registerWorkspaceRoutes } from "./routes/workspaces.js";
41
47
  import { SseHub } from "./sse/hub.js";
42
48
  import { InflightTracker } from "./sse/inflight.js";
@@ -93,21 +99,60 @@ export async function startServer(options = {}) {
93
99
  // process become interrupted before any client lists them.
94
100
  catalog.interruptRuns();
95
101
  catalog.interruptStale();
102
+ // Background tasks of a dead server or TUI process become `lost` (a live process's are kept).
103
+ new BackgroundTaskStore(catalog.db).markLost(Date.now());
104
+ // A goal left `active` by a process that is gone pauses (reason `restart`); it never resumes by
105
+ // itself, the user decides.
106
+ new GoalStore(catalog.db).pauseOrphans(Date.now());
96
107
  let shutdown;
97
108
  let sessions;
98
109
  const inflight = new InflightTracker();
99
110
  let recycler;
111
+ let goalDriver;
100
112
  const scheduler = new RunScheduler({
101
113
  ...(options.maxConcurrentRuns ? { maxConcurrent: options.maxConcurrentRuns } : {}),
102
- onChange: (job, status) => {
114
+ onChange: (job, status, outcome) => {
103
115
  if (status === "finished") {
104
116
  inflight.clear(job.sessionId, job.runId);
105
117
  // A deferred plugin change applies once the workspace has no runs left.
106
118
  setImmediate(() => recycler?.idle(job.workspaceId));
119
+ // A finished-task notification held back because this session was busy may go now.
120
+ setImmediate(() => job.app.tasks.sessionIdle(job.sessionId));
121
+ // An approved plan starts its implementation run once the plan run is over.
122
+ if (sessions && !job.tool)
123
+ try {
124
+ followUpPlanRun({
125
+ job,
126
+ cancelled: !!outcome?.cancelled,
127
+ catalog,
128
+ sessions,
129
+ scheduler,
130
+ });
131
+ }
132
+ catch (error) {
133
+ logger.error("plan_follow_up_failed", {
134
+ sessionId: job.sessionId,
135
+ error: error instanceof Error ? error.message : String(error),
136
+ });
137
+ }
138
+ // The run is folded into the session's goal (tokens, turns, breakers), and the goal may
139
+ // continue: the pump runs after this tick so the plan follow-up above (if any) goes first.
140
+ if (outcome && !job.tool)
141
+ try {
142
+ goalDriver?.settle(job, outcome);
143
+ }
144
+ catch (error) {
145
+ logger.error("goal_settle_failed", {
146
+ sessionId: job.sessionId,
147
+ error: error instanceof Error ? error.message : String(error),
148
+ });
149
+ }
150
+ setImmediate(() => goalDriver?.pump(job.sessionId));
107
151
  }
108
152
  else
109
153
  inflight.mark(job.sessionId, job.runId, status);
110
154
  sessions?.notify(job.sessionId);
155
+ goalDriver?.refresh(job.sessionId);
111
156
  },
112
157
  });
113
158
  const workspaceOf = (sessionId) => {
@@ -140,6 +185,7 @@ export async function startServer(options = {}) {
140
185
  approvals: approvals?.pending(sessionId) ?? [],
141
186
  interactions: interactions?.pending(sessionId) ?? [],
142
187
  },
188
+ ...(goalDriver?.info(sessionId) ? { goal: goalDriver.info(sessionId) } : {}),
143
189
  };
144
190
  },
145
191
  messagesAfter: (sessionId, after) => catalog.messagesPage(sessionId, { after, limit: 200 }),
@@ -148,10 +194,62 @@ export async function startServer(options = {}) {
148
194
  ...(options.heartbeatMs ? { heartbeatMs: options.heartbeatMs } : {}),
149
195
  ...(options.coalesceMs ? { coalesceMs: options.coalesceMs } : {}),
150
196
  });
197
+ /** Whether a workspace's application has live background tasks (it must not be closed). */
198
+ const tasksLive = (id) => (workspaces.get(id)?.app.tasks.liveCount() ?? 0) > 0;
199
+ /**
200
+ * The finished-task notification: a normal run of the owner session whose request id is the
201
+ * batch id (like the plan approval's `plan-<id>`), so the same batch can never start two runs.
202
+ * `runner.enqueue` would only park the text until the next run, so the wake-up goes through the
203
+ * scheduler. Busy sessions (a run, an approval or question, another process holding the
204
+ * session) answer `busy` and the core retries; archived or missing ones answer `skip`.
205
+ */
206
+ const wakeOwner = (workspaceKey, request) => {
207
+ if (closing || !sessions)
208
+ return "skip";
209
+ let session;
210
+ try {
211
+ session = catalog.get(request.sessionId);
212
+ }
213
+ catch {
214
+ return "skip";
215
+ }
216
+ const opened = workspaces.get(workspaceKey);
217
+ if (session.parentId || session.archivedAt || !opened)
218
+ return "skip";
219
+ if (catalog.lockedBy(session.id) ||
220
+ scheduler.busy(session.id) ||
221
+ opened.app.runner.isRunning(session.id) ||
222
+ approvals?.awaiting(session.id) ||
223
+ interactions?.awaiting(session.id))
224
+ return "busy";
225
+ const correlationId = randomUUID();
226
+ const { run, created } = sessions.beginRun({
227
+ id: randomUUID(),
228
+ session: session.id,
229
+ status: "queued",
230
+ requestId: request.requestId,
231
+ correlationId,
232
+ model: session.model,
233
+ });
234
+ if (!created)
235
+ return "started";
236
+ const service = sessions;
237
+ scheduler.submit({
238
+ runId: run.id,
239
+ sessionId: session.id,
240
+ workspaceId: opened.id,
241
+ app: opened.app,
242
+ text: request.text,
243
+ display: request.display,
244
+ correlationId,
245
+ options: () => service.runOptions(session.id),
246
+ });
247
+ return "started";
248
+ };
151
249
  const workspaces = new WorkspaceHost({
152
250
  base,
153
251
  catalog,
154
- wire: () => ({
252
+ wire: (id) => ({
155
253
  onEvent: (event) => {
156
254
  inflight.apply(event);
157
255
  hub.publish(event);
@@ -165,12 +263,31 @@ export async function startServer(options = {}) {
165
263
  ...(approvals
166
264
  ? { approve: approvals.handler, approveExternalDirectory: approvals.directoryHandler }
167
265
  : {}),
266
+ tasks: {
267
+ ...(options.tasks ?? {}),
268
+ onChange: (task, root) => {
269
+ hub.toSession(root, { t: "tasks_changed", sessionId: root, task });
270
+ // A goal waiting for background tasks continues once the last one is over.
271
+ if (task.status !== "running" && task.status !== "queued" && task.status !== "stopping")
272
+ setImmediate(() => goalDriver?.pump(root));
273
+ else
274
+ goalDriver?.refresh(root);
275
+ // A deferred recycle (a plugin change) goes ahead once the last task is over.
276
+ if (task.status !== "running" && task.status !== "queued" && task.status !== "stopping") {
277
+ const owner = workspaceOf(root);
278
+ if (owner)
279
+ setImmediate(() => recycler?.idle(owner));
280
+ }
281
+ },
282
+ wake: (request) => wakeOwner(id, request),
283
+ },
284
+ goals: { onChange: (sessionId) => goalDriver?.refresh(sessionId) },
168
285
  }),
169
286
  onOpen: (entry) => {
170
287
  if (interactions)
171
288
  entry.app.plugins.setInteractiveUI(interactions.uiFor(entry.id));
172
289
  },
173
- busy: (id) => scheduler.busyWorkspace(id) || hub.watchesWorkspace(id),
290
+ busy: (id) => scheduler.busyWorkspace(id) || hub.watchesWorkspace(id) || tasksLive(id),
174
291
  ...(options.maxOpenWorkspaces ? { maxOpen: options.maxOpenWorkspaces } : {}),
175
292
  ...(options.idleEvictMs ? { idleEvictMs: options.idleEvictMs } : {}),
176
293
  ...(options.defaultWorkspace ? { defaultWorkspace: options.defaultWorkspace } : {}),
@@ -192,7 +309,10 @@ export async function startServer(options = {}) {
192
309
  runOf: (id) => scheduler.job(id)?.runId,
193
310
  ...(options.approvalGraceMs !== undefined ? { graceMs: options.approvalGraceMs } : {}),
194
311
  ...(options.approvalTimeoutMs !== undefined ? { timeoutMs: options.approvalTimeoutMs } : {}),
195
- onChange: (root) => service.notify(root),
312
+ onChange: (root) => {
313
+ service.notify(root);
314
+ goalDriver?.refresh(root);
315
+ },
196
316
  });
197
317
  interactions = new InteractionBridge({
198
318
  hub,
@@ -200,8 +320,37 @@ export async function startServer(options = {}) {
200
320
  workspaceOf,
201
321
  ...(options.approvalGraceMs !== undefined ? { graceMs: options.approvalGraceMs } : {}),
202
322
  ...(options.approvalTimeoutMs !== undefined ? { timeoutMs: options.approvalTimeoutMs } : {}),
203
- onOpen: (id) => service.notify(id),
204
- onChange: (root) => root && service.notify(root),
323
+ onOpen: (id) => {
324
+ service.notify(id);
325
+ goalDriver?.refresh(id);
326
+ },
327
+ onChange: (root) => {
328
+ if (!root)
329
+ return;
330
+ service.notify(root);
331
+ goalDriver?.refresh(root);
332
+ },
333
+ });
334
+ goalDriver = new GoalDriver({
335
+ catalog,
336
+ sessions: () => service,
337
+ scheduler: () => scheduler,
338
+ workspaces: () => workspaces,
339
+ awaiting: (root) => approvals?.awaiting(root)
340
+ ? "approval"
341
+ : interactions?.awaiting(root)
342
+ ? "question"
343
+ : undefined,
344
+ toSession: (sessionId, frame) => hub.toSession(sessionId, frame),
345
+ closing: () => closing,
346
+ log: (event, data) => logger.error(event, data),
347
+ });
348
+ // A session option (the agent, say) changed: the plan-mode wait of a goal may be over.
349
+ service.bind({
350
+ onOptions: (sessionId) => {
351
+ goalDriver?.refresh(sessionId);
352
+ setImmediate(() => goalDriver?.pump(sessionId));
353
+ },
205
354
  });
206
355
  const stats = () => ({
207
356
  shuttingDown: closing,
@@ -228,6 +377,13 @@ export async function startServer(options = {}) {
228
377
  registerWorkspaceRoutes(router, { workspaces, catalog, scheduler });
229
378
  registerSessionRoutes(router, { catalog, workspaces, sessions, scheduler });
230
379
  registerSessionViewRoutes(router, { catalog, sessions });
380
+ registerPluginViewRoutes(router, {
381
+ sessions,
382
+ workspaces,
383
+ logger,
384
+ ...(options.views?.timeoutMs !== undefined ? { timeoutMs: options.views.timeoutMs } : {}),
385
+ ...(options.views?.maxBytes !== undefined ? { maxBytes: options.views.maxBytes } : {}),
386
+ });
231
387
  registerFileRoutes(router, { workspaces, catalog, sessions });
232
388
  registerBlobRoutes(router, { blobs });
233
389
  registerArtifactRoutes(router, {
@@ -251,16 +407,28 @@ export async function startServer(options = {}) {
251
407
  toSession: (sessionId, frame) => hub.toSession(sessionId, frame),
252
408
  });
253
409
  registerPromptRoutes(router, { sessions, scheduler });
254
- registerCommandRoutes(router, { sessions, scheduler, workspaces });
255
- registerSideQuestionRoutes(router, { sessions });
256
- registerApprovalRoutes(router, { approvals, interactions });
410
+ registerGoalRoutes(router, { sessions, goals: goalDriver });
257
411
  const management = {
258
412
  workspaces,
259
413
  scheduler,
414
+ tasksLive,
260
415
  base,
261
416
  broadcast: (frame) => hub.broadcast(frame),
262
417
  };
263
418
  recycler = new WorkspaceRecycler(management);
419
+ const reloader = recycler;
420
+ registerChangelogRoutes(router, { version: options.version ?? "dev" });
421
+ registerCommandRoutes(router, {
422
+ sessions,
423
+ scheduler,
424
+ workspaces,
425
+ reload: (workspaceId) => reloader.reload(workspaceId),
426
+ ...(goalDriver ? { goals: goalDriver } : {}),
427
+ version: options.version ?? "dev",
428
+ });
429
+ registerSideQuestionRoutes(router, { sessions });
430
+ registerTaskRoutes(router, { sessions });
431
+ registerApprovalRoutes(router, { approvals, interactions });
264
432
  registerManagementRoutes(router, management, recycler);
265
433
  registerAgentDefinitionRoutes(router, management, recycler);
266
434
  registerProviderRoutes(router, management, recycler);
@@ -0,0 +1,9 @@
1
+ import type { Router } from "../http/router.ts";
2
+ /**
3
+ * `GET /api/changelog?version=&lastSeen=`: the shipped `CHANGELOG.md` entries (offline, parsed at
4
+ * build time), the newest five or the one `version` names, plus `news` when `lastSeen` is older
5
+ * than the running version. Same auth, Host and Origin rules as every other `/api` route.
6
+ */
7
+ export declare function registerChangelogRoutes(router: Router, options: {
8
+ version: string;
9
+ }): void;
@@ -0,0 +1,32 @@
1
+ import { changelogNews, loadChangelog, selectEntries } from "@alisio/core";
2
+ import { HttpError } from "../http/errors.js";
3
+ const MAX_QUERY = 100;
4
+ /**
5
+ * `GET /api/changelog?version=&lastSeen=`: the shipped `CHANGELOG.md` entries (offline, parsed at
6
+ * build time), the newest five or the one `version` names, plus `news` when `lastSeen` is older
7
+ * than the running version. Same auth, Host and Origin rules as every other `/api` route.
8
+ */
9
+ export function registerChangelogRoutes(router, options) {
10
+ router.get("/api/changelog", ({ url }) => {
11
+ const text = (name) => {
12
+ const value = url.searchParams.get(name)?.trim();
13
+ if (value && value.length > MAX_QUERY)
14
+ throw new HttpError("validation_failed", "Invalid query parameter", { fields: [name] });
15
+ return value || undefined;
16
+ };
17
+ const version = text("version");
18
+ const lastSeen = text("lastSeen");
19
+ const entries = loadChangelog();
20
+ const selected = selectEntries(entries, { ...(version ? { version } : {}), limit: 5 });
21
+ const news = changelogNews({ entries, current: options.version, lastSeen });
22
+ const body = {
23
+ current: options.version,
24
+ entries: selected.entries,
25
+ found: selected.found,
26
+ ...(news.show && news.latest
27
+ ? { news: { latest: news.latest, versions: news.versions } }
28
+ : {}),
29
+ };
30
+ return { body };
31
+ });
32
+ }
@@ -1,3 +1,5 @@
1
+ import type { ReloadReport } from "@alisio/sdk";
2
+ import type { GoalDriver } from "../host/goal.ts";
1
3
  import type { RunScheduler } from "../host/run-scheduler.ts";
2
4
  import type { SessionService } from "../host/sessions.ts";
3
5
  import type { WorkspaceHost } from "../host/workspace-host.ts";
@@ -18,4 +20,10 @@ export declare function registerCommandRoutes(router: Router, ctx: {
18
20
  scheduler: RunScheduler;
19
21
  workspaces: WorkspaceHost;
20
22
  recent?: RecentRequests;
23
+ /** `/reload` of a workspace (validate, build next to the current app, swap, announce). */
24
+ reload?: (workspaceId: string) => Promise<ReloadReport>;
25
+ /** The running Alisio version (the CLI's), for `/changelog`. */
26
+ version?: string;
27
+ /** `/goal`: the driver that makes a goal continue and reports what it waits for. */
28
+ goals?: Pick<GoalDriver, "info" | "pump" | "refresh">;
21
29
  }): void;