@astrale-os/shell 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/adapters/iframe.shell-adapter.d.ts +10 -0
  2. package/dist/adapters/iframe.shell-adapter.d.ts.map +1 -0
  3. package/dist/adapters/iframe.shell-adapter.js +55 -0
  4. package/dist/adapters/kernel-client.adapter.d.ts +53 -0
  5. package/dist/adapters/kernel-client.adapter.d.ts.map +1 -0
  6. package/dist/adapters/kernel-client.adapter.js +69 -0
  7. package/dist/adapters/message-port.transport.d.ts +7 -0
  8. package/dist/adapters/message-port.transport.d.ts.map +1 -0
  9. package/dist/adapters/message-port.transport.js +42 -0
  10. package/dist/application/channels/children-channel.d.ts +21 -0
  11. package/dist/application/channels/children-channel.d.ts.map +1 -0
  12. package/dist/application/channels/children-channel.js +82 -0
  13. package/dist/application/channels/observer.d.ts +9 -0
  14. package/dist/application/channels/observer.d.ts.map +1 -0
  15. package/dist/application/channels/observer.js +23 -0
  16. package/dist/application/channels/parent-channel.d.ts +17 -0
  17. package/dist/application/channels/parent-channel.d.ts.map +1 -0
  18. package/dist/application/channels/parent-channel.js +21 -0
  19. package/dist/application/channels/types.d.ts +12 -0
  20. package/dist/application/channels/types.d.ts.map +1 -0
  21. package/dist/application/channels/types.js +29 -0
  22. package/dist/application/intent/pipeline-runner.d.ts +12 -0
  23. package/dist/application/intent/pipeline-runner.d.ts.map +1 -0
  24. package/dist/application/intent/pipeline-runner.js +27 -0
  25. package/dist/application/intent/request.d.ts +34 -0
  26. package/dist/application/intent/request.d.ts.map +1 -0
  27. package/dist/application/intent/request.js +70 -0
  28. package/dist/application/intent/router.d.ts +56 -0
  29. package/dist/application/intent/router.d.ts.map +1 -0
  30. package/dist/application/intent/router.js +94 -0
  31. package/dist/application/kernel-facade.d.ts +33 -0
  32. package/dist/application/kernel-facade.d.ts.map +1 -0
  33. package/dist/application/kernel-facade.js +77 -0
  34. package/dist/application/shell.d.ts +100 -0
  35. package/dist/application/shell.d.ts.map +1 -0
  36. package/dist/application/shell.js +256 -0
  37. package/dist/application/windowing/child-init.d.ts +23 -0
  38. package/dist/application/windowing/child-init.d.ts.map +1 -0
  39. package/dist/application/windowing/child-init.js +101 -0
  40. package/dist/application/windowing/handshake.d.ts +30 -0
  41. package/dist/application/windowing/handshake.d.ts.map +1 -0
  42. package/dist/application/windowing/handshake.js +63 -0
  43. package/dist/application/windowing/lifecycle-controller.d.ts +10 -0
  44. package/dist/application/windowing/lifecycle-controller.d.ts.map +1 -0
  45. package/dist/application/windowing/lifecycle-controller.js +30 -0
  46. package/dist/application/windowing/mount-controller.d.ts +25 -0
  47. package/dist/application/windowing/mount-controller.d.ts.map +1 -0
  48. package/dist/application/windowing/mount-controller.js +277 -0
  49. package/dist/application/windowing/protocol.d.ts +21 -0
  50. package/dist/application/windowing/protocol.d.ts.map +1 -0
  51. package/dist/application/windowing/protocol.js +19 -0
  52. package/dist/application/windowing/token-refresh.d.ts +19 -0
  53. package/dist/application/windowing/token-refresh.d.ts.map +1 -0
  54. package/dist/application/windowing/token-refresh.js +60 -0
  55. package/dist/domain/identity/delegation.d.ts +15 -0
  56. package/dist/domain/identity/delegation.d.ts.map +1 -0
  57. package/dist/domain/identity/delegation.js +12 -0
  58. package/dist/domain/identity/path.d.ts +11 -0
  59. package/dist/domain/identity/path.d.ts.map +1 -0
  60. package/dist/domain/identity/path.js +13 -0
  61. package/dist/domain/identity/resolve-self.d.ts +21 -0
  62. package/dist/domain/identity/resolve-self.d.ts.map +1 -0
  63. package/dist/domain/identity/resolve-self.js +36 -0
  64. package/dist/domain/intent/middleware/capabilities.d.ts +28 -0
  65. package/dist/domain/intent/middleware/capabilities.d.ts.map +1 -0
  66. package/dist/domain/intent/middleware/capabilities.js +18 -0
  67. package/dist/domain/intent/middleware/logging.d.ts +14 -0
  68. package/dist/domain/intent/middleware/logging.d.ts.map +1 -0
  69. package/dist/domain/intent/middleware/logging.js +21 -0
  70. package/dist/domain/intent/middleware/rate-limit.d.ts +20 -0
  71. package/dist/domain/intent/middleware/rate-limit.d.ts.map +1 -0
  72. package/dist/domain/intent/middleware/rate-limit.js +33 -0
  73. package/dist/domain/intent/pipeline.d.ts +3 -0
  74. package/dist/domain/intent/pipeline.d.ts.map +1 -0
  75. package/dist/domain/intent/pipeline.js +1 -0
  76. package/dist/domain/intent/router.d.ts +6 -0
  77. package/dist/domain/intent/router.d.ts.map +1 -0
  78. package/dist/domain/intent/router.js +1 -0
  79. package/dist/domain/message/errors.d.ts +2 -0
  80. package/dist/domain/message/errors.d.ts.map +1 -0
  81. package/dist/domain/message/errors.js +1 -0
  82. package/dist/domain/message/registries.d.ts +145 -0
  83. package/dist/domain/message/registries.d.ts.map +1 -0
  84. package/dist/domain/message/registries.js +1 -0
  85. package/dist/domain/message/types.d.ts +30 -0
  86. package/dist/domain/message/types.d.ts.map +1 -0
  87. package/dist/domain/message/types.js +1 -0
  88. package/dist/domain/window/config.d.ts +26 -0
  89. package/dist/domain/window/config.d.ts.map +1 -0
  90. package/dist/domain/window/config.js +1 -0
  91. package/dist/domain/window/lifecycle.d.ts +27 -0
  92. package/dist/domain/window/lifecycle.d.ts.map +1 -0
  93. package/dist/domain/window/lifecycle.js +36 -0
  94. package/dist/domain/window/window.d.ts +12 -0
  95. package/dist/domain/window/window.d.ts.map +1 -0
  96. package/dist/domain/window/window.js +1 -0
  97. package/dist/index.d.ts +42 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +20 -0
  100. package/dist/ports/kernel-client.d.ts +33 -0
  101. package/dist/ports/kernel-client.d.ts.map +1 -0
  102. package/dist/ports/kernel-client.js +1 -0
  103. package/dist/ports/shell-adapter.d.ts +13 -0
  104. package/dist/ports/shell-adapter.d.ts.map +1 -0
  105. package/dist/ports/shell-adapter.js +1 -0
  106. package/dist/ports/transport.d.ts +7 -0
  107. package/dist/ports/transport.d.ts.map +1 -0
  108. package/dist/ports/transport.js +1 -0
  109. package/package.json +41 -0
  110. package/src/adapters/iframe.shell-adapter.ts +68 -0
  111. package/src/adapters/kernel-client.adapter.ts +138 -0
  112. package/src/adapters/message-port.transport.ts +50 -0
  113. package/src/application/channels/children-channel.ts +112 -0
  114. package/src/application/channels/observer.ts +33 -0
  115. package/src/application/channels/parent-channel.ts +43 -0
  116. package/src/application/channels/types.ts +41 -0
  117. package/src/application/intent/pipeline-runner.ts +36 -0
  118. package/src/application/intent/request.ts +87 -0
  119. package/src/application/intent/router.ts +161 -0
  120. package/src/application/kernel-facade.ts +143 -0
  121. package/src/application/shell.ts +433 -0
  122. package/src/application/windowing/child-init.ts +137 -0
  123. package/src/application/windowing/handshake.ts +98 -0
  124. package/src/application/windowing/lifecycle-controller.ts +40 -0
  125. package/src/application/windowing/mount-controller.ts +335 -0
  126. package/src/application/windowing/protocol.ts +37 -0
  127. package/src/application/windowing/token-refresh.ts +78 -0
  128. package/src/domain/identity/delegation.ts +25 -0
  129. package/src/domain/identity/path.ts +14 -0
  130. package/src/domain/identity/resolve-self.ts +38 -0
  131. package/src/domain/intent/middleware/capabilities.ts +53 -0
  132. package/src/domain/intent/middleware/logging.ts +45 -0
  133. package/src/domain/intent/middleware/rate-limit.ts +54 -0
  134. package/src/domain/intent/pipeline.ts +3 -0
  135. package/src/domain/intent/router.ts +6 -0
  136. package/src/domain/message/errors.ts +9 -0
  137. package/src/domain/message/registries.ts +76 -0
  138. package/src/domain/message/types.ts +32 -0
  139. package/src/domain/window/config.ts +28 -0
  140. package/src/domain/window/lifecycle.ts +39 -0
  141. package/src/domain/window/window.ts +13 -0
  142. package/src/index.ts +103 -0
  143. package/src/ports/kernel-client.ts +32 -0
  144. package/src/ports/shell-adapter.ts +11 -0
  145. package/src/ports/transport.ts +7 -0
