@astrale-os/cli 1.0.0-beta.32 → 1.0.0-beta.43

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 (441) hide show
  1. package/CHANGELOG.md +928 -0
  2. package/README.md +53 -43
  3. package/THIRD-PARTY-NOTICES.md +37 -1
  4. package/dist/astrale.js +349631 -83669
  5. package/dist/public/connect-core.js +16 -47
  6. package/dist/public/keys/index.js +1 -1
  7. package/dist/types/admin/contract.d.ts +11 -0
  8. package/dist/types/admin/graph/index.d.ts +1 -0
  9. package/dist/types/admin/graph/nodes.d.ts +12 -0
  10. package/dist/types/admin/instance/client.d.ts +2 -0
  11. package/dist/types/connection/lifetime.d.ts +1 -1
  12. package/dist/types/errors.d.ts +3 -0
  13. package/dist/types/lib/proc.d.ts +1 -0
  14. package/dist/types/lib/update.d.ts +15 -7
  15. package/package.json +8 -6
  16. package/studio/client/dist/assets/index-5C1uT6ML.css +2 -0
  17. package/studio/client/dist/assets/index-C4kNlvwF.js +80 -0
  18. package/studio/client/dist/assets/schema-studio-BR9d0faB.js +8 -0
  19. package/studio/client/dist/index.html +13 -2
  20. package/studio/package.json +2 -1
  21. package/studio/server/agent/ask.test.ts +7 -1
  22. package/studio/server/agent/bridge/grant.ts +15 -3
  23. package/studio/server/agent/prompts/system.test.ts +5 -0
  24. package/studio/server/agent/prompts/system.ts +19 -4
  25. package/studio/server/agent/routes.test.ts +39 -1
  26. package/studio/server/agent/routes.ts +3 -0
  27. package/studio/server/agent/run/coordinator.test.ts +7 -1
  28. package/studio/server/agent/run/preparation.ts +1 -0
  29. package/studio/server/agent/run/transcript.test.ts +47 -2
  30. package/studio/server/agent/run/transcript.ts +32 -3
  31. package/studio/server/api/documents.test.ts +31 -0
  32. package/studio/server/api/documents.ts +35 -1
  33. package/studio/server/api-agent-loadout.test.ts +7 -1
  34. package/studio/server/api.test.ts +7 -1
  35. package/studio/server/cache.ts +4 -4
  36. package/studio/server/detect.ts +9 -12
  37. package/studio/server/domain.test.ts +83 -3
  38. package/studio/server/domain.ts +205 -27
  39. package/studio/server/handoff/service.ts +1 -1
  40. package/studio/server/index.ts +5 -8
  41. package/studio/server/introspect/extractor.ts +111 -17
  42. package/studio/server/introspect/overlay-tsmorph.test.ts +9 -9
  43. package/studio/server/introspect/overlay.ts +1 -1
  44. package/studio/server/introspect/runtime.ts +9 -1
  45. package/studio/server/introspect/source-overlay/spans.ts +2 -5
  46. package/studio/server/lifecycle.ts +1 -1
  47. package/studio/server/state/baseline.test.ts +14 -0
  48. package/studio/server/state/baseline.ts +16 -2
  49. package/studio/server/watch.test.ts +2 -1
  50. package/studio/server/watch.ts +5 -2
  51. package/studio/server/workspace/create.ts +9 -8
  52. package/studio/server/workspace-state.ts +4 -6
  53. package/studio/server/workspace-watch.ts +33 -22
  54. package/studio/shared/contracts/agent.ts +3 -0
  55. package/viewer/dist/main.js +12 -12
  56. package/src/__tests__/fixtures/publication.ts +0 -20
  57. package/src/admin/.spec/api.d.ts +0 -3
  58. package/src/admin/.spec/architecture.md +0 -22
  59. package/src/admin/.spec/icon.svg +0 -3
  60. package/src/admin/.spec/layout.ts +0 -6
  61. package/src/admin/__tests__/fixture.ts +0 -25
  62. package/src/admin/catalog/.spec/api.d.ts +0 -63
  63. package/src/admin/catalog/.spec/architecture.md +0 -7
  64. package/src/admin/catalog/.spec/icon.svg +0 -3
  65. package/src/admin/catalog/.spec/layout.ts +0 -6
  66. package/src/admin/catalog/__tests__/client.test.ts +0 -245
  67. package/src/admin/catalog/client.ts +0 -195
  68. package/src/admin/catalog/index.ts +0 -13
  69. package/src/admin/catalog/model.ts +0 -41
  70. package/src/admin/catalog/tsconfig.json +0 -8
  71. package/src/admin/contract.ts +0 -47
  72. package/src/admin/graph/.spec/api.d.ts +0 -22
  73. package/src/admin/graph/.spec/architecture.md +0 -5
  74. package/src/admin/graph/.spec/icon.svg +0 -3
  75. package/src/admin/graph/.spec/layout.ts +0 -3
  76. package/src/admin/graph/index.ts +0 -6
  77. package/src/admin/graph/nodes.ts +0 -56
  78. package/src/admin/index.ts +0 -2
  79. package/src/admin/instance/.spec/api.d.ts +0 -62
  80. package/src/admin/instance/.spec/architecture.md +0 -10
  81. package/src/admin/instance/.spec/icon.svg +0 -3
  82. package/src/admin/instance/.spec/layout.ts +0 -6
  83. package/src/admin/instance/__tests__/client.test.ts +0 -273
  84. package/src/admin/instance/client.ts +0 -191
  85. package/src/admin/instance/index.ts +0 -15
  86. package/src/admin/instance/model.ts +0 -59
  87. package/src/admin/instance/tsconfig.json +0 -8
  88. package/src/commands/__tests__/admin-instance.test.ts +0 -91
  89. package/src/commands/__tests__/auth-login.test.ts +0 -231
  90. package/src/commands/__tests__/auth-token.test.ts +0 -223
  91. package/src/commands/__tests__/call-describe.test.ts +0 -80
  92. package/src/commands/__tests__/call.test.ts +0 -63
  93. package/src/commands/__tests__/domain-install-operation.test.ts +0 -145
  94. package/src/commands/__tests__/domain-install-owned.test.ts +0 -191
  95. package/src/commands/__tests__/domain-list.test.ts +0 -133
  96. package/src/commands/__tests__/domain-uninstall.test.ts +0 -53
  97. package/src/commands/__tests__/install-direct.test.ts +0 -128
  98. package/src/commands/__tests__/install-identity-override.test.ts +0 -76
  99. package/src/commands/__tests__/instance-bookmark.test.ts +0 -166
  100. package/src/commands/__tests__/instance-list-admin.test.ts +0 -26
  101. package/src/commands/__tests__/instance-list-rows.test.ts +0 -64
  102. package/src/commands/__tests__/instance-status.test.ts +0 -139
  103. package/src/commands/__tests__/instance-use.test.ts +0 -67
  104. package/src/commands/__tests__/introspect-parse.test.ts +0 -100
  105. package/src/commands/__tests__/logs.test.ts +0 -345
  106. package/src/commands/__tests__/read-commands.test.ts +0 -377
  107. package/src/commands/__tests__/setup-plan.test.ts +0 -61
  108. package/src/commands/__tests__/studio-descriptor.test.ts +0 -22
  109. package/src/commands/__tests__/token-ttl.test.ts +0 -66
  110. package/src/commands/__tests__/update.test.ts +0 -95
  111. package/src/commands/__tests__/view-build.test.ts +0 -58
  112. package/src/commands/__tests__/view.test.ts +0 -197
  113. package/src/commands/admin/status.ts +0 -61
  114. package/src/commands/admin/use.ts +0 -100
  115. package/src/commands/auth/login.ts +0 -82
  116. package/src/commands/auth/logout.ts +0 -53
  117. package/src/commands/auth/status.ts +0 -86
  118. package/src/commands/auth/token.ts +0 -174
  119. package/src/commands/browser.ts +0 -236
  120. package/src/commands/call-describe.ts +0 -123
  121. package/src/commands/call.ts +0 -230
  122. package/src/commands/domain/install.ts +0 -575
  123. package/src/commands/domain/list.ts +0 -155
  124. package/src/commands/domain/publish.ts +0 -136
  125. package/src/commands/domain/uninstall.ts +0 -128
  126. package/src/commands/get.ts +0 -69
  127. package/src/commands/identity/__tests__/register.test.ts +0 -93
  128. package/src/commands/identity/create.ts +0 -35
  129. package/src/commands/identity/delete.ts +0 -24
  130. package/src/commands/identity/export.ts +0 -46
  131. package/src/commands/identity/import.ts +0 -61
  132. package/src/commands/identity/list.ts +0 -56
  133. package/src/commands/identity/register.ts +0 -195
  134. package/src/commands/identity/sync.ts +0 -38
  135. package/src/commands/identity/unsync.ts +0 -34
  136. package/src/commands/identity/use.ts +0 -24
  137. package/src/commands/identity/whoami.ts +0 -34
  138. package/src/commands/idp/add.ts +0 -150
  139. package/src/commands/idp/list.ts +0 -57
  140. package/src/commands/idp/refresh.ts +0 -38
  141. package/src/commands/idp/remove.ts +0 -36
  142. package/src/commands/idp/show.ts +0 -29
  143. package/src/commands/instance/active.ts +0 -73
  144. package/src/commands/instance/bookmark.ts +0 -101
  145. package/src/commands/instance/create.ts +0 -61
  146. package/src/commands/instance/delete.ts +0 -69
  147. package/src/commands/instance/forget.ts +0 -26
  148. package/src/commands/instance/list.ts +0 -197
  149. package/src/commands/instance/status.ts +0 -92
  150. package/src/commands/instance/use.ts +0 -263
  151. package/src/commands/introspect.ts +0 -129
  152. package/src/commands/logs.ts +0 -371
  153. package/src/commands/mutate.ts +0 -122
  154. package/src/commands/query.ts +0 -142
  155. package/src/commands/session/analyze.ts +0 -72
  156. package/src/commands/session/list.ts +0 -36
  157. package/src/commands/setup.ts +0 -54
  158. package/src/commands/status.ts +0 -60
  159. package/src/commands/studio.ts +0 -434
  160. package/src/commands/token.ts +0 -121
  161. package/src/commands/ui/__tests__/commands.test.ts +0 -152
  162. package/src/commands/ui/add.ts +0 -51
  163. package/src/commands/ui/doctor.ts +0 -21
  164. package/src/commands/ui/init.ts +0 -38
  165. package/src/commands/ui/list.ts +0 -26
  166. package/src/commands/ui/preset-apply.ts +0 -19
  167. package/src/commands/ui/preset-list.ts +0 -12
  168. package/src/commands/ui/shared.ts +0 -25
  169. package/src/commands/update.ts +0 -356
  170. package/src/commands/use.ts +0 -86
  171. package/src/commands/view-serve.ts +0 -26
  172. package/src/commands/view.ts +0 -658
  173. package/src/connect-core.test.ts +0 -38
  174. package/src/connect-core.ts +0 -42
  175. package/src/connection/.spec/api.d.ts +0 -110
  176. package/src/connection/.spec/architecture.md +0 -58
  177. package/src/connection/.spec/capabilities/connection.ts +0 -13
  178. package/src/connection/.spec/examples/call.ts +0 -7
  179. package/src/connection/.spec/flows/command.ts +0 -20
  180. package/src/connection/.spec/flows/session.ts +0 -62
  181. package/src/connection/.spec/laws/connection.ts +0 -213
  182. package/src/connection/.spec/layout.ts +0 -22
  183. package/src/connection/__tests__/auth.test.ts +0 -129
  184. package/src/connection/__tests__/ca-fetch.test.ts +0 -60
  185. package/src/connection/__tests__/call.test.ts +0 -17
  186. package/src/connection/__tests__/credential.test.ts +0 -254
  187. package/src/connection/__tests__/errors.test.ts +0 -577
  188. package/src/connection/__tests__/exchange.test.ts +0 -404
  189. package/src/connection/__tests__/failure-fixtures.ts +0 -39
  190. package/src/connection/__tests__/failure-safety.test.ts +0 -75
  191. package/src/connection/__tests__/fixtures/localhost-cert.pem +0 -19
  192. package/src/connection/__tests__/fixtures/localhost-key.base64 +0 -1
  193. package/src/connection/__tests__/managed.test.ts +0 -39
  194. package/src/connection/__tests__/reasons.test.ts +0 -78
  195. package/src/connection/__tests__/self.test.ts +0 -71
  196. package/src/connection/__tests__/session.test.ts +0 -144
  197. package/src/connection/__tests__/target.test.ts +0 -95
  198. package/src/connection/auth.ts +0 -265
  199. package/src/connection/call.ts +0 -7
  200. package/src/connection/command.ts +0 -67
  201. package/src/connection/credential.ts +0 -138
  202. package/src/connection/errors.ts +0 -1
  203. package/src/connection/exchange.ts +0 -394
  204. package/src/connection/failure/classify.ts +0 -110
  205. package/src/connection/failure/index.ts +0 -18
  206. package/src/connection/failure/model.ts +0 -28
  207. package/src/connection/failure/render.ts +0 -116
  208. package/src/connection/index.ts +0 -8
  209. package/src/connection/lifetime.ts +0 -25
  210. package/src/connection/reasons.ts +0 -209
  211. package/src/connection/self.ts +0 -138
  212. package/src/connection/session.ts +0 -203
  213. package/src/connection/target.ts +0 -112
  214. package/src/connection/tsconfig.json +0 -6
  215. package/src/errors.ts +0 -66
  216. package/src/graph/.spec/api.d.ts +0 -41
  217. package/src/graph/.spec/laws/documents.ts +0 -24
  218. package/src/graph/.spec/laws/projection.ts +0 -13
  219. package/src/graph/.spec/layout.ts +0 -14
  220. package/src/graph/__tests__/mutation.test.ts +0 -51
  221. package/src/graph/__tests__/projection.test.ts +0 -22
  222. package/src/graph/__tests__/query.test.ts +0 -180
  223. package/src/graph/class.ts +0 -30
  224. package/src/graph/index.ts +0 -4
  225. package/src/graph/mutation.ts +0 -46
  226. package/src/graph/projection.ts +0 -21
  227. package/src/graph/query.ts +0 -95
  228. package/src/graph/tsconfig.json +0 -6
  229. package/src/identity/.history/ard/bootstrap-root-transfer.md +0 -57
  230. package/src/identity/.history/ard/domain-mediated-registration.md +0 -36
  231. package/src/identity/.spec/api.d.ts +0 -124
  232. package/src/identity/.spec/architecture.md +0 -33
  233. package/src/identity/.spec/capabilities/identity.ts +0 -7
  234. package/src/identity/.spec/flows/bootstrap-root.ts +0 -79
  235. package/src/identity/.spec/flows/domain-mediated-registration.ts +0 -51
  236. package/src/identity/.spec/flows/import.ts +0 -29
  237. package/src/identity/.spec/laws/identity.ts +0 -56
  238. package/src/identity/.spec/layout.ts +0 -13
  239. package/src/identity/__tests__/fixtures/registry-journey.ts +0 -65
  240. package/src/identity/__tests__/registration.test.ts +0 -64
  241. package/src/identity/__tests__/registry.test.ts +0 -74
  242. package/src/identity/__tests__/transfer.test.ts +0 -198
  243. package/src/identity/index.ts +0 -23
  244. package/src/identity/registration.ts +0 -72
  245. package/src/identity/registry.ts +0 -181
  246. package/src/identity/transfer.ts +0 -187
  247. package/src/identity/tsconfig.json +0 -6
  248. package/src/index.ts +0 -1
  249. package/src/keys/.spec/api.d.ts +0 -74
  250. package/src/keys/.spec/architecture.md +0 -13
  251. package/src/keys/.spec/laws/keys.ts +0 -43
  252. package/src/keys/.spec/layout.ts +0 -6
  253. package/src/keys/__tests__/algorithm.test.ts +0 -39
  254. package/src/keys/__tests__/keys.test.ts +0 -172
  255. package/src/keys/algorithm.ts +0 -33
  256. package/src/keys/credential.ts +0 -113
  257. package/src/keys/index.ts +0 -14
  258. package/src/keys/pair.ts +0 -219
  259. package/src/keys/tsconfig.json +0 -6
  260. package/src/lib/__tests__/admin-target.test.ts +0 -184
  261. package/src/lib/__tests__/binary.test.ts +0 -70
  262. package/src/lib/__tests__/browser-retention.test.ts +0 -212
  263. package/src/lib/__tests__/command-dx.test.ts +0 -102
  264. package/src/lib/__tests__/concurrency.test.ts +0 -62
  265. package/src/lib/__tests__/config.test.ts +0 -81
  266. package/src/lib/__tests__/design.test.ts +0 -99
  267. package/src/lib/__tests__/domain-publication.test.ts +0 -138
  268. package/src/lib/__tests__/format.test.ts +0 -22
  269. package/src/lib/__tests__/idp-session.driver.ts +0 -60
  270. package/src/lib/__tests__/idp-session.test.ts +0 -404
  271. package/src/lib/__tests__/idp.test.ts +0 -415
  272. package/src/lib/__tests__/instance-candidates.test.ts +0 -146
  273. package/src/lib/__tests__/instance-target.test.ts +0 -213
  274. package/src/lib/__tests__/instance.test.ts +0 -192
  275. package/src/lib/__tests__/local-status.test.ts +0 -202
  276. package/src/lib/__tests__/log-errors.test.ts +0 -64
  277. package/src/lib/__tests__/log.test.ts +0 -31
  278. package/src/lib/__tests__/meta.test.ts +0 -55
  279. package/src/lib/__tests__/output.test.ts +0 -162
  280. package/src/lib/__tests__/panel.test.ts +0 -40
  281. package/src/lib/__tests__/port.test.ts +0 -44
  282. package/src/lib/__tests__/prompt.test.ts +0 -25
  283. package/src/lib/__tests__/sdk-deps.test.ts +0 -68
  284. package/src/lib/__tests__/self.test.ts +0 -50
  285. package/src/lib/__tests__/skills.test.ts +0 -834
  286. package/src/lib/__tests__/studio-server-deps.test.ts +0 -111
  287. package/src/lib/__tests__/table.test.ts +0 -53
  288. package/src/lib/__tests__/update.test.ts +0 -511
  289. package/src/lib/__tests__/use-target.test.ts +0 -56
  290. package/src/lib/__tests__/validation.test.ts +0 -34
  291. package/src/lib/__tests__/view-assets.test.ts +0 -171
  292. package/src/lib/__tests__/view-external-open-intent.test.ts +0 -150
  293. package/src/lib/__tests__/view-external-open-origins.test.ts +0 -27
  294. package/src/lib/__tests__/view-open-intent.test.ts +0 -280
  295. package/src/lib/__tests__/view-port-allocation.test.ts +0 -82
  296. package/src/lib/__tests__/view-server.test.ts +0 -91
  297. package/src/lib/__tests__/view-session.test.ts +0 -100
  298. package/src/lib/__tests__/view-snapshot.test.ts +0 -77
  299. package/src/lib/admin-domain.ts +0 -43
  300. package/src/lib/admin-instance.ts +0 -56
  301. package/src/lib/admin-target.ts +0 -290
  302. package/src/lib/binary.ts +0 -127
  303. package/src/lib/browser-retention.ts +0 -210
  304. package/src/lib/browser.ts +0 -150
  305. package/src/lib/ca-fetch.ts +0 -133
  306. package/src/lib/command-dx.ts +0 -191
  307. package/src/lib/concurrency.ts +0 -31
  308. package/src/lib/config.ts +0 -57
  309. package/src/lib/credential-lifetime.ts +0 -24
  310. package/src/lib/domain-publication.ts +0 -104
  311. package/src/lib/failure-debug.ts +0 -26
  312. package/src/lib/format.ts +0 -4
  313. package/src/lib/idp-session.ts +0 -156
  314. package/src/lib/idp.ts +0 -926
  315. package/src/lib/instance-candidates.ts +0 -49
  316. package/src/lib/instance-target.ts +0 -197
  317. package/src/lib/instance.ts +0 -468
  318. package/src/lib/invocation.ts +0 -11
  319. package/src/lib/local-status.ts +0 -152
  320. package/src/lib/log.ts +0 -143
  321. package/src/lib/login-flow.ts +0 -201
  322. package/src/lib/meta.ts +0 -90
  323. package/src/lib/output.ts +0 -238
  324. package/src/lib/panel.ts +0 -61
  325. package/src/lib/port.ts +0 -41
  326. package/src/lib/proc.ts +0 -87
  327. package/src/lib/prompt.ts +0 -136
  328. package/src/lib/provision-instance.ts +0 -139
  329. package/src/lib/sdk-deps.ts +0 -104
  330. package/src/lib/self.ts +0 -22
  331. package/src/lib/skills/lock.ts +0 -117
  332. package/src/lib/skills/sync.ts +0 -878
  333. package/src/lib/skills.ts +0 -120
  334. package/src/lib/table.ts +0 -62
  335. package/src/lib/update.ts +0 -568
  336. package/src/lib/use-target.ts +0 -24
  337. package/src/lib/validation.ts +0 -59
  338. package/src/lib/view/assets.ts +0 -87
  339. package/src/lib/view/external-open-intent.ts +0 -41
  340. package/src/lib/view/external-open-origins.ts +0 -37
  341. package/src/lib/view/host-capabilities.ts +0 -17
  342. package/src/lib/view/open-intent.ts +0 -67
  343. package/src/lib/view/port-allocation.ts +0 -19
  344. package/src/lib/view/resolve.ts +0 -111
  345. package/src/lib/view/server.ts +0 -259
  346. package/src/lib/view/session.ts +0 -154
  347. package/src/lib/view/snapshot.ts +0 -101
  348. package/src/paths/.spec/api.d.ts +0 -13
  349. package/src/paths/.spec/architecture.md +0 -5
  350. package/src/paths/.spec/layout.ts +0 -3
  351. package/src/paths/index.ts +0 -13
  352. package/src/program/.spec/api.d.ts +0 -43
  353. package/src/program/.spec/architecture.md +0 -18
  354. package/src/program/.spec/laws/program.ts +0 -29
  355. package/src/program/.spec/layout.ts +0 -14
  356. package/src/program/__tests__/program.test.ts +0 -556
  357. package/src/program/argv.ts +0 -14
  358. package/src/program/build.ts +0 -205
  359. package/src/program/command.ts +0 -43
  360. package/src/program/index.ts +0 -3
  361. package/src/program/options.ts +0 -41
  362. package/src/program/registry.ts +0 -64
  363. package/src/program/tsconfig.json +0 -6
  364. package/src/setup/__tests__/instance-step.test.ts +0 -256
  365. package/src/setup/__tests__/util.test.ts +0 -29
  366. package/src/setup/engine.ts +0 -83
  367. package/src/setup/render.ts +0 -107
  368. package/src/setup/steps/admin.ts +0 -93
  369. package/src/setup/steps/agent-browser.ts +0 -81
  370. package/src/setup/steps/auth.ts +0 -54
  371. package/src/setup/steps/domain.ts +0 -68
  372. package/src/setup/steps/index.ts +0 -18
  373. package/src/setup/steps/instance.ts +0 -206
  374. package/src/setup/steps/skills-bridge.ts +0 -70
  375. package/src/setup/steps/skills.ts +0 -61
  376. package/src/setup/types.ts +0 -61
  377. package/src/setup/util.ts +0 -34
  378. package/src/state/.spec/api.d.ts +0 -152
  379. package/src/state/.spec/architecture.md +0 -45
  380. package/src/state/.spec/capabilities/state.ts +0 -7
  381. package/src/state/.spec/flows/identity-update.ts +0 -22
  382. package/src/state/.spec/laws/state.ts +0 -73
  383. package/src/state/.spec/layout.ts +0 -15
  384. package/src/state/__tests__/exchange-credentials.test.ts +0 -341
  385. package/src/state/__tests__/files.test.ts +0 -136
  386. package/src/state/__tests__/fixtures/identity-transition.ts +0 -37
  387. package/src/state/__tests__/fixtures/session-route-process.ts +0 -93
  388. package/src/state/__tests__/identities.test.ts +0 -142
  389. package/src/state/__tests__/paths.test.ts +0 -28
  390. package/src/state/__tests__/session-routes.test.ts +0 -138
  391. package/src/state/exchange-credentials.ts +0 -250
  392. package/src/state/files.ts +0 -170
  393. package/src/state/identities.ts +0 -229
  394. package/src/state/index.ts +0 -31
  395. package/src/state/paths.ts +0 -64
  396. package/src/state/session-routes.ts +0 -34
  397. package/src/state/tsconfig.json +0 -6
  398. package/src/telemetry/__tests__/analyze-log.test.ts +0 -38
  399. package/src/telemetry/__tests__/gate.test.ts +0 -63
  400. package/src/telemetry/__tests__/recorder.test.ts +0 -111
  401. package/src/telemetry/__tests__/redact.test.ts +0 -79
  402. package/src/telemetry/__tests__/retention.test.ts +0 -267
  403. package/src/telemetry/__tests__/session.test.ts +0 -166
  404. package/src/telemetry/__tests__/settings.test.ts +0 -102
  405. package/src/telemetry/__tests__/store-scan.test.ts +0 -128
  406. package/src/telemetry/__tests__/trigger.test.ts +0 -78
  407. package/src/telemetry/adapters/__tests__/claude-code.test.ts +0 -89
  408. package/src/telemetry/adapters/__tests__/codex.test.ts +0 -138
  409. package/src/telemetry/adapters/__tests__/index.test.ts +0 -88
  410. package/src/telemetry/adapters/claude-code.ts +0 -90
  411. package/src/telemetry/adapters/codex.ts +0 -160
  412. package/src/telemetry/adapters/index.ts +0 -45
  413. package/src/telemetry/adapters/types.ts +0 -21
  414. package/src/telemetry/analyze.ts +0 -249
  415. package/src/telemetry/gate.ts +0 -79
  416. package/src/telemetry/recorder.ts +0 -68
  417. package/src/telemetry/redact.ts +0 -53
  418. package/src/telemetry/retention.ts +0 -176
  419. package/src/telemetry/session.ts +0 -94
  420. package/src/telemetry/settings.ts +0 -79
  421. package/src/telemetry/store.ts +0 -174
  422. package/src/telemetry/trigger.ts +0 -107
  423. package/src/telemetry/types.ts +0 -64
  424. package/src/test-utils.ts +0 -18
  425. package/src/ui/.spec/api.d.ts +0 -14
  426. package/src/ui/.spec/architecture.md +0 -14
  427. package/src/ui/.spec/laws.ts +0 -50
  428. package/src/ui/.spec/layout.ts +0 -16
  429. package/src/ui/__tests__/ui.test.ts +0 -1742
  430. package/src/ui/index.ts +0 -13
  431. package/src/ui/lock.ts +0 -89
  432. package/src/ui/model.ts +0 -83
  433. package/src/ui/operations.ts +0 -1044
  434. package/src/ui/project.ts +0 -333
  435. package/src/ui/release.ts +0 -398
  436. package/src/ui/runner.ts +0 -18
  437. package/studio/client/dist/assets/index-BFVFs_8x.js +0 -81
  438. package/studio/client/dist/assets/index-DuB9iUdu.css +0 -2
  439. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +0 -8
  440. package/studio/tsconfig.json +0 -24
  441. package/viewer/tsconfig.json +0 -13
