@agentshouse/app-bridge 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The renderer-side client of the agents.house App bridge. An App served by House
4
4
  receives one message port before its own scripts run; this package is that
5
- port's one listener. It carries three families:
5
+ port's one listener. It carries four families:
6
6
 
7
7
  - **Navigation** - the part of the shell's URL below the App's path, with its
8
8
  query and fragment. The shell delivers it when the App starts and whenever the
@@ -12,8 +12,11 @@ port's one listener. It carries three families:
12
12
  absolute path, carries its current address as the return target, and the page
13
13
  returns there when it completes. An App names an address House gave it in a
14
14
  reading and composes none.
15
- - **Status** - sync state, credential state, release availability and observed
16
- failure.
15
+ - **Status** - the App reference the Runtime port is attached under, sync
16
+ state, credential state, release availability and observed failure.
17
+ - **Site image read** - given a Published Room and an original attachment
18
+ reference, it answers the web copy's path and metadata, or `null` when no
19
+ copy exists. The original bytes never cross this bridge at a Site.
17
20
 
18
21
  The package carries no credential handling and no Runtime family. It surfaces
19
22
  the dedicated Runtime port the worker transfers over the bridge as a plain
@@ -63,6 +66,9 @@ host.open('/rooms/atlas');
63
66
  ```
64
67
 
65
68
  The host plays the shell and the worker: it delivers the address the App starts
66
- at, records every push and replace and answers each with the address the shell
67
- would then show, records an admitted ceremony, refuses one that is not a
69
+ at and each address `open` plays as Back or Forward through the shell's own
70
+ slice below the App's path, admitting nothing, so it delivers `//notes` exactly
71
+ as a browser at `/apps/<app>//notes` does. It records every push and replace
72
+ and answers each with the address the shell would then show, records an
73
+ admitted ceremony, refuses a push, replace or ceremony that is not a
68
74
  same-origin absolute path, publishes a status, and attaches a Runtime port.
@@ -7,6 +7,12 @@ export declare class HouseNavigationRefused extends Error {
7
7
  readonly address: string | null;
8
8
  constructor(address: string | null);
9
9
  }
