@animalabs/agent-framework 0.28.0 → 0.29.0

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 (240) hide show
  1. package/README.md +88 -25
  2. package/dist/src/agent.d.ts.map +1 -1
  3. package/dist/src/agent.js +4 -1
  4. package/dist/src/agent.js.map +1 -1
  5. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  6. package/dist/src/api/mcp-server-core.js +3 -12
  7. package/dist/src/api/mcp-server-core.js.map +1 -1
  8. package/dist/src/api/server.d.ts.map +1 -1
  9. package/dist/src/api/server.js +7 -3
  10. package/dist/src/api/server.js.map +1 -1
  11. package/dist/src/auto-continue.d.ts +2 -0
  12. package/dist/src/auto-continue.d.ts.map +1 -1
  13. package/dist/src/auto-continue.js +5 -2
  14. package/dist/src/auto-continue.js.map +1 -1
  15. package/dist/src/code-execution/isolate/runner.d.ts +7 -5
  16. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
  17. package/dist/src/code-execution/isolate/runner.js +7 -5
  18. package/dist/src/code-execution/isolate/runner.js.map +1 -1
  19. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  20. package/dist/src/code-execution/isolate/scripts.js +3 -2
  21. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  22. package/dist/src/framework.d.ts +164 -36
  23. package/dist/src/framework.d.ts.map +1 -1
  24. package/dist/src/framework.js +1120 -479
  25. package/dist/src/framework.js.map +1 -1
  26. package/dist/src/gate/event-gate.d.ts +34 -10
  27. package/dist/src/gate/event-gate.d.ts.map +1 -1
  28. package/dist/src/gate/event-gate.js +118 -48
  29. package/dist/src/gate/event-gate.js.map +1 -1
  30. package/dist/src/gate/index.d.ts +1 -1
  31. package/dist/src/gate/index.d.ts.map +1 -1
  32. package/dist/src/gate/index.js +1 -1
  33. package/dist/src/gate/index.js.map +1 -1
  34. package/dist/src/gate/types.d.ts +12 -2
  35. package/dist/src/gate/types.d.ts.map +1 -1
  36. package/dist/src/heartbeat/heartbeat.d.ts +18 -0
  37. package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
  38. package/dist/src/heartbeat/heartbeat.js +34 -8
  39. package/dist/src/heartbeat/heartbeat.js.map +1 -1
  40. package/dist/src/held-writes.d.ts +3 -3
  41. package/dist/src/held-writes.js +3 -3
  42. package/dist/src/host-presentation.d.ts +170 -0
  43. package/dist/src/host-presentation.d.ts.map +1 -0
  44. package/dist/src/host-presentation.js +561 -0
  45. package/dist/src/host-presentation.js.map +1 -0
  46. package/dist/src/index.d.ts +2 -2
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +1 -1
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/line-reader.d.ts +4 -0
  51. package/dist/src/line-reader.d.ts.map +1 -0
  52. package/dist/src/line-reader.js +59 -0
  53. package/dist/src/line-reader.js.map +1 -0
  54. package/dist/src/mcpl/channel-registry.d.ts +34 -6
  55. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  56. package/dist/src/mcpl/channel-registry.js +131 -53
  57. package/dist/src/mcpl/channel-registry.js.map +1 -1
  58. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  59. package/dist/src/mcpl/conversation-router.js +2 -1
  60. package/dist/src/mcpl/conversation-router.js.map +1 -1
  61. package/dist/src/mcpl/convert-block.d.ts +1 -0
  62. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  63. package/dist/src/mcpl/convert-block.js +16 -4
  64. package/dist/src/mcpl/convert-block.js.map +1 -1
  65. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  66. package/dist/src/mcpl/hook-orchestrator.js +4 -3
  67. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  68. package/dist/src/mcpl/inbound-source.d.ts +3 -1
  69. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  70. package/dist/src/mcpl/inbound-source.js +3 -1
  71. package/dist/src/mcpl/inbound-source.js.map +1 -1
  72. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  73. package/dist/src/mcpl/modern-connection.js +11 -7
  74. package/dist/src/mcpl/modern-connection.js.map +1 -1
  75. package/dist/src/mcpl/protocol-family.d.ts +3 -2
  76. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  77. package/dist/src/mcpl/protocol-family.js +25 -3
  78. package/dist/src/mcpl/protocol-family.js.map +1 -1
  79. package/dist/src/mcpl/push-handler.js +2 -2
  80. package/dist/src/mcpl/push-handler.js.map +1 -1
  81. package/dist/src/mcpl/references.d.ts +12 -40
  82. package/dist/src/mcpl/references.d.ts.map +1 -1
  83. package/dist/src/mcpl/references.js +29 -23
  84. package/dist/src/mcpl/references.js.map +1 -1
  85. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  86. package/dist/src/mcpl/server-connection.js +20 -20
  87. package/dist/src/mcpl/server-connection.js.map +1 -1
  88. package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
  89. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
  90. package/dist/src/mcpl/skip-reply-wake.js +25 -0
  91. package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
  92. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  93. package/dist/src/mcpl/tool-presence.js +18 -17
  94. package/dist/src/mcpl/tool-presence.js.map +1 -1
  95. package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
  96. package/dist/src/mcpl/tool-signature.js +17 -16
  97. package/dist/src/mcpl/tool-signature.js.map +1 -1
  98. package/dist/src/mcpl/toolset-host.d.ts +8 -26
  99. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  100. package/dist/src/mcpl/toolset-host.js +16 -6
  101. package/dist/src/mcpl/toolset-host.js.map +1 -1
  102. package/dist/src/mcpl/toolsets-module.d.ts +14 -25
  103. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  104. package/dist/src/mcpl/toolsets-module.js +180 -50
  105. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  106. package/dist/src/mcpl/transport.d.ts +1 -1
  107. package/dist/src/mcpl/transport.d.ts.map +1 -1
  108. package/dist/src/mcpl/transport.js +16 -16
  109. package/dist/src/mcpl/transport.js.map +1 -1
  110. package/dist/src/mcpl/types.d.ts +28 -12
  111. package/dist/src/mcpl/types.d.ts.map +1 -1
  112. package/dist/src/mcpl/types.js.map +1 -1
  113. package/dist/src/module-registry.d.ts +6 -7
  114. package/dist/src/module-registry.d.ts.map +1 -1
  115. package/dist/src/module-registry.js +6 -7
  116. package/dist/src/module-registry.js.map +1 -1
  117. package/dist/src/modules/api/index.d.ts.map +1 -1
  118. package/dist/src/modules/api/index.js +4 -2
  119. package/dist/src/modules/api/index.js.map +1 -1
  120. package/dist/src/modules/discord/index.d.ts.map +1 -1
  121. package/dist/src/modules/discord/index.js +33 -25
  122. package/dist/src/modules/discord/index.js.map +1 -1
  123. package/dist/src/modules/history/index.js +8 -6
  124. package/dist/src/modules/history/index.js.map +1 -1
  125. package/dist/src/modules/sender-name.d.ts +7 -0
  126. package/dist/src/modules/sender-name.d.ts.map +1 -0
  127. package/dist/src/modules/sender-name.js +10 -0
  128. package/dist/src/modules/sender-name.js.map +1 -0
  129. package/dist/src/modules/workspace/answers.d.ts +5 -1
  130. package/dist/src/modules/workspace/answers.d.ts.map +1 -1
  131. package/dist/src/modules/workspace/answers.js +16 -1
  132. package/dist/src/modules/workspace/answers.js.map +1 -1
  133. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
  134. package/dist/src/modules/workspace/direct-write.js +6 -4
  135. package/dist/src/modules/workspace/direct-write.js.map +1 -1
  136. package/dist/src/modules/workspace/effects.d.ts +4 -2
  137. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  138. package/dist/src/modules/workspace/effects.js +50 -40
  139. package/dist/src/modules/workspace/effects.js.map +1 -1
  140. package/dist/src/modules/workspace/index.d.ts +1 -0
  141. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  142. package/dist/src/modules/workspace/index.js +66 -36
  143. package/dist/src/modules/workspace/index.js.map +1 -1
  144. package/dist/src/modules/workspace/observe.d.ts +51 -20
  145. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  146. package/dist/src/modules/workspace/observe.js +120 -82
  147. package/dist/src/modules/workspace/observe.js.map +1 -1
  148. package/dist/src/modules/workspace/reconcile.d.ts +7 -1
  149. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  150. package/dist/src/modules/workspace/reconcile.js +133 -37
  151. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  152. package/dist/src/notice-tags.d.ts +79 -0
  153. package/dist/src/notice-tags.d.ts.map +1 -0
  154. package/dist/src/notice-tags.js +83 -0
  155. package/dist/src/notice-tags.js.map +1 -0
  156. package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
  157. package/dist/src/reasoning-loss-notice.js +5 -3
  158. package/dist/src/reasoning-loss-notice.js.map +1 -1
  159. package/dist/src/silent-heartbeat.d.ts +1 -53
  160. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  161. package/dist/src/silent-heartbeat.js +56 -3
  162. package/dist/src/silent-heartbeat.js.map +1 -1
  163. package/dist/src/supplied-text.d.ts +35 -0
  164. package/dist/src/supplied-text.d.ts.map +1 -0
  165. package/dist/src/supplied-text.js +141 -0
  166. package/dist/src/supplied-text.js.map +1 -0
  167. package/dist/src/tool-args.d.ts.map +1 -1
  168. package/dist/src/tool-args.js +9 -8
  169. package/dist/src/tool-args.js.map +1 -1
  170. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  171. package/dist/src/tool-image-ledger.js +2 -1
  172. package/dist/src/tool-image-ledger.js.map +1 -1
  173. package/dist/src/tool-presentation.d.ts.map +1 -1
  174. package/dist/src/tool-presentation.js +5 -4
  175. package/dist/src/tool-presentation.js.map +1 -1
  176. package/dist/src/tune-out/coordinator.d.ts +3 -2
  177. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  178. package/dist/src/tune-out/coordinator.js +24 -26
  179. package/dist/src/tune-out/coordinator.js.map +1 -1
  180. package/dist/src/types/agent.d.ts +23 -9
  181. package/dist/src/types/agent.d.ts.map +1 -1
  182. package/dist/src/types/framework.d.ts +43 -0
  183. package/dist/src/types/framework.d.ts.map +1 -1
  184. package/dist/src/types/module.d.ts +22 -11
  185. package/dist/src/types/module.d.ts.map +1 -1
  186. package/package.json +2 -2
  187. package/src/agent.ts +4 -1
  188. package/src/api/mcp-server-core.ts +3 -14
  189. package/src/api/server.ts +7 -3
  190. package/src/auto-continue.ts +8 -2
  191. package/src/code-execution/isolate/runner.ts +7 -5
  192. package/src/code-execution/isolate/scripts.ts +3 -2
  193. package/src/framework.ts +1132 -492
  194. package/src/gate/event-gate.ts +128 -47
  195. package/src/gate/index.ts +1 -1
  196. package/src/gate/types.ts +12 -2
  197. package/src/heartbeat/heartbeat.ts +48 -9
  198. package/src/held-writes.ts +3 -3
  199. package/src/host-presentation.ts +605 -0
  200. package/src/index.ts +2 -2
  201. package/src/line-reader.ts +58 -0
  202. package/src/mcpl/channel-registry.ts +137 -52
  203. package/src/mcpl/conversation-router.ts +2 -1
  204. package/src/mcpl/convert-block.ts +17 -4
  205. package/src/mcpl/hook-orchestrator.ts +4 -3
  206. package/src/mcpl/inbound-source.ts +3 -1
  207. package/src/mcpl/modern-connection.ts +11 -7
  208. package/src/mcpl/protocol-family.ts +24 -3
  209. package/src/mcpl/push-handler.ts +2 -2
  210. package/src/mcpl/references.ts +31 -23
  211. package/src/mcpl/server-connection.ts +20 -18
  212. package/src/mcpl/skip-reply-wake.ts +35 -0
  213. package/src/mcpl/tool-presence.ts +18 -17
  214. package/src/mcpl/tool-signature.ts +17 -16
  215. package/src/mcpl/toolset-host.ts +18 -6
  216. package/src/mcpl/toolsets-module.ts +142 -50
  217. package/src/mcpl/transport.ts +18 -14
  218. package/src/mcpl/types.ts +29 -12
  219. package/src/module-registry.ts +6 -7
  220. package/src/modules/api/index.ts +4 -2
  221. package/src/modules/discord/index.ts +37 -25
  222. package/src/modules/history/index.ts +8 -6
  223. package/src/modules/sender-name.ts +9 -0
  224. package/src/modules/workspace/answers.ts +20 -2
  225. package/src/modules/workspace/direct-write.ts +4 -3
  226. package/src/modules/workspace/effects.ts +50 -40
  227. package/src/modules/workspace/index.ts +58 -34
  228. package/src/modules/workspace/observe.ts +139 -88
  229. package/src/modules/workspace/reconcile.ts +159 -36
  230. package/src/notice-tags.ts +84 -0
  231. package/src/reasoning-loss-notice.ts +5 -3
  232. package/src/silent-heartbeat.ts +4 -3
  233. package/src/supplied-text.ts +154 -0
  234. package/src/tool-args.ts +9 -8
  235. package/src/tool-image-ledger.ts +2 -1
  236. package/src/tool-presentation.ts +5 -4
  237. package/src/tune-out/coordinator.ts +25 -27
  238. package/src/types/agent.ts +23 -9
  239. package/src/types/framework.ts +44 -0
  240. package/src/types/module.ts +23 -11
