@animalabs/agent-framework 0.22.0 → 0.22.2

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 (418) hide show
  1. package/README.md +12 -5
  2. package/dist/src/agent.d.ts +192 -19
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +407 -66
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.d.ts +41 -0
  7. package/dist/src/api/mcp-server-core.d.ts.map +1 -0
  8. package/dist/src/api/mcp-server-core.js +593 -0
  9. package/dist/src/api/mcp-server-core.js.map +1 -0
  10. package/dist/src/api/mcp-server.d.ts +2 -36
  11. package/dist/src/api/mcp-server.d.ts.map +1 -1
  12. package/dist/src/api/mcp-server.js +15 -595
  13. package/dist/src/api/mcp-server.js.map +1 -1
  14. package/dist/src/api/server.d.ts +9 -1
  15. package/dist/src/api/server.d.ts.map +1 -1
  16. package/dist/src/api/server.js +84 -23
  17. package/dist/src/api/server.js.map +1 -1
  18. package/dist/src/api/types.d.ts +9 -1
  19. package/dist/src/api/types.d.ts.map +1 -1
  20. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  21. package/dist/src/code-execution/py-runner.js.map +1 -1
  22. package/dist/src/framework.d.ts +682 -103
  23. package/dist/src/framework.d.ts.map +1 -1
  24. package/dist/src/framework.js +3202 -718
  25. package/dist/src/framework.js.map +1 -1
  26. package/dist/src/gate/event-gate.d.ts.map +1 -1
  27. package/dist/src/gate/gate-script.d.ts.map +1 -1
  28. package/dist/src/gate/gate-script.js.map +1 -1
  29. package/dist/src/index.d.ts +4 -4
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +2 -2
  32. package/dist/src/index.js.map +1 -1
  33. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  34. package/dist/src/mcpl/channel-registry.d.ts +26 -7
  35. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  36. package/dist/src/mcpl/channel-registry.js +102 -22
  37. package/dist/src/mcpl/channel-registry.js.map +1 -1
  38. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  39. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  40. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  41. package/dist/src/mcpl/errors.js.map +1 -1
  42. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  43. package/dist/src/mcpl/feature-set-manager.js +4 -3
  44. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  45. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  46. package/dist/src/mcpl/index.d.ts +2 -0
  47. package/dist/src/mcpl/index.d.ts.map +1 -1
  48. package/dist/src/mcpl/index.js.map +1 -1
  49. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  50. package/dist/src/mcpl/log-value.d.ts +54 -0
  51. package/dist/src/mcpl/log-value.d.ts.map +1 -0
  52. package/dist/src/mcpl/log-value.js +109 -0
  53. package/dist/src/mcpl/log-value.js.map +1 -0
  54. package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
  55. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  56. package/dist/src/mcpl/prose-stream-router.js +4 -4
  57. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  58. package/dist/src/mcpl/push-coalescer.d.ts +38 -3
  59. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  60. package/dist/src/mcpl/push-coalescer.js +81 -16
  61. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  62. package/dist/src/mcpl/push-handler.d.ts +26 -3
  63. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  64. package/dist/src/mcpl/push-handler.js +87 -23
  65. package/dist/src/mcpl/push-handler.js.map +1 -1
  66. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  67. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  68. package/dist/src/mcpl/reference-fetcher.js +20 -9
  69. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  70. package/dist/src/mcpl/references.d.ts +29 -6
  71. package/dist/src/mcpl/references.d.ts.map +1 -1
  72. package/dist/src/mcpl/references.js +57 -16
  73. package/dist/src/mcpl/references.js.map +1 -1
  74. package/dist/src/mcpl/refusal-lines.d.ts +37 -0
  75. package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
  76. package/dist/src/mcpl/refusal-lines.js +61 -0
  77. package/dist/src/mcpl/refusal-lines.js.map +1 -0
  78. package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
  79. package/dist/src/mcpl/server-connection.d.ts +46 -3
  80. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  81. package/dist/src/mcpl/server-connection.js +98 -15
  82. package/dist/src/mcpl/server-connection.js.map +1 -1
  83. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  84. package/dist/src/mcpl/tool-classes.d.ts +1 -1
  85. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  86. package/dist/src/mcpl/tool-classes.js +3 -2
  87. package/dist/src/mcpl/tool-classes.js.map +1 -1
  88. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  89. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  90. package/dist/src/mcpl/transport.d.ts +23 -5
  91. package/dist/src/mcpl/transport.d.ts.map +1 -1
  92. package/dist/src/mcpl/transport.js +103 -10
  93. package/dist/src/mcpl/transport.js.map +1 -1
  94. package/dist/src/mcpl/types.d.ts +30 -26
  95. package/dist/src/mcpl/types.d.ts.map +1 -1
  96. package/dist/src/mcpl/types.js.map +1 -1
  97. package/dist/src/module-registry.d.ts +9 -3
  98. package/dist/src/module-registry.d.ts.map +1 -1
  99. package/dist/src/module-registry.js +3 -2
  100. package/dist/src/module-registry.js.map +1 -1
  101. package/dist/src/modules/api/index.d.ts.map +1 -1
  102. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  103. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  104. package/dist/src/modules/discord/index.d.ts.map +1 -1
  105. package/dist/src/modules/discord/index.js +4 -2
  106. package/dist/src/modules/discord/index.js.map +1 -1
  107. package/dist/src/modules/health/index.d.ts.map +1 -1
  108. package/dist/src/modules/health/index.js.map +1 -1
  109. package/dist/src/modules/history/index.d.ts.map +1 -1
  110. package/dist/src/modules/history/index.js.map +1 -1
  111. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  112. package/dist/src/modules/history/semantic.js.map +1 -1
  113. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  114. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  115. package/dist/src/modules/workspace/disk-agreement.d.ts +22 -4
  116. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/disk-agreement.js +63 -25
  118. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  119. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  120. package/dist/src/modules/workspace/index.d.ts +8 -4
  121. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  122. package/dist/src/modules/workspace/index.js +85 -16
  123. package/dist/src/modules/workspace/index.js.map +1 -1
  124. package/dist/src/modules/workspace/observe.d.ts +56 -11
  125. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  126. package/dist/src/modules/workspace/observe.js +288 -43
  127. package/dist/src/modules/workspace/observe.js.map +1 -1
  128. package/dist/src/modules/workspace/reconcile.d.ts +31 -1
  129. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  130. package/dist/src/modules/workspace/reconcile.js +63 -12
  131. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  132. package/dist/src/modules/workspace/types.d.ts +27 -2
  133. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  134. package/dist/src/modules/workspace/types.js.map +1 -1
  135. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  136. package/dist/src/modules/workspace/watcher.js.map +1 -1
  137. package/dist/src/operator-log.d.ts +41 -0
  138. package/dist/src/operator-log.d.ts.map +1 -1
  139. package/dist/src/operator-log.js.map +1 -1
  140. package/dist/src/provider-waits.d.ts +216 -0
  141. package/dist/src/provider-waits.d.ts.map +1 -0
  142. package/dist/src/provider-waits.js +482 -0
  143. package/dist/src/provider-waits.js.map +1 -0
  144. package/dist/src/queue.d.ts.map +1 -1
  145. package/dist/src/record-journal.d.ts.map +1 -1
  146. package/dist/src/record-journal.js.map +1 -1
  147. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  148. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  149. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  150. package/dist/src/recovery/offline-branch.js +29 -24
  151. package/dist/src/recovery/offline-branch.js.map +1 -1
  152. package/dist/src/recovery/recover-cli.js.map +1 -1
  153. package/dist/src/refusal-reactions.d.ts +14 -12
  154. package/dist/src/refusal-reactions.d.ts.map +1 -1
  155. package/dist/src/refusal-reactions.js +14 -12
  156. package/dist/src/refusal-reactions.js.map +1 -1
  157. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
  158. package/dist/src/runtime/liveness-watchdog.js.map +1 -1
  159. package/dist/src/shutdown.d.ts.map +1 -1
  160. package/dist/src/shutdown.js.map +1 -1
  161. package/dist/src/stored-removal.d.ts +73 -0
  162. package/dist/src/stored-removal.d.ts.map +1 -0
  163. package/dist/src/stored-removal.js +100 -0
  164. package/dist/src/stored-removal.js.map +1 -0
  165. package/dist/src/thinking-token-stamp.js.map +1 -1
  166. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  167. package/dist/src/tool-image-ledger.js.map +1 -1
  168. package/dist/src/tool-presentation.d.ts.map +1 -1
  169. package/dist/src/tool-presentation.js.map +1 -1
  170. package/dist/src/tool-result-guard.d.ts +30 -13
  171. package/dist/src/tool-result-guard.d.ts.map +1 -1
  172. package/dist/src/tool-result-guard.js +71 -25
  173. package/dist/src/tool-result-guard.js.map +1 -1
  174. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  175. package/dist/src/tune-out/coordinator.js.map +1 -1
  176. package/dist/src/tune-out/tools.d.ts +1 -1
  177. package/dist/src/tune-out/tools.d.ts.map +1 -1
  178. package/dist/src/types/agent.d.ts +42 -6
  179. package/dist/src/types/agent.d.ts.map +1 -1
  180. package/dist/src/types/events.d.ts +12 -0
  181. package/dist/src/types/events.d.ts.map +1 -1
  182. package/dist/src/types/framework.d.ts +10 -0
  183. package/dist/src/types/framework.d.ts.map +1 -1
  184. package/dist/src/types/module.d.ts +20 -8
  185. package/dist/src/types/module.d.ts.map +1 -1
  186. package/dist/src/types/trace.d.ts +75 -13
  187. package/dist/src/types/trace.d.ts.map +1 -1
  188. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  189. package/dist/test/agent-runtime-settings.test.js +1139 -3
  190. package/dist/test/agent-runtime-settings.test.js.map +1 -1
  191. package/dist/test/batched-wake-locus.test.js.map +1 -1
  192. package/dist/test/channel-label-history.test.js.map +1 -1
  193. package/dist/test/code-execution.test.js.map +1 -1
  194. package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -1
  195. package/dist/test/conversation-router.test.js.map +1 -1
  196. package/dist/test/conversation-routing.test.js +240 -6
  197. package/dist/test/conversation-routing.test.js.map +1 -1
  198. package/dist/test/discord-awareness-outbox.test.js +107 -0
  199. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  200. package/dist/test/discord-typing-keepalive.test.d.ts +2 -0
  201. package/dist/test/discord-typing-keepalive.test.d.ts.map +1 -0
  202. package/dist/test/discord-typing-keepalive.test.js +77 -0
  203. package/dist/test/discord-typing-keepalive.test.js.map +1 -0
  204. package/dist/test/ephemeral-settle.test.js.map +1 -1
  205. package/dist/test/event-gate-passthrough.test.js.map +1 -1
  206. package/dist/test/event-gate-policy-mutation.test.js.map +1 -1
  207. package/dist/test/event-gate-rate-limit.test.js.map +1 -1
  208. package/dist/test/event-gate-script.test.js.map +1 -1
  209. package/dist/test/event-gate-selfwake.test.js.map +1 -1
  210. package/dist/test/event-gate-tags.test.js.map +1 -1
  211. package/dist/test/event-gate.test.js.map +1 -1
  212. package/dist/test/explicit-prose-routing.test.js +363 -44
  213. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  214. package/dist/test/framework.test.js +160 -1
  215. package/dist/test/framework.test.js.map +1 -1
  216. package/dist/test/helpers/coalescing-fixture.d.ts +2 -0
  217. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  218. package/dist/test/helpers/coalescing-fixture.js +11 -4
  219. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  220. package/dist/test/helpers/mock-membrane.d.ts.map +1 -1
  221. package/dist/test/helpers/mock-membrane.js.map +1 -1
  222. package/dist/test/hide-then-turn.test.d.ts +2 -0
  223. package/dist/test/hide-then-turn.test.d.ts.map +1 -0
  224. package/dist/test/hide-then-turn.test.js +101 -0
  225. package/dist/test/hide-then-turn.test.js.map +1 -0
  226. package/dist/test/history-module-overview.test.js.map +1 -1
  227. package/dist/test/history-module-semantic.test.js.map +1 -1
  228. package/dist/test/history-module.test.js.map +1 -1
  229. package/dist/test/history-semantic-review-findings.test.js.map +1 -1
  230. package/dist/test/host-quiesce.test.js +125 -0
  231. package/dist/test/host-quiesce.test.js.map +1 -1
  232. package/dist/test/inference-exhaustion-rewind.test.js +220 -14
  233. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  234. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  235. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  236. package/dist/test/inference-failure-bounds.test.js +1202 -0
  237. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  238. package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -1
  239. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  240. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  241. package/dist/test/late-events-after-stop.test.js +122 -0
  242. package/dist/test/late-events-after-stop.test.js.map +1 -0
  243. package/dist/test/liveness-watchdog.test.js.map +1 -1
  244. package/dist/test/maintenance-timer.test.js +1564 -2
  245. package/dist/test/maintenance-timer.test.js.map +1 -1
  246. package/dist/test/mcp-server-bin.test.d.ts +2 -0
  247. package/dist/test/mcp-server-bin.test.d.ts.map +1 -0
  248. package/dist/test/mcp-server-bin.test.js +142 -0
  249. package/dist/test/mcp-server-bin.test.js.map +1 -0
  250. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  251. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  252. package/dist/test/mcpl-child-env.test.js +1 -1
  253. package/dist/test/mcpl-child-env.test.js.map +1 -1
  254. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +2 -0
  255. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +1 -0
  256. package/dist/test/mcpl-coalesced-content-parity.test.js +532 -0
  257. package/dist/test/mcpl-coalesced-content-parity.test.js.map +1 -0
  258. package/dist/test/mcpl-coalescing.test.js +78 -16
  259. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  260. package/dist/test/mcpl-config-diagnostics.test.js +6 -3
  261. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -1
  262. package/dist/test/mcpl-gate-roundtrip.test.js.map +1 -1
  263. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  264. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  265. package/dist/test/mcpl-reconnect-events.test.js.map +1 -1
  266. package/dist/test/mcpl-reconnect-reregistration.test.js +2 -0
  267. package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
  268. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  269. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  270. package/dist/test/mcpl-reference-fields.test.js +69 -2
  271. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  272. package/dist/test/mcpl-rejection-log-lines.test.d.ts +2 -0
  273. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +1 -0
  274. package/dist/test/mcpl-rejection-log-lines.test.js +349 -0
  275. package/dist/test/mcpl-rejection-log-lines.test.js.map +1 -0
  276. package/dist/test/mcpl-stderr-tail.test.d.ts +2 -0
  277. package/dist/test/mcpl-stderr-tail.test.d.ts.map +1 -0
  278. package/dist/test/mcpl-stderr-tail.test.js +210 -0
  279. package/dist/test/mcpl-stderr-tail.test.js.map +1 -0
  280. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +1 -1
  281. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  282. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -1
  283. package/dist/test/mcpl-tool-lifecycle.test.js.map +1 -1
  284. package/dist/test/mcpl-transport-close.test.d.ts +2 -0
  285. package/dist/test/mcpl-transport-close.test.d.ts.map +1 -0
  286. package/dist/test/mcpl-transport-close.test.js +373 -0
  287. package/dist/test/mcpl-transport-close.test.js.map +1 -0
  288. package/dist/test/mcpl-transport-spawn.test.d.ts +2 -0
  289. package/dist/test/mcpl-transport-spawn.test.d.ts.map +1 -0
  290. package/dist/test/mcpl-transport-spawn.test.js +58 -0
  291. package/dist/test/mcpl-transport-spawn.test.js.map +1 -0
  292. package/dist/test/mcpl-ws-transport.test.js +104 -0
  293. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  294. package/dist/test/offline-recovery-branch.test.js +344 -0
  295. package/dist/test/offline-recovery-branch.test.js.map +1 -1
  296. package/dist/test/operator-surgery.test.js +197 -0
  297. package/dist/test/operator-surgery.test.js.map +1 -1
  298. package/dist/test/ops-alert.test.js +2 -1
  299. package/dist/test/ops-alert.test.js.map +1 -1
  300. package/dist/test/overbudget-drain-breaker.test.js +1 -0
  301. package/dist/test/overbudget-drain-breaker.test.js.map +1 -1
  302. package/dist/test/pack-installs-alone.test.d.ts +2 -0
  303. package/dist/test/pack-installs-alone.test.d.ts.map +1 -0
  304. package/dist/test/pack-installs-alone.test.js +284 -0
  305. package/dist/test/pack-installs-alone.test.js.map +1 -0
  306. package/dist/test/physical-window-restart.test.d.ts +6 -4
  307. package/dist/test/physical-window-restart.test.d.ts.map +1 -1
  308. package/dist/test/physical-window-restart.test.js +275 -10
  309. package/dist/test/physical-window-restart.test.js.map +1 -1
  310. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  311. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  312. package/dist/test/placed-reaction-baseline.test.js +312 -0
  313. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  314. package/dist/test/present-while-acting.test.js +949 -6
  315. package/dist/test/present-while-acting.test.js.map +1 -1
  316. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  317. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  318. package/dist/test/provider-waits.test.d.ts +2 -0
  319. package/dist/test/provider-waits.test.d.ts.map +1 -0
  320. package/dist/test/provider-waits.test.js +2259 -0
  321. package/dist/test/provider-waits.test.js.map +1 -0
  322. package/dist/test/puppet-tool-call.test.js +2 -0
  323. package/dist/test/puppet-tool-call.test.js.map +1 -1
  324. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  325. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  326. package/dist/test/reaction-baseline-platform.test.js +124 -0
  327. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  328. package/dist/test/record-journal.test.js.map +1 -1
  329. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  330. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  331. package/dist/test/refusal-rewind.test.js +9 -0
  332. package/dist/test/refusal-rewind.test.js.map +1 -1
  333. package/dist/test/refusal-single-call.test.js.map +1 -1
  334. package/dist/test/release-bump.test.d.ts +2 -0
  335. package/dist/test/release-bump.test.d.ts.map +1 -0
  336. package/dist/test/release-bump.test.js +248 -0
  337. package/dist/test/release-bump.test.js.map +1 -0
  338. package/dist/test/release-changelog.test.js.map +1 -1
  339. package/dist/test/release-holds.test.d.ts +2 -0
  340. package/dist/test/release-holds.test.d.ts.map +1 -0
  341. package/dist/test/release-holds.test.js +456 -0
  342. package/dist/test/release-holds.test.js.map +1 -0
  343. package/dist/test/same-round-think-policy.test.js.map +1 -1
  344. package/dist/test/save-recent-image.test.js +138 -2
  345. package/dist/test/save-recent-image.test.js.map +1 -1
  346. package/dist/test/shutdown-errors.test.js.map +1 -1
  347. package/dist/test/silent-heartbeat-rows.test.js.map +1 -1
  348. package/dist/test/stop-with-host-operations.test.d.ts +20 -0
  349. package/dist/test/stop-with-host-operations.test.d.ts.map +1 -0
  350. package/dist/test/stop-with-host-operations.test.js +1174 -0
  351. package/dist/test/stop-with-host-operations.test.js.map +1 -0
  352. package/dist/test/stop-with-work-in-flight.test.js +162 -50
  353. package/dist/test/stop-with-work-in-flight.test.js.map +1 -1
  354. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  355. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  356. package/dist/test/surgery-marks-receipt.test.js +540 -0
  357. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  358. package/dist/test/tool-batch-hook.test.js.map +1 -1
  359. package/dist/test/tool-result-guard.test.js +352 -16
  360. package/dist/test/tool-result-guard.test.js.map +1 -1
  361. package/dist/test/tool-result-spill.test.js.map +1 -1
  362. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +1 -1
  363. package/dist/test/trunk-channel-routing.test.js +330 -7
  364. package/dist/test/trunk-channel-routing.test.js.map +1 -1
  365. package/dist/test/tune-out-e2e.test.js.map +1 -1
  366. package/dist/test/unconvertible-tool-result.test.js.map +1 -1
  367. package/dist/test/unsent-compiles.test.d.ts +2 -0
  368. package/dist/test/unsent-compiles.test.d.ts.map +1 -0
  369. package/dist/test/unsent-compiles.test.js +139 -0
  370. package/dist/test/unsent-compiles.test.js.map +1 -0
  371. package/dist/test/workspace-branch-continuation.test.js.map +1 -1
  372. package/dist/test/workspace-materialize-freshness.test.js.map +1 -1
  373. package/dist/test/workspace-read-text.test.js.map +1 -1
  374. package/dist/test/workspace-three-way.test.js +1022 -7
  375. package/dist/test/workspace-three-way.test.js.map +1 -1
  376. package/dist/test/workspace-watcher.test.js.map +1 -1
  377. package/dist/test/xml-round-persist.test.js.map +1 -1
  378. package/dist/tsconfig.tsbuildinfo +1 -1
  379. package/package.json +8 -6
  380. package/src/agent.ts +415 -67
  381. package/src/api/mcp-server-core.ts +681 -0
  382. package/src/api/mcp-server.ts +16 -685
  383. package/src/api/server.ts +87 -26
  384. package/src/api/types.ts +9 -1
  385. package/src/framework.ts +3389 -782
  386. package/src/index.ts +9 -4
  387. package/src/mcpl/channel-registry.ts +126 -31
  388. package/src/mcpl/feature-set-manager.ts +4 -3
  389. package/src/mcpl/index.ts +2 -0
  390. package/src/mcpl/log-value.ts +116 -0
  391. package/src/mcpl/prose-stream-router.ts +6 -6
  392. package/src/mcpl/push-coalescer.ts +76 -12
  393. package/src/mcpl/push-handler.ts +95 -23
  394. package/src/mcpl/reference-fetcher.ts +18 -8
  395. package/src/mcpl/references.ts +57 -13
  396. package/src/mcpl/refusal-lines.ts +59 -0
  397. package/src/mcpl/server-connection.ts +125 -15
  398. package/src/mcpl/tool-classes.ts +3 -2
  399. package/src/mcpl/transport.ts +105 -14
  400. package/src/mcpl/types.ts +5 -1
  401. package/src/module-registry.ts +17 -9
  402. package/src/modules/discord/index.ts +4 -2
  403. package/src/modules/workspace/disk-agreement.ts +74 -18
  404. package/src/modules/workspace/index.ts +85 -15
  405. package/src/modules/workspace/observe.ts +295 -43
  406. package/src/modules/workspace/reconcile.ts +90 -12
  407. package/src/modules/workspace/types.ts +26 -1
  408. package/src/operator-log.ts +30 -0
  409. package/src/provider-waits.ts +523 -0
  410. package/src/recovery/offline-branch.ts +32 -28
  411. package/src/refusal-reactions.ts +14 -12
  412. package/src/stored-removal.ts +126 -0
  413. package/src/tool-result-guard.ts +72 -24
  414. package/src/types/agent.ts +42 -6
  415. package/src/types/events.ts +12 -0
  416. package/src/types/framework.ts +10 -0
  417. package/src/types/module.ts +20 -8
  418. package/src/types/trace.ts +76 -14
