@agentshouse/mdruntime 0.1.0-c6e4c19c13638 → 0.1.0-c6f02ec9468d4

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 (184) hide show
  1. package/README.md +23 -10
  2. package/dist/adapter.d.ts +156 -43
  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 +53 -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 +33 -5
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +201 -48
  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 +37 -16
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +156 -20
  28. package/dist/collection.d.ts +12 -14
  29. package/dist/collection.d.ts.map +1 -1
  30. package/dist/collection.js +52 -56
  31. package/dist/compile.d.ts +16 -0
  32. package/dist/compile.d.ts.map +1 -0
  33. package/dist/compile.js +786 -0
  34. package/dist/connection.d.ts +61 -20
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +3877 -885
  37. package/dist/conversation.d.ts +48 -19
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +723 -240
  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 +4 -1
  44. package/dist/durability.d.ts.map +1 -1
  45. package/dist/durability.js +5 -0
  46. package/dist/entry.d.ts +20 -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 -19
  59. package/dist/file.d.ts.map +1 -1
  60. package/dist/file.js +7 -122
  61. package/dist/fulltext.d.ts +27 -0
  62. package/dist/fulltext.d.ts.map +1 -0
  63. package/dist/fulltext.js +370 -0
  64. package/dist/host.d.ts +13 -2
  65. package/dist/host.d.ts.map +1 -1
  66. package/dist/host.js +134 -24
  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 +9 -0
  73. package/dist/live.d.ts +22 -0
  74. package/dist/live.d.ts.map +1 -0
  75. package/dist/live.js +123 -0
  76. package/dist/media.d.ts +8 -0
  77. package/dist/media.d.ts.map +1 -0
  78. package/dist/media.js +218 -0
  79. package/dist/memory.d.ts +18 -7
  80. package/dist/memory.d.ts.map +1 -1
  81. package/dist/memory.js +252 -92
  82. package/dist/messages.d.ts +28 -0
  83. package/dist/messages.d.ts.map +1 -0
  84. package/dist/messages.js +90 -0
  85. package/dist/model.d.ts +19 -4
  86. package/dist/model.d.ts.map +1 -1
  87. package/dist/model.js +10 -2
  88. package/dist/name.d.ts +4 -0
  89. package/dist/name.d.ts.map +1 -0
  90. package/dist/name.js +122 -0
  91. package/dist/observation.d.ts +20 -2
  92. package/dist/observation.d.ts.map +1 -1
  93. package/dist/observation.js +113 -12
  94. package/dist/open.d.ts +2 -0
  95. package/dist/open.d.ts.map +1 -0
  96. package/dist/open.js +1 -0
  97. package/dist/opened.d.ts +22 -0
  98. package/dist/opened.d.ts.map +1 -0
  99. package/dist/opened.js +107 -0
  100. package/dist/outcome.d.ts +72 -5
  101. package/dist/outcome.d.ts.map +1 -1
  102. package/dist/outcome.js +81 -1
  103. package/dist/overlay.d.ts +15 -5
  104. package/dist/overlay.d.ts.map +1 -1
  105. package/dist/overlay.js +136 -180
  106. package/dist/paths.d.ts +1 -0
  107. package/dist/paths.d.ts.map +1 -1
  108. package/dist/paths.js +4 -0
  109. package/dist/query.d.ts +70 -4
  110. package/dist/query.d.ts.map +1 -1
  111. package/dist/query.js +53 -38
  112. package/dist/reach.d.ts +18 -4
  113. package/dist/reach.d.ts.map +1 -1
  114. package/dist/reach.js +235 -64
  115. package/dist/react.d.ts +32 -19
  116. package/dist/react.d.ts.map +1 -1
  117. package/dist/react.js +13 -21
  118. package/dist/readiness.d.ts +52 -0
  119. package/dist/readiness.d.ts.map +1 -0
  120. package/dist/readiness.js +27 -0
  121. package/dist/recovery.d.ts +26 -0
  122. package/dist/recovery.d.ts.map +1 -0
  123. package/dist/recovery.js +224 -0
  124. package/dist/refresh.d.ts +20 -7
  125. package/dist/refresh.d.ts.map +1 -1
  126. package/dist/refresh.js +148 -59
  127. package/dist/replica.d.ts +58 -33
  128. package/dist/replica.d.ts.map +1 -1
  129. package/dist/replica.js +320 -157
  130. package/dist/room-draft.d.ts +57 -10
  131. package/dist/room-draft.d.ts.map +1 -1
  132. package/dist/room-draft.js +222 -39
  133. package/dist/rows.d.ts +39 -35
  134. package/dist/rows.d.ts.map +1 -1
  135. package/dist/rows.js +176 -99
  136. package/dist/save.d.ts +73 -4
  137. package/dist/save.d.ts.map +1 -1
  138. package/dist/save.js +825 -150
  139. package/dist/search.d.ts +41 -29
  140. package/dist/search.d.ts.map +1 -1
  141. package/dist/search.js +37 -105
  142. package/dist/seed.d.ts +16 -0
  143. package/dist/seed.d.ts.map +1 -0
  144. package/dist/seed.js +75 -0
  145. package/dist/shared.d.ts +15 -0
  146. package/dist/shared.d.ts.map +1 -0
  147. package/dist/shared.js +421 -0
  148. package/dist/status.d.ts +7 -2
  149. package/dist/status.d.ts.map +1 -1
  150. package/dist/status.js +3 -9
  151. package/dist/store.d.ts +110 -59
  152. package/dist/store.d.ts.map +1 -1
  153. package/dist/store.js +324 -344
  154. package/dist/stored.d.ts +130 -0
  155. package/dist/stored.d.ts.map +1 -0
  156. package/dist/stored.js +560 -0
  157. package/dist/tables.d.ts +325 -0
  158. package/dist/tables.d.ts.map +1 -0
  159. package/dist/tables.js +578 -0
  160. package/dist/transfer.d.ts +0 -5
  161. package/dist/transfer.d.ts.map +1 -1
  162. package/dist/turn.d.ts +9 -0
  163. package/dist/turn.d.ts.map +1 -0
  164. package/dist/turn.js +75 -0
  165. package/dist/validation.d.ts +29 -0
  166. package/dist/validation.d.ts.map +1 -0
  167. package/dist/validation.js +101 -0
  168. package/dist/windows.d.ts +22 -0
  169. package/dist/windows.d.ts.map +1 -0
  170. package/dist/windows.js +89 -0
  171. package/dist/wire.d.ts +170 -33
  172. package/dist/wire.d.ts.map +1 -1
  173. package/dist/wire.js +69 -31
  174. package/dist/words.d.ts +19 -0
  175. package/dist/words.d.ts.map +1 -0
  176. package/dist/words.js +156 -0
  177. package/package.json +26 -15
  178. package/AGENTS.md +0 -241
  179. package/dist/attachment.d.ts +0 -3
  180. package/dist/attachment.d.ts.map +0 -1
  181. package/dist/attachment.js +0 -8
  182. package/dist/read-side.d.ts +0 -18
  183. package/dist/read-side.d.ts.map +0 -1
  184. package/dist/read-side.js +0 -433
