@animalabs/agent-framework 0.28.0 → 0.30.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 (289) hide show
  1. package/README.md +88 -25
  2. package/dist/src/admission/admit.d.ts +90 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -0
  4. package/dist/src/admission/admit.js +43 -0
  5. package/dist/src/admission/admit.js.map +1 -0
  6. package/dist/src/agent.d.ts +21 -0
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +47 -7
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  11. package/dist/src/api/mcp-server-core.js +3 -12
  12. package/dist/src/api/mcp-server-core.js.map +1 -1
  13. package/dist/src/api/server.d.ts.map +1 -1
  14. package/dist/src/api/server.js +7 -3
  15. package/dist/src/api/server.js.map +1 -1
  16. package/dist/src/author-presentation.d.ts +50 -0
  17. package/dist/src/author-presentation.d.ts.map +1 -0
  18. package/dist/src/author-presentation.js +58 -0
  19. package/dist/src/author-presentation.js.map +1 -0
  20. package/dist/src/auto-continue.d.ts +2 -0
  21. package/dist/src/auto-continue.d.ts.map +1 -1
  22. package/dist/src/auto-continue.js +5 -2
  23. package/dist/src/auto-continue.js.map +1 -1
  24. package/dist/src/code-execution/isolate/runner.d.ts +7 -5
  25. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
  26. package/dist/src/code-execution/isolate/runner.js +7 -5
  27. package/dist/src/code-execution/isolate/runner.js.map +1 -1
  28. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  29. package/dist/src/code-execution/isolate/scripts.js +3 -2
  30. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  31. package/dist/src/dendrite/index.d.ts +6 -0
  32. package/dist/src/dendrite/index.d.ts.map +1 -0
  33. package/dist/src/dendrite/index.js +3 -0
  34. package/dist/src/dendrite/index.js.map +1 -0
  35. package/dist/src/dendrite/presets.d.ts +60 -0
  36. package/dist/src/dendrite/presets.d.ts.map +1 -0
  37. package/dist/src/dendrite/presets.js +117 -0
  38. package/dist/src/dendrite/presets.js.map +1 -0
  39. package/dist/src/dendrite/registry.d.ts +260 -0
  40. package/dist/src/dendrite/registry.d.ts.map +1 -0
  41. package/dist/src/dendrite/registry.js +742 -0
  42. package/dist/src/dendrite/registry.js.map +1 -0
  43. package/dist/src/dendrite/types.d.ts +392 -0
  44. package/dist/src/dendrite/types.d.ts.map +1 -0
  45. package/dist/src/dendrite/types.js +24 -0
  46. package/dist/src/dendrite/types.js.map +1 -0
  47. package/dist/src/framework.d.ts +846 -67
  48. package/dist/src/framework.d.ts.map +1 -1
  49. package/dist/src/framework.js +4213 -924
  50. package/dist/src/framework.js.map +1 -1
  51. package/dist/src/gate/event-gate.d.ts +46 -14
  52. package/dist/src/gate/event-gate.d.ts.map +1 -1
  53. package/dist/src/gate/event-gate.js +143 -53
  54. package/dist/src/gate/event-gate.js.map +1 -1
  55. package/dist/src/gate/index.d.ts +1 -1
  56. package/dist/src/gate/index.d.ts.map +1 -1
  57. package/dist/src/gate/index.js +1 -1
  58. package/dist/src/gate/index.js.map +1 -1
  59. package/dist/src/gate/types.d.ts +12 -2
  60. package/dist/src/gate/types.d.ts.map +1 -1
  61. package/dist/src/heartbeat/heartbeat.d.ts +18 -0
  62. package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
  63. package/dist/src/heartbeat/heartbeat.js +34 -8
  64. package/dist/src/heartbeat/heartbeat.js.map +1 -1
  65. package/dist/src/held-writes.d.ts +3 -3
  66. package/dist/src/held-writes.js +3 -3
  67. package/dist/src/host-presentation.d.ts +174 -0
  68. package/dist/src/host-presentation.d.ts.map +1 -0
  69. package/dist/src/host-presentation.js +565 -0
  70. package/dist/src/host-presentation.js.map +1 -0
  71. package/dist/src/index.d.ts +7 -4
  72. package/dist/src/index.d.ts.map +1 -1
  73. package/dist/src/index.js +7 -2
  74. package/dist/src/index.js.map +1 -1
  75. package/dist/src/line-reader.d.ts +4 -0
  76. package/dist/src/line-reader.d.ts.map +1 -0
  77. package/dist/src/line-reader.js +59 -0
  78. package/dist/src/line-reader.js.map +1 -0
  79. package/dist/src/mcpl/channel-registry.d.ts +118 -15
  80. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  81. package/dist/src/mcpl/channel-registry.js +266 -110
  82. package/dist/src/mcpl/channel-registry.js.map +1 -1
  83. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  84. package/dist/src/mcpl/conversation-router.js +2 -1
  85. package/dist/src/mcpl/conversation-router.js.map +1 -1
  86. package/dist/src/mcpl/convert-block.d.ts +1 -0
  87. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  88. package/dist/src/mcpl/convert-block.js +16 -4
  89. package/dist/src/mcpl/convert-block.js.map +1 -1
  90. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  91. package/dist/src/mcpl/hook-orchestrator.js +4 -3
  92. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  93. package/dist/src/mcpl/inbound-source.d.ts +3 -1
  94. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  95. package/dist/src/mcpl/inbound-source.js +3 -1
  96. package/dist/src/mcpl/inbound-source.js.map +1 -1
  97. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  98. package/dist/src/mcpl/modern-connection.js +11 -7
  99. package/dist/src/mcpl/modern-connection.js.map +1 -1
  100. package/dist/src/mcpl/protocol-family.d.ts +3 -2
  101. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  102. package/dist/src/mcpl/protocol-family.js +25 -3
  103. package/dist/src/mcpl/protocol-family.js.map +1 -1
  104. package/dist/src/mcpl/push-coalescer.d.ts +9 -0
  105. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  106. package/dist/src/mcpl/push-coalescer.js +21 -0
  107. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  108. package/dist/src/mcpl/push-handler.js +2 -2
  109. package/dist/src/mcpl/push-handler.js.map +1 -1
  110. package/dist/src/mcpl/references.d.ts +12 -40
  111. package/dist/src/mcpl/references.d.ts.map +1 -1
  112. package/dist/src/mcpl/references.js +29 -23
  113. package/dist/src/mcpl/references.js.map +1 -1
  114. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  115. package/dist/src/mcpl/server-connection.js +20 -20
  116. package/dist/src/mcpl/server-connection.js.map +1 -1
  117. package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
  118. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
  119. package/dist/src/mcpl/skip-reply-wake.js +25 -0
  120. package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
  121. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  122. package/dist/src/mcpl/tool-presence.js +18 -17
  123. package/dist/src/mcpl/tool-presence.js.map +1 -1
  124. package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
  125. package/dist/src/mcpl/tool-signature.js +17 -16
  126. package/dist/src/mcpl/tool-signature.js.map +1 -1
  127. package/dist/src/mcpl/toolset-host.d.ts +8 -26
  128. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  129. package/dist/src/mcpl/toolset-host.js +16 -6
  130. package/dist/src/mcpl/toolset-host.js.map +1 -1
  131. package/dist/src/mcpl/toolsets-module.d.ts +14 -25
  132. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  133. package/dist/src/mcpl/toolsets-module.js +187 -50
  134. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  135. package/dist/src/mcpl/transport.d.ts +1 -1
  136. package/dist/src/mcpl/transport.d.ts.map +1 -1
  137. package/dist/src/mcpl/transport.js +16 -16
  138. package/dist/src/mcpl/transport.js.map +1 -1
  139. package/dist/src/mcpl/types.d.ts +28 -12
  140. package/dist/src/mcpl/types.d.ts.map +1 -1
  141. package/dist/src/mcpl/types.js.map +1 -1
  142. package/dist/src/module-registry.d.ts +6 -7
  143. package/dist/src/module-registry.d.ts.map +1 -1
  144. package/dist/src/module-registry.js +6 -7
  145. package/dist/src/module-registry.js.map +1 -1
  146. package/dist/src/modules/api/index.d.ts.map +1 -1
  147. package/dist/src/modules/api/index.js +4 -2
  148. package/dist/src/modules/api/index.js.map +1 -1
  149. package/dist/src/modules/discord/index.d.ts.map +1 -1
  150. package/dist/src/modules/discord/index.js +33 -25
  151. package/dist/src/modules/discord/index.js.map +1 -1
  152. package/dist/src/modules/history/index.js +8 -6
  153. package/dist/src/modules/history/index.js.map +1 -1
  154. package/dist/src/modules/sender-name.d.ts +7 -0
  155. package/dist/src/modules/sender-name.d.ts.map +1 -0
  156. package/dist/src/modules/sender-name.js +10 -0
  157. package/dist/src/modules/sender-name.js.map +1 -0
  158. package/dist/src/modules/workspace/answers.d.ts +5 -1
  159. package/dist/src/modules/workspace/answers.d.ts.map +1 -1
  160. package/dist/src/modules/workspace/answers.js +16 -1
  161. package/dist/src/modules/workspace/answers.js.map +1 -1
  162. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
  163. package/dist/src/modules/workspace/direct-write.js +6 -4
  164. package/dist/src/modules/workspace/direct-write.js.map +1 -1
  165. package/dist/src/modules/workspace/effects.d.ts +4 -2
  166. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  167. package/dist/src/modules/workspace/effects.js +50 -40
  168. package/dist/src/modules/workspace/effects.js.map +1 -1
  169. package/dist/src/modules/workspace/index.d.ts +1 -0
  170. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  171. package/dist/src/modules/workspace/index.js +66 -36
  172. package/dist/src/modules/workspace/index.js.map +1 -1
  173. package/dist/src/modules/workspace/observe.d.ts +51 -20
  174. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  175. package/dist/src/modules/workspace/observe.js +120 -82
  176. package/dist/src/modules/workspace/observe.js.map +1 -1
  177. package/dist/src/modules/workspace/reconcile.d.ts +7 -1
  178. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  179. package/dist/src/modules/workspace/reconcile.js +133 -37
  180. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  181. package/dist/src/notice-tags.d.ts +81 -0
  182. package/dist/src/notice-tags.d.ts.map +1 -0
  183. package/dist/src/notice-tags.js +85 -0
  184. package/dist/src/notice-tags.js.map +1 -0
  185. package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
  186. package/dist/src/reasoning-loss-notice.js +5 -3
  187. package/dist/src/reasoning-loss-notice.js.map +1 -1
  188. package/dist/src/silent-heartbeat.d.ts +5 -55
  189. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  190. package/dist/src/silent-heartbeat.js +63 -7
  191. package/dist/src/silent-heartbeat.js.map +1 -1
  192. package/dist/src/stored-injections.d.ts +4 -0
  193. package/dist/src/stored-injections.d.ts.map +1 -1
  194. package/dist/src/stored-injections.js +6 -0
  195. package/dist/src/stored-injections.js.map +1 -1
  196. package/dist/src/supplied-text.d.ts +35 -0
  197. package/dist/src/supplied-text.d.ts.map +1 -0
  198. package/dist/src/supplied-text.js +141 -0
  199. package/dist/src/supplied-text.js.map +1 -0
  200. package/dist/src/tool-args.d.ts.map +1 -1
  201. package/dist/src/tool-args.js +9 -8
  202. package/dist/src/tool-args.js.map +1 -1
  203. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  204. package/dist/src/tool-image-ledger.js +2 -1
  205. package/dist/src/tool-image-ledger.js.map +1 -1
  206. package/dist/src/tool-presentation.d.ts.map +1 -1
  207. package/dist/src/tool-presentation.js +5 -4
  208. package/dist/src/tool-presentation.js.map +1 -1
  209. package/dist/src/tune-out/coordinator.d.ts +382 -11
  210. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  211. package/dist/src/tune-out/coordinator.js +1100 -119
  212. package/dist/src/tune-out/coordinator.js.map +1 -1
  213. package/dist/src/tune-out/tools.d.ts +101 -5
  214. package/dist/src/tune-out/tools.d.ts.map +1 -1
  215. package/dist/src/tune-out/tools.js +55 -18
  216. package/dist/src/tune-out/tools.js.map +1 -1
  217. package/dist/src/types/agent.d.ts +48 -9
  218. package/dist/src/types/agent.d.ts.map +1 -1
  219. package/dist/src/types/framework.d.ts +142 -0
  220. package/dist/src/types/framework.d.ts.map +1 -1
  221. package/dist/src/types/module.d.ts +22 -11
  222. package/dist/src/types/module.d.ts.map +1 -1
  223. package/dist/src/types/trace.d.ts +136 -0
  224. package/dist/src/types/trace.d.ts.map +1 -1
  225. package/package.json +3 -3
  226. package/src/admission/admit.ts +113 -0
  227. package/src/agent.ts +52 -7
  228. package/src/api/mcp-server-core.ts +3 -14
  229. package/src/api/server.ts +7 -3
  230. package/src/author-presentation.ts +81 -0
  231. package/src/auto-continue.ts +8 -2
  232. package/src/code-execution/isolate/runner.ts +7 -5
  233. package/src/code-execution/isolate/scripts.ts +3 -2
  234. package/src/dendrite/index.ts +32 -0
  235. package/src/dendrite/presets.ts +149 -0
  236. package/src/dendrite/registry.ts +808 -0
  237. package/src/dendrite/types.ts +397 -0
  238. package/src/framework.ts +4362 -852
  239. package/src/gate/event-gate.ts +159 -52
  240. package/src/gate/index.ts +1 -1
  241. package/src/gate/types.ts +12 -2
  242. package/src/heartbeat/heartbeat.ts +48 -9
  243. package/src/held-writes.ts +3 -3
  244. package/src/host-presentation.ts +612 -0
  245. package/src/index.ts +17 -4
  246. package/src/line-reader.ts +58 -0
  247. package/src/mcpl/channel-registry.ts +306 -111
  248. package/src/mcpl/conversation-router.ts +2 -1
  249. package/src/mcpl/convert-block.ts +17 -4
  250. package/src/mcpl/hook-orchestrator.ts +4 -3
  251. package/src/mcpl/inbound-source.ts +3 -1
  252. package/src/mcpl/modern-connection.ts +11 -7
  253. package/src/mcpl/protocol-family.ts +24 -3
  254. package/src/mcpl/push-coalescer.ts +19 -0
  255. package/src/mcpl/push-handler.ts +2 -2
  256. package/src/mcpl/references.ts +31 -23
  257. package/src/mcpl/server-connection.ts +20 -18
  258. package/src/mcpl/skip-reply-wake.ts +35 -0
  259. package/src/mcpl/tool-presence.ts +18 -17
  260. package/src/mcpl/tool-signature.ts +17 -16
  261. package/src/mcpl/toolset-host.ts +18 -6
  262. package/src/mcpl/toolsets-module.ts +149 -50
  263. package/src/mcpl/transport.ts +18 -14
  264. package/src/mcpl/types.ts +29 -12
  265. package/src/module-registry.ts +6 -7
  266. package/src/modules/api/index.ts +4 -2
  267. package/src/modules/discord/index.ts +37 -25
  268. package/src/modules/history/index.ts +8 -6
  269. package/src/modules/sender-name.ts +9 -0
  270. package/src/modules/workspace/answers.ts +20 -2
  271. package/src/modules/workspace/direct-write.ts +4 -3
  272. package/src/modules/workspace/effects.ts +50 -40
  273. package/src/modules/workspace/index.ts +58 -34
  274. package/src/modules/workspace/observe.ts +139 -88
  275. package/src/modules/workspace/reconcile.ts +159 -36
  276. package/src/notice-tags.ts +86 -0
  277. package/src/reasoning-loss-notice.ts +5 -3
  278. package/src/silent-heartbeat.ts +11 -7
  279. package/src/stored-injections.ts +7 -0
  280. package/src/supplied-text.ts +154 -0
  281. package/src/tool-args.ts +9 -8
  282. package/src/tool-image-ledger.ts +2 -1
  283. package/src/tool-presentation.ts +5 -4
  284. package/src/tune-out/coordinator.ts +1252 -123
  285. package/src/tune-out/tools.ts +152 -11
  286. package/src/types/agent.ts +51 -9
  287. package/src/types/framework.ts +146 -0
  288. package/src/types/module.ts +23 -11
  289. package/src/types/trace.ts +150 -0