@@ -15,7 +15,9 @@
15
15
  * that isn't that directory any more (an unmounted drive whose mountpoint is
16
16
  * now empty, a replaced directory) is an unavailable mount, never a disk on
17
17
  * which every file was deleted; only an explicit acceptance takes it, and
18
- * that starts its evidence afresh.
18
+ * that starts its evidence afresh. A place, where a walk stopped (`walks`),
19
+ * binds no root: it only says where the next walk starts, in whatever
20
+ * directory the root is by then.
19
21
  *
20
22
  * Every transition is write-ahead (see reconcile.ts): an `intent` entry
21
23
  * (`pending`, with the prior value and the expected one) is durable before the
@@ -92,16 +94,30 @@ type JournalEntry =
92
94
  /** The root's identity; with `reset`, an accepted new root, whose evidence starts empty. */
93
95
  | { t: 'root'; mount: string; identity: RootIdentity; reset?: true }
94
96
  | { t: 'set'; mount: string; path: string; value: Physical }
95
- | { t: 'forget'; mount: string; path: string };
97
+ | { t: 'forget'; mount: string; path: string }
98
+ /**
99
+ * Where a capped walk of `scope` stopped (the last entry it took), or null
100
+ * once a walk covered the scope. It carries no `path` field: agent-framework
101
+ * 0.22 replays an entry it doesn't know as a `forget` of `entry.path`, and
102
+ * so leaves the evidence alone when there is none.
103
+ */
104
+ | { t: 'walk'; mount: string; scope: string; after: string | null };
96
105
 
