@agentshouse/mdruntime 0.1.0-cf6041a06849c → 0.1.0-cf6e25bcd52f6

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 (185) hide show
  1. package/README.md +23 -10
  2. package/dist/adapter.d.ts +144 -29
  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 +41 -6
  14. package/dist/bootstrap.d.ts.map +1 -1
  15. package/dist/bootstrap.js +224 -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 +156 -19
  28. package/dist/collection.d.ts +14 -13
  29. package/dist/collection.d.ts.map +1 -1
  30. package/dist/collection.js +66 -54
  31. package/dist/compile.d.ts +16 -0
  32. package/dist/compile.d.ts.map +1 -0
  33. package/dist/compile.js +765 -0
  34. package/dist/connection.d.ts +63 -20
  35. package/dist/connection.d.ts.map +1 -1
  36. package/dist/connection.js +3889 -743
  37. package/dist/conversation.d.ts +42 -12
  38. package/dist/conversation.d.ts.map +1 -1
  39. package/dist/conversation.js +642 -188
  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 -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 +368 -0
  64. package/dist/host.d.ts +15 -2
  65. package/dist/host.d.ts.map +1 -1
  66. package/dist/host.js +134 -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 +19 -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/media.d.ts +8 -0
  77. package/dist/media.d.ts.map +1 -0
  78. package/dist/media.js +208 -0
  79. package/dist/memory.d.ts +13 -5
  80. package/dist/memory.d.ts.map +1 -1
  81. package/dist/memory.js +140 -31
  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 +20 -4
  86. package/dist/model.d.ts.map +1 -1
  87. package/dist/model.js +19 -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 +73 -3
  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 -1
  107. package/dist/paths.d.ts.map +1 -1
  108. package/dist/paths.js +4 -16
  109. package/dist/query.d.ts +71 -5
  110. package/dist/query.d.ts.map +1 -1
  111. package/dist/query.js +100 -26
  112. package/dist/reach.d.ts +18 -4
  113. package/dist/reach.d.ts.map +1 -1
  114. package/dist/reach.js +247 -84
  115. package/dist/react.d.ts +31 -19
  116. package/dist/react.d.ts.map +1 -1
  117. package/dist/react.js +13 -21
  118. package/dist/readiness.d.ts +64 -0
  119. package/dist/readiness.d.ts.map +1 -0
  120. package/dist/readiness.js +33 -0
  121. package/dist/reads.d.ts +11 -0
  122. package/dist/reads.d.ts.map +1 -0
  123. package/dist/reads.js +100 -0
  124. package/dist/recovery.d.ts +25 -0
  125. package/dist/recovery.d.ts.map +1 -0
  126. package/dist/recovery.js +198 -0
  127. package/dist/refresh.d.ts +19 -8
  128. package/dist/refresh.d.ts.map +1 -1
  129. package/dist/refresh.js +125 -57
  130. package/dist/replica.d.ts +59 -33
  131. package/dist/replica.d.ts.map +1 -1
  132. package/dist/replica.js +322 -155
  133. package/dist/room-draft.d.ts +41 -9
  134. package/dist/room-draft.d.ts.map +1 -1
  135. package/dist/room-draft.js +186 -37
  136. package/dist/rows.d.ts +39 -34
  137. package/dist/rows.d.ts.map +1 -1
  138. package/dist/rows.js +197 -80
  139. package/dist/save.d.ts +74 -4
  140. package/dist/save.d.ts.map +1 -1
  141. package/dist/save.js +900 -178
  142. package/dist/search.d.ts +41 -29
  143. package/dist/search.d.ts.map +1 -1
  144. package/dist/search.js +37 -105
  145. package/dist/seed.d.ts +16 -0
  146. package/dist/seed.d.ts.map +1 -0
  147. package/dist/seed.js +75 -0
  148. package/dist/shared.d.ts +16 -0
  149. package/dist/shared.d.ts.map +1 -0
  150. package/dist/shared.js +431 -0
  151. package/dist/status.d.ts +11 -2
  152. package/dist/status.d.ts.map +1 -1
  153. package/dist/status.js +3 -9
  154. package/dist/store.d.ts +132 -58
  155. package/dist/store.d.ts.map +1 -1
  156. package/dist/store.js +379 -344
  157. package/dist/stored.d.ts +154 -0
  158. package/dist/stored.d.ts.map +1 -0
  159. package/dist/stored.js +578 -0
  160. package/dist/tables.d.ts +362 -0
  161. package/dist/tables.d.ts.map +1 -0
  162. package/dist/tables.js +627 -0
  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 +30 -0
  167. package/dist/validation.d.ts.map +1 -0
  168. package/dist/validation.js +162 -0
  169. package/dist/windows.d.ts +22 -0
  170. package/dist/windows.d.ts.map +1 -0
  171. package/dist/windows.js +89 -0
  172. package/dist/wire.d.ts +173 -33
  173. package/dist/wire.d.ts.map +1 -1
  174. package/dist/wire.js +69 -31
  175. package/dist/words.d.ts +19 -0
  176. package/dist/words.d.ts.map +1 -0
  177. package/dist/words.js +156 -0
  178. package/package.json +26 -15
  179. package/AGENTS.md +0 -231
  180. package/dist/attachment.d.ts +0 -3
  181. package/dist/attachment.d.ts.map +0 -1
  182. package/dist/attachment.js +0 -8
  183. package/dist/read-side.d.ts +0 -18
  184. package/dist/read-side.d.ts.map +0 -1
  185. package/dist/read-side.js +0 -377
