@agentshouse/mdruntime-adapter 0.1.0-cf9ab9b7cbbca → 0.1.0-cfd2dc1938a2c

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 (73) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +6 -66
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +23 -340
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +245 -366
  7. package/dist/app-reference.d.ts +2 -0
  8. package/dist/app-reference.d.ts.map +1 -0
  9. package/dist/app-reference.js +2 -0
  10. package/dist/authored-path.d.ts +11 -0
  11. package/dist/authored-path.d.ts.map +1 -0
  12. package/dist/authored-path.js +35 -0
  13. package/dist/chunks.d.ts +38 -0
  14. package/dist/chunks.d.ts.map +1 -0
  15. package/dist/chunks.js +247 -0
  16. package/dist/crockford.d.ts +4 -0
  17. package/dist/crockford.d.ts.map +1 -0
  18. package/dist/crockford.js +3 -0
  19. package/dist/discovery.d.ts +23 -15
  20. package/dist/discovery.d.ts.map +1 -1
  21. package/dist/discovery.js +92 -55
  22. package/dist/history.d.ts +12 -0
  23. package/dist/history.d.ts.map +1 -0
  24. package/dist/history.js +92 -0
  25. package/dist/house-model.d.ts +682 -0
  26. package/dist/house-model.d.ts.map +1 -0
  27. package/dist/house-model.js +397 -0
  28. package/dist/index.d.ts +12 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +11 -1
  31. package/dist/model.d.ts +5 -4
  32. package/dist/model.d.ts.map +1 -1
  33. package/dist/model.js +34 -41
  34. package/dist/read.d.ts +57 -64
  35. package/dist/read.d.ts.map +1 -1
  36. package/dist/read.js +154 -318
  37. package/dist/refusal.d.ts +1 -3
  38. package/dist/refusal.d.ts.map +1 -1
  39. package/dist/refusal.js +4 -5
  40. package/dist/room-types.d.ts +5 -0
  41. package/dist/room-types.d.ts.map +1 -0
  42. package/dist/room-types.js +120 -0
  43. package/dist/search.d.ts +5 -0
  44. package/dist/search.d.ts.map +1 -0
  45. package/dist/search.js +47 -0
  46. package/dist/site.d.ts.map +1 -1
  47. package/dist/site.js +102 -202
  48. package/dist/space-schema.d.ts +7 -0
  49. package/dist/space-schema.d.ts.map +1 -0
  50. package/dist/space-schema.js +18 -0
  51. package/dist/stream.d.ts +16 -15
  52. package/dist/stream.d.ts.map +1 -1
  53. package/dist/stream.js +261 -421
  54. package/dist/transport.d.ts +66 -43
  55. package/dist/transport.d.ts.map +1 -1
  56. package/dist/transport.js +54 -11
  57. package/dist/upload.d.ts +12 -0
  58. package/dist/upload.d.ts.map +1 -0
  59. package/dist/upload.js +63 -0
  60. package/dist/write.d.ts +18 -2
  61. package/dist/write.d.ts.map +1 -1
  62. package/dist/write.js +108 -80
  63. package/package.json +8 -4
  64. package/AGENTS.md +0 -299
  65. package/dist/gather.d.ts +0 -4
  66. package/dist/gather.d.ts.map +0 -1
  67. package/dist/gather.js +0 -45
  68. package/dist/handed.d.ts +0 -15
  69. package/dist/handed.d.ts.map +0 -1
  70. package/dist/handed.js +0 -38
  71. package/dist/referrer.d.ts +0 -7
  72. package/dist/referrer.d.ts.map +0 -1
  73. package/dist/referrer.js +0 -35
package/dist/stream.js CHANGED
@@ -1,117 +1,129 @@
1
- import { acceptedMessage } from './actions.js';
2
- import { sha256Hex } from './digest.js';
3
- import { binaryFileOf, headChange, identities } from './read.js';
4
- import { roomVersion } from './version.js';
5
- const HELD_FRAMES_MAX = 256;
1
+ import { ROW_WEIGHT, stepped, } from '@agentshouse/mdruntime';
2
+ import { acceptedMessage, cursorPosition, houseHistory } from './history.js';
3
+ import { keptSpaceSchemas, streamedModels } from './model.js';
4
+ import { answered, answering, binaryFileOf, createdOf, folded, forgotten, headChange, revised, } from './read.js';
5
+ import { classifyDoor } from './refusal.js';
6
+ import { houseDoorRoute, } from './transport.js';
7
+ import { DECIMAL, logPosition, presented, roomVersion } from './version.js';
6
8
  function turnOperation(frame) {
7
9
  const { type: _type, conversation_id: _conversation, turn_id: _turn, ...operation } = frame;
8
10
  return structuredClone(operation);
9
11
  }
