@astrale-os/shell 0.1.0 → 0.2.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 (284) hide show
  1. package/LICENSE +202 -0
  2. package/dist/application/apps.d.ts +18 -0
  3. package/dist/application/apps.d.ts.map +1 -0
  4. package/dist/application/apps.js +35 -0
  5. package/dist/application/namespaces.d.ts +21 -0
  6. package/dist/application/namespaces.d.ts.map +1 -0
  7. package/dist/application/namespaces.js +12 -0
  8. package/dist/application/session/open.d.ts +19 -0
  9. package/dist/application/session/open.d.ts.map +1 -0
  10. package/dist/application/session/open.js +46 -0
  11. package/dist/application/session/session.d.ts +41 -0
  12. package/dist/application/session/session.d.ts.map +1 -0
  13. package/dist/application/session/session.js +9 -0
  14. package/dist/application/session/token.d.ts +24 -0
  15. package/dist/application/session/token.d.ts.map +1 -0
  16. package/dist/application/session/token.js +51 -0
  17. package/dist/application/spaces.d.ts +17 -0
  18. package/dist/application/spaces.d.ts.map +1 -0
  19. package/dist/application/spaces.js +30 -0
  20. package/dist/application/user.d.ts +20 -0
  21. package/dist/application/user.d.ts.map +1 -0
  22. package/dist/application/user.js +26 -0
  23. package/dist/application/view.d.ts +20 -0
  24. package/dist/application/view.d.ts.map +1 -0
  25. package/dist/application/view.js +18 -0
  26. package/dist/{application/channels/children-channel.d.ts → channel/children.d.ts} +4 -4
  27. package/dist/channel/children.d.ts.map +1 -0
  28. package/dist/channel/listeners.d.ts +8 -0
  29. package/dist/channel/listeners.d.ts.map +1 -0
  30. package/dist/{adapters/message-port.transport.d.ts → channel/message-port.d.ts} +2 -2
  31. package/dist/channel/message-port.d.ts.map +1 -0
  32. package/dist/{application/channels → channel}/observer.d.ts +2 -1
  33. package/dist/channel/observer.d.ts.map +1 -0
  34. package/dist/{application/channels/parent-channel.d.ts → channel/parent.d.ts} +4 -4
  35. package/dist/channel/parent.d.ts.map +1 -0
  36. package/dist/{application/channels/parent-channel.js → channel/parent.js} +1 -1
  37. package/dist/channel/transport.d.ts +8 -0
  38. package/dist/channel/transport.d.ts.map +1 -0
  39. package/dist/index.d.ts +56 -41
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +23 -20
  42. package/dist/{domain/intent → intent}/middleware/capabilities.d.ts +2 -2
  43. package/dist/intent/middleware/capabilities.d.ts.map +1 -0
  44. package/dist/intent/middleware/logging.d.ts.map +1 -0
  45. package/dist/{domain/intent → intent}/middleware/rate-limit.d.ts +1 -1
  46. package/dist/intent/middleware/rate-limit.d.ts.map +1 -0
  47. package/dist/{application/intent/pipeline-runner.d.ts → intent/pipeline.d.ts} +3 -3
  48. package/dist/intent/pipeline.d.ts.map +1 -0
  49. package/dist/intent/policy.d.ts +40 -0
  50. package/dist/intent/policy.d.ts.map +1 -0
  51. package/dist/intent/policy.js +15 -0
  52. package/dist/{application/intent → intent}/request.d.ts +5 -14
  53. package/dist/intent/request.d.ts.map +1 -0
  54. package/dist/{application/intent → intent}/request.js +6 -20
  55. package/dist/intent/router.d.ts +31 -0
  56. package/dist/intent/router.d.ts.map +1 -0
  57. package/dist/{application/intent → intent}/router.js +1 -15
  58. package/dist/kernel/client.d.ts +33 -0
  59. package/dist/kernel/client.d.ts.map +1 -0
  60. package/dist/kernel/credential.d.ts +35 -0
  61. package/dist/kernel/credential.d.ts.map +1 -0
  62. package/dist/kernel/credential.js +43 -0
  63. package/dist/kernel/delegated.d.ts +17 -0
  64. package/dist/kernel/delegated.d.ts.map +1 -0
  65. package/dist/kernel/delegated.js +29 -0
  66. package/dist/{application/windowing/token-refresh.d.ts → kernel/delegation/refresh.d.ts} +2 -2
  67. package/dist/kernel/delegation/refresh.d.ts.map +1 -0
  68. package/dist/{application/windowing/token-refresh.js → kernel/delegation/refresh.js} +1 -1
  69. package/dist/{domain/identity/delegation.d.ts → kernel/delegation/token.d.ts} +1 -1
  70. package/dist/kernel/delegation/token.d.ts.map +1 -0
  71. package/dist/{adapters/kernel-client.adapter.d.ts → kernel/session.d.ts} +15 -12
  72. package/dist/kernel/session.d.ts.map +1 -0
  73. package/dist/{adapters/kernel-client.adapter.js → kernel/session.js} +10 -15
  74. package/dist/message/ctrl.d.ts +34 -0
  75. package/dist/message/ctrl.d.ts.map +1 -0
  76. package/dist/message/error.d.ts +9 -0
  77. package/dist/message/error.d.ts.map +1 -0
  78. package/dist/{application/windowing/protocol.d.ts → message/init.d.ts} +1 -1
  79. package/dist/message/init.d.ts.map +1 -0
  80. package/dist/{domain/message/registries.d.ts → message/intent.d.ts} +45 -37
  81. package/dist/message/intent.d.ts.map +1 -0
  82. package/dist/message/message.d.ts +10 -0
  83. package/dist/message/message.d.ts.map +1 -0
  84. package/dist/shell/access.d.ts +10 -0
  85. package/dist/shell/access.d.ts.map +1 -0
  86. package/dist/shell/access.js +19 -0
  87. package/dist/shell/build.d.ts +28 -0
  88. package/dist/shell/build.d.ts.map +1 -0
  89. package/dist/shell/build.js +58 -0
  90. package/dist/shell/config.d.ts +29 -0
  91. package/dist/shell/config.d.ts.map +1 -0
  92. package/dist/shell/create.d.ts +4 -0
  93. package/dist/shell/create.d.ts.map +1 -0
  94. package/dist/shell/create.js +5 -0
  95. package/dist/{application/windowing/child-init.d.ts → shell/sandboxed/boot.d.ts} +5 -5
  96. package/dist/shell/sandboxed/boot.d.ts.map +1 -0
  97. package/dist/{application/windowing/child-init.js → shell/sandboxed/boot.js} +5 -6
  98. package/dist/shell/sandboxed/guarded.d.ts +13 -0
  99. package/dist/shell/sandboxed/guarded.d.ts.map +1 -0
  100. package/dist/shell/sandboxed/guarded.js +28 -0
  101. package/dist/shell/sandboxed/pending.d.ts +14 -0
  102. package/dist/shell/sandboxed/pending.d.ts.map +1 -0
  103. package/dist/shell/sandboxed/pending.js +30 -0
  104. package/dist/shell/sandboxed/sandboxed.d.ts +4 -0
  105. package/dist/shell/sandboxed/sandboxed.d.ts.map +1 -0
  106. package/dist/shell/sandboxed/sandboxed.js +100 -0
  107. package/dist/shell/shell.d.ts +36 -0
  108. package/dist/shell/shell.d.ts.map +1 -0
  109. package/dist/shell/standalone.d.ts +4 -0
  110. package/dist/shell/standalone.d.ts.map +1 -0
  111. package/dist/shell/standalone.js +85 -0
  112. package/dist/{domain/window/config.d.ts → window/capabilities.d.ts} +3 -3
  113. package/dist/window/capabilities.d.ts.map +1 -0
  114. package/dist/{ports/shell-adapter.d.ts → window/embed/adapter.d.ts} +3 -2
  115. package/dist/window/embed/adapter.d.ts.map +1 -0
  116. package/dist/{adapters/iframe.shell-adapter.d.ts → window/embed/iframe.d.ts} +2 -2
  117. package/dist/window/embed/iframe.d.ts.map +1 -0
  118. package/dist/{application/windowing/lifecycle-controller.d.ts → window/lifecycle/states.d.ts} +4 -3
  119. package/dist/window/lifecycle/states.d.ts.map +1 -0
  120. package/dist/{application/windowing/lifecycle-controller.js → window/lifecycle/states.js} +1 -1
  121. package/dist/{domain/window/lifecycle.d.ts → window/lifecycle/transition.d.ts} +3 -3
  122. package/dist/window/lifecycle/transition.d.ts.map +1 -0
  123. package/dist/{domain/window/lifecycle.js → window/lifecycle/transition.js} +1 -1
  124. package/dist/window/mount/close.d.ts +27 -0
  125. package/dist/window/mount/close.d.ts.map +1 -0
  126. package/dist/window/mount/close.js +74 -0
  127. package/dist/window/mount/credential.d.ts +41 -0
  128. package/dist/window/mount/credential.d.ts.map +1 -0
  129. package/dist/window/mount/credential.js +74 -0
  130. package/dist/window/mount/handshake.d.ts.map +1 -0
  131. package/dist/{application/windowing → window/mount}/handshake.js +1 -1
  132. package/dist/{application/windowing/mount-controller.d.ts → window/mount/mount.d.ts} +7 -7
  133. package/dist/window/mount/mount.d.ts.map +1 -0
  134. package/dist/window/mount/mount.js +143 -0
  135. package/dist/window/mount/params.d.ts +73 -0
  136. package/dist/window/mount/params.d.ts.map +1 -0
  137. package/dist/window/mount/params.js +1 -0
  138. package/dist/window/mount/plain.d.ts +20 -0
  139. package/dist/window/mount/plain.d.ts.map +1 -0
  140. package/dist/window/mount/plain.js +46 -0
  141. package/dist/{domain/window → window}/window.d.ts +1 -1
  142. package/dist/window/window.d.ts.map +1 -0
  143. package/dist/window/window.js +1 -0
  144. package/package.json +11 -6
  145. package/src/application/apps.ts +60 -0
  146. package/src/application/namespaces.ts +38 -0
  147. package/src/application/session/open.ts +73 -0
  148. package/src/application/session/session.ts +46 -0
  149. package/src/application/session/token.ts +87 -0
  150. package/src/application/spaces.ts +52 -0
  151. package/src/application/user.ts +54 -0
  152. package/src/application/view.ts +42 -0
  153. package/src/{application/channels/children-channel.ts → channel/children.ts} +3 -3
  154. package/src/{application/channels/types.ts → channel/listeners.ts} +2 -7
  155. package/src/{adapters/message-port.transport.ts → channel/message-port.ts} +2 -2
  156. package/src/{application/channels → channel}/observer.ts +2 -1
  157. package/src/{application/channels/parent-channel.ts → channel/parent.ts} +4 -4
  158. package/src/{ports → channel}/transport.ts +2 -1
  159. package/src/index.ts +80 -88
  160. package/src/{domain/intent → intent}/middleware/capabilities.ts +2 -3
  161. package/src/{domain/intent → intent}/middleware/logging.ts +1 -1
  162. package/src/{domain/intent → intent}/middleware/rate-limit.ts +1 -1
  163. package/src/{application/intent/pipeline-runner.ts → intent/pipeline.ts} +3 -2
  164. package/src/intent/policy.ts +46 -0
  165. package/src/{application/intent → intent}/request.ts +14 -34
  166. package/src/{application/intent → intent}/router.ts +12 -49
  167. package/src/kernel/client.ts +33 -0
  168. package/src/kernel/credential.ts +78 -0
  169. package/src/kernel/delegated.ts +49 -0
  170. package/src/{application/windowing/token-refresh.ts → kernel/delegation/refresh.ts} +2 -2
  171. package/src/{adapters/kernel-client.adapter.ts → kernel/session.ts} +26 -33
  172. package/src/message/ctrl.ts +25 -0
  173. package/src/{domain/message/errors.ts → message/error.ts} +8 -0
  174. package/src/message/intent.ts +98 -0
  175. package/src/message/message.ts +11 -0
  176. package/src/shell/access.ts +26 -0
  177. package/src/shell/build.ts +95 -0
  178. package/src/shell/config.ts +32 -0
  179. package/src/shell/create.ts +9 -0
  180. package/src/{application/windowing/child-init.ts → shell/sandboxed/boot.ts} +10 -11
  181. package/src/shell/sandboxed/guarded.ts +32 -0
  182. package/src/shell/sandboxed/pending.ts +52 -0
  183. package/src/shell/sandboxed/sandboxed.ts +131 -0
  184. package/src/shell/shell.ts +37 -0
  185. package/src/shell/standalone.ts +97 -0
  186. package/src/{domain/window/config.ts → window/capabilities.ts} +2 -2
  187. package/src/{ports/shell-adapter.ts → window/embed/adapter.ts} +2 -1
  188. package/src/{adapters/iframe.shell-adapter.ts → window/embed/iframe.ts} +2 -2
  189. package/src/{application/windowing/lifecycle-controller.ts → window/lifecycle/states.ts} +3 -2
  190. package/src/{domain/window/lifecycle.ts → window/lifecycle/transition.ts} +2 -2
  191. package/src/window/mount/close.ts +111 -0
  192. package/src/window/mount/credential.ts +101 -0
  193. package/src/{application/windowing → window/mount}/handshake.ts +3 -2
  194. package/src/window/mount/mount.ts +185 -0
  195. package/src/window/mount/params.ts +64 -0
  196. package/src/window/mount/plain.ts +68 -0
  197. package/src/{domain/window → window}/window.ts +1 -1
  198. package/dist/adapters/iframe.shell-adapter.d.ts.map +0 -1
  199. package/dist/adapters/kernel-client.adapter.d.ts.map +0 -1
  200. package/dist/adapters/message-port.transport.d.ts.map +0 -1
  201. package/dist/application/channels/children-channel.d.ts.map +0 -1
  202. package/dist/application/channels/observer.d.ts.map +0 -1
  203. package/dist/application/channels/parent-channel.d.ts.map +0 -1
  204. package/dist/application/channels/types.d.ts +0 -12
  205. package/dist/application/channels/types.d.ts.map +0 -1
  206. package/dist/application/intent/pipeline-runner.d.ts.map +0 -1
  207. package/dist/application/intent/request.d.ts.map +0 -1
  208. package/dist/application/intent/router.d.ts +0 -56
  209. package/dist/application/intent/router.d.ts.map +0 -1
  210. package/dist/application/kernel-facade.d.ts +0 -33
  211. package/dist/application/kernel-facade.d.ts.map +0 -1
  212. package/dist/application/kernel-facade.js +0 -77
  213. package/dist/application/shell.d.ts +0 -100
  214. package/dist/application/shell.d.ts.map +0 -1
  215. package/dist/application/shell.js +0 -256
  216. package/dist/application/windowing/child-init.d.ts.map +0 -1
  217. package/dist/application/windowing/handshake.d.ts.map +0 -1
  218. package/dist/application/windowing/lifecycle-controller.d.ts.map +0 -1
  219. package/dist/application/windowing/mount-controller.d.ts.map +0 -1
  220. package/dist/application/windowing/mount-controller.js +0 -277
  221. package/dist/application/windowing/protocol.d.ts.map +0 -1
  222. package/dist/application/windowing/token-refresh.d.ts.map +0 -1
  223. package/dist/domain/identity/delegation.d.ts.map +0 -1
  224. package/dist/domain/identity/path.d.ts +0 -11
  225. package/dist/domain/identity/path.d.ts.map +0 -1
  226. package/dist/domain/identity/path.js +0 -13
  227. package/dist/domain/identity/resolve-self.d.ts +0 -21
  228. package/dist/domain/identity/resolve-self.d.ts.map +0 -1
  229. package/dist/domain/identity/resolve-self.js +0 -36
  230. package/dist/domain/intent/middleware/capabilities.d.ts.map +0 -1
  231. package/dist/domain/intent/middleware/logging.d.ts.map +0 -1
  232. package/dist/domain/intent/middleware/rate-limit.d.ts.map +0 -1
  233. package/dist/domain/intent/pipeline.d.ts +0 -3
  234. package/dist/domain/intent/pipeline.d.ts.map +0 -1
  235. package/dist/domain/intent/router.d.ts +0 -6
  236. package/dist/domain/intent/router.d.ts.map +0 -1
  237. package/dist/domain/message/errors.d.ts +0 -2
  238. package/dist/domain/message/errors.d.ts.map +0 -1
  239. package/dist/domain/message/registries.d.ts.map +0 -1
  240. package/dist/domain/message/types.d.ts +0 -30
  241. package/dist/domain/message/types.d.ts.map +0 -1
  242. package/dist/domain/window/config.d.ts.map +0 -1
  243. package/dist/domain/window/lifecycle.d.ts.map +0 -1
  244. package/dist/domain/window/window.d.ts.map +0 -1
  245. package/dist/ports/kernel-client.d.ts +0 -33
  246. package/dist/ports/kernel-client.d.ts.map +0 -1
  247. package/dist/ports/shell-adapter.d.ts.map +0 -1
  248. package/dist/ports/transport.d.ts +0 -7
  249. package/dist/ports/transport.d.ts.map +0 -1
  250. package/src/application/kernel-facade.ts +0 -143
  251. package/src/application/shell.ts +0 -433
  252. package/src/application/windowing/mount-controller.ts +0 -335
  253. package/src/domain/identity/path.ts +0 -14
  254. package/src/domain/identity/resolve-self.ts +0 -38
  255. package/src/domain/intent/pipeline.ts +0 -3
  256. package/src/domain/intent/router.ts +0 -6
  257. package/src/domain/message/registries.ts +0 -76
  258. package/src/domain/message/types.ts +0 -32
  259. package/src/ports/kernel-client.ts +0 -32
  260. /package/dist/{application/channels/children-channel.js → channel/children.js} +0 -0
  261. /package/dist/{application/channels/types.js → channel/listeners.js} +0 -0
  262. /package/dist/{adapters/message-port.transport.js → channel/message-port.js} +0 -0
  263. /package/dist/{application/channels → channel}/observer.js +0 -0
  264. /package/dist/{ports → channel}/transport.js +0 -0
  265. /package/dist/{domain/intent → intent}/middleware/capabilities.js +0 -0
  266. /package/dist/{domain/intent → intent}/middleware/logging.d.ts +0 -0
  267. /package/dist/{domain/intent → intent}/middleware/logging.js +0 -0
  268. /package/dist/{domain/intent → intent}/middleware/rate-limit.js +0 -0
  269. /package/dist/{application/intent/pipeline-runner.js → intent/pipeline.js} +0 -0
  270. /package/dist/{domain/intent/pipeline.js → kernel/client.js} +0 -0
  271. /package/dist/{domain/identity/delegation.js → kernel/delegation/token.js} +0 -0
  272. /package/dist/{domain/intent/router.js → message/ctrl.js} +0 -0
  273. /package/dist/{domain/message/errors.js → message/error.js} +0 -0
  274. /package/dist/{application/windowing/protocol.js → message/init.js} +0 -0
  275. /package/dist/{domain/message/registries.js → message/intent.js} +0 -0
  276. /package/dist/{domain/message/types.js → message/message.js} +0 -0
  277. /package/dist/{domain/window → shell}/config.js +0 -0
  278. /package/dist/{domain/window/window.js → shell/shell.js} +0 -0
  279. /package/dist/{ports/kernel-client.js → window/capabilities.js} +0 -0
  280. /package/dist/{ports/shell-adapter.js → window/embed/adapter.js} +0 -0
  281. /package/dist/{adapters/iframe.shell-adapter.js → window/embed/iframe.js} +0 -0
  282. /package/dist/{application/windowing → window/mount}/handshake.d.ts +0 -0
  283. /package/src/{domain/identity/delegation.ts → kernel/delegation/token.ts} +0 -0
  284. /package/src/{application/windowing/protocol.ts → message/init.ts} +0 -0