@@ -39,9 +39,9 @@
39
39
  */
40
40
  import { candidateOf, sameCandidate, sameRootIdentity, } from './disk-agreement.js';
41
41
  import { storedBytes } from './sync.js';
42
- import { IGNORED, NOT_A_DIRECTORY, OTHER_MOUNT, ROOT_REPLACED, fingerprintVouches, ignoreBeneath, inOtherMount, locateRoot, looksBinary, observePath, passedOverAt, passedOverBetween, provenAbsent, readHead, readPath, sniffImageMime, walkScope, withinIncludes, } from './observe.js';
43
- import { EffectFailed, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
44
- import { errnoWords } from './answers.js';
42
+ import { BENEATH_SYMLINK, CHANGED, CHANGED_WALK, DIRECTORY, EDITOR_FILE_REASON, FILE_CHANGED, IGNORED, LEAVES_MOUNT, LINK_NOT_WALKED, LINK_TO_DIRECTORY, NOT_A_DIRECTORY, NOT_FOLLOWED, NOT_REGULAR, OUTSIDE_PARENT, RESOLVES_OUTSIDE, ROOT_UNAVAILABLE, OTHER_MOUNT, ROOT_REPLACED, fingerprintVouches, ignoreBeneath, inOtherMount, locateRoot, looksBinary, observePath, passedOverAt, passedOverBetween, provenAbsent, readHead, readPath, sniffImageMime, walkScope, withinIncludes, } from './observe.js';
43
+ import { EffectFailed, callFailed, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
44
+ import { OUTSIDE_STEP, RESOLVED_OUTSIDE_STEP, UNFOLLOWED_STEP, errnoClause, errnoWords } from './answers.js';
45
45
  const ABSENT = { kind: 'absent' };
46
46
  export const WALK_CAP = 5000;
47
47
  function diskCandidate(d) {
@@ -198,21 +198,22 @@ function settleAdoption(p, s) {
198
198
  /**
199
199
  * What's in the way, and the way out, when disk's shape keeps a push from a
200
200
  * path: no push writes a file over a directory or unlinks one, unlinks
201
- * anything but a regular file or a followed symlink to one, or writes below
202
- * a file or a symlink the walk won't take (`writeContained`,
203
- * `unlinkContained`), forced or not. Null for any other reason, which is
204
- * passed on as it is.
201
+ * anything but a regular file or a followed symlink to one, writes below a
202
+ * file or a symlink the walk won't take, or writes or unlinks through a
203
+ * symlink the mount doesn't follow or one that leads out of the mount
204
+ * (`writeContained`, `unlinkContained`), forced or not. Null for any other
205
+ * reason.
205
206
  */
206
207
  function inTheWay(reason, deletion) {
207
208
  // Observation's words for what a deletion never unlinks, which the unlink
208
209
  // refuses in the same words.
209
- if (deletion && (reason === 'not a regular file' || reason === 'a symlink to a directory, not walked')) {
210
- const what = reason === 'not a regular file'
210
+ if (deletion && (reason === NOT_REGULAR || reason === LINK_TO_DIRECTORY)) {
211
+ const what = reason === NOT_REGULAR
211
212
  ? 'a pipe, socket or device at this path, or a symlink to one'
212
213
  : 'a symlink to a directory at this path';
213
214
  return `disk has ${what}, and no push unlinks it, forced or not — ask whoever runs this host if it should go`;
214
215
  }
215
- if (reason === 'a directory') {
216
+ if (reason === DIRECTORY) {
216
217
  return deletion
217
218
  ? 'disk has a directory at this path, and no push unlinks one, forced or not — if it should go, move or remove it on disk'
218
219
  : 'disk has a directory at this path, and no push writes a file over one, forced or not — ' +
@@ -224,8 +225,90 @@ function inTheWay(reason, deletion) {
224
225
  return 'disk has a file or a symlink where this path needs a directory, and no push writes below one, forced or not — ' +
225
226
  'move or remove it on disk and materialize again, or write the file under another path';
226
227
  }
228
+ const link = reason === NOT_FOLLOWED ? 'disk has a symlink at this path, which this mount does not follow'
229
+ : reason === LEAVES_MOUNT ? 'this path leads out of the mount through a symlink on it'
230
+ : null;
231
+ if (link !== null) {
232
+ return deletion
233
+ ? `${link}, and no push unlinks ${reason === NOT_FOLLOWED ? 'one' : 'through one'}, forced or not — ask whoever runs this host if it should go`
234
+ : `${link}, and no push writes through one, forced or not — write the file under another path, ` +
235
+ `or whoever runs this host can remove the link, and ${THEN.write}`;
236
+ }
237
+ if (deletion && reason === BENEATH_SYMLINK) {
238
+ return 'a symlink on this path does not lead to a directory inside the mount, so nothing beneath it is in this mount, and no push unlinks beneath one, '
239
+ + `forced or not — whoever runs this host can remove the link or make it lead to a directory inside the mount, and ${THEN.unlink}`;
240
+ }
241
+ if (reason === OUTSIDE_PARENT) {
242
+ return `a directory on this path leads out of the mount, and no push ${deletion ? 'unlinks' : 'writes'} beneath one, forced or not — ` +
243
+ (deletion ? 'ask whoever runs this host if what is there should go' : 'write the file under another path');
244
+ }
245
+ if (reason === NOT_REGULAR || reason === LINK_TO_DIRECTORY) {
246
+ const what = reason === NOT_REGULAR ? 'a pipe, socket or device at this path, or a symlink to one' : 'a symlink to a directory at this path';
247
+ return `disk has ${what}, and no push writes a file over it, forced or not — write the file under another path, ` +
248
+ `or whoever runs this host can remove it, and ${THEN.write}`;
249
+ }
227
250
  return null;
228
251
  }
252
+ const AGAIN = {
253
+ write: 'materializing again',
254
+ unlink: 'materializing again with applyDeletions',
255
+ sync: 'syncing this path again',
256
+ list: 'listing it',
257
+ read: 'calling again',
258
+ };
259
+ const THEN = {
260
+ write: 'materializing again then gets past it',
261
+ unlink: 'materializing again with applyDeletions then gets past it',
262
+ sync: 'a sync of this path then takes it in',
263
+ list: 'the workspace then reaches it',
264
+ read: 'a read then reaches it',
265
+ };
266
+ const CHANGED_WHILE = new Set([CHANGED, CHANGED_WALK, FILE_CHANGED]);
267
+ const WALKED_PAST = new Set([IGNORED, EDITOR_FILE_REASON, LINK_NOT_WALKED]);
268
+ export function explained(reason, cause, meets) {
269
+ if (meets === 'write' || meets === 'unlink') {
270
+ const shaped = inTheWay(reason, meets === 'unlink');
271
+ if (shaped !== null)
272
+ return shaped;
273
+ }
274
+ return withWayOn(reason, cause, meets);
275
+ }
276
+ function withWayOn(reason, cause, meets) {
277
+ const on = wayOn(reason, cause, meets);
278
+ return on === null ? reason : `${reason} — ${on}`;
279
+ }
280
+ export function wayOn(reason, cause, meets) {
281
+ const lasting = `${AGAIN[meets]} won't change that; `;
282
+ if (reason === ROOT_UNAVAILABLE) {
283
+ if (cause.errno === 'ENOENT' || cause.errno === 'ENOTDIR')
284
+ return `${lasting}whoever runs this host can put the mount's directory back at its path, and ${THEN[meets]}`;
285
+ if (cause.errno === undefined)
286
+ return `${AGAIN[meets]} checks it afresh; if it's still unavailable, whoever runs this host can check the mount's directory and its path`;
287
+ }
288
+ if (cause.errno !== undefined)
289
+ return errnoClause(cause.errno, AGAIN[meets], THEN[meets]);
290
+ if (cause.again === true || CHANGED_WHILE.has(reason))
291
+ return `${AGAIN[meets]} checks it afresh`;
292
+ switch (reason) {
293
+ case NOT_FOLLOWED:
294
+ return lasting + UNFOLLOWED_STEP;
295
+ case LEAVES_MOUNT:
296
+ return lasting + OUTSIDE_STEP;
297
+ case BENEATH_SYMLINK:
298
+ return `${lasting}nothing beneath it is in this mount, and whoever runs this host can make it lead to a directory inside the mount, or, where it leads to a directory outside, mount that directory`;
299
+ case OUTSIDE_PARENT:
300
+ case RESOLVES_OUTSIDE:
301
+ return lasting + RESOLVED_OUTSIDE_STEP;
302
+ case NOT_REGULAR:
303
+ return `${lasting}whoever runs this host can put a regular file there, and ${THEN[meets]}`;
304
+ case DIRECTORY:
305
+ return `${lasting}ls of this path lists what's in it`;
306
+ case LINK_TO_DIRECTORY:
307
+ return `${lasting}ls of the directory it leads to lists what's in it`;
308
+ default:
309
+ return meets === 'sync' && WALKED_PAST.has(reason) ? 'a sync of this path checks it' : null;
310
+ }
311
+ }
229
312
  /**
230
313
  * The forced way out the refusal of a disk copy that couldn't be verified
231
314
  * names, if any. A forced push skips the verification, so it gets past the
@@ -243,6 +326,12 @@ function forcedWay(note, deletion, forced) {
243
326
  return `, or, if disk lets the workspace ${deletion ? 'unlink' : 'write'} the file, ${forced}`;
244
327
  return '';
245
328
  }
329
+ const UNSETTLED = "whether disk holds the change isn't known until it's checked: a materialize of this path checks it and says how it ended";
330
+ function unverifiedCopy(v, meets, forced) {
331
+ const note = v.note ?? 'not observed';
332
+ const on = wayOn(note, v.noteCause ?? {}, meets);
333
+ return `cannot verify disk copy (${note})${on !== null ? ` — ${on}` : ''}${forcedWay(v.note, meets === 'unlink', forced)}`;
334
+ }
246
335
  /**
247
336
  * The synchronous precondition of every workspace mutation: evidence owed a
248
337
  * barrier is made durable first, and an adoption left pending on the
@@ -362,6 +451,9 @@ function adoptDisk(d, s) {
362
451
  return { state: 'synced', adopt: 'ingest', op: s ? 'modified' : 'created', p: agreedFromDisk(d), intent: {} };
363
452
  return { state: 'disk-only', ...(s ? { adopt: 'drop', op: 'deleted' } : {}), p: 'forget', intent: {} };
364
453
  }
454
+ function causeOf({ errno, ownStep }) {
455
+ return { ...(errno !== undefined ? { errno } : {}), ...(ownStep ? { ownStep } : {}) };
456
+ }
365
457
  /** The rule (see module doc). Pure. */
366
458
  function decide(d, s, pIn, bi, branchId, adopt) {
367
459
  // A branch intent that can't be read may have held a tombstone or a
@@ -371,9 +463,9 @@ function decide(d, s, pIn, bi, branchId, adopt) {
371
463
  if (d.kind === 'unobserved') {
372
464
  // Disk proves nothing, but an adoption on this branch settles by the tree.
373
465
  if (pIn?.kind === 'pending' && pIn.effect === 'adopt' && pIn.branchId === branchId && !adopt) {
374
- return { state: 'unverified', note: d.reason, p: settleAdoption(pIn, s) };
466
+ return { state: 'unverified', note: d.reason, noteCause: causeOf(d), p: settleAdoption(pIn, s) };
375
467
  }
376
- return { state: 'unverified', note: d.reason };
468
+ return { state: 'unverified', note: d.reason, noteCause: causeOf(d) };
377
469
  }
378
470
  if (d.kind === 'unhashed') {
379
471
  // Observed this way only when nothing tracks the path, which is then a
@@ -551,6 +643,7 @@ async function gather(store, agreement, mount, scope, opts) {
551
643
  // is followed, as `rootReal` is: `followSymlinks` governs links within it.
552
644
  const root = await locateRoot(mount.view, mount.rootReal);
553
645
  const rootUnavailable = 'reason' in root ? root.reason : null;
646
+ const rootCause = 'reason' in root && root.errno !== undefined ? { errno: root.errno } : {};
554
647
  // The branch is labelled where the tracked candidates are read, with no
555
648
  // await between: the decision is checked against this label.
556
649
  const branchId = store.currentBranch().id;
@@ -565,7 +658,7 @@ async function gather(store, agreement, mount, scope, opts) {
565
658
  if (rootUnavailable !== null) {
566
659
  // Said whether or not anything is tracked: an unavailable mount must not
567
660
  // read as a verified empty directory.
568
- gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: rootUnavailable });
661
+ gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(rootUnavailable, rootCause, 'list') });
569
662
  }
570
663
  // Which paths to decide: what disk shows, and what the store, P and intent
571
664
  // track. The tracked ones are read synchronously, on this branch. A path
@@ -617,7 +710,7 @@ async function gather(store, agreement, mount, scope, opts) {
617
710
  // Observe. Tracked facts are read again, synchronously, when deciding.
618
711
  for (const path of candidates) {
619
712
  if (rootUnavailable !== null) {
620
- gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable });
713
+ gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable, ...rootCause });
621
714
  continue;
622
715
  }
623
716
  // What the mount passes over by design stays unobserved by a pass that
@@ -672,7 +765,9 @@ async function gather(store, agreement, mount, scope, opts) {
672
765
  if (region === undefined || r.path.length > region.path.length)
673
766
  region = r;
674
767
  }
675
- gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
768
+ gathered.facts.set(path, region
769
+ ? { kind: 'unobserved', reason: region.reason, ...(region.errno !== undefined ? { errno: region.errno } : {}) }
770
+ : { kind: 'unobserved', reason: 'not reached by the walk' });
676
771
  continue;
677
772
  }
678
773
  const p = agreement.get(mount.name, path);
@@ -688,12 +783,13 @@ async function gather(store, agreement, mount, scope, opts) {
688
783
  const after = await locateRoot(mount.view, mount.rootReal);
689
784
  if (!('identity' in after) || !sameRootIdentity(after.identity, gathered.rootIdentity)) {
690
785
  const reason = 'reason' in after ? after.reason : ROOT_REPLACED;
786
+ const errno = 'errno' in after ? after.errno : undefined;
691
787
  for (const path of gathered.facts.keys())
692
- gathered.facts.set(path, { kind: 'unobserved', reason });
788
+ gathered.facts.set(path, { kind: 'unobserved', reason, ...(errno !== undefined ? { errno } : {}) });
693
789
  gathered.rootIdentity = null;
694
790
  gathered.walk = null;
695
791
  gathered.dirs = [];
696
- gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason });
792
+ gathered.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(reason, errno !== undefined ? { errno } : {}, 'list') });
697
793
  }
698
794
  }
699
795
  return gathered;
@@ -723,14 +819,13 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
723
819
  }
