@agentshouse/app-bridge 0.1.0-alpha.8 → 0.1.0-c0079debc581d

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -70
  3. package/dist/calls.d.ts +955 -0
  4. package/dist/calls.js +1 -0
  5. package/dist/index.d.ts +162 -0
  6. package/dist/index.js +1 -0
  7. package/dist/mdmodel/blocks.d.ts +10 -0
  8. package/dist/mdmodel/condition.d.ts +161 -0
  9. package/dist/mdmodel/definition.d.ts +80 -0
  10. package/dist/mdmodel/divide.d.ts +2 -0
  11. package/dist/mdmodel/document.d.ts +15 -0
  12. package/dist/mdmodel/fields.d.ts +5 -0
  13. package/dist/mdmodel/index.d.ts +19 -0
  14. package/dist/mdmodel/links.d.ts +28 -0
  15. package/dist/mdmodel/paths.d.ts +25 -0
  16. package/dist/mdmodel/profile.d.ts +5 -0
  17. package/dist/mdmodel/project.d.ts +21 -0
  18. package/dist/mdmodel/references.d.ts +3 -0
  19. package/dist/mdmodel/types.d.ts +166 -0
  20. package/dist/mdmodel/validate.d.ts +10 -0
  21. package/dist/mdmodel/version.d.ts +1 -0
  22. package/dist/mdmodel/yaml.d.ts +55 -0
  23. package/dist/mdruntime/adapter.d.ts +507 -0
  24. package/dist/mdruntime/binary.d.ts +22 -0
  25. package/dist/mdruntime/change-set.d.ts +37 -0
  26. package/dist/mdruntime/change.d.ts +118 -0
  27. package/dist/mdruntime/client.d.ts +89 -0
  28. package/dist/mdruntime/collection.d.ts +39 -0
  29. package/dist/mdruntime/connection.d.ts +150 -0
  30. package/dist/mdruntime/conversation.d.ts +116 -0
  31. package/dist/mdruntime/durability.d.ts +14 -0
  32. package/dist/mdruntime/failure.d.ts +12 -0
  33. package/dist/mdruntime/file.d.ts +11 -0
  34. package/dist/mdruntime/model.d.ts +57 -0
  35. package/dist/mdruntime/observation.d.ts +31 -0
  36. package/dist/mdruntime/open.d.ts +1 -0
  37. package/dist/mdruntime/outcome.d.ts +71 -0
  38. package/dist/mdruntime/overlay.d.ts +27 -0
  39. package/dist/mdruntime/query.d.ts +274 -0
  40. package/dist/mdruntime/reach.d.ts +48 -0
  41. package/dist/mdruntime/readiness.d.ts +52 -0
  42. package/dist/mdruntime/recovery.d.ts +24 -0
  43. package/dist/mdruntime/replica.d.ts +50 -0
  44. package/dist/mdruntime/room-draft.d.ts +157 -0
  45. package/dist/mdruntime/rows.d.ts +38 -0
  46. package/dist/mdruntime/save.d.ts +101 -0
  47. package/dist/mdruntime/search.d.ts +104 -0
  48. package/dist/mdruntime/seed.d.ts +15 -0
  49. package/dist/mdruntime/shared.d.ts +18 -0
  50. package/dist/mdruntime/status.d.ts +26 -0
  51. package/dist/mdruntime/store.d.ts +171 -0
  52. package/dist/mdruntime/tables.d.ts +180 -0
  53. package/dist/mdruntime/transfer.d.ts +9 -0
  54. package/dist/mdruntime/turn.d.ts +8 -0
  55. package/dist/mdruntime/wire.d.ts +230 -0
  56. package/dist/test-host.d.ts +33 -0
  57. package/dist/test-host.js +200 -0
  58. package/docs/families.md +149 -0
  59. package/package.json +11 -14
  60. package/client/src/bridge.d.ts +0 -32
  61. package/client/src/bridge.js +0 -206
  62. package/client/src/index.d.ts +0 -3
  63. package/client/src/index.js +0 -2
  64. package/client/src/test-host.d.ts +0 -30
  65. package/client/src/test-host.js +0 -156
  66. package/client/src/wire.d.ts +0 -69
  67. package/client/src/wire.js +0 -1
  68. package/src/apps/remainder.d.ts +0 -2
  69. package/src/apps/remainder.js +0 -16
  70. package/src/apps/tab.d.ts +0 -6
  71. package/src/apps/tab.js +0 -27