97
106
  interface MountEvidence {
98
107
  root: string;
99
108
  identity?: RootIdentity;
100
109
  paths: Map<string, Physical>;
110
+ /** By scope ('' for the mount root): where the next walk of it resumes. */
111
+ walks: Map<string, string>;
101
112
  }
102
113
 
103
114
  interface Snapshot {
104
- mounts: Record<string, { root: string; identity?: RootIdentity; paths: Record<string, Physical> }>;
115
+ mounts: Record<string, {
116
+ root: string;
117
+ identity?: RootIdentity;
118
+ paths: Record<string, Physical>;
119
+ walks?: Record<string, string>;
120
+ }>;
105
121
  }
106
122
 
107
123
  export const DISK_AGREEMENT_RECORD_TYPE = 'workspace/disk-agreement';
@@ -123,9 +139,10 @@ export class DiskAgreement {
123
139
  private readonly mounts = new Map<string, MountEvidence>();
124
140
  private configured: Array<{ name: string; root: string }> = [];
125
141
  /**
126
- * An append (or a rebuild) not yet made durable. Cleared only by a
127
- * barrier that succeeds, so a failed barrier stays owed: a later pass with
128
- * nothing new to record still settles it before deciding anything.
142
+ * An append of evidence (or a rebuild) not yet made durable. Cleared only
143
+ * by a barrier that succeeds, so a failed barrier stays owed: a later pass
144
+ * with nothing new to record still settles it before deciding anything. A
145
+ * place owes none (recordWalk).
129
146
  */
130
147
  private unsynced = false;
131
148
 
@@ -183,19 +200,19 @@ export class DiskAgreement {
183
200
  root: mount.root,
184
201
  ...(mount.identity ? { identity: mount.identity } : {}),
185
202
  paths: new Map(Object.entries(mount.paths)),
203
+ walks: new Map(Object.entries(mount.walks ?? {})),
186
204
  });