724
820
  }
725
821
  if (gathered === null) {
726
- result.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: 'the selected branch kept changing while the scan ran' });
822
+ result.undecided = 'the selected branch kept changing while the scan ran';
823
+ result.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : '', reason: explained(result.undecided, { again: true }, 'list') });
727
824
  return result;
728
825
  }
729
826
  if (opts.branchId !== undefined && gathered.branchId !== opts.branchId) {
730
- result.incomplete.push({
731
- path: scope.kind === 'dir' ? scope.dir : scope.paths.join(', '),
732
- reason: 'the selected branch changed before this sync ran; nothing was taken from disk — sync again on this branch to take it',
733
- });
827
+ result.undecided = 'the selected branch changed before this sync ran; nothing was taken from disk — sync again on this branch to take it';
828
+ result.incomplete.push({ path: scope.kind === 'dir' ? scope.dir : scope.paths.join(', '), reason: result.undecided });
734
829
  return result;
735
830
  }
736
831
  const { walk, facts } = gathered;
@@ -861,6 +956,8 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
861
956
  }
862
957
  if (x.verdict.note)
863
958
  report.note = x.verdict.note;
959
+ if (x.verdict.noteCause !== undefined)
960
+ report.noteCause = x.verdict.noteCause;
864
961
  if (!(x.verdict.state === 'synced' && !entry))