12
+ async function houseAgents(transport) {
13
+ const answer = await transport.door(houseDoorRoute('agents/list'), {});
14
+ if (classifyDoor(answer.status, answer.body, 'agents/list')?.kind === 'transport-uncertainty') {
15
+ throw new Error('agents/list reached no answer');
16
+ }
17
+ const agents = answer.status === 200 ? answer.body?.agents : null;
18
+ if (!Array.isArray(agents))
19
+ return null;
20
+ return agents.flatMap((one) => {
21
+ const { id, name, retired_at: retired } = (one ?? {});
22
+ return typeof id === 'string' && typeof name === 'string'
23
+ ? [{ agent: id, name, retired: retired !== null && retired !== undefined }]
24
+ : [];
25
+ });
26
+ }
10
27
  function listedConversation(listed) {
11
28
  return {
12
29
  conversation: listed.conversation_id,
13
30
  agent: listed.agent_id,
31
+ agentName: listed.agent_name,
32
+ agentRetired: listed.agent_retired,
14
33
  name: listed.name,
15
34
  created: listed.created_at,
16
35
  finished: listed.finished_at,
17
36
  outcome: listed.outcome,
37
+ waiting: listed.waiting,
38
+ newest: listed.newest === null ? null : cursorPosition(listed.newest),
39
+ processEnded: listed.process_ended_at,
18
40
  };
19
41
  }
20
42
  function position(value) {
21
- if (!/^(?:0|[1-9][0-9]*)$/.test(value))
43
+ if (!DECIMAL.test(value))
22
44
  return null;
23
45
  return BigInt(value);
24
46
  }
