@agentshouse/mdruntime 0.1.0-ca7c26c6bc27e → 0.1.0-ca92b54804c33

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 (182) hide show
  1. package/README.md +25 -12
  2. package/dist/adapter.d.ts +199 -54
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +1 -9
  5. package/dist/attached-model.d.ts +7 -10
  6. package/dist/attached-model.d.ts.map +1 -1
  7. package/dist/attached-model.js +40 -28
  8. package/dist/attempt.d.ts +16 -0
  9. package/dist/attempt.d.ts.map +1 -0
  10. package/dist/attempt.js +70 -0
  11. package/dist/binary.d.ts +18 -0
  12. package/dist/binary.d.ts.map +1 -0
  13. package/dist/binary.js +8 -0
  14. package/dist/bootstrap.d.ts +37 -5
  15. package/dist/bootstrap.d.ts.map +1 -1
  16. package/dist/bootstrap.js +336 -51
  17. package/dist/chain.d.ts +12 -0
  18. package/dist/chain.d.ts.map +1 -0
  19. package/dist/chain.js +64 -0
  20. package/dist/change-set.d.ts +9 -14
  21. package/dist/change-set.d.ts.map +1 -1
  22. package/dist/change-set.js +11 -56
  23. package/dist/change.d.ts +46 -0
  24. package/dist/change.d.ts.map +1 -1
  25. package/dist/change.js +34 -0
  26. package/dist/client.d.ts +44 -18
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +161 -30
  29. package/dist/collection.d.ts +13 -14
  30. package/dist/collection.d.ts.map +1 -1
  31. package/dist/collection.js +52 -56
  32. package/dist/compile.d.ts +11 -0
  33. package/dist/compile.d.ts.map +1 -0
  34. package/dist/compile.js +586 -0
  35. package/dist/connection.d.ts +67 -24
  36. package/dist/connection.d.ts.map +1 -1
  37. package/dist/connection.js +4414 -1205
  38. package/dist/context.d.ts +2 -0
  39. package/dist/context.d.ts.map +1 -0
  40. package/dist/context.js +7 -0
  41. package/dist/conversation.d.ts +65 -21
  42. package/dist/conversation.d.ts.map +1 -1
  43. package/dist/conversation.js +922 -273
  44. package/dist/digest.d.ts +7 -0
  45. package/dist/digest.d.ts.map +1 -0
  46. package/dist/digest.js +113 -0
  47. package/dist/durability.d.ts +10 -1
  48. package/dist/durability.d.ts.map +1 -1
  49. package/dist/durability.js +5 -0
  50. package/dist/entry.d.ts +20 -0
  51. package/dist/entry.d.ts.map +1 -0
  52. package/dist/entry.js +161 -0
  53. package/dist/expectation.d.ts +21 -0
  54. package/dist/expectation.d.ts.map +1 -0
  55. package/dist/expectation.js +70 -0
  56. package/dist/failure.d.ts +13 -0
  57. package/dist/failure.d.ts.map +1 -0
  58. package/dist/failure.js +19 -0
  59. package/dist/file.d.ts +2 -19
  60. package/dist/file.d.ts.map +1 -1
  61. package/dist/file.js +7 -122
  62. package/dist/host.d.ts +11 -2
  63. package/dist/host.d.ts.map +1 -1
  64. package/dist/host.js +134 -34
  65. package/dist/identity.d.ts +3 -0
  66. package/dist/identity.d.ts.map +1 -1
  67. package/dist/identity.js +13 -1
  68. package/dist/index.d.ts +24 -10
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +11 -0
  71. package/dist/job.d.ts +14 -0
  72. package/dist/job.d.ts.map +1 -0
  73. package/dist/job.js +28 -0
  74. package/dist/live.d.ts +23 -0
  75. package/dist/live.d.ts.map +1 -0
  76. package/dist/live.js +165 -0
  77. package/dist/media.d.ts +8 -0
  78. package/dist/media.d.ts.map +1 -0
  79. package/dist/media.js +218 -0
  80. package/dist/memory.d.ts +22 -8
  81. package/dist/memory.d.ts.map +1 -1
  82. package/dist/memory.js +299 -94
  83. package/dist/messages.d.ts +28 -0
  84. package/dist/messages.d.ts.map +1 -0
  85. package/dist/messages.js +91 -0
  86. package/dist/model.d.ts +17 -12
  87. package/dist/model.d.ts.map +1 -1
  88. package/dist/model.js +9 -9
  89. package/dist/name.d.ts +4 -0
  90. package/dist/name.d.ts.map +1 -0
  91. package/dist/name.js +122 -0
  92. package/dist/observation.d.ts +22 -4
  93. package/dist/observation.d.ts.map +1 -1
  94. package/dist/observation.js +120 -14
  95. package/dist/open.d.ts +2 -0
  96. package/dist/open.d.ts.map +1 -0
  97. package/dist/open.js +1 -0
  98. package/dist/opened.d.ts +22 -0
  99. package/dist/opened.d.ts.map +1 -0
  100. package/dist/opened.js +107 -0
  101. package/dist/outcome.d.ts +72 -5
  102. package/dist/outcome.d.ts.map +1 -1
  103. package/dist/outcome.js +81 -1
  104. package/dist/overlay.d.ts +16 -5
  105. package/dist/overlay.d.ts.map +1 -1
  106. package/dist/overlay.js +147 -180
  107. package/dist/paths.d.ts +1 -1
  108. package/dist/paths.d.ts.map +1 -1
  109. package/dist/paths.js +4 -16
  110. package/dist/query.d.ts +42 -181
  111. package/dist/query.d.ts.map +1 -1
  112. package/dist/query.js +3 -423
  113. package/dist/reach.d.ts +19 -5
  114. package/dist/reach.d.ts.map +1 -1
  115. package/dist/reach.js +298 -92
  116. package/dist/react.d.ts +45 -0
  117. package/dist/react.d.ts.map +1 -0
  118. package/dist/react.js +54 -0
  119. package/dist/readiness.d.ts +50 -0
  120. package/dist/readiness.d.ts.map +1 -0
  121. package/dist/readiness.js +27 -0
  122. package/dist/recovery.d.ts +26 -0
  123. package/dist/recovery.d.ts.map +1 -0
  124. package/dist/recovery.js +232 -0
  125. package/dist/refresh.d.ts +20 -7
  126. package/dist/refresh.d.ts.map +1 -1
  127. package/dist/refresh.js +155 -59
  128. package/dist/replica.d.ts +61 -34
  129. package/dist/replica.d.ts.map +1 -1
  130. package/dist/replica.js +355 -162
  131. package/dist/room-draft.d.ts +59 -11
  132. package/dist/room-draft.d.ts.map +1 -1
  133. package/dist/room-draft.js +276 -80
  134. package/dist/rows.d.ts +32 -34
  135. package/dist/rows.d.ts.map +1 -1
  136. package/dist/rows.js +65 -87
  137. package/dist/save.d.ts +74 -6
  138. package/dist/save.d.ts.map +1 -1
  139. package/dist/save.js +902 -182
  140. package/dist/search.d.ts +15 -54
  141. package/dist/search.d.ts.map +1 -1
  142. package/dist/search.js +10 -121
  143. package/dist/seed.d.ts +16 -0
  144. package/dist/seed.d.ts.map +1 -0
  145. package/dist/seed.js +75 -0
  146. package/dist/shared.d.ts +15 -0
  147. package/dist/shared.d.ts.map +1 -0
  148. package/dist/shared.js +421 -0
  149. package/dist/status.d.ts +7 -4
  150. package/dist/status.d.ts.map +1 -1
  151. package/dist/status.js +3 -15
  152. package/dist/store.d.ts +174 -68
  153. package/dist/store.d.ts.map +1 -1
  154. package/dist/store.js +367 -360
  155. package/dist/stored.d.ts +156 -0
  156. package/dist/stored.d.ts.map +1 -0
  157. package/dist/stored.js +635 -0
  158. package/dist/tables.d.ts +381 -0
  159. package/dist/tables.d.ts.map +1 -0
  160. package/dist/tables.js +700 -0
  161. package/dist/transfer.d.ts +0 -5
  162. package/dist/transfer.d.ts.map +1 -1
  163. package/dist/turn.d.ts +9 -0
  164. package/dist/turn.d.ts.map +1 -0
  165. package/dist/turn.js +75 -0
  166. package/dist/validation.d.ts +29 -0
  167. package/dist/validation.d.ts.map +1 -0
  168. package/dist/validation.js +104 -0
  169. package/dist/windows.d.ts +11 -0
  170. package/dist/windows.d.ts.map +1 -0
  171. package/dist/windows.js +16 -0
  172. package/dist/wire.d.ts +179 -45
  173. package/dist/wire.d.ts.map +1 -1
  174. package/dist/wire.js +72 -46
  175. package/package.json +33 -10
  176. package/AGENTS.md +0 -215
  177. package/dist/attachment.d.ts +0 -3
  178. package/dist/attachment.d.ts.map +0 -1
  179. package/dist/attachment.js +0 -8
  180. package/dist/read-side.d.ts +0 -18
  181. package/dist/read-side.d.ts.map +0 -1
  182. package/dist/read-side.js +0 -377