865
962
  result.reports.set(x.path, report);
866
963
  }
@@ -891,8 +988,9 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
891
988
  // holder), so naming the directory as unreached would never clear.
892
989
  const wholeScope = scope.kind === 'dir' && scope.dir !== '' && region.path === scope.dir;
893
990
  const unreached = hides && region.reason !== OTHER_MOUNT;
894
- if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope)
895
- result.incomplete.push({ path: region.path, reason: region.reason });
991
+ if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope) {
992
+ result.incomplete.push({ path: region.path, reason: explained(region.reason, region.errno !== undefined ? { errno: region.errno } : {}, 'list') });
993
+ }
896
994
  // A name passed over by design that hides nothing tracked is reported nowhere else:
897
995
  // counted, so a sync that says 0 also says what it didn't look at
898
996
  // (agent-framework #276). A symlink region isn't counted: a link the
@@ -941,8 +1039,9 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
941
1039
  // unmounted from. The push plans nothing, so it reports no branch.
942
1040
  const root = await locateRoot(mount.view, mount.rootReal);
943
1041
  if ('reason' in root) {
1042
+ const why = explained(root.reason, root.errno !== undefined ? { errno: root.errno } : {}, 'write');
944
1043
  for (const path of paths)
945
- result.skipped.push({ path, reason: root.reason });
1044
+ result.skipped.push({ path, reason: why });
946
1045
  return result;
947
1046
  }