package/dist/reach.js CHANGED
@@ -1,30 +1,39 @@
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('conversations/create'),
18
+ send: unconversed('conversations/send'),
19
+ answer: unconversed('conversations/interaction/answer'),
20
+ interrupt: unconversed('conversations/interrupt'),
21
+ kill: unconversed('conversations/kill'),
22
+ delete: unconversed('conversations/delete'),
23
23
  };
24
24
  const NO_CHAT_DRAFT = {
25
25
  write: () => Promise.resolve(),
26
+ prepend: () => Promise.resolve(),
26
27
  discard: () => Promise.resolve(),
27
28
  };
29
+ function areasOf(reach) {
30
+ return reach.areas ?? [];
31
+ }
32
+ function fenced(reach, room, path) {
33
+ return areasOf(reach).some((area) => area.room === room &&
34
+ beneath(area.path, path) &&
35
+ !area.open.some((open) => beneath(open, path)));
36
+ }
28
37
  export function holds(reach, room) {
29
38
  return (reach === null ||
30
39
  reach.rooms.includes(room) ||
@@ -45,8 +54,9 @@ export function governs(reach, room) {
45
54
  }
46
55
  function covers(reach, room, path) {
47
56
  return (reach === null ||
48
- reach.rooms.includes(room) ||
49
- reach.documents.some((document) => document.room === room && document.path === path));
57
+ (!fenced(reach, room, path) &&
58
+ (reach.rooms.includes(room) ||
59
+ reach.documents.some((document) => document.room === room && document.path === path))));
50
60
  }
51
61
  function conversing(reach) {
52
62
  return reach === null || reach.conversations;
@@ -59,6 +69,9 @@ export function planReach(reach) {
59
69
  documents: [
60
70
  ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
61
71
  ].sort(),
72
+ areas: areasOf(reach)
73
+ .map(({ room, path, open }) => ({ room, path, open: [...new Set(open)].sort() }))
74
+ .sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
62
75
  };
63
76
  }
64
77
  function roomsWithin(reach, rooms) {
@@ -74,7 +87,7 @@ function typesWithin(reach, room, types) {
74
87
  };
75
88
  }
76
89
  function moves(reach, room, move) {
77
- return move === undefined || covers(reach, room, moveTarget(move));
90
+ return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
78
91
  }
79
92
  function draftWithin(reach, draft) {
80
93
  return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
@@ -88,7 +101,7 @@ function shownDraft(reach, draft) {
88
101
  function writeWithin(reach, write) {
89
102
  return write.outcome === 'written'
90
103
  ? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
91
- : { outcome: 'stale', current: shownDraft(reach, write.current) };
104
+ : staleWrite(shownDraft(reach, write.current));
92
105
  }
93
106
  function savedWithin(reach, room, result) {
94
107
  if (reach === null || result.outcome === 'saved')
@@ -108,75 +121,127 @@ function statusWithin(reach, status) {
108
121
  return {
109
122
  ...status,
110
123
  attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
124
+ unsent: status.unsent.filter((change) => covers(reach, change.room, change.path)),
111
125
  };
112
126
  }
113
127
  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))));