@@ -4,10 +4,12 @@ import {
4
4
  type FnMap,
5
5
  type FunctionSchema,
6
6
  } from '@astrale-os/kernel-client'
7
- import { ClientSession, type ClientSessionConfig } from '@astrale-os/kernel-client/session'
8
-
9
- import { identityMethod } from '../domain/identity/path'
10
- import { createSelfResolver, WHOAMI_PATH } from '../domain/identity/resolve-self'
7
+ import {
8
+ type BoundClientSessionView,
9
+ ClientSession,
10
+ type ClientSessionConfig,
11
+ delegationMintVia,
12
+ } from '@astrale-os/kernel-client/session'
11
13
 
12
14
  // Untyped FnMap — the shell uses string paths, not typed method keys.
13
15
  type AnyFnMap = FnMap
@@ -16,8 +18,8 @@ type AnyFnMap = FnMap
16
18
  * Low-level kernel adapter — wraps a `ClientSession` (the post-v2 orchestration
17
19
  * layer above `KernelClient`).
18
20
  *
19
- * Methods take an explicit credential. The application-layer facade
20
- * manages which credential to use.
21
+ * Methods take an explicit credential. The credential facades manage which
22
+ * credential to use.
21
23
  *
22
24
  * Remote-domain redirects are followed transparently: when the kernel
