@moltzap/client 2026.529.0 → 2026.718.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/dist/agent-client.d.ts +2 -65
  2. package/dist/agent-client.d.ts.map +1 -1
  3. package/dist/agent-client.js +1 -286
  4. package/dist/agent-client.js.map +1 -1
  5. package/dist/app-client.d.ts +3 -0
  6. package/dist/app-client.d.ts.map +1 -0
  7. package/dist/app-client.js +2 -0
  8. package/dist/app-client.js.map +1 -0
  9. package/dist/auth.d.ts +6 -15
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +12 -16
  12. package/dist/auth.js.map +1 -1
  13. package/dist/channel-base/format-cross-conv.d.ts +10 -15
  14. package/dist/channel-base/format-cross-conv.d.ts.map +1 -1
  15. package/dist/channel-base/format-cross-conv.js +10 -15
  16. package/dist/channel-base/format-cross-conv.js.map +1 -1
  17. package/dist/channel-base/format-group-block.d.ts +8 -10
  18. package/dist/channel-base/format-group-block.d.ts.map +1 -1
  19. package/dist/channel-base/format-group-block.js +8 -10
  20. package/dist/channel-base/format-group-block.js.map +1 -1
  21. package/dist/channel-base/index.d.ts +3 -62
  22. package/dist/channel-base/index.d.ts.map +1 -1
  23. package/dist/channel-base/index.js +2 -65
  24. package/dist/channel-base/index.js.map +1 -1
  25. package/dist/channel-base/lease-guard.d.ts +0 -2
  26. package/dist/channel-base/lease-guard.d.ts.map +1 -1
  27. package/dist/channel-base/lease-guard.js +0 -2
  28. package/dist/channel-base/lease-guard.js.map +1 -1
  29. package/dist/channel-base/lease-store.d.ts +2 -3
  30. package/dist/channel-base/lease-store.d.ts.map +1 -1
  31. package/dist/channel-base/lease-store.js +2 -3
  32. package/dist/channel-base/lease-store.js.map +1 -1
  33. package/dist/channel-base/lease.d.ts +25 -31
  34. package/dist/channel-base/lease.d.ts.map +1 -1
  35. package/dist/channel-base/lease.js +29 -45
  36. package/dist/channel-base/lease.js.map +1 -1
  37. package/dist/channel-core-enrichment.d.ts +1 -1
  38. package/dist/channel-core-enrichment.d.ts.map +1 -1
  39. package/dist/channel-core-test-support.d.ts +17 -11
  40. package/dist/channel-core-test-support.d.ts.map +1 -1
  41. package/dist/channel-core-test-support.js +5 -6
  42. package/dist/channel-core-test-support.js.map +1 -1
  43. package/dist/channel-core.d.ts +24 -29
  44. package/dist/channel-core.d.ts.map +1 -1
  45. package/dist/channel-core.js +18 -42
  46. package/dist/channel-core.js.map +1 -1
  47. package/dist/cli/commands/agents.d.ts +3 -6
  48. package/dist/cli/commands/agents.d.ts.map +1 -1
  49. package/dist/cli/commands/agents.js +14 -50
  50. package/dist/cli/commands/agents.js.map +1 -1
  51. package/dist/cli/commands/contacts.d.ts +5 -6
  52. package/dist/cli/commands/contacts.d.ts.map +1 -1
  53. package/dist/cli/commands/contacts.js +15 -43
  54. package/dist/cli/commands/contacts.js.map +1 -1
  55. package/dist/cli/commands/conversations.d.ts +8 -18
  56. package/dist/cli/commands/conversations.d.ts.map +1 -1
  57. package/dist/cli/commands/conversations.js +14 -65
  58. package/dist/cli/commands/conversations.js.map +1 -1
  59. package/dist/cli/commands/messages.d.ts +13 -35
  60. package/dist/cli/commands/messages.d.ts.map +1 -1
  61. package/dist/cli/commands/messages.js +21 -46
  62. package/dist/cli/commands/messages.js.map +1 -1
  63. package/dist/cli/commands/register.d.ts +10 -12
  64. package/dist/cli/commands/register.d.ts.map +1 -1
  65. package/dist/cli/commands/register.js +47 -133
  66. package/dist/cli/commands/register.js.map +1 -1
  67. package/dist/cli/commands/send.d.ts +23 -36
  68. package/dist/cli/commands/send.d.ts.map +1 -1
  69. package/dist/cli/commands/send.js +25 -72
  70. package/dist/cli/commands/send.js.map +1 -1
  71. package/dist/cli/commands/start.d.ts +10 -124
  72. package/dist/cli/commands/start.d.ts.map +1 -1
  73. package/dist/cli/commands/start.js +28 -510
  74. package/dist/cli/commands/start.js.map +1 -1
  75. package/dist/cli/commands/status.d.ts +1 -1
  76. package/dist/cli/commands/status.d.ts.map +1 -1
  77. package/dist/cli/commands/status.js +8 -9
  78. package/dist/cli/commands/status.js.map +1 -1
  79. package/dist/cli/commands/test-transport.d.ts +12 -5
  80. package/dist/cli/commands/test-transport.d.ts.map +1 -1
  81. package/dist/cli/commands/test-transport.js +15 -15
  82. package/dist/cli/commands/test-transport.js.map +1 -1
  83. package/dist/cli/index.js +13 -40
  84. package/dist/cli/index.js.map +1 -1
  85. package/dist/cli/output.d.ts +4 -0
  86. package/dist/cli/output.d.ts.map +1 -0
  87. package/dist/cli/output.js +9 -0
  88. package/dist/cli/output.js.map +1 -0
  89. package/dist/cli/socket-client.d.ts +7 -14
  90. package/dist/cli/socket-client.d.ts.map +1 -1
  91. package/dist/cli/socket-client.js +15 -38
  92. package/dist/cli/socket-client.js.map +1 -1
  93. package/dist/cli/transport.d.ts +53 -116
  94. package/dist/cli/transport.d.ts.map +1 -1
  95. package/dist/cli/transport.js +45 -295
  96. package/dist/cli/transport.js.map +1 -1
  97. package/dist/config.d.ts +24 -0
  98. package/dist/config.d.ts.map +1 -0
  99. package/dist/config.js +35 -0
  100. package/dist/config.js.map +1 -0
  101. package/dist/config.test-utils.d.ts +13 -0
  102. package/dist/config.test-utils.d.ts.map +1 -0
  103. package/dist/config.test-utils.js +46 -0
  104. package/dist/config.test-utils.js.map +1 -0
  105. package/dist/index.d.ts +3 -8
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -12
  108. package/dist/index.js.map +1 -1
  109. package/dist/local-daemon-rpc.d.ts +259 -8
  110. package/dist/local-daemon-rpc.d.ts.map +1 -1
  111. package/dist/local-daemon-rpc.js +189 -12
  112. package/dist/local-daemon-rpc.js.map +1 -1
  113. package/dist/{runtime/local-history.d.ts → local-history.d.ts} +16 -21
  114. package/dist/local-history.d.ts.map +1 -0
  115. package/dist/{runtime/local-history.js → local-history.js} +16 -21
  116. package/dist/local-history.js.map +1 -0
  117. package/dist/local-paths.d.ts +0 -2
  118. package/dist/local-paths.d.ts.map +1 -1
  119. package/dist/local-paths.js +0 -4
  120. package/dist/local-paths.js.map +1 -1
  121. package/dist/local-socket-server.d.ts +22 -0
  122. package/dist/local-socket-server.d.ts.map +1 -0
  123. package/dist/{runtime/local-socket-server.js → local-socket-server.js} +4 -9
  124. package/dist/local-socket-server.js.map +1 -0
  125. package/dist/message-rendering.d.ts +3 -0
  126. package/dist/message-rendering.d.ts.map +1 -0
  127. package/dist/message-rendering.js +7 -0
  128. package/dist/message-rendering.js.map +1 -0
  129. package/dist/notification/__tests__/{snapshot-semantics.types-check.d.ts → subscribe-signatures.types-check.d.ts} +1 -1
  130. package/dist/notification/__tests__/subscribe-signatures.types-check.d.ts.map +1 -0
  131. package/dist/notification/__tests__/subscribe-signatures.types-check.js +55 -0
  132. package/dist/notification/__tests__/subscribe-signatures.types-check.js.map +1 -0
  133. package/dist/notification/errors.d.ts +18 -6
  134. package/dist/notification/errors.d.ts.map +1 -1
  135. package/dist/notification/errors.js +13 -15
  136. package/dist/notification/errors.js.map +1 -1
  137. package/dist/notification/index.d.ts +5 -0
  138. package/dist/notification/index.d.ts.map +1 -0
  139. package/dist/notification/index.js +5 -0
  140. package/dist/notification/index.js.map +1 -0
  141. package/dist/notification/stream.d.ts +33 -49
  142. package/dist/notification/stream.d.ts.map +1 -1
  143. package/dist/notification/stream.js +33 -76
  144. package/dist/notification/stream.js.map +1 -1
  145. package/dist/pagination.d.ts +20 -10
  146. package/dist/pagination.d.ts.map +1 -1
  147. package/dist/pagination.js +8 -9
  148. package/dist/pagination.js.map +1 -1
  149. package/dist/profile.d.ts +92 -0
  150. package/dist/profile.d.ts.map +1 -0
  151. package/dist/profile.js +149 -0
  152. package/dist/profile.js.map +1 -0
  153. package/dist/refs.d.ts.map +1 -0
  154. package/dist/refs.js.map +1 -0
  155. package/dist/service-archive-purge.d.ts +2 -1
  156. package/dist/service-archive-purge.d.ts.map +1 -1
  157. package/dist/service-archive-purge.js.map +1 -1
  158. package/dist/{runtime/service-helpers.d.ts → service-helpers.d.ts} +5 -11
  159. package/dist/service-helpers.d.ts.map +1 -0
  160. package/dist/{runtime/service-helpers.js → service-helpers.js} +2 -9
  161. package/dist/service-helpers.js.map +1 -0
  162. package/dist/service-local-daemon.d.ts +22 -0
  163. package/dist/service-local-daemon.d.ts.map +1 -0
  164. package/dist/service-local-daemon.js +185 -0
  165. package/dist/service-local-daemon.js.map +1 -0
  166. package/dist/service.d.ts +65 -57
  167. package/dist/service.d.ts.map +1 -1
  168. package/dist/service.js +205 -156
  169. package/dist/service.js.map +1 -1
  170. package/dist/test-utils/channel-service-fixture.d.ts +3 -2
  171. package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
  172. package/dist/test-utils/channel-service-fixture.js.map +1 -1
  173. package/dist/test-utils/fake-service.d.ts +26 -18
  174. package/dist/test-utils/fake-service.d.ts.map +1 -1
  175. package/dist/test-utils/fake-service.js +28 -42
  176. package/dist/test-utils/fake-service.js.map +1 -1
  177. package/dist/test-utils/ids.d.ts +3 -1
  178. package/dist/test-utils/ids.d.ts.map +1 -1
  179. package/dist/test-utils/ids.js.map +1 -1
  180. package/dist/test-utils/index.d.ts +9 -2
  181. package/dist/test-utils/index.d.ts.map +1 -1
  182. package/dist/test-utils/index.js +8 -1
  183. package/dist/test-utils/index.js.map +1 -1
  184. package/dist/test-utils/standalone-provisioning.d.ts +17 -0
  185. package/dist/test-utils/standalone-provisioning.d.ts.map +1 -0
  186. package/dist/test-utils/standalone-provisioning.js +14 -0
  187. package/dist/test-utils/standalone-provisioning.js.map +1 -0
  188. package/dist/tsconfig.tsbuildinfo +1 -0
  189. package/package.json +49 -27
  190. package/dist/auth.test.d.ts +0 -2
  191. package/dist/auth.test.d.ts.map +0 -1
  192. package/dist/auth.test.js +0 -121
  193. package/dist/auth.test.js.map +0 -1
  194. package/dist/channel-core-context.test.d.ts +0 -2
  195. package/dist/channel-core-context.test.d.ts.map +0 -1
  196. package/dist/channel-core-context.test.js +0 -342
  197. package/dist/channel-core-context.test.js.map +0 -1
  198. package/dist/channel-core-dispatch.test.d.ts +0 -2
  199. package/dist/channel-core-dispatch.test.d.ts.map +0 -1
  200. package/dist/channel-core-dispatch.test.js +0 -557
  201. package/dist/channel-core-dispatch.test.js.map +0 -1
  202. package/dist/channel-core.test.d.ts +0 -2
  203. package/dist/channel-core.test.d.ts.map +0 -1
  204. package/dist/channel-core.test.js +0 -232
  205. package/dist/channel-core.test.js.map +0 -1
  206. package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts +0 -2
  207. package/dist/cli/__tests__/cli-multi-agent.int.test.d.ts.map +0 -1
  208. package/dist/cli/__tests__/cli-multi-agent.int.test.js +0 -132
  209. package/dist/cli/__tests__/cli-multi-agent.int.test.js.map +0 -1
  210. package/dist/cli/commands/invite.d.ts +0 -7
  211. package/dist/cli/commands/invite.d.ts.map +0 -1
  212. package/dist/cli/commands/invite.js +0 -11
  213. package/dist/cli/commands/invite.js.map +0 -1
  214. package/dist/cli/commands/messages.test.d.ts +0 -2
  215. package/dist/cli/commands/messages.test.d.ts.map +0 -1
  216. package/dist/cli/commands/messages.test.js +0 -99
  217. package/dist/cli/commands/messages.test.js.map +0 -1
  218. package/dist/cli/commands/ping.d.ts +0 -7
  219. package/dist/cli/commands/ping.d.ts.map +0 -1
  220. package/dist/cli/commands/ping.js +0 -37
  221. package/dist/cli/commands/ping.js.map +0 -1
  222. package/dist/cli/commands/register.test.d.ts +0 -2
  223. package/dist/cli/commands/register.test.d.ts.map +0 -1
  224. package/dist/cli/commands/register.test.js +0 -91
  225. package/dist/cli/commands/register.test.js.map +0 -1
  226. package/dist/cli/commands/send.test.d.ts +0 -2
  227. package/dist/cli/commands/send.test.d.ts.map +0 -1
  228. package/dist/cli/commands/send.test.js +0 -59
  229. package/dist/cli/commands/send.test.js.map +0 -1
  230. package/dist/cli/commands/start.test.d.ts +0 -2
  231. package/dist/cli/commands/start.test.d.ts.map +0 -1
  232. package/dist/cli/commands/start.test.js +0 -624
  233. package/dist/cli/commands/start.test.js.map +0 -1
  234. package/dist/cli/commands/whoami.d.ts +0 -8
  235. package/dist/cli/commands/whoami.d.ts.map +0 -1
  236. package/dist/cli/commands/whoami.js +0 -28
  237. package/dist/cli/commands/whoami.js.map +0 -1
  238. package/dist/cli/config.d.ts +0 -66
  239. package/dist/cli/config.d.ts.map +0 -1
  240. package/dist/cli/config.js +0 -148
  241. package/dist/cli/config.js.map +0 -1
  242. package/dist/cli/config.test.d.ts +0 -2
  243. package/dist/cli/config.test.d.ts.map +0 -1
  244. package/dist/cli/config.test.js +0 -89
  245. package/dist/cli/config.test.js.map +0 -1
  246. package/dist/cli/http-client.d.ts +0 -17
  247. package/dist/cli/http-client.d.ts.map +0 -1
  248. package/dist/cli/http-client.js +0 -38
  249. package/dist/cli/http-client.js.map +0 -1
  250. package/dist/cli/profile.d.ts +0 -126
  251. package/dist/cli/profile.d.ts.map +0 -1
  252. package/dist/cli/profile.js +0 -231
  253. package/dist/cli/profile.js.map +0 -1
  254. package/dist/cli/profile.test.d.ts +0 -2
  255. package/dist/cli/profile.test.d.ts.map +0 -1
  256. package/dist/cli/profile.test.js +0 -369
  257. package/dist/cli/profile.test.js.map +0 -1
  258. package/dist/cli/transport.test.d.ts +0 -2
  259. package/dist/cli/transport.test.d.ts.map +0 -1
  260. package/dist/cli/transport.test.js +0 -312
  261. package/dist/cli/transport.test.js.map +0 -1
  262. package/dist/notification/__tests__/filter-equivalence.test.d.ts +0 -2
  263. package/dist/notification/__tests__/filter-equivalence.test.d.ts.map +0 -1
  264. package/dist/notification/__tests__/filter-equivalence.test.js +0 -123
  265. package/dist/notification/__tests__/filter-equivalence.test.js.map +0 -1
  266. package/dist/notification/__tests__/snapshot-semantics.test.d.ts +0 -2
  267. package/dist/notification/__tests__/snapshot-semantics.test.d.ts.map +0 -1
  268. package/dist/notification/__tests__/snapshot-semantics.test.js +0 -261
  269. package/dist/notification/__tests__/snapshot-semantics.test.js.map +0 -1
  270. package/dist/notification/__tests__/snapshot-semantics.types-check.d.ts.map +0 -1
  271. package/dist/notification/__tests__/snapshot-semantics.types-check.js +0 -62
  272. package/dist/notification/__tests__/snapshot-semantics.types-check.js.map +0 -1
  273. package/dist/pagination.test.d.ts +0 -2
  274. package/dist/pagination.test.d.ts.map +0 -1
  275. package/dist/pagination.test.js +0 -72
  276. package/dist/pagination.test.js.map +0 -1
  277. package/dist/runtime/close-info.d.ts +0 -84
  278. package/dist/runtime/close-info.d.ts.map +0 -1
  279. package/dist/runtime/close-info.js +0 -102
  280. package/dist/runtime/close-info.js.map +0 -1
  281. package/dist/runtime/close-info.test.d.ts +0 -2
  282. package/dist/runtime/close-info.test.d.ts.map +0 -1
  283. package/dist/runtime/close-info.test.js +0 -109
  284. package/dist/runtime/close-info.test.js.map +0 -1
  285. package/dist/runtime/errors.d.ts +0 -11
  286. package/dist/runtime/errors.d.ts.map +0 -1
  287. package/dist/runtime/errors.js +0 -41
  288. package/dist/runtime/errors.js.map +0 -1
  289. package/dist/runtime/errors.test.d.ts +0 -2
  290. package/dist/runtime/errors.test.d.ts.map +0 -1
  291. package/dist/runtime/errors.test.js +0 -79
  292. package/dist/runtime/errors.test.js.map +0 -1
  293. package/dist/runtime/frame.d.ts +0 -8
  294. package/dist/runtime/frame.d.ts.map +0 -1
  295. package/dist/runtime/frame.js +0 -12
  296. package/dist/runtime/frame.js.map +0 -1
  297. package/dist/runtime/frame.test.d.ts +0 -2
  298. package/dist/runtime/frame.test.d.ts.map +0 -1
  299. package/dist/runtime/frame.test.js +0 -59
  300. package/dist/runtime/frame.test.js.map +0 -1
  301. package/dist/runtime/index.d.ts +0 -10
  302. package/dist/runtime/index.d.ts.map +0 -1
  303. package/dist/runtime/index.js +0 -9
  304. package/dist/runtime/index.js.map +0 -1
  305. package/dist/runtime/local-history.d.ts.map +0 -1
  306. package/dist/runtime/local-history.js.map +0 -1
  307. package/dist/runtime/local-service-commands.d.ts +0 -26
  308. package/dist/runtime/local-service-commands.d.ts.map +0 -1
  309. package/dist/runtime/local-service-commands.js +0 -29
  310. package/dist/runtime/local-service-commands.js.map +0 -1
  311. package/dist/runtime/local-socket-server.d.ts +0 -20
  312. package/dist/runtime/local-socket-server.d.ts.map +0 -1
  313. package/dist/runtime/local-socket-server.js.map +0 -1
  314. package/dist/runtime/refs.d.ts.map +0 -1
  315. package/dist/runtime/refs.js.map +0 -1
  316. package/dist/runtime/runtime.test.d.ts +0 -2
  317. package/dist/runtime/runtime.test.d.ts.map +0 -1
  318. package/dist/runtime/runtime.test.js +0 -37
  319. package/dist/runtime/runtime.test.js.map +0 -1
  320. package/dist/runtime/service-helpers.d.ts.map +0 -1
  321. package/dist/runtime/service-helpers.js.map +0 -1
  322. package/dist/runtime/service-teardown.d.ts +0 -5
  323. package/dist/runtime/service-teardown.d.ts.map +0 -1
  324. package/dist/runtime/service-teardown.js +0 -36
  325. package/dist/runtime/service-teardown.js.map +0 -1
  326. package/dist/runtime/service-teardown.test.d.ts +0 -2
  327. package/dist/runtime/service-teardown.test.d.ts.map +0 -1
  328. package/dist/runtime/service-teardown.test.js +0 -80
  329. package/dist/runtime/service-teardown.test.js.map +0 -1
  330. package/dist/runtime/subscribers.d.ts +0 -152
  331. package/dist/runtime/subscribers.d.ts.map +0 -1
  332. package/dist/runtime/subscribers.js +0 -222
  333. package/dist/runtime/subscribers.js.map +0 -1
  334. package/dist/service-socket-path.test.d.ts +0 -2
  335. package/dist/service-socket-path.test.d.ts.map +0 -1
  336. package/dist/service-socket-path.test.js +0 -87
  337. package/dist/service-socket-path.test.js.map +0 -1
  338. package/dist/service.test.d.ts +0 -2
  339. package/dist/service.test.d.ts.map +0 -1
  340. package/dist/service.test.js +0 -796
  341. package/dist/service.test.js.map +0 -1
  342. package/dist/test/index.d.ts +0 -47
  343. package/dist/test/index.d.ts.map +0 -1
  344. package/dist/test/index.js +0 -39
  345. package/dist/test/index.js.map +0 -1
  346. package/dist/test-utils/conformance-adapter.d.ts +0 -40
  347. package/dist/test-utils/conformance-adapter.d.ts.map +0 -1
  348. package/dist/test-utils/conformance-adapter.js +0 -250
  349. package/dist/test-utils/conformance-adapter.js.map +0 -1
  350. package/dist/tm-client-test-support.d.ts +0 -184
  351. package/dist/tm-client-test-support.d.ts.map +0 -1
  352. package/dist/tm-client-test-support.js +0 -466
  353. package/dist/tm-client-test-support.js.map +0 -1
  354. package/dist/tm-client.d.ts +0 -269
  355. package/dist/tm-client.d.ts.map +0 -1
  356. package/dist/tm-client.js +0 -518
  357. package/dist/tm-client.js.map +0 -1
  358. package/dist/tm-client.test.d.ts +0 -2
  359. package/dist/tm-client.test.d.ts.map +0 -1
  360. package/dist/tm-client.test.js +0 -490
  361. package/dist/tm-client.test.js.map +0 -1
  362. /package/dist/{runtime/refs.d.ts → refs.d.ts} +0 -0
  363. /package/dist/{runtime/refs.js → refs.js} +0 -0