package/dist/reach.js CHANGED
@@ -1,30 +1,38 @@
1
1
  import { unheldView } from './conversation.js';
2
2
  import { modelTypes, NO_MODEL } from './model.js';
3
- import { applied, observing } from './observation.js';
3
+ import { changeOf, digestApplied, digestOf, observing, } from './observation.js';
4
+ import { runtimeError } from './failure.js';
5
+ import { failure, lockedRoom, ROOM_LOCKED, undeclared, UNREADABLE, } from './outcome.js';
4
6
  import { beneath } from './paths.js';
5
- import { reconciled } from './read-side.js';
6
- import { moveTarget, readParts } from './room-draft.js';
7
+ import { moveTarget, readParts, staleWrite, } from './room-draft.js';
7
8
  import { documentKey } from './rows.js';
8
9
  const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
9
- const UNDECLARED = { kind: 'undeclared' };
10
- function undeclared() {
11
- return Promise.resolve(UNDECLARED);
10
+ function unconversed(name) {
11
+ return () => Promise.resolve(undeclared(name));
12
12
  }
13
13
  const NO_CONVERSATION = {
14
14
  list: () => Promise.resolve([]),
15
15
  read: (conversation) => Promise.resolve(unheldView(conversation)),
16
- create: undeclared,
17
- send: undeclared,
18
- answer: undeclared,
19
- interrupt: undeclared,
20
- kill: undeclared,
21
- cancel: undeclared,
22
- delete: undeclared,
16
+ create: unconversed('conversations/create'),
17
+ send: unconversed('conversations/send'),
18
+ answer: unconversed('conversations/interaction/answer'),
19
+ interrupt: unconversed('conversations/interrupt'),
20
+ kill: unconversed('conversations/kill'),
21
+ delete: unconversed('conversations/delete'),
23
22
  };
24
23
  const NO_CHAT_DRAFT = {
25
24
  write: () => Promise.resolve(),
25
+ prepend: () => Promise.resolve(),
26
26
  discard: () => Promise.resolve(),
27
27
  };
28
+ function areasOf(reach) {
29
+ return reach.areas ?? [];
30
+ }
31
+ function fenced(reach, room, path) {
32
+ return areasOf(reach).some((area) => area.room === room &&
33
+ beneath(area.path, path) &&
34
+ !area.open.some((open) => beneath(open, path)));
35
+ }
28
36
  export function holds(reach, room) {
29
37
  return (reach === null ||
30
38
  reach.rooms.includes(room) ||
@@ -45,8 +53,9 @@ export function governs(reach, room) {
45
53
  }
46
54
  function covers(reach, room, path) {
47
55
  return (reach === null ||
48
- reach.rooms.includes(room) ||
49
- reach.documents.some((document) => document.room === room && document.path === path));
56
+ (!fenced(reach, room, path) &&
57
+ (reach.rooms.includes(room) ||
58
+ reach.documents.some((document) => document.room === room && document.path === path))));
50
59
  }
51
60
  function conversing(reach) {
52
61
  return reach === null || reach.conversations;
@@ -59,25 +68,13 @@ export function planReach(reach) {
59
68
  documents: [
60
69
  ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
61
70
  ].sort(),
71
+ areas: areasOf(reach)
72
+ .map(({ room, path, open }) => ({ room, path, open: [...new Set(open)].sort() }))
73
+ .sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
62
74
  };
63
75
  }
64
76
  function roomsWithin(reach, rooms) {
65
- if (reach === null)
66
- return rooms;
67
- return rooms
68
- .filter((room) => holds(reach, room.locator))
69
- .map((room) => {
70
- if (wholly(reach, room.locator))
71
- return room;
72
- const governed = governs(reach, room);
73
- return {
74
- ...room,
75
- model: governed.collection ? room.model : modelTypes(NO_MODEL),
76
- typedSelection: governed.selection ? room.typedSelection : null,
77
- roomVersion: null,
78
- attachments: [],
79
- };
80
- });
77
+ return rooms.filter((room) => wholly(reach, room.locator));
81
78
  }
82
79
  function typesWithin(reach, room, types) {
83
80
  if (room === undefined)
@@ -89,7 +86,7 @@ function typesWithin(reach, room, types) {
89
86
  };
90
87
  }
91
88
  function moves(reach, room, move) {
92
- return move === undefined || covers(reach, room, moveTarget(move));
89
+ return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
93
90
  }
94
91
  function draftWithin(reach, draft) {
95
92
  return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
@@ -103,7 +100,7 @@ function shownDraft(reach, draft) {
103
100
  function writeWithin(reach, write) {
104
101
  return write.outcome === 'written'
105
102
  ? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
106
- : { outcome: 'stale', current: shownDraft(reach, write.current) };
103
+ : staleWrite(shownDraft(reach, write.current));
107
104
  }
108
105
  function savedWithin(reach, room, result) {
109
106
  if (reach === null || result.outcome === 'saved')
@@ -123,128 +120,337 @@ function statusWithin(reach, status) {
123
120
  return {
124
121
  ...status,
125
122
  attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
123
+ unsent: status.unsent.filter((change) => covers(reach, change.room, change.path)),
126
124
  };
127
125
  }
128
126
  function queueWithin(reach, room, entries) {
129
- return entries.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
130
- (change.kind !== 'move' || covers(reach, room, change.to))));
127
+ return entries
128
+ .filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
129
+ (change.kind !== 'move' || covers(reach, room, change.to))))
130
+ .map((entry) => {
131
+ const { between, ...batch } = entry.batch;
132
+ if (between === undefined)
133
+ return entry;
134
+ return wholly(reach, between.source.room) && wholly(reach, between.destination.room)
135
+ ? entry
136
+ : { ...entry, batch };
137
+ });
131
138
  }
132
139
  function unreached(room) {
133
- return new Error(`No Local Replica for ${room}`);
140
+ return runtimeError('unknown-room', `Unknown Room ${room}`);
141
+ }
142
+ function lockedError(room) {
143
+ return runtimeError(ROOM_LOCKED, `${room} is locked until House confirms its access`, true);
144
+ }
145
+ function deviceUnreached() {
146
+ return runtimeError('device-unreached', 'Only the panel reaches this device');
134
147
  }
135
- export function narrowed(runtime, given) {
148
+ const ATTACH_REQUIRED = 'attach-required';
149
+ function attachRequired() {
150
+ return runtimeError(ATTACH_REQUIRED, 'App code takes a file in with binaryFile.attach');
151
+ }
152
+ function carriesFile(parts) {
153
+ return parts !== null && typeof parts === 'object' && Object.hasOwn(parts, 'file');
154
+ }
155
+ export function narrowed(runtime, given, port) {
136
156
  const { md, watch } = runtime;
137
157
  let reach = given;
138
158
  const open = new Set();
139
- function following(begin) {
159
+ function following(begin, locking = false) {
140
160
  let stop = begin();
141
161
  const restart = () => {
142
162
  stop();
143
163
  stop = begin();
144
164
  };
145
165
  open.add(restart);
166
+ const unwatch = locking ? runtime.watchLocks(restart) : () => { };
146
167
  return () => {
147
168
  open.delete(restart);
169
+ unwatch();
170
+ stop();
171
+ };
172
+ }
173
+ const unlocking = (rooms, locked) => {
174
+ const held = (rooms ?? []).filter((room) => locked.includes(room));
175
+ return held.length === 0 ? null : { pending: held.map((room) => ({ room, awaits: 'unlock' })) };
176
+ };
177
+ const unlocked = (entries, locked) => entries.filter((entry) => !locked.includes(entry.room));
178
+ const intoLocked = async (held) => {
179
+ for (const move of held) {
180
+ if (move?.room !== undefined && (await runtime.locked(move.room)))
181
+ return true;
182
+ }
183
+ return false;
184
+ };
185
+ function ordered(deliver) {
186
+ let open = true;
187
+ let shown = Promise.resolve();
188
+ return {
189
+ push(making) {
190
+ shown = shown.then(() => making.then((value) => {
191
+ if (open)
192
+ deliver(value);
193
+ }, () => { }), () => { });
194
+ },
195
+ stop() {
196
+ open = false;
197
+ },
198
+ };
199
+ }
200
+ function unlockedWatch(topic, input, deliver, fail, within) {
201
+ const shown = ordered(deliver);
202
+ const stop = watch(topic, input, (message) => {
203
+ shown.push(runtime.lockedRooms().then((locked) => within(message, locked)));
204
+ }, fail);
205
+ return () => {
206
+ shown.stop();
207
+ stop();
208
+ };
209
+ }
210
+ function lockedFirst(room, start, shut) {
211
+ let open = true;
212
+ let stop = () => { };
213
+ void (async () => {
214
+ const closed = !holds(reach, room) || (await runtime.locked(room));
215
+ if (!open)
216
+ return;
217
+ if (closed)
218
+ shut();
219
+ else
220
+ stop = start();
221
+ })();
222
+ return () => {
223
+ open = false;
148
224
  stop();
149
225
  };
150
226
  }
151
- const within = (topic, input, deliver) => {
227
+ const within = (topic, input, deliver, fail) => {
152
228
  if (topic === 'rooms') {
153
- return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
229
+ return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms)), fail));
230
+ }
231
+ if (topic === 'readiness') {
232
+ return following(() => runtime.watchReadiness(port, reach, deliver));
154
233
  }
155
234
  if (topic === 'sync-status') {
156
- return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
235
+ return following(() => unlockedWatch(topic, input, deliver, fail, (status, locked) => {
236
+ const shown = statusWithin(reach, status);
237
+ return { ...shown, attentionRequired: unlocked(shown.attentionRequired, locked) };
238
+ }), true);
157
239
  }
158
240
  if (topic === 'room-drafts') {
159
- return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
241
+ return following(() => unlockedWatch(topic, input, deliver, fail, (drafts, locked) => unlocked(draftsWithin(reach, drafts), locked)), true);
160
242
  }
161
243
  if (topic === 'queue') {
162
244
  const room = input[0];
163
- return following(() => {
164
- if (!holds(reach, room)) {
165
- deliver([]);
166
- return () => { };
167
- }
168
- return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
169
- });
245
+ return following(() => lockedFirst(room, () => watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)), fail), () => deliver([])), true);
170
246
  }
