@harrylabsj/kiwi 0.6.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 (636) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +150 -0
  3. package/contracts/candidate-agent-dto-1.0.README.md +39 -0
  4. package/contracts/candidate-agent-dto-1.0.schema.json +172 -0
  5. package/contracts/interop/kiwi-buyer-message-send.json +43 -0
  6. package/contracts/interop/knp-data-part-examples.json +243 -0
  7. package/contracts/kiwi-catalog/1.0/agent-record.schema.json +78 -0
  8. package/contracts/kiwi-catalog/1.0/listing-record.schema.json +88 -0
  9. package/contracts/kiwi-catalog/1.0/listing-search-result.schema.json +60 -0
  10. package/contracts/negotiation/1.0/schema.json +501 -0
  11. package/contracts/shopping.negotiation/0.1/capabilities.schema.json +47 -0
  12. package/contracts/shopping.negotiation/0.1/decision.schema.json +120 -0
  13. package/contracts/shopping.negotiation/0.1/policy-result.schema.json +52 -0
  14. package/contracts/shopping.negotiation/0.1/snapshot.schema.json +172 -0
  15. package/dist/a2a/client/client.d.ts +33 -0
  16. package/dist/a2a/client/client.js +161 -0
  17. package/dist/a2a/client/client.js.map +1 -0
  18. package/dist/a2a/client/error.d.ts +35 -0
  19. package/dist/a2a/client/error.js +34 -0
  20. package/dist/a2a/client/error.js.map +1 -0
  21. package/dist/a2a/client/index.d.ts +29 -0
  22. package/dist/a2a/client/index.js +26 -0
  23. package/dist/a2a/client/index.js.map +1 -0
  24. package/dist/a2a/client/jsonrpc.d.ts +49 -0
  25. package/dist/a2a/client/jsonrpc.js +85 -0
  26. package/dist/a2a/client/jsonrpc.js.map +1 -0
  27. package/dist/a2a/client/parse.d.ts +18 -0
  28. package/dist/a2a/client/parse.js +134 -0
  29. package/dist/a2a/client/parse.js.map +1 -0
  30. package/dist/a2a/client/types.d.ts +102 -0
  31. package/dist/a2a/client/types.js +26 -0
  32. package/dist/a2a/client/types.js.map +1 -0
  33. package/dist/a2a/client/url-policy.d.ts +44 -0
  34. package/dist/a2a/client/url-policy.js +242 -0
  35. package/dist/a2a/client/url-policy.js.map +1 -0
  36. package/dist/a2a/negotiate.d.ts +68 -0
  37. package/dist/a2a/negotiate.js +288 -0
  38. package/dist/a2a/negotiate.js.map +1 -0
  39. package/dist/a2a/node.d.ts +37 -0
  40. package/dist/a2a/node.js +155 -0
  41. package/dist/a2a/node.js.map +1 -0
  42. package/dist/a2a/server/auth.d.ts +38 -0
  43. package/dist/a2a/server/auth.js +112 -0
  44. package/dist/a2a/server/auth.js.map +1 -0
  45. package/dist/a2a/server/card.d.ts +19 -0
  46. package/dist/a2a/server/card.js +81 -0
  47. package/dist/a2a/server/card.js.map +1 -0
  48. package/dist/a2a/server/errors.d.ts +109 -0
  49. package/dist/a2a/server/errors.js +178 -0
  50. package/dist/a2a/server/errors.js.map +1 -0
  51. package/dist/a2a/server/handler.d.ts +22 -0
  52. package/dist/a2a/server/handler.js +57 -0
  53. package/dist/a2a/server/handler.js.map +1 -0
  54. package/dist/a2a/server/inbound-message.d.ts +37 -0
  55. package/dist/a2a/server/inbound-message.js +104 -0
  56. package/dist/a2a/server/inbound-message.js.map +1 -0
  57. package/dist/a2a/server/index.d.ts +43 -0
  58. package/dist/a2a/server/index.js +42 -0
  59. package/dist/a2a/server/index.js.map +1 -0
  60. package/dist/a2a/server/merchant-handler.d.ts +69 -0
  61. package/dist/a2a/server/merchant-handler.js +316 -0
  62. package/dist/a2a/server/merchant-handler.js.map +1 -0
  63. package/dist/a2a/server/pipeline.d.ts +69 -0
  64. package/dist/a2a/server/pipeline.js +329 -0
  65. package/dist/a2a/server/pipeline.js.map +1 -0
  66. package/dist/a2a/server/server.d.ts +64 -0
  67. package/dist/a2a/server/server.js +420 -0
  68. package/dist/a2a/server/server.js.map +1 -0
  69. package/dist/a2a/server/task-registry.d.ts +31 -0
  70. package/dist/a2a/server/task-registry.js +67 -0
  71. package/dist/a2a/server/task-registry.js.map +1 -0
  72. package/dist/a2a/server/throttle.d.ts +154 -0
  73. package/dist/a2a/server/throttle.js +289 -0
  74. package/dist/a2a/server/throttle.js.map +1 -0
  75. package/dist/a2a/server/types.d.ts +191 -0
  76. package/dist/a2a/server/types.js +17 -0
  77. package/dist/a2a/server/types.js.map +1 -0
  78. package/dist/a2a/server/ucp.d.ts +49 -0
  79. package/dist/a2a/server/ucp.js +85 -0
  80. package/dist/a2a/server/ucp.js.map +1 -0
  81. package/dist/a2a/task/index.d.ts +24 -0
  82. package/dist/a2a/task/index.js +22 -0
  83. package/dist/a2a/task/index.js.map +1 -0
  84. package/dist/a2a/task/ledger.d.ts +48 -0
  85. package/dist/a2a/task/ledger.js +65 -0
  86. package/dist/a2a/task/ledger.js.map +1 -0
  87. package/dist/a2a/task/poller.d.ts +93 -0
  88. package/dist/a2a/task/poller.js +184 -0
  89. package/dist/a2a/task/poller.js.map +1 -0
  90. package/dist/a2a/task/state.d.ts +72 -0
  91. package/dist/a2a/task/state.js +119 -0
  92. package/dist/a2a/task/state.js.map +1 -0
  93. package/dist/a2a/ucp-agent.d.ts +49 -0
  94. package/dist/a2a/ucp-agent.js +78 -0
  95. package/dist/a2a/ucp-agent.js.map +1 -0
  96. package/dist/agent/agent-db.d.ts +31 -0
  97. package/dist/agent/agent-db.js +91 -0
  98. package/dist/agent/agent-db.js.map +1 -0
  99. package/dist/agent/buyer/buyer-tools.d.ts +70 -0
  100. package/dist/agent/buyer/buyer-tools.js +1353 -0
  101. package/dist/agent/buyer/buyer-tools.js.map +1 -0
  102. package/dist/agent/buyer/ranker.d.ts +53 -0
  103. package/dist/agent/buyer/ranker.js +153 -0
  104. package/dist/agent/buyer/ranker.js.map +1 -0
  105. package/dist/agent/buyer/scheduler.d.ts +58 -0
  106. package/dist/agent/buyer/scheduler.js +317 -0
  107. package/dist/agent/buyer/scheduler.js.map +1 -0
  108. package/dist/agent/buyer/search-loop.d.ts +42 -0
  109. package/dist/agent/buyer/search-loop.js +280 -0
  110. package/dist/agent/buyer/search-loop.js.map +1 -0
  111. package/dist/agent/buyer/task-store.d.ts +178 -0
  112. package/dist/agent/buyer/task-store.js +637 -0
  113. package/dist/agent/buyer/task-store.js.map +1 -0
  114. package/dist/agent/buyer/types.d.ts +222 -0
  115. package/dist/agent/buyer/types.js +124 -0
  116. package/dist/agent/buyer/types.js.map +1 -0
  117. package/dist/agent/chat-tools.d.ts +41 -0
  118. package/dist/agent/chat-tools.js +193 -0
  119. package/dist/agent/chat-tools.js.map +1 -0
  120. package/dist/agent/chat-tui.d.ts +49 -0
  121. package/dist/agent/chat-tui.js +314 -0
  122. package/dist/agent/chat-tui.js.map +1 -0
  123. package/dist/agent/connector/fake-connector.d.ts +50 -0
  124. package/dist/agent/connector/fake-connector.js +138 -0
  125. package/dist/agent/connector/fake-connector.js.map +1 -0
  126. package/dist/agent/connector/http-connector.d.ts +49 -0
  127. package/dist/agent/connector/http-connector.js +208 -0
  128. package/dist/agent/connector/http-connector.js.map +1 -0
  129. package/dist/agent/connector/types.d.ts +103 -0
  130. package/dist/agent/connector/types.js +97 -0
  131. package/dist/agent/connector/types.js.map +1 -0
  132. package/dist/agent/fake-chat-model.d.ts +22 -0
  133. package/dist/agent/fake-chat-model.js +134 -0
  134. package/dist/agent/fake-chat-model.js.map +1 -0
  135. package/dist/agent/kernel.d.ts +258 -0
  136. package/dist/agent/kernel.js +1099 -0
  137. package/dist/agent/kernel.js.map +1 -0
  138. package/dist/agent/memory/schema.d.ts +32 -0
  139. package/dist/agent/memory/schema.js +291 -0
  140. package/dist/agent/memory/schema.js.map +1 -0
  141. package/dist/agent/memory/store.d.ts +193 -0
  142. package/dist/agent/memory/store.js +881 -0
  143. package/dist/agent/memory/store.js.map +1 -0
  144. package/dist/agent/memory/types.d.ts +139 -0
  145. package/dist/agent/memory/types.js +119 -0
  146. package/dist/agent/memory/types.js.map +1 -0
  147. package/dist/agent/memory/vault.d.ts +49 -0
  148. package/dist/agent/memory/vault.js +125 -0
  149. package/dist/agent/memory/vault.js.map +1 -0
  150. package/dist/agent/merchant/action-candidate.d.ts +106 -0
  151. package/dist/agent/merchant/action-candidate.js +241 -0
  152. package/dist/agent/merchant/action-candidate.js.map +1 -0
  153. package/dist/agent/merchant/credential-broker.d.ts +67 -0
  154. package/dist/agent/merchant/credential-broker.js +76 -0
  155. package/dist/agent/merchant/credential-broker.js.map +1 -0
  156. package/dist/agent/merchant/fake-merchant-client.d.ts +47 -0
  157. package/dist/agent/merchant/fake-merchant-client.js +143 -0
  158. package/dist/agent/merchant/fake-merchant-client.js.map +1 -0
  159. package/dist/agent/merchant/merchant-client.d.ts +49 -0
  160. package/dist/agent/merchant/merchant-client.js +186 -0
  161. package/dist/agent/merchant/merchant-client.js.map +1 -0
  162. package/dist/agent/merchant/merchant-tools.d.ts +74 -0
  163. package/dist/agent/merchant/merchant-tools.js +582 -0
  164. package/dist/agent/merchant/merchant-tools.js.map +1 -0
  165. package/dist/agent/merchant/types.d.ts +119 -0
  166. package/dist/agent/merchant/types.js +133 -0
  167. package/dist/agent/merchant/types.js.map +1 -0
  168. package/dist/agent/mode.d.ts +32 -0
  169. package/dist/agent/mode.js +34 -0
  170. package/dist/agent/mode.js.map +1 -0
  171. package/dist/agent/negotiation-chat.d.ts +81 -0
  172. package/dist/agent/negotiation-chat.js +330 -0
  173. package/dist/agent/negotiation-chat.js.map +1 -0
  174. package/dist/agent/session.d.ts +22 -0
  175. package/dist/agent/session.js +164 -0
  176. package/dist/agent/session.js.map +1 -0
  177. package/dist/agent/system-prompt.d.ts +26 -0
  178. package/dist/agent/system-prompt.js +77 -0
  179. package/dist/agent/system-prompt.js.map +1 -0
  180. package/dist/agent/write-gate.d.ts +98 -0
  181. package/dist/agent/write-gate.js +76 -0
  182. package/dist/agent/write-gate.js.map +1 -0
  183. package/dist/cli.d.ts +18 -0
  184. package/dist/cli.js +1139 -0
  185. package/dist/cli.js.map +1 -0
  186. package/dist/commerce/data-source.d.ts +88 -0
  187. package/dist/commerce/data-source.js +25 -0
  188. package/dist/commerce/data-source.js.map +1 -0
  189. package/dist/commerce/erp-source.d.ts +51 -0
  190. package/dist/commerce/erp-source.js +184 -0
  191. package/dist/commerce/erp-source.js.map +1 -0
  192. package/dist/commerce/fake-client.d.ts +167 -0
  193. package/dist/commerce/fake-client.js +549 -0
  194. package/dist/commerce/fake-client.js.map +1 -0
  195. package/dist/commerce/http-client.d.ts +73 -0
  196. package/dist/commerce/http-client.js +284 -0
  197. package/dist/commerce/http-client.js.map +1 -0
  198. package/dist/commerce/local-db-source.d.ts +50 -0
  199. package/dist/commerce/local-db-source.js +162 -0
  200. package/dist/commerce/local-db-source.js.map +1 -0
  201. package/dist/commerce/shopping-cli-source.d.ts +57 -0
  202. package/dist/commerce/shopping-cli-source.js +206 -0
  203. package/dist/commerce/shopping-cli-source.js.map +1 -0
  204. package/dist/commerce/types.d.ts +146 -0
  205. package/dist/commerce/types.js +31 -0
  206. package/dist/commerce/types.js.map +1 -0
  207. package/dist/config/profile.d.ts +122 -0
  208. package/dist/config/profile.js +386 -0
  209. package/dist/config/profile.js.map +1 -0
  210. package/dist/contracts/negotiation-schema.d.ts +28 -0
  211. package/dist/contracts/negotiation-schema.js +101 -0
  212. package/dist/contracts/negotiation-schema.js.map +1 -0
  213. package/dist/contracts/schemas.d.ts +31 -0
  214. package/dist/contracts/schemas.js +122 -0
  215. package/dist/contracts/schemas.js.map +1 -0
  216. package/dist/counterparty/a2a-direct/index.d.ts +41 -0
  217. package/dist/counterparty/a2a-direct/index.js +330 -0
  218. package/dist/counterparty/a2a-direct/index.js.map +1 -0
  219. package/dist/counterparty/channel.d.ts +200 -0
  220. package/dist/counterparty/channel.js +61 -0
  221. package/dist/counterparty/channel.js.map +1 -0
  222. package/dist/counterparty/index.d.ts +28 -0
  223. package/dist/counterparty/index.js +24 -0
  224. package/dist/counterparty/index.js.map +1 -0
  225. package/dist/counterparty/platform-api/index.d.ts +41 -0
  226. package/dist/counterparty/platform-api/index.js +65 -0
  227. package/dist/counterparty/platform-api/index.js.map +1 -0
  228. package/dist/counterparty/registry.d.ts +51 -0
  229. package/dist/counterparty/registry.js +71 -0
  230. package/dist/counterparty/registry.js.map +1 -0
  231. package/dist/counterparty/shopping-cli-hosted/index.d.ts +36 -0
  232. package/dist/counterparty/shopping-cli-hosted/index.js +337 -0
  233. package/dist/counterparty/shopping-cli-hosted/index.js.map +1 -0
  234. package/dist/discovery/agent-card/error.d.ts +28 -0
  235. package/dist/discovery/agent-card/error.js +31 -0
  236. package/dist/discovery/agent-card/error.js.map +1 -0
  237. package/dist/discovery/agent-card/extensions.d.ts +20 -0
  238. package/dist/discovery/agent-card/extensions.js +59 -0
  239. package/dist/discovery/agent-card/extensions.js.map +1 -0
  240. package/dist/discovery/agent-card/index.d.ts +29 -0
  241. package/dist/discovery/agent-card/index.js +38 -0
  242. package/dist/discovery/agent-card/index.js.map +1 -0
  243. package/dist/discovery/agent-card/secrets.d.ts +30 -0
  244. package/dist/discovery/agent-card/secrets.js +240 -0
  245. package/dist/discovery/agent-card/secrets.js.map +1 -0
  246. package/dist/discovery/agent-card/types.d.ts +110 -0
  247. package/dist/discovery/agent-card/types.js +37 -0
  248. package/dist/discovery/agent-card/types.js.map +1 -0
  249. package/dist/discovery/agent-card/validate.d.ts +31 -0
  250. package/dist/discovery/agent-card/validate.js +210 -0
  251. package/dist/discovery/agent-card/validate.js.map +1 -0
  252. package/dist/discovery/capability/error.d.ts +23 -0
  253. package/dist/discovery/capability/error.js +28 -0
  254. package/dist/discovery/capability/error.js.map +1 -0
  255. package/dist/discovery/capability/index.d.ts +21 -0
  256. package/dist/discovery/capability/index.js +21 -0
  257. package/dist/discovery/capability/index.js.map +1 -0
  258. package/dist/discovery/capability/intersect.d.ts +56 -0
  259. package/dist/discovery/capability/intersect.js +137 -0
  260. package/dist/discovery/capability/intersect.js.map +1 -0
  261. package/dist/discovery/catalog-source/errors.d.ts +32 -0
  262. package/dist/discovery/catalog-source/errors.js +41 -0
  263. package/dist/discovery/catalog-source/errors.js.map +1 -0
  264. package/dist/discovery/catalog-source/index.d.ts +30 -0
  265. package/dist/discovery/catalog-source/index.js +29 -0
  266. package/dist/discovery/catalog-source/index.js.map +1 -0
  267. package/dist/discovery/catalog-source/kiwi-record.d.ts +183 -0
  268. package/dist/discovery/catalog-source/kiwi-record.js +72 -0
  269. package/dist/discovery/catalog-source/kiwi-record.js.map +1 -0
  270. package/dist/discovery/catalog-source/kiwi-schema.d.ts +33 -0
  271. package/dist/discovery/catalog-source/kiwi-schema.js +106 -0
  272. package/dist/discovery/catalog-source/kiwi-schema.js.map +1 -0
  273. package/dist/discovery/catalog-source/kiwi-source.d.ts +38 -0
  274. package/dist/discovery/catalog-source/kiwi-source.js +318 -0
  275. package/dist/discovery/catalog-source/kiwi-source.js.map +1 -0
  276. package/dist/discovery/catalog-source/register.d.ts +48 -0
  277. package/dist/discovery/catalog-source/register.js +83 -0
  278. package/dist/discovery/catalog-source/register.js.map +1 -0
  279. package/dist/discovery/catalog-source/schema.d.ts +21 -0
  280. package/dist/discovery/catalog-source/schema.js +63 -0
  281. package/dist/discovery/catalog-source/schema.js.map +1 -0
  282. package/dist/discovery/catalog-source/source.d.ts +45 -0
  283. package/dist/discovery/catalog-source/source.js +197 -0
  284. package/dist/discovery/catalog-source/source.js.map +1 -0
  285. package/dist/discovery/catalog-source/types.d.ts +122 -0
  286. package/dist/discovery/catalog-source/types.js +30 -0
  287. package/dist/discovery/catalog-source/types.js.map +1 -0
  288. package/dist/discovery/index.d.ts +25 -0
  289. package/dist/discovery/index.js +25 -0
  290. package/dist/discovery/index.js.map +1 -0
  291. package/dist/discovery/resolve.d.ts +175 -0
  292. package/dist/discovery/resolve.js +367 -0
  293. package/dist/discovery/resolve.js.map +1 -0
  294. package/dist/discovery/ucp/error.d.ts +25 -0
  295. package/dist/discovery/ucp/error.js +27 -0
  296. package/dist/discovery/ucp/error.js.map +1 -0
  297. package/dist/discovery/ucp/index.d.ts +33 -0
  298. package/dist/discovery/ucp/index.js +28 -0
  299. package/dist/discovery/ucp/index.js.map +1 -0
  300. package/dist/discovery/ucp/intersect.d.ts +148 -0
  301. package/dist/discovery/ucp/intersect.js +312 -0
  302. package/dist/discovery/ucp/intersect.js.map +1 -0
  303. package/dist/discovery/ucp/resolver.d.ts +62 -0
  304. package/dist/discovery/ucp/resolver.js +214 -0
  305. package/dist/discovery/ucp/resolver.js.map +1 -0
  306. package/dist/discovery/ucp/types.d.ts +97 -0
  307. package/dist/discovery/ucp/types.js +103 -0
  308. package/dist/discovery/ucp/types.js.map +1 -0
  309. package/dist/discovery/ucp/validate.d.ts +38 -0
  310. package/dist/discovery/ucp/validate.js +232 -0
  311. package/dist/discovery/ucp/validate.js.map +1 -0
  312. package/dist/discovery/ucp/vendor.d.ts +60 -0
  313. package/dist/discovery/ucp/vendor.js +61 -0
  314. package/dist/discovery/ucp/vendor.js.map +1 -0
  315. package/dist/doctor.d.ts +26 -0
  316. package/dist/doctor.js +113 -0
  317. package/dist/doctor.js.map +1 -0
  318. package/dist/exit-codes.d.ts +26 -0
  319. package/dist/exit-codes.js +27 -0
  320. package/dist/exit-codes.js.map +1 -0
  321. package/dist/fanout/disclosure.d.ts +138 -0
  322. package/dist/fanout/disclosure.js +188 -0
  323. package/dist/fanout/disclosure.js.map +1 -0
  324. package/dist/fanout/index.d.ts +32 -0
  325. package/dist/fanout/index.js +30 -0
  326. package/dist/fanout/index.js.map +1 -0
  327. package/dist/fanout/orchestrator.d.ts +153 -0
  328. package/dist/fanout/orchestrator.js +439 -0
  329. package/dist/fanout/orchestrator.js.map +1 -0
  330. package/dist/fanout/policy.d.ts +128 -0
  331. package/dist/fanout/policy.js +130 -0
  332. package/dist/fanout/policy.js.map +1 -0
  333. package/dist/handoff/acp-commerce.d.ts +58 -0
  334. package/dist/handoff/acp-commerce.js +69 -0
  335. package/dist/handoff/acp-commerce.js.map +1 -0
  336. package/dist/handoff/authorization.d.ts +127 -0
  337. package/dist/handoff/authorization.js +107 -0
  338. package/dist/handoff/authorization.js.map +1 -0
  339. package/dist/handoff/candidate.d.ts +94 -0
  340. package/dist/handoff/candidate.js +172 -0
  341. package/dist/handoff/candidate.js.map +1 -0
  342. package/dist/handoff/channel.d.ts +88 -0
  343. package/dist/handoff/channel.js +189 -0
  344. package/dist/handoff/channel.js.map +1 -0
  345. package/dist/handoff/completion.d.ts +44 -0
  346. package/dist/handoff/completion.js +136 -0
  347. package/dist/handoff/completion.js.map +1 -0
  348. package/dist/handoff/delivery.d.ts +59 -0
  349. package/dist/handoff/delivery.js +185 -0
  350. package/dist/handoff/delivery.js.map +1 -0
  351. package/dist/handoff/destination.d.ts +39 -0
  352. package/dist/handoff/destination.js +99 -0
  353. package/dist/handoff/destination.js.map +1 -0
  354. package/dist/handoff/errors.d.ts +34 -0
  355. package/dist/handoff/errors.js +47 -0
  356. package/dist/handoff/errors.js.map +1 -0
  357. package/dist/handoff/idempotency.d.ts +51 -0
  358. package/dist/handoff/idempotency.js +168 -0
  359. package/dist/handoff/idempotency.js.map +1 -0
  360. package/dist/handoff/index.d.ts +46 -0
  361. package/dist/handoff/index.js +48 -0
  362. package/dist/handoff/index.js.map +1 -0
  363. package/dist/handoff/ledger.d.ts +94 -0
  364. package/dist/handoff/ledger.js +195 -0
  365. package/dist/handoff/ledger.js.map +1 -0
  366. package/dist/handoff/lifecycle.d.ts +29 -0
  367. package/dist/handoff/lifecycle.js +102 -0
  368. package/dist/handoff/lifecycle.js.map +1 -0
  369. package/dist/handoff/metrics.d.ts +56 -0
  370. package/dist/handoff/metrics.js +78 -0
  371. package/dist/handoff/metrics.js.map +1 -0
  372. package/dist/handoff/operator-approval.d.ts +143 -0
  373. package/dist/handoff/operator-approval.js +287 -0
  374. package/dist/handoff/operator-approval.js.map +1 -0
  375. package/dist/handoff/order-record.d.ts +104 -0
  376. package/dist/handoff/order-record.js +235 -0
  377. package/dist/handoff/order-record.js.map +1 -0
  378. package/dist/handoff/package.d.ts +102 -0
  379. package/dist/handoff/package.js +132 -0
  380. package/dist/handoff/package.js.map +1 -0
  381. package/dist/handoff/transaction.d.ts +96 -0
  382. package/dist/handoff/transaction.js +263 -0
  383. package/dist/handoff/transaction.js.map +1 -0
  384. package/dist/handoff/ucp-checkout/cart-channel.d.ts +111 -0
  385. package/dist/handoff/ucp-checkout/cart-channel.js +370 -0
  386. package/dist/handoff/ucp-checkout/cart-channel.js.map +1 -0
  387. package/dist/handoff/ucp-checkout/cart-parse.d.ts +22 -0
  388. package/dist/handoff/ucp-checkout/cart-parse.js +102 -0
  389. package/dist/handoff/ucp-checkout/cart-parse.js.map +1 -0
  390. package/dist/handoff/ucp-checkout/cart-types.d.ts +93 -0
  391. package/dist/handoff/ucp-checkout/cart-types.js +38 -0
  392. package/dist/handoff/ucp-checkout/cart-types.js.map +1 -0
  393. package/dist/handoff/ucp-checkout/channel.d.ts +159 -0
  394. package/dist/handoff/ucp-checkout/channel.js +636 -0
  395. package/dist/handoff/ucp-checkout/channel.js.map +1 -0
  396. package/dist/handoff/ucp-checkout/client.d.ts +88 -0
  397. package/dist/handoff/ucp-checkout/client.js +228 -0
  398. package/dist/handoff/ucp-checkout/client.js.map +1 -0
  399. package/dist/handoff/ucp-checkout/endpoint.d.ts +36 -0
  400. package/dist/handoff/ucp-checkout/endpoint.js +145 -0
  401. package/dist/handoff/ucp-checkout/endpoint.js.map +1 -0
  402. package/dist/handoff/ucp-checkout/index.d.ts +35 -0
  403. package/dist/handoff/ucp-checkout/index.js +36 -0
  404. package/dist/handoff/ucp-checkout/index.js.map +1 -0
  405. package/dist/handoff/ucp-checkout/parse.d.ts +71 -0
  406. package/dist/handoff/ucp-checkout/parse.js +349 -0
  407. package/dist/handoff/ucp-checkout/parse.js.map +1 -0
  408. package/dist/handoff/ucp-checkout/totals.d.ts +41 -0
  409. package/dist/handoff/ucp-checkout/totals.js +36 -0
  410. package/dist/handoff/ucp-checkout/totals.js.map +1 -0
  411. package/dist/handoff/ucp-checkout/types.d.ts +149 -0
  412. package/dist/handoff/ucp-checkout/types.js +58 -0
  413. package/dist/handoff/ucp-checkout/types.js.map +1 -0
  414. package/dist/handoff/url-safety.d.ts +44 -0
  415. package/dist/handoff/url-safety.js +121 -0
  416. package/dist/handoff/url-safety.js.map +1 -0
  417. package/dist/i18n.d.ts +24 -0
  418. package/dist/i18n.js +27 -0
  419. package/dist/i18n.js.map +1 -0
  420. package/dist/negotiation/action-candidate.d.ts +97 -0
  421. package/dist/negotiation/action-candidate.js +160 -0
  422. package/dist/negotiation/action-candidate.js.map +1 -0
  423. package/dist/negotiation/condition/evaluator.d.ts +36 -0
  424. package/dist/negotiation/condition/evaluator.js +123 -0
  425. package/dist/negotiation/condition/evaluator.js.map +1 -0
  426. package/dist/negotiation/context-map/index.d.ts +23 -0
  427. package/dist/negotiation/context-map/index.js +22 -0
  428. package/dist/negotiation/context-map/index.js.map +1 -0
  429. package/dist/negotiation/context-map/store.d.ts +51 -0
  430. package/dist/negotiation/context-map/store.js +186 -0
  431. package/dist/negotiation/context-map/store.js.map +1 -0
  432. package/dist/negotiation/context-map/types.d.ts +29 -0
  433. package/dist/negotiation/context-map/types.js +57 -0
  434. package/dist/negotiation/context-map/types.js.map +1 -0
  435. package/dist/negotiation/domain/common.d.ts +95 -0
  436. package/dist/negotiation/domain/common.js +216 -0
  437. package/dist/negotiation/domain/common.js.map +1 -0
  438. package/dist/negotiation/domain/envelope.d.ts +55 -0
  439. package/dist/negotiation/domain/envelope.js +110 -0
  440. package/dist/negotiation/domain/envelope.js.map +1 -0
  441. package/dist/negotiation/domain/identifiers.d.ts +65 -0
  442. package/dist/negotiation/domain/identifiers.js +130 -0
  443. package/dist/negotiation/domain/identifiers.js.map +1 -0
  444. package/dist/negotiation/domain/objects.d.ts +149 -0
  445. package/dist/negotiation/domain/objects.js +335 -0
  446. package/dist/negotiation/domain/objects.js.map +1 -0
  447. package/dist/negotiation/idempotency/index.d.ts +26 -0
  448. package/dist/negotiation/idempotency/index.js +25 -0
  449. package/dist/negotiation/idempotency/index.js.map +1 -0
  450. package/dist/negotiation/idempotency/store.d.ts +52 -0
  451. package/dist/negotiation/idempotency/store.js +189 -0
  452. package/dist/negotiation/idempotency/store.js.map +1 -0
  453. package/dist/negotiation/idempotency/types.d.ts +102 -0
  454. package/dist/negotiation/idempotency/types.js +47 -0
  455. package/dist/negotiation/idempotency/types.js.map +1 -0
  456. package/dist/negotiation/jcs.d.ts +21 -0
  457. package/dist/negotiation/jcs.js +114 -0
  458. package/dist/negotiation/jcs.js.map +1 -0
  459. package/dist/negotiation/ledger/event.d.ts +142 -0
  460. package/dist/negotiation/ledger/event.js +206 -0
  461. package/dist/negotiation/ledger/event.js.map +1 -0
  462. package/dist/negotiation/ledger/index.d.ts +26 -0
  463. package/dist/negotiation/ledger/index.js +25 -0
  464. package/dist/negotiation/ledger/index.js.map +1 -0
  465. package/dist/negotiation/ledger/store.d.ts +97 -0
  466. package/dist/negotiation/ledger/store.js +338 -0
  467. package/dist/negotiation/ledger/store.js.map +1 -0
  468. package/dist/negotiation/prompt.d.ts +34 -0
  469. package/dist/negotiation/prompt.js +113 -0
  470. package/dist/negotiation/prompt.js.map +1 -0
  471. package/dist/negotiation/recovery/index.d.ts +26 -0
  472. package/dist/negotiation/recovery/index.js +24 -0
  473. package/dist/negotiation/recovery/index.js.map +1 -0
  474. package/dist/negotiation/recovery/outbound.d.ts +49 -0
  475. package/dist/negotiation/recovery/outbound.js +34 -0
  476. package/dist/negotiation/recovery/outbound.js.map +1 -0
  477. package/dist/negotiation/recovery/recover.d.ts +59 -0
  478. package/dist/negotiation/recovery/recover.js +486 -0
  479. package/dist/negotiation/recovery/recover.js.map +1 -0
  480. package/dist/negotiation/recovery/types.d.ts +85 -0
  481. package/dist/negotiation/recovery/types.js +17 -0
  482. package/dist/negotiation/recovery/types.js.map +1 -0
  483. package/dist/negotiation/state/phase.d.ts +70 -0
  484. package/dist/negotiation/state/phase.js +200 -0
  485. package/dist/negotiation/state/phase.js.map +1 -0
  486. package/dist/negotiation/types.d.ts +139 -0
  487. package/dist/negotiation/types.js +24 -0
  488. package/dist/negotiation/types.js.map +1 -0
  489. package/dist/net/safe-http.d.ts +54 -0
  490. package/dist/net/safe-http.js +116 -0
  491. package/dist/net/safe-http.js.map +1 -0
  492. package/dist/operator/controller.d.ts +181 -0
  493. package/dist/operator/controller.js +784 -0
  494. package/dist/operator/controller.js.map +1 -0
  495. package/dist/operator/runner.d.ts +128 -0
  496. package/dist/operator/runner.js +248 -0
  497. package/dist/operator/runner.js.map +1 -0
  498. package/dist/operator/store.d.ts +39 -0
  499. package/dist/operator/store.js +169 -0
  500. package/dist/operator/store.js.map +1 -0
  501. package/dist/operator/strategy.d.ts +54 -0
  502. package/dist/operator/strategy.js +249 -0
  503. package/dist/operator/strategy.js.map +1 -0
  504. package/dist/operator/tui.d.ts +27 -0
  505. package/dist/operator/tui.js +395 -0
  506. package/dist/operator/tui.js.map +1 -0
  507. package/dist/operator/types.d.ts +224 -0
  508. package/dist/operator/types.js +17 -0
  509. package/dist/operator/types.js.map +1 -0
  510. package/dist/product-buyer.d.ts +74 -0
  511. package/dist/product-buyer.js +161 -0
  512. package/dist/product-buyer.js.map +1 -0
  513. package/dist/product-cli.d.ts +56 -0
  514. package/dist/product-cli.js +175 -0
  515. package/dist/product-cli.js.map +1 -0
  516. package/dist/product-compat.d.ts +50 -0
  517. package/dist/product-compat.js +63 -0
  518. package/dist/product-compat.js.map +1 -0
  519. package/dist/product-init.d.ts +73 -0
  520. package/dist/product-init.js +203 -0
  521. package/dist/product-init.js.map +1 -0
  522. package/dist/product-publish.d.ts +85 -0
  523. package/dist/product-publish.js +216 -0
  524. package/dist/product-publish.js.map +1 -0
  525. package/dist/protocol/legacy-shopping-negotiation/adapter.d.ts +42 -0
  526. package/dist/protocol/legacy-shopping-negotiation/adapter.js +561 -0
  527. package/dist/protocol/legacy-shopping-negotiation/adapter.js.map +1 -0
  528. package/dist/protocol/legacy-shopping-negotiation/mapping.d.ts +78 -0
  529. package/dist/protocol/legacy-shopping-negotiation/mapping.js +321 -0
  530. package/dist/protocol/legacy-shopping-negotiation/mapping.js.map +1 -0
  531. package/dist/protocol/legacy-shopping-negotiation/money.d.ts +49 -0
  532. package/dist/protocol/legacy-shopping-negotiation/money.js +70 -0
  533. package/dist/protocol/legacy-shopping-negotiation/money.js.map +1 -0
  534. package/dist/protocol/legacy-shopping-negotiation/types.d.ts +107 -0
  535. package/dist/protocol/legacy-shopping-negotiation/types.js +34 -0
  536. package/dist/protocol/legacy-shopping-negotiation/types.js.map +1 -0
  537. package/dist/runtime/buyer-policy.d.ts +58 -0
  538. package/dist/runtime/buyer-policy.js +89 -0
  539. package/dist/runtime/buyer-policy.js.map +1 -0
  540. package/dist/runtime/fake-model.d.ts +77 -0
  541. package/dist/runtime/fake-model.js +307 -0
  542. package/dist/runtime/fake-model.js.map +1 -0
  543. package/dist/runtime/foreground.d.ts +56 -0
  544. package/dist/runtime/foreground.js +97 -0
  545. package/dist/runtime/foreground.js.map +1 -0
  546. package/dist/runtime/heartbeat.d.ts +36 -0
  547. package/dist/runtime/heartbeat.js +49 -0
  548. package/dist/runtime/heartbeat.js.map +1 -0
  549. package/dist/runtime/merchant-turn.d.ts +35 -0
  550. package/dist/runtime/merchant-turn.js +20 -0
  551. package/dist/runtime/merchant-turn.js.map +1 -0
  552. package/dist/runtime/model.d.ts +40 -0
  553. package/dist/runtime/model.js +86 -0
  554. package/dist/runtime/model.js.map +1 -0
  555. package/dist/runtime/negotiation-turn.d.ts +101 -0
  556. package/dist/runtime/negotiation-turn.js +317 -0
  557. package/dist/runtime/negotiation-turn.js.map +1 -0
  558. package/dist/runtime/tools.d.ts +64 -0
  559. package/dist/runtime/tools.js +236 -0
  560. package/dist/runtime/tools.js.map +1 -0
  561. package/dist/supervisor/init.d.ts +31 -0
  562. package/dist/supervisor/init.js +222 -0
  563. package/dist/supervisor/init.js.map +1 -0
  564. package/dist/supervisor/logs.d.ts +28 -0
  565. package/dist/supervisor/logs.js +101 -0
  566. package/dist/supervisor/logs.js.map +1 -0
  567. package/dist/supervisor/manage.d.ts +72 -0
  568. package/dist/supervisor/manage.js +419 -0
  569. package/dist/supervisor/manage.js.map +1 -0
  570. package/dist/supervisor/manifest.d.ts +69 -0
  571. package/dist/supervisor/manifest.js +209 -0
  572. package/dist/supervisor/manifest.js.map +1 -0
  573. package/dist/supervisor/stack-config.d.ts +79 -0
  574. package/dist/supervisor/stack-config.js +262 -0
  575. package/dist/supervisor/stack-config.js.map +1 -0
  576. package/dist/supervisor/wrapper.d.ts +17 -0
  577. package/dist/supervisor/wrapper.js +138 -0
  578. package/dist/supervisor/wrapper.js.map +1 -0
  579. package/dist/trust/identity/auth-verifier.d.ts +96 -0
  580. package/dist/trust/identity/auth-verifier.js +166 -0
  581. package/dist/trust/identity/auth-verifier.js.map +1 -0
  582. package/dist/trust/identity/identity-binding.d.ts +63 -0
  583. package/dist/trust/identity/identity-binding.js +59 -0
  584. package/dist/trust/identity/identity-binding.js.map +1 -0
  585. package/dist/trust/identity/index.d.ts +38 -0
  586. package/dist/trust/identity/index.js +31 -0
  587. package/dist/trust/identity/index.js.map +1 -0
  588. package/dist/trust/identity/jws.d.ts +64 -0
  589. package/dist/trust/identity/jws.js +178 -0
  590. package/dist/trust/identity/jws.js.map +1 -0
  591. package/dist/trust/identity/keys.d.ts +71 -0
  592. package/dist/trust/identity/keys.js +118 -0
  593. package/dist/trust/identity/keys.js.map +1 -0
  594. package/dist/trust/identity/message-signature.d.ts +105 -0
  595. package/dist/trust/identity/message-signature.js +297 -0
  596. package/dist/trust/identity/message-signature.js.map +1 -0
  597. package/dist/trust/identity/nonce.d.ts +45 -0
  598. package/dist/trust/identity/nonce.js +50 -0
  599. package/dist/trust/identity/nonce.js.map +1 -0
  600. package/dist/trust/identity/signature-base.d.ts +84 -0
  601. package/dist/trust/identity/signature-base.js +261 -0
  602. package/dist/trust/identity/signature-base.js.map +1 -0
  603. package/dist/trust/identity/trust-policy.d.ts +92 -0
  604. package/dist/trust/identity/trust-policy.js +95 -0
  605. package/dist/trust/identity/trust-policy.js.map +1 -0
  606. package/dist/trust/index.d.ts +21 -0
  607. package/dist/trust/index.js +22 -0
  608. package/dist/trust/index.js.map +1 -0
  609. package/dist/trust/records/evaluator.d.ts +76 -0
  610. package/dist/trust/records/evaluator.js +122 -0
  611. package/dist/trust/records/evaluator.js.map +1 -0
  612. package/dist/trust/records/index.d.ts +28 -0
  613. package/dist/trust/records/index.js +26 -0
  614. package/dist/trust/records/index.js.map +1 -0
  615. package/dist/trust/records/store.d.ts +75 -0
  616. package/dist/trust/records/store.js +339 -0
  617. package/dist/trust/records/store.js.map +1 -0
  618. package/dist/trust/records/types.d.ts +159 -0
  619. package/dist/trust/records/types.js +40 -0
  620. package/dist/trust/records/types.js.map +1 -0
  621. package/dist/tui/banner.d.ts +46 -0
  622. package/dist/tui/banner.js +99 -0
  623. package/dist/tui/banner.js.map +1 -0
  624. package/dist/tui/styles.d.ts +70 -0
  625. package/dist/tui/styles.js +197 -0
  626. package/dist/tui/styles.js.map +1 -0
  627. package/dist/tui/theme.d.ts +63 -0
  628. package/dist/tui/theme.js +136 -0
  629. package/dist/tui/theme.js.map +1 -0
  630. package/examples/profiles/buyer.deepseek.yaml +34 -0
  631. package/examples/profiles/buyer.fake.yaml +36 -0
  632. package/examples/profiles/buyer.local.yaml +40 -0
  633. package/examples/profiles/merchant.deepseek.yaml +32 -0
  634. package/examples/profiles/merchant.fake.yaml +31 -0
  635. package/examples/profiles/merchant.local.yaml +34 -0
  636. package/package.json +55 -0