package/dist/reach.js CHANGED
@@ -1,34 +1,46 @@
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
+ answer: unconversed('conversation.answer'),
20
+ interrupt: unconversed('conversation.interrupt'),
21
+ kill: unconversed('conversation.kill'),
22
+ cancel: unconversed('conversation.cancel'),
23
+ delete: unconversed('conversation.delete'),
23
24
  };
24
25
  const NO_CHAT_DRAFT = {
25
26
  write: () => Promise.resolve(),
27
+ prepend: () => Promise.resolve(),
26
28
  discard: () => Promise.resolve(),
27
29
  };
30
+ function areasOf(reach) {
31
+ return reach.areas ?? [];
32
+ }
33
+ function typedAreas(reach, room) {
34
+ return areasOf(reach).filter((area) => area.room === room && area.types.length > 0);
35
+ }
36
+ function fenced(reach, room, path) {
37
+ return areasOf(reach).some((area) => area.room === room && beneath(area.path, path));
38
+ }
28
39
  export function holds(reach, room) {
29
40
  return (reach === null ||
30
41
  reach.rooms.includes(room) ||
31
- reach.documents.some((document) => document.room === room));
42
+ reach.documents.some((document) => document.room === room) ||
43
+ typedAreas(reach, room).length > 0);
32
44
  }
33
45
  function wholly(reach, room) {
34
46
  return reach === null || reach.rooms.includes(room);
@@ -38,15 +50,19 @@ export function governs(reach, room) {
38
50
  return { collection: true, selection: room.typedSelection !== null };
39
51
  }
40
52
  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);
53
+ const paths = [
54
+ ...reach.documents
55
+ .filter((document) => document.room === room.locator)
56
+ .map(({ path }) => path),
57
+ ...typedAreas(reach, room.locator).map(({ path }) => path),
58
+ ];
44
59
  return { collection: paths.some((path) => !selected(path)), selection: paths.some(selected) };
45
60
  }
46
61
  function covers(reach, room, path) {
47
62
  return (reach === null ||
48
- reach.rooms.includes(room) ||
49
- reach.documents.some((document) => document.room === room && document.path === path));
63
+ (!fenced(reach, room, path) &&
64
+ (reach.rooms.includes(room) ||
65
+ reach.documents.some((document) => document.room === room && document.path === path))));
50
66
  }
51
67
  function conversing(reach) {
52
68
  return reach === null || reach.conversations;
@@ -59,25 +75,13 @@ export function planReach(reach) {
59
75
  documents: [
60
76
  ...new Set(reach.documents.map(({ room, path }) => documentKey(room, path))),
61
77
  ].sort(),
78
+ areas: areasOf(reach)
79
+ .map(({ room, path, types }) => ({ room, path, types: [...new Set(types)].sort() }))
80
+ .sort((left, right) => documentKey(left.room, left.path) < documentKey(right.room, right.path) ? -1 : 1),
62
81
  };
63
82
  }
64
83
  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
- });
84
+ return rooms.filter((room) => wholly(reach, room.locator));
81
85
  }