171
247
  if (topic === 'query') {
172
- const plan = runtime.plan(input[0], reach);
248
+ const planning = runtime.plan(input[0], reach);
173
249
  let held = null;
174
250
  return following(() => {
175
- let first = true;
176
- return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
177
- if (!first) {
178
- held = applied(held, message);
179
- deliver(message);
251
+ let open = true;
252
+ let stop = () => { };
253
+ void (async () => {
254
+ const plan = await planning;
255
+ const locked = await runtime.lockedRooms();
256
+ if (!open)
180
257
  return;
181
- }
182
- first = false;
183
- if (held === null) {
184
- held = message;
185
- deliver(message);
258
+ const pending = unlocking(plan.rooms, locked);
259
+ if (pending !== null) {
260
+ const answer = { result: pending, keys: [] };
261
+ if (held === null || changeOf(held, answer) !== null)
262
+ deliver(answer);
263
+ held = digestOf(answer);
186
264
  return;
187
265
  }
188
- const next = reconciled(held, message);
189
- if (next === null)
190
- return;
191
- held = next.keyed;
192
- deliver(next.change);
266
+ let first = true;
267
+ const heard = (message) => {
268
+ if (!first) {
269
+ held = digestApplied(held, message);
270
+ deliver(message);
271
+ return;
272
+ }
273
+ first = false;
274
+ const change = held === null ? message : changeOf(held, message);
275
+ held = digestOf(message);
276
+ if (change !== null)
277
+ deliver(change);
278
+ };
279
+ const current = { ...plan, reach: planReach(reach), locked };
280
+ stop = runtime.watchPlan(current, (message) => {
281
+ if (open)
282
+ heard(message);
283
+ }, (error) => {
284
+ if (open)
285
+ fail(error);
286
+ });
287
+ })().catch((error) => {
288
+ if (open)
289
+ fail(error);
193
290
  });
194
- });
291
+ return () => {
292
+ open = false;
293
+ stop();
294
+ };
295
+ }, true);
195
296
  }
