@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
package/dist/stream.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { ROW_WEIGHT, } from '@agentshouse/mdruntime';
1
2
  import { acceptedMessage } from './actions.js';
2
- import { headChange } from './read.js';
3
+ import { sha256Hex } from './digest.js';
4
+ import { answered, answering, binaryFileOf, folded, forgotten, headChange, revised, } from './read.js';
3
5
  import { roomVersion } from './version.js';
4
6
  const HELD_FRAMES_MAX = 256;
5
7
  function turnOperation(frame) {
@@ -14,6 +16,7 @@ function listedConversation(listed) {
14
16
  created: listed.created_at,
15
17
  finished: listed.finished_at,
16
18
  outcome: listed.outcome,
19
+ waiting: listed.waiting,
17
20
  };
18
21
  }
19
22
  function position(value) {
@@ -21,9 +24,78 @@ function position(value) {
21
24
  return null;
22
25
  return BigInt(value);
23
26
  }
27
+ function assembled(entries) {
28
+ const whole = [];
29
+ let open = null;
30
+ for (const entry of entries) {
31
+ if (entry.kind === 'piece') {
32
+ if (open === null || open.entry.path !== entry.path || entry.text === undefined)
33
+ return null;
34
+ open.texts.push(entry.text);
35
+ }
36
+ else {
37
+ if (open !== null)
38
+ return null;
39
+ if (entry.continued !== true) {
40
+ whole.push(entry);
41
+ continue;
42
+ }
43
+ if (entry.text === undefined)
44
+ return null;
45
+ open = { entry, texts: [entry.text] };
46
+ }
47
+ if (entry.continued === true)
48
+ continue;
49
+ const { continued: _continued, ...head } = open.entry;
50
+ whole.push({ ...head, text: open.texts.join('') });
51
+ open = null;
52
+ }
53
+ return open === null ? whole : null;
54
+ }
55
+ async function verified(entries) {
56
+ try {
57
+ for (const entry of entries) {
58
+ if (entry.text === undefined || entry.sha256 === undefined)
59
+ continue;
60
+ if ((await sha256Hex(entry.text)) !== entry.sha256)
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ function resumed(kept, frame) {
70
+ const last = frame.entries.at(-1);
71
+ const point = { parts: frame.index + 1, from: frame.from, target: frame.target };
72
+ if (last?.continued !== true)
73
+ return { ...point, handled: frame.handled, piece: null };
74
+ const text = last.text?.length ?? 0;
75
+ if (last.kind !== 'piece') {
76
+ return last.sha256 === undefined
77
+ ? kept.resume
78
+ : { ...point, handled: frame.handled, piece: { sha256: last.sha256, offset: text } };
79
+ }
80
+ const piece = kept.resume?.piece ?? null;
81
+ if (piece === null)
82
+ return kept.resume;
83
+ const pieces = frame.entries.reduce((sum, entry) => sum + (entry.kind === 'piece' ? (entry.text?.length ?? 0) : 0), 0);
84
+ return {
85
+ ...point,
86
+ handled: frame.handled,
87
+ piece: { sha256: piece.sha256, offset: piece.offset + pieces },
88
+ };
89
+ }
24
90
  export function houseNotifications(transport, mode) {
25
91
  const stream = transport.stream;
26
92
  const lines = new Map();
93
+ const kept = new Map();
94
+ let receipts = null;
95
+ let loaded = Promise.resolve();
96
+ let acknowledging = null;
97
+ let receiving = Promise.resolve();
98
+ let verifying = 0;
27
99
  const locators = new Map();
28
100
  const observers = new Set();
29
101
  const conversationObservers = new Set();
@@ -41,7 +113,10 @@ export function houseNotifications(transport, mode) {
41
113
  event.kind === 'questions' ||
42
114
  event.kind === 'process' ||
43
115
  event.kind === 'commands' ||
44
- event.kind === 'options') {
116
+ event.kind === 'options' ||
117
+ event.kind === 'starting' ||
118
+ event.kind === 'link' ||
119
+ event.kind === 'deliveries') {
45
120
  live.add(event.conversation);
46
121
  }
47
122
  for (const observer of conversationObservers)
@@ -49,6 +124,7 @@ export function houseNotifications(transport, mode) {
49
124
  }
50
125
  function reopened(next) {
51
126
  opening = next;
127
+ acknowledging = next;
52
128
  const gapped = new Set([...replicated, ...live]);
53
129
  live.clear();
54
130
  for (const conversation of gapped)
@@ -86,6 +162,18 @@ export function houseNotifications(transport, mode) {
86
162
  emitConversation({ kind: 'options', conversation, options: structuredClone(frame.options) });
87
163
  return;
88
164
  }
165
+ if (frame.type === 'starting') {
166
+ emitConversation({ kind: 'starting', conversation, step: frame.step });
167
+ return;
168
+ }
169
+ if (frame.type === 'link') {
170
+ emitConversation({ kind: 'link', conversation, connected: frame.connected });
171
+ return;
172
+ }
173
+ if (frame.type === 'deliveries') {
174
+ emitConversation({ kind: 'deliveries', conversation, unknown: [...frame.unknown] });
175
+ return;
176
+ }
89
177
  if (frame.type === 'interactions') {
90
178
  emitConversation({
91
179
  kind: 'questions',
@@ -110,7 +198,162 @@ export function houseNotifications(transport, mode) {
110
198
  if (line !== undefined)
111
199
  lines.set(authority, { ...line, frames: [] });
112
200
  }
113
- function receive(frame) {
201
+ async function forget(carries) {
202
+ const held = carries.filter((carry) => kept.delete(carry));
203
+ if (held.length > 0)
204
+ await receipts?.drop(held).catch(() => { });
205
+ }
206
+ async function lost(authority, carry) {
207
+ interrupt(authority);
208
+ await forget([carry]);
209
+ }
210
+ async function stage(frame, via) {
211
+ await loaded;
212
+ const port = receipts;
213
+ if (port === null)
214
+ return;
215
+ const held = kept.get(frame.carry);
216
+ if (held === undefined && frame.index !== 0) {
217
+ interrupt(frame.authority);
218
+ return;
219
+ }
220
+ if (held !== undefined &&
221
+ (held.fenced !== null || frame.index > held.parts || frame.index < (held.resume?.parts ?? 0))) {
222
+ await lost(frame.authority, frame.carry);
223
+ return;
224
+ }
225
+ if (held === undefined) {
226
+ await forget([...kept.values()]
227
+ .filter((other) => other.authority === frame.authority && other.fenced === null)
228
+ .map((other) => other.carry));
229
+ }
230
+ const start = held ?? {
231
+ authority: frame.authority,
232
+ carry: frame.carry,
233
+ logEpoch: lines.get(frame.authority)?.logEpoch ?? null,
234
+ after: frame.after,
235
+ parts: 0,
236
+ resume: null,
237
+ fenced: null,
238
+ };
239
+ const next = { ...start, parts: frame.index + 1, resume: resumed(start, frame) };
240
+ const part = { entries: frame.entries };
241
+ try {
242
+ await port.keep(frame.carry, next, { index: frame.index, value: part });
243
+ }
244
+ catch {
245
+ await lost(frame.authority, frame.carry);
246
+ return;
247
+ }
248
+ kept.set(frame.carry, next);
249
+ via?.received(frame.authority, frame.carry, frame.index);
250
+ }
251
+ async function keptEntries(carry, parts) {
252
+ if (parts === 0)
253
+ return [];
254
+ if (receipts === null)
255
+ return null;
256
+ const read = (await receipts.parts(carry, parts));
257
+ if (read.length !== parts)
258
+ return null;
259
+ const entries = assembled(read.flatMap((part) => part.entries));
260
+ return entries !== null && (await verified(entries)) ? entries : null;
261
+ }
262
+ async function carried(frame) {
263
+ await loaded;
264
+ const held = kept.get(frame.carry);
265
+ if (frame.parts > 0 && held?.parts !== frame.parts) {
266
+ await lost(frame.authority, frame.carry);
267
+ return;
268
+ }
269
+ const entries = await keptEntries(frame.carry, frame.parts).catch(() => null);
270
+ if (entries === null) {
271
+ await lost(frame.authority, frame.carry);
272
+ return;
273
+ }
274
+ const fenced = {
275
+ ...(held ?? {
276
+ authority: frame.authority,
277
+ carry: frame.carry,
278
+ logEpoch: frame.log_epoch,
279
+ after: frame.after,
280
+ parts: 0,
281
+ resume: null,
282
+ }),
283
+ after: frame.after,
284
+ fenced: { position: frame.position, logEpoch: frame.log_epoch },
285
+ };
286
+ try {
287
+ await receipts?.keep(frame.carry, fenced);
288
+ }
289
+ catch {
290
+ await lost(frame.authority, frame.carry);
291
+ return;
292
+ }
293
+ kept.set(frame.carry, fenced);
294
+ publish(frame, entries);
295
+ }
296
+ async function load(port) {
297
+ try {
298
+ for (const { key, head } of await port.heads())
299
+ kept.set(key, head);
300
+ }
301
+ catch {
302
+ kept.clear();
303
+ }
304
+ }
305
+ async function restored(room) {
306
+ const line = lines.get(room.authority);
307
+ let at = room.position;
308
+ const chain = [];
309
+ for (;;) {
310
+ const next = [...kept.values()].find((carry) => carry.authority === room.authority &&
311
+ carry.after === at &&
312
+ carry.fenced?.logEpoch === room.log_epoch);
313
+ if (next?.fenced == null)
314
+ break;
315
+ const entries = await keptEntries(next.carry, next.parts).catch(() => null);
316
+ if (entries === null)
317
+ break;
318
+ chain.push({ carry: next, entries });
319
+ at = next.fenced.position;
320
+ }
321
+ const partial = [...kept.values()].find((carry) => carry.authority === room.authority &&
322
+ carry.fenced === null &&
323
+ carry.logEpoch === room.log_epoch &&
324
+ carry.after === at &&
325
+ carry.resume !== null &&
326
+ carry.resume.parts > 0);
327
+ const stale = [...kept.values()].filter((carry) => carry.authority === room.authority &&
328
+ carry !== partial &&
329
+ !chain.some((link) => link.carry === carry));
330
+ await forget(stale.map((carry) => carry.carry));
331
+ if (line !== undefined && line.logEpoch === room.log_epoch && chain.length > 0) {
332
+ const frames = chain.map(({ carry, entries }) => ({
333
+ after: BigInt(carry.after),
334
+ position: BigInt(carry.fenced.position),
335
+ entries,
336
+ }));
337
+ lines.set(room.authority, { ...line, frames: [...line.frames, ...frames] });
338
+ }
339
+ return {
340
+ ...room,
341
+ position: at,
342
+ ...(partial?.resume == null
343
+ ? {}
344
+ : {
345
+ carry: {
346
+ id: partial.carry,
347
+ parts: partial.resume.parts,
348
+ from: partial.resume.from,
349
+ target: partial.resume.target,
350
+ handled: partial.resume.handled,
351
+ ...(partial.resume.piece === null ? {} : { piece: partial.resume.piece }),
352
+ },
353
+ }),
354
+ };
355
+ }
356
+ function handle(frame, via) {
114
357
  if (frame.type === 'signal') {
115
358
  const mode = frame.service_mode;
116
359
  if (mode !== 'open' && mode !== 'write-halted')
@@ -126,6 +369,10 @@ export function houseNotifications(transport, mode) {
126
369
  });
127
370
  return;
128
371
  }
372
+ if (frame.type === 'authority_changed') {
373
+ emit({ kind: 'authority-changed', scope: { authorities: frame.rooms } });
374
+ return;
375
+ }
129
376
  if (frame.type === 'conversations') {
130
377
  emitConversation({ kind: 'list', entries: frame.conversations.map(listedConversation) });
131
378
  return;
@@ -142,13 +389,20 @@ export function houseNotifications(transport, mode) {
142
389
  frame.type === 'process' ||
143
390
  frame.type === 'commands' ||
144
391
  frame.type === 'options' ||
392
+ frame.type === 'starting' ||
393
+ frame.type === 'link' ||
394
+ frame.type === 'deliveries' ||
145
395
  frame.type === 'interactions' ||
146
396
  frame.type === 'message') {
147
397
  converse(frame);
148
398
  return;
149
399
  }
150
- if (frame.type !== 'entries')
151
- return;
400
+ if (frame.type === 'part')
401
+ return stage(frame, via);
402
+ if (frame.type === 'carried')
403
+ return carried(frame);
404
+ }
405
+ function publish(frame, entries) {
152
406
  const at = position(frame.position);
153
407
  const after = position(frame.after);
154
408
  if (at === null || after === null || after >= at) {
@@ -162,7 +416,7 @@ export function houseNotifications(transport, mode) {
162
416
  const last = line.frames.at(-1);
163
417
  if (last !== undefined && at <= last.position)
164
418
  return;
165
- const held = { after, position: at, entries: frame.entries };
419
+ const held = { after, position: at, entries };
166
420
  const next = last !== undefined && after <= last.position ? [...line.frames, held] : [held];
167
421
  lines.set(frame.authority, { ...line, frames: next.length > HELD_FRAMES_MAX ? [] : next });
168
422
  }
@@ -171,11 +425,30 @@ export function houseNotifications(transport, mode) {
171
425
  }
172
426
  announce(frame.authority, { position: frame.position, logEpoch: frame.log_epoch });
173
427
  }
174
- stream.observe(receive);
428
+ stream.observe((frame) => {
429
+ const via = acknowledging;
430
+ if (verifying === 0) {
431
+ const handled = handle(frame, via);
432
+ if (handled === undefined)
433
+ return;
434
+ verifying += 1;
435
+ receiving = handled.finally(() => {
436
+ verifying -= 1;
437
+ });
438
+ return;
439
+ }
440
+ verifying += 1;
441
+ receiving = receiving
442
+ .then(() => handle(frame, via))
443
+ .finally(() => {
444
+ verifying -= 1;
445
+ });
446
+ });
175
447
  stream.opened(reopened);
176
- stream.closed((revoked) => {
448
+ stream.closed(({ revoked, lost }) => {
177
449
  opening = null;
178
- emit({ kind: 'closed', revoked });
450
+ acknowledging = null;
451
+ emit({ kind: 'closed', revoked, lost });
179
452
  });
180
453
  return {
181
454
  runtime,
@@ -198,8 +471,12 @@ export function houseNotifications(transport, mode) {
198
471
  };
199
472
  },
200
473
  },
201
- observe(notify) {
474
+ observe(notify, port) {
202
475
  observers.add(notify);
476
+ if (receipts === null) {
477
+ receipts = port;
478
+ loaded = load(port);
479
+ }
203
480
  if (opening !== null) {
204
481
  queueMicrotask(() => {
205
482
  if (observers.has(notify))
@@ -220,7 +497,35 @@ export function houseNotifications(transport, mode) {
220
497
  }
221
498
  const current = opening;
222
499
  opening = null;
223
- current?.hold(rooms, runtime);
500
+ if (current === null)
501
+ return;
502
+ void loaded
503
+ .then(() => Promise.all(rooms.map(restored)))
504
+ .then((presented) => {
505
+ current.hold(presented, runtime);
506
+ for (const [index, room] of presented.entries()) {
507
+ if (room.position === rooms[index].position)
508
+ continue;
509
+ announce(room.authority, { position: room.position, logEpoch: room.log_epoch });
510
+ }
511
+ });
512
+ },
513
+ published(locator, at) {
514
+ const authority = [...locators].find(([, held]) => held.includes(locator))?.[0];
515
+ const reached = position(at.position);
516
+ if (authority === undefined || reached === null)
517
+ return;
518
+ void forget([...kept.values()]
519
+ .filter((carry) => carry.authority === authority &&
520
+ carry.fenced !== null &&
521
+ carry.fenced.logEpoch === at.logEpoch &&
522
+ BigInt(carry.fenced.position) <= reached)
523
+ .map((carry) => carry.carry));
524
+ },
525
+ reopen() {
526
+ const current = opening;
527
+ opening = null;
528
+ current?.close();
224
529
  },
225
530
  track(entries) {
226
531
  locators.clear();
@@ -234,6 +539,9 @@ export function houseNotifications(transport, mode) {
234
539
  if (!locators.has(authority))
235
540
  lines.delete(authority);
236
541
  }
542
+ void loaded.then(() => forget([...kept.values()]
543
+ .filter((carry) => !locators.has(carry.authority))
544
+ .map((carry) => carry.carry)));
237
545
  },
238
546
  reach(authority, at) {
239
547
  const line = lines.get(authority);
@@ -248,7 +556,18 @@ export function houseNotifications(transport, mode) {
248
556
  held,
249
557
  });
250
558
  },
251
- advance(tracked, from, holds) {
559
+ edited(tracked, from) {
560
+ const line = lines.get(tracked.authority);
561
+ const start = position(from.position);
562
+ if (line === undefined || start === null || line.logEpoch !== from.logEpoch)
563
+ return [];
564
+ return [
565
+ ...new Set(line.frames
566
+ .filter((frame) => frame.position > start)
567
+ .flatMap((frame) => frame.entries.flatMap((entry) => entry.kind === 'edited' && entry.revision !== undefined ? [entry.path] : []))),
568
+ ];
569
+ },
570
+ *advance(tracked, from, holds) {
252
571
  const line = lines.get(tracked.authority);
253
572
  const start = position(from.position);
254
573
  if (line === undefined || start === null || line.logEpoch !== from.logEpoch)
@@ -257,64 +576,63 @@ export function houseNotifications(transport, mode) {
257
576
  const first = pending[0];
258
577
  if (first === undefined || first.after > start)
259
578
  return null;
260
- const documents = {};
261
- const revisions = {};
262
- const changes = {};
263
- const removals = [];
264
- const applied = [];
579
+ const answer = answering();
265
580
  const schemas = new Map();
266
- const forget = (path) => {
267
- delete documents[path];
268
- delete revisions[path];
269
- delete changes[path];
270
- const removed = removals.indexOf(path);
271
- if (removed !== -1)
272
- removals.splice(removed, 1);
273
- };
274
581
  for (const frame of pending) {
275
582
  for (const entry of frame.entries) {
583
+ yield ROW_WEIGHT + (entry.text?.length ?? 0);
584
+ if (entry.kind === 'folder') {
585
+ folded(answer, entry.path, entry.removed === true);
586
+ continue;
587
+ }
276
588
  if (entry.kind === 'edited' &&
277
589
  entry.revision !== undefined &&
278
590
  holds?.(entry.path, entry.revision) === true) {
279
- forget(entry.path);
591
+ forgotten(answer, entry.path);
280
592
  continue;
281
593
  }
282
- applied.push(entry);
283
594
  if (tracked.schemas.includes(entry.path))
284
595
  schemas.set(entry.path, entry);
285
596
  if (entry.kind === 'removed') {
286
- forget(entry.path);
287
- removals.push(entry.path);
597
+ forgotten(answer, entry.path);
598
+ answer.removals.add(entry.path);
288
599
  continue;
289
600
  }
290
- if (entry.text === undefined)
601
+ const binary = binaryFileOf(entry);
602
+ if (binary === null || (binary === undefined && entry.text === undefined))
291
603
  return null;
292
604
  if (mode === 'credential' && entry.revision === undefined)
293
605
  return null;
294
606
  const head = headChange(entry);
295
607
  if (head === null)
296
608
  return null;
297
- forget(entry.path);
298
- documents[entry.path] = entry.text;
299
- if (entry.revision !== undefined)
300
- revisions[entry.path] = entry.revision;
609
+ forgotten(answer, entry.path);
610
+ if (binary === undefined)
611
+ answer.documents.set(entry.path, entry.text);
612
+ else
613
+ answer.binaryFiles.set(entry.path, binary);
614
+ const revision = entry.revision ?? binary?.sha256;
615
+ if (revision !== undefined)
616
+ revised(answer, entry.path, revision);
301
617
  if (head !== undefined)
302
- changes[entry.path] = head;
618
+ answer.changes.set(entry.path, head);
303
619
  }
304
620
  }
305
621
  const last = pending.at(-1);
306
- lines.set(tracked.authority, { ...line, frames: pending, held: last.position });
622
+ const current = lines.get(tracked.authority);
623
+ if (current?.logEpoch === line.logEpoch) {
624
+ lines.set(tracked.authority, {
625
+ ...current,
626
+ frames: current.frames.filter((frame) => frame.position > start),
627
+ held: current.held !== null && current.held > last.position ? current.held : last.position,
628
+ });
629
+ }
307
630
  return {
308
631
  page: {
309
632
  kind: 'net-advancement',
310
633
  roomVersion: roomVersion({ position: last.position.toString(), logEpoch: line.logEpoch }),
311
- documents,
312
- revisions,
313
- changes,
314
- binaryTargets: [],
315
- removals: removals.sort(),
634
+ ...(yield* answered(answer)),
316
635
  },
317
- entries: applied,
318
636
  schemas,
319
637
  };
320
638
  },