82
86
  function typesWithin(reach, room, types) {
83
87
  if (room === undefined)
@@ -89,7 +93,7 @@ function typesWithin(reach, room, types) {
89
93
  };
90
94
  }
91
95
  function moves(reach, room, move) {
92
- return move === undefined || covers(reach, room, moveTarget(move));
96
+ return move === undefined || covers(reach, move.room ?? room, moveTarget(move));
93
97
  }
94
98
  function draftWithin(reach, draft) {
95
99
  return covers(reach, draft.room, draft.path) && moves(reach, draft.room, draft.parts.move);
@@ -103,7 +107,7 @@ function shownDraft(reach, draft) {
103
107
  function writeWithin(reach, write) {
104
108
  return write.outcome === 'written'
105
109
  ? { outcome: 'written', roomDraft: shownDraft(reach, write.roomDraft) }
106
- : { outcome: 'stale', current: shownDraft(reach, write.current) };
110
+ : staleWrite(shownDraft(reach, write.current));
107
111
  }
108
112
  function savedWithin(reach, room, result) {
109
113
  if (reach === null || result.outcome === 'saved')
@@ -122,76 +126,128 @@ function savedWithin(reach, room, result) {
122
126
  function statusWithin(reach, status) {
123
127
  return {
124
128
  ...status,
125
- attentionRequired: status.attentionRequired.filter((entry) => covers(reach, entry.room, entry.path)),
129
+ attentionRequired: status.attentionRequired.filter((entry) => 'refusal' in entry ? wholly(reach, entry.room) : covers(reach, entry.room, entry.path)),
130
+ unsent: status.unsent.filter((change) => covers(reach, change.room, change.path)),
126
131
  };
127
132
  }
128
133
  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))));
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
+ });
131
145
  }
132
146
  function unreached(room) {
133
- return new Error(`No Local Replica for ${room}`);
147
+ return runtimeError('room-not-held', `No Local Replica for ${room}`);
148
+ }
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');
134
154
  }