@@ -4,6 +4,7 @@
4
4
  * Provides a unified workspace with mount-based filesystem access,
5
5
  * auto-sync between real filesystem and Chronicle, and manual materialization.
6
6
  */
7
+ import { NOTICE_TAG } from '../../notice-tags.js';
7
8
  import { constants as fsConstants } from 'node:fs';
8
9
  import { open, readdir, readlink, stat, lstat, realpath } from 'node:fs/promises';
9
10
  import { basename, dirname, isAbsolute, join, parse, resolve, relative, sep } from 'node:path';
@@ -12,7 +13,7 @@ import { MountWatcher } from './watcher.js';
12
13
  import { hashContent, storedBytes, DEFAULT_MAX_FILE_SIZE } from './sync.js';
13
14
  import { DiskAgreement, EvidenceUnavailable, callGate, sameRootIdentity } from './disk-agreement.js';
14
15
  import { BranchIntents } from './branch-intent.js';
15
- import { CHANGED, DIRECTORY, IGNORED, LEAVES_MOUNT, NONBLOCK, NOT_FOLLOWED, NOT_REGULAR, OTHER_MOUNT, OUTSIDE_INCLUDE, ROOT_REPLACED, ROOT_UNAVAILABLE, beneathRoot, checkDir, checkRoot, confirmAbsent, coversLanding, directoryAt, diskSpelling, filesystemTrustsCtime, foldsCase, inOtherMount, insideMount, leadsInto, linkOnTheWay, linkRefusal, linkWords, locateRoot, looksBinary, namesHolder, nestedAtReal, nestedGuess, nestedHolder, nestedStep, observePath, parsedSpelling, passedOverAt, pathSegments, patternRefusal, readBytes, readPrefix, restBeneath, rootAbsence, sameRoot, shownLeads, sniffImageMime, surelyAtRoot, walkScope, withinIncludes } from './observe.js';
16
+ import { CHANGED, DIRECTORY, IGNORED, NONBLOCK, OTHER_MOUNT, OUTSIDE_INCLUDE, ROOT_REPLACED, ROOT_UNAVAILABLE, beneathRoot, checkDir, checkRoot, confirmAbsent, coversLanding, directoryAt, diskSpelling, filesystemTrustsCtime, foldsCase, inOtherMount, insideMount, leadsInto, linkOnTheWay, linkRefusal, linkWords, locateRoot, looksBinary, namesHolder, nestedAtReal, nestedGuess, nestedHolder, nestedStep, observePath, parsedSpelling, passedOverAt, pathSegments, patternRefusal, readBytes, readPrefix, restBeneath, rootAbsence, sameRoot, shownLeads, sniffImageMime, surelyAtRoot, walkScope, withinIncludes } from './observe.js';
16
17
  import { TOOL_BATCH_HOOK_TIMEOUT_MS, ensureStateRegistered } from '../../module-registry.js';