196
297
  if (topic === 'conversation') {
197
298
  return following(() => {
198
299
  if (conversing(reach))
199
- return watch(topic, input, deliver);
300
+ return watch(topic, input, deliver, fail);
200
301
  deliver(unheldView(input[0]));
201
302
  return () => { };
202
303
  });
203
304
  }
204
- return watch(topic, input, deliver);
305
+ if (topic === 'conversations' || topic === 'agents') {
306
+ return following(() => {
307
+ if (conversing(reach))
308
+ return watch(topic, input, deliver, fail);
309
+ deliver([]);
310
+ return () => { };
311
+ });
312
+ }
313
+ return watch(topic, input, deliver, fail);
205
314
  };
315
+ const files = new Map();
316
+ let handed = 0;
317
+ async function hand(reading) {
318
+ const answer = await reading;
319
+ if (answer.kind !== 'binary')
320
+ return answer;
321
+ handed += 1;
322
+ const file = String(handed);
323
+ files.set(file, answer.file);
324
+ return { ...answer, file };
325
+ }
206
326
  const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
207
327
  const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
208
328
  const view = {
209
329
  ...md,
210
- rooms: () => roomsWithin(reach, md.rooms()),
211
- query: ((input) => runtime.query(runtime.plan(input, reach))),
330
+ rooms: () => md.rooms().then((rooms) => roomsWithin(reach, rooms)),
331
+ query: (async (input) => {
332
+ const plan = await runtime.plan(input, reach);
333
+ const locked = await runtime.lockedRooms();
334
+ return unlocking(plan.rooms, locked) ?? runtime.query({ ...plan, locked });
335
+ }),
212
336
  search: (input) => runtime.search(input, reach),
213
- types(room) {
337
+ async types(room) {
338
+ if (holds(reach, room) && (await runtime.locked(room)))
339
+ throw lockedError(room);
214
340
  if (wholly(reach, room))
215
341
  return md.types(room);
216
342
  if (!holds(reach, room))
217
343
  return structuredClone(UNHELD_TYPES);
218
- const listed = md.rooms().find(({ locator }) => locator === room);
219
- return typesWithin(reach, listed, md.types(room));
344
+ return typesWithin(reach, await runtime.room(room), await md.types(room));
220
345
  },
221
346
  async save(room, roomDrafts, writtenBy) {
222
347
  if (!holds(reach, room))
223
348
  throw unreached(room);
224
- const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
225
- !moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
226
- if (outside !== undefined)
227
- return { outcome: 'refused', rule: 'stale', file: outside.path };
349
+ if (await runtime.locked(room)) {
350
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
351
+ }
352
+ const held = (await Promise.all(roomDrafts.map(({ path }) => md.roomDraft.read(room, path)))).map((draft) => draft?.parts.move);
353
+ const outside = roomDrafts.find(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]));
354
+ if (outside !== undefined) {
355
+ return { outcome: 'refused', code: 'stale', retryable: false, file: outside.path };
356
+ }
357
+ if (await intoLocked(held))
358
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
228
359
  return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
229
360
  },