135
- export function narrowed(runtime, given) {
155
+ const ATTACH_REQUIRED = 'attach-required';
156
+ function attachRequired() {
157
+ return runtimeError(ATTACH_REQUIRED, 'App code takes a file in with binaryFile.attach');
158
+ }
159
+ function carriesFile(parts) {
160
+ return parts !== null && typeof parts === 'object' && Object.hasOwn(parts, 'file');
161
+ }
162
+ export function narrowed(runtime, given, told) {
136
163
  const { md, watch } = runtime;
137
164
  let reach = given;
165
+ let port = told;
138
166
  const open = new Set();
139
- function following(begin) {
167
+ function following(begin, locking = false) {
140
168
  let stop = begin();
141
169
  const restart = () => {
142
170
  stop();
143
171
  stop = begin();
144
172
  };
145
173
  open.add(restart);
174
+ const unwatch = locking ? runtime.watchLocks(restart) : () => { };
146
175
  return () => {
147
176
  open.delete(restart);
177
+ unwatch();
148
178
  stop();
149
179
  };
150
180
  }
181
+ const unlocking = (rooms) => {
182
+ const locked = (rooms ?? []).filter((room) => runtime.locked(room));
183
+ return locked.length === 0
184
+ ? null
185
+ : { pending: locked.map((room) => ({ room, awaits: 'unlock' })) };
186
+ };
187
+ const unlocked = (entries) => entries.filter((entry) => !runtime.locked(entry.room));
188
+ const intoLocked = (held) => held.some((move) => move?.room !== undefined && runtime.locked(move.room));
151
189
  const within = (topic, input, deliver) => {
152
190
  if (topic === 'rooms') {
153
191
  return following(() => watch(topic, input, (rooms) => deliver(roomsWithin(reach, rooms))));
154
192
  }
193
+ if (topic === 'readiness') {
194
+ return following(() => runtime.watchReadiness(port, reach, deliver));
195
+ }
155
196
  if (topic === 'sync-status') {
156
- return following(() => watch(topic, input, (status) => deliver(statusWithin(reach, status))));
197
+ return following(() => watch(topic, input, (status) => {
198
+ const within = statusWithin(reach, status);
199
+ deliver({ ...within, attentionRequired: unlocked(within.attentionRequired) });
200
+ }), true);
157
201
  }
158
202
  if (topic === 'room-drafts') {
159
- return following(() => watch(topic, input, (drafts) => deliver(draftsWithin(reach, drafts))));
203
+ return following(() => watch(topic, input, (drafts) => deliver(unlocked(draftsWithin(reach, drafts)))), true);
160
204
  }
161
205
  if (topic === 'queue') {
162
206
  const room = input[0];
163
207
  return following(() => {
164
- if (!holds(reach, room)) {
208
+ if (!holds(reach, room) || runtime.locked(room)) {
165
209
  deliver([]);
166
210
  return () => { };
167
211
  }
168
212
  return watch(topic, input, (entries) => deliver(queueWithin(reach, room, entries)));
169
- });
213
+ }, true);
170
214
  }
171
215
  if (topic === 'query') {
172
216
  const plan = runtime.plan(input[0], reach);
173
217
  let held = null;
174
218
  return following(() => {
219
+ const locked = unlocking(plan.rooms);
220
+ if (locked !== null) {
221
+ const answer = { result: locked, keys: [] };
222
+ if (held === null || changeOf(held, answer) !== null)
223
+ deliver(answer);
224
+ held = digestOf(answer);
225
+ return () => { };
226
+ }
175
227
  let first = true;
176
- return runtime.watchPlan({ ...plan, reach: planReach(reach) }, (message) => {
228
+ let open = true;
229
+ const heard = (message) => {
177
230
  if (!first) {
178
- held = applied(held, message);
231
+ held = digestApplied(held, message);
179
232
  deliver(message);
180
233
  return;
181
234
  }
182
235
  first = false;
183
- if (held === null) {
184
- held = message;
185
- deliver(message);
186
- return;
187
- }
188
- const next = reconciled(held, message);
189
- if (next === null)
190
- return;
191
- held = next.keyed;
192
- deliver(next.change);
236
+ const change = held === null ? message : changeOf(held, message);
237
+ held = digestOf(message);
238
+ if (change !== null)
239
+ deliver(change);
240
+ };
241
+ const current = { ...plan, reach: planReach(reach), locked: runtime.lockedRooms() };
242
+ const stop = runtime.watchPlan(current, (message) => {
243
+ if (open)
244
+ heard(message);
193
245
  });
194
- });
246
+ return () => {
247
+ open = false;
248
+ stop();
249
+ };
250
+ }, true);
195
251
  }
196
252
  if (topic === 'conversation') {
197
253
  return following(() => {
@@ -201,7 +257,7 @@ export function narrowed(runtime, given) {
201
257
  return () => { };
202
258
  });
203
259
  }
204
- if (topic === 'conversations') {
260
+ if (topic === 'conversations' || topic === 'agents') {
205
261
  return following(() => {
206
262
  if (conversing(reach))
207
263
  return watch(topic, input, deliver);
@@ -211,48 +267,149 @@ export function narrowed(runtime, given) {
211
267
  }
212
268
  return watch(topic, input, deliver);
213
269
  };
270
+ const files = new Map();
271
+ let handed = 0;
272
+ async function hand(reading) {
273
+ const answer = await reading;
274
+ if (answer.kind !== 'binary')
275
+ return answer;
276
+ handed += 1;
277
+ const file = String(handed);
278
+ files.set(file, answer.file);
279
+ return { ...answer, file };
280
+ }
214
281
  const conversation = () => (conversing(reach) ? md.conversation : NO_CONVERSATION);
215
282
  const chatDraft = () => (conversing(reach) ? md.chatDraft : NO_CHAT_DRAFT);
216
283
  const view = {
217
284
  ...md,
218
- rooms: () => roomsWithin(reach, md.rooms()),
219
- query: ((input) => runtime.query(runtime.plan(input, reach))),
220
- search: (input) => runtime.search(input, reach),
285
+ rooms: () => md.rooms().then((rooms) => roomsWithin(reach, rooms)),
286
+ query: ((input) => {
287
+ const plan = runtime.plan(input, reach);
288
+ return Promise.resolve(unlocking(plan.rooms) ?? runtime.query({ ...plan, locked: runtime.lockedRooms() }));
289
+ }),
290
+ search: (input) => port.search === 'required'
291
+ ? runtime.search(input, reach, runtime.lockedRooms())
292
+ : Promise.resolve(undeclaredSearch()),
221
293
  types(room) {
294
+ if (holds(reach, room) && runtime.locked(room))
295
+ throw lockedError(room);
222
296
  if (wholly(reach, room))
223
297
  return md.types(room);
224
298
  if (!holds(reach, room))
225
299
  return structuredClone(UNHELD_TYPES);
226
- const listed = md.rooms().find(({ locator }) => locator === room);
227
- return typesWithin(reach, listed, md.types(room));
300
+ return typesWithin(reach, runtime.room(room), md.types(room));
228
301
  },
229
302
  async save(room, roomDrafts, writtenBy) {
230
303
  if (!holds(reach, room))
231
304
  throw unreached(room);
232
- const outside = roomDrafts.find(({ path }) => !covers(reach, room, path) ||
233
- !moves(reach, room, md.roomDraft.read(room, path)?.parts.move));
234
- if (outside !== undefined)
235
- return { outcome: 'refused', rule: 'stale', file: outside.path };
305
+ if (runtime.locked(room))
306
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
307
+ const held = (await Promise.all(roomDrafts.map(({ path }) => md.roomDraft.read(room, path)))).map((draft) => draft?.parts.move);
308
+ const outside = roomDrafts.find(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]));
309
+ if (outside !== undefined) {
310
+ return { outcome: 'refused', code: 'stale', retryable: false, file: outside.path };
311
+ }
312
+ if (intoLocked(held))
313
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
236
314
  return savedWithin(reach, room, await md.save(room, roomDrafts, writtenBy));
237
315
  },
238
- history: (room, path, query) => covers(reach, room, path)
239
- ? md.history(room, path, query)
240
- : Promise.resolve({ kind: 'room-not-readable', locator: room }),
241
- reached: (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
242
- md.reached(room, roomVersion, path),
316
+ async saveParts(room, parts, writtenBy) {
317
+ if (!holds(reach, room))
318
+ throw unreached(room);
319
+ const filed = reach === null ? undefined : parts.find(({ parts: one }) => carriesFile(one));
320
+ if (filed !== undefined) {
321
+ return { outcome: 'refused', code: ATTACH_REQUIRED, retryable: false, file: filed.path };
322
+ }
323
+ if (runtime.locked(room))
324
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
325
+ const held = parts.map(({ parts: one }) => readParts(one).move);
326
+ if (parts.some(({ path }, at) => !covers(reach, room, path) || !moves(reach, room, held[at]))) {
327
+ throw unreached(room);
328
+ }
329
+ if (intoLocked(held))
330
+ return { outcome: 'refused', code: ROOM_LOCKED, retryable: true };
331
+ return savedWithin(reach, room, await md.saveParts(room, parts, writtenBy));
332
+ },
333
+ resolve(room, path, destination) {
334
+ const at = runtime.located(room, path, destination);
335
+ return at !== null && !covers(reach, at.room, at.path)
336
+ ? Promise.resolve(UNREADABLE)
337
+ : md.resolve(room, path, destination);
338
+ },
339
+ history: (room, path, query) => !covers(reach, room, path)
340
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
341
+ : runtime.locked(room)
342
+ ? Promise.resolve(lockedRoom(room))
343
+ : md.history(room, path, query),
344
+ binaryFile: {
345
+ read: (room, path, revision) => !covers(reach, room, path)
346
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
347
+ : runtime.locked(room)
348
+ ? Promise.resolve(lockedRoom(room))
349
+ : hand(md.binaryFile.read(room, path, revision)),
350
+ link: (room, path, revision) => !covers(reach, room, path)
351
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
352
+ : runtime.locked(room)
353
+ ? Promise.resolve(lockedRoom(room))
354
+ : md.binaryFile.link(room, path, revision),
355
+ show: (room, path, revision) => !covers(reach, room, path)
356
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
357
+ : runtime.locked(room)
358
+ ? Promise.resolve(lockedRoom(room))
359
+ : hand(md.binaryFile.show(room, path, revision)),
360
+ attach: (room, file) => !wholly(reach, room)
361
+ ? Promise.resolve(failure({ kind: 'room-not-readable', locator: room }))
362
+ : runtime.locked(room)
363
+ ? Promise.resolve(lockedRoom(room))
364
+ : md.binaryFile.attach(room, file),
365
+ async part(file, part) {
366
+ const key = files.get(file);
367
+ if (key === undefined)
368
+ return null;
369
+ const bytes = await md.binaryFile.part(key, part);
370
+ if (bytes === null)
371
+ files.delete(file);
372
+ return bytes;
373
+ },
374
+ },
375
+ reached: async (room, roomVersion, path) => (path === undefined ? wholly(reach, room) : covers(reach, room, path)) &&
376
+ !runtime.locked(room) &&
377
+ (await md.reached(room, roomVersion, path)),
243
378
  log: () => (reach === null ? md.log() : ''),
379
+ cleanUp: () => (reach === null ? md.cleanUp() : Promise.reject(deviceUnreached())),
244
380
  roomDraft: {
245
- list: () => md.roomDraft.list().then((drafts) => draftsWithin(reach, drafts)),
246
- read: (room, path) => covers(reach, room, path) ? shownDraft(reach, md.roomDraft.read(room, path)) : null,
247
- async write(room, path, revision, parts) {
248
- if (!covers(reach, room, path) || !moves(reach, room, readParts(parts).move)) {
249
- throw unreached(room);
381
+ list: () => md.roomDraft.list().then((drafts) => unlocked(draftsWithin(reach, drafts))),
382
+ async read(room, path) {
383
+ if (!covers(reach, room, path))
384
+ return null;
385
+ if (runtime.locked(room))
386
+ throw lockedError(room);
387
+ return shownDraft(reach, await md.roomDraft.read(room, path));
388
+ },
389
+ write(room, path, revision, parts, next) {
390
+ try {
391
+ if (reach !== null && carriesFile(parts))
392
+ throw attachRequired();
393
+ const move = readParts(parts).move;
394
+ if (!covers(reach, room, path) || !moves(reach, room, move))
395
+ throw unreached(room);
396
+ if (runtime.locked(room))
397
+ throw lockedError(room);
398
+ if (intoLocked([move]))
399
+ throw lockedError(move.room);
250
400
  }
251
- return writeWithin(reach, await md.roomDraft.write(room, path, revision, parts));
401
+ catch (error) {
402
+ return Promise.reject(error);
403
+ }
404
+ return md.roomDraft
405
+ .write(room, path, revision, parts, next)
406
+ .then((written) => writeWithin(reach, written));
252
407
  },
253
408
  async discard(room, path, revision) {
254
409
  if (!covers(reach, room, path))
255
- return { outcome: 'stale', current: null };
410
+ return staleWrite(null);
411
+ if (runtime.locked(room))
412
+ throw lockedError(room);
256
413
  return writeWithin(reach, await md.roomDraft.discard(room, path, revision));
257
414
  },
258
415
  },
@@ -260,7 +417,7 @@ export function narrowed(runtime, given) {
260
417
  list: () => conversation().list(),
261
418
  read: (id, options) => conversation().read(id, options),
262
419
  create: (input) => conversation().create(input),
263
- send: (id) => conversation().send(id),
420
+ send: (id, operation, draft) => conversation().send(id, operation, draft),
264
421
  answer: (id, answer) => conversation().answer(id, answer),
265
422
  interrupt: (id) => conversation().interrupt(id),
266
423
  kill: (id) => conversation().kill(id),
@@ -268,7 +425,8 @@ export function narrowed(runtime, given) {
268
425
  delete: (id) => conversation().delete(id),
269
426
  },
270
427
  chatDraft: {
271
- write: (id, draft) => chatDraft().write(id, draft),
428
+ write: (id, draft, revision) => chatDraft().write(id, draft, revision),
429
+ prepend: (id, draft) => chatDraft().prepend(id, draft),
272
430
  discard: (id) => chatDraft().discard(id),
273
431
  },
274
432
  observe: observing(within),
@@ -281,5 +439,10 @@ export function narrowed(runtime, given) {
281
439
  for (const restart of [...open])
282
440
  restart();
283
441
  },
442
+ facts(next) {
443
+ port = next;
444
+ for (const restart of [...open])
445
+ restart();
446
+ },
284
447
  };
285
448
  }
package/dist/react.d.ts CHANGED
@@ -1,30 +1,42 @@
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
41
  export declare function useSave(client: MdRuntimeClient): Saving;
30
42
  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
  }