187
205
  }
188
206
  for (const { entry } of entries) {
189
207
  if (entry.t === 'mount') {
190
208
  const existing = replayed.get(entry.mount);
191
- if (!existing || !sameRootPath(existing.root, entry.root)) replayed.set(entry.mount, { root: entry.root, paths: new Map() });
209
+ if (!existing || !sameRootPath(existing.root, entry.root)) {
210
+ replayed.set(entry.mount, { root: entry.root, paths: new Map(), walks: new Map() });
211
+ }
192
212
  continue;
193
213
  }
194
214
  const mount = replayed.get(entry.mount);
195
- if (!mount) continue;
196
- if (entry.t === 'root') applyRoot(mount, entry);
197
- else if (entry.t === 'set') mount.paths.set(entry.path, entry.value);
198
- else mount.paths.delete(entry.path);
215
+ if (mount) apply(mount, entry);
199
216
  }
200
217
  this.mounts.clear();
201
218
  for (const { name, root } of this.configured) {
@@ -203,7 +220,7 @@ export class DiskAgreement {
203
220
  if (found && sameRootPath(found.root, root)) {
204
221
  this.mounts.set(name, found);
205
222
  } else {
206
- this.mounts.set(name, { root, paths: new Map() });
223
+ this.mounts.set(name, { root, paths: new Map(), walks: new Map() });
207
224
  this.journal.append({ t: 'mount', mount: name, root });
208
225
  }
209
226
  }
@@ -250,6 +267,29 @@ export class DiskAgreement {
250
267
  return out;
251
268
  }
252
269
 
270
+ /**
271
+ * Where the next walk of `scope` ('' for the mount root) resumes: the last
272
+ * entry a walk of it took before its cap stopped it, if one did.
273
+ */
274
+ walkResumesAfter(mount: string, scope: string): string | undefined {
275
+ this.ensureReconciled();
276
+ return this.mounts.get(mount)?.walks.get(scope);
277
+ }
278
+
279
+ /**
280
+ * Record where a walk of `scope` stopped (`after`), or that one covered it
281
+ * (null). Kept with the evidence so a restart resumes there too. A place
282
+ * that's lost costs coverage, never a decision: an append that didn't land
283
+ * loses one, and a checkpoint written by a version that doesn't keep them
284
+ * (0.22) loses them all, and each scope's walks start at its top again.
285
+ * So recording one owes no barrier: a pass that moves only its place makes
286
+ * no store sync, and the place becomes durable with the next one.
287
+ */
288
+ recordWalk(mount: string, scope: string, after: string | null): void {
289
+ if ((this.walkResumesAfter(mount, scope) ?? null) === after) return;
290
+ this.write({ t: 'walk', mount, scope, after }, {});
291
+ }
292
+
253
293
  /**
254
294
  * Record an intent. `durable` makes it (and every append before it) stable
255
295
  * before the caller performs the effect; batches pass it on their last one.
@@ -284,14 +324,13 @@ export class DiskAgreement {
284
324
 
285
325
  private write(entry: JournalEntry, opts: { durable?: boolean; afterCommittedState?: boolean }): void {
286
326
  this.ensureReconciled();
287
- this.unsynced = true; // until proven durable: a failed append may still have landed
327
+ // Evidence is owed a barrier until proven durable: a failed append may
328
+ // still have landed. A place isn't (recordWalk).
329
+ if (entry.t !== 'walk') this.unsynced = true;
288
330
  this.journal.append(entry, opts);
289
331
  if (opts.durable) this.unsynced = false; // synced after this append, and so everything before it
290
332
  const mount = this.mounts.get((entry as { mount: string }).mount);
291
- if (!mount) return;
292
- if (entry.t === 'root') applyRoot(mount, entry);
293
- else if (entry.t === 'set') mount.paths.set(entry.path, entry.value);
294
- else if (entry.t === 'forget') mount.paths.delete(entry.path);
333
+ if (mount) apply(mount, entry);
295
334
  }
296
335
 
297
336
  /** Checkpoint when the tail has outgrown the live evidence. */
@@ -307,6 +346,7 @@ export class DiskAgreement {
307
346
  root: mount.root,
308
347
  ...(mount.identity ? { identity: mount.identity } : {}),
309
348
  paths: Object.fromEntries(mount.paths),
349
+ ...(mount.walks.size > 0 ? { walks: Object.fromEntries(mount.walks) } : {}),
310
350
  };
311
351
  }