948
1047
  // Another mount's directory nested in this one is that mount's: no push
@@ -971,7 +1070,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
971
1070
  // nothing is decided or recorded from them.
972
1071
  const after = await locateRoot(mount.view, mount.rootReal);
973
1072
  if (!('identity' in after) || !sameRootIdentity(after.identity, root.identity)) {
974
- const reason = 'reason' in after ? after.reason : ROOT_REPLACED;
1073
+ const reason = 'reason' in after ? explained(after.reason, after.errno !== undefined ? { errno: after.errno } : {}, 'write') : ROOT_REPLACED;
975
1074
  for (const path of paths)
976
1075
  result.skipped.push({ path, reason });
977
1076
  return result;
@@ -992,9 +1091,6 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
992
1091
  const p = agreement.get(mount.name, path);
993
1092
  const bi = mount.intents.get(path);
994
1093
  const v = decide(d, s, p, bi, branchId, false);
995
- // The ways out a refusal names, as materialize takes them: a deletion
996
- // reaches disk only with applyDeletions.
997
- const again = s ? 'materialize again' : 'materialize again with applyDeletions';
998
1094
  // Where disk has deleted a file the workspace holds, a sync takes the
999
1095
  // deletion, which removes the workspace's copy, and a forced push writes
1000
1096
  // the workspace's copy back: there is no disk copy to adopt or overwrite.
@@ -1118,8 +1214,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
1118
1214
  ? d.kind === 'absent' || d.kind === 'file'
1119
1215
  ? `${v.note} — ${sync}`
1120
1216
  : `${v.note}, and disk's copy can't be verified (${d.kind === 'unobserved' ? d.reason : 'not hashed'}) — once it can be, ${sync}`
1121
- : inTheWay(v.note, !s) ??
1122
- `cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and ${again}${forcedWay(v.note, !s, forced)}`,
1217
+ : inTheWay(v.note, !s) ?? unverifiedCopy(v, s ? 'write' : 'unlink', forced),
1123
1218
  });