230
- history: (room, path, query) => covers(reach, room, path)
231
- ? md.history(room, path, query)
232
- : Promise.resolve({ kind: 'room-not-readable', locator: room }),
233
- reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
234
- md.reached(room, roomVersion, path),
361
+ async saveParts(room, parts, writtenBy) {
362
+ if (!holds(reach, room))
363
+ throw unreached(room);
364
+ const filed = reach === null ? undefined : parts.find(({ parts: one }) => carriesFile(one));
365
+ if (filed !== undefined) {
366
+ return { outcome: 'refused', code: ATTACH_REQUIRED, retryable: false, file: filed.path };
367
+ }
368
+ if (await runtime.locked(room)) {
369
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
370
+ }
371
+ const held = parts.map(({ path, parts: one }) => readParts(one, path).move);
372
+ if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
373
+ throw unreached(room);
374
+ }
375
+ if (await intoLocked(held))
376
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
377
+ return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
378
+ },
379
+ async resolve(room, path, destination) {
380
+ const at = await runtime.located(room, path, destination);
381
+ return at !== null && !covers(reach, at.room, at.path)
382
+ ? UNREADABLE
383
+ : md.resolve(room, path, destination);
384
+ },
385
+ history: async (room, path, query) => !covers(reach, room, path)
386
+ ? failure({ kind: 'room-not-readable', locator: room })
387
+ : (await runtime.locked(room))
388
+ ? lockedRoom(room)
389
+ : md.history(room, path, query),
390
+ binaryFile: {
391
+ read: async (room, path, revision) => !covers(reach, room, path)
392
+ ? failure({ kind: 'room-not-readable', locator: room })
393
+ : (await runtime.locked(room))
394
+ ? lockedRoom(room)
395
+ : hand(md.binaryFile.read(room, path, revision)),
396
+ link: async (room, path, revision) => !covers(reach, room, path)
397
+ ? failure({ kind: 'room-not-readable', locator: room })
398
+ : (await runtime.locked(room))
399
+ ? lockedRoom(room)
400
+ : md.binaryFile.link(room, path, revision),
401
+ show: async (room, path, revision) => !covers(reach, room, path)
402
+ ? failure({ kind: 'room-not-readable', locator: room })
403
+ : (await runtime.locked(room))
404
+ ? lockedRoom(room)
405
+ : hand(md.binaryFile.show(room, path, revision)),
406
+ attach: async (room, file) => !wholly(reach, room)
407
+ ? failure({ kind: 'room-not-readable', locator: room })
408
+ : (await runtime.locked(room))
409
+ ? lockedRoom(room)
410
+ : md.binaryFile.attach(room, file),
411
+ async part(file, part) {
412
+ const key = files.get(file);
413
+ if (key === undefined)
414
+ return null;
415
+ const bytes = await md.binaryFile.part(key, part);
416
+ if (bytes === null)
417
+ files.delete(file);
418
+ return bytes;
419
+ },
420
+ },
421
+ reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
422
+ !(await runtime.locked(room)) &&
423
+ (await md.reached(room, roomVersion, path)),
235
424
  log: () => (reach === null ? md.log() : ''),