312
352
  this.journal.checkpoint(snapshot);
@@ -319,7 +359,23 @@ export class DiskAgreement {
319
359
  }
320
360
  }
321
361
 
362
+ /** Apply one entry to a mount's evidence; one of a kind this version doesn't know changes nothing. */
363
+ function apply(mount: MountEvidence, entry: JournalEntry): void {
364
+ if (entry.t === 'root') applyRoot(mount, entry);
365
+ else if (entry.t === 'set') mount.paths.set(entry.path, entry.value);
366
+ else if (entry.t === 'forget') mount.paths.delete(entry.path);
367
+ else if (entry.t === 'walk') {
368
+ if (entry.after === null) mount.walks.delete(entry.scope);
369
+ else mount.walks.set(entry.scope, entry.after);
370
+ }
371
+ }
372
+
322
373
  function applyRoot(mount: MountEvidence, entry: Extract<JournalEntry, { t: 'root' }>): void {
323
374
  mount.identity = entry.identity;
324
- if (entry.reset) mount.paths.clear();
375
+ if (entry.reset) {
376
+ // Evidence about another directory says nothing about this one, and the
377
+ // accepted root's walks start at its top.
378
+ mount.paths.clear();
379
+ mount.walks.clear();
380
+ }
325
381
  }
@@ -39,7 +39,7 @@ import { MountWatcher, type FsChange } from './watcher.js';
39
39
  import { hashContent, DEFAULT_MAX_FILE_SIZE } from './sync.js';