128
+ return entries
129
+ .filter(({ batch }) => batch.changes.every((change) => covers(reach, room, change.path) &&
130
+ (change.kind !== 'move' || covers(reach, room, change.to))))
131
+ .map((entry) => {
132
+ const { between, ...batch } = entry.batch;
133
+ if (between === undefined)
134
+ return entry;
135
+ return wholly(reach, between.source.room) && wholly(reach, between.destination.room)
136
+ ? entry
137
+ : { ...entry, batch };
138
+ });
116
139
  }
117
140
  function unreached(room) {
118
- return new Error(`No Local Replica for ${room}`);
141
+ return runtimeError('unknown-room', `Unknown Room ${room}`);
142
+ }
143
+ function lockedError(room) {
144
+ return runtimeError(ROOM_LOCKED, `${room} is locked until House confirms its access`, true);
119
145
  }
120
- export function narrowed(runtime, given) {
146
+ function deviceUnreached() {
147
+ return runtimeError('device-unreached', 'Only the panel cleans up this device');
148
+ }
149
+ const ATTACH_REQUIRED = 'attach-required';
150
+ function attachRequired() {
151
+ return runtimeError(ATTACH_REQUIRED, 'App code takes a file in with binaryFile.attach');
152
+ }
153
+ function carriesFile(parts) {
154
+ return parts !== null && typeof parts === 'object' && Object.hasOwn(parts, 'file');
155
+ }
156
+ export function narrowed(runtime, given, told) {
121
157
  const { md, watch } = runtime;
122
158
  let reach = given;
159
+ let port = told;
123
160
  const open = new Set();
124
- function following(begin) {
161
+ function following(begin, locking = false) {
125
162
  let stop = begin();
126
163
  const restart = () => {
127
164
  stop();
128
165
  stop = begin();
129
166
  };
130
167
  open.add(restart);
168
+ const unwatch = locking ? runtime.watchLocks(restart) : () => { };
131
169
  return () => {
132
170
  open.delete(restart);
171
+ unwatch();
133
172
  stop();
134
173
  };
135
174
  }
175
+ const unlocking = (rooms) => {
176
+ const locked = (rooms ?? []).filter((room) => runtime.locked(room));
177
+ return locked.length === 0
178
+ ? null
179
+ : { pending: locked.map((room) => ({ room, awaits: 'unlock' })) };
180
+ };
181
+ const unlocked = (entries) => entries.filter((entry) => !runtime.locked(entry.room));
182
+ const intoLocked = (held) => held.some((move) => move?.room !== undefined && runtime.locked(move.room));
136
183
  const within = (topic, input, deliver) => {
137
184
  if (topic === 'rooms') {
138
185
  return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
139
186
  }
187
+ if (topic === 'readiness') {
188
+ return following(() => runtime.watchReadiness(port, reach, deliver));
189
+ }
140
190
  if (topic === 'sync-status') {
141
- return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
191
+ return following(() => watch(topic, input, (status) => {
192
+ const within = statusWithin(reach, status);
193
+ deliver({ ...within, attentionRequired: unlocked(within.attentionRequired) });
194
+ }), true);
142
195
  }
143
196
  if (topic === 'room-drafts') {
144
- return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
197
+ return following(() => watch(topic, input, (drafts) => deliver(unlocked(draftsWithin(reach, drafts)))), true);
145
198
  }
146
199
  if (topic === 'queue') {
147
200
  const room = input[0];
148
201
  return following(() => {
149
- if (!holds(reach, room)) {
202
+ if (!holds(reach, room) || runtime.locked(room)) {
150
203
  deliver([]);
151
204
  return () => { };
152
205
  }
153
206
  return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
154
- });
207
+ }, true);
155
208
  }
156
209
  if (topic === 'query') {
157
210
  const plan = runtime.plan(input[0], reach);
158
211
  let held = null;
159
212
  return following(() => {
213
+ const locked = unlocking(plan.rooms);
214
+ if (locked !== null) {
215
+ const answer = { result: locked, keys: [] };
216
+ if (held === null || changeOf(held, answer) !== null)
217
+ deliver(answer);
218
+ held = digestOf(answer);
219
+ return () => { };
220
+ }
160
221
  let first = true;
161
- return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
222
+ let open = true;
223
+ const heard = (message) => {
162
224
  if (!first) {
163
- held = applied(held, message);
225
+ held = digestApplied(held, message);
164
226
  deliver(message);
165
227
  return;
166
228
  }
167
229
  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);
230
+ const change = held === null ? message : changeOf(held, message);
231
+ held = digestOf(message);
232
+ if (change !== null)
233
+ deliver(change);
234
+ };
235
+ const current = { ...plan, reach: planReach(reach), locked: runtime.lockedRooms() };
236
+ const stop = runtime.watchPlan(current, (message) => {
237
+ if (open)
238
+ heard(message);
178
239
  });
179
- });
240
+ return () => {
241
+ open = false;
242
+ stop();
243
+ };
244
+ }, true);
180
245
  }