425
+ cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
426
+ unsent: () => (reach === null ? md.unsent() : Promise.reject(deviceUnreached())),
427
+ reset: () => (reach === null ? md.reset() : Promise.reject(deviceUnreached())),
236
428
  roomDraft: {
237
- list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
238
- read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
239
- async write(room, path, revision, parts) {
240
- if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
429
+ list: async () => unlocked(draftsWithin(reach, await md.roomDraft.list()), await runtime.lockedRooms()),
430
+ async read(room, path) {
431
+ if (!covers(reach, room, path))
432
+ return null;
433
+ if (await runtime.locked(room))
434
+ throw lockedError(room);
435
+ return shownDraft(reach, await md.roomDraft.read(room, path));
436
+ },
437
+ async write(room, path, revision, parts, next) {
438
+ if (reach !== null && carriesFile(parts))
439
+ throw attachRequired();
440
+ const move = readParts(parts, path).move;
441
+ if (!covers(reach, room, path) || !moves(reach, room, move))
241
442
  throw unreached(room);
242
- }
243
- return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
443
+ if (await runtime.locked(room))
444
+ throw lockedError(room);
445
+ if (await intoLocked([move]))
446
+ throw lockedError(move.room);
447
+ return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts, next));
244
448
  },
245
449
  async discard(room, path, revision) {
246
450
  if (!covers(reach, room, path))
247
- return { outcome: 'stale', current: null };
451
+ return staleWrite(null);
452
+ if (await runtime.locked(room))
453
+ throw lockedError(room);
248
454
  return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
249
455
  },