10
+ export interface HouseImageCopy {
11
+ readonly attachment: string;
12
+ readonly copy: string;
13
+ readonly name: string;
14
+ readonly media_type: string;
15
+ }
10
16
  export interface HouseBridge {
11
17
  readonly runtime: Promise<HouseMessagePort>;
12
18
  address(): string | null;
@@ -14,6 +20,7 @@ export interface HouseBridge {
14
20
  push(address: string): Promise<string>;
15
21
  replace(address: string): Promise<string>;
16
22
  ceremony(path: string): Promise<never>;
23
+ readAttachment(room: string, attachment: string): Promise<HouseImageCopy | null>;
17
24
  status(): HouseStatus | null;
18
25
  onStatus(listener: (status: HouseStatus) => void): () => void;
19
26
  }
@@ -31,6 +31,8 @@ export function connectHouseBridge(source = heldBridge()) {
31
31
  const runtime = settled();
32
32
  const ceremonies = [];
33
33
  const navigations = [];
34
+ const attachments = new Map();
35
+ let nextAttachment = 0;
34
36
  const addressListeners = new Set();
35
37
  const statusListeners = new Set();
36
38
  let outbox = [];
@@ -75,6 +77,17 @@ export function connectHouseBridge(source = heldBridge()) {
75
77
  pending?.settle.reject(new HouseNavigationRefused(named));
76
78
  return;
77
79
  }
80
+ if (data.house === 'attachment') {
81
+ const answer = data;
82
+ if (typeof answer.id !== 'number')
83
+ return;
84
+ const pending = attachments.get(answer.id);
85
+ if (pending === undefined)
86
+ return;
87
+ attachments.delete(answer.id);
88
+ pending.resolve(answer.image ?? null);
89
+ return;
90
+ }
78
91
  if (data.house === 'ceremony-refused') {
79
92
  const refused = data.path;
80
93
  const named = typeof refused === 'string' ? refused : null;
@@ -121,6 +134,13 @@ export function connectHouseBridge(source = heldBridge()) {
121
134
  send({ house: 'ceremony', path });
122
135
  return pending.promise;
123
136
  },
137
+ readAttachment(room, attachment) {
138
+ const pending = settled();
139
+ const id = ++nextAttachment;
140
+ attachments.set(id, pending.settle);
141
+ send({ house: 'attachment-read', id, room, attachment });
142
+ return pending.promise;
143
+ },
124
144
  status() {
125
145
  return status;
126
146
  },
@@ -1,3 +1,3 @@
1
- export { connectHouseBridge, HouseCeremonyRefused, HouseNavigationRefused, type HouseBridge, } from './bridge.js';
1
+ export { connectHouseBridge, HouseCeremonyRefused, HouseNavigationRefused, type HouseBridge, type HouseImageCopy, } from './bridge.js';
2
2
  export { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
3
3
  export type { HouseBridgeEvent, HouseBridgePort, HouseCredentialState, HouseMessagePort, HouseNavigationMode, HouseStatus, HouseSyncState, } from './wire.js';
@@ -1,3 +1,4 @@
1
+ import type { HouseImageCopy } from './bridge.js';
1
2
  import type { HouseBridgePort, HouseMessagePort, HouseNavigationMode, HouseStatus } from './wire.js';
2
3
  export interface HouseBridgeNavigation {
3
4
  readonly mode: HouseNavigationMode;
@@ -15,5 +16,8 @@ export interface HouseBridgeTestHost {
15
16
  export interface HouseBridgeTestHostOptions {
16
17
  readonly address?: string;
17
18
  readonly status?: HouseStatus;
19
+ readonly images?: readonly (HouseImageCopy & {
20
+ readonly room: string;
21
+ })[];
18
22
  }
19
23
  export declare function createHouseBridgeTestHost(options?: HouseBridgeTestHostOptions): HouseBridgeTestHost;
@@ -1,3 +1,4 @@
1
+ import { appAddressOf } from '../../src/apps/remainder.js';
1
2
  import { HOUSE_ADDRESS_MAX_CHARS } from './wire.js';
2
3
  const HOST_ORIGIN = 'https://house.invalid';
3
4
  function sameOriginPath(value) {
@@ -16,7 +17,7 @@ export function createHouseBridgeTestHost(options = {}) {
16
17
  const listeners = new Set();
17
18
  const queued = [];
18
19
  let started = false;
19
- let address = sameOriginPath(options.address ?? '/') ?? '/';
20
+ let address = appAddressOf('/', options.address ?? '/');
20
21
  function deliver(data, ports = []) {
21
22
  const event = { data, ports };
22
23
  if (!started) {
@@ -62,6 +63,22 @@ export function createHouseBridgeTestHost(options = {}) {
62
63
  else if (data.house === 'ceremony') {
63
64
  ceremony(data.path);
64
65
  }
66
+ else if (data.house === 'attachment-read') {
67
+ const asked = data;
68
+ const image = options.images?.find((entry) => entry.room === asked.room && entry.attachment === asked.attachment);
69
+ deliver({
70
+ house: 'attachment',
71
+ id: asked.id,
72
+ image: image === undefined
73
+ ? null
74
+ : {
75
+ attachment: image.attachment,
76
+ copy: image.copy,
77
+ name: image.name,
78
+ media_type: image.media_type,
79
+ },
80
+ });
81
+ }
65
82
  }
66
83
  const port = {
67
84
  postMessage(message) {
@@ -92,9 +109,7 @@ export function createHouseBridgeTestHost(options = {}) {
92
109
  return address;
93
110
  },
94
111
  open(asked) {
95
- const target = sameOriginPath(asked);
96
- if (target !== null)
97
- arrive(target, false);
112
+ arrive(appAddressOf('/', asked), false);
98
113
  },
99
114
  publish(status) {
100
115
  deliver({ house: 'status', status });
@@ -17,6 +17,7 @@ export interface HouseCredentialState {
17
17
  readonly expires_at: number | null;
18
18
  }
19
19
  export interface HouseStatus {
20
+ readonly app: string;
20
21
  readonly runtime: string;
21
22
  readonly credential: HouseCredentialState;
22
23
  readonly sync: HouseSyncState | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentshouse/app-bridge",
3
- "version": "0.1.0-alpha.1",
4
- "description": "Renderer-side client of the agents.house App bridge: navigation, ceremonies and status, with an in-page test host.",
3
+ "version": "0.1.0-alpha.3",
4
+ "description": "Renderer-side client of the agents.house App bridge: navigation, ceremonies, status and Site image reads, with an in-page test host.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -10,17 +10,19 @@
10
10
  },
11
11
  "exports": {
12
12
  ".": {
13
- "types": "./index.d.ts",
14
- "default": "./index.js"
13
+ "types": "./client/src/index.d.ts",
14
+ "default": "./client/src/index.js"
15
15
  },
16
16
  "./test-host": {
17
- "types": "./test-host.d.ts",
18
- "default": "./test-host.js"
17
+ "types": "./client/src/test-host.d.ts",
18
+ "default": "./client/src/test-host.js"
19
19
  }
20
20
  },
21
21
  "files": [
22
- "*.js",
23
- "*.d.ts",
22
+ "client/src/*.js",
23
+ "client/src/*.d.ts",
24
+ "src/apps/remainder.js",
25
+ "src/apps/remainder.d.ts",
24
26
  "README.md"
25
27
  ]
26
28
  }
@@ -0,0 +1,2 @@
1
+ export declare function appAddressOf(root: string, address: string): string;
2
+ export declare function shellAddressOf(root: string, appAddress: string): string;
@@ -0,0 +1,16 @@
1
+ function split(address) {
2
+ const mark = address.search(/[?#]/u);
3
+ return mark < 0
4
+ ? { path: address, rest: '' }
5
+ : { path: address.slice(0, mark), rest: address.slice(mark) };
6
+ }
7
+ export function appAddressOf(root, address) {
8
+ const { path, rest } = split(address);
9
+ const below = root === '/' ? path : path.slice(root.length);
10
+ return `${below.startsWith('/') ? below : `/${below}`}${rest}`;
11
+ }
12
+ export function shellAddressOf(root, appAddress) {
13
+ const { path, rest } = split(appAddress);
14
+ const below = path === '/' ? '' : path;
15
+ return `${root === '/' ? '' : root}${below}${rest}` || '/';
16
+ }
File without changes
File without changes