181
246
  if (topic === 'conversation') {
182
247
  return following(() => {
@@ -186,7 +251,7 @@ export function narrowed(runtime, given) {
186
251
  return () => { };
187
252
  });
188
253
  }
189
- if (topic === 'conversations') {
254
+ if (topic === 'conversations' || topic === 'agents') {
190
255
  return following(() => {
191
256
  if (conversing(reach))
192
257
  return watch(topic, input, deliver);
@@ -196,48 +261,149 @@ export function narrowed(runtime, given) {
196
261
  }
197
262
  return watch(topic, input, deliver);
198
263
  };
264
+ const files = new Map();
265
+ let handed = 0;
266
+ async function hand(reading) {
267
+ const answer = await reading;
268
+ if (answer.kind !== 'binary')
269
+ return answer;
270
+ handed += 1;
271
+ const file = String(handed);
272
+ files.set(file, answer.file);
273
+ return { ...answer, file };
274
+ }
199
275
  const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
200
276
  const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
201
277
  const view = {
202
278
  ...md,
203
- rooms: () => roomsWithin(reach, md.rooms()),
204
- query: ((input) => runtime.query(runtime.plan(input, reach))),
205
- search: (input) => runtime.search(input, reach),
279
+ rooms: () => md.rooms().then((rooms) => roomsWithin(reach, rooms)),
280
+ query: ((input) => {
281
+ const plan = runtime.plan(input, reach);
282
+ return Promise.resolve(unlocking(plan.rooms) ?? runtime.query({ ...plan, locked: runtime.lockedRooms() }));
283
+ }),
284
+ search: (input) => port.search === 'required'
285
+ ? runtime.search(input, reach, runtime.lockedRooms())
286
+ : Promise.resolve(undeclaredSearch()),
206
287
  types(room) {
288
+ if (holds(reach, room) && runtime.locked(room))
289
+ throw lockedError(room);
207
290
  if (wholly(reach, room))
208
291
  return md.types(room);
209
292
  if (!holds(reach, room))
210
293
  return structuredClone(UNHELD_TYPES);
211
- const listed = md.rooms().find(({ locator }) => locator === room);
212
- return typesWithin(reach, listed, md.types(room));
294
+ return typesWithin(reach, runtime.room(room), md.types(room));
213
295
  },
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
+ const filed = reach === null ? undefined : parts.find(({ parts: one }) => carriesFile(one));
314
+ if (filed !== undefined) {
315
+ return { outcome: 'refused', code: ATTACH_REQUIRED, retryable: false, file: filed.path };
316
+ }
317
+ if (runtime.locked(room))
318
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
319
+ const held = parts.map(({ parts: one }) => readParts(one).move);
320
+ if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
321
+ throw unreached(room);
322
+ }
323
+ if (intoLocked(held))
324
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
325
+ return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
326
+ },
327
+ resolve(room, path, destination) {
328
+ const at = runtime.located(room, path, destination);
329
+ return at !== null && !covers(reach, at.room, at.path)
330
+ ? Promise.resolve(UNREADABLE)
331
+ : md.resolve(room, path, destination);
332
+ },
333
+ history: (room, path, query) => !covers(reach, room, path)
334
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
335
+ : runtime.locked(room)
336
+ ? Promise.resolve(lockedRoom(room))
337
+ : md.history(room, path, query),
338
+ binaryFile: {
339
+ read: (room, path, revision) => !covers(reach, room, path)
340
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
341
+ : runtime.locked(room)
342
+ ? Promise.resolve(lockedRoom(room))
343
+ : hand(md.binaryFile.read(room, path, revision)),
344
+ link: (room, path, revision) => !covers(reach, room, path)
345
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
346
+ : runtime.locked(room)
347
+ ? Promise.resolve(lockedRoom(room))
348
+ : md.binaryFile.link(room, path, revision),
349
+ show: (room, path, revision) => !covers(reach, room, path)
350
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
351
+ : runtime.locked(room)
352
+ ? Promise.resolve(lockedRoom(room))
353
+ : hand(md.binaryFile.show(room, path, revision)),
354
+ attach: (room, file) => !wholly(reach, room)
355
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
356
+ : runtime.locked(room)
357
+ ? Promise.resolve(lockedRoom(room))
358
+ : md.binaryFile.attach(room, file),
359
+ async part(file, part) {
360
+ const key = files.get(file);
361
+ if (key === undefined)
362
+ return null;
363
+ const bytes = await md.binaryFile.part(key, part);
364
+ if (bytes === null)
365
+ files.delete(file);
366
+ return bytes;
367
+ },
368
+ },
369
+ reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
370
+ !runtime.locked(room) &&
371
+ (await md.reached(room, roomVersion, path)),
228
372
  log: () => (reach === null ? md.log() : ''),
373
+ cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
229
374
  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);
375
+ list: () => md.roomDraft.list().then((drafts) => unlocked(draftsWithin(reach, drafts))),
376
+ async read(room, path) {
377
+ if (!covers(reach, room, path))
378
+ return null;
379
+ if (runtime.locked(room))
380
+ throw lockedError(room);
381
+ return shownDraft(reach, await md.roomDraft.read(room, path));
382
+ },
383
+ write(room, path, revision, parts, next) {
384
+ try {
385
+ if (reach !== null && carriesFile(parts))
386
+ throw attachRequired();
387
+ const move = readParts(parts).move;
388
+ if (!covers(reach, room, path) || !moves(reach, room, move))
389
+ throw unreached(room);
390
+ if (runtime.locked(room))
391
+ throw lockedError(room);
392
+ if (intoLocked([move]))
393
+ throw lockedError(move.room);
394
+ }
395
+ catch (error) {
396
+ return Promise.reject(error);
235
397
  }
236
- return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
398
+ return md.roomDraft
399
+ .write(room, path, revision, parts, next)
400
+ .then((written) => writeWithin(reach, written));
237
401
  },
238
402
  async discard(room, path, revision) {
239
403
  if (!covers(reach, room, path))
240
- return { outcome: 'stale', current: null };
404
+ return staleWrite(null);
405
+ if (runtime.locked(room))
406
+ throw lockedError(room);
241
407
  return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
242
408
  },
243
409
  },
@@ -245,15 +411,15 @@ export function narrowed(runtime, given) {
245
411
  list: () => conversation().list(),
246
412
  read: (id, options) => conversation().read(id, options),
247
413
  create: (input) => conversation().create(input),
248
- send: (id) => conversation().send(id),
414
+ send: (id, operation, draft) => conversation().send(id, operation, draft),
249
415
  answer: (id, answer) => conversation().answer(id, answer),
250
416
  interrupt: (id) => conversation().interrupt(id),
251
417
  kill: (id) => conversation().kill(id),
252
- cancel: (id, turn) => conversation().cancel(id, turn),
253
418
  delete: (id) => conversation().delete(id),
254
419
  },
255
420
  chatDraft: {
256
- write: (id, draft) => chatDraft().write(id, draft),
421
+ write: (id, draft, revision) => chatDraft().write(id, draft, revision),
422
+ prepend: (id, draft) => chatDraft().prepend(id, draft),
257
423
  discard: (id) => chatDraft().discard(id),
258
424
  },
259
425
  observe: observing(within),
@@ -266,5 +432,10 @@ export function narrowed(runtime, given) {
266
432
  for (const restart of [...open])
267
433
  restart();
268
434
  },
435
+ facts(next) {
436
+ port = next;
437
+ for (const restart of [...open])
438
+ restart();
439
+ },
269
440
  };
