@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cae97ad2e9142

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 (179) hide show
  1. package/README.md +23 -10
  2. package/dist/adapter.d.ts +126 -30
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/attached-model.d.ts +5 -1
  5. package/dist/attached-model.d.ts.map +1 -1
  6. package/dist/attached-model.js +36 -6
  7. package/dist/attempt.d.ts +16 -0
  8. package/dist/attempt.d.ts.map +1 -0
  9. package/dist/attempt.js +70 -0
  10. package/dist/binary.d.ts +18 -0
  11. package/dist/binary.d.ts.map +1 -0
  12. package/dist/binary.js +8 -0
  13. package/dist/bootstrap.d.ts +40 -6
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +186 -45
  16. package/dist/chain.d.ts +11 -0
  17. package/dist/chain.d.ts.map +1 -0
  18. package/dist/chain.js +64 -0
  19. package/dist/change-set.d.ts +9 -14
  20. package/dist/change-set.d.ts.map +1 -1
  21. package/dist/change-set.js +11 -56
  22. package/dist/change.d.ts +45 -0
  23. package/dist/change.d.ts.map +1 -1
  24. package/dist/change.js +34 -0
  25. package/dist/client.d.ts +35 -14
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +107 -17
  28. package/dist/collection.d.ts +9 -13
  29. package/dist/collection.d.ts.map +1 -1
  30. package/dist/collection.js +46 -56
  31. package/dist/compile.d.ts +16 -0
  32. package/dist/compile.d.ts.map +1 -0
  33. package/dist/compile.js +767 -0
  34. package/dist/connection.d.ts +58 -19
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +3659 -726
  37. package/dist/conversation.d.ts +41 -12
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +588 -180
  40. package/dist/digest.d.ts +7 -0
  41. package/dist/digest.d.ts.map +1 -0
  42. package/dist/digest.js +113 -0
  43. package/dist/durability.d.ts +5 -1
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +19 -0
  47. package/dist/entry.d.ts.map +1 -0
  48. package/dist/entry.js +151 -0
  49. package/dist/expectation.d.ts +21 -0
  50. package/dist/expectation.d.ts.map +1 -0
  51. package/dist/expectation.js +70 -0
  52. package/dist/failure.d.ts +13 -0
  53. package/dist/failure.d.ts.map +1 -0
  54. package/dist/failure.js +19 -0
  55. package/dist/fence.d.ts +6 -0
  56. package/dist/fence.d.ts.map +1 -0
  57. package/dist/fence.js +10 -0
  58. package/dist/file.d.ts +2 -10
  59. package/dist/file.d.ts.map +1 -1
  60. package/dist/file.js +3 -120
  61. package/dist/fulltext.d.ts +27 -0
  62. package/dist/fulltext.d.ts.map +1 -0
  63. package/dist/fulltext.js +368 -0
  64. package/dist/host.d.ts +15 -2
  65. package/dist/host.d.ts.map +1 -1
  66. package/dist/host.js +132 -21
  67. package/dist/identity.d.ts +4 -0
  68. package/dist/identity.d.ts.map +1 -1
  69. package/dist/identity.js +16 -1
  70. package/dist/index.d.ts +20 -9
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +8 -0
  73. package/dist/live.d.ts +22 -0
  74. package/dist/live.d.ts.map +1 -0
  75. package/dist/live.js +124 -0
  76. package/dist/memory.d.ts +13 -5
  77. package/dist/memory.d.ts.map +1 -1
  78. package/dist/memory.js +99 -31
  79. package/dist/messages.d.ts +28 -0
  80. package/dist/messages.d.ts.map +1 -0
  81. package/dist/messages.js +90 -0
  82. package/dist/model.d.ts +14 -4
  83. package/dist/model.d.ts.map +1 -1
  84. package/dist/model.js +13 -2
  85. package/dist/name.d.ts +4 -0
  86. package/dist/name.d.ts.map +1 -0
  87. package/dist/name.js +122 -0
  88. package/dist/observation.d.ts +19 -2
  89. package/dist/observation.d.ts.map +1 -1
  90. package/dist/observation.js +113 -12
  91. package/dist/open.d.ts +2 -0
  92. package/dist/open.d.ts.map +1 -0
  93. package/dist/open.js +1 -0
  94. package/dist/opened.d.ts +22 -0
  95. package/dist/opened.d.ts.map +1 -0
  96. package/dist/opened.js +107 -0
  97. package/dist/outcome.d.ts +62 -3
  98. package/dist/outcome.d.ts.map +1 -1
  99. package/dist/outcome.js +67 -1
  100. package/dist/overlay.d.ts +15 -5
  101. package/dist/overlay.d.ts.map +1 -1
  102. package/dist/overlay.js +136 -180
  103. package/dist/query.d.ts +70 -4
  104. package/dist/query.d.ts.map +1 -1
  105. package/dist/query.js +53 -38
  106. package/dist/reach.d.ts +17 -4
  107. package/dist/reach.d.ts.map +1 -1
  108. package/dist/reach.js +226 -65
  109. package/dist/react.d.ts +31 -20
  110. package/dist/react.d.ts.map +1 -1
  111. package/dist/react.js +13 -21
  112. package/dist/readiness.d.ts +64 -0
  113. package/dist/readiness.d.ts.map +1 -0
  114. package/dist/readiness.js +33 -0
  115. package/dist/reads.d.ts +11 -0
  116. package/dist/reads.d.ts.map +1 -0
  117. package/dist/reads.js +100 -0
  118. package/dist/recovery.d.ts +25 -0
  119. package/dist/recovery.d.ts.map +1 -0
  120. package/dist/recovery.js +198 -0
  121. package/dist/refresh.d.ts +19 -8
  122. package/dist/refresh.d.ts.map +1 -1
  123. package/dist/refresh.js +125 -57
  124. package/dist/replica.d.ts +59 -33
  125. package/dist/replica.d.ts.map +1 -1
  126. package/dist/replica.js +321 -155
  127. package/dist/room-draft.d.ts +41 -9
  128. package/dist/room-draft.d.ts.map +1 -1
  129. package/dist/room-draft.js +186 -37
  130. package/dist/rows.d.ts +37 -35
  131. package/dist/rows.d.ts.map +1 -1
  132. package/dist/rows.js +168 -100
  133. package/dist/save.d.ts +71 -4
  134. package/dist/save.d.ts.map +1 -1
  135. package/dist/save.js +815 -146
  136. package/dist/search.d.ts +41 -29
  137. package/dist/search.d.ts.map +1 -1
  138. package/dist/search.js +37 -105
  139. package/dist/seed.d.ts +16 -0
  140. package/dist/seed.d.ts.map +1 -0
  141. package/dist/seed.js +75 -0
  142. package/dist/shared.d.ts +16 -0
  143. package/dist/shared.d.ts.map +1 -0
  144. package/dist/shared.js +431 -0
  145. package/dist/status.d.ts +6 -2
  146. package/dist/status.d.ts.map +1 -1
  147. package/dist/status.js +0 -8
  148. package/dist/store.d.ts +124 -58
  149. package/dist/store.d.ts.map +1 -1
  150. package/dist/store.js +362 -344
  151. package/dist/stored.d.ts +150 -0
  152. package/dist/stored.d.ts.map +1 -0
  153. package/dist/stored.js +564 -0
  154. package/dist/tables.d.ts +354 -0
  155. package/dist/tables.d.ts.map +1 -0
  156. package/dist/tables.js +624 -0
  157. package/dist/turn.d.ts +9 -0
  158. package/dist/turn.d.ts.map +1 -0
  159. package/dist/turn.js +75 -0
  160. package/dist/validation.d.ts +30 -0
  161. package/dist/validation.d.ts.map +1 -0
  162. package/dist/validation.js +162 -0
  163. package/dist/windows.d.ts +22 -0
  164. package/dist/windows.d.ts.map +1 -0
  165. package/dist/windows.js +89 -0
  166. package/dist/wire.d.ts +169 -33
  167. package/dist/wire.d.ts.map +1 -1
  168. package/dist/wire.js +68 -31
  169. package/dist/words.d.ts +19 -0
  170. package/dist/words.d.ts.map +1 -0
  171. package/dist/words.js +150 -0
  172. package/package.json +26 -15
  173. package/AGENTS.md +0 -244
  174. package/dist/attachment.d.ts +0 -3
  175. package/dist/attachment.d.ts.map +0 -1
  176. package/dist/attachment.js +0 -8
  177. package/dist/read-side.d.ts +0 -18
  178. package/dist/read-side.d.ts.map +0 -1
  179. package/dist/read-side.js +0 -433