17
18
  import { DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS } from '../../tool-result-history.js';
18
19
  import { countBudget, decodeForEdit, hintBudget, planFile } from './edit-plan.js';
@@ -20,9 +21,9 @@ import { cutLine, editList, editReceipt, fitted, refusalText } from './edit-text
20
21
  import { fileText } from './text.js';
21
22
  import { DEFAULT_READ_LINE_LIMIT, characterPage, linePage } from './read-page.js';
22
23
  import { grepText, textAnswer } from './result-text.js';
23
- import { NOT_PUSHED_IN_OTHER_MOUNT, WALK_CAP, entryMode, pushPaths, reconcilePass, settleAdoptionBeforeMutation, } from './reconcile.js';
24
+ import { NOT_PUSHED_IN_OTHER_MOUNT, WALK_CAP, entryMode, explained, pushPaths, reconcilePass, settleAdoptionBeforeMutation, wayOn, } from './reconcile.js';
24
25
  import { EffectFailed, unlinkContained } from './effects.js';
25
- import { AWAITS_REOPEN, ROOT_RECHECK, ROOT_UNCHECKED, Refusal, changesFiles, didNothing, errnoCode, errnoWayOn, errnoWords, hostFix, onlyTheHost, systemWords, unexpectedAnswer } from './answers.js';
26
+ import { AWAITS_REOPEN, MOUNT_IT, ROOT_RECHECK, Refusal, UNFOLLOWED_STEP, changesFiles, didNothing, errnoCode, errnoWayOn, errnoWords, hostFix, onlyTheHost, systemWords, unexpectedAnswer } from './answers.js';
26
27
  import { unknownToolMessage } from '../unknown-tool.js';