@@ -0,0 +1,1353 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Buyer capability tools for the main conversation (design §15.1/§15.2).
18
+ * All writes flow through BuyerTaskStore governance (state machine +
19
+ * idempotency); the model never touches SQL or tokens.
20
+ */
21
+ import { createHash } from "node:crypto";
22
+ import { DESTINATION_TYPES, HandoffIdempotencyStore, HandoffEventStore, createHandoffCandidate, defaultUrlSafety, executeHandoff, validateDestination, } from "../../handoff/index.js";
23
+ import { contentDigest } from "../../negotiation/jcs.js";
24
+ import { requireScopeCredential } from "../merchant/credential-broker.js";
25
+ import { NEGOTIATE_DEAL_PRICE_MINOR, NEGOTIATE_DELIVERY_BEFORE, NEGOTIATE_SKU, negotiateWithAgent, summarizeNegotiation, } from "../../a2a/negotiate.js";
26
+ import { buildNegotiationChatTools, writeGateText } from "../negotiation-chat.js";
27
+ import { routeWriteCandidate } from "../write-gate.js";
28
+ import { runSearchCycle } from "./search-loop.js";
29
+ import { BuyerTaskError } from "./types.js";
30
+ import { uuidv7 } from "@earendil-works/pi-ai";
31
+ function textResult(text, details) {
32
+ return { content: [{ type: "text", text }], details };
33
+ }
34
+ function errorText(err) {
35
+ if (err instanceof BuyerTaskError)
36
+ return `任务操作被拒绝(${err.code}):${err.message}`;
37
+ return `任务操作失败:${err instanceof Error ? err.message : String(err)}`;
38
+ }
39
+ function parseIntent(value) {
40
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
41
+ throw new BuyerTaskError("validation", "intent must be an object");
42
+ }
43
+ const v = value;
44
+ const out = {};
45
+ if (typeof v.category === "string")
46
+ out.category = v.category;
47
+ if (typeof v.use_case === "string")
48
+ out.use_case = v.use_case;
49
+ if (typeof v.query_text === "string")
50
+ out.query_text = v.query_text;
51
+ if (typeof v.city === "string")
52
+ out.city = v.city;
53
+ if (typeof v.area === "string")
54
+ out.area = v.area;
55
+ if (typeof v.needed_by === "string")
56
+ out.needed_by = v.needed_by;
57
+ if (typeof v.quantity === "number" && Number.isInteger(v.quantity) && v.quantity > 0) {
58
+ out.quantity = v.quantity;
59
+ }
60
+ if (typeof v.target_unit_price === "number" && Number.isFinite(v.target_unit_price) && v.target_unit_price >= 0) {
61
+ out.target_unit_price = v.target_unit_price;
62
+ }
63
+ if (Array.isArray(v.preferences))
64
+ out.preferences = v.preferences.map(String);
65
+ if (Array.isArray(v.required_terms))
66
+ out.required_terms = v.required_terms.map(String);
67
+ if (Array.isArray(v.open_questions))
68
+ out.open_questions = v.open_questions.map(String);
69
+ if (v.location_precision === "city" || v.location_precision === "district") {
70
+ out.location_precision = v.location_precision;
71
+ }
72
+ return out;
73
+ }
74
+ function parseConstraints(value) {
75
+ if (value === undefined)
76
+ return {};
77
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
78
+ throw new BuyerTaskError("validation", "constraints must be an object");
79
+ }
80
+ const v = value;
81
+ const out = {};
82
+ if (typeof v.max_unit_price === "number" && Number.isFinite(v.max_unit_price) && v.max_unit_price >= 0) {
83
+ out.max_unit_price = v.max_unit_price;
84
+ }
85
+ if (typeof v.max_total_price === "number" && Number.isFinite(v.max_total_price)) {
86
+ out.max_total_price = v.max_total_price;
87
+ }
88
+ if (typeof v.max_total_price_vault_ref === "string") {
89
+ out.max_total_price_vault_ref = v.max_total_price_vault_ref;
90
+ }
91
+ if (typeof v.latest_eta === "string")
92
+ out.latest_eta = v.latest_eta;
93
+ if (Array.isArray(v.required_terms))
94
+ out.required_terms = v.required_terms.map(String);
95
+ if (typeof v.exclude_out_of_stock === "boolean")
96
+ out.exclude_out_of_stock = v.exclude_out_of_stock;
97
+ return out;
98
+ }
99
+ /** Model-facing task constraints never echo the private budget (§11.2). */
100
+ function redactConstraints(constraints) {
101
+ const { max_total_price: _price, max_total_price_vault_ref: _ref, ...rest } = constraints;
102
+ if (_price !== undefined || _ref !== undefined) {
103
+ return { ...rest, max_total_price: "<私密预算>" };
104
+ }
105
+ return { ...rest };
106
+ }
107
+ /**
108
+ * §16 mode table: "创建任务和跟踪规则" is 建议 in manual — the write must NOT
109
+ * execute. supervised/autopilot execute as today. Blocks the low-risk local
110
+ * task/rule writes from silently running under manual.
111
+ */
112
+ function manualAdvice(mode) {
113
+ if (mode?.() === "manual") {
114
+ return {
115
+ ok: false,
116
+ reason: "manual 模式只提供建议、不执行任务写入;请切换到 supervised(写操作需批准)或 autopilot。",
117
+ };
118
+ }
119
+ return { ok: true };
120
+ }
121
+ /** Execute an approved non-binding selection (§12.4 — never an order). */
122
+ function executeSelection(store, now, args) {
123
+ const p = args;
124
+ const task = store.getTask(p.task_id);
125
+ if (task === undefined)
126
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
127
+ const candidate = store.getCandidate(p.candidate_id);
128
+ if (candidate === undefined || candidate.task_id !== p.task_id) {
129
+ throw new BuyerTaskError("not_found", `no candidate ${p.candidate_id} in task`);
130
+ }
131
+ if (candidate.candidate_status !== "shortlisted" && candidate.candidate_status !== "selected") {
132
+ throw new BuyerTaskError("validation", `candidate ${p.candidate_id} is ${candidate.candidate_status}; 只能选定 shortlisted 或已 selected 的候选`);
133
+ }
134
+ const observation = store.latestObservation(p.candidate_id);
135
+ store.appendEvent(p.task_id, "selected", {
136
+ candidate_id: p.candidate_id,
137
+ observation_id: observation?.observation_id ?? null,
138
+ selected_at: now(),
139
+ authorization: p.user_instruction,
140
+ score_explanation: candidate.score_explanation ?? null,
141
+ boundary: "未创建订单;非绑定选定不声明价格、库存或交期仍然有效",
142
+ }, "user", `select:${p.task_id}:${p.candidate_id}:${uuidv7()}`);
143
+ store.updateCandidate(p.candidate_id, { candidate_status: "selected" });
144
+ store.transitionTask({
145
+ task_id: p.task_id,
146
+ to: "selected_nonbinding",
147
+ expected_version: task.version,
148
+ event_type: "status_changed",
149
+ payload: { selected_candidate_id: p.candidate_id },
150
+ origin: "user",
151
+ idempotency_key: `select-transition:${p.task_id}:${p.candidate_id}`,
152
+ selected_candidate_id: p.candidate_id,
153
+ });
154
+ return {
155
+ task_id: p.task_id,
156
+ status: "selected_nonbinding",
157
+ candidate_id: p.candidate_id,
158
+ observation_id: observation?.observation_id ?? null,
159
+ };
160
+ }
161
+ /**
162
+ * 可直接发起本地 A2A 磋商的任务状态。排除:
163
+ * - draft/clarifying(意图未定);searching(搜索在途,避免版本并发冲突);
164
+ * - consulting/negotiating(已有磋商链接);selected_nonbinding(重谈是未来工作)。
165
+ */
166
+ const NEGOTIABLE_TASK_STATUSES = new Set([
167
+ "ready",
168
+ "tracking",
169
+ "shortlist_ready",
170
+ "awaiting_user",
171
+ ]);
172
+ /** 磋商前置快照(审批候选 + 执行前重读用,§16)。 */
173
+ function negotiationPreconditions(store, catalogConfigured, taskId) {
174
+ const task = store.getTask(taskId);
175
+ return {
176
+ task_status: task?.status ?? null,
177
+ task_version: task?.version ?? null,
178
+ catalog_configured: catalogConfigured,
179
+ };
180
+ }
181
+ /**
182
+ * 已批准的本地 A2A 磋商执行体:经 catalog 发现商家 → negotiateWithAgent
183
+ * 确定性 KNP 磋商(RFQ→offer→counter→conditional→accept)。成功:任务
184
+ * consulting → negotiating → selected_nonbinding,链路(connector=a2a-direct)
185
+ * 记 negotiation_id;失败:仅记 ok:false 事件,任务状态不动。
186
+ */
187
+ async function executeNegotiateBuyerTask(deps, args) {
188
+ const { store } = deps;
189
+ const a = args;
190
+ const task = store.getTask(a.task_id);
191
+ if (task === undefined)
192
+ throw new BuyerTaskError("not_found", `no task ${a.task_id}`);
193
+ if (!NEGOTIABLE_TASK_STATUSES.has(task.status)) {
194
+ throw new BuyerTaskError("illegal_transition", `task ${a.task_id} is ${task.status}; 可直接磋商的状态:${[...NEGOTIABLE_TASK_STATUSES].join("/")}`);
195
+ }
196
+ if (deps.catalog === undefined) {
197
+ throw new BuyerTaskError("validation", "未配置 agent catalog(KIWI_CATALOG_URL 或 --catalog)");
198
+ }
199
+ const intent = task.intent;
200
+ const sku = intent.category ?? intent.query_text ?? NEGOTIATE_SKU;
201
+ const result = await negotiateWithAgent({
202
+ catalog: deps.catalog,
203
+ // CD #27:listing → owner_agent_id → getRecord → fresh verify 全链,
204
+ // 与 search_listings 同一注入源(历史教训:只注入 search 面不注入磋商面)。
205
+ ...(deps.catalogSource !== undefined ? { catalogSource: deps.catalogSource } : {}),
206
+ ...(a.catalog_agent_id !== undefined && a.catalog_agent_id !== ""
207
+ ? { catalogAgentId: a.catalog_agent_id }
208
+ : {}),
209
+ sku,
210
+ quantity: intent.quantity ?? 1,
211
+ dealPriceMinor: intent.target_unit_price !== undefined
212
+ ? Math.round(intent.target_unit_price * 100)
213
+ : NEGOTIATE_DEAL_PRICE_MINOR,
214
+ deliveryBefore: intent.needed_by ?? NEGOTIATE_DELIVERY_BEFORE,
215
+ senderIdentity: deps.profile.agent_id,
216
+ });
217
+ const eventKey = `a2a-neg:${a.task_id}:${result.negotiationId}`;
218
+ if (!result.ok) {
219
+ store.appendEvent(a.task_id, "a2a_negotiated", {
220
+ ok: false,
221
+ error: result.error ?? "未知错误",
222
+ negotiation_id: result.negotiationId,
223
+ catalog_agent_id: result.catalogAgentId,
224
+ }, "model", eventKey);
225
+ return { ok: false, task_id: a.task_id, negotiation_id: result.negotiationId, error: result.error };
226
+ }
227
+ const facts = result.facts;
228
+ try {
229
+ const link = store.createConsultationLink({
230
+ task_id: a.task_id,
231
+ connector_id: "a2a-direct",
232
+ conversation_id: result.negotiationId,
233
+ idempotency_key: `a2a-consult:${a.task_id}:${result.negotiationId}`,
234
+ });
235
+ // consulting → negotiating → selected_nonbinding(各跳 version 守卫 + 幂等)。
236
+ let current = store.transitionTask({
237
+ task_id: a.task_id,
238
+ to: "consulting",
239
+ expected_version: task.version,
240
+ event_type: "status_changed",
241
+ payload: { negotiation_id: result.negotiationId, link_id: link.link_id },
242
+ origin: "model",
243
+ idempotency_key: `${eventKey}:consulting`,
244
+ });
245
+ current = store.transitionTask({
246
+ task_id: a.task_id,
247
+ to: "negotiating",
248
+ expected_version: current.version,
249
+ event_type: "status_changed",
250
+ origin: "model",
251
+ idempotency_key: `${eventKey}:negotiating`,
252
+ });
253
+ store.transitionTask({
254
+ task_id: a.task_id,
255
+ to: "selected_nonbinding",
256
+ expected_version: current.version,
257
+ event_type: "status_changed",
258
+ payload: {
259
+ negotiation_id: result.negotiationId,
260
+ agreement_id: result.agreement?.agreement_id ?? null,
261
+ deal_price_minor: facts?.dealPriceMinor ?? null,
262
+ // v0.7.0 KTH:agreement 快照落任务记录——handoff_agreement 的
263
+ // agreementReader 以此做 pre-execution revalidation(§10)。
264
+ agreed_terms: result.agreement?.agreed_terms ?? null,
265
+ terms_digest: result.agreement?.terms_digest ?? null,
266
+ merchant_identity_ref: `merchant:${result.catalogAgentId}`,
267
+ },
268
+ origin: "model",
269
+ idempotency_key: `${eventKey}:selected`,
270
+ });
271
+ store.updateConsultationLink(link.link_id, { status: "closed" });
272
+ store.appendEvent(a.task_id, "a2a_negotiated", {
273
+ ok: true,
274
+ negotiation_id: result.negotiationId,
275
+ catalog_agent_id: result.catalogAgentId,
276
+ agent_card_url: result.agentCardUrl,
277
+ sku: facts?.sku ?? sku,
278
+ quantity: facts?.quantity ?? intent.quantity ?? 1,
279
+ offer_price_minor: facts?.offerPriceMinor ?? null,
280
+ deal_price_minor: facts?.dealPriceMinor ?? null,
281
+ delivery_before: facts?.deliveryBefore ?? null,
282
+ agreement_id: result.agreement?.agreement_id ?? null,
283
+ steps: result.steps,
284
+ boundary: "非绑定协议 — 不创建订单、不锁库存、不授权支付",
285
+ }, "model", eventKey);
286
+ await deps.recordNegotiation?.({
287
+ negotiationId: result.negotiationId,
288
+ catalogAgentId: result.catalogAgentId,
289
+ sku: facts?.sku ?? sku,
290
+ quantity: facts?.quantity ?? intent.quantity ?? 1,
291
+ offerPriceMinor: facts?.offerPriceMinor,
292
+ dealPriceMinor: facts?.dealPriceMinor,
293
+ agreementId: typeof result.agreement?.agreement_id === "string"
294
+ ? result.agreement.agreement_id
295
+ : undefined,
296
+ }).catch(() => undefined); // 记忆尽力而为,失败不阻塞任务推进
297
+ return {
298
+ ok: true,
299
+ task_id: a.task_id,
300
+ status: "selected_nonbinding",
301
+ link_id: link.link_id,
302
+ negotiation_id: result.negotiationId,
303
+ catalog_agent_id: result.catalogAgentId,
304
+ agreement_id: result.agreement?.agreement_id ?? null,
305
+ facts,
306
+ summary: summarizeNegotiation(result),
307
+ };
308
+ }
309
+ catch (err) {
310
+ // 磋商已成功但本地持久化冲突(如调度器并发推进任务):协议在 ledger 里
311
+ // 仍有效,任务状态不动。绝不把异常抛出 execute(会卡死已 approved 候选)。
312
+ store.appendEvent(a.task_id, "a2a_negotiated", {
313
+ ok: false,
314
+ phase: "post-negotiation-persist",
315
+ negotiation_id: result.negotiationId,
316
+ error: err instanceof Error ? err.message : String(err),
317
+ }, "model", `${eventKey}:persist-fail`);
318
+ return {
319
+ ok: false,
320
+ task_id: a.task_id,
321
+ negotiation_id: result.negotiationId,
322
+ error: `磋商已完成但任务状态写入失败:${err instanceof Error ? err.message : String(err)}`,
323
+ };
324
+ }
325
+ }
326
+ /** Preconditions for a consultation start: task + candidate state (§16). */
327
+ function consultationPreconditions(store, taskId, candidateId) {
328
+ const task = store.getTask(taskId);
329
+ const candidate = store.getCandidate(candidateId);
330
+ return {
331
+ task_status: task?.status ?? null,
332
+ task_version: task?.version ?? null,
333
+ candidate_status: candidate?.candidate_status ?? null,
334
+ };
335
+ }
336
+ /**
337
+ * Execution of an approved consultation start: create the authoritative
338
+ * Marketplace Conversation via the connector, link the task to it, and
339
+ * transition the task to `consulting` (§11.8/§20-C).
340
+ */
341
+ async function executeStartConsultation(deps, args) {
342
+ const { store, connector } = deps;
343
+ const a = args;
344
+ const task = store.getTask(a.task_id);
345
+ if (task === undefined)
346
+ throw new BuyerTaskError("not_found", `no task ${a.task_id}`);
347
+ if (task.status !== "awaiting_user") {
348
+ throw new BuyerTaskError("illegal_transition", `task ${a.task_id} is ${task.status}; 发起咨询要求 awaiting_user`);
349
+ }
350
+ const conv = await connector.startConsultation({
351
+ buyer_id: deps.profile.owner_id,
352
+ sku: a.sku,
353
+ merchant_id: a.merchant_id,
354
+ opening_message: a.message,
355
+ });
356
+ const link = store.createConsultationLink({
357
+ task_id: a.task_id,
358
+ candidate_id: a.candidate_id,
359
+ connector_id: connector.connector_id,
360
+ conversation_id: conv.conversation_id,
361
+ idempotency_key: `consult:${a.task_id}:${a.candidate_id}:${conv.conversation_id}`,
362
+ });
363
+ store.transitionTask({
364
+ task_id: a.task_id,
365
+ to: "consulting",
366
+ expected_version: task.version,
367
+ event_type: "status_changed",
368
+ payload: { conversation_id: conv.conversation_id, link_id: link.link_id },
369
+ origin: "model",
370
+ idempotency_key: `consult-transition:${a.task_id}:${conv.conversation_id}`,
371
+ });
372
+ return { link_id: link.link_id, conversation_id: conv.conversation_id, status: conv.status };
373
+ }
374
+ /** Buyer-side consultation link update after a negotiation decision settles. */
375
+ function updateLinkAfterSettle(store, info) {
376
+ const link = store.linkByConversation(info.conversation_id);
377
+ if (link === undefined)
378
+ return;
379
+ if (info.result.result === "human_required") {
380
+ store.updateConsultationLink(link.link_id, { status: "consulting" });
381
+ return;
382
+ }
383
+ // Only an ACCEPTED decision advances the link: a rejected_retryable (local
384
+ // or gateway rejection) sent no message and the conversation did not move,
385
+ // so the link must stay consulting instead of claiming "negotiating".
386
+ if (info.result.result === "accepted") {
387
+ const status = info.result.next_actor === "none" ? "closed" : "negotiating";
388
+ store.updateConsultationLink(link.link_id, { status });
389
+ }
390
+ }
391
+ export function buildBuyerTools(deps) {
392
+ const { store, connector, now, profile } = deps;
393
+ const searchProducts = {
394
+ name: "search_products",
395
+ label: "搜索商品",
396
+ description: "在 Commerce 平台搜索商品(只读事实,含价格/库存/商家公开信息)。",
397
+ parameters: {
398
+ type: "object",
399
+ properties: {
400
+ query: { type: "string" },
401
+ city: { type: "string" },
402
+ area: { type: "string" },
403
+ max_price: { type: "number" },
404
+ include_out_of_stock: { type: "boolean" },
405
+ limit: { type: "integer" },
406
+ },
407
+ additionalProperties: false,
408
+ },
409
+ execute: async (_id, params) => {
410
+ try {
411
+ const p = params;
412
+ const products = await connector.searchProducts({
413
+ ...(typeof p.query === "string" ? { query: p.query } : {}),
414
+ ...(typeof p.city === "string" ? { city: p.city } : {}),
415
+ ...(typeof p.area === "string" ? { area: p.area } : {}),
416
+ ...(typeof p.max_price === "number" ? { max_price: p.max_price } : {}),
417
+ include_out_of_stock: p.include_out_of_stock === true,
418
+ ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
419
+ });
420
+ const rows = products.map((prod) => ({
421
+ sku: prod.sku,
422
+ title: prod.title,
423
+ price: prod.price,
424
+ currency: prod.currency,
425
+ delivery_fee: prod.delivery.fee,
426
+ eta_minutes: prod.delivery.eta_minutes,
427
+ stock: prod.stock,
428
+ merchant: prod.merchant.name,
429
+ city: prod.merchant.city,
430
+ }));
431
+ return textResult(JSON.stringify(rows), { count: rows.length });
432
+ }
433
+ catch (err) {
434
+ return textResult(errorText(err));
435
+ }
436
+ },
437
+ };
438
+ const getProduct = {
439
+ name: "get_product",
440
+ label: "读取商品",
441
+ description: "按 SKU 读取单个商品的最新公开事实。",
442
+ parameters: {
443
+ type: "object",
444
+ properties: { sku: { type: "string" } },
445
+ required: ["sku"],
446
+ additionalProperties: false,
447
+ },
448
+ execute: async (_id, params) => {
449
+ try {
450
+ const { sku } = params;
451
+ const p = await connector.getProduct(sku);
452
+ return textResult(JSON.stringify(p));
453
+ }
454
+ catch (err) {
455
+ return textResult(errorText(err));
456
+ }
457
+ },
458
+ };
459
+ const listTasks = {
460
+ name: "list_buyer_tasks",
461
+ label: "列出任务",
462
+ description: "列出当前进行中的 Buyer 任务(搜索、跟踪、待选择)。",
463
+ parameters: { type: "object", properties: {}, additionalProperties: false },
464
+ execute: async () => {
465
+ const tasks = store.listTasks();
466
+ if (tasks.length === 0)
467
+ return textResult("当前没有进行中的任务。");
468
+ return textResult(tasks
469
+ .map((t) => `· ${t.task_id} [${t.status}] ${t.goal_text}${t.next_run_at !== undefined ? `(下次唤醒 ${t.next_run_at})` : ""}`)
470
+ .join("\n"));
471
+ },
472
+ };
473
+ const getTask = {
474
+ name: "get_buyer_task",
475
+ label: "任务详情",
476
+ description: "查看一个 Buyer 任务的状态、候选、评分解释和最近事件。",
477
+ parameters: {
478
+ type: "object",
479
+ properties: { task_id: { type: "string" } },
480
+ required: ["task_id"],
481
+ additionalProperties: false,
482
+ },
483
+ execute: async (_id, params) => {
484
+ try {
485
+ const { task_id: taskId } = params;
486
+ const task = store.getTask(taskId);
487
+ if (task === undefined)
488
+ throw new BuyerTaskError("not_found", `no task ${taskId}`);
489
+ const candidates = store
490
+ .listCandidates(taskId)
491
+ .sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
492
+ // 失败必须可见:tracking 可能是"等条件满足",也可能是"搜索一直在失败"。
493
+ // 把最近一次 connector_retry 的原因和下次唤醒时间暴露给模型,避免把
494
+ // 重试等待误报为"网络错误已经过去"。
495
+ const retries = store.taskEvents(taskId).filter((e) => e.type === "connector_retry");
496
+ const lastRetry = retries.at(-1);
497
+ const nextWake = task.next_run_at !== undefined ? `;下次唤醒 ${task.next_run_at}` : "";
498
+ const lastError = lastRetry !== undefined && typeof lastRetry.payload.error === "string"
499
+ ? `;上次搜索失败:${String(lastRetry.payload.error)}`
500
+ : "";
501
+ const lines = [
502
+ `${task.task_id} [${task.status}] ${task.goal_text}${nextWake}${lastError}`,
503
+ `意图: ${JSON.stringify(task.intent)}`,
504
+ `约束: ${JSON.stringify(redactConstraints(task.constraints))}`,
505
+ ...candidates.map((c) => {
506
+ const reasons = c.rejection_reasons.length > 0 ? ` 排除: ${c.rejection_reasons.join(";")}` : "";
507
+ return `· ${c.candidate_id} [${c.candidate_status}/${c.eligibility}] ${c.sku ?? c.external_product_id} score=${c.score?.toFixed(3) ?? "-"}${reasons}`;
508
+ }),
509
+ ];
510
+ // 磋商链接(marketplace 或 A2A 直连)——磋商结果对模型可见。
511
+ const links = store.linksForTask(taskId);
512
+ if (links.length > 0) {
513
+ lines.push("磋商:", ...links.map((l) => `· ${l.conversation_id} [${l.connector_id}/${l.status}]`));
514
+ }
515
+ // 最近一次成功的 A2A 磋商商业要点(报价/条件价/协议 id)——模型和操作者
516
+ // 都要能看到成交数字,而不是只能看到"已达成协议"。
517
+ const negotiated = store
518
+ .taskEvents(taskId)
519
+ .filter((e) => e.type === "a2a_negotiated" && e.payload.ok === true)
520
+ .at(-1);
521
+ if (negotiated !== undefined) {
522
+ const p = negotiated.payload;
523
+ const fmt = (minor) => typeof minor === "number" && Number.isFinite(minor)
524
+ ? (minor / 100).toFixed(2)
525
+ : "?";
526
+ const sku = typeof p.sku === "string" ? p.sku : "?";
527
+ const qty = typeof p.quantity === "number" ? String(p.quantity) : "?";
528
+ const agreement = typeof p.agreement_id === "string" ? p.agreement_id : "?";
529
+ lines.push(`最近磋商: ${String(p.negotiation_id ?? "?")} 商家 ${String(p.catalog_agent_id ?? "?")} ` +
530
+ `${qty} 件 ${sku} 报价 ${fmt(p.offer_price_minor)} 元/件,` +
531
+ `条件价 ${fmt(p.deal_price_minor)} 元/件,agreement ${agreement}(非绑定)`);
532
+ }
533
+ return textResult(lines.join("\n"));
534
+ }
535
+ catch (err) {
536
+ return textResult(errorText(err));
537
+ }
538
+ },
539
+ };
540
+ const createTask = {
541
+ name: "create_buyer_task",
542
+ label: "创建购买任务",
543
+ description: "把用户的购买需求创建成 Buyer 任务。意图足够明确(有品类或关键词)会立即执行一轮搜索;" +
544
+ "关键信息缺失(品类/用途不明)时进入 clarifying,先向用户追问。私有预算会自动加密保存," +
545
+ "绝不在任务输出、回复或任何地方回显预算数值。",
546
+ parameters: {
547
+ type: "object",
548
+ properties: {
549
+ goal_text: { type: "string", description: "用户原始目标的简洁表达" },
550
+ intent: { type: "object", description: "结构化意图(category/query_text/city/needed_by/quantity/target_unit_price 等;target_unit_price = 砍价目标单价)" },
551
+ constraints: {
552
+ type: "object",
553
+ description: "硬约束(max_total_price 私有预算/latest_eta/required_terms/exclude_out_of_stock)",
554
+ },
555
+ run_search: { type: "boolean", description: "意图足够时是否立即搜索(默认 true)" },
556
+ expires_at: { type: "string", description: "RFC3339;任务到期自动失效(可选)" },
557
+ },
558
+ required: ["goal_text", "intent"],
559
+ additionalProperties: false,
560
+ },
561
+ execute: async (_id, params) => {
562
+ const guard = manualAdvice(deps.mode);
563
+ if (!guard.ok)
564
+ return textResult(guard.reason);
565
+ try {
566
+ const p = params;
567
+ const goalText = String(p.goal_text ?? "");
568
+ const intent = parseIntent(p.intent);
569
+ const constraints = parseConstraints(p.constraints);
570
+ const expiresAt = typeof p.expires_at === "string" ? p.expires_at : undefined;
571
+ const task = store.createTask({
572
+ goal_text: goalText,
573
+ intent,
574
+ constraints,
575
+ ...(expiresAt !== undefined ? { expires_at: expiresAt } : {}),
576
+ // Content-addressed: a model retry with the same args replays the
577
+ // existing task instead of creating a duplicate.
578
+ idempotency_key: `create:${createHash("sha256")
579
+ .update(JSON.stringify({ goal_text: goalText, intent, constraints, expires_at: expiresAt ?? null }))
580
+ .digest("hex")
581
+ .slice(0, 16)}`,
582
+ });
583
+ if (task.status !== "draft") {
584
+ // Content-addressed replay: this exact create already ran — the task
585
+ // exists and progressed. Short-circuit instead of re-transitioning.
586
+ return textResult(`任务 ${task.task_id} 已存在(${task.status})。`, {
587
+ task_id: task.task_id,
588
+ status: task.status,
589
+ });
590
+ }
591
+ // Clarifying gate (§12.1): no category AND no query text => ask first.
592
+ const needsClarification = (intent.category === undefined || intent.category === "") &&
593
+ (intent.query_text === undefined || intent.query_text === "");
594
+ if (needsClarification) {
595
+ store.transitionTask({
596
+ task_id: task.task_id,
597
+ to: "clarifying",
598
+ expected_version: task.version,
599
+ event_type: "clarified",
600
+ origin: "model",
601
+ idempotency_key: `${task.task_id}:clarifying`,
602
+ });
603
+ return textResult(`任务 ${task.task_id} 已创建(clarifying)。关键信息不足,请先向用户澄清品类/用途或关键词。`, { task_id: task.task_id, status: "clarifying" });
604
+ }
605
+ let current = store.transitionTask({
606
+ task_id: task.task_id,
607
+ to: "ready",
608
+ expected_version: task.version,
609
+ event_type: "status_changed",
610
+ origin: "model",
611
+ idempotency_key: `${task.task_id}:ready`,
612
+ });
613
+ if (p.run_search === false) {
614
+ return textResult(`任务 ${task.task_id} 已就绪(ready)。`, {
615
+ task_id: task.task_id,
616
+ status: "ready",
617
+ });
618
+ }
619
+ const cycle = await runSearchCycle({ store, connector, now }, task.task_id, `tool:${uuidv7()}`);
620
+ current = cycle.task;
621
+ if (cycle.outcome === "shortlist_ready") {
622
+ const lines = cycle.shortlist.map(({ candidate }) => {
623
+ const top = candidate.score_explanation?.dimensions
624
+ .slice()
625
+ .sort((a, b) => b.weight * b.score - a.weight * a.score)[0];
626
+ return `· ${candidate.candidate_id} ${candidate.sku}(${(candidate.score ?? 0).toFixed(2)})${top !== undefined ? ` 主要加分: ${top.dimension} ${top.note}` : ""}`;
627
+ });
628
+ return textResult(`搜索完成,${cycle.shortlist.length} 个候选待你选择:\n${lines.join("\n")}\n` +
629
+ `用 select_product_nonbinding 可形成非绑定选定(不是下单)。`, { task_id: task.task_id, status: current.status });
630
+ }
631
+ if (cycle.outcome === "tracking") {
632
+ return textResult(`暂无满足条件的候选,已转入跟踪(${task.task_id}),条件满足时会通知你。`, { task_id: task.task_id, status: current.status });
633
+ }
634
+ if (cycle.outcome === "retry") {
635
+ return textResult(`搜索遇到临时错误,已安排自动重试(${task.task_id})。错误:${cycle.error ?? "未知"}`, { task_id: task.task_id, status: current.status });
636
+ }
637
+ return textResult(`搜索失败:${cycle.error ?? "未知错误"}(任务已标记 failed)。`, {
638
+ task_id: task.task_id,
639
+ status: current.status,
640
+ });
641
+ }
642
+ catch (err) {
643
+ return textResult(errorText(err));
644
+ }
645
+ },
646
+ };
647
+ const updateConstraints = {
648
+ name: "update_buyer_task_constraints",
649
+ label: "调整任务约束",
650
+ description: "调整任务的硬约束(预算、交期等);awaiting_user 中的任务会立即重新搜索。",
651
+ parameters: {
652
+ type: "object",
653
+ properties: {
654
+ task_id: { type: "string" },
655
+ constraints: { type: "object" },
656
+ },
657
+ required: ["task_id", "constraints"],
658
+ additionalProperties: false,
659
+ },
660
+ execute: async (_id, params) => {
661
+ const guard = manualAdvice(deps.mode);
662
+ if (!guard.ok)
663
+ return textResult(guard.reason);
664
+ try {
665
+ const p = params;
666
+ const task = store.getTask(p.task_id);
667
+ if (task === undefined)
668
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
669
+ const updated = store.updateTask(p.task_id, { constraints: parseConstraints(p.constraints) }, task.version, `update-constraints:${uuidv7()}`);
670
+ if (updated.status === "awaiting_user") {
671
+ const searching = store.transitionTask({
672
+ task_id: updated.task_id,
673
+ to: "searching",
674
+ expected_version: updated.version,
675
+ event_type: "search_started",
676
+ origin: "user",
677
+ idempotency_key: `${updated.task_id}:re-search:${uuidv7()}`,
678
+ });
679
+ const cycle = await runSearchCycle({ store, connector, now }, searching.task_id, `tool:${uuidv7()}`);
680
+ return textResult(`约束已更新并重新搜索(结果:${cycle.outcome})。`);
681
+ }
682
+ return textResult(`约束已更新(任务 ${updated.task_id},当前 ${updated.status}),下次搜索生效。`);
683
+ }
684
+ catch (err) {
685
+ return textResult(errorText(err));
686
+ }
687
+ },
688
+ };
689
+ const addRule = {
690
+ name: "add_tracking_rule",
691
+ label: "添加跟踪规则",
692
+ description: "为任务或单个候选添加跟踪规则(降价、到货、促销变化、交期、定时复查)。",
693
+ parameters: {
694
+ type: "object",
695
+ properties: {
696
+ task_id: { type: "string" },
697
+ candidate_id: { type: "string" },
698
+ rule_type: {
699
+ type: "string",
700
+ enum: ["price_below", "stock_available", "delivery_before", "new_candidate", "periodic_review"],
701
+ },
702
+ condition: { type: "object", description: "如 {threshold: 90} 或 {eta_before: RFC3339}" },
703
+ interval_seconds: { type: "integer" },
704
+ cooldown_seconds: { type: "integer" },
705
+ },
706
+ required: ["task_id", "rule_type", "condition", "interval_seconds"],
707
+ additionalProperties: false,
708
+ },
709
+ execute: async (_id, params) => {
710
+ const guard = manualAdvice(deps.mode);
711
+ if (!guard.ok)
712
+ return textResult(guard.reason);
713
+ try {
714
+ const p = params;
715
+ const rule = store.addTrackingRule({
716
+ task_id: String(p.task_id),
717
+ ...(typeof p.candidate_id === "string" ? { candidate_id: p.candidate_id } : {}),
718
+ rule_type: p.rule_type,
719
+ condition: (p.condition ?? {}),
720
+ interval_seconds: Number(p.interval_seconds),
721
+ ...(typeof p.cooldown_seconds === "number"
722
+ ? { cooldown_seconds: p.cooldown_seconds }
723
+ : {}),
724
+ idempotency_key: `rule:${uuidv7()}`,
725
+ });
726
+ return textResult(`跟踪规则已安装(${rule.rule_id},每 ${rule.interval_seconds}s 检查)。`, { rule_id: rule.rule_id });
727
+ }
728
+ catch (err) {
729
+ return textResult(errorText(err));
730
+ }
731
+ },
732
+ };
733
+ const pauseRule = {
734
+ name: "pause_tracking_rule",
735
+ label: "暂停跟踪规则",
736
+ description: "暂停一条跟踪规则。",
737
+ parameters: {
738
+ type: "object",
739
+ properties: { rule_id: { type: "string" } },
740
+ required: ["rule_id"],
741
+ additionalProperties: false,
742
+ },
743
+ execute: async (_id, params) => {
744
+ const guard = manualAdvice(deps.mode);
745
+ if (!guard.ok)
746
+ return textResult(guard.reason);
747
+ try {
748
+ const rule = store.pauseRule(String(params.rule_id));
749
+ return textResult(`规则 ${rule.rule_id} 已暂停。`);
750
+ }
751
+ catch (err) {
752
+ return textResult(errorText(err));
753
+ }
754
+ },
755
+ };
756
+ const cancelTask = {
757
+ name: "cancel_buyer_task",
758
+ label: "取消任务",
759
+ description: "取消一个 Buyer 任务(仅 draft/clarifying/awaiting_user 可取消)。",
760
+ parameters: {
761
+ type: "object",
762
+ properties: { task_id: { type: "string" } },
763
+ required: ["task_id"],
764
+ additionalProperties: false,
765
+ },
766
+ execute: async (_id, params) => {
767
+ const guard = manualAdvice(deps.mode);
768
+ if (!guard.ok)
769
+ return textResult(guard.reason);
770
+ try {
771
+ const p = params;
772
+ const task = store.getTask(p.task_id);
773
+ if (task === undefined)
774
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
775
+ store.transitionTask({
776
+ task_id: p.task_id,
777
+ to: "cancelled",
778
+ expected_version: task.version,
779
+ event_type: "cancelled",
780
+ origin: "user",
781
+ idempotency_key: `cancel:${uuidv7()}`,
782
+ });
783
+ return textResult(`任务 ${p.task_id} 已取消。`);
784
+ }
785
+ catch (err) {
786
+ return textResult(errorText(err));
787
+ }
788
+ },
789
+ };
790
+ const selectNonbinding = {
791
+ name: "select_product_nonbinding",
792
+ label: "非绑定选定",
793
+ description: "用户明确选定某个候选时记录非绑定选定结果(§12.4:不是下单,不声明价格/库存仍有效)。" +
794
+ "选定是本地非绑定标记,直接执行、无需审批;任务在 awaiting_user(或已选定想再谈价回 consulting 后)时调用。",
795
+ parameters: {
796
+ type: "object",
797
+ properties: {
798
+ task_id: { type: "string" },
799
+ candidate_id: { type: "string" },
800
+ user_instruction: { type: "string", description: "用户选定指令原文(授权依据)" },
801
+ },
802
+ required: ["task_id", "candidate_id", "user_instruction"],
803
+ additionalProperties: false,
804
+ },
805
+ execute: async (_id, params) => {
806
+ const guard = manualAdvice(deps.mode);
807
+ if (!guard.ok)
808
+ return textResult(guard.reason);
809
+ try {
810
+ const p = params;
811
+ // 选定是本地非绑定标记(不是订单、不对外写)——无需审批,直接执行。
812
+ const result = executeSelection(store, now, {
813
+ task_id: p.task_id,
814
+ candidate_id: p.candidate_id,
815
+ user_instruction: p.user_instruction,
816
+ });
817
+ return textResult(`已记录非绑定选定(${result.candidate_id})。这不是订单;价格、库存或交期以当时观察 ${result.observation_id ?? "-"} 为准,可能已变化。`, result);
818
+ }
819
+ catch (err) {
820
+ return textResult(errorText(err));
821
+ }
822
+ },
823
+ };
824
+ const startConsultation = {
825
+ name: "start_consultation",
826
+ label: "发起咨询",
827
+ description: "把 Buyer 任务与一个 Marketplace Conversation 关联并发起咨询(§11.8/§20-C)。" +
828
+ "不复制权威会话状态;磋商继续由现有磋商运行时处理。写操作,supervised 模式需 /approve 批准。" +
829
+ "任务必须处于 awaiting_user 且候选在 shortlist 中。",
830
+ parameters: {
831
+ type: "object",
832
+ properties: {
833
+ task_id: { type: "string" },
834
+ candidate_id: { type: "string" },
835
+ message: { type: "string", description: "发给商家的咨询消息" },
836
+ },
837
+ required: ["task_id", "candidate_id", "message"],
838
+ additionalProperties: false,
839
+ },
840
+ execute: async (_id, params) => {
841
+ if (deps.approvals === undefined || deps.broker === undefined || deps.mode === undefined) {
842
+ return textResult("当前环境未配置咨询能力(缺少审批存储或磋商凭据),无法发起咨询。");
843
+ }
844
+ const credential = requireScopeCredential(deps.broker, "negotiation");
845
+ if (!credential.ok)
846
+ return textResult(credential.reason);
847
+ try {
848
+ const p = params;
849
+ const task = store.getTask(p.task_id);
850
+ if (task === undefined)
851
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
852
+ if (task.status !== "awaiting_user") {
853
+ throw new BuyerTaskError("illegal_transition", `task ${p.task_id} is ${task.status}; 发起咨询要求 awaiting_user`);
854
+ }
855
+ const candidate = store.getCandidate(p.candidate_id);
856
+ if (candidate === undefined || candidate.task_id !== p.task_id) {
857
+ throw new BuyerTaskError("not_found", `no candidate ${p.candidate_id} in task`);
858
+ }
859
+ if (candidate.candidate_status !== "shortlisted") {
860
+ throw new BuyerTaskError("validation", `candidate ${p.candidate_id} is ${candidate.candidate_status}, not shortlisted`);
861
+ }
862
+ const args = {
863
+ task_id: p.task_id,
864
+ candidate_id: p.candidate_id,
865
+ message: p.message,
866
+ sku: candidate.sku ?? candidate.external_product_id,
867
+ merchant_id: candidate.merchant_id ?? "",
868
+ };
869
+ const outcome = await routeWriteCandidate({
870
+ mode: deps.mode,
871
+ approvals: deps.approvals,
872
+ profile,
873
+ now,
874
+ registerPending: deps.registerPending,
875
+ }, {
876
+ tool: "start_consultation",
877
+ arguments: args,
878
+ preconditions: consultationPreconditions(store, p.task_id, p.candidate_id),
879
+ risk: "send_consultation",
880
+ execute: (approvedArgs) => executeStartConsultation(deps, approvedArgs),
881
+ readPreconditions: () => consultationPreconditions(store, p.task_id, p.candidate_id),
882
+ autopilotEscalation: () => {
883
+ if (profile.buyer_policy?.auto_negotiate === false) {
884
+ return "buyer 未授权自动咨询(auto_negotiate=false),需要人工确认。";
885
+ }
886
+ return undefined;
887
+ },
888
+ });
889
+ if (outcome.kind === "pending_approval") {
890
+ store.appendEvent(p.task_id, "approval_requested", { candidate_id: outcome.candidate.candidate_id, tool: "start_consultation" }, "model", `approval:${outcome.candidate.candidate_id}`);
891
+ }
892
+ return writeGateText(outcome);
893
+ }
894
+ catch (err) {
895
+ return textResult(errorText(err));
896
+ }
897
+ },
898
+ };
899
+ const negotiateBuyerTask = {
900
+ name: "negotiate_buyer_task",
901
+ label: "本地 A2A 磋商",
902
+ description: "用 Buyer 任务的意图直接与商家进行本地 A2A 磋商:经 agent catalog 发现商家," +
903
+ "与商家的 A2A 节点跑确定性 KNP 磋商(RFQ→offer→counter→conditional→accept)。" +
904
+ "不依赖 marketplace API;商家报价仍来自其自身商品库(折扣/底价在商家侧)。" +
905
+ "SKU/数量/目标单价/交期取自任务 intent(category 或 query_text 视为 SKU)。" +
906
+ "结果是非绑定协议(不创建订单、不锁库存、不授权支付)。写操作:supervised 模式" +
907
+ "需 /approve 批准后才真正发送消息;任务须处于 ready/tracking/shortlist_ready/awaiting_user。" +
908
+ "达成协议后任务进入 selected_nonbinding。",
909
+ parameters: {
910
+ type: "object",
911
+ properties: {
912
+ task_id: { type: "string" },
913
+ catalog_agent_id: {
914
+ type: "string",
915
+ description: "目标商家 catalog_agent_id(可选;缺省取第一个可发现商家)",
916
+ },
917
+ },
918
+ required: ["task_id"],
919
+ additionalProperties: false,
920
+ },
921
+ execute: async (_id, params) => {
922
+ if (deps.approvals === undefined || deps.mode === undefined) {
923
+ return textResult("当前环境未配置磋商能力(缺少审批存储),无法发起 A2A 磋商。");
924
+ }
925
+ try {
926
+ const p = params;
927
+ const task = store.getTask(p.task_id);
928
+ if (task === undefined)
929
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
930
+ if (!NEGOTIABLE_TASK_STATUSES.has(task.status)) {
931
+ return textResult(`任务 ${p.task_id} 当前状态 ${task.status} 不可直接磋商;` +
932
+ `可直接磋商的状态:${[...NEGOTIABLE_TASK_STATUSES].join("/")}。`);
933
+ }
934
+ if (deps.catalog === undefined) {
935
+ return textResult("未配置 agent catalog(KIWI_CATALOG_URL 或 --catalog),无法发现商家。");
936
+ }
937
+ const args = {
938
+ task_id: p.task_id,
939
+ ...(p.catalog_agent_id !== undefined && p.catalog_agent_id !== ""
940
+ ? { catalog_agent_id: p.catalog_agent_id }
941
+ : {}),
942
+ };
943
+ const outcome = await routeWriteCandidate({ mode: deps.mode, approvals: deps.approvals, profile, now, registerPending: deps.registerPending }, {
944
+ tool: "negotiate_buyer_task",
945
+ arguments: args,
946
+ preconditions: negotiationPreconditions(store, true, p.task_id),
947
+ risk: "send_negotiation_message",
948
+ execute: (approvedArgs) => executeNegotiateBuyerTask(deps, approvedArgs),
949
+ readPreconditions: () => negotiationPreconditions(store, deps.catalog !== undefined, p.task_id),
950
+ autopilotEscalation: () => profile.buyer_policy?.auto_negotiate === false
951
+ ? "buyer 未授权自动磋商(auto_negotiate=false),需要人工确认。"
952
+ : undefined,
953
+ });
954
+ if (outcome.kind === "pending_approval") {
955
+ store.appendEvent(p.task_id, "approval_requested", { candidate_id: outcome.candidate.candidate_id, tool: "negotiate_buyer_task" }, "model", `approval:${outcome.candidate.candidate_id}`);
956
+ }
957
+ return writeGateText(outcome);
958
+ }
959
+ catch (err) {
960
+ return textResult(errorText(err));
961
+ }
962
+ },
963
+ };
964
+ const negotiationTools = deps.commerceClient !== undefined &&
965
+ deps.broker !== undefined &&
966
+ deps.approvals !== undefined &&
967
+ deps.mode !== undefined
968
+ ? buildNegotiationChatTools({
969
+ profile,
970
+ commerceClient: deps.commerceClient,
971
+ broker: deps.broker,
972
+ approvals: deps.approvals,
973
+ mode: deps.mode,
974
+ now,
975
+ registerPending: deps.registerPending,
976
+ afterSettle: (info) => updateLinkAfterSettle(store, info),
977
+ })
978
+ : [];
979
+ const handoffAgreement = {
980
+ name: "handoff_agreement",
981
+ label: "非绑定协议交接",
982
+ description: "把已谈妥的非绑定协议(selected_nonbinding 任务)交接给真实成交入口(KTH/0.1)。" +
983
+ "经审批门(supervised 需 /approve)后执行:重验 agreement/terms/destination/expiry," +
984
+ "安全交付到 external checkout URL / PO / quote / contact 等目的地。不创建订单、不授权支付、不预留库存。",
985
+ parameters: {
986
+ type: "object",
987
+ properties: {
988
+ task_id: { type: "string", description: "已达成非绑定协议(selected_nonbinding)的 buyer 任务" },
989
+ destination_type: {
990
+ type: "string",
991
+ enum: [...DESTINATION_TYPES],
992
+ description: "目的地类型(KTH destination_type 词表,单一来源)",
993
+ },
994
+ destination_ref: {
995
+ type: "string",
996
+ description: "目的地引用:URL 类为 https URL,会话/文档类为 opaque ref",
997
+ },
998
+ display_summary_merchant: { type: "string", description: "展示用商家名(缺省取协议商家)" },
999
+ display_summary_text: { type: "string", description: "展示用摘要(如“200 units, CNY 835.00/unit”)" },
1000
+ expires_in_hours: { type: "number", description: "候选有效期(小时,缺省 24)" },
1001
+ },
1002
+ required: ["task_id", "destination_type", "destination_ref"],
1003
+ additionalProperties: false,
1004
+ },
1005
+ execute: async (_id, params) => {
1006
+ const handoff = deps.handoff;
1007
+ if (handoff === undefined) {
1008
+ return textResult("未配置 Handoff 能力(kernel 未注入 handoff 存储)。");
1009
+ }
1010
+ if (deps.approvals === undefined || deps.mode === undefined) {
1011
+ return textResult("当前环境未配置审批存储,无法发起 Handoff 交接。");
1012
+ }
1013
+ try {
1014
+ const p = params;
1015
+ const task = deps.store.getTask(p.task_id);
1016
+ if (task === undefined)
1017
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
1018
+ if (task.status !== "selected_nonbinding") {
1019
+ return textResult(`任务 ${p.task_id} 当前状态 ${task.status};handoff 需要先达成非绑定协议(selected_nonbinding)。`);
1020
+ }
1021
+ const agreement = agreementFromTask(deps.store.taskEvents(p.task_id));
1022
+ if (agreement === undefined) {
1023
+ return textResult("任务记录缺少 agreement 快照(磋商未完成或 terms 未持久化)。");
1024
+ }
1025
+ const destination = validateDestination({ type: p.destination_type, ref: p.destination_ref });
1026
+ // 协议级去重:同一磋商、同一目的地已交付过 → 拒绝(LLM 重试会生成
1027
+ // 新候选 → 新 digest,绕过 (candidate_id, digest) 幂等键,导致同协议
1028
+ // 二次交付/二次 URL 探测、negotiation_to_handoff_rate 虚高)。
1029
+ const priorDelivery = handoff.ledger.events(agreement.negotiation_id).find((e) => e.event_kind === "handoff_delivered" &&
1030
+ e.destination?.ref === destination.ref &&
1031
+ e.destination?.type === destination.type);
1032
+ if (priorDelivery !== undefined) {
1033
+ return textResult(`该协议已交付过(handoff ${priorDelivery.handoff_id ?? "unknown"},` +
1034
+ `目的地 ${destination.type} ${destination.ref}),同一目的地不重复交接。`);
1035
+ }
1036
+ // NaN 防护:非数字 expires_in_hours → Date.now()+NaN 抛 RangeError。
1037
+ const expiresInHours = Number(p.expires_in_hours ?? 24);
1038
+ if (!Number.isFinite(expiresInHours) || expiresInHours <= 0) {
1039
+ return textResult("expires_in_hours 必须是正数(小时)。");
1040
+ }
1041
+ // 审计链:stale 候选之后的新候选链接到被它取代的候选
1042
+ //(supersedes_candidate_id;此前从不写入,stale 后的新候选是审计孤儿)。
1043
+ const lastStale = [...handoff.ledger.events(agreement.negotiation_id)]
1044
+ .reverse()
1045
+ .find((e) => e.event_kind === "handoff_candidate_stale");
1046
+ const candidate = createHandoffCandidate({
1047
+ agreement_id: agreement.agreement_id,
1048
+ negotiation_id: agreement.negotiation_id,
1049
+ agreed_terms: agreement.agreed_terms,
1050
+ buyer_identity_ref: `principal:${profile.agent_id}`,
1051
+ merchant_identity_ref: agreement.merchant_identity_ref,
1052
+ destination: { type: destination.type, ref: destination.ref },
1053
+ display_summary: {
1054
+ merchant: p.display_summary_merchant ?? agreement.merchant_identity_ref,
1055
+ summary: p.display_summary_text ?? `negotiation ${agreement.negotiation_id}`,
1056
+ },
1057
+ policy_version: "handoff-policy/1",
1058
+ // 时钟统一用注入的 deps.now()(此前 created_at 走墙钟、事件走注入时钟,
1059
+ // time_to_handoff 指标掺入偏差)。
1060
+ created_at: deps.now(),
1061
+ expires_at: new Date(Date.parse(deps.now()) + expiresInHours * 3_600_000).toISOString(),
1062
+ ...(lastStale?.handoff_candidate_id !== undefined
1063
+ ? { supersedes_candidate_id: lastStale.handoff_candidate_id }
1064
+ : {}),
1065
+ requires_user_action: true,
1066
+ });
1067
+ const outcome = await routeWriteCandidate({ mode: deps.mode, approvals: deps.approvals, profile, now: deps.now, registerPending: deps.registerPending }, {
1068
+ tool: "handoff_agreement",
1069
+ arguments: { task_id: p.task_id, candidate },
1070
+ preconditions: { agreement_bound: true },
1071
+ risk: "handoff_delivery",
1072
+ execute: (approvedArgs) => executeHandoffForCandidate(deps, approvedArgs),
1073
+ // §16 stale 检测的真实重读:approval 与执行之间协议被重磋商改写
1074
+ //(agreement 消失 / 身份变化 / terms_digest 不匹配)→ 候选 supersede。
1075
+ // 此前恒返回 {agreement_bound: true},write-gate 的 stale 检测对
1076
+ // handoff 结构性失效(防护只剩 executeHandoff 执行期重验)。
1077
+ readPreconditions: () => {
1078
+ const current = agreementFromTask(deps.store.taskEvents(p.task_id));
1079
+ const stillBound = current !== undefined &&
1080
+ current.agreement_id === agreement.agreement_id &&
1081
+ current.negotiation_id === agreement.negotiation_id &&
1082
+ contentDigest(current.agreed_terms) === candidate.terms_digest;
1083
+ return { agreement_bound: stillBound };
1084
+ },
1085
+ // autopilot 政策闸:handoff 把买家送向真实成交入口(外部结账/PO/联系
1086
+ // 商家),是系统里最接近交易的写动作——未授权自动磋商时绝不自动执行。
1087
+ autopilotEscalation: () => profile.buyer_policy?.auto_negotiate === false
1088
+ ? "buyer 未授权自动交接(auto_negotiate=false),需要人工确认。"
1089
+ : undefined,
1090
+ });
1091
+ return writeGateText(outcome);
1092
+ }
1093
+ catch (err) {
1094
+ return textResult(errorText(err));
1095
+ }
1096
+ },
1097
+ };
1098
+ const searchListings = {
1099
+ name: "search_listings",
1100
+ label: "搜索商品/能力 Listing",
1101
+ description: "在 kiwi-catalog 按商品/能力意图搜索 Listing(rev1.5 CD #27)。返回 discovery " +
1102
+ "projection(authority=discovery_projection、requires_direct_confirmation=true):" +
1103
+ "报价/库存必须联系 owner Agent 确认。",
1104
+ parameters: {
1105
+ type: "object",
1106
+ properties: {
1107
+ need_description: { type: "string", description: "需求描述(q)" },
1108
+ category: { type: "string" },
1109
+ region: { type: "string" },
1110
+ listing_type: { type: "string", enum: ["product", "capability"] },
1111
+ min_moq: { type: "integer" },
1112
+ max_moq: { type: "integer" },
1113
+ limit: { type: "integer" },
1114
+ },
1115
+ additionalProperties: false,
1116
+ },
1117
+ execute: async (_id, params) => {
1118
+ const source = deps.catalogSource;
1119
+ if (source === undefined) {
1120
+ return textResult(errorText(new Error("kiwi-catalog listing 源未配置(工具不应被挂载)")));
1121
+ }
1122
+ try {
1123
+ const p = params;
1124
+ const results = await source.searchListings({
1125
+ ...(typeof p.need_description === "string" ? { q: p.need_description } : {}),
1126
+ ...(typeof p.category === "string" ? { category: p.category } : {}),
1127
+ ...(typeof p.region === "string" ? { region: p.region } : {}),
1128
+ ...(p.listing_type === "product" || p.listing_type === "capability"
1129
+ ? { listing_type: p.listing_type }
1130
+ : {}),
1131
+ ...(typeof p.min_moq === "number" ? { min_moq: p.min_moq } : {}),
1132
+ ...(typeof p.max_moq === "number" ? { max_moq: p.max_moq } : {}),
1133
+ ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
1134
+ });
1135
+ const rows = results.map((r) => ({
1136
+ listing_id: r.listing.listing_id,
1137
+ listing_type: r.listing.listing_type,
1138
+ title: r.listing.title,
1139
+ category: r.listing.category,
1140
+ merchant: r.merchant.display_name,
1141
+ owner_agent_id: r.agent.catalog_agent_id,
1142
+ owner_verification: r.agent.verification_level,
1143
+ commercial_hints: r.listing.commercial_hints ?? {},
1144
+ listing_freshness_state: r.listing.listing_freshness_state,
1145
+ authority: r.authority,
1146
+ requires_direct_confirmation: r.requires_direct_confirmation,
1147
+ }));
1148
+ return textResult(JSON.stringify(rows), { count: rows.length });
1149
+ }
1150
+ catch (err) {
1151
+ return textResult(errorText(err));
1152
+ }
1153
+ },
1154
+ };
1155
+ const shortlistListing = {
1156
+ name: "shortlist_listing",
1157
+ label: "入选 Listing",
1158
+ description: "把一个 Listing 写入任务候选并标记 shortlisted(供 negotiate_buyer_task 使用)。" +
1159
+ "Listing 是 discovery projection,真正询价时传 owner_agent_id 给 negotiate_buyer_task。",
1160
+ parameters: {
1161
+ type: "object",
1162
+ properties: {
1163
+ task_id: { type: "string" },
1164
+ listing_id: { type: "string" },
1165
+ owner_agent_id: { type: "string" },
1166
+ title: { type: "string" },
1167
+ sku: { type: "string" },
1168
+ },
1169
+ required: ["task_id", "listing_id", "owner_agent_id"],
1170
+ additionalProperties: false,
1171
+ },
1172
+ execute: async (_id, params) => {
1173
+ try {
1174
+ const p = params;
1175
+ const task = store.getTask(p.task_id);
1176
+ if (task === undefined)
1177
+ throw new BuyerTaskError("not_found", `no task ${p.task_id}`);
1178
+ const candidate = store.upsertCandidate({
1179
+ task_id: p.task_id,
1180
+ connector_id: "kiwi-catalog",
1181
+ platform: "kiwi-catalog",
1182
+ external_product_id: p.listing_id,
1183
+ sku: p.sku,
1184
+ });
1185
+ store.updateCandidate(candidate.candidate_id, {
1186
+ candidate_status: "shortlisted",
1187
+ eligibility: "eligible",
1188
+ });
1189
+ store.appendEvent(p.task_id, "candidate_shortlisted", {
1190
+ candidate_id: candidate.candidate_id,
1191
+ listing_id: p.listing_id,
1192
+ owner_agent_id: p.owner_agent_id,
1193
+ listing_title: p.title ?? null,
1194
+ source: "kiwi-catalog",
1195
+ }, "model", `shortlist:${p.task_id}:${p.listing_id}:${uuidv7()}`);
1196
+ return textResult(JSON.stringify({
1197
+ candidate_id: candidate.candidate_id,
1198
+ task_id: p.task_id,
1199
+ status: "shortlisted",
1200
+ owner_agent_id: p.owner_agent_id,
1201
+ }));
1202
+ }
1203
+ catch (err) {
1204
+ return textResult(errorText(err));
1205
+ }
1206
+ },
1207
+ };
1208
+ return [
1209
+ searchProducts,
1210
+ getProduct,
1211
+ listTasks,
1212
+ getTask,
1213
+ createTask,
1214
+ updateConstraints,
1215
+ addRule,
1216
+ pauseRule,
1217
+ cancelTask,
1218
+ selectNonbinding,
1219
+ startConsultation,
1220
+ negotiateBuyerTask,
1221
+ ...(deps.catalogSource !== undefined ? [searchListings, shortlistListing] : []),
1222
+ ...(deps.handoff !== undefined ? [handoffAgreement] : []),
1223
+ ...negotiationTools,
1224
+ ];
1225
+ }
1226
+ // ── v0.7.0 KTH:handoff_agreement ───────────────────────────────────────────
1227
+ /**
1228
+ * 从任务事件流重建 agreement 快照(handoff 的 agreementReader 权威源;
1229
+ * 取最新 status_changed 事件的 payload —— negotiate 成功时落 agreed_terms)。
1230
+ */
1231
+ function agreementFromTask(events) {
1232
+ let merchantDomain;
1233
+ for (let i = events.length - 1; i >= 0; i--) {
1234
+ const event = events[i];
1235
+ if (event === undefined)
1236
+ continue;
1237
+ if (event.type === "a2a_negotiated") {
1238
+ const cardUrl = event.payload.agent_card_url;
1239
+ // http(s) 均可(URL 安全策略对 loopback http 放行;https-only 由
1240
+ // validateExternalDestinationUrl 强制)。
1241
+ if (typeof cardUrl === "string" && /^https?:\/\//.test(cardUrl)) {
1242
+ try {
1243
+ merchantDomain = new URL(cardUrl).hostname;
1244
+ }
1245
+ catch {
1246
+ merchantDomain = undefined;
1247
+ }
1248
+ }
1249
+ }
1250
+ if (event.type !== "status_changed")
1251
+ continue;
1252
+ const payload = event.payload;
1253
+ const agreementId = payload.agreement_id;
1254
+ const negotiationId = payload.negotiation_id;
1255
+ const agreedTerms = payload.agreed_terms;
1256
+ if (typeof agreementId !== "string" ||
1257
+ typeof negotiationId !== "string" ||
1258
+ agreedTerms === null ||
1259
+ agreedTerms === undefined) {
1260
+ continue;
1261
+ }
1262
+ return {
1263
+ agreement_id: agreementId,
1264
+ negotiation_id: negotiationId,
1265
+ agreed_terms: agreedTerms,
1266
+ merchant_identity_ref: typeof payload.merchant_identity_ref === "string"
1267
+ ? payload.merchant_identity_ref
1268
+ : "merchant:unknown",
1269
+ ...(merchantDomain !== undefined ? { merchant_domain: merchantDomain } : {}),
1270
+ };
1271
+ }
1272
+ return undefined;
1273
+ }
1274
+ function handoffResultText(result) {
1275
+ switch (result.kind) {
1276
+ case "delivered": {
1277
+ const h = result.handoff;
1278
+ return textResult(`交接已交付(handoff ${h.handoff_id}):` +
1279
+ `${h.display_summary.merchant} — ${h.display_summary.summary}\n` +
1280
+ `目的地:${h.destination_type} ${h.destination_ref}${result.final_url !== undefined ? `\n最终 URL:${result.final_url}` : ""}\n` +
1281
+ `非绑定协议已安全交接;Kiwi 不创建订单/不授权支付/不预留库存。`, { status: "delivered", handoff_id: h.handoff_id, destination_type: h.destination_type });
1282
+ }
1283
+ case "already_delivered":
1284
+ return textResult(`该协议已交付过(handoff ${result.handoff_id}),未重复执行。`, {
1285
+ status: "already_delivered",
1286
+ });
1287
+ case "stale":
1288
+ return textResult(`交接未执行:候选已失效(${result.reason})。需重新生成候选。`, { status: "stale" });
1289
+ case "probe_failed":
1290
+ return textResult(`交接未执行:目的地探测瞬时失败(${result.reason})。候选保持可用,可重试。`, {
1291
+ status: "probe_failed",
1292
+ });
1293
+ case "rejected":
1294
+ return textResult(`交接未执行:${result.reason}。`, { status: "rejected" });
1295
+ case "expired":
1296
+ return textResult(`交接未执行:候选已过期。`, { status: "expired" });
1297
+ }
1298
+ }
1299
+ /** 批准后的实际执行:agreementReader 从任务事件重读(§10 revalidation)。 */
1300
+ async function executeHandoffForCandidate(deps, args) {
1301
+ const a = args;
1302
+ const handoff = deps.handoff;
1303
+ if (handoff === undefined) {
1304
+ return { ok: false, status: "unavailable", error: "Handoff 存储未配置,无法执行交接。" };
1305
+ }
1306
+ const agreement = agreementFromTask(deps.store.taskEvents(a.task_id));
1307
+ if (agreement === undefined) {
1308
+ return { ok: false, status: "stale", error: "任务记录缺少 agreement 快照,交接中止(fail-closed)" };
1309
+ }
1310
+ // created 事件在审批通过后才落链:被 /reject 的候选不在 Ledger 留下悬空的
1311
+ // PROPOSED(此前先落链,/reject 后 /handoff 永远显示"从未获批"的候选,
1312
+ // 且 LLM 重复调用会堆积无限候选事件与审批候选)。
1313
+ handoff.ledger.appendCandidateEvent({
1314
+ kind: "handoff_candidate_created",
1315
+ candidate: a.candidate,
1316
+ identity: {
1317
+ sender_identity: a.candidate.buyer_identity_ref,
1318
+ counterparty_identity: a.candidate.merchant_identity_ref,
1319
+ actor: "buyer",
1320
+ },
1321
+ capability: { capability: "com.harrylabsj.kiwi.shopping.negotiation", protocol_version: "1.0" },
1322
+ occurred_at: deps.now(),
1323
+ });
1324
+ const result = await executeHandoff({
1325
+ candidate: a.candidate,
1326
+ ledger: handoff.ledger,
1327
+ idempotency: handoff.idempotency,
1328
+ identity: {
1329
+ sender_identity: a.candidate.buyer_identity_ref,
1330
+ counterparty_identity: a.candidate.merchant_identity_ref,
1331
+ actor: "buyer",
1332
+ },
1333
+ capability: {
1334
+ capability: "com.harrylabsj.kiwi.shopping.negotiation",
1335
+ protocol_version: "1.0",
1336
+ },
1337
+ approval: async () => ({ approved: true, evidence: { via: "write-gate" } }),
1338
+ agreementReader: async () => ({
1339
+ agreement_id: agreement.agreement_id,
1340
+ negotiation_id: agreement.negotiation_id,
1341
+ agreed_terms: agreement.agreed_terms,
1342
+ }),
1343
+ urlSafety: defaultUrlSafety(agreement.merchant_domain),
1344
+ now: deps.now,
1345
+ });
1346
+ const rendered = handoffResultText(result);
1347
+ return {
1348
+ ok: result.kind === "delivered" || result.kind === "already_delivered",
1349
+ status: result.kind,
1350
+ text: rendered.content.map((c) => ("text" in c ? c.text : "")).join("\n"),
1351
+ };
1352
+ }
1353
+ //# sourceMappingURL=buyer-tools.js.map