package/dist/reach.js CHANGED
@@ -1,34 +1,47 @@
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';
9
+ import { undeclaredSearch } from './search.js';
8
10
  const UNHELD_TYPES = { collection: modelTypes(NO_MODEL), typedSelection: null };
9
- const UNDECLARED = { kind: 'undeclared' };
10
- function undeclared() {
11
- return Promise.resolve(UNDECLARED);
11
+ function unconversed(name) {
12
+ return () => Promise.resolve(undeclared(name));
12
13
  }
13
14
  const NO_CONVERSATION = {
14
15
  list: () => Promise.resolve([]),
15
16
  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,
17
+ create: unconversed('conversation.create'),
18
+ send: unconversed('conversation.send'),
19
+ attach: unconversed('conversation.attach'),
20
+ answer: unconversed('conversation.answer'),
21
+ interrupt: unconversed('conversation.interrupt'),
22
+ kill: unconversed('conversation.kill'),
23
+ cancel: unconversed('conversation.cancel'),
24
+ delete: unconversed('conversation.delete'),
23
25
  };
24
26
  const NO_CHAT_DRAFT = {
25
27
  write: () => Promise.resolve(),
28
+ prepend: () => Promise.resolve(),
26
29
  discard: () => Promise.resolve(),
27
30
  };
31
+ function areasOf(reach) {
32
+ return reach.areas ?? [];
33
+ }
34
+ function typedAreas(reach, room) {
35
+ return areasOf(reach).filter((area) => area.room === room && area.types.length > 0);
36
+ }
37
+ function fenced(reach, room, path) {
38
+ return areasOf(reach).some((area) => area.room === room && beneath(area.path, path));
39
+ }
28
40
  export function holds(reach, room) {
29
41
  return (reach === null ||
30
42
  reach.rooms.includes(room) ||
31
- reach.documents.some((document) => document.room === room));
43
+ reach.documents.some((document) => document.room === room) ||
44
+ typedAreas(reach, room).length > 0);
32
45
  }
