@agentshouse/mdruntime-adapter 0.1.0-ca764edcceec6 → 0.1.0-ca7fab027bb19

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