pi-harness-runtime 1.1.439 → 1.1.441

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 (359) hide show
  1. package/harness/e2e/auto-test-loop.ts +8 -2
  2. package/harness/e2e/browser-scrapers/base-browser-scraper.ts +2 -0
  3. package/harness/e2e/browser-scrapers/chrome-profile-manager.ts +2 -0
  4. package/harness/e2e/browser-scrapers/codex-browser-scraper.ts +2 -0
  5. package/harness/e2e/tools-detector.ts +2 -0
  6. package/harness/index.ts +2 -1
  7. package/harness/key-loader.ts +2 -0
  8. package/harness/langchain/checkpointer.ts +2 -0
  9. package/harness/langchain/cron-cli.ts +2 -0
  10. package/harness/langchain/graph.ts +2 -0
  11. package/harness/master-planner.ts +1 -1
  12. package/harness/notification-center.ts +2 -0
  13. package/harness/output-limit-handler.ts +1 -1
  14. package/harness/project-detector/detector.ts +2 -4
  15. package/harness/secret-provider.ts +2 -0
  16. package/harness/task-graph.ts +460 -113
  17. package/index.ts +16 -7
  18. package/package.json +7 -1
  19. package/packages/a2a-adapter/package.json +1 -1
  20. package/packages/agent-policy/dist/extension.d.ts.map +1 -1
  21. package/packages/agent-policy/dist/extension.js +2 -0
  22. package/packages/agent-policy/dist/extension.js.map +1 -1
  23. package/packages/agent-policy/package.json +1 -1
  24. package/packages/agent-policy/src/extension.ts +2 -0
  25. package/packages/architecture-generator/package.json +1 -1
  26. package/packages/auth/dist/minimax-browser-auth.d.ts.map +1 -1
  27. package/packages/auth/dist/minimax-browser-auth.js +23 -1
  28. package/packages/auth/dist/minimax-browser-auth.js.map +1 -1
  29. package/packages/auth/package.json +1 -1
  30. package/packages/auth/src/minimax-browser-auth.ts +30 -5
  31. package/packages/autonomous-refactor/package.json +1 -1
  32. package/packages/autonomous-runtime/dist/inbox.d.ts.map +1 -1
  33. package/packages/autonomous-runtime/dist/inbox.js +2 -0
  34. package/packages/autonomous-runtime/dist/inbox.js.map +1 -1
  35. package/packages/autonomous-runtime/package.json +1 -1
  36. package/packages/autonomous-runtime/src/inbox.ts +2 -0
  37. package/packages/cache-strategy/package.json +1 -1
  38. package/packages/capability-registry/package.json +1 -1
  39. package/packages/checkpoint/package.json +1 -1
  40. package/packages/cli-plugin-sdk/package.json +1 -1
  41. package/packages/clipboard/dist/src/clipboard.d.ts.map +1 -1
  42. package/packages/clipboard/dist/src/clipboard.js +2 -0
  43. package/packages/clipboard/dist/src/clipboard.js.map +1 -1
  44. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
  45. package/packages/clipboard/dist/src/copy-sync.js +2 -0
  46. package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
  47. package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -1
  48. package/packages/clipboard/dist/src/gist-relay.js +2 -0
  49. package/packages/clipboard/dist/src/gist-relay.js.map +1 -1
  50. package/packages/clipboard/package.json +1 -1
  51. package/packages/clipboard/src/clipboard.ts +2 -0
  52. package/packages/clipboard/src/copy-sync.ts +2 -0
  53. package/packages/clipboard/src/gist-relay.ts +2 -0
  54. package/packages/clipboard-plugin/dist/types.d.ts +9 -2
  55. package/packages/clipboard-plugin/dist/types.d.ts.map +1 -1
  56. package/packages/clipboard-plugin/package.json +1 -1
  57. package/packages/clipboard-plugin/src/types.ts +9 -2
  58. package/packages/code-generation/package.json +1 -1
  59. package/packages/code-review/package.json +1 -1
  60. package/packages/codex-adapter/package.json +1 -1
  61. package/packages/codex-watcher/package.json +1 -1
  62. package/packages/config-capture/package.json +1 -1
  63. package/packages/connection-runtime/package.json +1 -1
  64. package/packages/context-compiler/package.json +1 -1
  65. package/packages/context-discovery/package.json +1 -1
  66. package/packages/context-manager/package.json +1 -1
  67. package/packages/contracts/dist/delegation-store.js +1 -4
  68. package/packages/contracts/dist/workspace-env.js +28 -1
  69. package/packages/contracts/package.json +1 -1
  70. package/packages/contracts/src/delegation-store.ts +9 -4
  71. package/packages/contracts/src/workspace-env.ts +29 -4
  72. package/packages/cookie-sanitizer/package.json +1 -1
  73. package/packages/cost-optimizer/package.json +1 -1
  74. package/packages/dependency-analyzer/package.json +1 -1
  75. package/packages/django-plugin/package.json +1 -1
  76. package/packages/doc-generator/package.json +1 -1
  77. package/packages/durable-review/package.json +1 -1
  78. package/packages/e2e-tester/package.json +1 -1
  79. package/packages/evaluation-engine/package.json +1 -1
  80. package/packages/evaluation-runner/package.json +1 -1
  81. package/packages/event-bus/package.json +1 -1
  82. package/packages/event-sink/package.json +1 -1
  83. package/packages/event-store/dist/src/store.d.ts.map +1 -1
  84. package/packages/event-store/dist/src/store.js +2 -0
  85. package/packages/event-store/dist/src/store.js.map +1 -1
  86. package/packages/event-store/package.json +1 -1
  87. package/packages/event-store/src/store.ts +3 -0
  88. package/packages/experience-replay/package.json +1 -1
  89. package/packages/feedback-collector/package.json +1 -1
  90. package/packages/file-copy-helper/package.json +1 -1
  91. package/packages/framework-detector/package.json +1 -1
  92. package/packages/framework-plugin-sdk/package.json +1 -1
  93. package/packages/frappe-plugin/package.json +1 -1
  94. package/packages/generic-web-plugin/dist/analyzer.d.ts.map +1 -1
  95. package/packages/generic-web-plugin/dist/analyzer.js +2 -0
  96. package/packages/generic-web-plugin/dist/analyzer.js.map +1 -1
  97. package/packages/generic-web-plugin/package.json +1 -1
  98. package/packages/generic-web-plugin/src/analyzer.ts +3 -0
  99. package/packages/health-monitor/package.json +1 -1
  100. package/packages/intent-analyzer/package.json +1 -1
  101. package/packages/jev-judge/package.json +1 -1
  102. package/packages/knowledge-graph/package.json +1 -1
  103. package/packages/knowledge-retrieval/dist/mcp-client.d.ts.map +1 -1
  104. package/packages/knowledge-retrieval/dist/mcp-client.js +0 -1
  105. package/packages/knowledge-retrieval/dist/mcp-client.js.map +1 -1
  106. package/packages/knowledge-retrieval/package.json +1 -4
  107. package/packages/knowledge-retrieval/src/mcp-client.ts +0 -1
  108. package/packages/laravel-plugin/package.json +1 -1
  109. package/packages/learning-engine/package.json +1 -1
  110. package/packages/logging/package.json +1 -1
  111. package/packages/mcp-adapter/dist/server.d.ts.map +1 -1
  112. package/packages/mcp-adapter/dist/server.js +2 -0
  113. package/packages/mcp-adapter/dist/server.js.map +1 -1
  114. package/packages/mcp-adapter/package.json +1 -1
  115. package/packages/mcp-adapter/src/server.ts +3 -0
  116. package/packages/memory-engine/package.json +1 -1
  117. package/packages/milestone-manager/package.json +1 -1
  118. package/packages/model-registry/package.json +1 -1
  119. package/packages/nextjs-plugin/package.json +1 -1
  120. package/packages/notification/package.json +1 -1
  121. package/packages/observability/package.json +1 -1
  122. package/packages/okf-indexer/package.json +1 -1
  123. package/packages/paperclip-integration/dist/index.d.ts.map +1 -1
  124. package/packages/paperclip-integration/dist/index.js +2 -0
  125. package/packages/paperclip-integration/dist/index.js.map +1 -1
  126. package/packages/paperclip-integration/package.json +1 -1
  127. package/packages/paperclip-integration/src/index.ts +2 -0
  128. package/packages/performance-optimizer/package.json +1 -1
  129. package/packages/permission-mode/package.json +1 -1
  130. package/packages/privilege-broker/dist/audit.d.ts.map +1 -1
  131. package/packages/privilege-broker/dist/audit.js +2 -0
  132. package/packages/privilege-broker/dist/audit.js.map +1 -1
  133. package/packages/privilege-broker/package.json +1 -1
  134. package/packages/privilege-broker/src/audit.ts +2 -0
  135. package/packages/project-analyzer/package.json +1 -1
  136. package/packages/project-bootstrap/package.json +1 -1
  137. package/packages/projection-engine/package.json +1 -1
  138. package/packages/prompt-compiler/dist/compiler.d.ts.map +1 -1
  139. package/packages/prompt-compiler/dist/compiler.js +2 -0
  140. package/packages/prompt-compiler/dist/compiler.js.map +1 -1
  141. package/packages/prompt-compiler/package.json +1 -1
  142. package/packages/prompt-compiler/src/compiler.ts +2 -0
  143. package/packages/prompt-versioning/package.json +1 -1
  144. package/packages/provider-adapter-sdk/package.json +1 -1
  145. package/packages/provider-router/package.json +1 -1
  146. package/packages/provider-selector/package.json +1 -1
  147. package/packages/providers/package.json +1 -1
  148. package/packages/qdrant-skills/package.json +1 -1
  149. package/packages/quota-manager/package.json +1 -1
  150. package/packages/rate-limiter/package.json +1 -1
  151. package/packages/react-vite-plugin/package.json +1 -1
  152. package/packages/relay/package.json +1 -1
  153. package/packages/release-manager/package.json +1 -1
  154. package/packages/requirement-compiler/package.json +1 -1
  155. package/packages/runtime/package.json +1 -1
  156. package/packages/scheduler/package.json +1 -1
  157. package/packages/scheduler-adapter/package.json +1 -1
  158. package/packages/session/package.json +1 -1
  159. package/packages/session-api/package.json +1 -1
  160. package/packages/session-export/package.json +1 -1
  161. package/packages/shared-context/package.json +1 -1
  162. package/packages/skill-mcp-client/dist/client.d.ts.map +1 -1
  163. package/packages/skill-mcp-client/dist/client.js +0 -1
  164. package/packages/skill-mcp-client/dist/client.js.map +1 -1
  165. package/packages/skill-mcp-client/package.json +1 -4
  166. package/packages/skill-mcp-client/src/client.ts +0 -1
  167. package/packages/skill-registry/package.json +1 -1
  168. package/packages/skills/dist/parser.d.ts.map +1 -1
  169. package/packages/skills/dist/parser.js +2 -0
  170. package/packages/skills/dist/parser.js.map +1 -1
  171. package/packages/skills/dist/skill-versioning.d.ts.map +1 -1
  172. package/packages/skills/dist/skill-versioning.js +2 -0
  173. package/packages/skills/dist/skill-versioning.js.map +1 -1
  174. package/packages/skills/package.json +1 -1
  175. package/packages/skills/src/parser.ts +3 -0
  176. package/packages/skills/src/skill-versioning.ts +2 -0
  177. package/packages/sprint-planner/package.json +1 -1
  178. package/packages/subscription-engine/package.json +1 -1
  179. package/packages/task-compiler/package.json +1 -1
  180. package/packages/tencentdb-memory/package.json +1 -1
  181. package/packages/tencentdb-sync/package.json +1 -1
  182. package/packages/test-data-generator/package.json +1 -1
  183. package/packages/test-generator/package.json +1 -1
  184. package/packages/todo-bd-sync/package.json +1 -1
  185. package/packages/token-estimation/package.json +1 -1
  186. package/packages/token-optimizer/package.json +1 -1
  187. package/packages/tooling-preference/package.json +1 -1
  188. package/packages/trajectory/package.json +1 -1
  189. package/packages/tui/package.json +1 -1
  190. package/packages/two-phase-review/package.json +1 -1
  191. package/packages/types/package.json +1 -1
  192. package/packages/voice-api/package.json +1 -1
  193. package/packages/webmcp-tester/dist/index.js +0 -2
  194. package/packages/webmcp-tester/package.json +1 -1
  195. package/packages/webmcp-tester/src/index.ts +0 -3
  196. package/packages/wiki/package.json +1 -1
  197. package/packages/workflow-events/package.json +1 -1
  198. package/packages/workspace-scanner/package.json +1 -1
  199. package/packages/worktree/package.json +1 -1
  200. package/packages/write-review/dist/trigger.d.ts.map +1 -1
  201. package/packages/write-review/dist/trigger.js +1 -2
  202. package/packages/write-review/dist/trigger.js.map +1 -1
  203. package/packages/write-review/package.json +1 -1
  204. package/packages/write-review/src/trigger.ts +1 -2
  205. package/scripts/lib/runtime-imports.ts +189 -3
  206. package/scripts/release-all.ts +5 -3
  207. package/scripts/telegram-webhook-server.ts +2 -0
  208. package/scripts/typecheck-packages.mjs +7 -4
  209. package/scripts/verify-package-contents.ts +161 -2
  210. package/harness/e2e/tools-detector.test.ts +0 -156
  211. package/harness/glm-quota-countdown.test.ts +0 -323
  212. package/harness/langchain/agents.test.ts +0 -39
  213. package/harness/langchain/checkpointer.test.ts +0 -243
  214. package/harness/langchain/daemon.test.ts +0 -459
  215. package/harness/langchain/model-router.test.ts +0 -191
  216. package/harness/langchain/ping-pong-middleware.test.ts +0 -69
  217. package/harness/langchain/scoreboard.test.ts +0 -245
  218. package/harness/langchain/status-line.test.ts +0 -106
  219. package/harness/langchain/surge.test.ts +0 -467
  220. package/harness/langchain/widget.test.ts +0 -251
  221. package/harness/skill-sync.test.ts +0 -57
  222. package/harness/ssh-detach-interceptor.test.ts +0 -263
  223. package/harness/ssh-hang-recovery.test.ts +0 -86
  224. package/packages/a2a-adapter/test/a2a.test.ts +0 -151
  225. package/packages/agent-policy/test/interlock.test.ts +0 -101
  226. package/packages/agent-policy/test/policy-retention.test.ts +0 -380
  227. package/packages/agent-policy/test/policy.test.ts +0 -387
  228. package/packages/auth/test/auth.test.ts +0 -238
  229. package/packages/autonomous-refactor/test/refactor.test.ts +0 -202
  230. package/packages/autonomous-runtime/test/types.test.ts +0 -95
  231. package/packages/cache-strategy/test/cache.test.ts +0 -137
  232. package/packages/capability-registry/test/registry.test.ts +0 -204
  233. package/packages/checkpoint/test/engine.test.ts +0 -265
  234. package/packages/cli-plugin-sdk/test/cli.test.ts +0 -45
  235. package/packages/code-generation/test/generator.test.ts +0 -238
  236. package/packages/code-review/test/engine.test.ts +0 -302
  237. package/packages/codex-adapter/test/provider.test.ts +0 -142
  238. package/packages/codex-watcher/test/watch.test.ts +0 -453
  239. package/packages/context-compiler/test/context-compiler.test.ts +0 -746
  240. package/packages/context-manager/test/context.test.ts +0 -198
  241. package/packages/contracts/test/delegation.test.ts +0 -350
  242. package/packages/contracts/test/workspace-env.test.ts +0 -802
  243. package/packages/cookie-sanitizer/test/infer.test.ts +0 -110
  244. package/packages/cookie-sanitizer/test/normalize.test.ts +0 -113
  245. package/packages/cookie-sanitizer/test/parse.test.ts +0 -204
  246. package/packages/cookie-sanitizer/test/sync.test.ts +0 -237
  247. package/packages/cookie-sanitizer/test/watcher.test.ts +0 -27
  248. package/packages/cost-optimizer/test/cost-optimizer.test.ts +0 -438
  249. package/packages/dependency-analyzer/test/analyzer.test.ts +0 -49
  250. package/packages/django-plugin/test/analyzer.test.ts +0 -195
  251. package/packages/doc-generator/test/docs.test.ts +0 -168
  252. package/packages/durable-review/test/acceptance.test.ts +0 -357
  253. package/packages/durable-review/test/smoke.test.ts +0 -107
  254. package/packages/evaluation-engine/test/engine.test.ts +0 -277
  255. package/packages/evaluation-runner/test/runner.test.ts +0 -168
  256. package/packages/event-bus/test/bus.test.ts +0 -100
  257. package/packages/event-store/test/store.test.ts +0 -115
  258. package/packages/experience-replay/test/replay.test.ts +0 -267
  259. package/packages/feedback-collector/test/collector.test.ts +0 -142
  260. package/packages/framework-detector/test/detector.test.ts +0 -75
  261. package/packages/framework-plugin-sdk/test/manager.test.ts +0 -320
  262. package/packages/frappe-plugin/test/analyzer.test.ts +0 -250
  263. package/packages/generic-web-plugin/test/analyzer.test.ts +0 -134
  264. package/packages/health-monitor/test/health.test.ts +0 -231
  265. package/packages/intent-analyzer/test/analyzer.test.ts +0 -72
  266. package/packages/intent-analyzer/test/ping-pong.test.ts +0 -96
  267. package/packages/jev-judge/tests/auto-continue-context.test.ts +0 -280
  268. package/packages/jev-judge/tests/continuation-controller.test.ts +0 -1396
  269. package/packages/jev-judge/tests/jev-provider-chain.test.ts +0 -536
  270. package/packages/jev-judge/tests/outcome-application.test.ts +0 -132
  271. package/packages/jev-judge/tests/question-risk.test.ts +0 -129
  272. package/packages/jev-judge/tests/typesafe-provider.test.ts +0 -640
  273. package/packages/jev-judge/tests/validation-ledger.test.ts +0 -181
  274. package/packages/jev-judge/tests/validation-plan.test.ts +0 -170
  275. package/packages/jev-judge/tests/validation-tie-breaker.test.ts +0 -88
  276. package/packages/laravel-plugin/test/analyzer.test.ts +0 -220
  277. package/packages/learning-engine/test/engine.test.ts +0 -331
  278. package/packages/logging/test/buffer.test.ts +0 -44
  279. package/packages/logging/test/classification.test.ts +0 -137
  280. package/packages/logging/test/config.test.ts +0 -97
  281. package/packages/logging/test/fanout.test.ts +0 -186
  282. package/packages/logging/test/identity-transport.test.ts +0 -197
  283. package/packages/logging/test/incidents.test.ts +0 -404
  284. package/packages/logging/test/ingest-schema.test.ts +0 -184
  285. package/packages/logging/test/integration.test.ts +0 -140
  286. package/packages/logging/test/log-http.test.ts +0 -246
  287. package/packages/logging/test/redact.test.ts +0 -109
  288. package/packages/logging/test/sampler.test.ts +0 -57
  289. package/packages/logging/test/server.test.ts +0 -444
  290. package/packages/logging/test/shipper.test.ts +0 -152
  291. package/packages/logging/test/spool.test.ts +0 -96
  292. package/packages/mcp-adapter/test/server.test.ts +0 -91
  293. package/packages/memory-engine/test/engine.test.ts +0 -456
  294. package/packages/model-registry/test/registry.test.ts +0 -342
  295. package/packages/nextjs-plugin/test/analyzer.test.ts +0 -149
  296. package/packages/notification/test/approval-payload.test.ts +0 -269
  297. package/packages/notification/test/detail-format.test.ts +0 -106
  298. package/packages/notification/test/notification.test.ts +0 -375
  299. package/packages/notification/test/pending-question-lifecycle.test.ts +0 -195
  300. package/packages/notification/test/pending-question.test.ts +0 -331
  301. package/packages/notification/test/tooling-proposal-payload.test.ts +0 -212
  302. package/packages/observability/test/observability.test.ts +0 -1350
  303. package/packages/performance-optimizer/test/optimizer.test.ts +0 -267
  304. package/packages/project-analyzer/test/index.test.ts +0 -1719
  305. package/packages/prompt-compiler/test/prompt-compiler.test.ts +0 -813
  306. package/packages/prompt-versioning/test/versioning.test.ts +0 -112
  307. package/packages/provider-adapter-sdk/test/sdk.test.ts +0 -220
  308. package/packages/provider-router/test/provider-router.test.ts +0 -405
  309. package/packages/provider-selector/test/selector.test.ts +0 -192
  310. package/packages/providers/test/providers.test.ts +0 -225
  311. package/packages/qdrant-skills/test/collection-info.test.ts +0 -165
  312. package/packages/qdrant-skills/test/embedder-batch.test.ts +0 -184
  313. package/packages/qdrant-skills/test/embedder.test.ts +0 -97
  314. package/packages/quota-manager/test/quota.test.ts +0 -432
  315. package/packages/rate-limiter/test/limiter.test.ts +0 -128
  316. package/packages/react-vite-plugin/test/analyzer.test.ts +0 -139
  317. package/packages/relay/test/client.test.ts +0 -446
  318. package/packages/relay/test/relay-e2e.test.ts +0 -299
  319. package/packages/relay/test/relay-endpoint.test.ts +0 -278
  320. package/packages/release-manager/test/release.test.ts +0 -215
  321. package/packages/requirement-compiler/test/requirement-compiler.test.ts +0 -743
  322. package/packages/runtime/test/runtime-api.test.ts +0 -169
  323. package/packages/runtime/test/runtime.test.ts +0 -297
  324. package/packages/scheduler/test/scheduler.test.ts +0 -125
  325. package/packages/session/test/manager.test.ts +0 -233
  326. package/packages/shared-context/test/shared-context.test.ts +0 -114
  327. package/packages/skill-registry/test/registry.test.ts +0 -508
  328. package/packages/skills/test/init-skills.test.ts +0 -114
  329. package/packages/skills/test/remote-skills.test.ts +0 -360
  330. package/packages/skills/test/remote-source.test.ts +0 -248
  331. package/packages/skills/test/sanitize.test.ts +0 -250
  332. package/packages/skills/test/vector-indexing.test.ts +0 -205
  333. package/packages/skills/test/vector-schema.test.ts +0 -180
  334. package/packages/skills/test/vector-threshold.test.ts +0 -108
  335. package/packages/sprint-planner/test/planner.test.ts +0 -187
  336. package/packages/task-compiler/test/task-compiler.test.ts +0 -628
  337. package/packages/test-data-generator/test/generator.test.ts +0 -265
  338. package/packages/test-generator/test/generator.test.ts +0 -156
  339. package/packages/token-estimation/test/token-estimation.test.ts +0 -207
  340. package/packages/token-optimizer/test/optimizer.test.ts +0 -141
  341. package/packages/tooling-preference/tests/changed-files.test.ts +0 -124
  342. package/packages/tooling-preference/tests/detect.test.ts +0 -188
  343. package/packages/tooling-preference/tests/proposal.test.ts +0 -107
  344. package/packages/tooling-preference/tests/quality-check.test.ts +0 -272
  345. package/packages/tooling-preference/tests/registry.test.ts +0 -51
  346. package/packages/tooling-preference/tests/store.test.ts +0 -99
  347. package/packages/trajectory/test/store.test.ts +0 -250
  348. package/packages/tui/test/harness-footer.test.ts +0 -559
  349. package/packages/tui/test/harness-header.test.ts +0 -590
  350. package/packages/two-phase-review/test/classify.test.ts +0 -102
  351. package/packages/two-phase-review/test/helpers.ts +0 -72
  352. package/packages/two-phase-review/test/rules.test.ts +0 -150
  353. package/packages/two-phase-review/test/runtime.test.ts +0 -362
  354. package/packages/two-phase-review/test/scope.test.ts +0 -186
  355. package/packages/two-phase-review/test/signals.test.ts +0 -183
  356. package/packages/two-phase-review/test/state.test.ts +0 -201
  357. package/packages/wiki/test/basic.test.ts +0 -242
  358. package/packages/workspace-scanner/test/scanner.test.ts +0 -43
  359. package/packages/worktree/test/worktree.test.ts +0 -163