23
25
  * responds with a `redirect` result (pointing at an external worker URL),
@@ -34,6 +36,14 @@ export type KernelClientAdapter = {
34
36
  credential: CredentialInput,
35
37
  opts?: CallOptions,
36
38
  ): Promise<unknown>
39
+ /**
40
+ * Credential-bound view over the underlying `ClientSession` — the flattened
41
+ * `BoundClientSessionView` surface (`.get/.node/.children/.query/.mutate/
42
+ * .createNode/…` graph sugar beside `.call/.stream/.as/…`). `getCredential`
43
+ * is a thunk invoked per dispatch, so the view tracks the active identity the
44
+ * same way the per-call `credential` injection did.
45
+ */
46
+ boundView(getCredential: () => CredentialInput): BoundClientSessionView<AnyFnMap>
37
47
  load(schemas: FunctionSchema[]): void
38
48
  disconnect(): void
39
49
  readonly url: string
@@ -52,14 +62,6 @@ export type CreateKernelClientAdapterOpts = {
52
62
  * with `Credential audience mismatch`.
53
63
  */
54
64
  getCredential?: () => CredentialInput
55
- /**
56
- * Explicit identity node/path to use as the delegation mint subject. When
57
- * omitted, the authenticated caller is resolved via `whoami` (so a cookie-fronted
58
- * SPA mints as the logged-in user). Set this only to pin a subject; if set it
59
- * MUST match the principal `getCredential()` authenticates as, or
60
- * `mintDelegationCredential`'s self-invariant rejects the mint.
61
- */
62
- mintIdentity?: string
63
65
  /**
64
66
  * Wire-layer `fetch` shim, forwarded to the underlying `KernelClient`s.
65
67
  * Lets a same-origin SPA inject `credentials:'include'` + refresh-on-401 so
@@ -85,27 +87,14 @@ export function createKernelClientAdapter(
85
87
  if (opts.fetch) config.fetch = opts.fetch
86
88
  if (opts.getCredential) {
87
89
  const getCred = opts.getCredential
88
- const explicitMintIdentity = opts.mintIdentity
89
- const resolveSelf = createSelfResolver()
90
+ // Cross-origin redirects (kernel → worker) mint a fresh worker-scoped
91
+ // delegation from the active identity — the blessed self-mint adapter over
92
+ // `auth.delegate`, with the `skipDelegation` recursion guard baked in.
90
93
  config.delegation = {
91
- mint: async (audience, delegation) => {
94
+ mint: delegationMintVia(() => {
92
95
  if (!session) throw new Error('ClientSession not initialised')
93
- const s = session
94
- const subject =
95
- explicitMintIdentity ??
96
- (await resolveSelf(() =>
97
- s.call(WHOAMI_PATH, {}, { credential: getCred(), skipDelegation: true }),
98
- ))
99
- const result = await s.call(
100
- identityMethod(subject, 'mintDelegationCredential'),
101
- { audience, delegation, ttl: 3600 },
102
- { credential: getCred(), skipDelegation: true },
103
- )
104
- if (typeof result !== 'string') {
105
- throw new Error(`mintDelegationCredential returned non-string: ${typeof result}`)
106
- }
107
- return { credential: result, ttl: 3600 }
108
- },
96
+ return session
97
+ }, getCred),
109
98
  }
110
99
  }
111
100
  session = new ClientSession<AnyFnMap>(config)
@@ -114,6 +103,10 @@ export function createKernelClientAdapter(
114
103
  return {
115
104
  url: opts.url,
116
105
 
106
+ boundView(getCredential) {
107
+ return ready.as(getCredential)
108
+ },
109
+
117
110
  call(method, params, credential, callOpts) {
118
111
  const sessionOpts: Parameters<typeof ready.call>[2] = { credential }
119
112
  if (callOpts?.timeout !== undefined) sessionOpts.timeout = callOpts.timeout
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Control message registry — extensible via declaration merging.
3
+ */
4
+ export interface CtrlRegistry {
5
+ handshake: {
6
+ data: {
7
+ delegationToken: string
8
+ tokenExpiresAt: number
9
+ windowId: string
10
+ kernelUrl: string
11
+ functionId: string
12
+ targetNodeId?: string
13
+ mintIdentity?: string
14
+ }
15
+ }
16
+ handshakeAck: { data: { windowId: string } }
17
+ tokenRefresh: { data: { delegationToken: string; tokenExpiresAt: number } }
18
+ }
19
+
20
+ export type CtrlMessage<K extends keyof CtrlRegistry = keyof CtrlRegistry> = {
21
+ readonly type: 'ctrl'
22
+ readonly version: 1
23
+ readonly action: K
24
+ readonly data: CtrlRegistry[K]['data']
25
+ }
@@ -7,3 +7,11 @@ export type ErrorCode =
7
7
  | 'DELIVERY_FAILED'
8
8
  | 'TOKEN_REFRESH_FAILED'
9
9
  | 'NO_VIEW_AVAILABLE'
10
+
11
+ export type ErrorMessage = {
12
+ readonly type: 'error'
13
+ readonly version: 1
14
+ readonly code: ErrorCode
15
+ readonly message: string
16
+ readonly context?: unknown
17
+ }
@@ -0,0 +1,98 @@
1
+ export interface ViewRef {
2
+ readonly viewId: string
3
+ readonly functionId: string
4
+ readonly label?: string
5
+ }
6
+
7
+ /**
8
+ * Intent registry — extensible via declaration merging.
9
+ *
10
+ * An entry is EITHER `{ payload; reply }` (the reply-typed convention) OR a bare
11
+ * payload type (legacy). Read entries through {@link PayloadOf} / {@link ReplyOf}
12
+ * — never index `[K]['payload']` directly, so bare augmentations keep compiling.
13
+ *
14
+ * Consumers extend via:
15
+ * declare module '@astrale-os/shell' {
16
+ * interface IntentRegistry { ... }
17
+ * }
18
+ */
19
+ export interface IntentRegistry {
20
+ open: { payload: { nodeId: string; viewId?: string }; reply: { windowId: string } }
21
+ close: { payload: { windowId: string }; reply: void }
22
+ focus: { payload: { nodeId: string }; reply: void }
23
+ willClose: { payload: Record<string, never>; reply: void }
24
+ closeAck: { payload: Record<string, never>; reply: void }
25
+ closeRefuse: { payload: { reason: string }; reply: void }
26
+ pickView: { payload: { views: ViewRef[] }; reply: { viewId: string } }
27
+ receive: { payload: { data: unknown; sourceIntent: string }; reply: void }
28
+ /**
29
+ * Hot-swap — parent pushes a new target node to an already-mounted view
30
+ * without remounting. The child reads `targetNodeId` at handshake time
31
+ * for the initial value; `setTarget` replaces it for the lifetime of the
32
+ * window.
33
+ */
34
+ setTarget: { payload: { nodeId: string }; reply: void }
35
+ /**
36
+ * Child → parent: a view asks the user to grant `perms` (a permission
37
+ * bitmask) on `node` to `identity` (the beneficiary subject). Both `node`
38
+ * and `identity` are bare node ids (e.g. the view's `targetNodeId`, or
39
+ * `whoami().id` for "grant to myself") — the root shell addresses them as
40
+ * `@<id>`. The root shell (GUI) surfaces a modal, computes the delta vs.
41
+ * what the subject already holds, and — on approval — grants only the
42
+ * missing bits under the GUI user's own credential. `reason` is shown to
43
+ * the user. `reply.perms` is the bitmask actually granted (0 if
44
+ * declined / nothing was missing).
45
+ *
46
+ * Reaches a handler only from a view mounted as a DIRECT child of the root
47
+ * shell — the default intent policy does not bubble past one hop.
48
+ */
49
+ requestAccess: {
50
+ payload: { node: string; identity: string; perms: number; reason?: string }
51
+ reply: { granted: boolean; perms: number }
52
+ }
53
+ /**
54
+ * Generic correlated reply channel. The default intent transport is
55
+ * fire-and-forget; `requestIntent()` sends an intent carrying a
56
+ * `correlationId` and awaits an `intentReply` whose payload echoes it back
57
+ * with the handler's `result`. Routed to the resolver purely by
58
+ * `correlationId` — it intentionally matches no local handler.
59
+ */
60
+ intentReply: { payload: { correlationId: string; result: unknown }; reply: void }
61
+ }
62
+
63
+ /**
64
+ * Payload type for intent `K`. A `{ payload; reply }` entry projects its
65
+ * `payload`; a bare-payload (legacy) entry IS the payload. Distributes over `K`
66
+ * (the outer `extends` makes `K` naked) so `PayloadOf<keyof IntentRegistry>` is
67
+ * the union of per-key payloads, not the union of entries.
68
+ */
69
+ export type PayloadOf<K extends keyof IntentRegistry> = K extends keyof IntentRegistry
70
+ ? IntentRegistry[K] extends { payload: infer P }
71
+ ? P
72
+ : IntentRegistry[K]
73
+ : never
74
+
75
+ /**
76
+ * Reply type for intent `K` — the value a handler sends back through
77
+ * `ctx.reply` and a `requestIntent`/`useSendIntent` caller awaits. A
78
+ * `{ reply }` entry projects its `reply`; every other entry is `void`.
79
+ * Distributes over `K` (see {@link PayloadOf}).
80
+ */
81
+ export type ReplyOf<K extends keyof IntentRegistry> = K extends keyof IntentRegistry
82
+ ? IntentRegistry[K] extends { reply: infer R }
83
+ ? R
84
+ : void
85
+ : never
86
+
87
+ export type IntentEnvelope<K extends keyof IntentRegistry = keyof IntentRegistry> = {
88
+ readonly name: K
89
+ readonly payload: PayloadOf<K>
90
+ readonly sender: { readonly windowId: string }
91
+ readonly correlationId?: string
92
+ }
93
+
94
+ export type IntentMessage<K extends keyof IntentRegistry = keyof IntentRegistry> = {
95
+ readonly type: 'intent'
96
+ readonly version: 1
97
+ readonly envelope: IntentEnvelope<K>
98
+ }
@@ -0,0 +1,11 @@
1
+ import type { CtrlMessage } from './ctrl'
2
+ import type { ErrorMessage } from './error'
3
+ import type { IntentMessage } from './intent'
4
+
5
+ export type ShellMessage = IntentMessage | CtrlMessage | ErrorMessage
6
+
7
+ export type MessageHandler<M extends ShellMessage = ShellMessage> = (message: M) => void
8
+
9
+ export type MessageType = ShellMessage['type']
10
+
11
+ export type MessageOf<T extends MessageType> = Extract<ShellMessage, { type: T }>
@@ -0,0 +1,26 @@
1
+ import { NO_PERMS } from '@astrale-os/kernel-core/permissions'
2
+
3
+ import type { PayloadOf, ReplyOf } from '../message/intent'
4
+ import type { Shell } from './shell'
5
+
6
+ import { requestIntent } from '../intent/request'
7
+
8
+ /**
9
+ * View SDK convenience: ask the user (via the root shell) to grant `perms` on
10
+ * `node` to `identity`. Resolves with the bitmask actually granted (0 if the
11
+ * user declined or there was no parent to ask). The view typically passes its
12
+ * own identity (from `whoami`) as `identity`.
13
+ */
14
+ export function requestAccess(
15
+ shell: Shell,
16
+ opts: PayloadOf<'requestAccess'>,
17
+ ): Promise<ReplyOf<'requestAccess'>> {
18
+ if (!shell.parent) return Promise.resolve({ granted: false, perms: NO_PERMS })
19
+ // A sandboxed shell's `parent` is a proxy that throws until `init()` resolves;
20
+ // honour the documented "no parent to ask" fallback instead of surfacing that
21
+ // opaque rejection to the caller.
22
+ return requestIntent(shell.parent, 'requestAccess', opts).catch(() => ({
23
+ granted: false,
24
+ perms: NO_PERMS,
25
+ }))
26
+ }
@@ -0,0 +1,95 @@
1
+ import type { OsNamespaces } from '../application/namespaces'
2
+ import type { ChildrenChannelController } from '../channel/children'
3
+ import type { MessageObserver } from '../channel/observer'
4
+ import type { ParentChannel } from '../channel/parent'
5
+ import type { IntentPipeline } from '../intent/pipeline'
6
+ import type { LocalIntentHandler } from '../intent/router'
7
+ import type { KernelClient } from '../kernel/client'
8
+ import type { IntentRegistry } from '../message/intent'
9
+ import type { LifecycleController } from '../window/lifecycle/states'
10
+ import type { MountController } from '../window/mount/mount'
11
+ import type { Window } from '../window/window'
12
+ import type { Shell } from './shell'
13
+
14
+ import { createOsNamespaces } from '../application/namespaces'
15
+
16
+ export type FacadeDeps = {
17
+ parent: ParentChannel | null
18
+ childrenChannel: ChildrenChannelController
19
+ kernel: KernelClient
20
+ pipeline: IntentPipeline
21
+ observer: MessageObserver
22
+ lifecycle: LifecycleController
23
+ windows: Map<string, Window>
24
+ getMountController: () => MountController
25
+ onIntent: <K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>) => () => void
26
+ targetNodeId?: () => string | undefined
27
+ init?: () => Promise<void>
28
+ onDispose: () => Promise<void>
29
+ }
30
+
31
+ /** Assemble the Shell object shared by both modes from their wired parts. */
32
+ export function buildShellFacade(deps: FacadeDeps): Shell {
33
+ let disposed = false
34
+
35
+ // The OS namespaces are lazy over the kernel proxy: constructing them touches
36
+ // no kernel, so the sandboxed shell's "init() first" guard fires only when a
37
+ // namespace method actually dispatches.
38
+ let os: OsNamespaces | null = null
39
+ const osNamespaces = (): OsNamespaces => (os ??= createOsNamespaces(deps.kernel))
40
+
41
+ const shell: Shell = {
42
+ get parent() {
43
+ return deps.parent
44
+ },
45
+ children: deps.childrenChannel,
46
+ kernel: deps.kernel,
47
+ windows: deps.windows,
48
+ get targetNodeId() {
49
+ return deps.targetNodeId?.()
50
+ },
51
+ get user() {
52
+ return osNamespaces().user
53
+ },
54
+ get spaces() {
55
+ return osNamespaces().spaces
56
+ },
57
+ get apps() {
58
+ return osNamespaces().apps
59
+ },
60
+
61
+ use(middleware) {
62
+ deps.pipeline.use(middleware)
63
+ return shell
64
+ },
65
+
66
+ onMessage(handler) {
67
+ return deps.observer.subscribe(handler)
68
+ },
69
+
70
+ onIntent(name, handler) {
71
+ return deps.onIntent(name, handler)
72
+ },
73
+
74
+ mount(params) {
75
+ return deps.getMountController().mount(params)
76
+ },
77
+
78
+ close(windowId, opts) {
79
+ return deps.getMountController().close(windowId, opts)
80
+ },
81
+
82
+ async init() {
83
+ if (deps.init) await deps.init()
84
+ return shell
85
+ },
86
+
87
+ async dispose() {
88
+ if (disposed) return
89
+ disposed = true
90
+ await deps.onDispose()
91
+ },
92
+ }
93
+
94
+ return shell
95
+ }
@@ -0,0 +1,32 @@
1
+ import type { FunctionSchema } from '@astrale-os/kernel-client'
2
+
3
+ import type { ShellAdapter } from '../window/embed/adapter'
4
+
5
+ export type StandaloneConfig = {
6
+ readonly mode: 'standalone'
7
+ readonly kernelUrl: string
8
+ readonly credential?: string
9
+ readonly getCredential?: () => string
10
+ readonly mintIdentity?: string
11
+ readonly schema?: FunctionSchema[]
12
+ readonly adapter?: ShellAdapter
13
+ readonly windowId?: string
14
+ /**
15
+ * Optional wire-layer `fetch` shim for the kernel transport. A cookie-fronted
16
+ * SPA passes a wrapper adding `credentials:'include'` + refresh-on-401 and an
17
+ * empty `credential`, so a router unseals the cookie into a Bearer (the SPA
18
+ * holds no token). Omit it (the default) for token-based callers — including
19
+ * every sandboxed child view, which authenticates with a delegation token.
20
+ */
21
+ readonly fetch?: typeof fetch
22
+ }
23
+
24
+ export type SandboxedConfig = {
25
+ readonly mode: 'sandboxed'
26
+ readonly schema?: FunctionSchema[]
27
+ readonly parentWindow?: WindowProxy
28
+ readonly initTimeoutMs?: number
29
+ readonly adapter?: ShellAdapter
30
+ }
31
+
32
+ export type ShellConfig = StandaloneConfig | SandboxedConfig
@@ -0,0 +1,9 @@
1
+ import type { ShellConfig } from './config'
2
+ import type { Shell } from './shell'
3
+
4
+ import { createSandboxedShell } from './sandboxed/sandboxed'
5
+ import { createStandaloneShell } from './standalone'
6
+
7
+ export function createShell(config: ShellConfig): Shell {
8
+ return config.mode === 'standalone' ? createStandaloneShell(config) : createSandboxedShell(config)
9
+ }
@@ -1,18 +1,18 @@
1
1
  import type { FunctionSchema } from '@astrale-os/kernel-client'