33
46
  function wholly(reach, room) {
34
47
  return reach === null || reach.rooms.includes(room);
@@ -38,15 +51,19 @@ export function governs(reach, room) {
38
51
  return { collection: true, selection: room.typedSelection !== null };
39
52
  }
40
53
  const selected = (path) => room.typedSelection?.roots.some((root) => beneath(root, path)) ?? false;
41
- const paths = reach.documents
42
- .filter((document) => document.room === room.locator)
43
- .map(({ path }) => path);
54
+ const paths = [
55
+ ...reach.documents
56
+ .filter((document) => document.room === room.locator)
57
+ .map(({ path }) => path),
58
+ ...typedAreas(reach, room.locator).map(({ path }) => path),
59
+ ];
44
60
  return { collection: paths.some((path) => !selected(path)), selection: paths.some(selected) };
45
61
  }
46
62
  function covers(reach, room, path) {
47
63
  return (reach === null ||
48
- reach.rooms.includes(room) ||
49
- reach.documents.some((document) => document.room === room && document.path === path));
64
+ (!fenced(reach, room, path) &&
65
+ (reach.rooms.includes(room) ||
66
+ reach.documents.some((document) => document.room === room && document.path === path))));
50
67
  }
51
68
  function conversing(reach) {
52
69
  return reach === null || reach.conversations;
@@ -59,6 +76,9 @@ export function planReach(reach) {
59
76
  documents: [
60
77
  ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
61
78
  ].sort(),
79
+ areas: areasOf(reach)
80
+ .map(({ room, path, types }) => ({ room, path, types: [...new Set(types)].sort() }))
81
+ .sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
62
82
  };
63
83
  }
64
84
  function roomsWithin(reach, rooms) {
@@ -74,7 +94,7 @@ function typesWithin(reach, room, types) {
74
94
  };
75
95
  }
76
96
  function moves(reach, room, move) {
77
- return move === undefined || covers(reach, room, moveTarget(move));
97
+ return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
78
98
  }