@@ -1,243 +0,0 @@
1
- /**
2
- * FileCheckpointSaver tests — T9–T11 (wiki/auto-trigger-multi-agent.md §M5)
3
- *
4
- * Run:
5
- * bun test harness/langchain/checkpointer.test.ts
6
- */
7
-
8
- import { describe, it } from "node:test";
9
- import assert from "node:assert";
10
- import { existsSync, mkdirSync, rmSync } from "node:fs";
11
- import { join } from "node:path";
12
- import { randomUUID } from "node:crypto";
13
-
14
- import { FileCheckpointSaver, createLoopCheckpointer } from "./checkpointer.js";
15
-
16
- // ─── Fixtures ────────────────────────────────────────────────────────────────
17
-
18
- const ROOT = `/tmp/checkpointer-test-${randomUUID().slice(0, 8)}`;
19
-
20
- function freshSaver(): FileCheckpointSaver {
21
- const saver = new FileCheckpointSaver({ root: ROOT });
22
- saver.clearAll();
23
- return saver;
24
- }
25
-
26
- const THREAD = "thread-abc123";
27
- const CONFIG = { configurable: { thread_id: THREAD } };
28
- const CONFIG_WITH_CPID = {
29
- configurable: { thread_id: THREAD, checkpoint_id: "cp-001" },
30
- };
31
-
32
- // ─── T9: Basic put/get ──────────────────────────────────────────────────────
33
-
34
- describe("T9 — put and get basic checkpoint", () => {
35
- it("stores and retrieves a checkpoint", async () => {
36
- const saver = freshSaver();
37
-
38
- const checkpoint = {
39
- v: 4,
40
- id: "cp-001",
41
- ts: "2026-08-31T10:00:00.000Z",
42
- channel_values: { plan: "do the thing", code: "" },
43
- channel_versions: { plan: 1, code: 0 },
44
- versions_seen: {},
45
- };
46
- const metadata = {
47
- source: "input" as const,
48
- step: -1,
49
- parents: {},
50
- };
51
-
52
- const updatedConfig = await saver.put(CONFIG, checkpoint, metadata, {});
53
- assert.equal(updatedConfig.configurable?.checkpoint_id, "cp-001");
54
-
55
- // Retrieve it
56
- const tuple = await saver.getTuple(CONFIG_WITH_CPID);
57
- assert.ok(tuple, "should retrieve the checkpoint");
58
- assert.equal(tuple?.checkpoint.id, "cp-001");
59
- assert.equal(
60
- (tuple?.checkpoint.channel_values as Record<string, unknown>)?.plan,
61
- "do the thing",
62
- );
63
- });
64
-
65
- it("get() returns the checkpoint", async () => {
66
- const saver = freshSaver();
67
- const cp = {
68
- v: 4,
69
- id: "cp-get",
70
- ts: "2026-08-31T10:01:00.000Z",
71
- channel_values: { plan: "test plan" },
72
- channel_versions: {},
73
- versions_seen: {},
74
- };
75
- await saver.put(CONFIG, cp, { source: "input", step: -1, parents: {} }, {});
76
-
77
- const retrieved = await saver.get({
78
- configurable: { thread_id: THREAD, checkpoint_id: "cp-get" },
79
- });
80
- assert.ok(retrieved, "should return the checkpoint");
81
- assert.equal(retrieved?.id, "cp-get");
82
- });
83
- });
84
-
85
- // ─── T10: Parent chain ──────────────────────────────────────────────────────
86
-
87
- describe("T10 — parent chain", () => {
88
- it("stores and retrieves a parent chain (resume)", async () => {
89
- const saver = freshSaver();
90
-
91
- const root = {
92
- v: 4,
93
- id: "root",
94
- ts: "2026-08-31T10:00:00.000Z",
95
- channel_values: {},
96
- channel_versions: {},
97
- versions_seen: {},
98
- };
99
- const child = {
100
- v: 4,
101
- id: "child",
102
- ts: "2026-08-31T10:01:00.000Z",
103
- channel_values: { plan: "the plan" },
104
- channel_versions: { plan: 1 },
105
- versions_seen: {},
106
- };
107
-
108
- await saver.put(CONFIG, root, { source: "input", step: -1, parents: {} }, {});
109
- await saver.put(
110
- CONFIG,
111
- child,
112
- { source: "loop", step: 0, parents: { "": "root" } },
113
- {},
114
- );
115
-
116
- // Retrieve child and verify parent
117
- const tuple = await saver.getTuple({
118
- configurable: { thread_id: THREAD, checkpoint_id: "child" },
119
- });
120
- assert.ok(tuple);
121
- assert.ok(tuple?.parentConfig);
122
- assert.equal(tuple?.parentConfig?.configurable?.checkpoint_id, "root");
123
-
124
- // getTuple without checkpoint_id returns the latest
125
- const latest = await saver.getTuple(CONFIG);
126
- assert.ok(latest);
127
- assert.equal(latest?.checkpoint.id, "child");
128
- });
129
- });
130
-
131
- // ─── T11: list + deleteThread ───────────────────────────────────────────────
132
-
133
- describe("T11 — list and deleteThread", () => {
134
- it("lists checkpoints in reverse chronological order", async () => {
135
- const saver = freshSaver();
136
-
137
- for (let i = 0; i < 5; i++) {
138
- const cp = {
139
- v: 4,
140
- id: `cp-${i}`,
141
- ts: new Date(1000 + i * 1000).toISOString(),
142
- channel_values: { step: i },
143
- channel_versions: {},
144
- versions_seen: {},
145
- };
146
- await saver.put(CONFIG, cp, { source: "loop", step: i, parents: {} }, {});
147
- }
148
-
149
- const all: string[] = [];
150
- for await (const tuple of saver.list(CONFIG)) {
151
- all.push(tuple.checkpoint.id);
152
- }
153
- assert.equal(all.length, 5);
154
- // Newest first
155
- assert.equal(all[0], "cp-4");
156
- assert.equal(all[4], "cp-0");
157
- });
158
-
159
- it("limits list results", async () => {
160
- const saver = freshSaver();
161
- for (let i = 0; i < 3; i++) {
162
- const cp = {
163
- v: 4,
164
- id: `limit-${i}`,
165
- ts: new Date(2000 + i * 1000).toISOString(),
166
- channel_values: {},
167
- channel_versions: {},
168
- versions_seen: {},
169
- };
170
- await saver.put(CONFIG, cp, { source: "loop", step: i, parents: {} }, {});
171
- }
172
-
173
- const limited: string[] = [];
174
- for await (const tuple of saver.list(CONFIG, { limit: 2 })) {
175
- limited.push(tuple.checkpoint.id);
176
- }
177
- assert.equal(limited.length, 2);
178
- });
179
-
180
- it("deleteThread removes all checkpoints for a thread", async () => {
181
- const saver = freshSaver();
182
-
183
- const cp = {
184
- v: 4,
185
- id: "delete-me",
186
- ts: "2026-08-31T12:00:00.000Z",
187
- channel_values: {},
188
- channel_versions: {},
189
- versions_seen: {},
190
- };
191
- await saver.put(CONFIG, cp, { source: "input", step: -1, parents: {} }, {});
192
-
193
- assert.ok(await saver.getTuple(CONFIG), "checkpoint should exist");
194
-
195
- await saver.deleteThread(THREAD);
196
-
197
- assert.ok(
198
- !(await saver.getTuple(CONFIG)),
199
- "checkpoint should be gone after deleteThread",
200
- );
201
- });
202
-
203
- it("toJSON returns a string (not the internal object)", async () => {
204
- const saver = freshSaver();
205
- const json = saver.toJSON();
206
- assert.equal(typeof json, "string");
207
- assert.ok(json.length > 0);
208
- });
209
-
210
- it("listThreads returns thread ids", async () => {
211
- const saver = freshSaver();
212
- const tid = `thread-${randomUUID().slice(0, 6)}`;
213
- const cp = {
214
- v: 4,
215
- id: "cp-t",
216
- ts: "2026-08-31T12:00:00.000Z",
217
- channel_values: {},
218
- channel_versions: {},
219
- versions_seen: {},
220
- };
221
- await saver.put(
222
- { configurable: { thread_id: tid } },
223
- cp,
224
- { source: "input", step: -1, parents: {} },
225
- {},
226
- );
227
-
228
- const threads = saver.listThreads();
229
- assert.ok(
230
- threads.includes(tid),
231
- `Expected ${tid} in ${JSON.stringify(threads)}`,
232
- );
233
- });
234
- });
235
-
236
- // ─── T12: createLoopCheckpointer factory ─────────────────────────────────────
237
-
238
- describe("T12 — createLoopCheckpointer factory", () => {
239
- it("creates a saver with the correct root path", () => {
240
- const saver = createLoopCheckpointer("/my/workspace");
241
- assert.ok(saver instanceof FileCheckpointSaver);
242
- });
243
- });
@@ -1,459 +0,0 @@
1
- /**
2
- * Daemon Acceptance Tests (T1–T7)
3
- *
4
- * All tests run with dryRun=true — no API keys needed.
5
- * Tests write inbox files to /tmp/herdr-workspace (the real workspace the daemon watches).
6
- *
7
- * Run:
8
- * bun test harness/langchain/daemon.test.ts
9
- */
10
-
11
- import { before, describe, it } from "node:test";
12
- import assert from "node:assert";
13
- import {
14
- existsSync,
15
- mkdirSync,
16
- readFileSync,
17
- readdirSync,
18
- rmSync,
19
- writeFileSync,
20
- } from "node:fs";
21
- import { join } from "node:path";
22
- import { randomUUID } from "node:crypto";
23
-
24
- import { LoopDaemon, writeAck } from "./daemon.js";
25
- import type { DaemonConfig } from "./daemon.js";
26
- import type { LoopDeps } from "./graph.js";
27
-
28
- // ─── Test fixtures ────────────────────────────────────────────────────────────
29
-
30
- const WORKSPACE = "/tmp/herdr-workspace";
31
-
32
- /** Replicate the daemon's stableTaskId() logic so tests can write matching ack files. */
33
- function daemonTaskId(source: string, raw: string): string {
34
- if (source === "inbox") return raw;
35
- const base = `${source}:${raw}`.slice(0, 64);
36
- let hash = 0;
37
- for (let i = 0; i < base.length; i++) {
38
- const ch = base.charCodeAt(i);
39
- hash = (hash << 5) - hash + ch;
40
- hash |= 0;
41
- }
42
- return `task-${Math.abs(hash).toString(36)}-${Date.now().toString(36).slice(-6)}`;
43
- }
44
-
45
- function testConfig(overrides: Partial<DaemonConfig> = {}): DaemonConfig {
46
- return {
47
- agentId: `test-daemon-${randomUUID().slice(0, 6)}`,
48
- pollMs: 100,
49
- maxIterations: 3,
50
- approvalPolicy: "never",
51
- dryRun: true,
52
- sources: ["inbox", "bus"],
53
- ...overrides,
54
- } as DaemonConfig;
55
- }
56
-
57
- async function waitFor(
58
- fn: () => boolean,
59
- timeoutMs = 5000,
60
- intervalMs = 50,
61
- ): Promise<void> {
62
- const deadline = Date.now() + timeoutMs;
63
- while (Date.now() < deadline) {
64
- if (fn()) return;
65
- await new Promise((r) => setTimeout(r, intervalMs));
66
- }
67
- throw new Error(`waitFor timed out after ${timeoutMs}ms`);
68
- }
69
-
70
- function readEvents(): string[] {
71
- const path = join(WORKSPACE, "events.jsonl");
72
- if (!existsSync(path)) return [];
73
- return readFileSync(path, "utf8")
74
- .split("\n")
75
- .filter((l) => l.trim())
76
- .map((l) => {
77
- try {
78
- return (JSON.parse(l) as { topic?: string }).topic ?? "";
79
- } catch {
80
- return "";
81
- }
82
- })
83
- .filter(Boolean);
84
- }
85
-
86
- async function waitForEvent(topic: string, timeoutMs = 8000): Promise<void> {
87
- await waitFor(() => readEvents().includes(topic), timeoutMs);
88
- }
89
-
90
- function countEvents(topic: string): number {
91
- return readEvents().filter((t) => t === topic).length;
92
- }
93
-
94
- function cleanEvents(): void {
95
- const path = join(WORKSPACE, "events.jsonl");
96
- if (existsSync(path)) rmSync(path, { force: true });
97
- // Also clean ack files so they don't interfere with subsequent tests
98
- for (const file of readdirSync(WORKSPACE)) {
99
- if (file.startsWith("ack-")) {
100
- try {
101
- rmSync(join(WORKSPACE, file), { force: true });
102
- } catch {
103
- /* ignore */
104
- }
105
- }
106
- }
107
- }
108
-
109
- // ─── Setup ──────────────────────────────────────────────────────────────────
110
-
111
- before(() => {
112
- if (!existsSync(WORKSPACE)) mkdirSync(WORKSPACE, { recursive: true });
113
- });
114
-
115
- // ─── T1: Inbox file drop ─────────────────────────────────────────────────────
116
-
117
- describe("T1 — inbox file drop triggers loop", () => {
118
- it("loop.started / code.written / review.completed / loop.finished appear", async () => {
119
- cleanEvents();
120
- const daemon = new LoopDaemon(testConfig({ sources: ["inbox"] }));
121
- daemon.start();
122
- try {
123
- const filename = `test-T1-${randomUUID().slice(0, 6)}.md`;
124
- writeFileSync(
125
- join(WORKSPACE, filename),
126
- "Implement a greeting function",
127
- "utf8",
128
- );
129
- await waitForEvent("loop.finished", 15_000);
130
- const events = readEvents();
131
- assert(
132
- events.includes("loop.started"),
133
- `Missing loop.started: ${JSON.stringify(events)}`,
134
- );
135
- assert(
136
- events.includes("code.written"),
137
- `Missing code.written: ${JSON.stringify(events)}`,
138
- );
139
- assert(
140
- events.includes("review.completed"),
141
- `Missing review.completed: ${JSON.stringify(events)}`,
142
- );
143
- assert(
144
- events.includes("loop.finished"),
145
- `Missing loop.finished: ${JSON.stringify(events)}`,
146
- );
147
- } finally {
148
- daemon.stop();
149
- }
150
- });
151
- });
152
-
153
- // ─── T2: Bus task.proposed event ─────────────────────────────────────────────
154
-
155
- describe("T2 — bus task.proposed event triggers loop", () => {
156
- it("loop.finished appears after publishing task.proposed", async () => {
157
- cleanEvents();
158
- const daemon = new LoopDaemon(testConfig({ sources: ["bus"] }));
159
- daemon.start();
160
- try {
161
- writeFileSync(
162
- join(WORKSPACE, "events.jsonl"),
163
- JSON.stringify({
164
- eventId: randomUUID(),
165
- topic: "task.proposed",
166
- data: { taskId: "T2-task", request: "Add a rate limiter" },
167
- ts: new Date().toISOString(),
168
- }) + "\n",
169
- { flag: "a" },
170
- );
171
- await waitForEvent("loop.finished", 15_000);
172
- assert(
173
- countEvents("loop.finished") >= 1,
174
- "Expected at least one loop.finished",
175
- );
176
- } finally {
177
- daemon.stop();
178
- }
179
- });
180
- });
181
-
182
- // ─── T3: No-human proof ─────────────────────────────────────────────────────
183
-
184
- describe("T3 — no-human proof (approvalPolicy=never)", () => {
185
- it("loop completes without any ack file being created", async () => {
186
- cleanEvents();
187
- const daemon = new LoopDaemon(
188
- testConfig({ sources: ["inbox"], approvalPolicy: "never" }),
189
- );
190
- daemon.start();
191
- try {
192
- const filename = `test-T3-${randomUUID().slice(0, 6)}.md`;
193
- writeFileSync(join(WORKSPACE, filename), "Write a utility", "utf8");
194
- await waitForEvent("loop.finished", 15_000);
195
- const ackFiles = readdirSync(WORKSPACE).filter((f) => f.startsWith("ack-"));
196
- assert.strictEqual(
197
- ackFiles.length,
198
- 0,
199
- `Unexpected ack files: ${ackFiles.join(", ")}`,
200
- );
201
- } finally {
202
- daemon.stop();
203
- }
204
- });
205
- });
206
-
207
- // ─── T4: Approval gate ──────────────────────────────────────────────────────
208
-
209
- describe("T4 — approval gate", () => {
210
- it("parks when approvalPolicy=always and resumes after ack", async () => {
211
- cleanEvents();
212
- const filename = `test-T4a-${randomUUID().slice(0, 6)}.md`;
213
- const daemon = new LoopDaemon(
214
- testConfig({
215
- sources: ["inbox"],
216
- approvalPolicy: "always",
217
- maxIterations: 1,
218
- }),
219
- );
220
- daemon.start();
221
- try {
222
- writeFileSync(join(WORKSPACE, filename), "Test gate always", "utf8");
223
- // Daemon should wait for ack — no loop.started yet
224
- await new Promise((r) => setTimeout(r, 500));
225
- assert.strictEqual(
226
- countEvents("loop.started"),
227
- 0,
228
- "loop.started should not appear before ack",
229
- );
230
-
231
- // Write ack using daemon's computed taskId
232
- const taskId = daemonTaskId("inbox", filename);
233
- writeAck(WORKSPACE, taskId, "approved");
234
- await waitForEvent("loop.finished", 15_000);
235
- } finally {
236
- daemon.stop();
237
- }
238
- });
239
-
240
- it("task is skipped when ack is denied", async () => {
241
- cleanEvents();
242
- const filename = `test-T4b-${randomUUID().slice(0, 6)}.md`;
243
- const daemon = new LoopDaemon(
244
- testConfig({
245
- sources: ["inbox"],
246
- approvalPolicy: "always",
247
- maxIterations: 1,
248
- }),
249
- );
250
- daemon.start();
251
- try {
252
- writeFileSync(join(WORKSPACE, filename), "Test gate deny", "utf8");
253
- await new Promise((r) => setTimeout(r, 500));
254
- const taskId = daemonTaskId("inbox", filename);
255
- writeAck(WORKSPACE, taskId, "denied");
256
- await new Promise((r) => setTimeout(r, 500));
257
- assert.strictEqual(
258
- countEvents("loop.started"),
259
- 0,
260
- "No loop should start after denied ack",
261
- );
262
- } finally {
263
- daemon.stop();
264
- }
265
- });
266
- });
267
-
268
- // ─── T5: Single-writer ──────────────────────────────────────────────────────
269
-
270
- describe("T5 — single-writer (exactly one lease winner)", () => {
271
- it("two daemons: only one processes the task", async () => {
272
- cleanEvents();
273
- const daemon1 = new LoopDaemon(
274
- testConfig({ agentId: "daemon-A", sources: ["inbox"] }),
275
- );
276
- const daemon2 = new LoopDaemon(
277
- testConfig({ agentId: "daemon-B", sources: ["inbox"] }),
278
- );
279
- daemon1.start();
280
- daemon2.start();
281
- try {
282
- const filename = `test-T5-${randomUUID().slice(0, 6)}.md`;
283
- writeFileSync(join(WORKSPACE, filename), "Shared task", "utf8");
284
- await waitForEvent("loop.finished", 15_000);
285
- // Both daemons will see the file. LeaseManager ensures only one wins.
286
- // With two daemons, we expect exactly 1 loop.finished.
287
- // (Both may process it in theory if they race, but the lease prevents this.)
288
- const finishedCount = countEvents("loop.finished");
289
- assert.ok(
290
- finishedCount >= 1,
291
- `Expected at least 1 loop.finished, got ${finishedCount}`,
292
- );
293
- } finally {
294
- daemon1.stop();
295
- daemon2.stop();
296
- }
297
- });
298
- });
299
-
300
- // ─── T6: maxIterations cap ─────────────────────────────────────────────────
301
-
302
- describe("T6 — maxIterations cap", () => {
303
- it("with maxIterations=2, exactly 2 code.written events appear", async () => {
304
- cleanEvents();
305
- // Mock reviewer: always changes_requested with a rotating file to avoid smart-stop.
306
- // Use a mutable ref so review() sees the CURRENT writeCount (after increment).
307
- const counter = { n: 0 };
308
- const mockDeps: LoopDeps = {
309
- maxIterations: 2,
310
- onStep: undefined,
311
- plan: async () => "# Stub plan\n1. Do thing",
312
- write: async () => {
313
- counter.n++;
314
- return `// code pass ${counter.n}`;
315
- },
316
- review: async () => ({
317
- verdict: "changes_requested" as const,
318
- summary: "need changes",
319
- comments: [
320
- { file: `file${counter.n}.ts`, comment: "fix this", severity: "major" },
321
- ],
322
- }),
323
- };
324
- const daemon = new LoopDaemon(
325
- testConfig({ sources: ["inbox"], maxIterations: 2, deps: mockDeps }),
326
- );
327
- daemon.start();
328
- try {
329
- const filename = `test-T6-${randomUUID().slice(0, 6)}.md`;
330
- writeFileSync(join(WORKSPACE, filename), "Test iteration cap", "utf8");
331
- await waitForEvent("loop.finished", 15_000);
332
- // Mock: writeCount=1 → review(changes, file1.ts) → writeCount=2 → review(changes, file2.ts)
333
- // With maxIterations=2, the hard cap kicks in after iteration 2 → loop exits
334
- // Smart-stop: different files (file1 vs file2) → no stuck-file trigger
335
- assert.strictEqual(
336
- counter.n,
337
- 2,
338
- `Expected exactly 2 writes (maxIterations=2, smart-stop overridden by different files)`,
339
- );
340
- } finally {
341
- daemon.stop();
342
- }
343
- });
344
-
345
- it("smart-stop: converged (only minor comments) terminates early", async () => {
346
- cleanEvents();
347
- let writeCount = 0;
348
- // Reviewer returns only minor comments → smart-stop should trigger at iteration 1
349
- const mockDeps: LoopDeps = {
350
- maxIterations: 5,
351
- onStep: undefined,
352
- plan: async () => "# Stub plan",
353
- write: async () => {
354
- writeCount++;
355
- return `// code ${writeCount}`;
356
- },
357
- review: async () => ({
358
- verdict: "changes_requested" as const,
359
- summary: "minor",
360
- comments: [
361
- { file: "a.ts", comment: "nitpick", severity: "minor" },
362
- { file: "b.ts", comment: "typo", severity: "minor" },
363
- ],
364
- }),
365
- };
366
- const daemon = new LoopDaemon(
367
- testConfig({ sources: ["inbox"], maxIterations: 5, deps: mockDeps }),
368
- );
369
- daemon.start();
370
- try {
371
- const filename = `test-T6b-${randomUUID().slice(0, 6)}.md`;
372
- writeFileSync(join(WORKSPACE, filename), "Test smart-stop", "utf8");
373
- await waitForEvent("loop.finished", 15_000);
374
- // Smart-stop: hasOnlyMinor=true (2 minor comments) → stop at iteration 1
375
- assert.strictEqual(writeCount, 1, "converged: should stop after 1 write");
376
- } finally {
377
- daemon.stop();
378
- }
379
- });
380
- });
381
-
382
- // ─── T7: Crash resilience ───────────────────────────────────────────────────
383
-
384
- describe("T7 — crash resilience", () => {
385
- it("daemon restart processes pending tasks", async () => {
386
- cleanEvents();
387
- const daemon1 = new LoopDaemon(testConfig({ sources: ["inbox"] }));
388
- daemon1.start();
389
- try {
390
- const filename = `test-T7-${randomUUID().slice(0, 6)}.md`;
391
- writeFileSync(join(WORKSPACE, filename), "Restart test", "utf8");
392
- await new Promise((r) => setTimeout(r, 500));
393
- } finally {
394
- daemon1.stop();
395
- }
396
-
397
- // Simulate restart — new daemon picks up the task
398
- const daemon2 = new LoopDaemon(testConfig({ sources: ["inbox"] }));
399
- daemon2.start();
400
- try {
401
- await waitForEvent("loop.finished", 15_000);
402
- assert.ok(
403
- countEvents("loop.finished") >= 1,
404
- "Expected loop.finished after restart",
405
- );
406
- } finally {
407
- daemon2.stop();
408
- }
409
- });
410
- });
411
-
412
- // ─── T13: Persistent checkpointer (M5) ──────────────────────────────────────
413
-
414
- describe("T13 — persistent checkpointer (M5)", () => {
415
- it("loop checkpoints survive daemon stop and resume from last checkpoint", async () => {
416
- cleanEvents();
417
- // Use persistent checkpointer (true = use workspace root for checkpoints)
418
- const daemon1 = new LoopDaemon(
419
- testConfig({ sources: ["inbox"], checkpointer: true }),
420
- );
421
- daemon1.start();
422
-
423
- const filename = `test-T13-${randomUUID().slice(0, 6)}.md`;
424
- writeFileSync(
425
- join(WORKSPACE, filename),
426
- "Persistent checkpoint test",
427
- "utf8",
428
- );
429
-
430
- // Let the loop run for one iteration
431
- await new Promise((r) => setTimeout(r, 500));
432
- daemon1.stop();
433
-
434
- // Verify checkpoints exist on disk
435
- const { createLoopCheckpointer } = await import("./checkpointer.js");
436
- const saver = createLoopCheckpointer(WORKSPACE);
437
- const threads = saver.listThreads();
438
- assert.ok(
439
- threads.length > 0,
440
- `Expected checkpoint threads, got: ${JSON.stringify(threads)}`,
441
- );
442
-
443
- // Resume with new daemon — should pick up from checkpoint
444
- const daemon2 = new LoopDaemon(
445
- testConfig({ sources: ["inbox"], checkpointer: true }),
446
- );
447
- daemon2.start();
448
- try {
449
- await waitForEvent("loop.finished", 15_000);
450
- assert.ok(
451
- countEvents("loop.finished") >= 1,
452
- "Expected loop.finished after resume",
453
- );
454
- } finally {
455
- daemon2.stop();
456
- saver.clearAll();
457
- }
458
- });
459
- });