@@ -4,9 +4,20 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Domain Studio</title>
7
- <script type="module" crossorigin src="/assets/index-BFVFs_8x.js"></script>
7
+ <script>
8
+ // Paint the theme before the first frame — the app would otherwise flash light.
9
+ try {
10
+ var pref = localStorage.getItem('studio.theme') || 'system'
11
+ if (
12
+ pref === 'dark' ||
13
+ (pref === 'system' && matchMedia('(prefers-color-scheme: dark)').matches)
14
+ )
15
+ document.documentElement.classList.add('dark')
16
+ } catch {}
17
+ </script>
18
+ <script type="module" crossorigin src="/assets/index-C4kNlvwF.js"></script>
8
19
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dd_uD5pT.js">
9
- <link rel="stylesheet" crossorigin href="/assets/index-DuB9iUdu.css">
20
+ <link rel="stylesheet" crossorigin href="/assets/index-5C1uT6ML.css">
10
21
  </head>
11
22
  <body>
12
23
  <div id="root"></div>
@@ -21,7 +21,7 @@
21
21
  "typecheck": "tsgo --noEmit"
22
22
  },
23
23
  "dependencies": {
24
- "@astrale-os/sdk": "0.5.0-beta.54",
24
+ "@astrale-os/sdk": "0.5.0-beta.64",
25
25
  "@astrale-os/shell": "0.4.2-beta.6",
26
26
  "@dagrejs/dagre": "^3.0.0",
27
27
  "@radix-ui/react-collapsible": "^1.1.0",
@@ -34,6 +34,7 @@
34
34
  "@radix-ui/react-slot": "^1.1.2",
35
35
  "@radix-ui/react-tooltip": "^1.1.8",
36
36
  "@tanstack/react-query": "^5.66.0",
37
+ "@tisoap/react-flow-smart-edge": "^5.0.0",
37
38
  "@xyflow/react": "^12.4.4",
38
39
  "chokidar": "^4.0.3",
39
40
  "class-variance-authority": "^0.7.1",
@@ -35,8 +35,14 @@ test('Ask uses the active harness model and matching per-harness conversation',
35
35
  roots.push(root)
36
36
  mkdirSync(join(root, 'schema'))
37
37
  writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
38
- writeFileSync(join(root, 'application.ts'), 'export default {}\n')
39
38
  writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
39
+ writeFileSync(
40
+ join(root, 'application.ts'),
41
+ `import { defineApplication } from '@astrale-os/sdk/application'
42
+ import { Test } from './schema/index.js'
43
+ export default defineApplication({ schema: Test, runtime: {} as never })
44
+ `,
45
+ )
40
46
  const handle = registerDomain(root)!
41
47
  domainIds.push(handle.id)
42
48
 
@@ -6,6 +6,7 @@ import type { StudioEvent } from '../../../shared/types'
6
6
  import type { DomainHandle } from '../../domain'
7
7
  import type { HarnessMcpServer } from '../harness/adapter'
8
8
 
9
+ import { studioCliCommand } from '../../cli'
9
10
  import { removeState, statePath, writeJson } from '../../state/store'
10
11
  import { openBridgeSession } from './routes'
11
12
 
@@ -32,7 +33,6 @@ export function setBridgePort(port: number): void {
32
33
  }
33
34
 
34
35
  const MCP_SERVER = join(import.meta.dir, 'stdio.ts')
35
- const BIN = process.env.DOMAIN_STUDIO_BRIDGE_BUN || process.execPath
36
36
 
37
37
  /** Mint the run-scoped MCP grant and its secret-bearing configuration file. */
38
38
  export function startBridge(handle: DomainHandle, notify: (event: StudioEvent) => void): Bridge {
@@ -45,10 +45,22 @@ export function startBridge(handle: DomainHandle, notify: (event: StudioEvent) =
45
45
  const bridgeConfigPath = statePath(handle.root, bridgeRel)
46
46
  chmodSync(bridgeConfigPath, 0o600)
47
47
 
48
+ let bridgeCommand: string[]
49
+ try {
50
+ bridgeCommand = studioCliCommand(['__studio-bridge', '--config', bridgeConfigPath])
51
+ } catch {
52
+ bridgeCommand = [
53
+ process.env.DOMAIN_STUDIO_BRIDGE_BUN || process.execPath,
54
+ MCP_SERVER,
55
+ '--config',
56
+ bridgeConfigPath,
57
+ ]
58
+ }
59
+
48
60
  const server: HarnessMcpServer = {
49
61
  name: 'domain-studio',
50
- command: BIN,
51
- args: [MCP_SERVER, '--config', bridgeConfigPath],
62
+ command: bridgeCommand[0],
63
+ args: bridgeCommand.slice(1),
52
64
  required: true,
53
65
  approvalMode: 'approve',
54
66
  enabledTools: Object.keys(TOOL_ROUTES),
@@ -8,5 +8,10 @@ test('embedded agents receive the current SDK and frontend skill contracts', ()
8
8
  expect(prompt).toContain('modular Actions and Workflows')
9
9
  expect(prompt).toContain('astrale-frontend-design')
10
10
  expect(prompt).toContain('Runtime/Application entries')
11
+ expect(prompt).toContain('"@$ASTRALE_ISSUES_PROJECT_ID::createIssue"')
12
+ expect(prompt).toContain('astrale --ci --no-prompt call')
13
+ expect(prompt).toContain('Issue Node ID')
14
+ expect(prompt).not.toContain('class.Issue:create')
15
+ expect(prompt).not.toContain('initialTags')
11
16
  expect(prompt).not.toContain('schema/ runtime/ views/')
12
17
  })
@@ -54,11 +54,26 @@ export function buildSystemPrompt(options: { bridge: boolean }): string {
54
54
  'working in this domain costs YOU friction — a confusing or missing Astrale API/error, stale',
55
55
  'docs or skills, or a bug that forced a workaround — FILE a DX issue as part of the task, per',
56
56
  'the **astrale-cli** skill’s “Reporting DX Issues” section: dedup against open issues first, then',
57
- '`astrale call /:issues.astrale.ai:class.Issue:create -i admin parent=/issues status=open title=… body=… initialTags=\'["<bug|friction|feature>","<area>"]\'`.',
58
- 'It is best-effort and non-blocking — never let filing one derail the user’s task — and mention',
59
- 'the returned issue path in your reply when you file one.',
57
+ 'use `astrale --ci --no-prompt call "@$ASTRALE_ISSUES_PROJECT_ID::createIssue" -i admin',
58
+ ' --data \'{"title":"<one line>","description":"<evidence>","priority":2}\' --json`.',
59
+ 'ASTRALE_ISSUES_PROJECT_ID must name the destination Project. It is best-effort and non-blocking',
60
+ '— never let filing one derail the user’s task — and mention the returned Issue Node ID and',
61
+ 'reference in your reply when you file one.',
60
62
  '',
61
- 'REPLY PROTOCOL this is how the user sees your answers (they are NOT watching your terminal):',
63
+ 'WHERE THE USER READS YOU two channels, and they are not interchangeable:',
64
+ ' 1. THE CONVERSATION. Your final message is shown as your reply in the studio chat. Always',
65
+ ' end the turn with one, even when you also answered threads — a silent turn reads as a',
66
+ ' failure. Keep it to what changed and what it means.',
67
+ ' 2. THE THREADS. Comments pinned on the schema are answered IN PLACE, on the thread.',
68
+ 'When open threads came with this turn, answer them in the threads AND say so in your final',
69
+ 'message (e.g. "answered 2 comments on Invoice and Quote") so the reader knows where to look.',
70
+ 'With no open thread, the conversation is the only channel — just answer there.',
71
+ '',
72
+ 'CONTEXT DOCUMENTS — whatever the user attached lives under `.domain-studio/context/docs/`',
73
+ 'and is listed with its path in each turn. Nothing is pasted into the prompt: open the ones',
74
+ 'that matter, ignore the rest.',
75
+ '',
76
+ 'REPLY PROTOCOL — how a thread answer must be shaped:',
62
77
  '- For EVERY open thread, append exactly one concise {role:"author"} reply: what you changed,',
63
78
  ' your answer (with `options` when it is a decision), or a clarifying question.',
64
79
  '- If you fully addressed a thread, also set "status":"closed" and add a short "closeNote".',
@@ -3,11 +3,13 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
+ import type { AgentRun } from '../../shared/types'
6
7
  import type { AskResult } from './harness/adapter'
7
8
 
8
9
  import { registerDomain, unregisterDomain } from '../domain'
9
10
  import { getHarnessById } from './harness/registry'
10
11
  import { handleAgentRoute } from './routes'
12
+ import { persistRun } from './run/transcript'
11
13
  import { NdjsonChannel } from './stream'
12
14
 
13
15
  const roots: string[] = []
@@ -26,8 +28,14 @@ function fixture() {
26
28
  roots.push(root)
27
29
  mkdirSync(join(root, 'schema'))
28
30
  writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
29
- writeFileSync(join(root, 'application.ts'), 'export default {}\n')
30
31
  writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
32
+ writeFileSync(
33
+ join(root, 'application.ts'),
34
+ `import { defineApplication } from '@astrale-os/sdk/application'
35
+ import { Test } from './schema/index.js'
36
+ export default defineApplication({ schema: Test, runtime: {} as never })
37
+ `,
38
+ )
31
39
  const handle = registerDomain(root)!
32
40
  domainIds.push(handle.id)
33
41
  return handle
@@ -67,6 +75,36 @@ test('owns harness status and prompt routes behind one agent boundary', async ()
67
75
  })
68
76
  })
69
77
 
78
+ test('serves the bounded persisted conversation history', async () => {
79
+ const handle = fixture()
80
+ const saved = (id: string, createdAt: string): AgentRun => ({
81
+ id,
82
+ domainId: handle.id,
83
+ harness: 'codex',
84
+ status: 'succeeded',
85
+ createdAt,
86
+ summary: id,
87
+ instruction: `Do ${id}`,
88
+ targetCommentIds: [],
89
+ events: [],
90
+ })
91
+ persistRun(handle.root, saved('older', '2026-08-20T01:00:00.000Z'), true)
92
+ persistRun(handle.root, saved('newer', '2026-08-20T02:00:00.000Z'), true)
93
+
94
+ const url = new URL(`http://127.0.0.1/api/domain/${handle.id}/agent/history?limit=1`)
95
+ const response = await handleAgentRoute({
96
+ req: new Request(url),
97
+ url,
98
+ rest: '/agent/history',
99
+ body: {},
100
+ handle,
101
+ notify: () => {},
102
+ })
103
+
104
+ expect(response?.status).toBe(200)
105
+ expect(await response?.json()).toEqual([expect.objectContaining({ id: 'newer' })])
106
+ })
107
+
70
108
  test('ignores non-agent routes and rejects unknown agent routes', async () => {
71
109
  process.env.DOMAIN_STUDIO_HARNESS = 'mock'
72
110
  expect(await route('/context')).toBeNull()
@@ -28,6 +28,7 @@ import {
28
28
  setSessionId,
29
29
  submitRun,
30
30
  } from './run/coordinator'
31
+ import { readRunHistory } from './run/transcript'
31
32
  import { readUsage } from './run/usage'
32
33
  import { NdjsonChannel } from './stream'
33
34
 
@@ -88,6 +89,8 @@ export async function handleAgentRoute(input: AgentRouteInput): Promise<Response
88
89
  })
89
90
  return result.error ? json({ error: result.error }) : json(result.run)
90
91
  }
92
+ if (rest === '/agent/history' && req.method === 'GET')
93
+ return json(readRunHistory(id, root, Number(url.searchParams.get('limit')) || undefined))
91
94
  if (rest === '/agent/cancel' && req.method === 'POST') return json({ ok: cancelRun(id) })
92
95
  if (rest === '/agent/reset' && req.method === 'POST') return json({ ok: resetConversation(id) })
93
96
  if (rest === '/agent/session') {
@@ -58,8 +58,14 @@ function fixture(): DomainHandle {
58
58
  roots.push(root)
59
59
  mkdirSync(join(root, 'schema'))
60
60
  writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
61
- writeFileSync(join(root, 'application.ts'), 'export default {}\n')
62
61
  writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
62
+ writeFileSync(
63
+ join(root, 'application.ts'),
64
+ `import { defineApplication } from '@astrale-os/sdk/application'
65
+ import { Test } from './schema/index.js'
66
+ export default defineApplication({ schema: Test, runtime: {} as never })
67
+ `,
68
+ )
63
69
  const handle = registerDomain(root)!
64
70
  domainIds.push(handle.id)
65
71
  return handle
@@ -136,6 +136,7 @@ export async function prepareRun(
136
136
  : awaiting.length === 1
137
137
  ? '1 open thread'
138
138
  : `${awaiting.length} open threads`,
139
+ ...(message ? { instruction: message } : {}),
139
140
  targetCommentIds: awaiting.map((comment) => comment.id),
140
141
  events: [],
141
142
  sessionId: resume,
@@ -1,5 +1,5 @@
1
1
  import { afterEach, expect, test } from 'bun:test'
2
- import { mkdtempSync, rmSync } from 'node:fs'
2
+ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
@@ -8,7 +8,7 @@ import type { AgentRun } from '../../../shared/types'
8
8
  import { asJsonRecord } from '../../json'
9
9
  import { readJson, writeJson } from '../../state/store'
10
10
  import { currentRun, hydrateRun } from './live-state'
11
- import { readLastRun } from './transcript'
11
+ import { persistRun, readLastRun, readRunHistory } from './transcript'
12
12
 
13
13
  const roots: string[] = []
14
14
 
@@ -45,6 +45,51 @@ test('reconciles a run orphaned by restart and hydrates its terminal snapshot',
45
45
  status: 'interrupted',
46
46
  sessionId: 'thread-1',
47
47
  })
48
+ expect(readRunHistory(domainId, root)).toEqual([
49
+ expect.objectContaining({ id: running.id, status: 'interrupted' }),
50
+ ])
51
+ })
52
+
53
+ test('reads bounded terminal history oldest first and skips unrelated or unreadable entries', () => {
54
+ const root = mkdtempSync(join(tmpdir(), 'studio-agent-history-'))
55
+ roots.push(root)
56
+ const domainId = `domain-${crypto.randomUUID()}`
57
+ const run = (id: string, createdAt: string, owner = domainId): AgentRun => ({
58
+ id,
59
+ domainId: owner,
60
+ harness: 'codex',
61
+ status: 'succeeded',
62
+ createdAt,
63
+ summary: id,
64
+ instruction: `Do ${id}`,
65
+ targetCommentIds: [],
66
+ events: [],
67
+ prompt: {
68
+ createdAt,
69
+ systemPrompt: 'system',
70
+ turnPrompt: 'the whole handoff markdown',
71
+ firstTurn: true,
72
+ resumed: false,
73
+ mcpTools: [],
74
+ },
75
+ })
76
+
77
+ persistRun(root, run('newer', '2026-08-20T02:00:00.000Z'), true)
78
+ persistRun(root, run('older', '2026-08-20T01:00:00.000Z'), true)
79
+ persistRun(root, run('foreign', '2026-08-20T03:00:00.000Z', 'another-domain'), true)
80
+ mkdirSync(join(root, '.domain-studio/.cache/agent/runs/unreadable.json'))
81
+
82
+ expect(readRunHistory(domainId, root)).toEqual([
83
+ expect.objectContaining({ id: 'older', instruction: 'Do older' }),
84
+ expect.objectContaining({ id: 'newer', instruction: 'Do newer' }),
85
+ ])
86
+ expect(readRunHistory(domainId, root, 1)).toEqual([expect.objectContaining({ id: 'newer' })])
87
+ // the frozen prompt is the biggest field of a turn and the chat never shows it
88
+ expect(
89
+ readJson(root, '.cache/agent/runs/newer.json', (v) => v as AgentRun, null)?.prompt,
90
+ ).toBeDefined()
91
+ expect(readRunHistory(domainId, root).every((turn) => turn.prompt === undefined)).toBe(true)
92
+ expect(readRunHistory(domainId, root, -10)).toEqual([expect.objectContaining({ id: 'newer' })])
48
93
  })
49
94
 
50
95
  test('admits future run fields but rejects malformed persisted run structure', () => {
@@ -7,10 +7,11 @@ import {
7
7
  type MergeResult,
8
8
  } from '../../../shared/types'
9
9
  import { asBoolean, asFiniteNumber, asJsonRecord, asString, asStringArray } from '../../json'
10
- import { readJson, writeJson } from '../../state/store'
10
+ import { listState, readJson, writeJson } from '../../state/store'
11
11
 
12
12
  const LAST_RUN_FILE = '.cache/agent/last-run.json'
13
- const runFile = (id: string) => `.cache/agent/runs/${id}.json`
13
+ const RUNS_DIR = '.cache/agent/runs'
14
+ const runFile = (id: string) => `${RUNS_DIR}/${id}.json`
14
15
 
15
16
  const RUN_STATUSES = new Set<AgentRun['status']>([
16
17
  'queued',
@@ -139,6 +140,7 @@ function decodeAgentRun(value: unknown): AgentRun | undefined {
139
140
  const decoded = decodeAgentEvent(event)
140
141
  return decoded ? [decoded] : []
141
142
  })
143
+ const instruction = asString(record.instruction)
142
144
  const finishedAt = asString(record.finishedAt)
143
145
  const sessionId = asString(record.sessionId)
144
146
  const resumed = asBoolean(record.resumed)
@@ -158,6 +160,7 @@ function decodeAgentRun(value: unknown): AgentRun | undefined {
158
160
  summary,
159
161
  targetCommentIds,
160
162
  events,
163
+ ...(instruction === undefined ? {} : { instruction }),
161
164
  ...(finishedAt === undefined ? {} : { finishedAt }),
162
165
  ...(sessionId === undefined ? {} : { sessionId }),
163
166
  ...(resumed === undefined ? {} : { resumed }),
@@ -190,7 +193,33 @@ export function readLastRun(domainId: string, root: string): AgentRun | null {
190
193
  last.finishedAt = last.finishedAt ?? new Date().toISOString()
191
194
  last.error =
192
195
  'the studio restarted during this turn — your conversation is preserved; submit again to continue'
193
- persistRun(root, last)
196
+ persistRun(root, last, true)
194
197
  }
195
198
  return last
196
199
  }
200
+
201
+ /**
202
+ * The conversation so far, oldest first: every transcript this domain kept.
203
+ *
204
+ * Only terminal runs are written to `runs/`, so the ACTIVE one is missing here —
205
+ * callers layer it on top (the live run is already streamed to them).
206
+ *
207
+ * The frozen `prompt` of each turn is dropped: it is the largest field by far (the
208
+ * whole handoff markdown) and nothing reads it here — the activity drawer inspects
209
+ * the CURRENT run, which still carries it.
210
+ */
211
+ export function readRunHistory(domainId: string, root: string, limit = 40): AgentRun[] {
212
+ const boundedLimit = Number.isSafeInteger(limit) ? Math.min(Math.max(limit, 1), 100) : 40
213
+ const runs: AgentRun[] = []
214
+ for (const file of listState(root, RUNS_DIR)) {
215
+ if (!file.endsWith('.json')) continue
216
+ try {
217
+ const run = readJson(root, `${RUNS_DIR}/${file}`, decodeAgentRun, null)
218
+ if (run && run.domainId === domainId) runs.push(run)
219
+ } catch {
220
+ // One unreadable transcript must not hide the rest of the conversation.
221
+ }
222
+ }
223
+ runs.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
224
+ return runs.slice(-boundedLimit).map(({ prompt: _prompt, ...turn }) => turn)
225
+ }
@@ -0,0 +1,31 @@
1
+ import { expect, test } from 'bun:test'
2
+
3
+ import { documentResponseHeaders } from './documents'
4
+
5
+ test('an uploaded document can never execute on the studio origin', () => {
6
+ // the uploader picks the stored MIME type, and this origin drives a local agent
7
+ const html = documentResponseHeaders('text/html;charset=utf-8', 'notes.html')
8
+
9
+ expect(html['content-type']).toBe('application/octet-stream')
10
+ expect(html['content-disposition']).toBe("attachment; filename*=UTF-8''notes.html")
11
+ expect(html['x-content-type-options']).toBe('nosniff')
12
+ expect(documentResponseHeaders('image/svg+xml', 'logo.svg')['content-type']).toBe(
13
+ 'application/octet-stream',
14
+ )
15
+ })
16
+
17
+ test('documents that only ever render as data stay inline', () => {
18
+ const markdown = documentResponseHeaders('text/markdown;charset=utf-8', 'pricing.md')
19
+
20
+ expect(markdown['content-type']).toBe('text/markdown;charset=utf-8')
21
+ expect(markdown['content-disposition']).toBe('inline')
22
+ expect(documentResponseHeaders('application/pdf', 'quote.pdf')['content-disposition']).toBe(
23
+ 'inline',
24
+ )
25
+ })
26
+
27
+ test('a download name survives spaces and non-ascii', () => {
28
+ expect(documentResponseHeaders('application/zip', 'Décisions tarifaires.zip')).toMatchObject({
29
+ 'content-disposition': "attachment; filename*=UTF-8''D%C3%A9cisions%20tarifaires.zip",
30
+ })
31
+ })
@@ -8,6 +8,40 @@ import {
8
8
  } from '../state/documents'
9
9
  import { json, notFound, type DomainRouteContext } from './http'
10
10
 
11
+ /**
12
+ * Types safe to render in a tab. Everything else downloads instead.
13
+ *
14
+ * The uploader chooses the stored MIME type, and this origin also serves the API
15
+ * that drives a local agent — so an `text/html` (or SVG) document opened from the
16
+ * studio would run its script with the studio's own privileges.
17
+ */
18
+ const INLINE_TYPES = new Set([
19
+ 'text/plain',
20
+ 'text/markdown',
21
+ 'text/csv',
22
+ 'application/pdf',
23
+ 'image/png',
24
+ 'image/jpeg',
25
+ 'image/gif',
26
+ 'image/webp',
27
+ 'image/avif',
28
+ ])
29
+
30
+ export function documentResponseHeaders(type: string, name: string): Record<string, string> {
31
+ const mime = (type.split(';')[0] ?? '').trim().toLowerCase()
32
+ if (INLINE_TYPES.has(mime))
33
+ return {
34
+ 'content-type': type,
35
+ 'content-disposition': 'inline',
36
+ 'x-content-type-options': 'nosniff',
37
+ }
38
+ return {
39
+ 'content-type': 'application/octet-stream',
40
+ 'content-disposition': `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,
41
+ 'x-content-type-options': 'nosniff',
42
+ }
43
+ }
44
+
11
45
  /** Routes that must run before POST bodies are parsed as JSON. */
12
46
  export async function handleDocumentTransport(
13
47
  req: Request,
@@ -33,7 +67,7 @@ export async function handleDocumentTransport(
33
67
  const document = readDocument(root, decodeURIComponent(raw[1]))
34
68
  if (!document) return notFound()
35
69
  return new Response(Bun.file(document.abs), {
36
- headers: { 'content-type': document.meta.type },
70
+ headers: documentResponseHeaders(document.meta.type, document.meta.name),
37
71
  })
38
72
  }
39
73
 
@@ -27,8 +27,14 @@ test('loadout fails closed instead of probing ambient Claude when gateway auth f
27
27
  roots.push(root)
28
28
  mkdirSync(join(root, 'schema'))
29
29
  writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
30
- writeFileSync(join(root, 'application.ts'), 'export default {}\n')
31
30
  writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
31
+ writeFileSync(
32
+ join(root, 'application.ts'),
33
+ `import { defineApplication } from '@astrale-os/sdk/application'
34
+ import { Test } from './schema/index.js'
35
+ export default defineApplication({ schema: Test, runtime: {} as never })
36
+ `,
37
+ )
32
38
  const handle = registerDomain(root)!
33
39
  domainIds.push(handle.id)
34
40
 
@@ -19,8 +19,14 @@ function fixture() {
19
19
  roots.push(root)
20
20
  mkdirSync(join(root, 'schema'))
21
21
  writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
22
- writeFileSync(join(root, 'application.ts'), 'export default {}\n')
23
22
  writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
23
+ writeFileSync(
24
+ join(root, 'application.ts'),
25
+ `import { defineApplication } from '@astrale-os/sdk/application'
26
+ import { Test } from './schema/index.js'
27
+ export default defineApplication({ schema: Test, runtime: {} as never })
28
+ `,
29
+ )
24
30
  const handle = registerDomain(root)!
25
31
  domainIds.push(handle.id)
26
32
  return handle
@@ -169,13 +169,13 @@ function hashFileIfPresent(hash: ReturnType<typeof createHash>, label: string, f
169
169
  }
170
170
  }
171
171
 
172
- function bundleCacheKey(root: string, schemaDirName: string): string {
172
+ function bundleCacheKey(root: string, schemaDirName: string, applicationFile: string): string {
173
173
  const hash = createHash('sha256')
174
174
  hash.update(`domain-studio-bundle-cache-v${BUNDLE_CACHE_VERSION}\0`)
175
175
  hash.update(`schema-dir:${schemaDirName}\0`)
176
176
  hash.update(`bun:${Bun.version}\0`)
177
177
 
178
- const files = hashAnatomyFiles(root, schemaDirName)
178
+ const files = hashAnatomyFiles(root, schemaDirName, applicationFile)
179
179
  for (const [file, digest] of Object.entries(files).sort(([a], [b]) => a.localeCompare(b))) {
180
180
  hash.update(`${file}\0${digest}\0`)
181
181
  }
@@ -213,7 +213,7 @@ export async function getBundle(id: string, rebuild = false): Promise<StudioSche
213
213
  const h = getDomain(id)
214
214
  if (!h) return null
215
215
  if (!rebuild && bundles.has(id)) return bundles.get(id)!
216
- const keyBefore = bundleCacheKey(h.root, h.schemaDirName)
216
+ const keyBefore = bundleCacheKey(h.root, h.schemaDirName, h.applicationFile)
217
217
  if (!rebuild) {
218
218
  const cached = readCachedBundle(h.root, keyBefore)
219
219
  if (cached) {
@@ -224,7 +224,7 @@ export async function getBundle(id: string, rebuild = false): Promise<StudioSche
224
224
  }
225
225
  const b = await buildBundle(h)
226
226
  bundles.set(id, b)
227
- const keyAfter = bundleCacheKey(h.root, h.schemaDirName)
227
+ const keyAfter = bundleCacheKey(h.root, h.schemaDirName, h.applicationFile)
228
228
  if (keyAfter === keyBefore) writeCachedBundle(h.root, keyAfter, b)
229
229
  return b
230
230
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * detect.ts — workspace domain detection. Walks a directory tree (symlink-safe,
3
3
  * ignoring node_modules/.git/.astrale/dist/…), registering every confirmed
4
- * domain. Also handles being pointed at a single astrale.config.ts.
4
+ * domain whose Application selects an authored Schema. Also handles being pointed
5
+ * at a single astrale.config.ts.
5
6
  */
6
7
  import { existsSync, lstatSync, readdirSync } from 'node:fs'
7
8
  import { dirname, join, resolve } from 'node:path'
@@ -23,33 +24,29 @@ const IGNORE = new Set([
23
24
  ])
24
25
 
25
26
  /** Resolve a CLI target: a path to astrale.config.ts, a domain dir, or a workspace to scan. */
26
- export function resolveTarget(target: string, schemaDirName = 'schema'): DomainHandle[] {
27
+ export function resolveTarget(target: string): DomainHandle[] {
27
28
  const abs = resolve(target)
28
29
  if (abs.endsWith('astrale.config.ts')) {
29
- const h = registerDomain(dirname(abs), schemaDirName)
30
+ const h = registerDomain(dirname(abs))
30
31
  return h ? [h] : []
31
32
  }
32
33
  // a single domain dir?
33
- const single = registerDomain(abs, schemaDirName)
34
+ const single = registerDomain(abs)
34
35
  if (single) {
35
36
  // still scan beneath for sibling/nested domains (e.g. a workspace whose root is also a domain)
36
- const nested = scanWorkspace(abs, schemaDirName).filter((d) => d.id !== single.id)
37
+ const nested = scanWorkspace(abs).filter((d) => d.id !== single.id)
37
38
  return dedupe([single, ...nested])
38
39
  }
39
- return scanWorkspace(abs, schemaDirName)
40
+ return scanWorkspace(abs)
40
41
  }
41
42
 
42
- export function scanWorkspace(
43
- workspace: string,
44
- schemaDirName = 'schema',
45
- maxDepth = 4,
46
- ): DomainHandle[] {
43
+ export function scanWorkspace(workspace: string, maxDepth = 4): DomainHandle[] {
47
44
  const found: DomainHandle[] = []
48
45
  const root = resolve(workspace)
49
46
  if (!existsSync(root)) return found
50
47
 
51
48
  const walk = (dir: string, depth: number) => {
52
- const h = registerDomain(dir, schemaDirName)
49
+ const h = registerDomain(dir)
53
50
  if (h) found.push(h)
54
51
  if (depth >= maxDepth) return
55
52
  let entries: string[]