270
441
  }
package/dist/react.d.ts CHANGED
@@ -1,30 +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: 'authority-changes'): Observed<number>;
27
+ export declare function useObserve(client: Client, topic: 'query', query: DocumentQuery): Observed<DocumentResult | PendingResult>;
28
+ export declare function useObserve(client: Client, topic: 'query', query: GroupQuery): Observed<GroupResult | PendingResult>;
29
+ export declare function useObserve(client: Client, topic: 'queue', locator: string): Observed<readonly QueueEntry[]>;
30
+ export declare function useObserve(client: Client, topic: 'room-drafts'): Observed<readonly RoomDraft[]>;
31
+ export declare function useObserve(client: Client, topic: 'presentation'): Observed<unknown>;
32
+ export declare function useObserve(client: Client, topic: 'conversation', conversation: string, options: ConversationOptions): Observed<ConversationView>;
33
+ export declare function useObserve(client: Client, topic: 'conversations'): Observed<readonly ConversationEntry[]>;
34
+ export declare function useObserve(client: Client, topic: 'agents'): Observed<readonly ConversationAgent[]>;
23
35
  export type Saving = {
24
36
  readonly busy: boolean;
25
37
  readonly outcome: SaveResult | null;
26
- readonly failure: Error | null;
27
- save(room: string, roomDrafts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
38
+ readonly failure: RuntimeError | null;
39
+ save(room: string, parts: ReadonlyMap<string, RoomDraftParts>): Promise<SaveResult>;
40
+ saveWritten(room: string, roomDrafts: readonly SavedRoomDraft[]): Promise<SaveResult>;
28
41
  };
29
42
  export declare function useSave(client: MdRuntimeClient): Saving;
30
43
  export {};
@@ -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
- async function written(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 written(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
  }
@@ -0,0 +1,52 @@
1
+ import type { Open } from './open.js';
2
+ export type CodeAvailability = Open<'stored' | 'absent'>;
3
+ export type RoomPreparation = Open<'complete' | 'acquiring' | 'preparing' | 'unadmitted'>;
4
+ export type RoomCoverage = {
5
+ readonly room: string;
6
+ readonly state: RoomPreparation;
7
+ };
8
+ export type ConversationCoverage = Open<'complete' | 'acquiring' | 'undeclared'>;
9
+ export type SearchObligation = Open<'required' | 'unused'>;
10
+ export type TargetCoverage = {
11
+ readonly complete: boolean;
12
+ readonly listed: boolean;
13
+ readonly rooms: readonly RoomCoverage[];
14
+ readonly conversations: ConversationCoverage;
15
+ readonly search: SearchObligation;
16
+ };
17
+ export type Persistence = Open<'granted' | 'denied' | 'unavailable'>;
18
+ export type Durability = Open<'persistent' | 'best-effort'>;
19
+ export type ConnectionAvailability = Open<'connected' | 'connecting' | 'unreachable'>;
20
+ export type HouseOutcome = Open<'open' | 'write-halted' | 'refused'>;
21
+ export type Space = Open<'full'>;
22
+ export type InstallationStep = Open<'downloading' | 'writing' | 'replaying'>;
23
+ export type InstallationAmount = {
24
+ readonly done: number;
25
+ readonly total: number;
26
+ };
27
+ export type Installation = {
28
+ readonly step: InstallationStep | null;
29
+ readonly rooms: InstallationAmount;
30
+ readonly bytes: InstallationAmount;
31
+ };
32
+ export type ReadinessStatus = Open<'preparing' | 'paused' | 'ready' | 'lost'>;
33
+ export type Readiness = {
34
+ readonly status: ReadinessStatus;
35
+ readonly ready: boolean;
36
+ readonly usable: boolean;
37
+ readonly code: CodeAvailability;
38
+ readonly coverage: TargetCoverage;
39
+ readonly durability: Durability;
40
+ readonly persistence: Persistence;
41
+ readonly connection: ConnectionAvailability;
42
+ readonly house: HouseOutcome;
43
+ readonly space: Space | null;
44
+ readonly installation: Installation;
45
+ readonly lost: boolean;
46
+ };
47
+ export type ReadinessFacts = Omit<Readiness, 'status' | 'ready' | 'usable' | 'durability' | 'coverage'> & {
48
+ readonly coverage: Omit<TargetCoverage, 'complete'>;
49
+ readonly established: boolean;
50
+ };
51
+ export declare function readinessOf(facts: ReadinessFacts): Readiness;
52
+ //# sourceMappingURL=readiness.d.ts.map