2
2
 
3
- import type { DelegationToken } from '../../domain/identity/delegation'
4
- import type { CtrlMessage } from '../../domain/message/types'
5
- import type { MessageObserver } from '../channels/observer'
6
- import type { ParentChannelController } from '../channels/parent-channel'
7
-
8
- import { createKernelClientAdapter } from '../../adapters/kernel-client.adapter'
9
- import { createMessagePortTransport } from '../../adapters/message-port.transport'
10
- import { createParentChannel } from '../channels/parent-channel'
3
+ import type { MessageObserver } from '../../channel/observer'
4
+ import type { ParentChannelController } from '../../channel/parent'
5
+ import type { DelegationToken } from '../../kernel/delegation/token'
6
+ import type { CtrlMessage } from '../../message/ctrl'
7
+
8
+ import { createMessagePortTransport } from '../../channel/message-port'
9
+ import { createParentChannel } from '../../channel/parent'
11
10
  import {
12
11
  createDelegationKernelFacade,
13
12
  type DelegationKernelFacadeController,
14
- } from '../kernel-facade'
15
- import { INIT_REQUEST_TYPE, isInitResponse } from './protocol'
13
+ } from '../../kernel/delegated'
14
+ import { createKernelClientAdapter } from '../../kernel/session'
15
+ import { INIT_REQUEST_TYPE, isInitResponse } from '../../message/init'
16
16
 
