@agentshouse/mdruntime-adapter 0.1.0-cdcec0737ac0e → 0.1.0-cdd3671d36a47

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 (52) hide show
  1. package/README.md +1 -1
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +15 -3
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +67 -75
  7. package/dist/authored-path.d.ts +11 -0
  8. package/dist/authored-path.d.ts.map +1 -0
  9. package/dist/authored-path.js +35 -0
  10. package/dist/chunks.d.ts +38 -0
  11. package/dist/chunks.d.ts.map +1 -0
  12. package/dist/chunks.js +247 -0
  13. package/dist/discovery.d.ts +2 -2
  14. package/dist/discovery.d.ts.map +1 -1
  15. package/dist/discovery.js +8 -5
  16. package/dist/history.js +1 -1
  17. package/dist/house-model.d.ts +93 -102
  18. package/dist/house-model.d.ts.map +1 -1
  19. package/dist/house-model.js +79 -55
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/model.d.ts +3 -7
  24. package/dist/model.d.ts.map +1 -1
  25. package/dist/model.js +3 -13
  26. package/dist/read.d.ts +9 -9
  27. package/dist/read.d.ts.map +1 -1
  28. package/dist/read.js +24 -143
  29. package/dist/refusal.d.ts +1 -0
  30. package/dist/refusal.d.ts.map +1 -1
  31. package/dist/refusal.js +4 -0
  32. package/dist/room-types.d.ts +0 -2
  33. package/dist/room-types.d.ts.map +1 -1
  34. package/dist/room-types.js +2 -5
  35. package/dist/search.js +1 -1
  36. package/dist/site.d.ts.map +1 -1
  37. package/dist/site.js +30 -40
  38. package/dist/stream.d.ts +4 -3
  39. package/dist/stream.d.ts.map +1 -1
  40. package/dist/stream.js +83 -14
  41. package/dist/transport.d.ts +4 -8
  42. package/dist/transport.d.ts.map +1 -1
  43. package/dist/transport.js +12 -12
  44. package/dist/write.d.ts.map +1 -1
  45. package/dist/write.js +3 -5
  46. package/package.json +2 -2
  47. package/dist/part.d.ts +0 -14
  48. package/dist/part.d.ts.map +0 -1
  49. package/dist/part.js +0 -90
  50. package/dist/room-stream.d.ts +0 -20
  51. package/dist/room-stream.d.ts.map +0 -1
  52. package/dist/room-stream.js +0 -57
package/README.md CHANGED
@@ -25,7 +25,7 @@ host.attach(port, 'my-app');
25
25
 
26
26
  App code never imports this package; it reaches the Runtime only through the
27
27
  client its host hands it. The rules an App's agent follows are the `apps/` pages
28
- of House's Guide, which House's `open_guide` opens.
28
+ of House's Guide, which House's `guide/open` opens.
29
29
 
30
30
  ## License
31
31
 
package/dist/actions.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ActionDeclaration, ActionRequest, ActionResult } from '@agentshouse/mdruntime';
2
2
  import type { HouseDoorAnswer, HouseTransport } from './transport.js';
3
3
  export type HouseActDoor = Pick<HouseTransport, 'door'>;
4
+ export declare const HOUSE_UNKEPT_READS: readonly string[];
5
+ export declare const HOUSE_LEADING_READS: readonly string[];
4
6
  export declare function declaredActions(routes: readonly string[]): readonly ActionDeclaration[];
5
7
  export declare function houseAction(transport: HouseActDoor, request: ActionRequest): Promise<ActionResult>;
6
8
  export declare function actionOutcome(answer: HouseDoorAnswer, name: string, input: unknown): ActionResult;
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE7F,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAExD,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,iBAAiB,EAAE,CAEvF;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAIvB;AAwBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,CAUjG"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE7F,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEtE,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAG/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAKhD,CAAC;AAEF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,iBAAiB,EAAE,CAEvF;AAED,wBAAgB,WAAW,CACzB,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAIvB;AAwBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,CAajG"}
package/dist/actions.js CHANGED
@@ -1,4 +1,14 @@
1
- import { doorRefusal } from './refusal.js';
1
+ import { doorRefusal, resetOf } from './refusal.js';
2
+ export const HOUSE_UNKEPT_READS = [
3
+ 'account/security/read',
4
+ 'clients/last-use/list',
5
+ ];
6
+ export const HOUSE_LEADING_READS = [
7
+ 'rooms/list',
8
+ 'apps/list',
9
+ 'sites/list',
10
+ 'rooms/members/list',
11
+ ];
2
12
  export function declaredActions(routes) {
3
13
  return routes.map((name) => ({ name, inputShape: {}, resultKind: 'house', room: 'room_ref' }));
4
14
  }