79
99
  function draftWithin(reach, draft) {
80
100
  return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
@@ -88,7 +108,7 @@ function shownDraft(reach, draft) {
88
108
  function writeWithin(reach, write) {
89
109
  return write.outcome === 'written'
90
110
  ? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
91
- : { outcome: 'stale', current: shownDraft(reach, write.current) };
111
+ : staleWrite(shownDraft(reach, write.current));
92
112
  }
93
113
  function savedWithin(reach, room, result) {
94
114
  if (reach === null || result.outcome === 'saved')
@@ -107,76 +127,120 @@ function savedWithin(reach, room, result) {
107
127
  function statusWithin(reach, status) {
108
128
  return {
109
129
  ...status,
110
- attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
130
+ attentionRequired: status.attentionRequired.filter((entry) => 'refusal' in entry ? wholly(reach, entry.room) : covers(reach, entry.room, entry.path)),
111
131
  };
112
132
  }
113
133
  function queueWithin(reach, room, entries) {
114
- return entries.filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
115
- (change.kind !== 'move' || covers(reach, room, change.to))));
134
+ return entries
135
+ .filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
136
+ (change.kind !== 'move' || covers(reach, room, change.to))))
137
+ .map((entry) => {
138
+ const { between, ...batch } = entry.batch;
139
+ if (between === undefined)
140
+ return entry;
141
+ return wholly(reach, between.source.room) && wholly(reach, between.destination.room)
142
+ ? entry
143
+ : { ...entry, batch };
144
+ });
116
145
  }
117
146
  function unreached(room) {
118
- return new Error(`No Local Replica for ${room}`);
147
+ return runtimeError('room-not-held', `No Local Replica for ${room}`);
119
148
  }