@@ -0,0 +1,10 @@
1
+ import type { ShellAdapter } from '../ports/shell-adapter';
2
+ export type IframeShellAdapterConfig = {
3
+ /**
4
+ * Optional class list applied to every iframe created by this adapter.
5
+ * Useful for consistent styling without leaking styling into the SDK.
6
+ */
7
+ readonly className?: string;
8
+ };
9
+ export declare function createIframeShellAdapter(config?: IframeShellAdapterConfig): ShellAdapter;
10
+ //# sourceMappingURL=iframe.shell-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iframe.shell-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/iframe.shell-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,wBAAwB,CAAA;AAExE,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAUD,wBAAgB,wBAAwB,CAAC,MAAM,GAAE,wBAA6B,GAAG,YAAY,CAsC5F"}
@@ -0,0 +1,55 @@
1
+ const DEFAULT_SANDBOX = {
2
+ allowScripts: true,
3
+ allowForms: false,
4
+ allowPopups: false,
5
+ allowSameOrigin: false,
6
+ allowModals: false,
7
+ };
8
+ export function createIframeShellAdapter(config = {}) {
9
+ return {
10
+ mount(windowConfig) {
11
+ if (windowConfig.isolation !== 'sandboxed') {
12
+ throw new Error(`iframe adapter only supports sandboxed windows, got '${windowConfig.isolation}'`);
13
+ }
14
+ const element = document.createElement('iframe');
15
+ element.dataset.astraleFunctionId = windowConfig.functionId;
16
+ if (config.className)
17
+ element.className = config.className;
18
+ // `sandbox: null` is an explicit opt-out — leave the iframe without a
19
+ // `sandbox` attribute (only cross-origin isolation applies). Used by
20
+ // the GUI for first-party trusted views (see trusted-view-origins).
21
+ if (windowConfig.sandbox !== null) {
22
+ const profile = windowConfig.sandbox ?? DEFAULT_SANDBOX;
23
+ element.setAttribute('sandbox', sandboxAttrFor(profile));
24
+ }
25
+ element.setAttribute('allow', 'clipboard-read; clipboard-write');
26
+ element.setAttribute('loading', 'eager');
27
+ element.setAttribute('referrerpolicy', 'no-referrer');
28
+ const handle = {
29
+ element,
30
+ port: null,
31
+ origin: '',
32
+ };
33
+ return {
34
+ handle,
35
+ dispose: () => {
36
+ element.remove();
37
+ },
38
+ };
39
+ },
40
+ };
41
+ }
42
+ function sandboxAttrFor(profile) {
43
+ const tokens = [];
44
+ if (profile.allowScripts)
45
+ tokens.push('allow-scripts');
46
+ if (profile.allowForms)
47
+ tokens.push('allow-forms');
48
+ if (profile.allowPopups)
49
+ tokens.push('allow-popups');
50
+ if (profile.allowSameOrigin)
51
+ tokens.push('allow-same-origin');
52
+ if (profile.allowModals)
53
+ tokens.push('allow-modals');
54
+ return tokens.join(' ');
55
+ }
@@ -0,0 +1,53 @@
1
+ import { type CallOptions, type CredentialInput, type FunctionSchema } from '@astrale-os/kernel-client';
2
+ /**
3
+ * Low-level kernel adapter — wraps a `ClientSession` (the post-v2 orchestration
4
+ * layer above `KernelClient`).
5
+ *
6
+ * Methods take an explicit credential. The application-layer facade
7
+ * manages which credential to use.
8
+ *
9
+ * Remote-domain redirects are followed transparently: when the kernel
10
+ * responds with a `redirect` result (pointing at an external worker URL),
11
+ * the ClientSession mints a worker-scoped delegation credential — for the
12
+ * audience the kernel carries on the redirect (`CallRedirection.iss`, the
13
+ * target's own identity) — and re-dials the target URL with it. So
14
+ * `shell.kernel.call(...)` "just works" for methods whose Function has a
15
+ * `binding.remoteUrl`.
16
+ */
17
+ export type KernelClientAdapter = {
18
+ call(method: string, params: unknown, credential: CredentialInput, opts?: CallOptions): Promise<unknown>;
19
+ load(schemas: FunctionSchema[]): void;
20
+ disconnect(): void;
21
+ readonly url: string;
22
+ };
23
+ export type CreateKernelClientAdapterOpts = {
24
+ url: string;
25
+ schema?: FunctionSchema[];
26
+ requestTimeout?: number;
27
+ /**
28
+ * Returns the current base credential. When provided, the adapter wires a
29
+ * `delegation: { mint }` on the ClientSession so cross-origin redirects
30
+ * (kernel → worker) are followed with a fresh worker-scoped delegation
31
+ * minted from the active identity. Without it,
32
+ * ClientSession falls back to the base credential and workers reject
33
+ * with `Credential audience mismatch`.
34
+ */
35
+ getCredential?: () => CredentialInput;
36
+ /**
37
+ * Explicit identity node/path to use as the delegation mint subject. When
38
+ * omitted, the authenticated caller is resolved via `whoami` (so a cookie-fronted
39
+ * SPA mints as the logged-in user). Set this only to pin a subject; if set it
40
+ * MUST match the principal `getCredential()` authenticates as, or
41
+ * `mintDelegationCredential`'s self-invariant rejects the mint.
42
+ */
43
+ mintIdentity?: string;
44
+ /**
45
+ * Wire-layer `fetch` shim, forwarded to the underlying `KernelClient`s.
46
+ * Lets a same-origin SPA inject `credentials:'include'` + refresh-on-401 so
47
+ * a router can unseal a cookie into a Bearer (the parent holds no token).
48
+ * Default: global fetch — sandboxed child views never set it.
49
+ */
50
+ fetch?: typeof fetch;
51
+ };
52
+ export declare function createKernelClientAdapter(opts: CreateKernelClientAdapterOpts): KernelClientAdapter;
53
+ //# sourceMappingURL=kernel-client.adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel-client.adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/kernel-client.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAA;AASlC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CACF,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,eAAe,EAC3B,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,CAAA;IACrC,UAAU,IAAI,IAAI,CAAA;IAClB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,eAAe,CAAA;IACrC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB,CAAA;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,6BAA6B,GAClC,mBAAmB,CAgErB"}
@@ -0,0 +1,69 @@
1
+ import {} from '@astrale-os/kernel-client';
2
+ import { ClientSession } from '@astrale-os/kernel-client/session';
3
+ import { identityMethod } from '../domain/identity/path';
4
+ import { createSelfResolver, WHOAMI_PATH } from '../domain/identity/resolve-self';
5
+ export function createKernelClientAdapter(opts) {
6
+ let session = null;
7
+ const config = {
8
+ default: opts.url,
9
+ // Identity is supplied per-call via `opts.credential`; no picker bound.
10
+ // No `delegationPolicy` → ClientSession's default `allowAllDelegations`
11
+ // mints for the audience the kernel carries on the redirect
12
+ // (`CallRedirection.iss`), which is exactly what the target worker verifies.
13
+ identity: undefined,
14
+ };
15
+ if (opts.schema)
16
+ config.schema = opts.schema;
17
+ if (opts.fetch)
18
+ config.fetch = opts.fetch;
19
+ if (opts.getCredential) {
20
+ const getCred = opts.getCredential;
21
+ const explicitMintIdentity = opts.mintIdentity;
22
+ const resolveSelf = createSelfResolver();
23
+ config.delegation = {
24
+ mint: async (audience, delegation) => {
25
+ if (!session)
26
+ throw new Error('ClientSession not initialised');
27
+ const s = session;
28
+ const subject = explicitMintIdentity ??
29
+ (await resolveSelf(() => s.call(WHOAMI_PATH, {}, { credential: getCred(), skipDelegation: true })));
30
+ const result = await s.call(identityMethod(subject, 'mintDelegationCredential'), { audience, delegation, ttl: 3600 }, { credential: getCred(), skipDelegation: true });
31
+ if (typeof result !== 'string') {
32
+ throw new Error(`mintDelegationCredential returned non-string: ${typeof result}`);
33
+ }
34
+ return { credential: result, ttl: 3600 };
35
+ },
36
+ };
37
+ }
38
+ session = new ClientSession(config);
39
+ const ready = session;
40
+ return {
41
+ url: opts.url,
42
+ call(method, params, credential, callOpts) {
43
+ const sessionOpts = { credential };
44
+ if (callOpts?.timeout !== undefined)
45
+ sessionOpts.timeout = callOpts.timeout;
46
+ if (callOpts?.signal !== undefined)
47
+ sessionOpts.signal = callOpts.signal;
48
+ if (callOpts?.class !== undefined)
49
+ sessionOpts.class = callOpts.class;
50
+ if (callOpts?.transport !== undefined)
51
+ sessionOpts.transport = callOpts.transport;
52
+ if (callOpts?.protocol !== undefined)
53
+ sessionOpts.protocol = callOpts.protocol;
54
+ if (callOpts?.correlationId !== undefined)
55
+ sessionOpts.correlationId = callOpts.correlationId;
56
+ if (callOpts?.self !== undefined)
57
+ sessionOpts.self = callOpts.self;
58
+ if (callOpts?.url !== undefined)
59
+ sessionOpts.url = callOpts.url;
60
+ return ready.call(method, params, sessionOpts);
61
+ },
62
+ load(schemas) {
63
+ ready.registry.load(schemas);
64
+ },
65
+ disconnect() {
66
+ ready.disconnect();
67
+ },
68
+ };
69
+ }
@@ -0,0 +1,7 @@
1
+ import type { Transport } from '../ports/transport';
2
+ export type MessagePortTransportConfig = {
3
+ port: MessagePort;
4
+ onClose?: () => void;
5
+ };
6
+ export declare function createMessagePortTransport(config: MessagePortTransportConfig): Transport;
7
+ //# sourceMappingURL=message-port.transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-port.transport.d.ts","sourceRoot":"","sources":["../../src/adapters/message-port.transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,SAAS,CAyCxF"}
@@ -0,0 +1,42 @@
1
+ export function createMessagePortTransport(config) {
2
+ const { port, onClose } = config;
3
+ let closed = false;
4
+ const handlers = new Set();
5
+ port.onmessage = (event) => {
6
+ if (closed)
7
+ return;
8
+ const message = event.data;
9
+ for (const handler of handlers)
10
+ handler(message);
11
+ };
12
+ port.onmessageerror = () => {
13
+ if (closed)
14
+ return;
15
+ close();
16
+ };
17
+ port.start();
18
+ function close() {
19
+ if (closed)
20
+ return;
21
+ closed = true;
22
+ handlers.clear();
23
+ try {
24
+ port.close();
25
+ }
26
+ catch {
27
+ // port may already be closed
28
+ }
29
+ onClose?.();
30
+ }
31
+ return {
32
+ send(message) {
33
+ if (closed)
34
+ return;
35
+ port.postMessage(message);
36
+ },
37
+ onMessage(handler) {
38
+ handlers.add(handler);
39
+ },
40
+ close,
41
+ };
42
+ }
@@ -0,0 +1,21 @@
1
+ import type { ShellMessage } from '../../domain/message/types';
2
+ import type { Transport } from '../../ports/transport';
3
+ import type { MessageObserver } from './observer';
4
+ import type { MessageOf, MessageType } from './types';
5
+ export interface ChildrenChannel {
6
+ send(windowId: string, message: ShellMessage): void;
7
+ on<T extends MessageType>(type: T, handler: (windowId: string, message: MessageOf<T>) => void): () => void;
8
+ has(windowId: string): boolean;
9
+ onClose(handler: (windowId: string) => void): () => void;
10
+ }
11
+ export interface ChildrenChannelController extends ChildrenChannel {
12
+ attach(windowId: string, transport: Transport): void;
13
+ detach(windowId: string): void;
14
+ dispose(): void;
15
+ }
16
+ export type ChildrenChannelConfig = {
17
+ observer: MessageObserver;
18
+ onDeliveryFailed?: (windowId: string, message: ShellMessage) => void;
19
+ };
20
+ export declare function createChildrenChannel(config: ChildrenChannelConfig): ChildrenChannelController;
21
+ //# sourceMappingURL=children-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"children-channel.d.ts","sourceRoot":"","sources":["../../../src/application/channels/children-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAErD,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IACnD,EAAE,CAAC,CAAC,SAAS,WAAW,EACtB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,GACzD,MAAM,IAAI,CAAA;IACb,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;CACzD;AAED,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAChE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;IACpD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,eAAe,CAAA;IACzB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;CACrE,CAAA;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,yBAAyB,CAmF9F"}
@@ -0,0 +1,82 @@
1
+ export function createChildrenChannel(config) {
2
+ const { observer, onDeliveryFailed } = config;
3
+ const entries = new Map();
4
+ const byType = new Map();
5
+ const closeListeners = new Set();
6
+ function notifyListeners(windowId, message) {
7
+ const bucket = byType.get(message.type);
8
+ if (!bucket || bucket.size === 0)
9
+ return;
10
+ for (const listener of bucket) {
11
+ try {
12
+ listener(windowId, message);
13
+ }
14
+ catch {
15
+ // one rogue listener must not break siblings
16
+ }
17
+ }
18
+ }
19
+ return {
20
+ attach(windowId, transport) {
21
+ const existing = entries.get(windowId);
22
+ if (existing)
23
+ throw new Error(`Child '${windowId}' already attached`);
24
+ entries.set(windowId, transport);
25
+ transport.onMessage((message) => {
26
+ observer.notify('inbound', message);
27
+ notifyListeners(windowId, message);
28
+ });
29
+ },
30
+ detach(windowId) {
31
+ const transport = entries.get(windowId);
32
+ if (!transport)
33
+ return;
34
+ entries.delete(windowId);
35
+ transport.close();
36
+ // `entries.delete` ran first, so `has(windowId)` is already false here —
37
+ // a listener replying to the gone child is a best-effort no-op.
38
+ for (const listener of closeListeners) {
39
+ try {
40
+ listener(windowId);
41
+ }
42
+ catch {
43
+ // one rogue listener must not break siblings
44
+ }
45
+ }
46
+ },
47
+ dispose() {
48
+ for (const transport of entries.values())
49
+ transport.close();
50
+ entries.clear();
51
+ },
52
+ send(windowId, message) {
53
+ const transport = entries.get(windowId);
54
+ if (!transport) {
55
+ onDeliveryFailed?.(windowId, message);
56
+ return;
57
+ }
58
+ observer.notify('outbound', message);
59
+ transport.send(message);
60
+ },
61
+ on(type, handler) {
62
+ let bucket = byType.get(type);
63
+ if (!bucket) {
64
+ bucket = new Set();
65
+ byType.set(type, bucket);
66
+ }
67
+ bucket.add(handler);
68
+ return () => {
69
+ bucket.delete(handler);
70
+ };
71
+ },
72
+ has(windowId) {
73
+ return entries.has(windowId);
74
+ },
75
+ onClose(handler) {
76
+ closeListeners.add(handler);
77
+ return () => {
78
+ closeListeners.delete(handler);
79
+ };
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,9 @@
1
+ import type { ShellMessage } from '../../domain/message/types';
2
+ export type MessageDirection = 'inbound' | 'outbound';
3
+ export type ObserverHandler = (message: ShellMessage, direction: MessageDirection) => void;
4
+ export interface MessageObserver {
5
+ notify(direction: MessageDirection, message: ShellMessage): void;
6
+ subscribe(handler: ObserverHandler): () => void;
7
+ }
8
+ export declare function createMessageObserver(): MessageObserver;
9
+ //# sourceMappingURL=observer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../../../src/application/channels/observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAA;AAE1F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAChE,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,IAAI,CAAA;CAChD;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAqBvD"}
@@ -0,0 +1,23 @@
1
+ export function createMessageObserver() {
2
+ const handlers = new Set();
3
+ return {
4
+ notify(direction, message) {
5
+ if (handlers.size === 0)
6
+ return;
7
+ for (const handler of handlers) {
8
+ try {
9
+ handler(message, direction);
10
+ }
11
+ catch {
12
+ // one rogue observer must not break siblings
13
+ }
14
+ }
15
+ },
16
+ subscribe(handler) {
17
+ handlers.add(handler);
18
+ return () => {
19
+ handlers.delete(handler);
20
+ };
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,17 @@
1
+ import type { ShellMessage } from '../../domain/message/types';
2
+ import type { Transport } from '../../ports/transport';
3
+ import type { MessageObserver } from './observer';
4
+ import type { MessageHandler, MessageOf, MessageType } from './types';
5
+ export interface ParentChannel {
6
+ send(message: ShellMessage): void;
7
+ on<T extends MessageType>(type: T, handler: MessageHandler<MessageOf<T>>): () => void;
8
+ }
9
+ export interface ParentChannelController extends ParentChannel {
10
+ dispose(): void;
11
+ }
12
+ export type ParentChannelConfig = {
13
+ transport: Transport;
14
+ observer: MessageObserver;
15
+ };
16
+ export declare function createParentChannel(config: ParentChannelConfig): ParentChannelController;
17
+ //# sourceMappingURL=parent-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parent-channel.d.ts","sourceRoot":"","sources":["../../../src/application/channels/parent-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAIrE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IACjC,EAAE,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;CACtF;AAED,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,SAAS,CAAA;IACpB,QAAQ,EAAE,eAAe,CAAA;CAC1B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,uBAAuB,CAqBxF"}
@@ -0,0 +1,21 @@
1
+ import { createMessageListeners } from './types';
2
+ export function createParentChannel(config) {
3
+ const { transport, observer } = config;
4
+ const listeners = createMessageListeners();
5
+ transport.onMessage((message) => {
6
+ observer.notify('inbound', message);
7
+ listeners.dispatch(message);
8
+ });
9
+ return {
10
+ send(message) {
11
+ observer.notify('outbound', message);
12
+ transport.send(message);
13
+ },
14
+ on(type, handler) {
15
+ return listeners.on(type, handler);
16
+ },
17
+ dispose() {
18
+ transport.close();
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,12 @@
1
+ import type { ShellMessage } from '../../domain/message/types';
2
+ export type MessageHandler<M extends ShellMessage = ShellMessage> = (message: M) => void;
3
+ export type MessageType = ShellMessage['type'];
4
+ export type MessageOf<T extends MessageType> = Extract<ShellMessage, {
5
+ type: T;
6
+ }>;
7
+ export interface MessageListeners {
8
+ on<T extends MessageType>(type: T, handler: MessageHandler<MessageOf<T>>): () => void;
9
+ dispatch(message: ShellMessage): void;
10
+ }
11
+ export declare function createMessageListeners(): MessageListeners;
12
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/application/channels/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAA;AAExF,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;AAE9C,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAA;IACrF,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CACtC;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CA2BzD"}
@@ -0,0 +1,29 @@
1
+ export function createMessageListeners() {
2
+ const byType = new Map();
3
+ return {
4
+ on(type, handler) {
5
+ let bucket = byType.get(type);
6
+ if (!bucket) {
7
+ bucket = new Set();
8
+ byType.set(type, bucket);
9
+ }
10
+ bucket.add(handler);
11
+ return () => {
12
+ bucket.delete(handler);
13
+ };
14
+ },
15
+ dispatch(message) {
16
+ const bucket = byType.get(message.type);
17
+ if (!bucket || bucket.size === 0)
18
+ return;
19
+ for (const handler of bucket) {
20
+ try {
21
+ handler(message);
22
+ }
23
+ catch {
24
+ // one rogue handler must not break siblings
25
+ }
26
+ }
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,12 @@
1
+ import type { IntentMiddleware } from '../../domain/intent/pipeline';
2
+ import type { IntentMessage } from '../../domain/message/types';
3
+ export interface IntentPipeline {
4
+ use(middleware: IntentMiddleware): void;
5
+ run(message: IntentMessage, terminate: () => Promise<void>): Promise<void>;
6
+ }
7
+ /**
8
+ * Composable middleware runner. Each middleware may transform, short-circuit,
9
+ * or wrap the call to `next`. `terminate` is the innermost handler (router).
10
+ */
11
+ export declare function createIntentPipeline(): IntentPipeline;
12
+ //# sourceMappingURL=pipeline-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-runner.d.ts","sourceRoot":"","sources":["../../../src/application/intent/pipeline-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvC,GAAG,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAuBrD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Composable middleware runner. Each middleware may transform, short-circuit,
3
+ * or wrap the call to `next`. `terminate` is the innermost handler (router).
4
+ */
5
+ export function createIntentPipeline() {
6
+ const middlewares = [];
7
+ return {
8
+ use(middleware) {
9
+ middlewares.push(middleware);
10
+ },
11
+ async run(message, terminate) {
12
+ let i = -1;
13
+ const dispatch = async (idx) => {
14
+ if (idx <= i)
15
+ throw new Error('next() called multiple times');
16
+ i = idx;
17
+ if (idx === middlewares.length) {
18
+ await terminate();
19
+ return;
20
+ }
21
+ const mw = middlewares[idx];
22
+ await mw(message, () => dispatch(idx + 1));
23
+ };
24
+ await dispatch(0);
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,34 @@
1
+ import type { IntentRegistry } from '../../domain/message/registries';
2
+ import type { IntentMessage } from '../../domain/message/types';
3
+ import type { ChildrenChannel } from '../channels/children-channel';
4
+ import type { ParentChannel } from '../channels/parent-channel';
5
+ import type { Shell } from '../shell';
6
+ /**
7
+ * Request/reply over the otherwise fire-and-forget intent transport.
8
+ *
9
+ * The child raises `name` with a generated `correlationId` and awaits the
10
+ * `intentReply` whose payload echoes that id. The reply also re-enters the
11
+ * child router as an inbound parent intent, but no handler is registered for
12
+ * `intentReply`, so it is harmlessly dropped there — only this resolver,
13
+ * matching by `correlationId`, consumes it.
14
+ *
15
+ * No timeout by design: callers (e.g. `requestAccess`) wait on a human, and
16
+ * the parent always replies on every path. A dead parent means the view is
17
+ * gone too, so a never-resolving promise cannot strand a live view.
18
+ */
19
+ export declare function requestIntent<K extends keyof IntentRegistry>(parent: ParentChannel, name: K, payload: IntentRegistry[K]['payload']): Promise<IntentRegistry[K]['response']>;
20
+ /**
21
+ * Parent-side counterpart to `requestIntent`: deliver `result` back to the
22
+ * originating child window, keyed by the request's `correlationId`. No-op for
23
+ * a request that carried no `correlationId` (a fire-and-forget intent), or if
24
+ * the child window has since detached (best-effort, drops silently).
25
+ */
26
+ export declare function replyToIntent<K extends keyof IntentRegistry>(children: ChildrenChannel, windowId: string, request: IntentMessage<K>, result: IntentRegistry[K]['response']): void;
27
+ /**
28
+ * View SDK convenience: ask the user (via the root shell) to grant `perms` on
29
+ * `node` to `identity`. Resolves with the bitmask actually granted (0 if the
30
+ * user declined or there was no parent to ask). The view typically passes its
31
+ * own identity (from `whoami`) as `identity`.
32
+ */
33
+ export declare function requestAccess(shell: Shell, opts: IntentRegistry['requestAccess']['payload']): Promise<IntentRegistry['requestAccess']['response']>;
34
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../src/application/intent/request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAErC;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,cAAc,EAC1D,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GACpC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAgBxC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,cAAc,EAC1D,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GACpC,IAAI,CAYN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,GAC/C,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC,CAStD"}
@@ -0,0 +1,70 @@
1
+ import { NO_PERMS } from '@astrale-os/kernel-core/permissions';
2
+ /**
3
+ * Request/reply over the otherwise fire-and-forget intent transport.
4
+ *
5
+ * The child raises `name` with a generated `correlationId` and awaits the
6
+ * `intentReply` whose payload echoes that id. The reply also re-enters the
7
+ * child router as an inbound parent intent, but no handler is registered for
8
+ * `intentReply`, so it is harmlessly dropped there — only this resolver,
9
+ * matching by `correlationId`, consumes it.
10
+ *
11
+ * No timeout by design: callers (e.g. `requestAccess`) wait on a human, and
12
+ * the parent always replies on every path. A dead parent means the view is
13
+ * gone too, so a never-resolving promise cannot strand a live view.
14
+ */
15
+ export function requestIntent(parent, name, payload) {
16
+ const correlationId = crypto.randomUUID();
17
+ return new Promise((resolve) => {
18
+ const off = parent.on('intent', (message) => {
19
+ if (message.envelope.name !== 'intentReply')
20
+ return;
21
+ const reply = message.envelope.payload;
22
+ if (reply.correlationId !== correlationId)
23
+ return;
24
+ off();
25
+ resolve(reply.result);
26
+ });
27
+ parent.send({
28
+ type: 'intent',
29
+ version: 1,
30
+ envelope: { name, payload, sender: { windowId: 'self' }, correlationId },
31
+ });
32
+ });
33
+ }
34
+ /**
35
+ * Parent-side counterpart to `requestIntent`: deliver `result` back to the
36
+ * originating child window, keyed by the request's `correlationId`. No-op for
37
+ * a request that carried no `correlationId` (a fire-and-forget intent), or if
38
+ * the child window has since detached (best-effort, drops silently).
39
+ */
40
+ export function replyToIntent(children, windowId, request, result) {
41
+ const correlationId = request.envelope.correlationId;
42
+ if (!correlationId)
43
+ return;
44
+ children.send(windowId, {
45
+ type: 'intent',
46
+ version: 1,
47
+ envelope: {
48
+ name: 'intentReply',
49
+ payload: { correlationId, result },
50
+ sender: { windowId: 'root' },
51
+ },
52
+ });
53
+ }
54
+ /**
55
+ * View SDK convenience: ask the user (via the root shell) to grant `perms` on
56
+ * `node` to `identity`. Resolves with the bitmask actually granted (0 if the
57
+ * user declined or there was no parent to ask). The view typically passes its
58
+ * own identity (from `whoami`) as `identity`.
59
+ */
60
+ export function requestAccess(shell, opts) {
61
+ if (!shell.parent)
62
+ return Promise.resolve({ granted: false, perms: NO_PERMS });
63
+ // A sandboxed shell's `parent` is a proxy that throws until `init()` resolves;
64
+ // honour the documented "no parent to ask" fallback instead of surfacing that
65
+ // opaque rejection to the caller.
66
+ return requestIntent(shell.parent, 'requestAccess', opts).catch(() => ({
67
+ granted: false,
68
+ perms: NO_PERMS,
69
+ }));
70
+ }