27
28
  import { closestNames } from '../../closest-names.js';
28
29
  import { RecordJournal } from '../../record-journal.js';
@@ -124,6 +125,13 @@ class NotRegularFile extends WorkspaceReadError {
124
125
  super('not_found', `Not a regular file: ${path}`, stage);
125
126
  }
126
127
  }
128
+ class LinkOnTheWay extends WorkspaceReadError {
129
+ reason;
130
+ constructor(code, message, reason, errno) {
131
+ super(code, message, 'readlink', errno);
132
+ this.reason = reason;
133
+ }
134
+ }
127
135
  class RootUnavailable extends WorkspaceReadError {
128
136
  mount;
129
137
  reason;
@@ -139,10 +147,10 @@ class RootUnavailable extends WorkspaceReadError {
139
147
  * Why a pass decided nothing, or null where it decided its paths. A pass gives
140
148
  * up deciding, and reports no path, when the selected branch kept changing
141
149
  * while it gathered, or when a sync's branch was no longer selected; it then
142
- * carries no branchId, and the region it leaves incomplete says why.
150
+ * carries no branchId, and says why in `undecided`.
143
151
  */
144
152
  function undecidedOf(pass) {
145
- return pass.branchId === undefined ? (pass.incomplete[0]?.reason ?? 'the pass decided nothing') : null;
153
+ return pass.branchId === undefined ? (pass.undecided ?? 'the pass decided nothing') : null;
146
154
  }
147
155
  function errnoOf(err) {
148
156
  return typeof err === 'object' && err !== null && 'code' in err && typeof err.code === 'string'
@@ -194,15 +202,16 @@ function matchesPartialPrefix(buffer, prefix) {
194
202
  */
195
203
  async function linkReadError(mount, found, mountPrefixedPath, relativePath, nested, rootReal) {
196
204
  if (!('link' in found)) {
197
- return new WorkspaceReadError('not_found', `Unable to read file: ${mountPrefixedPath}: ${linkWords(mount.config.name, found, null)}`, 'readlink', found.code);
205
+ const reason = linkWords(mount.config.name, found, null);
206
+ return new LinkOnTheWay('not_found', `Unable to read file: ${mountPrefixedPath}: ${reason}`, reason, found.code);
198
207
  }
199
208
  const leads = await shownLeads(rootReal, rootReal, found, mount.config.include);
200
- const reason = `${mountPrefixedPath}: ${linkWords(mount.config.name, found, leads, nested, mount.config.include)}`;
209
+ const reason = linkWords(mount.config.name, found, leads, nested, mount.config.include);
201
210
  const holder = leads?.kind === 'inside' ? nestedHolder(leads.target, nested) : undefined;
202
211
  const code = leads?.kind === 'outside' ? 'escape'
203
212
  : leads?.kind === 'inside' && holder && namesHolder(leads, nested) ? 'other_mount'
204
213
  : relativePath === found.link ? 'symlink' : 'parent_symlink';
205
- return new WorkspaceReadError(code, reason, 'readlink');
214
+ return new LinkOnTheWay(code, `${mountPrefixedPath}: ${reason}`, reason);
206
215
  }
207
216
  /**
208
217
  * `to` beneath `from`, mount-relative and `/`-separated, whatever the
@@ -1996,14 +2005,14 @@ export class WorkspaceModule {
1996
2005
  return { requestInference: true };
1997
2006
  let text;
1998
2007
  if (paths.length === 1) {
1999
- text = `[workspace event · ${op} · ${paths[0]}]`;
2008
+ text = `[${NOTICE_TAG.workspaceEvent} · ${op} · ${paths[0]}]`;
2000
2009
  }
2001
2010
  else {
2002
2011
  const list = paths.map(p => `- ${p}`).join('\n');
2003
- text = `[workspace event · ${op} · ${paths.length} files in ${mountName}/]\n${list}`;
2012
+ text = `[${NOTICE_TAG.workspaceEvent} · ${op} · ${paths.length} files in ${mountName}/]\n${list}`;
2004
2013
  }
2005
2014
  if (conflicts && conflicts.length > 0) {
2006
- text += `\n[conflicts with disk: ${conflicts.join(', ')}; reading each shows how to settle it]`;
2015
+ text += `\n[${NOTICE_TAG.conflictsWithDisk}: ${conflicts.join(', ')}; reading each shows how to settle it]`;
2007
2016
  }
2008
2017
  return {
2009
2018
  requestInference: true,
@@ -2830,6 +2839,16 @@ export class WorkspaceModule {
2830
2839
  const names = [...this.mounts.keys()];
2831
2840
  return names.length === 0 ? 'this workspace has no mounts' : `the mounts are ${listed(names.map((name) => JSON.stringify(name)))}`;
2832
2841
  }
2842
+ emptyPathField(mount, bare) {
2843
+ const empty = '"path" is "", which names no file or directory.';
2844
+ if (mount === undefined)
2845
+ return `${empty} Leaving "path" out ${bare}.`;
2846
+ if (mount === '')
2847
+ return `${empty} "mount" is "" too; leaving both out ${bare}.`;
2848
+ if (this.mounts.has(mount))
2849
+ return `${empty} Leaving "path" out covers mount ${JSON.stringify(mount)}.`;
2850
+ return `${empty} "mount" is ${JSON.stringify(mount)}, which is no mount's name; ${this.mountList()}. Leaving both out ${bare}.`;
2851
+ }
2833
2852
  noSuchMountField(given, bare) {
2834
2853
  const [close] = closestNames(given, [...this.mounts.keys()], 1);
2835
2854
  return `"mount" is ${JSON.stringify(given)}, which is no mount's name; ${this.mountList()}.`
@@ -3568,7 +3587,7 @@ export class WorkspaceModule {
3568
3587
  return `${shown} is in conflict: the workspace deleted it and disk changed it since. ` +
3569
3588
  'Sync this path to take the disk version.';
3570
3589
  case 'unverified':
3571
- return uncheckedFile(shown, report.note);
3590
+ return uncheckedFile(shown, report.note, report.noteCause ?? {});
3572
3591
  default:
3573
3592
  return missing ?? `there's no file at ${shown}. ls or glob lists the files that exist.`;
3574
3593
  }
@@ -3736,9 +3755,9 @@ export class WorkspaceModule {
3736
3755
  const rootReal = await realpath(root).catch(() => root);
3737
3756
  const view = this.viewOf(mount, this.agreementOrThrow().rootIdentityForRead(mount.config.name));
3738
3757
  const proof = await confirmAbsent(view, rootReal, relativePath);
3739
- if (typeof proof !== 'string')
3758
+ if (!('reason' in proof))
3740
3759
  return 'none';
3741
- return { unreadable: proof === CHANGED ? containedRefusal(err, path) : proof };
3760
+ return { unreadable: proof.reason === CHANGED ? containedRefusal(err, path) : proof.reason };
3742
3761
  }
3743
3762
  async restoreEach(mount, versions, each) {
3744
3763
  const byHash = new Map();
@@ -4700,7 +4719,7 @@ export class WorkspaceModule {
4700
4719
  // whether the file is gone, so it's taken as the agent's write too.
4701
4720
  if (error instanceof EffectFailed && error.touched) {
4702
4721
  this.recordDirectWrite(mount, agentName, since);
4703
- return { success: false, isError: true, error: `The delete of ${path} failed after it reached the file, so the file may be gone: ${directReason(error)}. ls shows whether it is.` };
4722
+ return { success: false, isError: true, error: `The delete of ${JSON.stringify(path)} failed after it reached the file, so the file may be gone: ${directReason(error)}. ls shows whether it is.` };
4704
4723
  }
4705
4724
  return { success: false, isError: true, error: directDidNothing('delete', path, error) };
4706
4725
  }
@@ -5059,7 +5078,8 @@ export class WorkspaceModule {
5059
5078
  .filter((region) => region.kind === 'error' || region.kind === 'cap' || (region.kind === 'ignored' && region.path === dir && dir !== ''))
5060
5079
  .map((region) => ({
5061
5080
  path: region.path,
5062
- reason: region.reason === OTHER_MOUNT ? this.otherMountNote(mount.config.name, region.path, 'list', moved) : region.reason,
5081
+ reason: region.reason === OTHER_MOUNT ? this.otherMountNote(mount.config.name, region.path, 'list', moved)
5082
+ : explained(region.reason, region.errno !== undefined ? { errno: region.errno } : {}, 'list'),
5063
5083
  }));
5064
5084
  return {
5065
5085
  view,
@@ -5669,7 +5689,10 @@ export class WorkspaceModule {
5669
5689
  const pendingDeletions = [];
5670
5690
  const modesKept = [];
5671
5691
  let explicit = null;
5672
- if (input.path) {
5692
+ if (input.path !== undefined) {
5693
+ if (input.path === '') {
5694
+ return { success: false, error: didNothing('materialize', this.emptyPathField(input.mount, "covers every mount that takes writes and doesn't already write them straight to disk")), isError: true };
5695
+ }
5673
5696
  const parsed = await this.parsePath(input.path);
5674
5697
  explicit = parsed;
5675
5698
  // A path outside the mount's include that's no scope (refusesAsOutside)
@@ -5683,7 +5706,7 @@ export class WorkspaceModule {
5683
5706
  if (explicit) {
5684
5707
  mountsToMaterialize = [{ name: explicit.mount.config.name, mount: explicit.mount }];
5685
5708
  }
5686
- else if (input.mount) {
5709
+ else if (input.mount !== undefined) {
5687
5710
  const m = this.mounts.get(input.mount);
5688
5711
  if (!m) {
5689
5712
  return { success: false, error: didNothing('materialize', this.noSuchMountField(input.mount, "covers every mount that takes writes and doesn't already write them straight to disk")), isError: true };
@@ -5885,7 +5908,9 @@ export class WorkspaceModule {
5885
5908
  }
5886
5909
  // A path names its own mount; otherwise the given mount, or every mount.
5887
5910
  let targets;
5888
- if (input.path) {
5911
+ if (input.path !== undefined) {
5912
+ if (input.path === '')
5913
+ return { success: false, error: didNothing('sync', this.emptyPathField(input.mount, 'covers every mount')), isError: true };
5889
5914
  const { mount, relativePath, moved, root } = await this.parsePath(input.path);
5890
5915
  // A path outside the mount's include that's no scope (refusesAsOutside)
5891
5916
  // is refused; beneath a directory, the walk passes over what the include
@@ -5895,7 +5920,7 @@ export class WorkspaceModule {
5895
5920
  }
5896
5921
  targets = [{ name: mount.config.name, mount, relativePath }];
5897
5922
  }
5898
- else if (input.mount) {
5923
+ else if (input.mount !== undefined) {
5899
5924
  const m = this.mounts.get(input.mount);
5900
5925
  if (!m) {
5901
5926
  return { success: false, error: didNothing('sync', this.noSuchMountField(input.mount, 'covers every mount')), isError: true };
@@ -5976,7 +6001,7 @@ export class WorkspaceModule {
5976
6001
  // refused" don't look identical from the outside.
5977
6002
  for (const r of pass.reports.values()) {
5978
6003
  if (r.state === 'unverified') {
5979
- allSkipped.push({ mount: name, path: r.path, reason: `disk could not be checked: ${r.note ?? 'not observed'}` });
6004
+ allSkipped.push({ mount: name, path: r.path, reason: `disk could not be checked: ${explained(r.note ?? 'not observed', r.noteCause ?? {}, 'sync')}` });
5980
6005
  }
5981
6006
  else if (r.state === 'disk-only') {
5982
6007
  allSkipped.push({ mount: name, path: r.path, reason: 'disk-only: binary or over the size limit, never stored' });
@@ -6406,7 +6431,17 @@ function directReason(error) {
6406
6431
  return error instanceof Error ? error.message : String(error);
6407
6432
  }
6408
6433
  function directWayOn(error) {
6409
- return error instanceof FileChanged ? 'Calling again reads it afresh.' : error instanceof WriteRefused ? error.wayOn : null;
6434
+ if (error instanceof FileChanged)
6435
+ return 'Calling again reads it afresh.';
6436
+ if (error instanceof WriteRefused)
6437
+ return error.wayOn;
6438
+ if (error instanceof EffectFailed) {
6439
+ if (error.detail.errno !== undefined)
6440
+ return errnoWayOn(error.detail.errno);
6441
+ if (error.detail.again === true)
6442
+ return 'Calling again checks it afresh.';
6443
+ }
6444
+ return null;
6410
6445
  }
6411
6446
  function directDidNothing(tool, path, error) {
6412
6447
  const on = directWayOn(error);
@@ -6439,17 +6474,12 @@ function containedRefusal(err, path) {
6439
6474
  function deletedOnDisk(path) {
6440
6475
  return `there's no file at ${JSON.stringify(path)} any more: disk's copy was deleted since the workspace last agreed with it, and the workspace took that in first. ls or glob lists the files that exist.`;
6441
6476
  }
6442
- function uncheckedFile(shown, note) {
6477
+ function uncheckedFile(shown, note, cause) {
6443
6478
  if (note === DIRECTORY)
6444
6479
  return `${shown} is a directory, not a file; ls ${shown} lists what's in it.`;
6445
- const on = note === ROOT_UNAVAILABLE ? ROOT_UNCHECKED
6446
- : note === ROOT_REPLACED ? null
6447
- : note === NOT_FOLLOWED ? "Calling again won't change that; if you know the file it leads to, name that file by its own path."
6448
- : note === LEAVES_MOUNT ? "Calling again won't change that; if a mount holds the file it leads to, name it by its path there."
6449
- : note === NOT_REGULAR ? onlyTheHost('put a regular file there')
6450
- : note === CHANGED ? 'Calling again checks it afresh.'
6451
- : 'Calling again checks disk again; if the same reason comes back, whoever runs this host can fix it on disk.';
6452
- return `${shown} isn't in the workspace, and disk couldn't be checked: ${sentence(note ?? 'not observed')}${on !== null ? ` ${on}` : ''}`;
6480
+ const on = note === ROOT_REPLACED || cause.ownStep === true ? null
6481
+ : (note === undefined ? null : wayOn(note, cause, 'read')) ?? 'calling again checks disk again; if the same reason comes back, whoever runs this host can fix it on disk';
6482
+ return `${shown} isn't in the workspace, and disk couldn't be checked: ${sentence(note ?? 'not observed')}${on !== null ? ` ${on[0].toUpperCase()}${sentence(on.slice(1))}` : ''}`;
6453
6483
  }
6454
6484
  function namesRoot(path, mount) {
6455
6485
  return `${JSON.stringify(path)} names the root of mount "${mount}", which isn't a file; ls "${mount}" lists the files in it.`;
@@ -6485,17 +6515,17 @@ function readRefusal(err, path) {
6485
6515
  }
6486
6516
  return `${shown} can't be read: ${sentence(err.reason)}`;
6487
6517
  }
6488
- if (err.stage === 'readlink')
6489
- return sentence(err.message);
6518
+ if (err instanceof LinkOnTheWay)
6519
+ return `${shown} can't be reached: ${sentence(err.reason)}`;
6490
6520
  switch (err.code) {
6491
6521
  case 'directory':
6492
6522
  return `${shown} is a directory, not a file; ls ${shown} lists what's in it.`;
6493
6523
  case 'symlink':
6494
6524
  return `${shown} is a symlink, and its mount doesn't follow symlinks, so the workspace's tools don't go through it. `
6495
- + 'Calling again won\'t change that; if you know the file it leads to, name that file by its own path.';
6525
+ + `Calling again won't change that; ${UNFOLLOWED_STEP}.`;
6496
6526
  case 'escape':
6497
6527
  return `${shown} leads through a symlink to a place outside its mount, so the workspace's tools don't reach it. `
6498
- + 'Calling again won\'t change that; if a mount holds that file, name it by its path there.';
6528
+ + `Calling again won't change that; ${MOUNT_IT}.`;
6499
6529
  case 'changed':
6500
6530
  return `${shown} changed on disk while it was being read. Calling again reads it afresh.`;
6501
6531
  case 'not_found':
@@ -6557,7 +6587,7 @@ function remedyOfLost(lost) {
6557
6587
  */
6558
6588
  function headMoveText(mounts) {
6559
6589
  const one = mounts.length === 1;
6560
- return `[workspace] Writes to ${listed(mounts.map((name) => `\`${name}\``))} from turns the history no longer holds are still on disk. `
6590
+ return `[${NOTICE_TAG.workspace}] Writes to ${listed(mounts.map((name) => `\`${name}\``))} from turns the history no longer holds are still on disk. `
6561
6591
  + `If ${one ? "it's a git checkout" : "they're git checkouts"}, \`git status\` and \`git diff\` show them; `
6562
6592
  + 'a `git checkout` of a file drops every unstaged change in it, including the ones the history still holds.';
6563
6593
  }