120
- export function narrowed(runtime, given) {
149
+ function lockedError(room) {
150
+ return runtimeError(ROOM_LOCKED, `${room} is locked until House confirms its access`, true);
151
+ }
152
+ function deviceUnreached() {
153
+ return runtimeError('device-unreached', 'Only the panel cleans up this device');
154
+ }
155
+ export function narrowed(runtime, given, told) {
121
156
  const { md, watch } = runtime;
122
157
  let reach = given;
158
+ let port = told;
123
159
  const open = new Set();
124
- function following(begin) {
160
+ function following(begin, locking = false) {
125
161
  let stop = begin();
126
162
  const restart = () => {
127
163
  stop();
128
164
  stop = begin();
129
165
  };
130
166
  open.add(restart);
167
+ const unwatch = locking ? runtime.watchLocks(restart) : () => { };
131
168
  return () => {
132
169
  open.delete(restart);
170
+ unwatch();
133
171
  stop();
134
172
  };
135
173
  }
174
+ const unlocking = (rooms) => {
175
+ const locked = (rooms ?? []).filter((room) => runtime.locked(room));
176
+ return locked.length === 0
177
+ ? null
178
+ : { pending: locked.map((room) => ({ room, awaits: 'unlock' })) };
179
+ };
180
+ const unlocked = (entries) => entries.filter((entry) => !runtime.locked(entry.room));
181
+ const intoLocked = (held) => held.some((move) => move?.room !== undefined && runtime.locked(move.room));
136
182
  const within = (topic, input, deliver) => {
137
183
  if (topic === 'rooms') {
138
184
  return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
139
185
  }
186
+ if (topic === 'readiness') {
187
+ return following(() => runtime.watchReadiness(port, reach, deliver));
188
+ }
140
189
  if (topic === 'sync-status') {
141
- return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
190
+ return following(() => watch(topic, input, (status) => {
191
+ const within = statusWithin(reach, status);
192
+ deliver({ ...within, attentionRequired: unlocked(within.attentionRequired) });
193
+ }), true);
142
194
  }
143
195
  if (topic === 'room-drafts') {
144
- return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
196
+ return following(() => watch(topic, input, (drafts) => deliver(unlocked(draftsWithin(reach, drafts)))), true);
145
197
  }
146
198
  if (topic === 'queue') {
147
199
  const room = input[0];
148
200
  return following(() => {
149
- if (!holds(reach, room)) {
201
+ if (!holds(reach, room) || runtime.locked(room)) {
150
202
  deliver([]);
151
203
  return () => { };
152
204
  }
153
205
  return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
154
- });
206
+ }, true);
155
207
  }
156
208
  if (topic === 'query') {
157
209
  const plan = runtime.plan(input[0], reach);
158
210
  let held = null;
159
211
  return following(() => {
212
+ const locked = unlocking(plan.rooms);
213
+ if (locked !== null) {
214
+ const answer = { result: locked, keys: [] };
215
+ if (held === null || changeOf(held, answer) !== null)
216
+ deliver(answer);
217
+ held = digestOf(answer);
218
+ return () => { };
219
+ }
160
220
  let first = true;
161
- return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
221
+ let open = true;
222
+ const heard = (message) => {
162
223
  if (!first) {
163
- held = applied(held, message);
224
+ held = digestApplied(held, message);
164
225
  deliver(message);
165
226
  return;
166
227
  }
167
228
  first = false;
168
- if (held === null) {
169
- held = message;
170
- deliver(message);
171
- return;
172
- }
173
- const next = reconciled(held, message);
174
- if (next === null)
175
- return;
176
- held = next.keyed;
177
- deliver(next.change);
229
+ const change = held === null ? message : changeOf(held, message);
230
+ held = digestOf(message);
231
+ if (change !== null)
232
+ deliver(change);
233
+ };
234
+ const current = { ...plan, reach: planReach(reach), locked: runtime.lockedRooms() };
235
+ const stop = runtime.watchPlan(current, (message) => {
236
+ if (open)
237
+ heard(message);
178
238
  });
179
- });
239
+ return () => {
240
+ open = false;
241
+ stop();
242
+ };
243
+ }, true);
180
244
  }
181
245
  if (topic === 'conversation') {
182
246
  return following(() => {
@@ -186,7 +250,7 @@ export function narrowed(runtime, given) {
186
250
  return () => { };
187
251
  });
188
252
  }
189
- if (topic === 'conversations') {
253
+ if (topic === 'conversations' || topic === 'agents') {
190
254
  return following(() => {
191
255
  if (conversing(reach))
192
256
  return watch(topic, input, deliver);
@@ -196,14 +260,32 @@ export function narrowed(runtime, given) {
196
260
  }
197
261
  return watch(topic, input, deliver);
198
262
  };
263
+ const files = new Map();
264
+ let handed = 0;
265
+ const hand = async (reading) => {
266
+ const answer = await reading;
267
+ if (answer.kind !== 'binary')
268
+ return answer;
269
+ handed += 1;
270
+ const file = String(handed);
271
+ files.set(file, answer.file);
272
+ return { ...answer, file };
273
+ };
199
274
  const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
200
275
  const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
201
276
  const view = {
202
277
  ...md,
203
278
  rooms: () => roomsWithin(reach, md.rooms()),
204
- query: ((input) => runtime.query(runtime.plan(input, reach))),
205
- search: (input) => runtime.search(input, reach),
279
+ query: ((input) => {
280
+ const plan = runtime.plan(input, reach);
281
+ return Promise.resolve(unlocking(plan.rooms) ?? runtime.query({ ...plan, locked: runtime.lockedRooms() }));
282
+ }),
283
+ search: (input) => port.search === 'required'
284
+ ? runtime.search(input, reach, runtime.lockedRooms())
285
+ : Promise.resolve(undeclaredSearch()),
206
286
  types(room) {
287
+ if (holds(reach, room) && runtime.locked(room))
288
+ throw lockedError(room);
207
289
  if (wholly(reach, room))
208
290
  return md.types(room);
209
291
  if (!holds(reach, room))
@@ -214,30 +296,98 @@ export function narrowed(runtime, given) {
214
296
  async save(room, roomDrafts, writtenBy) {
215
297
  if (!holds(reach, room))
216
298
  throw unreached(room);
217
- const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
218
- !moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
219
- if (outside !== undefined)
220
- return { outcome: 'refused', rule: 'stale', file: outside.path };
299
+ if (runtime.locked(room))
300
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
301
+ const held = (await Promise.all(roomDrafts.map(({ path }) => md.roomDraft.read(room, path)))).map((draft) => draft?.parts.move);
302
+ const outside = roomDrafts.find(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]));
303
+ if (outside !== undefined) {
304
+ return { outcome: 'refused', code: 'stale', retryable: false, file: outside.path };
305
+ }
306
+ if (intoLocked(held))
307
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
221
308
  return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
222
309
  },
223
- history: (room, path, query) => covers(reach, room, path)
224
- ? md.history(room, path, query)
225
- : Promise.resolve({ kind: 'room-not-readable', locator: room }),
226
- reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
227
- md.reached(room, roomVersion, path),
310
+ async saveParts(room, parts, writtenBy) {
311
+ if (!holds(reach, room))
312
+ throw unreached(room);
313
+ if (runtime.locked(room))
314
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
315
+ const held = parts.map(({ parts: one }) => readParts(one).move);
316
+ if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
317
+ throw unreached(room);
318
+ }
319
+ if (intoLocked(held))
320
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
321
+ return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
322
+ },
323
+ resolve(room, path, destination) {
324
+ const at = runtime.located(room, path, destination);
325
+ return at !== null && !covers(reach, at.room, at.path)
326
+ ? Promise.resolve(UNREADABLE)
327
+ : md.resolve(room, path, destination);
328
+ },
329
+ history: (room, path, query) => !covers(reach, room, path)
330
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
331
+ : runtime.locked(room)
332
+ ? Promise.resolve(lockedRoom(room))
333
+ : md.history(room, path, query),
334
+ binaryFile: {
335
+ read: (room, path, revision) => !covers(reach, room, path)
336
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
337
+ : runtime.locked(room)
338
+ ? Promise.resolve(lockedRoom(room))
339
+ : hand(md.binaryFile.read(room, path, revision)),
340
+ link: (room, path, revision) => !covers(reach, room, path)
341
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
342
+ : runtime.locked(room)
343
+ ? Promise.resolve(lockedRoom(room))
344
+ : md.binaryFile.link(room, path, revision),
345
+ async part(file, part) {
346
+ const key = files.get(file);
347
+ if (key === undefined)
348
+ return null;
349
+ const bytes = await md.binaryFile.part(key, part);
350
+ if (bytes === null)
351
+ files.delete(file);
352
+ return bytes;
353
+ },
354
+ },
355
+ reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
356
+ !runtime.locked(room) &&
357
+ (await md.reached(room, roomVersion, path)),
228
358
  log: () => (reach === null ? md.log() : ''),
359
+ cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
229
360
  roomDraft: {
230
- list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
231
- read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
232
- async write(room, path, revision, parts) {
233
- if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
234
- throw unreached(room);
361
+ list: () => md.roomDraft.list().then((drafts) => unlocked(draftsWithin(reach, drafts))),
362
+ async read(room, path) {
363
+ if (!covers(reach, room, path))
364
+ return null;
365
+ if (runtime.locked(room))
366
+ throw lockedError(room);
367
+ return shownDraft(reach, await md.roomDraft.read(room, path));
368
+ },
369
+ write(room, path, revision, parts, next) {
370
+ try {
371
+ const move = readParts(parts).move;
372
+ if (!covers(reach, room, path) || !moves(reach, room, move))
373
+ throw unreached(room);
374
+ if (runtime.locked(room))
375
+ throw lockedError(room);
376
+ if (intoLocked([move]))
377
+ throw lockedError(move.room);
378
+ }
379
+ catch (error) {
380
+ return Promise.reject(error);
235
381
  }
236
- return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
382
+ return md.roomDraft
383
+ .write(room, path, revision, parts, next)
384
+ .then((written) => writeWithin(reach, written));
237
385
  },
238
386
  async discard(room, path, revision) {
239
387
  if (!covers(reach, room, path))
240
- return { outcome: 'stale', current: null };
388
+ return staleWrite(null);
389
+ if (runtime.locked(room))
390
+ throw lockedError(room);
241
391
  return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
242
392
  },
243
393
  },
@@ -245,7 +395,12 @@ export function narrowed(runtime, given) {
245
395
  list: () => conversation().list(),
246
396
  read: (id, options) => conversation().read(id, options),
247
397
  create: (input) => conversation().create(input),
248
- send: (id) => conversation().send(id),
398
+ send: (id, operation, draft) => conversation().send(id, operation, draft),
399
+ attach: (room, file) => !wholly(reach, room)
400
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
401
+ : runtime.locked(room)
402
+ ? Promise.resolve(lockedRoom(room))
403
+ : conversation().attach(room, file),
249
404
  answer: (id, answer) => conversation().answer(id, answer),
250
405
  interrupt: (id) => conversation().interrupt(id),
251
406
  kill: (id) => conversation().kill(id),
@@ -253,7 +408,8 @@ export function narrowed(runtime, given) {
253
408
  delete: (id) => conversation().delete(id),
254
409
  },
255
410
  chatDraft: {
256
- write: (id, draft) => chatDraft().write(id, draft),
411
+ write: (id, draft, revision) => chatDraft().write(id, draft, revision),
412
+ prepend: (id, draft) => chatDraft().prepend(id, draft),
257
413
  discard: (id) => chatDraft().discard(id),
258
414
  },
259
415
  observe: observing(within),
@@ -266,5 +422,10 @@ export function narrowed(runtime, given) {
266
422
  for (const restart of [...open])
267
423
  restart();
268
424
  },
425
+ facts(next) {
426
+ port = next;
427
+ for (const restart of [...open])
428
+ restart();
429
+ },
269
430
  };
270
431
  }
package/dist/react.d.ts CHANGED
@@ -1,32 +1,43 @@
1
1
  import type { MdRuntimeClient } from './client.js';
2
- import type { AppRoom, ConnectionRefusal, SaveResult } from './connection.js';
3
- import type { ServiceMode } from './adapter.js';
4
- import type { ConversationEntry, ConversationOptions, ConversationView } from './conversation.js';
2
+ import type { AppRoom, ConnectionRefusal, SavedRoomDraft, SaveResult } from './connection.js';
3
+ import type { Member, ServiceMode } from './adapter.js';
4
+ import type { ConversationAgent, ConversationEntry, ConversationOptions, ConversationView } from './conversation.js';
5
+ import { type RuntimeError } from './failure.js';
5
6
  import type { QueueEntry } from './outcome.js';
6
- import type { DocumentQuery, DocumentResult, GroupQuery, GroupResult } from './query.js';
7
+ import type { DocumentQuery, DocumentResult, GroupQuery, GroupResult, PendingResult } from './query.js';
8
+ import type { Readiness } from './readiness.js';
7
9
  import type { RoomDraft, RoomDraftParts } from './room-draft.js';
8
10
  import type { SyncStatus } from './status.js';
9
11
  type Client = MdRuntimeClient | null;
10
- export declare function useObserve(client: Client, topic: 'deployment-facts'): unknown;
11
- export declare function useObserve(client: Client, topic: 'rooms'): readonly AppRoom[] | undefined;
12
- export declare function useObserve(client: Client, topic: 'connection-refusal'): ConnectionRefusal | null | undefined;
13
- export declare function useObserve(client: Client, topic: 'storage-loss'): string | null | undefined;
14
- export declare function useObserve(client: Client, topic: 'sync-status'): SyncStatus | undefined;
15
- export declare function useObserve(client: Client, topic: 'service-mode'): ServiceMode | undefined;
16
- export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): DocumentResult | undefined;
17
- export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): GroupResult | undefined;
18
- export declare function useObserve(client: Client, topic: 'queue', locator: string): readonly QueueEntry[] | undefined;
19
- export declare function useObserve(client: Client, topic: 'room-drafts'): readonly RoomDraft[] | undefined;
20
- export declare function useObserve(client: Client, topic: 'presentation'): unknown;
21
- export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): ConversationView | undefined;
22
- export declare function useObserve(client: Client, topic: 'conversations'): readonly ConversationEntry[] | undefined;
12
+ export type Observed<T> = {
13
+ readonly state: 'loading';
14
+ } | {
15
+ readonly state: 'ready';
16
+ readonly value: T;
17
+ };
18
+ export declare function useObserve(client: Client, topic: 'deployment-facts'): Observed<unknown>;
19
+ export declare function useObserve(client: Client, topic: 'rooms'): Observed<readonly AppRoom[]>;
20
+ export declare function useObserve(client: Client, topic: 'readiness'): Observed<Readiness>;
21
+ export declare function useObserve(client: Client, topic: 'connection-refusal'): Observed<ConnectionRefusal | null>;
22
+ export declare function useObserve(client: Client, topic: 'storage-loss'): Observed<string | null>;
23
+ export declare function useObserve(client: Client, topic: 'sync-status'): Observed<SyncStatus>;
24
+ export declare function useObserve(client: Client, topic: 'service-mode'): Observed<ServiceMode>;
25
+ export declare function useObserve(client: Client, topic: 'member'): Observed<Member | null>;
26
+ export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): Observed<DocumentResult | PendingResult>;
27
+ export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): Observed<GroupResult | PendingResult>;
28
+ export declare function useObserve(client: Client, topic: 'queue', locator: string): Observed<readonly QueueEntry[]>;
29
+ export declare function useObserve(client: Client, topic: 'room-drafts'): Observed<readonly RoomDraft[]>;
30
+ export declare function useObserve(client: Client, topic: 'presentation'): Observed<unknown>;
31
+ export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): Observed<ConversationView>;
32
+ export declare function useObserve(client: Client, topic: 'conversations'): Observed<readonly ConversationEntry[]>;
33
+ export declare function useObserve(client: Client, topic: 'agents'): Observed<readonly ConversationAgent[]>;
23
34
  export type Saving = {
24
35
  readonly busy: boolean;
25
36
  readonly outcome: SaveResult | null;
26
- readonly failure: Error | null;
27
- save(room: string, roomDrafts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
37
+ readonly failure: RuntimeError | null;
38
+ save(room: string, parts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
39
+ saveWritten(room: string, roomDrafts: readonly SavedRoomDraft[]): Promise<SaveResult>;
28
40
  };
29
- export declare function saveRoomDrafts(client: MdRuntimeClient, room: string, roomDrafts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
30
41
  export declare function useSave(client: MdRuntimeClient): Saving;
31
42
  export {};
32
43
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"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"}
package/dist/react.js CHANGED
@@ -1,10 +1,12 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
1
+ import { useCallback, useLayoutEffect, useRef, useState } from 'react';
2
+ import { failed } from './failure.js';
3
+ const LOADING = { state: 'loading' };
2
4
  export function useObserve(client, topic, ...input) {
3
5
  const key = JSON.stringify([topic, ...input]);
4
6
  const [held, setHeld] = useState(null);
5
7
  const opened = useRef(input);
6
8
  opened.current = input;
7
- useEffect(() => {
9
+ useLayoutEffect(() => {
8
10
  if (client === null)
9
11
  return;
10
12
  let live = true;
@@ -18,7 +20,7 @@ export function useObserve(client, topic, ...input) {
18
20
  observation = open;
19
21
  const take = (value) => {
20
22
  if (live)
21
- setHeld({ client, key, value });
23
+ setHeld({ client, key, observed: { state: 'ready', value } });
22
24
  };
23
25
  take(open.current());
24
26
  open.subscribe(take);
@@ -28,35 +30,25 @@ export function useObserve(client, topic, ...input) {
28
30
  observation?.close();
29
31
  };
30
32
  }, [client, key]);
31
- return held !== null && held.client === client && held.key === key ? held.value : undefined;
33
+ return held !== null && held.client === client && held.key === key ? held.observed : LOADING;
32
34
  }
33
35
  const IDLE = { busy: false, outcome: null, failure: null };
34
- export async function saveRoomDrafts(client, room, roomDrafts) {
35
- const kept = [];
36
- for (const [path, parts] of roomDrafts) {
37
- const prior = await client.roomDraft.read(room, path);
38
- const write = await client.roomDraft.write(room, path, prior?.revision ?? null, parts);
39
- if (write.outcome !== 'written' || write.roomDraft === null) {
40
- throw new Error(`the Room Draft at ${path} was not written`);
41
- }
42
- kept.push({ path, revision: write.roomDraft.revision });
43
- }
44
- return client.save(room, kept);
45
- }
46
36
  export function useSave(client) {
47
37
  const [state, setState] = useState(IDLE);
48
- const save = useCallback(async (room, roomDrafts) => {
38
+ const settled = useCallback(async (saving) => {
49
39
  setState({ busy: true, outcome: null, failure: null });
50
40
  try {
51
- const outcome = await saveRoomDrafts(client, room, roomDrafts);
41
+ const outcome = await saving();
52
42
  setState({ busy: false, outcome, failure: null });
53
43
  return outcome;
54
44
  }
55
45
  catch (error) {
56
- const failure = error instanceof Error ? error : new Error(String(error));
46
+ const failure = failed(error);
57
47
  setState({ busy: false, outcome: null, failure });
58
48
  throw failure;
59
49
  }
60
- }, [client]);
61
- return { ...state, save };
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 };
62
54
  }