@agentshouse/mdruntime-adapter 0.1.0-cca5d676bc88e → 0.1.0-ccda1a2da11c3

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 (53) hide show
  1. package/AGENTS.md +291 -213
  2. package/dist/actions.d.ts +69 -2
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +197 -198
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +378 -112
  8. package/dist/discovery.d.ts +7 -6
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +50 -18
  11. package/dist/gather.d.ts +2 -1
  12. package/dist/gather.d.ts.map +1 -1
  13. package/dist/gather.js +15 -20
  14. package/dist/index.d.ts +3 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/model.d.ts +1 -1
  18. package/dist/model.d.ts.map +1 -1
  19. package/dist/model.js +21 -7
  20. package/dist/part.d.ts +13 -0
  21. package/dist/part.d.ts.map +1 -0
  22. package/dist/part.js +80 -0
  23. package/dist/read.d.ts +72 -20
  24. package/dist/read.d.ts.map +1 -1
  25. package/dist/read.js +353 -155
  26. package/dist/referrer.d.ts +9 -0
  27. package/dist/referrer.d.ts.map +1 -0
  28. package/dist/referrer.js +41 -0
  29. package/dist/refusal.d.ts +5 -9
  30. package/dist/refusal.d.ts.map +1 -1
  31. package/dist/refusal.js +31 -17
  32. package/dist/room-types.d.ts +6 -0
  33. package/dist/room-types.d.ts.map +1 -0
  34. package/dist/room-types.js +66 -0
  35. package/dist/site.d.ts +1 -1
  36. package/dist/site.d.ts.map +1 -1
  37. package/dist/site.js +184 -62
  38. package/dist/stream.d.ts +6 -4
  39. package/dist/stream.d.ts.map +1 -1
  40. package/dist/stream.js +360 -42
  41. package/dist/transport.d.ts +67 -11
  42. package/dist/transport.d.ts.map +1 -1
  43. package/dist/transport.js +3 -1
  44. package/dist/version.d.ts +1 -0
  45. package/dist/version.d.ts.map +1 -1
  46. package/dist/version.js +1 -1
  47. package/dist/write.d.ts +14 -9
  48. package/dist/write.d.ts.map +1 -1
  49. package/dist/write.js +172 -47
  50. package/package.json +3 -4
  51. package/dist/links.d.ts +0 -20
  52. package/dist/links.d.ts.map +0 -1
  53. package/dist/links.js +0 -104