40
40
  import { DiskAgreement, sameRootIdentity } from './disk-agreement.js';
41
41
  import { BranchIntents } from './branch-intent.js';
42
- import { filesystemTrustsCtime, locateRoot } from './observe.js';
42
+ import { coveredByIgnore, filesystemTrustsCtime, locateRoot } from './observe.js';
43
43
  import { TOOL_BATCH_HOOK_TIMEOUT_MS } from '../../module-registry.js';
44
44
  import {
45
45
  type MountRuntime,
@@ -50,6 +50,7 @@ import {
50
50
  type PushResult,
51
51
  type Scope,
52
52
  type TreeOp,
53
+ WALK_CAP,
53
54
  pushPaths,
54
55
  reconcilePass,
55
56
  settleAdoptionBeforeMutation,
@@ -651,6 +652,16 @@ export class WorkspaceModule implements Module {
651
652
  private savedState: WorkspaceModuleState | null = null;
652
653
 
653
654
  constructor(config: WorkspaceConfig) {
655
+ for (const mount of config.mounts) {
656
+ const cap: unknown = mount.walkCap;
657
+ if (cap !== undefined && !(Number.isSafeInteger(cap) && (cap as number) > 0)) {
658
+ // Numbers as JavaScript writes them (JSON would say null for NaN), anything else as JSON.
659
+ const shown = typeof cap === 'number' ? String(cap) : typeof cap === 'bigint' ? `${cap}n` : JSON.stringify(cap) ?? String(cap);
660
+ throw new Error(
661
+ `[workspace] Mount "${mount.name}": walkCap must be a positive integer no larger than ${Number.MAX_SAFE_INTEGER}, not ${shown}`,
662
+ );
663
+ }
664
+ }
654
665
  // Detect overlapping mount paths: if mount A contains mount B,
655
666
  // auto-add an ignore rule on A for B's path to prevent syncing
656
667
  // the sub-mount's directory through the super-mount.
@@ -779,6 +790,8 @@ export class WorkspaceModule implements Module {
779
790
  treeStateId: mount.treeStateId,
780
791
  intents: this.intents.get(mount.config.name)!,
781
792
  readOnly: mount.config.mode === 'read-only',
793
+ walkCap: mount.config.walkCap ?? WALK_CAP,
794
+ catchUpScan: (mount.config.watch ?? 'always') === 'always',
782
795
  };
783
796
  }
784
797
 
@@ -982,9 +995,12 @@ export class WorkspaceModule implements Module {
982
995
  this.watchers.set(name, watcher);
983
996
 
984
997
  // Chokidar is started with ignoreInitial:true, so changes made while
985
- // the session was down would be invisible. One full pass catches up
986
- // under the three-way rule: only real differences produce tree
987
- // changes and events.
998
+ // the session was down would be invisible. A catch-up pass takes them
999
+ // in under the three-way rule: only real differences produce tree
1000
+ // changes and events. It takes its turn with the mount's bare syncs,
1001
+ // so on a mount over its walkCap it covers the walkCap entries after
1002
+ // where the last turn stopped, and a change elsewhere waits for the
1003
+ // rotation to reach it, or for a sync of its path.
988
1004
  void this.initialScan(name);
989
1005
  }
990
1006
 
@@ -1004,7 +1020,7 @@ export class WorkspaceModule implements Module {
1004
1020
  try {
1005
1021
  // A full pass under the three-way rule: deletions on disk are confirmed
1006
1022
  // where nothing newer is pending, and nothing unvisited is touched.
1007
- await this.withMount(mount, () => this.passUnlocked(mount, { kind: 'dir', dir: '', recursive: true }));
1023
+ await this.withMount(mount, () => this.passUnlocked(mount, { kind: 'dir', dir: '', recursive: true }, { resume: true }));
1008
1024
  } catch (err) {
1009
1025
  const msg = err instanceof Error ? err.message : String(err);
1010
1026
  this.ctx?.pushEvent({
@@ -1211,7 +1227,12 @@ export class WorkspaceModule implements Module {
1211
1227
  {
1212
1228
  name: 'sync',
1213
1229
  description: 'Bring disk changes into the workspace. Without a path, every mount (or the given one) is '
1214
- + 'rechecked in full and nothing pending in the workspace is discarded. With a path (file or directory), '
1230
+ + 'rechecked and nothing pending in the workspace is discarded. A sync takes up to the mount\'s walk '
1231
+ + 'cap of entries, files, links and the like (5000 unless configured). Past it, what this one didn\'t reach is listed under '
1232
+ + '`incomplete`, and the walk takes turns: the next sync of the same mount or directory picks up where '
1233
+ + 'the last turn stopped, so repeated syncs cover it all. On a mount watched continuously, its own catch-up '
1234
+ + 'scan, at start and when its watcher reattaches, takes turns with its whole-mount syncs. With a '
1235
+ + 'path (file or directory), '
1215
1236
  + 'the workspace takes disk\'s state there explicitly: it restores a workspace-deleted file, resolves '
1216
1237
  + 'conflicts toward disk, replaces a workspace draft, and removes a workspace file disk does not have. '
1217
1238
  + 'Each workspace change it gives up is listed under `discarded`, with the state it had and, as `op`, '
@@ -1220,7 +1241,14 @@ export class WorkspaceModule implements Module {
1220
1241
  + 'A mount whose root is not the directory disk last agreed with (an unmounted drive, a replaced '
1221
1242
  + 'directory) is unavailable and listed under `incomplete`. Reconnect the drive; acceptRoot is for a '
1222
1243
  + 'directory replaced on purpose, or a drive back under a new device number, never for one still '
1223
- + 'unplugged, whose empty mountpoint would become the root.',
1244
+ + 'unplugged, whose empty mountpoint would become the root. What the scan passed over by design and '
1245
+ + 'reported nowhere else is counted under `passedOver`, by mount and reason: entries the mount\'s '
1246
+ + 'ignore list covers that hold nothing the workspace tracks (an ignored directory counts once). So a '
1247
+ + 'total of 0, with no conflicts and nothing under `passedOver`, `incomplete` or `skipped`, means the '
1248
+ + 'sync looked everywhere and nothing had changed. A path the mount ignores is synced when you name '
1249
+ + 'it, and listed under `ignored`: a sync without a path passes over it. Naming an ignored directory '
1250
+ + 'sets aside the patterns that cover it or a directory above it; one that covers only something '
1251
+ + 'inside it still applies, and what it covers is passed over.',
1224
1252
  inputSchema: {
1225
1253
  type: 'object' as const,
1226
1254
  properties: {
@@ -2313,7 +2341,9 @@ export class WorkspaceModule implements Module {
2313
2341
  };
2314
2342
  }
2315
2343
 
2316
- return { success: true, data: { path: input.path, deleted: true } };
2344
+ // Its whole receipt is what it changed: kept in the stub if the guard
2345
+ // withholds the result (agent-framework #277).
2346
+ return { success: true, data: { path: input.path, deleted: true }, keepWhenWithheld: ['path', 'deleted'] };
2317
2347
  });
2318
2348
  }
2319
2349
 
@@ -2761,6 +2791,10 @@ export class WorkspaceModule implements Module {
2761
2791
  : {}),
2762
2792
  ...(blocked.length > 0 ? { skipped: blocked } : {}),
2763
2793
  },
2794
+ // What disk lost, then what it was written: kept in the stub if the
2795
+ // guard withholds the result (agent-framework #277). What was left or
2796
+ // skipped, and why, is in the full result.
2797
+ keepWhenWithheld: ['deleted', 'materialized'],
2764
2798
  };
2765
2799
  }
2766
2800
 
@@ -2811,6 +2845,8 @@ export class WorkspaceModule implements Module {
2811
2845
  }> = [];
2812
2846
  const allSkipped: Array<{ mount: string; path: string; reason: string }> = [];
2813
2847
  const allIncomplete: Array<{ mount: string; path: string; reason: string }> = [];
2848
+ const allPassedOver: Array<{ mount: string; reason: string; count: number }> = [];
2849
+ const allIgnored: Array<{ mount: string; path: string; note: string }> = [];
2814
2850
  const rootsAccepted: string[] = [];
2815
2851
  if (input.acceptRoot && input.path) {
2816
2852
  return { success: false, error: 'acceptRoot takes a whole mount\'s root: give mount, or nothing, not path', isError: true };
@@ -2836,7 +2872,8 @@ export class WorkspaceModule implements Module {
2836
2872
  if (input.acceptRoot && await this.acceptRootUnlocked(mount)) rootsAccepted.push(name);
2837
2873
  if (!relativePath) {
2838
2874
  // A full recheck: every file rehashed, nothing pending discarded.
2839
- return this.passUnlocked(mount, { kind: 'dir', dir: '', recursive: true }, { rehash: true });
2875
+ // Past the mount's walkCap, it resumes where the last one stopped.
2876
+ return this.passUnlocked(mount, { kind: 'dir', dir: '', recursive: true }, { rehash: true, resume: true });
2840
2877
  }
2841
2878
  // An explicit path: the workspace takes disk's state there.
2842
2879
  const prefix = relativePath + '/';
@@ -2845,9 +2882,11 @@ export class WorkspaceModule implements Module {
2845
2882
  store.treeList(mount.treeStateId, prefix).length > 0 ||
2846
2883
  this.agreementOrThrow().paths(name, relativePath).some(([p]) => p.startsWith(prefix)) ||
2847
2884
  this.intents.get(name)!.list(relativePath).some(([p]) => p.startsWith(prefix));
2848
- const opts: PassOptions = { rehash: true, adopt: () => true, branchId: askedOn };
2885
+ // named: ignore patterns covering the path asked for don't apply
2886
+ // beneath it (a directory scope's walk; a path scope doesn't walk).
2887
+ const opts: PassOptions = { rehash: true, adopt: () => true, branchId: askedOn, named: true };
2849
2888
  if (!isDir) return this.passUnlocked(mount, { kind: 'paths', paths: [relativePath] }, opts);
2850
- const beneath = await this.passUnlocked(mount, { kind: 'dir', dir: relativePath, recursive: true }, opts);
2889
+ const beneath = await this.passUnlocked(mount, { kind: 'dir', dir: relativePath, recursive: true }, { ...opts, resume: true });
2851
2890
  // Something at the path itself is no part of a directory scope: a file
2852
2891
  // disk has where the workspace holds a directory, or a workspace file
2853
2892
  // where disk has a directory. Its own pass takes disk's state there
@@ -2860,12 +2899,15 @@ export class WorkspaceModule implements Module {
2860
2899
  || this.intents.get(name)!.get(relativePath) !== null;
2861
2900
  if (!atPath) return beneath;
2862
2901
  const itself = await this.passUnlocked(mount, { kind: 'paths', paths: [relativePath] }, opts);
2902
+ const passedOver = new Map(beneath.passedOver);
2903
+ for (const [reason, count] of itself.passedOver) passedOver.set(reason, (passedOver.get(reason) ?? 0) + count);
2863
2904
  return {
2864
2905
  ops: [...beneath.ops, ...itself.ops],
2865
2906
  discarded: [...beneath.discarded, ...itself.discarded],
2866
2907
  newConflicts: [...beneath.newConflicts, ...itself.newConflicts],
2867
2908
  reports: new Map([...beneath.reports, ...itself.reports]),
2868
2909
  incomplete: [...beneath.incomplete, ...itself.incomplete],
2910
+ passedOver,
2869
2911
  dirs: beneath.dirs,
2870
2912
  branchId: itself.branchId ?? beneath.branchId,
2871
2913
  };
@@ -2892,6 +2934,17 @@ export class WorkspaceModule implements Module {
2892
2934
  }
2893
2935
  }
2894
2936
  for (const region of pass.incomplete) allIncomplete.push({ mount: name, ...region });
2937
+ for (const [reason, count] of pass.passedOver) allPassedOver.push({ mount: name, reason, count });
2938
+ // A path named explicitly is synced even where the mount's ignore list
2939
+ // covers it, and the result says so: a sync without a path passes
2940
+ // over it, so nothing else keeps it current (agent-framework #276).
2941
+ if (coveredByIgnore(relativePath, mount.config.ignore ?? [])) {
2942
+ allIgnored.push({
2943
+ mount: name,
2944
+ path: relativePath,
2945
+ note: 'this mount\'s ignore list covers it, so a sync without a path won\'t maintain it',
2946
+ });
2947
+ }
2895
2948
  }
2896
2949
 
2897
2950
  return {
@@ -2902,8 +2955,17 @@ export class WorkspaceModule implements Module {
2902
2955
  totalConflicts: allResults.reduce((sum, r) => sum + r.conflicts.length, 0),
2903
2956
  ...(allSkipped.length > 0 ? { skipped: allSkipped } : {}),
2904
2957
  ...(allIncomplete.length > 0 ? { incomplete: allIncomplete } : {}),
2958
+ ...(allPassedOver.length > 0 ? { passedOver: allPassedOver } : {}),
2959
+ ...(allIgnored.length > 0 ? { ignored: allIgnored } : {}),
2905
2960
  ...(rootsAccepted.length > 0 ? { rootsAccepted } : {}),
2906
2961
  },
2962
+ // What the workspace took from disk, gave up, or now holds as a
2963
+ // conflict, per mount, and any root it accepted: kept in the stub if
2964
+ // the guard withholds the result (agent-framework #277). What the scan
2965
+ // skipped or couldn't see is in the full result.
2966
+ // A root it accepted is short and telling: first, so a long results
2967
+ // list can't cut it away.
2968
+ keepWhenWithheld: ['rootsAccepted', 'results'],
2907
2969
  };
2908
2970
  }
2909
2971
 
@@ -2948,10 +3010,14 @@ export class WorkspaceModule implements Module {
2948
3010
  * After an agent's completed tool batch, before anything continues its turn:
2949
3011
  * scan every `watch: 'on-agent-action'` mount, so what the batch's tools did
2950
3012
  * on disk (a shell command's files, a deletion) is in the workspace when the
2951
- * agent next looks. A scan that outlasts the deadline stops holding the
2952
- * round: the miss is recorded on the mount's status and pushed as a
2953
- * `workspace:agent-action-scan-incomplete` event, and the scan continues,
2954
- * deciding on the branch selected when it applies.
3013
+ * agent next looks. On a mount over its walkCap, that holds for the first
3014
+ * walkCap entries in the walk's order, which every batch's scan takes; files
3015
+ * past them come in through syncs and the listings that reach them, while a
3016
+ * read takes in only a file the workspace doesn't hold yet. A scan that
3017
+ * outlasts the deadline stops holding the round: the miss is recorded on
3018
+ * the mount's status and pushed as a `workspace:agent-action-scan-incomplete`
3019
+ * event, and the scan continues, deciding on the branch selected when it
3020
+ * applies.
2955
3021
  */
2956
3022
  async onToolBatchComplete(_agentName?: string): Promise<void> {
2957
3023
  // Every agent's workspace is the store's current branch, so the agent
@@ -3017,6 +3083,10 @@ export class WorkspaceModule implements Module {
3017
3083
  const scan: AgentActionScan = { late: false, settled: Promise.resolve() };
3018
3084
  const pass = this.withMount(mount, () => {
3019
3085
  if (mount.agentActionScanWaiting === scan) mount.agentActionScanWaiting = undefined;
3086
+ // From the mount's start, as a listing walks, keeping no place: on a
3087
+ // mount over its walkCap, every batch's scan takes the first walkCap
3088
+ // entries in the walk's order, and the place that syncs and catch-up
3089
+ // scans resume from stays theirs.
3020
3090
  return this.passUnlocked(mount, { kind: 'dir', dir: '', recursive: true });
3021
3091
  });
3022
3092
  scan.settled = pass.then(