@agentshouse/mdruntime-adapter 0.1.0-cd59fd8945dcb → 0.1.0-cd5a35054e28c

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