@@ -35,8 +45,10 @@ export function actionOutcome(answer, name, input) {
35
45
  if (refusal?.code === 'operation_contended' && numbered(input)) {
36
46
  return { kind: 'transport-uncertainty' };
37
47
  }
38
- if (refusal !== null)
39
- return { kind: 'rejected', ...refusal };
48
+ if (refusal !== null) {
49
+ const reset = resetOf(refusal);
50
+ return { kind: 'rejected', ...refusal, ...(reset === undefined ? {} : { reset }) };
51
+ }
40
52
  if (answer.status === 404)
41
53
  return unimplemented(name);
42
54
  if (answer.status === 0 || answer.status >= 400)
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,OAAO,EAIP,aAAa,EAgBd,MAAM,wBAAwB,CAAC;AAiChC,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAoBxB,MAAM,MAAM,yBAAyB,GACjC;IACE,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;CACrC,GACD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA6DrD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAqapF"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,OAAO,EAIP,aAAa,EAed,MAAM,wBAAwB,CAAC;AAyBhC,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAqBxB,MAAM,MAAM,yBAAyB,GACjC;IACE,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;CACrC,GACD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAoBrD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAmbpF"}
package/dist/adapter.js CHANGED
@@ -1,20 +1,20 @@
1
1
  import { stepped } from '@agentshouse/mdruntime';
2
2
  import { capabilities, discoveredRoom, mapDiscovery, parseAuthorityChange, parseDiscovery, trackedFrom, } from './discovery.js';
3
3
  import { createSiteAdapter } from './site.js';
4
- import { authorityRoot, bootstrapping, parseFileAddress, parseGrant, } from './read.js';
5
- import { roomStreams } from './room-stream.js';
4
+ import { parseFileAddress, parseGrant } from './read.js';
5
+ import { chunkedHead, chunkLister, chunkPart } from './chunks.js';
6
6
  import { spaceSchemas } from './space-schema.js';
7
- import { sha256Hex } from './digest.js';
8
7
  import { finishedUpload, houseUpload } from './upload.js';
9
- import { houseAction } from './actions.js';
8
+ import { houseAction, HOUSE_LEADING_READS, HOUSE_UNKEPT_READS } from './actions.js';
10
9
  import { houseSearch } from './search.js';
11
10
  import { classifyDoor, doorRefusal } from './refusal.js';
12
11
  import { houseNotifications, } from './stream.js';
13
12
  import { houseDoorRoute, } from './transport.js';
14
13
  import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
14
+ import { admittedPath } from './authored-path.js';
15
15
  import { HOUSE_TYPED_PATHS } from './house-model.js';
16
16
  import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
17
- import { followsVersion, logPosition, presented, reachedVersion, roomVersion } from './version.js';
17
+ import { followsVersion, reachedVersion, roomVersion } from './version.js';
18
18
  const UNCERTAIN = { kind: 'transport-uncertainty' };
19
19
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
20
20
  const OCTET_STREAM = 'application/octet-stream';
@@ -22,32 +22,6 @@ const EXPIRED_ADDRESS = 403;
22
22
  function lapsedGrant(outcome) {
23
23
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
24
24
  }