@@ -1,624 +0,0 @@
1
- /**
2
- * Unit tests for `moltzap start` (spec D2 #599 / sub-issue #661).
3
- *
4
- * Coverage map matches per-flow doc 09 §7. Each test body is a named
5
- * helper to keep describe-blocks under the 50-line per-function limit
6
- * (matches the pattern in `transport.test.ts`).
7
- */
8
- import { Effect } from "effect";
9
- import * as fc from "fast-check";
10
- import { it as effectIt } from "@effect/vitest";
11
- import { afterEach, beforeEach, describe, expect, it as plainIt, vi, } from "vitest";
12
- import { AgentsLookupByName, DEFAULT_APP_ID, MessagesSend, TaskConversationList, TaskRequest, } from "@moltzap/protocol";
13
- import { Transport } from "../transport.js";
14
- import { makeFakeTransport } from "./test-transport.js";
15
- import { runStartHandler, startCommand } from "./start.js";
16
- const it = effectIt.effect;
17
- // ─── Contractual literals (also asserted in `help` test) ──────────────────
18
- const HELP_SYNOPSIS = "Start a task with named participants";
19
- const HELP_MENTIONS_MESSAGES_SEND = "MessagesSend";
20
- const HELP_APP_ID_FLAG = "--app-id";
21
- const HELP_EXIT_CODES_HEADING = "Exit codes:";
22
- // ─── Fixtures ─────────────────────────────────────────────────────────────
23
- const CONVERSATION_NAME = "demo";
24
- const TASK_ID = "00000000-0000-4000-8000-000000000001";
25
- const CONVERSATION_ID = "00000000-0000-4000-8000-00000000000c";
26
- const MESSAGE_ID = "00000000-0000-4000-8000-00000000000a";
27
- const INITIATOR_ID = "00000000-0000-4000-8000-0000000000f0";
28
- const BOB_AGENT_ID = "00000000-0000-4000-8000-000000000ab1";
29
- const CAROL_AGENT_ID = "00000000-0000-4000-8000-000000000ab2";
30
- const DAVE_AGENT_ID_UUID = "00000000-0000-4000-8000-000000000ab3";
31
- const NOW_ISO = "2026-05-19T00:00:00Z";
32
- const VALID_APP_ID_OVERRIDE = "11111111-2222-4333-8444-555555555555";
33
- const UUID_V1_OVERRIDE = "11111111-2222-1333-8444-555555555555";
34
- const taskFixture = {
35
- id: TASK_ID,
36
- appId: DEFAULT_APP_ID,
37
- initiatorAgentId: INITIATOR_ID,
38
- status: "active",
39
- startedAt: NOW_ISO,
40
- endedAt: null,
41
- createdAt: NOW_ISO,
42
- };
43
- const conversationFixture = {
44
- id: CONVERSATION_ID,
45
- name: CONVERSATION_NAME,
46
- createdBy: INITIATOR_ID,
47
- createdAt: NOW_ISO,
48
- updatedAt: NOW_ISO,
49
- };
50
- const messageFixture = {
51
- id: MESSAGE_ID,
52
- conversationId: CONVERSATION_ID,
53
- senderId: INITIATOR_ID,
54
- parts: [{ type: "text", text: "hello" }],
55
- createdAt: NOW_ISO,
56
- };
57
- const agentCard = (id, name) => ({
58
- id,
59
- name,
60
- status: "active",
61
- });
62
- const TASK_CREATE_OK = () => ({
63
- task: taskFixture,
64
- conversation: conversationFixture,
65
- });
66
- const MESSAGES_SEND_OK = () => ({ message: messageFixture });
67
- const respondLookup = (call, config) => {
68
- // Batched lookup (P3-2): coalesce all requested-name agent cards from
69
- // the mock's `lookupResults` map. Names with no entry return nothing
70
- // — the handler maps that to `UnresolvedParticipantError` per token.
71
- const params = call.params;
72
- const lookupResults = config.lookupResults ?? {};
73
- const agents = params.names.flatMap((name) => lookupResults[name] ?? []);
74
- return { agents };
75
- };
76
- const respondFromConfig = (config) => (call) => {
77
- if (call.method === AgentsLookupByName.name) {
78
- return respondLookup(call, config);
79
- }
80
- if (call.method === TaskRequest.name) {
81
- return (config.taskCreate ?? TASK_CREATE_OK)();
82
- }
83
- if (call.method === MessagesSend.name) {
84
- return (config.messagesSend ?? MESSAGES_SEND_OK)();
85
- }
86
- if (call.method === TaskConversationList.name) {
87
- return (config.taskConversationList ?? (() => ({ items: [] })))(call);
88
- }
89
- return new Error(`Unexpected RPC: ${call.method}`);
90
- };
91
- const makeTransportWith = (config) => makeFakeTransport(respondFromConfig(config));
92
- const runWith = (transport, args) => runStartHandler(args).pipe(Effect.provideService(Transport, transport));
93
- const findCallOf = (calls, method) => calls.find((c) => c.method === method);
94
- const taskCreateParams = (calls) => findCallOf(calls, TaskRequest.name).params;
95
- const mutatingCalls = (calls) => calls.filter((c) => c.method === TaskRequest.name || c.method === MessagesSend.name);
96
- const standardArgs = (participants, overrides = {}) => ({
97
- name: CONVERSATION_NAME,
98
- participants,
99
- message: undefined,
100
- appId: undefined,
101
- ...overrides,
102
- });
103
- const onlyBobLookup = () => ({
104
- lookupResults: { bob: [agentCard(BOB_AGENT_ID, "bob")] },
105
- });
106
- const bobCarolLookup = () => ({
107
- lookupResults: {
108
- bob: [agentCard(BOB_AGENT_ID, "bob")],
109
- carol: [agentCard(CAROL_AGENT_ID, "carol")],
110
- },
111
- });
112
- // ─── Shared harness (console + process.exit) ──────────────────────────────
113
- let stdoutSpy;
114
- let stderrSpy;
115
- let exitSpy;
116
- const originalExit = process.exit;
117
- const installHarness = () => {
118
- stdoutSpy = vi.spyOn(console, "log").mockImplementation(() => { });
119
- stderrSpy = vi.spyOn(console, "error").mockImplementation(() => { });
120
- exitSpy = vi.fn();
121
- process.exit = exitSpy;
122
- };
123
- const restoreHarness = () => {
124
- process.exit = originalExit;
125
- stdoutSpy.mockRestore();
126
- stderrSpy.mockRestore();
127
- };
128
- /**
129
- * Single-source the install/restore lifecycle for one `body` execution.
130
- * Used by property-test iterators (`expectNonV4Rejected`,
131
- * `expectNonAgentTokenRejected`) so each `fc.assert` iteration gets a
132
- * fresh, isolated harness without inline `installHarness()` / explicit
133
- * `restoreHarness()` calls that drift out of sync over time. `finally`
134
- * guarantees restore even on `expect` throw.
135
- */
136
- const withInstalledHarness = (body) => {
137
- installHarness();
138
- try {
139
- body();
140
- }
141
- finally {
142
- restoreHarness();
143
- }
144
- };
145
- const stderrLines = () => stderrSpy.mock.calls.map((c) => String(c[0] ?? ""));
146
- // ─── Test bodies: participant model ───────────────────────────────────────
147
- const dmShape = () => Effect.gen(function* () {
148
- const { calls, transport } = makeTransportWith(onlyBobLookup());
149
- yield* runWith(transport, standardArgs(["agent:bob"]));
150
- const params = taskCreateParams(calls);
151
- expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID]);
152
- expect(params.invitedAgentIds.length).toBe(1);
153
- expect(params.initialConversation.participants).toEqual([BOB_AGENT_ID]);
154
- expect(params.initialConversation.name).toBe(CONVERSATION_NAME);
155
- });
156
- const groupShape = () => Effect.gen(function* () {
157
- const { calls, transport } = makeTransportWith(bobCarolLookup());
158
- yield* runWith(transport, standardArgs(["agent:bob", "agent:carol"]));
159
- const params = taskCreateParams(calls);
160
- expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID, CAROL_AGENT_ID]);
161
- // Pins the P3-2 batched-lookup optimization: N name-shaped tokens
162
- // coalesce into ONE `AgentsLookupByName` RPC, not N. Drift here
163
- // re-introduces a fan-out per token (visible cost on group tasks).
164
- const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
165
- expect(lookups).toHaveLength(1);
166
- expect(lookups[0].params).toEqual({ names: ["bob", "carol"] });
167
- });
168
- const duplicateNameDedupesLookup = () => Effect.gen(function* () {
169
- // P3-2 secondary invariant: duplicate name tokens dedupe in the
170
- // batched lookup payload (one wire-name per unique value) but
171
- // resolve to the agent id at every input position. UUID-shaped
172
- // siblings still skip the wire.
173
- const { calls, transport } = makeTransportWith(onlyBobLookup());
174
- yield* runWith(transport, standardArgs(["agent:bob", `agent:${DAVE_AGENT_ID_UUID}`, "agent:bob"]));
175
- const params = taskCreateParams(calls);
176
- expect(params.invitedAgentIds).toEqual([
177
- BOB_AGENT_ID,
178
- DAVE_AGENT_ID_UUID,
179
- BOB_AGENT_ID,
180
- ]);
181
- const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
182
- expect(lookups).toHaveLength(1);
183
- expect(lookups[0].params).toEqual({ names: ["bob"] });
184
- });
185
- const callerExcluded = () => Effect.gen(function* () {
186
- const { calls, transport } = makeTransportWith(onlyBobLookup());
187
- yield* runWith(transport, standardArgs(["agent:bob"]));
188
- const params = taskCreateParams(calls);
189
- expect(params.invitedAgentIds).not.toContain(INITIATOR_ID);
190
- expect(params.invitedAgentIds).toEqual([BOB_AGENT_ID]);
191
- });
192
- const uuidShorthand = () => Effect.gen(function* () {
193
- const { calls, transport } = makeTransportWith({});
194
- yield* runWith(transport, standardArgs([`agent:${DAVE_AGENT_ID_UUID}`]));
195
- const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
196
- expect(lookups).toEqual([]);
197
- const params = taskCreateParams(calls);
198
- expect(params.invitedAgentIds).toEqual([DAVE_AGENT_ID_UUID]);
199
- });
200
- const zeroParticipants = () => Effect.gen(function* () {
201
- const { calls, transport } = makeTransportWith({});
202
- yield* runWith(transport, standardArgs([]));
203
- const params = taskCreateParams(calls);
204
- expect(params.invitedAgentIds).toEqual([]);
205
- // P2-B: zero-participant path MUST omit `participants` from
206
- // `initialConversation` so the wire payload satisfies
207
- // `InitialConversationSchema` (participants is Optional with
208
- // `minItems: 1`). The exact shape pin guards against drift.
209
- expect(params.initialConversation).toEqual({ name: CONVERSATION_NAME });
210
- expect("participants" in params.initialConversation).toBe(false);
211
- const lookups = calls.filter((c) => c.method === AgentsLookupByName.name);
212
- expect(lookups).toEqual([]);
213
- });
214
- describe("moltzap start — participant model", () => {
215
- beforeEach(installHarness);
216
- afterEach(restoreHarness);
217
- it("dm-shape", dmShape);
218
- it("group-shape, order preserved", groupShape);
219
- it("caller-excluded", callerExcluded);
220
- it("uuid-shorthand", uuidShorthand);
221
- it("zero-participants admitted (plan §R4)", zeroParticipants);
222
- it("duplicate name tokens dedupe in batched lookup (P3-2)", duplicateNameDedupesLookup);
223
- });
224
- // ─── Test bodies: output format ───────────────────────────────────────────
225
- const outputFormat = () => Effect.gen(function* () {
226
- const { transport } = makeTransportWith(onlyBobLookup());
227
- yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
228
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${TASK_ID} (conversation: ${CONVERSATION_ID})`);
229
- expect(stdoutSpy).toHaveBeenCalledWith(`Message sent: ${MESSAGE_ID}`);
230
- });
231
- describe("moltzap start — output format", () => {
232
- beforeEach(installHarness);
233
- afterEach(restoreHarness);
234
- it("prints exact stdout strings with response IDs", outputFormat);
235
- });
236
- // ─── Test bodies: --app-id ────────────────────────────────────────────────
237
- const defaultAppId = () => Effect.gen(function* () {
238
- const { calls, transport } = makeTransportWith(onlyBobLookup());
239
- yield* runWith(transport, standardArgs(["agent:bob"]));
240
- expect(taskCreateParams(calls).appId).toBe(DEFAULT_APP_ID);
241
- });
242
- const validAppIdOverride = () => Effect.gen(function* () {
243
- const { calls, transport } = makeTransportWith(onlyBobLookup());
244
- yield* runWith(transport, standardArgs(["agent:bob"], { appId: VALID_APP_ID_OVERRIDE }));
245
- expect(taskCreateParams(calls).appId).toBe(VALID_APP_ID_OVERRIDE);
246
- });
247
- const invalidAppIdGarbage = () => Effect.gen(function* () {
248
- const { calls, transport } = makeTransportWith({});
249
- yield* runWith(transport, standardArgs([], { appId: "not-a-uuid" }));
250
- expect(exitSpy).toHaveBeenCalledWith(64);
251
- expect(calls).toEqual([]);
252
- expect(stderrSpy).toHaveBeenCalledWith("Invalid --app-id: not a UUID");
253
- });
254
- const invalidAppIdUuidV1 = () => Effect.gen(function* () {
255
- const { calls, transport } = makeTransportWith({});
256
- yield* runWith(transport, standardArgs([], { appId: UUID_V1_OVERRIDE }));
257
- expect(exitSpy).toHaveBeenCalledWith(64);
258
- expect(calls).toEqual([]);
259
- });
260
- const nonV4HexDigit = fc
261
- .integer({ min: 0, max: 15 })
262
- .filter((n) => n !== 4)
263
- .map((n) => n.toString(16));
264
- /**
265
- * Property body: builds a syntactically-valid UUID with a non-4
266
- * version digit and asserts the handler exits 64 with zero RPC calls.
267
- * Runs synchronously via `Effect.runSync` because the test transport
268
- * never suspends.
269
- */
270
- const expectNonV4Rejected = (digit) => withInstalledHarness(() => {
271
- const uuid = `11111111-2222-${digit}333-8444-555555555555`;
272
- const { calls, transport } = makeTransportWith({});
273
- Effect.runSync(runWith(transport, standardArgs([], { appId: uuid })));
274
- expect(exitSpy).toHaveBeenCalledWith(64);
275
- expect(calls).toEqual([]);
276
- });
277
- const invariantNonV4Rejection = () => {
278
- fc.assert(fc.property(nonV4HexDigit, expectNonV4Rejected), { numRuns: 30 });
279
- // Re-install for afterEach symmetry; each property iteration tore
280
- // down its harness via `withInstalledHarness`, leaving none for the
281
- // outer afterEach to restore.
282
- installHarness();
283
- };
284
- describe("moltzap start — --app-id", () => {
285
- beforeEach(installHarness);
286
- afterEach(restoreHarness);
287
- it("default sends DEFAULT_APP_ID", defaultAppId);
288
- it("override sends the provided v4", validAppIdOverride);
289
- it("garbage UUID -> exit 64, ZERO RPC calls", invalidAppIdGarbage);
290
- it("UUID v1 -> exit 64, ZERO RPC calls", invalidAppIdUuidV1);
291
- plainIt("invariant: every non-v4 syntax exits 64 with ZERO RPC calls", invariantNonV4Rejection);
292
- });
293
- // ─── Test bodies: failure paths ───────────────────────────────────────────
294
- const serverRejectsTaskCreate = () => Effect.gen(function* () {
295
- const { transport } = makeTransportWith({
296
- ...onlyBobLookup(),
297
- taskCreate: () => new Error("app not installed"),
298
- });
299
- yield* runWith(transport, standardArgs(["agent:bob"], { appId: VALID_APP_ID_OVERRIDE }));
300
- expect(exitSpy).toHaveBeenCalledWith(1);
301
- expect(stderrLines().some((s) => s.startsWith("Failed: "))).toBe(true);
302
- expect(stdoutSpy).not.toHaveBeenCalled();
303
- });
304
- const partialSuccess = () => Effect.gen(function* () {
305
- const { transport } = makeTransportWith({
306
- ...onlyBobLookup(),
307
- messagesSend: () => new Error("conv full"),
308
- });
309
- yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
310
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${TASK_ID} (conversation: ${CONVERSATION_ID})`);
311
- expect(stderrLines().some((s) => s.startsWith("Error sending message: "))).toBe(true);
312
- expect(exitSpy).toHaveBeenCalledWith(2);
313
- });
314
- const unresolvedLookupEmpty = () => Effect.gen(function* () {
315
- const { calls, transport } = makeTransportWith({ lookupResults: {} });
316
- yield* runWith(transport, standardArgs(["agent:unknown"]));
317
- expect(exitSpy).toHaveBeenCalledWith(64);
318
- expect(mutatingCalls(calls)).toEqual([]);
319
- expect(stderrLines().some((s) => s.includes("agent:unknown"))).toBe(true);
320
- });
321
- const unresolvedShape = () => Effect.gen(function* () {
322
- const { calls, transport } = makeTransportWith({});
323
- yield* runWith(transport, standardArgs(["bob"]));
324
- expect(exitSpy).toHaveBeenCalledWith(64);
325
- expect(calls).toEqual([]);
326
- });
327
- const tooManyDistinctNames = () => Effect.gen(function* () {
328
- // P3-2 carve-out: `AgentsLookupByName.params.names` is capped at
329
- // `maxItems: 100`. With 101 DISTINCT name tokens, the batched RPC
330
- // would otherwise fail server-side AJV → opaque exit 1. The
331
- // pre-RPC cap check surfaces it as exit 64 with a clear message.
332
- const tokens = Array.from({ length: 101 }, (_, i) => `agent:user${i.toString().padStart(3, "0")}`);
333
- const { calls, transport } = makeTransportWith({});
334
- yield* runWith(transport, standardArgs(tokens));
335
- expect(exitSpy).toHaveBeenCalledWith(64);
336
- // Cap fires BEFORE the RPC — zero wire calls.
337
- expect(calls).toEqual([]);
338
- // Structural assertion: diagnostic mentions BOTH the offending
339
- // count and the schema cap so the user can reduce input directly.
340
- expect(stderrLines().some((s) => s.includes("Too many") && s.includes("101") && s.includes("100"))).toBe(true);
341
- });
342
- /**
343
- * Property body: any token without the `agent:` prefix fails at the
344
- * shape check before any RPC. The handler must always exit 64 with
345
- * zero RPC calls — the resolver short-circuits at the prefix test.
346
- */
347
- const expectNonAgentTokenRejected = (token) => withInstalledHarness(() => {
348
- const { calls, transport } = makeTransportWith({});
349
- Effect.runSync(runWith(transport, standardArgs([token])));
350
- expect(exitSpy).toHaveBeenCalledWith(64);
351
- expect(calls).toEqual([]);
352
- });
353
- const nonAgentToken = fc
354
- .string({ minLength: 1, maxLength: 30 })
355
- .filter((s) => !s.startsWith("agent:"));
356
- const invariantNonAgentRejection = () => {
357
- fc.assert(fc.property(nonAgentToken, expectNonAgentTokenRejected), {
358
- numRuns: 30,
359
- });
360
- installHarness();
361
- };
362
- describe("moltzap start — failure paths", () => {
363
- beforeEach(installHarness);
364
- afterEach(restoreHarness);
365
- it("server-reject TaskRequest -> exit 1", serverRejectsTaskCreate);
366
- it("partial-success TaskRequest OK + MessagesSend fail -> exit 2", partialSuccess);
367
- it("unresolved (lookup empty) -> exit 64, ZERO mutating RPCs", unresolvedLookupEmpty);
368
- it("unresolved (shape) -> exit 64, ZERO RPC calls", unresolvedShape);
369
- it(">100 distinct names -> exit 64 with TooManyParticipantNamesError diagnostic", tooManyDistinctNames);
370
- plainIt("invariant: non-agent: prefix tokens exit 64 with ZERO RPC calls", invariantNonAgentRejection);
371
- });
372
- // ─── Test bodies: dedup hit (P2-A, spec D2 amendment N6) ─────────────────
373
- const EXISTING_TASK_ID = "00000000-0000-4000-8000-0000000000d1";
374
- const EXISTING_CONV_ID_ACTIVE = "00000000-0000-4000-8000-0000000000d2";
375
- const EXISTING_CONV_ID_OLDER = "00000000-0000-4000-8000-0000000000d3";
376
- const OTHER_TASK_ID = "00000000-0000-4000-8000-0000000000e9";
377
- const existingTaskFixture = {
378
- ...taskFixture,
379
- id: EXISTING_TASK_ID,
380
- };
381
- const existingTaskClosedFixture = {
382
- ...existingTaskFixture,
383
- status: "closed",
384
- endedAt: NOW_ISO,
385
- };
386
- const conversationFixtureActive = {
387
- ...conversationFixture,
388
- id: EXISTING_CONV_ID_ACTIVE,
389
- createdAt: "2026-05-19T01:00:00Z",
390
- };
391
- const conversationFixtureOlder = {
392
- ...conversationFixture,
393
- id: EXISTING_CONV_ID_OLDER,
394
- createdAt: "2026-05-18T00:00:00Z",
395
- };
396
- const TASK_CREATE_DEDUP = (task = existingTaskFixture) => () => ({
397
- task,
398
- conversation: null,
399
- });
400
- const taskConvListItem = (taskId, conversation) => ({
401
- taskId,
402
- conversation,
403
- participants: [INITIATOR_ID, BOB_AGENT_ID],
404
- });
405
- const dedupHitSingleConversation = () => Effect.gen(function* () {
406
- const { calls, transport } = makeTransportWith({
407
- ...onlyBobLookup(),
408
- taskCreate: TASK_CREATE_DEDUP(),
409
- taskConversationList: () => ({
410
- items: [taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive)],
411
- }),
412
- });
413
- yield* runWith(transport, standardArgs(["agent:bob"]));
414
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
415
- expect(exitSpy).not.toHaveBeenCalled();
416
- // Must have called TaskConversationList exactly once (single page,
417
- // single match). Cursor MUST NOT be passed on first page.
418
- const listCall = findCallOf(calls, TaskConversationList.name);
419
- expect(listCall).toBeDefined();
420
- expect(listCall.params).toEqual({ limit: 100 });
421
- });
422
- const dedupHitMultipleConversations = () => Effect.gen(function* () {
423
- // Server-side ordering is activity-desc, so the FIRST item under
424
- // the target task is the most-recently-active. The handler picks
425
- // it without re-sorting (per spec D2 amendment N6 tie-break: "first
426
- // match in server iteration order").
427
- const { transport } = makeTransportWith({
428
- ...onlyBobLookup(),
429
- taskCreate: TASK_CREATE_DEDUP(),
430
- taskConversationList: () => ({
431
- items: [
432
- // Server order: active first, older second.
433
- taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
434
- taskConvListItem(EXISTING_TASK_ID, conversationFixtureOlder),
435
- ],
436
- }),
437
- });
438
- yield* runWith(transport, standardArgs(["agent:bob"]));
439
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
440
- expect(exitSpy).not.toHaveBeenCalled();
441
- });
442
- const dedupHitWithMessage = () => Effect.gen(function* () {
443
- const { calls, transport } = makeTransportWith({
444
- ...onlyBobLookup(),
445
- taskCreate: TASK_CREATE_DEDUP(),
446
- taskConversationList: () => ({
447
- items: [taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive)],
448
- }),
449
- });
450
- yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
451
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
452
- expect(stdoutSpy).toHaveBeenCalledWith(`Message sent: ${MESSAGE_ID}`);
453
- // The MessagesSend payload MUST route to the REUSED conversation,
454
- // not the (null) freshly-created one.
455
- const sendCall = findCallOf(calls, MessagesSend.name);
456
- expect(sendCall).toBeDefined();
457
- expect(sendCall.params.conversationId).toBe(EXISTING_CONV_ID_ACTIVE);
458
- });
459
- const dedupHitFiltersOtherTaskAndArchived = () => Effect.gen(function* () {
460
- // The list mixes items from other tasks AND archived rows under
461
- // our task — the handler must skip both and find the lone non-
462
- // archived match.
463
- const archivedConv = {
464
- ...conversationFixtureOlder,
465
- archivedAt: NOW_ISO,
466
- };
467
- const { transport } = makeTransportWith({
468
- ...onlyBobLookup(),
469
- taskCreate: TASK_CREATE_DEDUP(),
470
- taskConversationList: () => ({
471
- items: [
472
- taskConvListItem(OTHER_TASK_ID, conversationFixtureActive),
473
- taskConvListItem(EXISTING_TASK_ID, archivedConv),
474
- taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
475
- ],
476
- }),
477
- });
478
- yield* runWith(transport, standardArgs(["agent:bob"]));
479
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
480
- });
481
- const dedupHitTaskClosedNoUsableConversation = () => Effect.gen(function* () {
482
- // Existing task is reported by the dedup query but all
483
- // conversations under it are archived → `findReusableConversation`
484
- // returns null → handler emits closed-task diagnostic + exit 1.
485
- const archivedConv = {
486
- ...conversationFixtureActive,
487
- archivedAt: NOW_ISO,
488
- };
489
- const { transport } = makeTransportWith({
490
- ...onlyBobLookup(),
491
- taskCreate: TASK_CREATE_DEDUP(existingTaskClosedFixture),
492
- taskConversationList: () => ({
493
- items: [taskConvListItem(EXISTING_TASK_ID, archivedConv)],
494
- }),
495
- });
496
- yield* runWith(transport, standardArgs(["agent:bob"]));
497
- expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
498
- expect(stdoutSpy).not.toHaveBeenCalled();
499
- expect(exitSpy).toHaveBeenCalledWith(1);
500
- });
501
- const dedupHitPaginatesUntilFound = () => Effect.gen(function* () {
502
- // First page is full of OTHER tasks → handler must follow
503
- // `nextCursor` to find the target on page two. Pin both calls so a
504
- // regression that drops pagination support fails the test.
505
- let callCount = 0;
506
- const { calls, transport } = makeTransportWith({
507
- ...onlyBobLookup(),
508
- taskCreate: TASK_CREATE_DEDUP(),
509
- taskConversationList: () => {
510
- callCount += 1;
511
- if (callCount === 1) {
512
- return {
513
- items: [taskConvListItem(OTHER_TASK_ID, conversationFixtureOlder)],
514
- nextCursor: "2026-05-18T12:00:00Z",
515
- };
516
- }
517
- return {
518
- items: [
519
- taskConvListItem(EXISTING_TASK_ID, conversationFixtureActive),
520
- ],
521
- };
522
- },
523
- });
524
- yield* runWith(transport, standardArgs(["agent:bob"]));
525
- expect(callCount).toBe(2);
526
- expect(stdoutSpy).toHaveBeenCalledWith(`Task started: ${EXISTING_TASK_ID} (reusing existing conversation: ${EXISTING_CONV_ID_ACTIVE})`);
527
- // Second call MUST forward the cursor returned by the first page.
528
- const listCalls = calls.filter((c) => c.method === TaskConversationList.name);
529
- expect(listCalls).toHaveLength(2);
530
- expect(listCalls[1].params).toEqual({
531
- limit: 100,
532
- cursor: "2026-05-18T12:00:00Z",
533
- });
534
- });
535
- const dedupHitCapsAtMaxPages = () => Effect.gen(function* () {
536
- // Adversarial server that NEVER returns nextCursor: undefined. The
537
- // handler MUST stop at `DEDUP_LOOKUP_MAX_PAGES = 10` and surface
538
- // the closed-task diagnostic; without the cap this loops forever
539
- // and the test times out. Pins the cap value as a contract.
540
- let pageCount = 0;
541
- const { calls, transport } = makeTransportWith({
542
- ...onlyBobLookup(),
543
- taskCreate: TASK_CREATE_DEDUP(),
544
- taskConversationList: () => {
545
- pageCount += 1;
546
- return {
547
- items: [taskConvListItem(OTHER_TASK_ID, conversationFixtureOlder)],
548
- nextCursor: `cursor-page-${pageCount}`,
549
- };
550
- },
551
- });
552
- yield* runWith(transport, standardArgs(["agent:bob"]));
553
- const listCalls = calls.filter((c) => c.method === TaskConversationList.name);
554
- expect(listCalls).toHaveLength(10); // DEDUP_LOOKUP_MAX_PAGES
555
- expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
556
- expect(stdoutSpy).not.toHaveBeenCalled();
557
- expect(exitSpy).toHaveBeenCalledWith(1);
558
- });
559
- const dedupHitClosedTaskWithMessage = () => Effect.gen(function* () {
560
- // Partial-failure: TaskRequest dedup-hit + closed-task + --message.
561
- // The user's message is intentionally DROPPED — handler exits 1
562
- // before reaching `sendFirstMessage`. Pinned so any future change
563
- // to add a separate diagnostic ('Message NOT sent: ...') has to
564
- // update this test, preventing silent regression.
565
- const archivedConv = {
566
- ...conversationFixtureActive,
567
- archivedAt: NOW_ISO,
568
- };
569
- const { calls, transport } = makeTransportWith({
570
- ...onlyBobLookup(),
571
- taskCreate: TASK_CREATE_DEDUP(existingTaskClosedFixture),
572
- taskConversationList: () => ({
573
- items: [taskConvListItem(EXISTING_TASK_ID, archivedConv)],
574
- }),
575
- });
576
- yield* runWith(transport, standardArgs(["agent:bob"], { message: "hello" }));
577
- expect(calls.filter((c) => c.method === MessagesSend.name)).toEqual([]);
578
- expect(stderrLines()).toContain(`Task already exists but is closed: ${EXISTING_TASK_ID}`);
579
- expect(stdoutSpy).not.toHaveBeenCalled();
580
- expect(exitSpy).toHaveBeenCalledWith(1);
581
- });
582
- const dedupHitListRpcFailure = () => Effect.gen(function* () {
583
- // TaskRequest succeeds (dedup hit). TaskConversationList fails with
584
- // a wire error (e.g. cursor-format drift). Without the
585
- // `DedupListFailedError` remap, the user would see
586
- // `Failed: <list-error>` and assume TaskRequest failed → retry →
587
- // re-dedup forever. The remap surfaces the existing taskId so the
588
- // user knows the task is real.
589
- const { transport } = makeTransportWith({
590
- ...onlyBobLookup(),
591
- taskCreate: TASK_CREATE_DEDUP(),
592
- taskConversationList: () => new Error("cursor server-rejected"),
593
- });
594
- yield* runWith(transport, standardArgs(["agent:bob"]));
595
- expect(stderrLines().some((s) => s.startsWith(`Task ${EXISTING_TASK_ID} already exists`) &&
596
- s.includes("reusable-conversation lookup failed"))).toBe(true);
597
- expect(stdoutSpy).not.toHaveBeenCalled();
598
- expect(exitSpy).toHaveBeenCalledWith(1);
599
- });
600
- describe("moltzap start — dedup hit (P2-A / spec D2 amendment N6)", () => {
601
- beforeEach(installHarness);
602
- afterEach(restoreHarness);
603
- it("single existing conversation -> reuses it", dedupHitSingleConversation);
604
- it("multiple conversations -> picks first in server iteration order", dedupHitMultipleConversations);
605
- it("with --message -> sends to reused conversation", dedupHitWithMessage);
606
- it("filters items from other tasks and archived rows", dedupHitFiltersOtherTaskAndArchived);
607
- it("task closed / all archived -> exit 1 with stderr diagnostic", dedupHitTaskClosedNoUsableConversation);
608
- it("paginates via nextCursor when first page misses", dedupHitPaginatesUntilFound);
609
- it("caps at DEDUP_LOOKUP_MAX_PAGES when server never returns final page", dedupHitCapsAtMaxPages);
610
- it("closed task + --message -> message dropped, exit 1", dedupHitClosedTaskWithMessage);
611
- it("TaskConversationList wire failure -> distinct diagnostic, NOT 'Failed: ...'", dedupHitListRpcFailure);
612
- });
613
- // ─── Tests: help text ─────────────────────────────────────────────────────
614
- const helpDescription = () => {
615
- const desc = JSON.stringify(startCommand);
616
- expect(desc).toContain(HELP_SYNOPSIS);
617
- expect(desc).toContain(HELP_MENTIONS_MESSAGES_SEND);
618
- expect(desc).toContain(HELP_APP_ID_FLAG);
619
- expect(desc).toContain(HELP_EXIT_CODES_HEADING);
620
- };
621
- describe("moltzap start — help", () => {
622
- plainIt("Command.withDescription contains synopsis + --app-id + exit-code table", helpDescription);
623
- });
624
- //# sourceMappingURL=start.test.js.map