250
456
  },
@@ -252,15 +458,15 @@ export function narrowed(runtime, given) {
252
458
  list: () => conversation().list(),
253
459
  read: (id, options) => conversation().read(id, options),
254
460
  create: (input) => conversation().create(input),
255
- send: (id) => conversation().send(id),
461
+ send: (id, operation, draft) => conversation().send(id, operation, draft),
256
462
  answer: (id, answer) => conversation().answer(id, answer),
257
463
  interrupt: (id) => conversation().interrupt(id),
258
464
  kill: (id) => conversation().kill(id),
259
- cancel: (id, turn) => conversation().cancel(id, turn),
260
465
  delete: (id) => conversation().delete(id),
261
466
  },
262
467
  chatDraft: {
263
- write: (id, draft) => chatDraft().write(id, draft),
468
+ write: (id, draft, revision) => chatDraft().write(id, draft, revision),
469
+ prepend: (id, draft) => chatDraft().prepend(id, draft),
264
470
  discard: (id) => chatDraft().discard(id),
265
471
  },
266
472
  observe: observing(within),
@@ -0,0 +1,45 @@
1
+ import type { DocumentQuery, GroupQuery } from '@agentshouse/mdmodel';
2
+ import type { MdRuntimeClient } from './client.js';
3
+ import type { AppRoom, ConnectionRefusal, SavedRoomDraft, SaveResult } from './connection.js';
4
+ import type { Member, ServiceMode } from './adapter.js';
5
+ import type { ConversationAgent, ConversationEntry, ConversationOptions, ConversationView } from './conversation.js';
6
+ import { type RuntimeError } from './failure.js';
7
+ import type { QueueEntry } from './outcome.js';
8
+ import type { DocumentResult, GroupResult, PendingResult } from './query.js';
9
+ import type { Readiness } from './readiness.js';
10
+ import type { RoomDraft, RoomDraftParts } from './room-draft.js';
11
+ import type { SyncStatus } from './status.js';
12
+ type Client = MdRuntimeClient | null;
13
+ export type Observed<T> = {
14
+ readonly state: 'loading';
15
+ } | {
16
+ readonly state: 'ready';
17
+ readonly value: T;
18
+ };
19
+ export declare function useObserve(client: Client, topic: 'deployment-facts'): Observed<unknown>;
20
+ export declare function useObserve(client: Client, topic: 'rooms'): Observed<readonly AppRoom[]>;
21
+ export declare function useObserve(client: Client, topic: 'readiness'): Observed<Readiness>;
22
+ export declare function useObserve(client: Client, topic: 'connection-refusal'): Observed<ConnectionRefusal | null>;
23
+ export declare function useObserve(client: Client, topic: 'storage-loss'): Observed<string | null>;
24
+ export declare function useObserve(client: Client, topic: 'sync-status'): Observed<SyncStatus>;
25
+ export declare function useObserve(client: Client, topic: 'service-mode'): Observed<ServiceMode>;
26
+ export declare function useObserve(client: Client, topic: 'member'): Observed<Member | null>;
27
+ export declare function useObserve(client: Client, topic: 'authority-changes'): Observed<number>;
28
+ export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): Observed<DocumentResult | PendingResult>;
29
+ export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): Observed<GroupResult | PendingResult>;
30
+ export declare function useObserve(client: Client, topic: 'queue', locator: string): Observed<readonly QueueEntry[]>;
31
+ export declare function useObserve(client: Client, topic: 'room-drafts'): Observed<readonly RoomDraft[]>;
32
+ export declare function useObserve(client: Client, topic: 'presentation'): Observed<unknown>;
33
+ export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): Observed<ConversationView>;
34
+ export declare function useObserve(client: Client, topic: 'conversations'): Observed<readonly ConversationEntry[]>;
35
+ export declare function useObserve(client: Client, topic: 'agents'): Observed<readonly ConversationAgent[]>;
36
+ export type Saving = {
37
+ readonly busy: boolean;
38
+ readonly outcome: SaveResult | null;
39
+ readonly failure: RuntimeError | null;
40
+ save(room: string, parts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
41
+ saveWritten(room: string, roomDrafts: readonly SavedRoomDraft[]): Promise<SaveResult>;
42
+ };
43
+ export declare function useSave(client: MdRuntimeClient): Saving;
44
+ export {};
45
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,KAAK,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC;AAErC,MAAM,MAAM,QAAQ,CAAC,CAAC,IAClB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAUnD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;AACzF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;AACzF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AACpF,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,oBAAoB,GAC1B,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;AACtC,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAC3F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AACvF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;AACzF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AACrF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AACzF,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,aAAa,GACnB,QAAQ,CAAC,cAAc,GAAG,aAAa,CAAC,CAAC;AAC5C,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,UAAU,GAChB,QAAQ,CAAC,WAAW,GAAG,aAAa,CAAC,CAAC;AACzC,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,QAAQ,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;AACnC,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;AACjG,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;AACrF,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,cAAc,EACrB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAC3B,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAC9B,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,QAAQ,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;AAC1C,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAAC;AAsCpG,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACpF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvF,CAAC;AAMF,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA8BvD"}
package/dist/react.js ADDED
@@ -0,0 +1,54 @@
1
+ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
+ import { failed } from './failure.js';
3
+ const LOADING = { state: 'loading' };
4
+ export function useObserve(client, topic, ...input) {
5
+ const key = JSON.stringify([topic, ...input]);
6
+ const [held, setHeld] = useState(null);
7
+ const opened = useRef(input);
8
+ opened.current = input;
9
+ useLayoutEffect(() => {
10
+ if (client === null)
11
+ return;
12
+ let live = true;
13
+ let observation = null;
14
+ const observe = client.observe;
15
+ void observe(topic, ...opened.current).then((open) => {
16
+ if (!live) {
17
+ open.close();
18
+ return;
19
+ }
20
+ observation = open;
21
+ const take = (value) => {
22
+ if (live)
23
+ setHeld({ client, key, observed: { state: 'ready', value } });
24
+ };
25
+ take(open.current());
26
+ open.subscribe(take);
27
+ });
28
+ return () => {
29
+ live = false;
30
+ observation?.close();
31
+ };
32
+ }, [client, key]);
33
+ return held !== null && held.client === client && held.key === key ? held.observed : LOADING;
34
+ }
35
+ const IDLE = { busy: false, outcome: null, failure: null };
36
+ export function useSave(client) {
37
+ const [state, setState] = useState(IDLE);
38
+ const settled = useCallback(async (saving) => {
39
+ setState({ busy: true, outcome: null, failure: null });
40
+ try {
41
+ const outcome = await saving();
42
+ setState({ busy: false, outcome, failure: null });
43
+ return outcome;
44
+ }
45
+ catch (error) {
46
+ const failure = failed(error);
47
+ setState({ busy: false, outcome: null, failure });
48
+ throw failure;
49
+ }
50
+ }, []);
51
+ const save = useCallback((room, parts) => settled(() => client.saveParts(room, [...parts].map(([path, one]) => ({ path, parts: one })))), [client, settled]);
52
+ const saveWritten = useCallback((room, roomDrafts) => settled(() => client.save(room, roomDrafts)), [client, settled]);
53
+ return { ...state, save, saveWritten };
54
+ }