17
17
  export type ChildInitParams = {
18
18
  readonly parent: WindowProxy
@@ -78,7 +78,6 @@ export function performChildInit(params: ChildInitParams): Promise<ChildInitResu
78
78
  url: data.kernelUrl,
79
79
  schema: params.schema,
80
80
  getCredential: () => currentToken.credential,
81
- mintIdentity: data.mintIdentity,
82
81
  })
83
82
  const kernel = createDelegationKernelFacade({
84
83
  adapter,
@@ -0,0 +1,32 @@
1
+ import type { ParentChannel, ParentChannelController } from '../../channel/parent'
2
+ import type { KernelClient } from '../../kernel/client'
3
+
4
+ /** Throw the canonical "call init() first" error while `get()` has no state yet. */
5
+ export function requireInit<T>(get: () => T | null): T {
6
+ const state = get()
7
+ if (!state) throw new Error('Sandboxed shell not initialized — call shell.init() first')
8
+ return state
9
+ }
10
+
11
+ /**
12
+ * Forward the whole unified kernel surface (graph sugar + `.call/.stream/…` +
13
+ * the shell extras) to the post-init facade lazily, so the "init() first"
14
+ * guard applies uniformly to every member without re-declaring each one.
15
+ */
16
+ export function guardedKernel(require: () => KernelClient): KernelClient {
17
+ return new Proxy({} as KernelClient, {
18
+ get(_target, prop) {
19
+ const kernel = require() as unknown as Record<PropertyKey, unknown>
20
+ const value = kernel[prop]
21
+ return typeof value === 'function' ? value.bind(kernel) : value
22
+ },
23
+ })
24
+ }
25
+
26
+ /** Same guard for the parent channel — throws on use until `init()` resolves. */
27
+ export function guardedParent(require: () => ParentChannelController): ParentChannel {
28
+ return {
29
+ send: (message) => require().send(message),
30
+ on: (type, handler) => require().on(type, handler),
31
+ }
32
+ }
@@ -0,0 +1,52 @@
1
+ import type { IntentRouterController, LocalIntentHandler } from '../../intent/router'
2
+ import type { IntentRegistry } from '../../message/intent'
3
+
4
+ /**
5
+ * Buffer intent subscriptions made before `init()` resolves — the router is
6
+ * only built once the handshake completes, but consumers want to call
7
+ * `onIntent` at construction time.
8
+ */
9
+ export interface PendingIntentSubs {
10
+ add<K extends keyof IntentRegistry>(name: K, handler: LocalIntentHandler<K>): () => void
11
+ /** Register the buffered subscriptions on the real router, then drain. */
12
+ replay(router: IntentRouterController): void
13
+ }
14
+
15
+ type Entry = {
16
+ name: string
17
+ handler: LocalIntentHandler
18
+ unsubscribed: boolean
19
+ off?: () => void
20
+ }
21
+
22
+ export function createPendingIntentSubs(): PendingIntentSubs {
23
+ const entries: Entry[] = []
24
+
25
+ return {
26
+ add(name, handler) {
27
+ const entry: Entry = {
28
+ name: name as string,
29
+ handler: handler as LocalIntentHandler,
30
+ unsubscribed: false,
31
+ }
32
+ entries.push(entry)
33
+ return () => {
34
+ entry.unsubscribed = true
35
+ // After init, `off` is the router's real disposer (set during replay);
36
+ // before init, flipping `unsubscribed` is enough (replay skips it).
37
+ entry.off?.()
38
+ }
39
+ },
40
+
41
+ replay(router) {
42
+ // Capture each real disposer so a post-init unsubscribe (via the closure
43
+ // returned at add time) actually removes the handler from the router
44
+ // instead of no-op'ing.
45
+ for (const entry of entries) {
46
+ if (entry.unsubscribed) continue
47
+ entry.off = router.onLocal(entry.name as keyof IntentRegistry, entry.handler)
48
+ }
49
+ entries.length = 0
50
+ },
51
+ }
52
+ }
@@ -0,0 +1,131 @@
1
+ import type { ParentChannelController } from '../../channel/parent'
2
+ import type { IntentRouterController, LocalIntentHandler } from '../../intent/router'
3
+ import type { KernelFacadeController } from '../../kernel/credential'
4
+ import type { IntentRegistry } from '../../message/intent'
5
+ import type { MountController } from '../../window/mount/mount'
6
+ import type { Window } from '../../window/window'
7
+ import type { SandboxedConfig } from '../config'
8
+ import type { Shell } from '../shell'
9
+
10
+ import { createChildrenChannel } from '../../channel/children'
11
+ import { createMessageObserver } from '../../channel/observer'
12
+ import { createIntentPipeline } from '../../intent/pipeline'
13
+ import { createIntentRouter } from '../../intent/router'
14
+ import { createLifecycleController } from '../../window/lifecycle/states'
15
+ import { createMountController } from '../../window/mount/mount'
16
+ import { buildShellFacade } from '../build'
17
+ import { performChildInit } from './boot'
18
+ import { guardedKernel, guardedParent, requireInit } from './guarded'
19
+ import { createPendingIntentSubs } from './pending'
20
+
21
+ /** Trusted sender id stamped on intents arriving from the parent channel. */
22
+ const PARENT_SENDER_ID = '<parent>'
23
+
24
+ type SandboxedState = {
25
+ windowId: string
26
+ kernelUrl: string
27
+ targetNodeId?: string
28
+ mintIdentity?: string
29
+ kernel: KernelFacadeController
30
+ parent: ParentChannelController
31
+ }
32
+
33
+ export function createSandboxedShell(config: SandboxedConfig): Shell {
34
+ const observer = createMessageObserver()
35
+ const lifecycle = createLifecycleController()
36
+ const windows = new Map<string, Window>()
37
+ const childrenChannel = createChildrenChannel({
38
+ observer,
39
+ onDeliveryFailed: (toWindowId) => lifecycle.markCrashed(toWindowId),
40
+ })
41
+ const pipeline = createIntentPipeline()
42
+
43
+ let state: SandboxedState | null = null
44
+ let router: IntentRouterController | null = null
45
+ let mountController: MountController | null = null
46
+
47
+ const pending = createPendingIntentSubs()
48
+
49
+ function subscribeIntent<K extends keyof IntentRegistry>(
50
+ name: K,
51
+ handler: LocalIntentHandler<K>,
52
+ ): () => void {
53
+ if (router) return router.onLocal(name, handler)
54
+ return pending.add(name, handler)
55
+ }
56
+
57
+ const requireState = () => requireInit(() => state)
58
+
59
+ const kernelProxy = guardedKernel(() => requireState().kernel)
60
+ const parentProxy = guardedParent(() => requireState().parent)
61
+
62
+ function getMountController(): MountController {
63
+ if (mountController) return mountController
64
+ if (!config.adapter) {
65
+ throw new Error('Shell.mount() requires a ShellAdapter in the config')
66
+ }
67
+ const current = requireState()
68
+ mountController = createMountController({
69
+ adapter: config.adapter,
70
+ kernel: kernelProxy,
71
+ kernelUrl: current.kernelUrl,
72
+ mintIdentity: current.mintIdentity,
73
+ childrenChannel,
74
+ windows,
75
+ lifecycle,
76
+ })
77
+ return mountController
78
+ }
79
+
80
+ return buildShellFacade({
81
+ parent: parentProxy,
82
+ childrenChannel,
83
+ kernel: kernelProxy,
84
+ pipeline,
85
+ observer,
86
+ lifecycle,
87
+ windows,
88
+ getMountController,
89
+ targetNodeId: () => state?.targetNodeId,
90
+ onIntent: subscribeIntent,
91
+ init: async () => {
92
+ const result = await performChildInit({
93
+ parent: config.parentWindow ?? window.parent,
94
+ timeoutMs: config.initTimeoutMs,
95
+ schema: config.schema,
96
+ })
97
+
98
+ state = {
99
+ windowId: result.windowId,
100
+ kernelUrl: result.kernelUrl,
101
+ targetNodeId: result.targetNodeId,
102
+ mintIdentity: result.mintIdentity,
103
+ kernel: result.kernel,
104
+ parent: result.parentChannel(observer),
105
+ }
106
+
107
+ router = createIntentRouter({
108
+ selfWindowId: state.windowId,
109
+ pipeline,
110
+ parent: state.parent,
111
+ children: childrenChannel,
112
+ windows,
113
+ })
114
+
115
+ childrenChannel.on('intent', (windowId, message) => {
116
+ void router!.handleInbound('child', message, windowId)
117
+ })
118
+ state.parent.on('intent', (message) => {
119
+ void router!.handleInbound('parent', message, PARENT_SENDER_ID)
120
+ })
121
+
122
+ pending.replay(router)
123
+ },
124
+ onDispose: async () => {
125
+ if (mountController) await mountController.dispose()
126
+ childrenChannel.dispose()
127
+ state?.parent.dispose()
128
+ state?.kernel.dispose()
129
+ },
130
+ })
131
+ }