@@ -0,0 +1,230 @@
1
+ import type { ConnectionRefusal } from './connection.js';
2
+ import type { Described } from './failure.js';
3
+ export type RuntimePort = {
4
+ postMessage(message: unknown): void;
5
+ addEventListener(type: 'message' | 'close', listener: (event: {
6
+ data?: unknown;
7
+ }) => void): void;
8
+ start(): void;
9
+ };
10
+ export type WireReach = 'within' | 'app' | 'runtime' | 'actions' | 'whole';
11
+ export type WireEffect = 'read' | 'write' | 'action';
12
+ export type WireCall = {
13
+ readonly reach: WireReach;
14
+ readonly effect: WireEffect;
15
+ };
16
+ declare const MEMBERS: {
17
+ readonly rooms: {
18
+ readonly reach: 'within';
19
+ readonly effect: 'read';
20
+ };
21
+ readonly query: {
22
+ readonly reach: 'within';
23
+ readonly effect: 'read';
24
+ };
25
+ readonly search: {
26
+ readonly reach: 'within';
27
+ readonly effect: 'read';
28
+ };
29
+ readonly types: {
30
+ readonly reach: 'within';
31
+ readonly effect: 'read';
32
+ };
33
+ readonly save: {
34
+ readonly reach: 'within';
35
+ readonly effect: 'write';
36
+ };
37
+ readonly saveParts: {
38
+ readonly reach: 'within';
39
+ readonly effect: 'write';
40
+ };
41
+ readonly history: {
42
+ readonly reach: 'within';
43
+ readonly effect: 'read';
44
+ };
45
+ readonly resolve: {
46
+ readonly reach: 'within';
47
+ readonly effect: 'read';
48
+ };
49
+ readonly 'binaryFile.read': {
50
+ readonly reach: 'within';
51
+ readonly effect: 'read';
52
+ };
53
+ readonly 'binaryFile.link': {
54
+ readonly reach: 'within';
55
+ readonly effect: 'read';
56
+ };
57
+ readonly 'binaryFile.readReference': {
58
+ readonly reach: 'within';
59
+ readonly effect: 'read';
60
+ };
61
+ readonly call: {
62
+ readonly reach: 'actions';
63
+ readonly effect: 'action';
64
+ };
65
+ readonly reached: {
66
+ readonly reach: 'within';
67
+ readonly effect: 'read';
68
+ };
69
+ readonly log: {
70
+ readonly reach: 'whole';
71
+ readonly effect: 'read';
72
+ };
73
+ readonly persistence: {
74
+ readonly reach: 'runtime';
75
+ readonly effect: 'read';
76
+ };
77
+ readonly retry: {
78
+ readonly reach: 'runtime';
79
+ readonly effect: 'write';
80
+ };
81
+ readonly reauthorize: {
82
+ readonly reach: 'runtime';
83
+ readonly effect: 'write';
84
+ };
85
+ readonly 'roomDraft.list': {
86
+ readonly reach: 'within';
87
+ readonly effect: 'read';
88
+ };
89
+ readonly 'roomDraft.read': {
90
+ readonly reach: 'within';
91
+ readonly effect: 'read';
92
+ };
93
+ readonly 'roomDraft.write': {
94
+ readonly reach: 'within';
95
+ readonly effect: 'write';
96
+ };
97
+ readonly 'roomDraft.discard': {
98
+ readonly reach: 'within';
99
+ readonly effect: 'write';
100
+ };
101
+ readonly 'presentation.read': {
102
+ readonly reach: 'app';
103
+ readonly effect: 'read';
104
+ };
105
+ readonly 'presentation.write': {
106
+ readonly reach: 'app';
107
+ readonly effect: 'write';
108
+ };
109
+ readonly 'presentation.edit': {
110
+ readonly reach: 'app';
111
+ readonly effect: 'write';
112
+ };
113
+ readonly 'conversation.list': {
114
+ readonly reach: 'within';
115
+ readonly effect: 'read';
116
+ };
117
+ readonly 'conversation.read': {
118
+ readonly reach: 'within';
119
+ readonly effect: 'read';
120
+ };
121
+ readonly 'conversation.create': {
122
+ readonly reach: 'within';
123
+ readonly effect: 'action';
124
+ };
125
+ readonly 'conversation.send': {
126
+ readonly reach: 'within';
127
+ readonly effect: 'action';
128
+ };
129
+ readonly 'conversation.attach': {
130
+ readonly reach: 'within';
131
+ readonly effect: 'action';
132
+ };
133
+ readonly 'conversation.answer': {
134
+ readonly reach: 'within';
135
+ readonly effect: 'action';
136
+ };
137
+ readonly 'conversation.interrupt': {
138
+ readonly reach: 'within';
139
+ readonly effect: 'action';
140
+ };
141
+ readonly 'conversation.kill': {
142
+ readonly reach: 'within';
143
+ readonly effect: 'action';
144
+ };
145
+ readonly 'conversation.cancel': {
146
+ readonly reach: 'within';
147
+ readonly effect: 'action';
148
+ };
149
+ readonly 'conversation.delete': {
150
+ readonly reach: 'within';
151
+ readonly effect: 'action';
152
+ };
153
+ readonly 'chatDraft.write': {
154
+ readonly reach: 'within';
155
+ readonly effect: 'write';
156
+ };
157
+ readonly 'chatDraft.prepend': {
158
+ readonly reach: 'within';
159
+ readonly effect: 'write';
160
+ };
161
+ readonly 'chatDraft.discard': {
162
+ readonly reach: 'within';
163
+ readonly effect: 'write';
164
+ };
165
+ };
166
+ declare const TOPICS: {
167
+ readonly rooms: 'within';
168
+ readonly readiness: 'within';
169
+ readonly 'connection-refusal': 'runtime';
170
+ readonly 'storage-loss': 'runtime';
171
+ readonly 'deployment-facts': 'runtime';
172
+ readonly 'sync-status': 'within';
173
+ readonly 'service-mode': 'runtime';
174
+ readonly member: 'runtime';
175
+ readonly query: 'within';
176
+ readonly queue: 'within';
177
+ readonly 'room-drafts': 'within';
178
+ readonly presentation: 'app';
179
+ readonly conversation: 'within';
180
+ readonly conversations: 'within';
181
+ readonly agents: 'within';
182
+ };
183
+ export declare const WIRE: {
184
+ readonly members: Readonly<Record<string, WireCall>>;
185
+ readonly topics: Readonly<Record<string, WireReach>>;
186
+ };
187
+ export type WireMember = keyof typeof MEMBERS;
188
+ export type WireTopic = keyof typeof TOPICS;
189
+ export type WireRequest = {
190
+ kind: 'hello';
191
+ } | {
192
+ kind: 'call';
193
+ id: number;
194
+ member: WireMember;
195
+ input: readonly unknown[];
196
+ } | {
197
+ kind: 'observe';
198
+ id: number;
199
+ topic: WireTopic;
200
+ input: readonly unknown[];
201
+ } | {
202
+ kind: 'stop';
203
+ id: number;
204
+ };
205
+ export type WireAnswer = {
206
+ kind: 'result';
207
+ id: number;
208
+ value: unknown;
209
+ } | ({
210
+ kind: 'failure';
211
+ id: number;
212
+ } & Described) | {
213
+ kind: 'event';
214
+ id: number;
215
+ value: unknown;
216
+ } | {
217
+ kind: 'refusal';
218
+ refusal: ConnectionRefusal;
219
+ } | {
220
+ kind: 'ended';
221
+ };
222
+ export declare function unanswered(call: {
223
+ readonly id: number;
224
+ readonly member: WireMember;
225
+ }): WireAnswer | null;
226
+ export declare const AUTHORIZATION_ENDED = "the host authorization ended";
227
+ export declare const PORT_CLOSED = "the port closed";
228
+ export declare function request(message: unknown): WireRequest | null;
229
+ export declare function answer(message: unknown): WireAnswer | null;
230
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { MdRuntimeClient } from './mdruntime/client.js';
2
+ import type { HouseActivation, HouseAnalyticsChoice, HouseAppMessage, HouseExternalAnswer, HouseExternalRequest, HouseNavigationMode, HouseRefusal, HouseSourceDownload, HouseSourceTop, HouseStatus, HouseTab, HouseTask } from './index.js';
3
+ export interface HouseBridgeNavigation {
4
+ readonly mode: HouseNavigationMode;
5
+ readonly address: string;
6
+ }
7
+ export interface HouseBridgeTestHost {
8
+ readonly navigations: readonly HouseBridgeNavigation[];
9
+ readonly ceremonies: readonly string[];
10
+ readonly consents: readonly HouseAnalyticsChoice[];
11
+ readonly tabs: readonly HouseTab[];
12
+ readonly externalCalls: readonly HouseExternalRequest[];
13
+ address(): string;
14
+ open(address: string): void;
15
+ publish(status: HouseStatus): void;
16
+ message(message: HouseAppMessage): void;
17
+ attachRuntime(runtime: MdRuntimeClient): void;
18
+ checkpoint(): Promise<HouseTask>;
19
+ restore(task: HouseTask | null): Promise<boolean>;
20
+ settle(activation: HouseActivation): void;
21
+ }
22
+ export interface HouseBridgeTestHostOptions {
23
+ readonly address?: string;
24
+ readonly status?: HouseStatus;
25
+ readonly sources?: readonly {
26
+ readonly top: HouseSourceTop;
27
+ readonly download: HouseSourceDownload;
28
+ }[];
29
+ readonly screenshots?: Readonly<Record<string, Blob>>;
30
+ readonly siteScreenshots?: Readonly<Record<string, Blob>>;
31
+ readonly external?: (request: HouseExternalRequest) => HouseExternalAnswer | HouseRefusal | null | Promise<HouseExternalAnswer | HouseRefusal | null>;
32
+ }
33
+ export declare function createHouseBridgeTestHost(options?: HouseBridgeTestHostOptions): HouseBridgeTestHost;
@@ -0,0 +1,200 @@
1
+ const HOST_ORIGIN = 'https://house.invalid';
2
+ const ADDRESS_MAX_CHARS = 64 * 1024;
3
+ function sameOriginPath(value) {
4
+ if (typeof value !== 'string')
5
+ return null;
6
+ if (value.charAt(0) !== '/' || value.slice(0, 2) === '//')
7
+ return null;
8
+ if (value.length > ADDRESS_MAX_CHARS)
9
+ return null;
10
+ const target = new URL(value, HOST_ORIGIN);
11
+ return target.origin === HOST_ORIGIN ? `${target.pathname}${target.search}${target.hash}` : null;
12
+ }
13
+ function appAddress(address) {
14
+ return address.startsWith('/') ? address : `/${address}`;
15
+ }
16
+ function ceremonyRefused(path) {
17
+ return Object.assign(new Error('house_ceremony_refused'), {
18
+ name: 'HouseCeremonyRefused',
19
+ code: 'house_ceremony_refused',
20
+ retryable: false,
21
+ path,
22
+ });
23
+ }
24
+ function navigationRefused(address) {
25
+ return Object.assign(new Error('house_navigation_refused'), {
26
+ name: 'HouseNavigationRefused',
27
+ code: 'house_navigation_refused',
28
+ retryable: false,
29
+ address,
30
+ });
31
+ }
32
+ function followHeld(listeners, listener, held) {
33
+ let following = true;
34
+ queueMicrotask(() => {
35
+ if (!following)
36
+ return;
37
+ listeners.add(listener);
38
+ const value = held();
39
+ if (value !== null)
40
+ listener(value);
41
+ });
42
+ return () => {
43
+ following = false;
44
+ listeners.delete(listener);
45
+ };
46
+ }
47
+ export function createHouseBridgeTestHost(options = {}) {
48
+ const navigations = [];
49
+ const ceremonies = [];
50
+ const consents = [];
51
+ const tabs = [];
52
+ const externalCalls = [];
53
+ const addressListeners = new Set();
54
+ const statusListeners = new Set();
55
+ const messageListeners = new Set();
56
+ let current = appAddress(options.address ?? '/');
57
+ let published = options.status ?? null;
58
+ let shown = null;
59
+ let seen = null;
60
+ let cooperation = null;
61
+ let attached = () => { };
62
+ const runtime = new Promise((resolve) => {
63
+ attached = resolve;
64
+ });
65
+ function arrive(target) {
66
+ current = target;
67
+ queueMicrotask(() => {
68
+ shown = target;
69
+ for (const listener of addressListeners)
70
+ listener(target);
71
+ });
72
+ }
73
+ function publish(next) {
74
+ published = next;
75
+ queueMicrotask(() => {
76
+ seen = next;
77
+ for (const listener of statusListeners)
78
+ listener(next);
79
+ });
80
+ }
81
+ function navigate(mode, asked) {
82
+ let path = asked;
83
+ if (/^https?:\/\//i.test(asked)) {
84
+ try {
85
+ const target = new URL(asked);
86
+ path = target.origin === HOST_ORIGIN ? target.pathname + target.search + target.hash : '';
87
+ }
88
+ catch {
89
+ path = '';
90
+ }
91
+ }
92
+ const target = sameOriginPath(path);
93
+ if (target === null)
94
+ return Promise.reject(navigationRefused(asked));
95
+ navigations.push({ mode, address: target });
96
+ arrive(target);
97
+ return new Promise((resolve) => {
98
+ queueMicrotask(() => resolve(target));
99
+ });
100
+ }
101
+ const bridge = {
102
+ runtime: runtime,
103
+ address: () => shown,
104
+ onAddress(listener) {
105
+ return followHeld(addressListeners, listener, () => shown);
106
+ },
107
+ push: (asked) => navigate('push', asked),
108
+ replace: (asked) => navigate('replace', asked),
109
+ ceremony(path) {
110
+ const target = sameOriginPath(path);
111
+ if (target === null)
112
+ return Promise.reject(ceremonyRefused(path));
113
+ ceremonies.push(target);
114
+ return new Promise(() => { });
115
+ },
116
+ install: () => Promise.resolve('manual'),
117
+ notifications: () => Promise.resolve('unavailable'),
118
+ setBadge: () => Promise.resolve(),
119
+ setTab(tab) {
120
+ tabs.push(tab);
121
+ },
122
+ sourceTop: (app) => Promise.resolve(options.sources?.find((entry) => entry.top.app_ref === app)?.top ?? null),
123
+ sourceDownload: (app) => Promise.resolve(options.sources?.find((entry) => entry.top.app_ref === app)?.download ?? null),
124
+ callExternal(request) {
125
+ externalCalls.push(request);
126
+ return Promise.resolve(options.external?.(request) ?? null);
127
+ },
128
+ readScreenshot: (app) => Promise.resolve(options.screenshots?.[app] ?? null),
129
+ readSiteScreenshot: (site) => Promise.resolve(options.siteScreenshots?.[site] ?? null),
130
+ status: () => seen,
131
+ onStatus(listener) {
132
+ return followHeld(statusListeners, listener, () => seen);
133
+ },
134
+ messages(listener) {
135
+ messageListeners.add(listener);
136
+ return () => {
137
+ messageListeners.delete(listener);
138
+ };
139
+ },
140
+ analyticsConsent(choice) {
141
+ if (published === null || !published.analytics.measures)
142
+ return;
143
+ if (choice !== 'accept' && choice !== 'decline')
144
+ return;
145
+ consents.push(choice);
146
+ publish({ ...published, analytics: { measures: true, choice } });
147
+ },
148
+ cooperate(given) {
149
+ cooperation = given;
150
+ return () => {
151
+ if (cooperation === given)
152
+ cooperation = null;
153
+ };
154
+ },
155
+ };
156
+ function cooperating() {
157
+ if (cooperation === null)
158
+ throw new Error('the App registered no cooperation');
159
+ return cooperation;
160
+ }
161
+ Object.defineProperty(globalThis, 'houseBridge', { value: bridge, configurable: true });
162
+ if (published !== null)
163
+ publish(published);
164
+ arrive(current);
165
+ return {
166
+ navigations,
167
+ ceremonies,
168
+ consents,
169
+ tabs,
170
+ externalCalls,
171
+ address: () => current,
172
+ open(asked) {
173
+ arrive(appAddress(asked));
174
+ },
175
+ publish,
176
+ message({ from, body }) {
177
+ queueMicrotask(() => {
178
+ for (const listener of messageListeners)
179
+ listener({ from, body });
180
+ });
181
+ },
182
+ attachRuntime(client) {
183
+ attached(client);
184
+ },
185
+ async checkpoint() {
186
+ const held = cooperating();
187
+ await held.checkpoint();
188
+ await held.persist();
189
+ return JSON.parse(JSON.stringify((await held.capture()) ?? null));
190
+ },
191
+ async restore(task) {
192
+ const held = cooperating();
193
+ await held.restore(task);
194
+ return (await held.ready()) === true;
195
+ },
196
+ settle(activation) {
197
+ cooperating().settle(activation);
198
+ },
199
+ };
200
+ }
@@ -0,0 +1,149 @@
1
+ # The App bridge's families
2
+
3
+ - **Navigation** - the part of the shell's URL below the App's path, with its
4
+ query and fragment. The shell delivers it when the App starts and whenever the
5
+ User navigates; the App asks the shell to push or replace it. The App also
6
+ sets its tab's title and icon with `setTab({ title, icon })`, either or both:
7
+ a title of at most 200 characters and an icon given as a base64 `data:` URL
8
+ of an SVG, PNG, GIF or WebP image of at most 1,048,576 characters. The shell
9
+ shows them over the App's display name, or a Site's title, and its tab icon
10
+ until the next load, and ignores a request outside those bounds.
11
+ - **Ceremony** - the App asks the shell to leave for one of House's own
12
+ purpose-bound authenticated pages. The shell navigates only to a same-origin
13
+ absolute path, carries its current address as the return target, and the page
14
+ returns there when it completes. An App names an address House gave it in a
15
+ reading and composes one only for `/bind/<app_ref>`, where the User binds
16
+ its Rooms and connects its account slots, with `?slot=<name>` to open at one
17
+ slot.
18
+ - **Installation** - `install()` offers the page as a Home-screen app. Where the
19
+ browser has an install prompt ready the shell shows it and the call resolves
20
+ `'accepted'` or `'dismissed'` with the User's choice, spending that prompt;
21
+ otherwise, as on every iPhone and iPad, it resolves `'manual'` and the App
22
+ shows its own instructions. A prompt the browser refuses to show, as for a
23
+ call made without a click, rejects with an error named and coded by the
24
+ browser's own error name, with `retryable` false, and stays ready. Call it from a click. The standard
25
+ `display-mode: standalone` media query tells the App it runs installed.
26
+ - **Notifications** - House's own panel alone; an App's or a Site's call
27
+ rejects with an error named `HouseCeremonyRefused`, carrying `code` and `retryable`. `notifications()` answers this browser
28
+ storage's push state: `'on'`, `'off'`, `'blocked'` where the browser refuses
29
+ notifications, or `'unavailable'` where it has no push, as on an iPhone or
30
+ iPad outside a Home-screen app. `notifications(true)` asks the browser's
31
+ permission and subscribes, `notifications(false)` unsubscribes, and each
32
+ answers the state it leaves; a failure of the browser's push service, and
33
+ of `setBadge`, rejects the same way. Call `notifications(true)` from a
34
+ click. `setBadge(unread)` takes the identifiers of the Human Notifications
35
+ unread in this storage and sets the icon's badge to their number, or clears
36
+ it at none, where the browser shows one.
37
+ - **Status** - the App reference the runtime client is attached under,
38
+ `preferences`: the `roomRef` and Path of the App's own preferences document,
39
+ or `null` until the runtime holds that Room, sync
40
+ state, credential state and its `expiresAt`, observed failure, `analytics`:
41
+ whether the page measures and the visitor's stored analytics choice, and
42
+ `serviceMode`: `open`, `write-halted` while House refuses writes, so the
43
+ App can stop retrying them, or `null` while the runtime holds no House
44
+ connection. Every status field is camelCase. `onStatus(listener)` and
45
+ `onAddress(listener)` hand a listener the status and the address the bridge
46
+ already holds, then each change.
47
+ The App reports the visitor's choice with `analyticsConsent('accept')` or
48
+ `analyticsConsent('decline')`; the shell hands it to House's analytics loader, and
49
+ the choice never reaches House.
50
+ - **Handoff** - when House hands its runtime over to a newer build, the App keeps
51
+ running, its `runtime` client and observations included: a call it makes
52
+ meanwhile waits for the newer runtime, however long that takes, and is
53
+ answered once, by it; each observation goes on with the newer runtime's
54
+ values; and the status and the address arrive again. A runtime that ended
55
+ before answering has its reads answered by the next one; since it may have
56
+ applied any other call, an act on House is answered the outcome
57
+ `transport-uncertainty`, and every other call, a Save or a draft write among
58
+ them, fails with the retryable code `transport-uncertainty`.
59
+ - **Site image read** - given a Published Room and an original attachment
60
+ reference, it answers the web copy's path and metadata, or `null` when no
61
+ copy exists. The original bytes never cross this bridge at a Site.
62
+ - **App release code** - `sourceTop(app, release)` answers the top level of
63
+ the latest successful release of an App the User may use: its folders, its
64
+ files with their sizes and its root README's text, or `null` for none;
65
+ `sourceDownload(app, release)` answers every file of that release with its
66
+ path, its size and its `text`, or its bytes as `base64`. The browser keeps
67
+ each answer per App and release: a read naming a release it holds answers
68
+ without asking House, and keeping a newer release drops the earlier one. A
69
+ refusal is House's `{ error: { code, retryable } }`, `app_release_not_found` for an
70
+ App with no release; a read House does not answer, or one that outlived
71
+ the browser's signed-in User, is `null`.
72
+ - **External calls** - `callExternal({ url, method, headers, body, slot, room_ref })`
73
+ has House's server send an HTTPS request to one of the release's declared
74
+ external origins and resolves with the service's `status`, `headers` and its
75
+ body as `body` text or `body_base64`, or a refusal as House's
76
+ `{ error: { code, retryable } }`, or `null` when House does not answer. House fills in the
77
+ App and its running release; naming an account slot and one of the App's
78
+ bound Rooms places the key of the account that Room connects to the slot,
79
+ when the User holds one of the account's roles there, which the App never
80
+ sees, and naming neither sends no key. A Site's call names no slot, and the House panel names an
81
+ `account` instead. `body_base64` replaces `body` for bytes.
82
+ - **App messages** - `messages(listener)` hands the listener each message
83
+ another holder of this App sends to this User's running copies, as `from`,
84
+ the sender's User id, and `body`, the JSON value they sent, until the
85
+ function it returns is called. App code sends one through the Runtime's
86
+ `call('message.send', { to, body })` with the recipient's User id and
87
+ a body of at most 32 KiB of JSON; House fills in the App and relays it only
88
+ to copies running now, storing nothing. The panel sends none.
89
+ - **App screenshot read** - given an App reference, `readScreenshot(app)`
90
+ answers the image this browser captured of that App's screen the first time
91
+ it ran its current release, as a PNG `Blob` at most 1200 pixels wide, or
92
+ `null` when this browser holds none. The image stays in the browser; a Site
93
+ holds none.
94
+ - **Site screenshot read** - given one of the User's Site identifiers,
95
+ `readSiteScreenshot(site)` answers the image House captured of that Site
96
+ after its latest release, as a JPEG `Blob`, or `null` when House holds none
97
+ or does not answer. A Site's own renderer reads none.
98
+
99
+ - **Cooperation** - `cooperate({ checkpoint, persist, capture, restore,
100
+ ready, settle })` lets House activate a newer release of the App while it
101
+ runs, keeping the User's task; the function it returns withdraws it.
102
+ Register it in the entry script, before the first draw, or House treats the
103
+ page as not cooperating and the newer release waits for the App's next
104
+ open. When a newer release is stored, House calls `checkpoint()`, which
105
+ resolves at the App's next safe checkpoint and may never resolve, then
106
+ `persist()`, which makes authored work durable, then `capture()`, which
107
+ answers the meaningful task the App owns, such as the selected record,
108
+ filters, cursor, selection and scroll position, as JSON of at most 65,536
109
+ characters; House keeps the address itself. In the newer release House
110
+ calls `restore(task)` once its document has drawn, so the scroll position
111
+ and focus it sets hold, or `restore(null)` when it opens the release without
112
+ a task, then `ready()`, the App's own check that its local data and
113
+ capabilities work, and finally `settle('committed')` once the release is
114
+ active or `settle('abandoned')` when House returns to the release before
115
+ it, restoring the same task there. A release House opens already active is
116
+ settled `'committed'` at once, after `restore(task)` when House hands it a
117
+ task, and a cooperation registered after House settled is settled at its
118
+ registration. Until `settle('committed')` House fails the release's outside
119
+ calls at once, as offline, those of the workers it starts included, and
120
+ refuses its Saves, write acts and external calls with the retryable code
121
+ `house_trial_withheld`, so a start-up effect is not repeated; run start-up
122
+ effects after `settle('committed')`.
123
+
124
+ ## The test host
125
+
126
+ `createHouseBridgeTestHost(options)` plays House in one page: it installs a
127
+ bridge object that `houseBridge()` returns, until the next host replaces it.
128
+ It delivers the address the App starts at and each address `open` plays as
129
+ Back or Forward, admitting nothing, so it delivers `//notes` exactly as a
130
+ browser at `/apps/<app>//notes` does. It records every push and replace and
131
+ answers each with the address the shell would then show, records an admitted
132
+ ceremony, and refuses a push, replace or ceremony that is not a same-origin
133
+ absolute path of at most 1,000 characters with an error named
134
+ `HouseNavigationRefused` or `HouseCeremonyRefused`. It publishes a status,
135
+ its `serviceMode` included, records in `consents` each `analyticsConsent` it
136
+ takes while that status measures and publishes the status the choice leaves,
137
+ records in `tabs` each `setTab`, answers a release code read from the
138
+ `sources` it was given, or `null`, answers each `callExternal` from the
139
+ `external` function it was given, or `null`, and records the request in
140
+ `externalCalls`, answers `readScreenshot` from the `screenshots` it was given
141
+ by App reference and `readSiteScreenshot` from the `siteScreenshots` it was
142
+ given by Site identifier, answers every `install()` `'manual'`, as a browser
143
+ with no install prompt does, answers every `notifications()` `'unavailable'`,
144
+ as a browser without push does, settles every `setBadge`, delivers each App
145
+ message `message({ from, body })` plays, and hands `bridge.runtime` the MD
146
+ Runtime client `attachRuntime(client)` gives it. `checkpoint()` runs the
147
+ registered cooperation's checkpoint, persist and capture and answers the task,
148
+ `restore(task)` runs its restore and answers its readiness check, and
149
+ `settle(activation)` hands it the outcome.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agentshouse/app-bridge",
3
- "version": "0.1.0-alpha.8",
4
- "description": "Renderer-side client of the agents.house App bridge: navigation, the tab title and icon, ceremonies, status, Site image reads, App release code and App screenshot reads, with an in-page test host.",
5
- "license": "UNLICENSED",
3
+ "version": "0.1.0-c0079debc581d",
4
+ "description": "Types of the agents.house App bridge House hands every App: navigation, the tab title and icon, ceremonies, installation, notifications and the icon badge, status, Site image reads, App release code, App and Site screenshot reads, and App messages, with an accessor and an in-page test host.",
5
+ "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "engines": {
@@ -10,21 +10,18 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./client/src/index.d.ts",
14
- "default": "./client/src/index.js"
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
15
  },
16
16
  "./test-host": {
17
- "types": "./client/src/test-host.d.ts",
18
- "default": "./client/src/test-host.js"
17
+ "types": "./dist/test-host.d.ts",
18
+ "default": "./dist/test-host.js"
19
19
  }
20
20
  },
21
21
  "files": [
22
- "client/src/*.js",
23
- "client/src/*.d.ts",
24
- "src/apps/remainder.js",
25
- "src/apps/remainder.d.ts",
26
- "src/apps/tab.js",
27
- "src/apps/tab.d.ts",
28
- "README.md"
22
+ "dist",
23
+ "docs",
24
+ "README.md",
25
+ "LICENSE"
29
26
  ]
30
27
  }