1124
1219
  continue;
1125
1220
  }
@@ -1186,7 +1281,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
1186
1281
  // A deletion no push makes, forced or not (inTheWay), is refused for
1187
1282
  // that, as an unforced push refuses it, rather than left for
1188
1283
  // applyDeletions, which couldn't make it either.
1189
- const lasting = inTheWay(v.note, true);
1284
+ const lasting = inTheWay(v.note, true) ?? (v.noteCause?.ownStep === true ? v.note ?? null : null);
1190
1285
  if (lasting !== null)
1191
1286
  result.skipped.push({ path, reason: lasting });
1192
1287
  else
@@ -1319,19 +1414,19 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
1319
1414
  }
1320
1415
  }
1321
1416
  catch (err) {
1322
- const failure = err instanceof EffectFailed ? err : new EffectFailed(err instanceof Error ? err.message : String(err), true);
1417
+ const failure = err instanceof EffectFailed ? err : callFailed('the push failed', err, true, 'colon');
1323
1418
  if (!failure.touched) {
1324
1419
  // Disk at this path is as planning saw it: its evidence goes back to
1325
1420
  // exactly what it was, whatever kind that was.
1326
1421
  agreement.restore(mount.name, step.path, step.before);
1327
1422
  recorded = true;
1328
- const why = inTheWay(failure.message, step.kind === 'unlink') ?? failure.message;
1423
+ const why = step.kind === 'chmod' ? withWayOn(failure.message, failure.detail, 'write') : explained(failure.message, failure.detail, step.kind);
1329
1424
  result.skipped.push({ path: step.path, reason: `not ${step.kind === 'unlink' ? 'unlinked' : step.kind === 'chmod' ? 'given the workspace\'s mode' : 'written'}: ${why}` });
1330
1425
  }
1331
1426
  else {
1332
1427
  // No completion: the intent stays pending, and the next observation
1333
1428
  // resolves it from whatever disk then holds.
1334
- result.skipped.push({ path: step.path, reason: `${failure.message}; the outcome is checked at the next observation` });
1429
+ result.skipped.push({ path: step.path, reason: `${failure.message} — ${UNSETTLED}` });
1335
1430
  }
1336
1431
  }
1337
1432
  }
@@ -1349,7 +1444,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
1349
1444
  result.skipped.push({
1350
1445
  path: step.path,
1351
1446
  reason: `could not make the ${step.kind === 'write' ? 'write' : 'unlink'} durable (directory fsync of ${failed === '' ? 'the mount root' : failed}): ` +
1352
- `${unsynced.get(failed)}; the outcome is checked at the next observation`,
1447
+ `${unsynced.get(failed)} — ${UNSETTLED}`,
1353
1448
  });
1354
1449
  continue;
1355
1450
  }
@@ -1429,6 +1524,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
1429
1524
  return result;
1430
1525
  }
1431
1526
  function unstoredCopy(stored) {
1432
- return 'missing' in stored ? 'the workspace copy is missing from the store' : `the workspace copy is unreadable in the store (${stored.unreadable})`;
1527
+ return `${'missing' in stored ? 'the workspace copy is missing from the store' : `the workspace copy is unreadable in the store (${stored.unreadable})`}`
1528
+ + " — a read of this path takes disk's copy where it can, and otherwise says what can be done";
1433
1529
  }
1434
1530
  //# sourceMappingURL=reconcile.js.map