25
- async function heldFiles(authority, held, bound) {
26
- const listed = await held.files(null, bound + 1);
27
- const files = listed.slice(0, bound);
28
- const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
29
- return files.length === 0 ? null : { room: authority, through, files };
30
- }
31
- async function reused(scope, held) {
32
- const sent = new Set(scope.contents.map((entry) => entry.path));
33
- const contents = [...scope.contents];
34
- const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
35
- if (asked.length === 0)
36
- return scope;
37
- if (held === undefined)
38
- return null;
39
- const kept = await held.contents(asked.map(({ path }) => path));
40
- for (const file of asked) {
41
- const found = kept.get(file.path);
42
- if (found === undefined ||
43
- found.revision !== file.revision ||
44
- (await sha256Hex(found.content)) !== file.sha256) {
45
- return null;
46
- }
47
- contents.push({ path: file.path, content: found.content });
48
- }
49
- return { ...scope, contents };
50
- }
51
25
  export function createAgentsHouseAdapter(options) {
52
26
  if ('site' in options)
53
27
  return createSiteAdapter(options.site, options.snapshot === true);
@@ -56,10 +30,12 @@ export function createAgentsHouseAdapter(options) {
56
30
  const notifications = houseNotifications(transport, 'credential', {
57
31
  opened: async (frame) => (frame === null ? null : opened(frame)),
58
32
  changed,
33
+ expired: (authorities) => lister.askAll(authorities.flatMap((authority) => [...tracked.values()].filter((entry) => entry.authority === authority))),
59
34
  }, spaces);
60
35
  const tracked = new Map();
61
- const rooms = roomStreams('credential', spaces);
62
- const unreused = new Set();
36
+ const lister = chunkLister(transport, () => declared().max_prefixes);
37
+ const chunked = new Map();
38
+ const renewals = new WeakMap();
63
39
  let bounds;
64
40
  function declared() {
65
41
  if (bounds === undefined)
@@ -72,6 +48,7 @@ export function createAgentsHouseAdapter(options) {
72
48
  return null;
73
49
  bounds = document.bounds;
74
50
  tracked.clear();
51
+ lister.forget();
75
52
  for (const entry of trackedFrom(document))
76
53
  tracked.set(entry.locator, entry);
77
54
  notifications.track([...tracked.values()]);
@@ -96,42 +73,52 @@ export function createAgentsHouseAdapter(options) {
96
73
  member: change.member,
97
74
  };
98
75
  }
99
- async function streamedPage(entry, token, held) {
100
- const part = await rooms.part(entry, token);
101
- if ('kind' in part)
102
- return part;
103
- const scope = await reused(part.scope, held);
104
- if (scope === null) {
105
- unreused.add(entry.locator);
106
- return UNCERTAIN_READ;
107
- }
108
- return {
109
- ...(await stepped(bootstrapping(scope))),
110
- ...(part.models === undefined ? {} : { models: part.models }),
111
- ...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
112
- bytes: part.bytes,
113
- };
76
+ async function listedRoom(entry) {
77
+ const listed = await lister.list(entry);
78
+ if ('kind' in listed)
79
+ return listed;
80
+ const head = await chunkedHead(listed, entry, spaces);
81
+ if (head.kind === 'bootstrap')
82
+ chunked.set(entry.locator, listed);
83
+ return head;
114
84
  }
115
- async function openedRoom(entry, request) {
116
- const reusing = unreused.delete(entry.locator) ? undefined : request.held;
117
- const files = reusing === undefined
118
- ? null
119
- : await heldFiles(entry.authority, reusing, declared().max_held_files);
120
- const answered = await transport.streamed(houseDoorRoute('bootstrap'), {
121
- room_ref: entry.authority,
122
- prefixes: [authorityRoot(entry.handle)],
123
- ...(files === null ? {} : { held: [files] }),
124
- }, request.signal ?? new AbortController().signal);
125
- if (answered.room === null)
126
- return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
127
- return rooms.head(entry, answered.room, request.arriving, request.signal);
85
+ async function chunkRead(entry, id) {
86
+ const room = chunked.get(entry.locator);
87
+ const address = room?.chunks.get(id);
88
+ if (room === undefined || address === undefined)
89
+ return null;
90
+ const read = await transport.download(address.url);
91
+ if (read.status !== EXPIRED_ADDRESS)
92
+ return { room, read };
93
+ const renewed = await renewal(entry, room);
94
+ if ('kind' in renewed || !renewed.chunks.has(id))
95
+ return null;
96
+ chunked.set(entry.locator, renewed);
97
+ return { room: renewed, read: await transport.download(renewed.chunks.get(id).url) };
128
98
  }
129
- function held(rooms) {
130
- notifications.send(rooms.flatMap((room) => {
131
- const entry = tracked.get(room.locator);
132
- const at = logPosition(room.roomVersion);
133
- return entry === undefined || at === null ? [] : [presented(entry.authority, at)];
134
- }));
99
+ function renewal(entry, stale) {
100
+ const held = renewals.get(stale);
101
+ if (held !== undefined)
102
+ return held;
103
+ const renewed = lister.renew(entry, [...chunked.keys()].flatMap((locator) => tracked.get(locator) ?? []));
104
+ renewals.set(stale, renewed);
105
+ return renewed;
106
+ }
107
+ function hold(rooms) {
108
+ for (const room of rooms) {
109
+ const listed = chunked.get(room.locator);
110
+ if (listed !== undefined &&
111
+ room.roomVersion !== null &&
112
+ reachedVersion(room.roomVersion, roomVersion(listed), listed.logEpoch))
113
+ chunked.delete(room.locator);
114
+ }
115
+ notifications.send(rooms);
116
+ }
117
+ async function chunkPage(entry, id, arriving) {
118
+ const fetched = await chunkRead(entry, id);
119
+ if (fetched === null || fetched.read.status !== 200)
120
+ return UNCERTAIN_READ;
121
+ return chunkPart(fetched.read.body, fetched.room, id, arriving);
135
122
  }
136
123
  function authorityFor(locator) {
137
124
  return tracked.get(locator);
@@ -156,7 +143,7 @@ export function createAgentsHouseAdapter(options) {
156
143
  if (handed !== undefined && handed.sha256 === sha256) {
157
144
  return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
158
145
  }
159
- const minted = await transport.door(houseDoorRoute('batch-upload'), {
146
+ const minted = await transport.door(houseDoorRoute('files/batch-upload'), {
160
147
  ...head,
161
148
  paths,
162
149
  bytes,
@@ -209,11 +196,13 @@ export function createAgentsHouseAdapter(options) {
209
196
  : { body: inline };
210
197
  if ('outcome' in prepared)
211
198
  return prepared.outcome;
212
- const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
199
+ const answer = await transport.door(houseDoorRoute('files/batch'), prepared.body);
213
200
  const stagedFiles = await staged(answer, request);
214
201
  if (stagedFiles === false)
215
202
  return UNCERTAIN;
216
- const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
203
+ const settled = stagedFiles === null
204
+ ? answer
205
+ : await transport.door(houseDoorRoute('files/batch'), prepared.body);
217
206
  if (stagedFiles === true && (await staged(settled, request)) !== null)
218
207
  return UNCERTAIN;
219
208
  return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
@@ -281,7 +270,7 @@ export function createAgentsHouseAdapter(options) {
281
270
  const entry = authorityFor(request.locator);
282
271
  if (entry === undefined)
283
272
  return null;
284
- return () => transport.door(houseDoorRoute('files/get'), {
273
+ return () => transport.door(houseDoorRoute('files/read'), {
285
274
  room_ref: entry.authority,
286
275
  path: request.path,
287
276
  revision: request.revision,
@@ -291,6 +280,9 @@ export function createAgentsHouseAdapter(options) {
291
280
  roomTypes: HOUSE_ROOM_TYPES,
292
281
  nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
293
282
  typedPaths: HOUSE_TYPED_PATHS,
283
+ pathRule: admittedPath,
284
+ unkeptReads: HOUSE_UNKEPT_READS,
285
+ leadingReads: HOUSE_LEADING_READS,
294
286
  async authorize() {
295
287
  return options.authorize();
296
288
  },
@@ -299,8 +291,8 @@ export function createAgentsHouseAdapter(options) {
299
291
  if (entry === undefined)
300
292
  return { kind: 'room-not-readable', locator: request.locator };
301
293
  return request.pageToken === undefined
302
- ? openedRoom(entry, request)
303
- : streamedPage(entry, request.pageToken, request.held);
294
+ ? listedRoom(entry)
295
+ : chunkPage(entry, request.pageToken, request.arriving);
304
296
  },
305
297
  async mutate(_authorization, request, keep) {
306
298
  const entry = authorityFor(request.locator);
@@ -331,7 +323,7 @@ export function createAgentsHouseAdapter(options) {
331
323
  const entry = authorityFor(request.locator);
332
324
  if (entry === undefined)
333
325
  return { kind: 'room-not-readable', locator: request.locator };
334
- const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
326
+ const answer = await transport.door(houseDoorRoute('files/revisions/list'), revisionBody({
335
327
  path: request.path,
336
328
  handle: entry.handle,
337
329
  ...(request.revision === undefined ? {} : { revision: request.revision }),
@@ -396,7 +388,7 @@ export function createAgentsHouseAdapter(options) {
396
388
  return houseSearch(transport, [...tracked.values()], request);
397
389
  },
398
390
  roomStream: {
399
- hold: held,
391
+ hold,
400
392
  observe: notifications.observe,
401
393
  },
402
394
  conversations: notifications.conversations,
@@ -0,0 +1,11 @@
1
+ export declare const GENERATED_PROVENANCE = "_provenance";
2
+ export declare const GENERATED_REVISIONS = "_revisions";
3
+ export declare const RESERVED_DERIVED = "_derived";
4
+ export declare const WHOLE_ESCAPED_NAMES: readonly string[];
5
+ export declare const AUTHORED_SEGMENT_MAX_CHARS = 255;
6
+ export declare const AUTHORED_SEGMENT: RegExp;
7
+ export declare const LOCAL_COPY_MANIFEST = ".house";
8
+ export declare const LOCAL_COPY_ROOT_NAMES: readonly string[];
9
+ export declare const APP_PREFERENCES_HOME = "settings/apps";
10
+ export declare function admittedPath(path: string): boolean;
11
+ //# sourceMappingURL=authored-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authored-path.d.ts","sourceRoot":"","sources":["../src/authored-path.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD,eAAO,MAAM,mBAAmB,eAAe,CAAC;AAEhD,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAE3C,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAMhD,CAAC;AAEF,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAO9C,eAAO,MAAM,gBAAgB,QAE5B,CAAC;AAEF,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAIlD,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AASpD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQlD"}
@@ -0,0 +1,35 @@
1
+ import { APP_REFERENCE_PATTERN } from './app-reference.js';
2
+ export const GENERATED_PROVENANCE = '_provenance';
3
+ export const GENERATED_REVISIONS = '_revisions';
4
+ export const RESERVED_DERIVED = '_derived';
5
+ export const WHOLE_ESCAPED_NAMES = [
6
+ '.',
7
+ '..',
8
+ GENERATED_PROVENANCE,
9
+ GENERATED_REVISIONS,
10
+ RESERVED_DERIVED,
11
+ ];
12
+ export const AUTHORED_SEGMENT_MAX_CHARS = 255;
13
+ const REFUSED_SEGMENTS = [
14
+ ...WHOLE_ESCAPED_NAMES.map((name) => name.replaceAll('.', '[.]')),
15
+ '[.][Gg][Ii][Tt][.]*',
16
+ ].join('|');
17
+ export const AUTHORED_SEGMENT = new RegExp(`^(?!(?:${REFUSED_SEGMENTS})(?:/|$))[A-Za-z0-9._][A-Za-z0-9._-]{0,${AUTHORED_SEGMENT_MAX_CHARS - 1}}$`);
18
+ export const LOCAL_COPY_MANIFEST = '.house';
19
+ export const LOCAL_COPY_ROOT_NAMES = [
20
+ '.gitignore',
21
+ '.ignore',
22
+ LOCAL_COPY_MANIFEST,
23
+ ];
24
+ export const APP_PREFERENCES_HOME = 'settings/apps';
25
+ function appPreferences(path) {
26
+ return (path.endsWith('.md') &&
27
+ APP_REFERENCE_PATTERN.test(path.slice(APP_PREFERENCES_HOME.length + 1, -3)));
28
+ }
29
+ export function admittedPath(path) {
30
+ const names = path.split('/');
31
+ const [root = ''] = names;
32
+ return (!LOCAL_COPY_ROOT_NAMES.includes(root.toLowerCase()) &&
33
+ names.every((name) => AUTHORED_SEGMENT.test(name)) &&
34
+ (!path.startsWith(`${APP_PREFERENCES_HOME}/`) || appPreferences(path)));
35
+ }
@@ -0,0 +1,38 @@
1
+ import { type AdapterFailure, type BootstrapPart, type BootstrapResult } from '@agentshouse/mdruntime';
2
+ import type { TrackedAuthority } from './discovery.js';
3
+ import { type HouseModels, type HouseScope } from './read.js';
4
+ import type { SpaceSchemas } from './space-schema.js';
5
+ import { type HouseDoor, type HouseMode } from './transport.js';
6
+ import { type LogPosition } from './version.js';
7
+ export type ChunkAddress = {
8
+ readonly url: string;
9
+ readonly bytes: number;
10
+ };
11
+ export type ChunkedRoom = LogPosition & {
12
+ readonly anchor: string;
13
+ readonly mode: HouseMode;
14
+ readonly chunks: ReadonlyMap<string, ChunkAddress>;
15
+ readonly parts: readonly BootstrapPart[];
16
+ readonly names: ReadonlyMap<string, string>;
17
+ readonly models: HouseModels;
18
+ };
19
+ export type Listed = ChunkedRoom | {
20
+ readonly kind: 'not-ready';
21
+ } | AdapterFailure;
22
+ type Answered = {
23
+ readonly status: number;
24
+ readonly body: unknown;
25
+ };
26
+ export declare function partId(logEpoch: string, hash: string): string;
27
+ export declare function siteChunkList(answer: Answered, locator: string): Listed;
28
+ export declare function chunkedHead(listed: ChunkedRoom, entry: TrackedAuthority, spaces: SpaceSchemas): Promise<BootstrapResult>;
29
+ export declare function chunkPart(body: ReadableStream<Uint8Array> | null, room: ChunkedRoom, id: string, arriving: ((bytes: number) => void) | undefined): Promise<BootstrapResult>;
30
+ export declare function chunkLister(transport: HouseDoor, most: () => number): {
31
+ askAll: (entries: readonly TrackedAuthority[]) => void;
32
+ list: (entry: TrackedAuthority) => Promise<Listed>;
33
+ renew(entry: TrackedAuthority, installing: readonly TrackedAuthority[]): Promise<Listed>;
34
+ forget(): void;
35
+ };
36
+ export declare function chunkScope(body: ReadableStream<Uint8Array>, room: ChunkedRoom, arriving: ((bytes: number) => void) | undefined): Promise<HouseScope | null>;
37
+ export {};
38
+ //# sourceMappingURL=chunks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../src/chunks.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAAG,cAAc,CAAC;AAEnF,KAAK,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAoCpE,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AA4BD,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIvE;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,EACvC,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAC9C,OAAO,CAAC,eAAe,CAAC,CAM1B;AAgBD,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,MAAM;sBAwCzC,SAAS,gBAAgB,EAAE;kBAU/B,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;iBASxC,gBAAgB,cAAc,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;;EAc3F;AAuCD,wBAAsB,UAAU,CAC9B,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAC9C,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAgC5B"}
package/dist/chunks.js ADDED
@@ -0,0 +1,247 @@
1
+ import { paced, ROW_WEIGHT, stepped, } from '@agentshouse/mdruntime';
2
+ import { manifestedModels } from './model.js';
3
+ import { authorityRoot, bootstrapping, parseFile, } from './read.js';
4
+ import { classifyDoor } from './refusal.js';
5
+ import { houseDoorRoute } from './transport.js';
6
+ import { readPosition, roomVersion } from './version.js';
7
+ function authorKey(author) {
8
+ for (const kind of ['user', 'site', 'inbox']) {
9
+ if (typeof author[kind] === 'string')
10
+ return `${kind}:${author[kind]}`;
11
+ }
12
+ const { app, release, function: named } = author;
13
+ return typeof app === 'string' && typeof release === 'string' && typeof named === 'string'
14
+ ? `app:${app}:${release}:${named}`
15
+ : null;
16
+ }
17
+ function namesOf(authors) {
18
+ if (!Array.isArray(authors))
19
+ return null;
20
+ const names = new Map();
21
+ for (const author of authors) {
22
+ const key = typeof author === 'object' && author !== null ? authorKey(author) : null;
23
+ if (key === null || typeof author.name !== 'string')
24
+ return null;
25
+ names.set(key, author.name);
26
+ }
27
+ return names;
28
+ }
29
+ function modelsOf(models, mode) {
30
+ if (!Array.isArray(models))
31
+ return null;
32
+ const files = [];
33
+ const contents = [];
34
+ for (const entry of models) {
35
+ const file = parseFile(entry, mode);
36
+ if (file === null || typeof entry.content !== 'string')
37
+ return null;
38
+ files.push(file);
39
+ contents.push({ path: file.path, content: entry.content });
40
+ }
41
+ return { files, contents };
42
+ }
43
+ export function partId(logEpoch, hash) {
44
+ return `${logEpoch}:${hash}`;
45
+ }
46
+ function chunkedRoom(room, mode) {
47
+ const at = readPosition(room);
48
+ const names = mode === 'site' ? new Map() : namesOf(room.authors);
49
+ const models = modelsOf(room.models, mode);
50
+ if (at === null || names === null || models === null || !Array.isArray(room.chunks))
51
+ return null;
52
+ const chunks = new Map();
53
+ const parts = [];
54
+ for (const chunk of room.chunks) {
55
+ const { hash, bytes, download } = chunk;
56
+ if (typeof hash !== 'string' || typeof bytes !== 'number' || typeof download !== 'string')
57
+ return null;
58
+ const id = partId(at.logEpoch, hash);
59
+ chunks.set(id, { url: download, bytes });
60
+ parts.push({ id, bytes });
61
+ }
62
+ return { anchor: room.room_ref, ...at, mode, chunks, parts, names, models };
63
+ }
64
+ function listedRoom(room, mode) {
65
+ if (typeof room !== 'object' || room === null)
66
+ return null;
67
+ const held = room;
68
+ if (typeof held.room_ref !== 'string')
69
+ return null;
70
+ if (held.ready === false)
71
+ return { kind: 'not-ready' };
72
+ return held.ready === true ? chunkedRoom(held, mode) : null;
73
+ }
74
+ export function siteChunkList(answer, locator) {
75
+ const failure = classifyDoor(answer.status, answer.body, locator);
76
+ if (failure !== null)
77
+ return failure;
78
+ return listedRoom(answer.body, 'site') ?? { kind: 'transport-uncertainty' };
79
+ }
80
+ export async function chunkedHead(listed, entry, spaces) {
81
+ if (listed.anchor !== entry.authority)
82
+ return { kind: 'transport-uncertainty' };
83
+ const models = await manifestedModels({
84
+ anchor: listed.anchor,
85
+ position: listed.position,
86
+ logEpoch: listed.logEpoch,
87
+ models: listed.models,
88
+ }, entry, spaces);
89
+ return {
90
+ kind: 'bootstrap',
91
+ roomVersion: roomVersion(listed),
92
+ documents: {},
93
+ facts: {},
94
+ revisions: {},
95
+ changes: {},
96
+ binaryFiles: {},
97
+ parts: listed.parts,
98
+ ...(models === undefined ? {} : { models }),
99
+ };
100
+ }
101
+ export async function chunkPart(body, room, id, arriving) {
102
+ const address = room.chunks.get(id);
103
+ if (body === null || address === undefined)
104
+ return { kind: 'transport-uncertainty' };
105
+ const scope = await chunkScope(body, room, arriving).catch(() => null);
106
+ if (scope === null)
107
+ return { kind: 'transport-uncertainty' };
108
+ return { ...(await stepped(bootstrapping(scope))), bytes: address.bytes };
109
+ }
110
+ function listedRooms(answer) {
111
+ const rooms = answer.body?.rooms;
112
+ if (!Array.isArray(rooms))
113
+ return null;
114
+ const listed = new Map();
115
+ for (const room of rooms) {
116
+ const held = listedRoom(room, 'credential');
117
+ if (held === null)
118
+ return null;
119
+ listed.set(room.room_ref, held);
120
+ }
121
+ return listed;
122
+ }
123
+ export function chunkLister(transport, most) {
124
+ const asked = new Map();
125
+ function answered(entries, whole) {
126
+ const prefixes = whole ? ['/'] : entries.map((entry) => authorityRoot(entry.handle));
127
+ return transport.door(houseDoorRoute('room-chunks/list'), { prefixes }).then((answer) => {
128
+ const failure = classifyDoor(answer.status, answer.body, '');
129
+ if (failure !== null)
130
+ return failed(entries, failure);
131
+ const listed = listedRooms(answer);
132
+ return listed ?? failed(entries, { kind: 'transport-uncertainty' });
133
+ }, () => failed(entries, { kind: 'transport-uncertainty' }));
134
+ }
135
+ function failed(entries, failure) {
136
+ return new Map(entries.map((entry) => [
137
+ entry.authority,
138
+ failure.kind === 'room-not-readable' || failure.kind === 'scoped-refusal'
139
+ ? { ...failure, locator: entry.locator }
140
+ : failure,
141
+ ]));
142
+ }
143
+ function settled(entry, lists) {
144
+ return lists.then((listed) => listed.get(entry.authority) ?? { kind: 'room-not-readable', locator: entry.locator });
145
+ }
146
+ function askAll(entries) {
147
+ if (entries.length === 0)
148
+ return;
149
+ const lists = answered(entries, entries.length > most());
150
+ for (const entry of entries) {
151
+ const listed = settled(entry, lists);
152
+ listed.catch(() => { });
153
+ asked.set(entry.authority, { entry, listed });
154
+ }
155
+ }
156
+ function list(entry) {
157
+ const held = asked.get(entry.authority);
158
+ asked.delete(entry.authority);
159
+ return held?.listed ?? settled(entry, answered([entry], false));
160
+ }
161
+ return {
162
+ askAll,
163
+ list,
164
+ renew(entry, installing) {
165
+ if (!asked.has(entry.authority)) {
166
+ const waiting = [...asked.values()].map((held) => held.entry);
167
+ const due = new Map([entry, ...installing, ...waiting].map((held) => [held.authority, held]));
168
+ askAll([...due.values()]);
169
+ }
170
+ return list(entry);
171
+ },
172
+ forget() {
173
+ asked.clear();
174
+ },
175
+ };
176
+ }
177
+ function folderLine(entry) {
178
+ return typeof entry.folder === 'string' && Object.keys(entry).length === 1 ? entry.folder : null;
179
+ }
180
+ function chunkEntry(line, room, read) {
181
+ let entry;
182
+ try {
183
+ entry = JSON.parse(line);
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ if (typeof entry !== 'object' || entry === null)
189
+ return false;
190
+ const folder = folderLine(entry);
191
+ if (folder !== null) {
192
+ read.folders.push(folder);
193
+ return true;
194
+ }
195
+ const author = entry.author;
196
+ let named = entry;
197
+ if (author !== undefined) {
198
+ const key = typeof author === 'object' && author !== null ? authorKey(author) : null;
199
+ const name = key === null ? undefined : room.names.get(key);
200
+ if (name === undefined)
201
+ return false;
202
+ named = { ...entry, author: { ...author, name } };
203
+ }
204
+ const file = parseFile(named, room.mode);
205
+ if (file === null)
206
+ return false;
207
+ read.files.push(file);
208
+ if (typeof entry.content === 'string')
209
+ read.contents.push({ path: file.path, content: entry.content });
210
+ return true;
211
+ }
212
+ export async function chunkScope(body, room, arriving) {
213
+ const read = {
214
+ files: [],
215
+ contents: [],
216
+ folders: [],
217
+ };
218
+ const reader = body.getReader();
219
+ const decoder = new TextDecoder();
220
+ const pace = paced();
221
+ const unfinished = [];
222
+ let bytes = 0;
223
+ for (let chunk = await reader.read(); chunk.done !== true; chunk = await reader.read()) {
224
+ bytes += chunk.value.length;
225
+ arriving?.(bytes);
226
+ const text = decoder.decode(chunk.value, { stream: true });
227
+ let start = 0;
228
+ for (let end = text.indexOf('\n'); end !== -1; end = text.indexOf('\n', start)) {
229
+ const piece = text.slice(start, end);
230
+ const line = unfinished.length === 0 ? piece : unfinished.splice(0).join('') + piece;
231
+ start = end + 1;
232
+ if (line.length > 0 && !chunkEntry(line, room, read)) {
233
+ await reader.cancel();
234
+ return null;
235
+ }
236
+ const due = pace(line.length + ROW_WEIGHT);
237
+ if (due instanceof Promise)
238
+ await due;
239
+ }
240
+ if (start < text.length)
241
+ unfinished.push(text.slice(start));
242
+ }
243
+ const rest = unfinished.join('') + decoder.decode();
244
+ if (rest.length > 0 && !chunkEntry(rest, room, read))
245
+ return null;
246
+ return { anchor: room.anchor, position: room.position, logEpoch: room.logEpoch, ...read };
247
+ }