25
- function assembled(entries) {
26
- const whole = [];
27
- let open = null;
47
+ function* changed(tracked, entries, version, schemas) {
48
+ const answer = answering();
28
49
  for (const entry of entries) {
29
- if (entry.kind === 'piece') {
30
- if (open === null || open.entry.path !== entry.path || entry.text === undefined)
31
- return null;
32
- open.texts.push(entry.text);
50
+ yield ROW_WEIGHT + (entry.text?.length ?? 0);
51
+ if (entry.kind === 'folder') {
52
+ folded(answer, entry.path, entry.removed === true);
53
+ continue;
33
54
  }
34
- else {
35
- if (open !== null)
36
- return null;
37
- if (entry.continued !== true) {
38
- whole.push(entry);
39
- continue;
40
- }
41
- if (entry.text === undefined)
42
- return null;
43
- open = { entry, texts: [entry.text] };
55
+ if (entry.path === tracked.schema)
56
+ schemas.set(entry.path, entry);
57
+ forgotten(answer, entry.path);
58
+ if (entry.space !== undefined) {
59
+ answer.removals.add(entry.path);
60
+ continue;
44
61
  }
45
- if (entry.continued === true)
62
+ if (entry.kind === 'removed') {
63
+ answer.removals.add(entry.path);
46
64
  continue;
47
- const { continued: _continued, ...head } = open.entry;
48
- whole.push({ ...head, text: open.texts.join('') });
49
- open = null;
50
- }
51
- return open === null ? whole : null;
52
- }
53
- async function verified(entries) {
54
- try {
55
- for (const entry of entries) {
56
- if (entry.text === undefined || entry.sha256 === undefined)
57
- continue;
58
- if ((await sha256Hex(entry.text)) !== entry.sha256)
59
- return false;
60
65
  }
61
- return true;
62
- }
63
- catch {
64
- return false;
66
+ const binary = binaryFileOf(entry);
67
+ if (binary === undefined)
68
+ answer.documents.set(entry.path, entry.text ?? '');
69
+ else if (binary !== null)
70
+ answer.binaryFiles.set(entry.path, binary);
71
+ if (binary === undefined && entry.facts !== undefined)
72
+ answer.facts.set(entry.path, entry.facts);
73
+ const revision = entry.revision ?? binary?.sha256;
74
+ if (revision !== undefined)
75
+ revised(answer, entry.path, revision);
76
+ const head = headChange(entry);
77
+ if (head)
78
+ answer.changes.set(entry.path, head);
79
+ const created = createdOf(entry);
80
+ if (created)
81
+ answer.created.set(entry.path, created);
65
82
  }
83
+ return { kind: 'net-advancement', roomVersion: version, ...(yield* answered(answer)) };
66
84
  }
67
- function resumed(kept, frame) {
68
- const last = frame.entries.at(-1);
69
- const point = { parts: frame.index + 1, from: frame.from, target: frame.target };
70
- if (last?.continued !== true)
71
- return { ...point, handled: frame.handled, piece: null };
72
- const text = last.text?.length ?? 0;
73
- if (last.kind !== 'piece') {
74
- return last.sha256 === undefined
75
- ? kept.resume
76
- : { ...point, handled: frame.handled, piece: { sha256: last.sha256, offset: text } };
77
- }
78
- const piece = kept.resume?.piece ?? null;
79
- if (piece === null)
80
- return kept.resume;
81
- const pieces = frame.entries.reduce((sum, entry) => sum + (entry.kind === 'piece' ? (entry.text?.length ?? 0) : 0), 0);
82
- return {
83
- ...point,
84
- handled: frame.handled,
85
- piece: { sha256: piece.sha256, offset: piece.offset + pieces },
86
- };
85
+ async function changePage(tracked, entries, version, mode, spaces, whole) {
86
+ const schemas = new Map();
87
+ const page = await stepped(changed(tracked, entries, version, schemas));
88
+ const models = await streamedModels(schemas, tracked, mode, spaces, whole);
89
+ return models === undefined ? page : { ...page, models };
87
90
  }
88
- export function houseNotifications(transport, mode) {
91
+ export function houseNotifications(transport, mode, discovering, spaces) {
89
92
  const stream = transport.stream;
90
- const lines = new Map();
91
- const kept = new Map();
92
- let receipts = null;
93
- let loaded = Promise.resolve();
94
- let acknowledging = null;
95
- let receiving = Promise.resolve();
96
- let verifying = 0;
97
- const locators = new Map();
93
+ const staged = new Map();
94
+ let socket = null;
95
+ const tracking = new Map();
98
96
  const observers = new Set();
97
+ const replaying = new Map();
99
98
  const conversationObservers = new Set();
100
99
  const modeObservers = new Set();
101
100
  let replicated = [];
101
+ let listedCursor = null;
102
+ const unanswered = [];
103
+ const heldAtZero = new Set();
102
104
  const live = new Set();
103
105
  let opening = null;
106
+ let announced = null;
104
107
  const runtime = crypto.randomUUID();
105
108
  function emit(event) {
106
- for (const observer of observers)
107
- observer(event);
109
+ for (const observer of observers) {
110
+ const held = replaying.get(observer);
111
+ if (held === undefined)
112
+ observer(event);
113
+ else
114
+ held.push(event);
115
+ }
108
116
  }
109
117
  function emitConversation(event) {
110
118
  if (event.kind === 'turn' ||
111
119
  event.kind === 'questions' ||
112
120
  event.kind === 'process' ||
113
121
  event.kind === 'commands' ||
114
- event.kind === 'options') {
122
+ event.kind === 'options' ||
123
+ event.kind === 'starting' ||
124
+ event.kind === 'subagents' ||
125
+ event.kind === 'link' ||
126
+ event.kind === 'deliveries') {
115
127
  live.add(event.conversation);
116
128
  }
117
129
  for (const observer of conversationObservers)
@@ -119,12 +131,33 @@ export function houseNotifications(transport, mode) {
119
131
  }
120
132
  function reopened(next) {
121
133
  opening = next;
122
- acknowledging = next;
134
+ announced = null;
135
+ socket = next;
136
+ staged.clear();
137
+ unanswered.length = 0;
138
+ heldAtZero.clear();
123
139
  const gapped = new Set([...replicated, ...live]);
124
140
  live.clear();
125
141
  for (const conversation of gapped)
126
142
  emitConversation({ kind: 'gap', conversation });
127
143
  emit({ kind: 'opened' });
144
+ if (mode === 'site')
145
+ announce(next, null);
146
+ }
147
+ function announce(via, frame) {
148
+ void discovering.opened(frame).then((discovery) => {
149
+ if (socket !== via)
150
+ return;
151
+ if (discovery === null) {
152
+ via.close();
153
+ return;
154
+ }
155
+ announced = { kind: 'discovered', discovery };
156
+ emit(announced);
157
+ }, () => {
158
+ if (socket === via)
159
+ via.close();
160
+ });
128
161
  }
129
162
  function converse(frame) {
130
163
  const conversation = frame.conversation_id;
@@ -143,6 +176,7 @@ export function houseNotifications(transport, mode) {
143
176
  return;
144
177
  emitConversation({ kind: 'commands', conversation, commands: [] });
145
178
  emitConversation({ kind: 'options', conversation, options: [] });
179
+ emitConversation({ kind: 'subagents', conversation, subagents: [] });
146
180
  return;
147
181
  }
148
182
  if (frame.type === 'commands') {
@@ -157,6 +191,26 @@ export function houseNotifications(transport, mode) {
157
191
  emitConversation({ kind: 'options', conversation, options: structuredClone(frame.options) });
158
192
  return;
159
193
  }
194
+ if (frame.type === 'starting') {
195
+ emitConversation({ kind: 'starting', conversation, step: frame.step });
196
+ return;
197
+ }
198
+ if (frame.type === 'subagents') {
199
+ emitConversation({
200
+ kind: 'subagents',
201
+ conversation,
202
+ subagents: structuredClone(frame.subagents),
203
+ });
204
+ return;
205
+ }
206
+ if (frame.type === 'link') {
207
+ emitConversation({ kind: 'link', conversation, connected: frame.connected });
208
+ return;
209
+ }
210
+ if (frame.type === 'deliveries') {
211
+ emitConversation({ kind: 'deliveries', conversation, unknown: [...frame.unknown] });
212
+ return;
213
+ }
160
214
  if (frame.type === 'interactions') {
161
215
  emitConversation({
162
216
  kind: 'questions',
@@ -170,171 +224,31 @@ export function houseNotifications(transport, mode) {
170
224
  return;
171
225
  emitConversation({ kind: 'accepted-message', conversation, message });
172
226
  }
173
- function announce(authority, at) {
174
- const version = roomVersion(at);
175
- for (const locator of locators.get(authority) ?? []) {
176
- emit({ kind: 'room-version', locator, roomVersion: version });
177
- }
178
- }
179
- function interrupt(authority) {
180
- const line = lines.get(authority);
181
- if (line !== undefined)
182
- lines.set(authority, { ...line, frames: [] });
183
- }
184
- async function forget(carries) {
185
- const held = carries.filter((carry) => kept.delete(carry));
186
- if (held.length > 0)
187
- await receipts?.drop(held).catch(() => { });
188
- }
189
- async function lost(authority, carry) {
190
- interrupt(authority);
191
- await forget([carry]);
192
- }
193
- async function stage(frame, via) {
194
- await loaded;
195
- const port = receipts;
196
- if (port === null)
197
- return;
198
- const held = kept.get(frame.carry);
199
- if (held === undefined && frame.index !== 0) {
200
- interrupt(frame.authority);
201
- return;
202
- }
203
- if (held !== undefined &&
204
- (held.fenced !== null || frame.index > held.parts || frame.index < (held.resume?.parts ?? 0))) {
205
- await lost(frame.authority, frame.carry);
227
+ function stage(frame, via) {
228
+ const held = staged.get(frame.carry);
229
+ if (frame.index !== (held?.parts ?? 0)) {
230
+ staged.delete(frame.carry);
206
231
  return;
207
232
  }
208
233
  if (held === undefined) {
209
- await forget([...kept.values()]
210
- .filter((other) => other.authority === frame.authority && other.fenced === null)
211
- .map((other) => other.carry));
212
- }
213
- const start = held ?? {
214
- authority: frame.authority,
215
- carry: frame.carry,
216
- logEpoch: lines.get(frame.authority)?.logEpoch ?? null,
217
- after: frame.after,
218
- parts: 0,
219
- resume: null,
220
- fenced: null,
221
- };
222
- const next = { ...start, parts: frame.index + 1, resume: resumed(start, frame) };
223
- const part = { entries: frame.entries };
224
- try {
225
- await port.keep(frame.carry, next, { index: frame.index, value: part });
226
- }
227
- catch {
228
- await lost(frame.authority, frame.carry);
229
- return;
234
+ for (const [carry, other] of staged) {
235
+ if (other.authority === frame.authority)
236
+ staged.delete(carry);
237
+ }
230
238
  }
231
- kept.set(frame.carry, next);
239
+ const next = held ?? { authority: frame.authority, parts: 0, entries: [] };
240
+ keptSpaceSchemas(frame.entries, spaces);
241
+ next.entries.push(...frame.entries);
242
+ next.parts += 1;
243
+ staged.set(frame.carry, next);
232
244
  via?.received(frame.authority, frame.carry, frame.index);
233
245
  }
234
- async function keptEntries(carry, parts) {
235
- if (parts === 0)
236
- return [];
237
- if (receipts === null)
238
- return null;
239
- const read = (await receipts.parts(carry, parts));
240
- if (read.length !== parts)
241
- return null;
242
- const entries = assembled(read.flatMap((part) => part.entries));
243
- return entries !== null && (await verified(entries)) ? entries : null;
244
- }
245
- async function carried(frame) {
246
- await loaded;
247
- const held = kept.get(frame.carry);
248
- if (frame.parts > 0 && held?.parts !== frame.parts) {
249
- await lost(frame.authority, frame.carry);
250
- return;
251
- }
252
- const entries = await keptEntries(frame.carry, frame.parts).catch(() => null);
253
- if (entries === null) {
254
- await lost(frame.authority, frame.carry);
255
- return;
256
- }
257
- const fenced = {
258
- ...(held ?? {
259
- authority: frame.authority,
260
- carry: frame.carry,
261
- logEpoch: frame.log_epoch,
262
- after: frame.after,
263
- parts: 0,
264
- resume: null,
265
- }),
266
- after: frame.after,
267
- fenced: { position: frame.position, logEpoch: frame.log_epoch },
268
- };
269
- try {
270
- await receipts?.keep(frame.carry, fenced);
271
- }
272
- catch {
273
- await lost(frame.authority, frame.carry);
246
+ function carried(frame) {
247
+ const held = staged.get(frame.carry);
248
+ staged.delete(frame.carry);
249
+ if ((held?.parts ?? 0) !== frame.parts)
274
250
  return;
275
- }
276
- kept.set(frame.carry, fenced);
277
- publish(frame, entries);
278
- }
279
- async function load(port) {
280
- try {
281
- for (const { key, head } of await port.heads())
282
- kept.set(key, head);
283
- }
284
- catch {
285
- kept.clear();
286
- }
287
- }
288
- async function restored(room) {
289
- const line = lines.get(room.authority);
290
- let at = room.position;
291
- const chain = [];
292
- for (;;) {
293
- const next = [...kept.values()].find((carry) => carry.authority === room.authority &&
294
- carry.after === at &&
295
- carry.fenced?.logEpoch === room.log_epoch);
296
- if (next?.fenced == null)
297
- break;
298
- const entries = await keptEntries(next.carry, next.parts).catch(() => null);
299
- if (entries === null)
300
- break;
301
- chain.push({ carry: next, entries });
302
- at = next.fenced.position;
303
- }
304
- const partial = [...kept.values()].find((carry) => carry.authority === room.authority &&
305
- carry.fenced === null &&
306
- carry.logEpoch === room.log_epoch &&
307
- carry.after === at &&
308
- carry.resume !== null &&
309
- carry.resume.parts > 0);
310
- const stale = [...kept.values()].filter((carry) => carry.authority === room.authority &&
311
- carry !== partial &&
312
- !chain.some((link) => link.carry === carry));
313
- await forget(stale.map((carry) => carry.carry));
314
- if (line !== undefined && line.logEpoch === room.log_epoch && chain.length > 0) {
315
- const frames = chain.map(({ carry, entries }) => ({
316
- after: BigInt(carry.after),
317
- position: BigInt(carry.fenced.position),
318
- entries,
319
- }));
320
- lines.set(room.authority, { ...line, frames: [...line.frames, ...frames] });
321
- }
322
- return {
323
- ...room,
324
- position: at,
325
- ...(partial?.resume == null
326
- ? {}
327
- : {
328
- carry: {
329
- id: partial.carry,
330
- parts: partial.resume.parts,
331
- from: partial.resume.from,
332
- target: partial.resume.target,
333
- handled: partial.resume.handled,
334
- ...(partial.resume.piece === null ? {} : { piece: partial.resume.piece }),
335
- },
336
- }),
337
- };
251
+ publish(frame, held?.entries ?? []);
338
252
  }
339
253
  function handle(frame, via) {
340
254
  if (frame.type === 'signal') {
@@ -346,14 +260,35 @@ export function houseNotifications(transport, mode) {
346
260
  return;
347
261
  }
348
262
  if (frame.type === 'opening') {
263
+ if (via !== null && mode === 'credential')
264
+ announce(via, frame);
265
+ return;
266
+ }
267
+ if (frame.type === 'caught_up') {
268
+ discovering.expired?.(frame.expired);
349
269
  emit({
350
- kind: 'opening',
351
- expired: frame.expired.flatMap((authority) => locators.get(authority) ?? []),
270
+ kind: 'caught-up',
271
+ expired: [
272
+ ...frame.expired.flatMap((authority) => (tracking.get(authority) ?? []).map((tracked) => tracked.locator)),
273
+ ...(unanswered.shift() ?? []),
274
+ ],
352
275
  });
353
276
  return;
354
277
  }
278
+ if (frame.type === 'authority_changed') {
279
+ const change = discovering.changed(frame);
280
+ if (change !== null)
281
+ emit(change);
282
+ return;
283
+ }
355
284
  if (frame.type === 'conversations') {
356
- emitConversation({ kind: 'list', entries: frame.conversations.map(listedConversation) });
285
+ emitConversation({
286
+ kind: 'list',
287
+ entries: frame.conversations.map(listedConversation),
288
+ whole: frame.whole,
289
+ more: frame.more,
290
+ cursor: frame.cursor,
291
+ });
357
292
  return;
358
293
  }
359
294
  if (frame.type === 'conversation') {
@@ -368,63 +303,49 @@ export function houseNotifications(transport, mode) {
368
303
  frame.type === 'process' ||
369
304
  frame.type === 'commands' ||
370
305
  frame.type === 'options' ||
306
+ frame.type === 'starting' ||
307
+ frame.type === 'subagents' ||
308
+ frame.type === 'link' ||
309
+ frame.type === 'deliveries' ||
371
310
  frame.type === 'interactions' ||
372
311
  frame.type === 'message') {
373
312
  converse(frame);
374
313
  return;
375
314
  }
376
315
  if (frame.type === 'part')
377
- return stage(frame, via);
378
- if (frame.type === 'carried')
379
- return carried(frame);
316
+ stage(frame, via);
317
+ else if (frame.type === 'carried')
318
+ carried(frame);
380
319
  }
381
320
  function publish(frame, entries) {
382
321
  const at = position(frame.position);
383
322
  const after = position(frame.after);
384
- if (at === null || after === null || after >= at) {
385
- interrupt(frame.authority);
323
+ if (at === null || after === null || after >= at)
386
324
  return;
387
- }
388
- const line = lines.get(frame.authority);
389
- if (line?.logEpoch === frame.log_epoch) {
390
- if (line.held !== null && at <= line.held)
391
- return;
392
- const last = line.frames.at(-1);
393
- if (last !== undefined && at <= last.position)
394
- return;
395
- const held = { after, position: at, entries };
396
- const next = last !== undefined && after <= last.position ? [...line.frames, held] : [held];
397
- lines.set(frame.authority, { ...line, frames: next.length > HELD_FRAMES_MAX ? [] : next });
398
- }
399
- else {
400
- interrupt(frame.authority);
401
- }
402
- announce(frame.authority, { position: frame.position, logEpoch: frame.log_epoch });
403
- }
404
- stream.observe((frame) => {
405
- const via = acknowledging;
406
- if (verifying === 0) {
407
- const handled = handle(frame, via);
408
- if (handled === undefined)
409
- return;
410
- verifying += 1;
411
- receiving = handled.finally(() => {
412
- verifying -= 1;
325
+ const from = after === 0n && !heldAtZero.has(frame.authority)
326
+ ? null
327
+ : roomVersion({ position: frame.after, logEpoch: frame.log_epoch });
328
+ const version = roomVersion({ position: frame.position, logEpoch: frame.log_epoch });
329
+ for (const tracked of tracking.get(frame.authority) ?? []) {
330
+ emit({
331
+ kind: 'room-change',
332
+ locator: tracked.locator,
333
+ from,
334
+ roomVersion: version,
335
+ read: () => changePage(tracked, entries, version, mode, spaces, from === null),
413
336
  });
414
- return;
415
337
  }
416
- verifying += 1;
417
- receiving = receiving
418
- .then(() => handle(frame, via))
419
- .finally(() => {
420
- verifying -= 1;
421
- });
422
- });
338
+ }
339
+ stream.observe((frame) => handle(frame, socket));
423
340
  stream.opened(reopened);
424
- stream.closed(({ revoked, lost }) => {
341
+ stream.closed(({ revoked, lost, authorityChanged }) => {
425
342
  opening = null;
426
- acknowledging = null;
427
- emit({ kind: 'closed', revoked, lost });
343
+ announced = null;
344
+ socket = null;
345
+ staged.clear();
346
+ unanswered.length = 0;
347
+ heldAtZero.clear();
348
+ emit({ kind: 'closed', revoked, lost, authorityChanged });
428
349
  });
429
350
  return {
430
351
  runtime,
@@ -440,175 +361,94 @@ export function houseNotifications(transport, mode) {
440
361
  replicate(next) {
441
362
  replicated = [...next];
442
363
  },
364
+ hold(cursor) {
365
+ listedCursor = cursor;
366
+ },
443
367
  observe(notify) {
444
368
  conversationObservers.add(notify);
445
369
  return () => {
446
370
  conversationObservers.delete(notify);
447
371
  };
448
372
  },
373
+ ...(mode === 'credential'
374
+ ? {
375
+ agents: () => houseAgents(transport),
376
+ history: (request) => houseHistory(transport, request),
377
+ }
378
+ : {}),
449
379
  },
450
- observe(notify, port) {
380
+ observe(notify) {
451
381
  observers.add(notify);
452
- if (receipts === null) {
453
- receipts = port;
454
- loaded = load(port);
455
- }
456
382
  if (opening !== null) {
383
+ const replayed = [{ kind: 'opened' }];
384
+ if (announced !== null)
385
+ replayed.push(announced);
386
+ replaying.set(notify, replayed);
457
387
  queueMicrotask(() => {
458
- if (observers.has(notify))
459
- notify({ kind: 'opened' });
388
+ replaying.delete(notify);
389
+ for (const event of replayed)
390
+ if (observers.has(notify))
391
+ notify(event);
460
392
  });
461
393
  }
462
394
  return () => {
463
395
  observers.delete(notify);
464
396
  };
465
397
  },
466
- send(rooms, demand) {
467
- for (const room of rooms) {
468
- const line = lines.get(room.authority);
469
- const held = position(room.position);
470
- if (line === undefined || held === null || line.logEpoch !== room.log_epoch)
471
- continue;
472
- lines.set(room.authority, { ...line, frames: [], held });
473
- }
474
- const current = opening;
398
+ send(rooms) {
475
399
  opening = null;
476
- if (current === null)
400
+ announced = null;
401
+ if (socket === null)
477
402
  return;
478
- void loaded
479
- .then(() => Promise.all(rooms.map(restored)))
480
- .then((presented) => {
481
- current.hold(presented, runtime, demand);
482
- for (const [index, room] of presented.entries()) {
483
- if (room.position === rooms[index].position)
484
- continue;
485
- announce(room.authority, { position: room.position, logEpoch: room.log_epoch });
486
- }
403
+ const entries = new Map([...tracking.values()].flat().map((tracked) => [tracked.locator, tracked]));
404
+ const named = rooms.flatMap((room) => {
405
+ const entry = entries.get(room.locator);
406
+ const at = room.roomVersion === null ? null : logPosition(room.roomVersion);
407
+ return entry === undefined || at === null ? [] : [{ entry, at }];
487
408
  });
488
- },
489
- published(locator, at) {
490
- const authority = [...locators].find(([, held]) => held.includes(locator))?.[0];
491
- const reached = position(at.position);
492
- if (authority === undefined || reached === null)
409
+ const unheld = rooms.flatMap((room) => {
410
+ const entry = entries.get(room.locator);
411
+ return entry === undefined || room.roomVersion !== null ? [] : [entry];
412
+ });
413
+ for (const { entry, at } of named) {
414
+ if (at.position === '0')
415
+ heldAtZero.add(entry.authority);
416
+ else
417
+ heldAtZero.delete(entry.authority);
418
+ }
419
+ for (const entry of unheld)
420
+ heldAtZero.delete(entry.authority);
421
+ const site = mode === 'site';
422
+ unanswered.push(site ? unheld.map((entry) => entry.locator) : []);
423
+ socket.hold([
424
+ ...named.map(({ entry, at }) => presented(entry.authority, at)),
425
+ ...(site ? [] : unheld).map((entry) => presented(entry.authority, { position: '0', logEpoch: entry.logEpoch })),
426
+ ], runtime, listedCursor);
427
+ if (site)
493
428
  return;
494
- void forget([...kept.values()]
495
- .filter((carry) => carry.authority === authority &&
496
- carry.fenced !== null &&
497
- carry.fenced.logEpoch === at.logEpoch &&
498
- BigInt(carry.fenced.position) <= reached)
499
- .map((carry) => carry.carry));
500
- },
501
- reopen() {
502
- const current = opening;
503
- opening = null;
504
- current?.close();
429
+ const made = unheld.filter((entry) => entry.position === '0');
430
+ queueMicrotask(() => {
431
+ for (const entry of made) {
432
+ const version = roomVersion(entry);
433
+ emit({
434
+ kind: 'room-change',
435
+ locator: entry.locator,
436
+ from: null,
437
+ roomVersion: version,
438
+ read: () => changePage(entry, [], version, mode, spaces, true),
439
+ });
440
+ }
441
+ });
505
442
  },
506
443
  track(entries) {
507
- locators.clear();
444
+ tracking.clear();
508
445
  for (const entry of entries) {
509
- locators.set(entry.authority, [...(locators.get(entry.authority) ?? []), entry.locator]);
510
- if (lines.get(entry.authority)?.logEpoch !== entry.logEpoch) {
511
- lines.set(entry.authority, { logEpoch: entry.logEpoch, frames: [], held: null });
512
- }
446
+ tracking.set(entry.authority, [...(tracking.get(entry.authority) ?? []), entry]);
513
447
  }
514
- for (const authority of lines.keys()) {
515
- if (!locators.has(authority))
516
- lines.delete(authority);
517
- }
518
- void loaded.then(() => forget([...kept.values()]
519
- .filter((carry) => !locators.has(carry.authority))
520
- .map((carry) => carry.carry)));
521
- },
522
- reach(authority, at) {
523
- const line = lines.get(authority);
524
- const held = position(at.position);
525
- if (line === undefined || held === null || line.logEpoch !== at.logEpoch)
526
- return;
527
- if (line.held !== null && held <= line.held)
528
- return;
529
- lines.set(authority, {
530
- ...line,
531
- frames: line.frames.filter((frame) => frame.position > held),
532
- held,
533
- });
534
- },
535
- advance(tracked, from, holds) {
536
- const line = lines.get(tracked.authority);
537
- const start = position(from.position);
538
- if (line === undefined || start === null || line.logEpoch !== from.logEpoch)
539
- return null;
540
- const pending = line.frames.filter((frame) => frame.position > start);
541
- const first = pending[0];
542
- if (first === undefined || first.after > start)
543
- return null;
544
- const documents = {};
545
- const revisions = {};
546
- const changes = {};
547
- const binaryFiles = {};
548
- const removals = [];
549
- const applied = [];
550
- const schemas = new Map();
551
- const forget = (path) => {
552
- delete documents[path];
553
- delete revisions[path];
554
- delete changes[path];
555
- delete binaryFiles[path];
556
- const removed = removals.indexOf(path);
557
- if (removed !== -1)
558
- removals.splice(removed, 1);
559
- };
560
- for (const frame of pending) {
561
- for (const entry of frame.entries) {
562
- if (entry.kind === 'edited' &&
563
- entry.revision !== undefined &&
564
- holds?.(entry.path, entry.revision) === true) {
565
- forget(entry.path);
566
- continue;
567
- }
568
- applied.push(entry);
569
- if (tracked.schemas.includes(entry.path))
570
- schemas.set(entry.path, entry);
571
- if (entry.kind === 'removed') {
572
- forget(entry.path);
573
- removals.push(entry.path);
574
- continue;
575
- }
576
- const binary = binaryFileOf(entry);
577
- if (binary === null || (binary === undefined && entry.text === undefined))
578
- return null;
579
- if (mode === 'credential' && entry.revision === undefined)
580
- return null;
581
- const head = headChange(entry);
582
- if (head === null)
583
- return null;
584
- forget(entry.path);
585
- if (binary === undefined)
586
- documents[entry.path] = entry.text;
587
- else
588
- binaryFiles[entry.path] = binary;
589
- const revision = entry.revision ?? binary?.sha256;
590
- if (revision !== undefined)
591
- revisions[entry.path] = revision;
592
- if (head !== undefined)
593
- changes[entry.path] = head;
594
- }
448
+ for (const [carry, held] of staged) {
449
+ if (!tracking.has(held.authority))
450
+ staged.delete(carry);
595
451
  }
596
- const last = pending.at(-1);
597
- lines.set(tracked.authority, { ...line, frames: pending, held: last.position });
598
- return {
599
- page: {
600
- kind: 'net-advancement',
601
- roomVersion: roomVersion({ position: last.position.toString(), logEpoch: line.logEpoch }),
602
- documents,
603
- revisions,
604
- ...identities(revisions),
605
- changes,
606
- binaryFiles,
607
- removals: removals.sort(),
608
- },
609
- entries: applied,
610
- schemas,
611
- };
612
452
  },
613
453
  };
614
454
  }