@@ -0,0 +1 @@
1
+ {"version":3,"file":"referrer.d.ts","sourceRoot":"","sources":["../src/referrer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC;AAgB5F,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC7C,aAAa,GAAG,IAAI,CAoBtB;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC7C,aAAa,GAAG,IAAI,CAOtB"}
@@ -0,0 +1,41 @@
1
+ function fields(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
3
+ ? value
4
+ : null;
5
+ }
6
+ function named(held, ...keys) {
7
+ const present = Object.keys(held);
8
+ return (present.length === keys.length &&
9
+ keys.every((key) => typeof held[key] === 'string' && held[key] !== ''));
10
+ }
11
+ export function houseReferrer(referrer, tracked) {
12
+ const held = fields(referrer);
13
+ if (held === null)
14
+ return null;
15
+ if (named(held, 'room', 'path')) {
16
+ const entry = tracked.get(held.room);
17
+ if (entry === undefined)
18
+ return null;
19
+ return {
20
+ locator: entry.locator,
21
+ referrer: { room_ref: entry.authority, path: held.path },
22
+ };
23
+ }
24
+ if (named(held, 'room_ref', 'record')) {
25
+ const roomRef = held.room_ref;
26
+ const entry = [...tracked.values()].find((candidate) => candidate.authority === roomRef);
27
+ return {
28
+ locator: entry?.locator ?? roomRef,
29
+ referrer: { room_ref: roomRef, record: held.record },
30
+ };
31
+ }
32
+ return null;
33
+ }
34
+ export function houseReferrerScope(referrer, tracked) {
35
+ const held = fields(referrer);
36
+ if (held !== null && named(held, 'room', 'path')) {
37
+ return { locator: held.room, path: held.path };
38
+ }
39
+ const found = houseReferrer(referrer, tracked);
40
+ return found === null ? null : { locator: found.locator };
41
+ }
package/dist/refusal.d.ts CHANGED
@@ -1,13 +1,9 @@
1
1
  import type { AdapterFailure, DiscoverResult, ProviderRefusal, ScopedRefusal } from '@agentshouse/mdruntime';
2
- export type DoorRefusal = ProviderRefusal & {
3
- retryable: boolean;
4
- };
5
- export declare function doorRefusal(body: unknown): DoorRefusal | null;
6
- export declare function scopedRefusal(locator: string, scope: ScopedRefusal['scope'], refusal: DoorRefusal): ScopedRefusal;
7
- export declare function classifyDoor(status: number, body: unknown, locator: string): AdapterFailure | {
2
+ export declare function doorRefusal(body: unknown): ProviderRefusal | null;
3
+ export declare function scopedRefusal(locator: string, scope: ScopedRefusal['scope'], refusal: ProviderRefusal): ScopedRefusal;
4
+ export declare function classifyDoor(status: number, body: unknown, locator: string): AdapterFailure | null;
5
+ export declare function classifyRead(status: number, body: unknown, locator: string): AdapterFailure | {
8
6
  kind: 'beyond-horizon';
9
7
  } | null;
10
- export declare function discoveryFailure(classified: AdapterFailure | {
11
- kind: 'beyond-horizon';
12
- }): DiscoverResult;
8
+ export declare function discoveryFailure(status: number, body: unknown): DiscoverResult | null;
13
9
  //# sourceMappingURL=refusal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refusal.d.ts","sourceRoot":"","sources":["../src/refusal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACd,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAO7D;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAC7B,OAAO,EAAE,WAAW,GACnB,aAAa,CAEf;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,MAAM,GACd,cAAc,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAAG,IAAI,CAapD;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACtD,cAAc,CAMhB"}
1
+ {"version":3,"file":"refusal.d.ts","sourceRoot":"","sources":["../src/refusal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACd,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAOjE;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAC7B,OAAO,EAAE,eAAe,GACvB,aAAa,CAEf;AAqBD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,MAAM,GACd,cAAc,GAAG,IAAI,CAMvB;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,MAAM,GACd,cAAc,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAAG,IAAI,CAGpD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAQrF"}
package/dist/refusal.js CHANGED
@@ -6,38 +6,52 @@ export function doorRefusal(body) {
6
6
  const error = body.error;
7
7
  if (typeof error.code !== 'string')
8
8
  return null;
9
- const { code, ...fields } = error;
10
- return { code, fields, retryable: error.retryable === true };
9
+ const { code, retryable, ...fields } = error;
10
+ return { code, fields, retryable: retryable === true };
11
11
  }
12
12
  export function scopedRefusal(locator, scope, refusal) {
13
- return { kind: 'scoped-refusal', locator, scope, code: refusal.code, fields: refusal.fields };
13
+ return { kind: 'scoped-refusal', locator, scope, ...refusal };
14
14
  }
15
- export function classifyDoor(status, body, locator) {
15
+ function doorFailure(status, body) {
16
16
  if (status === 0)
17
17
  return { kind: 'transport-uncertainty' };
18
18
  const refusal = doorRefusal(body);
19
19
  if (status === 401)
20
20
  return { kind: 'revoked-authorization' };
21
- if (refusal?.retryable === true)
21
+ if (refusal?.retryable === true || refusal?.code === 'update_required') {
22
22
  return { kind: 'transport-uncertainty' };
23
- if (refusal !== null && refusal.code === 'position_expired')
24
- return { kind: 'beyond-horizon' };
25
- if (status === 404 || refusal?.code === 'room_not_found') {
26
- return { kind: 'room-not-readable', locator };
27
23
  }
24
+ if (status === 404 || refusal?.code === 'room_not_found')
25
+ return { kind: 'room-not-readable' };
28
26
  if (status < 400)
29
27
  return null;
30
28
  if (refusal === null)
31
29
  return { kind: 'transport-uncertainty' };
32
- if (status === 403)
33
- return scopedRefusal(locator, 'operation', refusal);
34
- return scopedRefusal(locator, 'undisclosed', refusal);
30
+ return { kind: 'refused', scope: status === 403 ? 'operation' : 'undisclosed', refusal };
31
+ }
32
+ export function classifyDoor(status, body, locator) {
33
+ const failure = doorFailure(status, body);
34
+ if (failure === null)
35
+ return null;
36
+ if (failure.kind === 'room-not-readable')
37
+ return { kind: 'room-not-readable', locator };
38
+ if (failure.kind === 'refused')
39
+ return scopedRefusal(locator, failure.scope, failure.refusal);
40
+ return failure;
35
41
  }
36
- export function discoveryFailure(classified) {
37
- if (classified.kind === 'revoked-authorization')
38
- return classified;
39
- if (classified.kind === 'scoped-refusal') {
40
- return { kind: 'contract-unavailable', code: classified.code, fields: classified.fields };
42
+ export function classifyRead(status, body, locator) {
43
+ if (doorRefusal(body)?.code === 'expired')
44
+ return { kind: 'beyond-horizon' };
45
+ return classifyDoor(status, body, locator);
46
+ }
47
+ export function discoveryFailure(status, body) {
48
+ const failure = doorFailure(status, body);
49
+ if (failure === null)
50
+ return null;
51
+ if (failure.kind === 'revoked-authorization')
52
+ return failure;
53
+ if (failure.kind === 'refused') {
54
+ return { kind: 'contract-unavailable', ...failure.refusal };
41
55
  }
42
56
  return { kind: 'transport-uncertainty' };
43
57
  }
@@ -0,0 +1,6 @@
1
+ export declare const INBOX_CHANNEL_KINDS: readonly ['telegram-bot'];
2
+ export type InboxChannelKind = (typeof INBOX_CHANNEL_KINDS)[number];
3
+ export declare const INBOX_CHAT_TYPE = "inbox-chat";
4
+ export declare const INBOX_MESSAGE_TYPE = "inbox-message";
5
+ export declare const HOUSE_ROOM_TYPES: string;
6
+ //# sourceMappingURL=room-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-types.d.ts","sourceRoot":"","sources":["../src/room-types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,YAAI,cAAc,CAAU,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAalD,eAAO,MAAM,gBAAgB,QAsDjB,CAAC"}
@@ -0,0 +1,66 @@
1
+ export const INBOX_CHANNEL_KINDS = ['telegram-bot'];
2
+ export const INBOX_CHAT_TYPE = 'inbox-chat';
3
+ export const INBOX_MESSAGE_TYPE = 'inbox-message';
4
+ const IDENTITY = "{ type: string, pattern: '^\\S+$' }";
5
+ const AGENT = [' agent: { type: string, format: uuid }', ' name: { type: string }'];
6
+ const ASSIGNEE = [
7
+ ' type: object',
8
+ ' properties:',
9
+ ' user: { type: string, format: uuid }',
10
+ ...AGENT,
11
+ ];
12
+ export const HOUSE_ROOM_TYPES = [
13
+ `${INBOX_CHAT_TYPE}:`,
14
+ ' properties:',
15
+ ' inbox: { type: string, format: uuid }',
16
+ ` channel: { enum: [${INBOX_CHANNEL_KINDS.join(', ')}] }`,
17
+ ` chat: ${IDENTITY}`,
18
+ ' customer:',
19
+ ' type: object',
20
+ ' properties:',
21
+ ' name: { type: string }',
22
+ ` username: ${IDENTITY}`,
23
+ ` phone: ${IDENTITY}`,
24
+ ' assignee:',
25
+ ...ASSIGNEE,
26
+ ' return_to:',
27
+ ' type: object',
28
+ ' properties:',
29
+ ...AGENT,
30
+ ' conversation: { type: string, format: uuid }',
31
+ ' required: [inbox, channel, chat, customer]',
32
+ `${INBOX_MESSAGE_TYPE}:`,
33
+ ' properties:',
34
+ ' direction: { enum: [in, out] }',
35
+ ' sender:',
36
+ ' type: object',
37
+ ' properties:',
38
+ ' name: { type: string }',
39
+ ` username: ${IDENTITY}`,
40
+ ' sent_at: { type: string, format: date-time }',
41
+ ` channel_message: ${IDENTITY}`,
42
+ ' state: { enum: [sending, sent, failed] }',
43
+ ' reason: { type: string }',
44
+ ' reactions:',
45
+ ' type: array',
46
+ ' items:',
47
+ ' type: object',
48
+ ' properties:',
49
+ ' reaction: { type: string, minLength: 1 }',
50
+ ' sender:',
51
+ ' type: object',
52
+ ' properties:',
53
+ ` id: ${IDENTITY}`,
54
+ ' name: { type: string }',
55
+ ` username: ${IDENTITY}`,
56
+ ' count: { type: integer, minimum: 1 }',
57
+ ' required: [reaction]',
58
+ ` post: ${IDENTITY}`,
59
+ ' assignee:',
60
+ ...ASSIGNEE,
61
+ ' sent_outside: { const: true }',
62
+ ' required: [direction]',
63
+ ' if: { properties: { direction: { const: in } } }',
64
+ ' then: { required: [sent_at] }',
65
+ '',
66
+ ].join('\n');
package/dist/site.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { Adapter } from '@agentshouse/mdruntime';
2
2
  import { type HouseSiteTransport } from './transport.js';
3
- export declare function createSiteAdapter(transport: HouseSiteTransport): Adapter;
3
+ export declare function createSiteAdapter(transport: HouseSiteTransport, snapshot?: boolean): Adapter;
4
4
  //# sourceMappingURL=site.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAMR,MAAM,wBAAwB,CAAC;AAsBhC,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AA+BxB,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,kBAAkB,GAAG,OAAO,CAiIxE"}
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAUR,MAAM,wBAAwB,CAAC;AA8BhC,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAuCxB,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,kBAAkB,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAoQ1F"}
package/dist/site.js CHANGED
@@ -1,86 +1,144 @@
1
- import { actionOutcome } from './actions.js';
1
+ import { stepped } from '@agentshouse/mdruntime';
2
+ import { actionOutcome, SITE_SUBMIT } from './actions.js';
2
3
  import { mapDiscovery, parseSiteDiscovery, siteCapabilities, siteTracked, } from './discovery.js';
3
- import { scopeModels, streamedModels, viewModels } from './model.js';
4
- import { advancedScope, bootstrapOf, parseEnumeration, parseSiteBootstrap, refreshFrom, viewOf, } from './read.js';
5
- import { classifyDoor, discoveryFailure } from './refusal.js';
4
+ import { partModels, streamedModels, viewModels } from './model.js';
5
+ import { advancedScope, bootstrapping, emptyScope, housePart, parseEnumeration, parseSiteParted, refreshFrom, viewOf, wholeView, } from './read.js';
6
+ import { PART_ATTEMPTS, unverifiedPart } from './part.js';
7
+ import { classifyDoor, classifyRead, discoveryFailure } from './refusal.js';
8
+ import { HOUSE_ROOM_TYPES } from './room-types.js';
6
9
  import { houseNotifications } from './stream.js';
7
- import { PRIVATE_LOCATOR, SITE_DOOR_BOOTSTRAP, SITE_DOOR_DISCOVER, SITE_DOOR_ENUMERATE, SITE_DOOR_SUBMIT, } from './transport.js';
10
+ import { SITE_DOOR_BOOTSTRAP, SITE_DOOR_DISCOVER, SITE_DOOR_ENUMERATE, SITE_DOOR_SUBMIT, SITE_FILE_RECORD, SITE_FILES, } from './transport.js';
11
+ import { houseReferrer } from './referrer.js';
8
12
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
9
13
  const UNCERTAIN = { kind: 'transport-uncertainty' };
14
+ function unfound() {
15
+ return { kind: 'rejected', code: 'file_not_found', fields: {}, retryable: false };
16
+ }
10
17
  function siteRoom(tracked) {
11
18
  return {
12
19
  name: tracked.handle,
13
20
  locator: tracked.locator,
21
+ handle: tracked.handle,
14
22
  roomVersion: roomVersion(tracked),
15
- operations: { read: [''], sync: [''], write: [], delete: [] },
16
- replica: { selection: tracked.locator, admission: 'admitted' },
23
+ operations: { write: [], delete: [] },
24
+ replica: { selection: tracked.locator, admission: 'admitted', offline: 'open' },
17
25
  providerOwnedPaths: [],
18
26
  };
19
27
  }
20
28
  function settled(refused) {
21
29
  return refused.kind === 'beyond-horizon' ? UNCERTAIN : refused;
22
30
  }
23
- export function createSiteAdapter(transport) {
31
+ export function createSiteAdapter(transport, snapshot = false) {
24
32
  const notifications = houseNotifications(transport, 'site');
25
33
  const tracked = new Map();
26
- async function enumeration(entry, from) {
34
+ const expired = new Map();
35
+ function published(referrer) {
36
+ const named = houseReferrer(referrer, tracked);
37
+ return named === null || !tracked.has(named.locator) ? null : named;
38
+ }
39
+ function fileRoute(request) {
40
+ const named = published(request.kind === 'path' ? { room: request.locator, path: request.path } : request.referrer);
41
+ if (named === null) {
42
+ return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
43
+ }
44
+ if (request.fileRef === undefined)
45
+ return unfound();
46
+ const { room_ref, path, record } = named.referrer;
47
+ return `${SITE_FILES}${room_ref}/${encodeURIComponent(request.fileRef)}/${path === undefined
48
+ ? `${SITE_FILE_RECORD}/${encodeURIComponent(record)}`
49
+ : encodeURIComponent(path)}`;
50
+ }
51
+ async function enumerated(entry, from, page) {
27
52
  const answer = await transport.door(SITE_DOOR_ENUMERATE, {
28
- positions: [presented(entry.authority, from)],
53
+ positions: [
54
+ page === undefined
55
+ ? presented(entry.authority, from)
56
+ : {
57
+ ...presented(entry.authority, { position: page.after, logEpoch: from.logEpoch }),
58
+ page: { through: page.through, read: page.read },
59
+ },
60
+ ],
29
61
  });
30
- const classified = classifyDoor(answer.status, answer.body, entry.locator);
62
+ const classified = classifyRead(answer.status, answer.body, entry.locator);
31
63
  if (classified !== null)
32
64
  return classified;
33
- return parseEnumeration(answer.body, 'site') ?? UNCERTAIN;
65
+ const found = parseEnumeration(answer.body, 'site');
66
+ if (found === null)
67
+ return UNCERTAIN;
68
+ return viewOf(found, entry) ?? { kind: 'room-not-readable', locator: entry.locator };
69
+ }
70
+ async function enumeration(entry, from) {
71
+ const first = await enumerated(entry, from);
72
+ return 'kind' in first ? first : wholeView(first, (page) => enumerated(entry, from, page));
34
73
  }
35
- async function caughtUp(entry) {
36
- const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}`);
74
+ async function partedRoom(entry) {
75
+ const cut = expired.get(entry.locator);
76
+ const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}${cut === undefined ? '' : `?expired=${cut}`}`);
37
77
  const classified = classifyDoor(answer.status, answer.body, entry.locator);
38
78
  if (classified !== null)
39
- return { failure: settled(classified) };
40
- const scope = parseSiteBootstrap(answer.body);
41
- if (scope === null)
42
- return { failure: UNCERTAIN };
43
- if (roomVersion(scope) === roomVersion(entry))
44
- return { scope };
45
- const found = await enumeration(entry, scope);
46
- if ('kind' in found)
47
- return { failure: settled(found) };
48
- const view = viewOf(found, entry);
49
- if (view === undefined)
50
- return { failure: { kind: 'room-not-readable', locator: entry.locator } };
51
- return { scope: advancedScope(scope, view) };
79
+ return classified;
80
+ const room = parseSiteParted(answer.body);
81
+ return room === null || room.anchor !== entry.authority ? UNCERTAIN : room;
82
+ }
83
+ async function fetchedPart(entry, room, listed) {
84
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
85
+ const answer = await transport.read(`${SITE_DOOR_BOOTSTRAP}${entry.authority}?part=${encodeURIComponent(listed.part)}`);
86
+ const failure = classifyRead(answer.status, answer.body, entry.locator);
87
+ if (failure?.kind === 'beyond-horizon')
88
+ expired.set(entry.locator, room.position);
89
+ if (failure !== null)
90
+ return failure;
91
+ const scope = await housePart(room, answer.body, listed, 'site');
92
+ if (scope !== null)
93
+ return scope;
94
+ }
95
+ return unverifiedPart(entry.locator);
96
+ }
97
+ async function caughtUp(entry, continued) {
98
+ const listed = continued.room.parts[continued.next];
99
+ const scope = listed === undefined
100
+ ? emptyScope(continued.room)
101
+ : await fetchedPart(entry, continued.room, listed);
102
+ if ('kind' in scope)
103
+ return scope;
104
+ if (continued.room.parts.length > 1 || roomVersion(scope) === roomVersion(entry))
105
+ return scope;
106
+ const view = await enumeration(entry, scope);
107
+ if ('kind' in view)
108
+ return settled(view);
109
+ return stepped(advancedScope(scope, view));
52
110
  }
53
111
  async function refreshed(entry, fromRoomVersion) {
54
112
  const from = logPosition(fromRoomVersion);
55
113
  if (from === null)
56
114
  return { kind: 'beyond-horizon' };
57
- const streamed = notifications.advance(entry, from);
115
+ const streamed = await stepped(notifications.advance(entry, from));
58
116
  if (streamed !== null) {
59
117
  const models = await streamedModels(streamed.schemas, entry, 'site');
60
118
  return models === undefined ? streamed.page : { ...streamed.page, models };
61
119
  }
62
- const found = await enumeration(entry, from);
63
- if ('kind' in found)
64
- return found;
65
- const view = viewOf(found, entry);
66
- if (view === undefined)
67
- return { kind: 'room-not-readable', locator: entry.locator };
120
+ const view = await enumeration(entry, from);
121
+ if ('kind' in view)
122
+ return view;
68
123
  notifications.reach(entry.authority, view);
69
- const answer = refreshFrom(view, fromRoomVersion);
124
+ const answer = await refreshFrom(view, fromRoomVersion);
70
125
  if (answer.kind !== 'net-advancement')
71
126
  return answer;
72
127
  const models = await viewModels(view, entry);
73
128
  return models === undefined ? answer : { ...answer, models };
74
129
  }
75
130
  return {
131
+ localSaves: true,
132
+ exhaustiveDiscovery: true,
133
+ roomTypes: HOUSE_ROOM_TYPES,
76
134
  async authorize() {
77
135
  return {};
78
136
  },
79
137
  async discover() {
80
138
  const answer = await transport.read(SITE_DOOR_DISCOVER);
81
- const classified = classifyDoor(answer.status, answer.body, PRIVATE_LOCATOR);
82
- if (classified !== null)
83
- return discoveryFailure(classified);
139
+ const failed = discoveryFailure(answer.status, answer.body);
140
+ if (failed !== null)
141
+ return failed;
84
142
  const document = parseSiteDiscovery(answer.body);
85
143
  if (document === null)
86
144
  return UNCERTAIN;
@@ -95,14 +153,32 @@ export function createSiteAdapter(transport) {
95
153
  const entry = tracked.get(request.locator);
96
154
  if (entry === undefined)
97
155
  return { kind: 'room-not-readable', locator: request.locator };
98
- if (request.pageToken !== undefined)
99
- return UNCERTAIN;
100
- const held = await caughtUp(entry);
101
- if ('failure' in held)
102
- return held.failure;
103
- notifications.reach(entry.authority, held.scope);
104
- const models = await scopeModels(held.scope, entry);
105
- return { ...bootstrapOf(held.scope), ...(models === undefined ? {} : { models }) };
156
+ let continued;
157
+ if (request.pageToken === undefined) {
158
+ const room = await partedRoom(entry);
159
+ if ('kind' in room)
160
+ return room;
161
+ continued = { room, next: 0, seen: [] };
162
+ }
163
+ else
164
+ continued = JSON.parse(request.pageToken);
165
+ const scope = await caughtUp(entry, continued);
166
+ if ('kind' in scope)
167
+ return scope;
168
+ notifications.reach(entry.authority, scope);
169
+ const next = continued.next + 1;
170
+ const more = next < continued.room.parts.length;
171
+ const seen = [
172
+ ...continued.seen,
173
+ ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
174
+ ];
175
+ const models = await partModels(scope, entry, new Set(seen), !more);
176
+ const { folders: _folders, ...page } = await stepped(bootstrapping(scope));
177
+ return {
178
+ ...page,
179
+ ...(models === undefined ? {} : { models }),
180
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
181
+ };
106
182
  },
107
183
  async refresh(_authorization, request) {
108
184
  const entry = tracked.get(request.locator);
@@ -110,14 +186,47 @@ export function createSiteAdapter(transport) {
110
186
  return { kind: 'room-not-readable', locator: request.locator };
111
187
  if (request.pageToken !== undefined)
112
188
  return UNCERTAIN;
189
+ if (snapshot) {
190
+ return request.fromRoomVersion === roomVersion(entry)
191
+ ? { kind: 'still-current' }
192
+ : { kind: 'beyond-horizon' };
193
+ }
113
194
  return refreshed(entry, request.fromRoomVersion);
114
195
  },
115
196
  async mutate() {
116
- return { kind: 'rejected', affected: {}, code: 'write-grant', fields: {} };
197
+ return { kind: 'rejected', affected: {}, code: 'write-grant', fields: {}, retryable: false };
117
198
  },
118
199
  async history() {
119
200
  return { kind: 'no-history' };
120
201
  },
202
+ async binary(_authorization, request) {
203
+ const route = fileRoute(request);
204
+ if (typeof route !== 'string')
205
+ return route;
206
+ const read = await transport.download(route);
207
+ if (read.status === 404)
208
+ return unfound();
209
+ return read.status === 200 && read.body !== null
210
+ ? { kind: 'binary', body: read.body, mediaType: read.mediaType }
211
+ : UNCERTAIN;
212
+ },
213
+ async link(_authorization, request) {
214
+ const route = fileRoute(request);
215
+ if (typeof route !== 'string')
216
+ return route;
217
+ return {
218
+ kind: 'link',
219
+ url: route,
220
+ name: request.path.slice(request.path.lastIndexOf('/') + 1),
221
+ };
222
+ },
223
+ referrerScope(referrer) {
224
+ const named = published(referrer);
225
+ if (named === null)
226
+ return null;
227
+ const { path } = named.referrer;
228
+ return { locator: named.locator, ...(path === undefined ? {} : { path }) };
229
+ },
121
230
  reached(locator, version, target) {
122
231
  return reachedVersion(version, target, tracked.get(locator)?.logEpoch);
123
232
  },
@@ -125,26 +234,39 @@ export function createSiteAdapter(transport) {
125
234
  return followsVersion(version, next);
126
235
  },
127
236
  async action(_authorization, request) {
128
- if (request.name !== 'submit') {
129
- return { kind: 'rejected', code: 'action-unimplemented', fields: { name: request.name } };
237
+ if (request.name !== SITE_SUBMIT) {
238
+ return {
239
+ kind: 'rejected',
240
+ code: 'action-unimplemented',
241
+ fields: { name: request.name },
242
+ retryable: false,
243
+ };
130
244
  }
131
245
  if (!transport.online())
132
246
  return UNCERTAIN;
133
- return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input), 'submit');
247
+ return actionOutcome(await transport.door(SITE_DOOR_SUBMIT, request.input), SITE_SUBMIT);
134
248
  },
135
- roomStream: {
136
- hold(rooms) {
137
- notifications.send(rooms.flatMap((room) => {
138
- const entry = tracked.get(room.locator);
139
- const at = logPosition(room.roomVersion);
140
- if (entry === undefined || at === null)
141
- return [];
142
- notifications.reach(entry.authority, at);
143
- return [presented(entry.authority, at)];
144
- }));
249
+ roomStream: snapshot
250
+ ? undefined
251
+ : {
252
+ hold(rooms) {
253
+ notifications.send(rooms.flatMap((room) => {
254
+ const entry = tracked.get(room.locator);
255
+ const at = logPosition(room.roomVersion);
256
+ if (entry === undefined || at === null)
257
+ return [];
258
+ notifications.reach(entry.authority, at);
259
+ return [presented(entry.authority, at)];
260
+ }));
261
+ },
262
+ reopen: notifications.reopen,
263
+ observe: notifications.observe,
264
+ published(locator, roomVersion) {
265
+ const at = logPosition(roomVersion);
266
+ if (at !== null)
267
+ notifications.published(locator, at);
268
+ },
145
269
  },
146
- observe: notifications.observe,
147
- },
148
270
  serviceMode: notifications.serviceMode,
149
271
  };
150
272
  }
package/dist/stream.d.ts CHANGED
@@ -1,20 +1,22 @@
1
- import type { ConversationStream, RefreshPage, RoomStreamEvent, ServiceModeSignal } from '@agentshouse/mdruntime';
1
+ import { type ConversationStream, type RefreshPage, type RoomStreamEvent, type ServiceModeSignal, type StreamReceipts } from '@agentshouse/mdruntime';
2
2
  import type { TrackedAuthority } from './discovery.js';
3
3
  import type { HouseDoor, HouseHeldRoom, HouseMode, HouseStreamEntry } from './transport.js';
4
4
  import { type LogPosition } from './version.js';
5
5
  export type HeldRevision = (path: string, revision: string) => boolean;
6
6
  export type StreamedAdvancement = {
7
7
  page: RefreshPage;
8
- entries: readonly HouseStreamEntry[];
9
8
  schemas: ReadonlyMap<string, HouseStreamEntry>;
10
9
  };
11
10
  export type HouseNotifications = {
12
11
  runtime: string;
13
- observe(notify: (event: RoomStreamEvent) => void): () => void;
12
+ observe(notify: (event: RoomStreamEvent) => void, receipts: StreamReceipts): () => void;
14
13
  send(rooms: readonly HouseHeldRoom[]): void;
14
+ published(locator: string, at: LogPosition): void;
15
+ reopen(): void;
15
16
  track(authorities: readonly TrackedAuthority[]): void;
16
17
  reach(authority: string, at: LogPosition): void;
17
- advance(tracked: TrackedAuthority, from: LogPosition, holds?: HeldRevision): StreamedAdvancement | null;
18
+ edited(tracked: TrackedAuthority, from: LogPosition): readonly string[];
19
+ advance(tracked: TrackedAuthority, from: LogPosition, holds?: HeldRevision): Generator<number, StreamedAdvancement | null, void>;
18
20
  conversations: ConversationStream;
19
21
  serviceMode: ServiceModeSignal;
20
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAElB,WAAW,EACX,eAAe,EAEf,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAEb,SAAS,EAET,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAgB7D,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACrC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC9D,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,OAAO,CACL,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,EACjB,KAAK,CAAC,EAAE,YAAY,GACnB,mBAAmB,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,kBAAkB,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAuBF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,kBAAkB,CAoS5F"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EAEvB,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAUvD,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAEb,SAAS,EAET,gBAAgB,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AA0C7D,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IACxF,IAAI,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAClD,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,SAAS,MAAM,EAAE,CAAC;IACxE,OAAO,CACL,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,WAAW,EACjB,KAAK,CAAC,EAAE,YAAY,GACnB,SAAS,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;IACvD,aAAa,EAAE,kBAAkB,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AAmFF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,kBAAkB,CA8jB5F"}