@codespar/cli 0.13.0 → 0.14.1

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 (248) hide show
  1. package/README.md +51 -1
  2. package/dist/commands/agent.js +13 -7
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/init.js +91 -23
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/login.js +2 -1
  7. package/dist/commands/login.js.map +1 -1
  8. package/dist/commands/mandate-verify.js +75 -27
  9. package/dist/commands/mandate-verify.js.map +1 -1
  10. package/dist/config.js +17 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/did.js +262 -41
  13. package/dist/did.js.map +1 -1
  14. package/dist/index.js +23 -8
  15. package/dist/index.js.map +1 -1
  16. package/package.json +3 -1
  17. package/templates/bills-agent/LICENSE +21 -0
  18. package/templates/bills-agent/README.md +21 -0
  19. package/templates/bills-agent/_gitignore +10 -0
  20. package/templates/bills-agent/agents/bills-agent/.env.example +7 -0
  21. package/templates/bills-agent/agents/bills-agent/AGENTS.md +16 -0
  22. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +16 -0
  23. package/templates/bills-agent/agents/bills-agent/README.md +89 -0
  24. package/templates/bills-agent/agents/bills-agent/SYSTEM_PROMPT.md +23 -0
  25. package/templates/bills-agent/agents/bills-agent/agent.yaml +26 -0
  26. package/templates/bills-agent/agents/bills-agent/channels/terminal/index.ts +108 -0
  27. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap-mandate.json +9 -0
  28. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap.json +10 -0
  29. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
  30. package/templates/bills-agent/agents/bills-agent/evals/adversarial/exfiltration.json +13 -0
  31. package/templates/bills-agent/agents/bills-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  32. package/templates/bills-agent/agents/bills-agent/evals/adversarial/false-authority.json +9 -0
  33. package/templates/bills-agent/agents/bills-agent/evals/adversarial/false-authority.transcript.jsonl +3 -0
  34. package/templates/bills-agent/agents/bills-agent/evals/adversarial/fractioning.json +15 -0
  35. package/templates/bills-agent/agents/bills-agent/evals/adversarial/fractioning.transcript.jsonl +6 -0
  36. package/templates/bills-agent/agents/bills-agent/evals/adversarial/model-total.json +10 -0
  37. package/templates/bills-agent/agents/bills-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
  38. package/templates/bills-agent/agents/bills-agent/evals/adversarial/prompt-injection.json +9 -0
  39. package/templates/bills-agent/agents/bills-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
  40. package/templates/bills-agent/agents/bills-agent/evals/adversarial/webhook-replay.json +9 -0
  41. package/templates/bills-agent/agents/bills-agent/evals/eval.yaml +9 -0
  42. package/templates/bills-agent/agents/bills-agent/guardrails.json +15 -0
  43. package/templates/bills-agent/agents/bills-agent/mandate.example.json +46 -0
  44. package/templates/bills-agent/agents/bills-agent/package.json +24 -0
  45. package/templates/bills-agent/agents/bills-agent/runbook.md +19 -0
  46. package/templates/bills-agent/agents/bills-agent/scenarios/beneficiary-not-allowed.json +14 -0
  47. package/templates/bills-agent/agents/bills-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +2 -0
  48. package/templates/bills-agent/agents/bills-agent/scenarios/cap-exceeded.json +12 -0
  49. package/templates/bills-agent/agents/bills-agent/scenarios/cap-exceeded.transcript.jsonl +2 -0
  50. package/templates/bills-agent/agents/bills-agent/scenarios/escalated-above-threshold.json +20 -0
  51. package/templates/bills-agent/agents/bills-agent/scenarios/escalated-above-threshold.transcript.jsonl +7 -0
  52. package/templates/bills-agent/agents/bills-agent/scenarios/happy-path.json +14 -0
  53. package/templates/bills-agent/agents/bills-agent/scenarios/happy-path.transcript.jsonl +3 -0
  54. package/templates/bills-agent/agents/bills-agent/scenarios/mandate-revoked.json +21 -0
  55. package/templates/bills-agent/agents/bills-agent/scenarios/mandate-revoked.transcript.jsonl +9 -0
  56. package/templates/bills-agent/agents/bills-agent/scenarios/prompt-injection.json +12 -0
  57. package/templates/bills-agent/agents/bills-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
  58. package/templates/bills-agent/agents/bills-agent/src/adversarial.ts +177 -0
  59. package/templates/bills-agent/agents/bills-agent/src/bills.ts +28 -0
  60. package/templates/bills-agent/agents/bills-agent/src/commands/approve.ts +9 -0
  61. package/templates/bills-agent/agents/bills-agent/src/commands/check.ts +13 -0
  62. package/templates/bills-agent/agents/bills-agent/src/commands/consent.ts +26 -0
  63. package/templates/bills-agent/agents/bills-agent/src/commands/decide.ts +45 -0
  64. package/templates/bills-agent/agents/bills-agent/src/commands/deny.ts +7 -0
  65. package/templates/bills-agent/agents/bills-agent/src/commands/eval.ts +37 -0
  66. package/templates/bills-agent/agents/bills-agent/src/commands/reconcile.ts +38 -0
  67. package/templates/bills-agent/agents/bills-agent/src/commands/rerun.ts +65 -0
  68. package/templates/bills-agent/agents/bills-agent/src/commands/resume.ts +30 -0
  69. package/templates/bills-agent/agents/bills-agent/src/main.ts +185 -0
  70. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +125 -0
  71. package/templates/bills-agent/agents/bills-agent/src/modules/pix-out.ts +73 -0
  72. package/templates/bills-agent/agents/bills-agent/src/scenarios.ts +196 -0
  73. package/templates/bills-agent/agents/bills-agent/src/setup.ts +207 -0
  74. package/templates/bills-agent/agents/bills-agent/test/adversarial.test.ts +40 -0
  75. package/templates/bills-agent/agents/bills-agent/test/check.test.ts +85 -0
  76. package/templates/bills-agent/agents/bills-agent/test/cli.test.ts +177 -0
  77. package/templates/bills-agent/agents/bills-agent/test/scenarios.test.ts +73 -0
  78. package/templates/bills-agent/agents/bills-agent/tools.json +51 -0
  79. package/templates/bills-agent/agents/bills-agent/tsconfig.json +11 -0
  80. package/templates/bills-agent/package.json +34 -0
  81. package/templates/bills-agent/packages/agent-core/README.md +19 -0
  82. package/templates/bills-agent/packages/agent-core/package.json +24 -0
  83. package/templates/bills-agent/packages/agent-core/src/agent.ts +125 -0
  84. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +154 -0
  85. package/templates/bills-agent/packages/agent-core/src/api/client.ts +53 -0
  86. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
  87. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +101 -0
  88. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +45 -0
  89. package/templates/bills-agent/packages/agent-core/src/approval.ts +109 -0
  90. package/templates/bills-agent/packages/agent-core/src/bundle.ts +113 -0
  91. package/templates/bills-agent/packages/agent-core/src/check.ts +171 -0
  92. package/templates/bills-agent/packages/agent-core/src/engine.ts +784 -0
  93. package/templates/bills-agent/packages/agent-core/src/escalate.ts +85 -0
  94. package/templates/bills-agent/packages/agent-core/src/events.ts +40 -0
  95. package/templates/bills-agent/packages/agent-core/src/guardrails.ts +45 -0
  96. package/templates/bills-agent/packages/agent-core/src/hash.ts +49 -0
  97. package/templates/bills-agent/packages/agent-core/src/ids.ts +10 -0
  98. package/templates/bills-agent/packages/agent-core/src/index.ts +29 -0
  99. package/templates/bills-agent/packages/agent-core/src/mandate.ts +100 -0
  100. package/templates/bills-agent/packages/agent-core/src/manifest.ts +76 -0
  101. package/templates/bills-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  102. package/templates/bills-agent/packages/agent-core/src/providers/replay.ts +63 -0
  103. package/templates/bills-agent/packages/agent-core/src/providers/types.ts +39 -0
  104. package/templates/bills-agent/packages/agent-core/src/rail.ts +87 -0
  105. package/templates/bills-agent/packages/agent-core/src/revocation.ts +30 -0
  106. package/templates/bills-agent/packages/agent-core/src/secrets.ts +30 -0
  107. package/templates/bills-agent/packages/agent-core/src/state/store.ts +307 -0
  108. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +146 -0
  109. package/templates/bills-agent/packages/agent-core/src/stubs/charge-rail.ts +193 -0
  110. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
  111. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +124 -0
  112. package/templates/bills-agent/packages/agent-core/src/tools.ts +55 -0
  113. package/templates/bills-agent/packages/agent-core/src/types.ts +106 -0
  114. package/templates/bills-agent/packages/agent-core/test/approval.test.ts +83 -0
  115. package/templates/bills-agent/packages/agent-core/test/engine.test.ts +524 -0
  116. package/templates/bills-agent/packages/agent-core/test/escalate.test.ts +42 -0
  117. package/templates/bills-agent/packages/agent-core/test/helpers.ts +139 -0
  118. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
  119. package/templates/bills-agent/packages/agent-core/test/manifest.test.ts +45 -0
  120. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +265 -0
  121. package/templates/bills-agent/packages/agent-core/test/secrets.test.ts +28 -0
  122. package/templates/bills-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  123. package/templates/bills-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  124. package/templates/bills-agent/packages/agent-core/tsconfig.json +10 -0
  125. package/templates/bills-agent/tsconfig.base.json +16 -0
  126. package/templates/bills-agent/vitest.config.ts +12 -0
  127. package/templates/collections-agent/LICENSE +21 -0
  128. package/templates/collections-agent/README.md +20 -0
  129. package/templates/collections-agent/_gitignore +10 -0
  130. package/templates/collections-agent/agents/collections-agent/.env.example +4 -0
  131. package/templates/collections-agent/agents/collections-agent/AGENTS.md +19 -0
  132. package/templates/collections-agent/agents/collections-agent/CLAUDE.md +19 -0
  133. package/templates/collections-agent/agents/collections-agent/README.md +94 -0
  134. package/templates/collections-agent/agents/collections-agent/SYSTEM_PROMPT.md +31 -0
  135. package/templates/collections-agent/agents/collections-agent/agent.yaml +28 -0
  136. package/templates/collections-agent/agents/collections-agent/channels/terminal/index.ts +178 -0
  137. package/templates/collections-agent/agents/collections-agent/channels/webhook/index.ts +131 -0
  138. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap-mandate.json +11 -0
  139. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap.json +12 -0
  140. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
  141. package/templates/collections-agent/agents/collections-agent/evals/adversarial/exfiltration.json +19 -0
  142. package/templates/collections-agent/agents/collections-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  143. package/templates/collections-agent/agents/collections-agent/evals/adversarial/false-authority.json +11 -0
  144. package/templates/collections-agent/agents/collections-agent/evals/adversarial/false-authority.transcript.jsonl +2 -0
  145. package/templates/collections-agent/agents/collections-agent/evals/adversarial/fractioning.json +12 -0
  146. package/templates/collections-agent/agents/collections-agent/evals/adversarial/fractioning.transcript.jsonl +4 -0
  147. package/templates/collections-agent/agents/collections-agent/evals/adversarial/model-total.json +13 -0
  148. package/templates/collections-agent/agents/collections-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
  149. package/templates/collections-agent/agents/collections-agent/evals/adversarial/prompt-injection.json +12 -0
  150. package/templates/collections-agent/agents/collections-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
  151. package/templates/collections-agent/agents/collections-agent/evals/adversarial/webhook-replay.json +11 -0
  152. package/templates/collections-agent/agents/collections-agent/evals/eval.yaml +11 -0
  153. package/templates/collections-agent/agents/collections-agent/guardrails.json +20 -0
  154. package/templates/collections-agent/agents/collections-agent/mandate.example.json +40 -0
  155. package/templates/collections-agent/agents/collections-agent/package.json +29 -0
  156. package/templates/collections-agent/agents/collections-agent/runbook.md +27 -0
  157. package/templates/collections-agent/agents/collections-agent/scenarios/beneficiary-not-allowed.json +42 -0
  158. package/templates/collections-agent/agents/collections-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +3 -0
  159. package/templates/collections-agent/agents/collections-agent/scenarios/cap-exceeded.json +27 -0
  160. package/templates/collections-agent/agents/collections-agent/scenarios/cap-exceeded.transcript.jsonl +3 -0
  161. package/templates/collections-agent/agents/collections-agent/scenarios/charge-expired.json +36 -0
  162. package/templates/collections-agent/agents/collections-agent/scenarios/charge-expired.transcript.jsonl +4 -0
  163. package/templates/collections-agent/agents/collections-agent/scenarios/escalated-above-threshold.json +49 -0
  164. package/templates/collections-agent/agents/collections-agent/scenarios/escalated-above-threshold.transcript.jsonl +5 -0
  165. package/templates/collections-agent/agents/collections-agent/scenarios/happy-path.json +60 -0
  166. package/templates/collections-agent/agents/collections-agent/scenarios/happy-path.transcript.jsonl +4 -0
  167. package/templates/collections-agent/agents/collections-agent/scenarios/instalments.json +49 -0
  168. package/templates/collections-agent/agents/collections-agent/scenarios/instalments.transcript.jsonl +4 -0
  169. package/templates/collections-agent/agents/collections-agent/scenarios/mandate-revoked.json +42 -0
  170. package/templates/collections-agent/agents/collections-agent/scenarios/mandate-revoked.transcript.jsonl +6 -0
  171. package/templates/collections-agent/agents/collections-agent/scenarios/prompt-injection.json +27 -0
  172. package/templates/collections-agent/agents/collections-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
  173. package/templates/collections-agent/agents/collections-agent/src/adversarial.ts +190 -0
  174. package/templates/collections-agent/agents/collections-agent/src/agreements.ts +45 -0
  175. package/templates/collections-agent/agents/collections-agent/src/commands/approve.ts +10 -0
  176. package/templates/collections-agent/agents/collections-agent/src/commands/check.ts +13 -0
  177. package/templates/collections-agent/agents/collections-agent/src/commands/decide.ts +48 -0
  178. package/templates/collections-agent/agents/collections-agent/src/commands/deny.ts +7 -0
  179. package/templates/collections-agent/agents/collections-agent/src/commands/eval.ts +39 -0
  180. package/templates/collections-agent/agents/collections-agent/src/commands/poll.ts +35 -0
  181. package/templates/collections-agent/agents/collections-agent/src/commands/reconcile.ts +38 -0
  182. package/templates/collections-agent/agents/collections-agent/src/commands/rerun.ts +55 -0
  183. package/templates/collections-agent/agents/collections-agent/src/commands/resume.ts +30 -0
  184. package/templates/collections-agent/agents/collections-agent/src/commands/webhook.ts +36 -0
  185. package/templates/collections-agent/agents/collections-agent/src/envelope.ts +103 -0
  186. package/templates/collections-agent/agents/collections-agent/src/main.ts +193 -0
  187. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +105 -0
  188. package/templates/collections-agent/agents/collections-agent/src/poll.ts +94 -0
  189. package/templates/collections-agent/agents/collections-agent/src/scenarios.ts +252 -0
  190. package/templates/collections-agent/agents/collections-agent/src/setup.ts +250 -0
  191. package/templates/collections-agent/agents/collections-agent/test/adversarial.test.ts +45 -0
  192. package/templates/collections-agent/agents/collections-agent/test/check.test.ts +51 -0
  193. package/templates/collections-agent/agents/collections-agent/test/cli.test.ts +172 -0
  194. package/templates/collections-agent/agents/collections-agent/test/envelope.test.ts +53 -0
  195. package/templates/collections-agent/agents/collections-agent/test/fixtures/accept-1042.transcript.jsonl +2 -0
  196. package/templates/collections-agent/agents/collections-agent/test/fixtures/accept-1103.transcript.jsonl +2 -0
  197. package/templates/collections-agent/agents/collections-agent/test/scenarios.test.ts +95 -0
  198. package/templates/collections-agent/agents/collections-agent/test/webhook.test.ts +100 -0
  199. package/templates/collections-agent/agents/collections-agent/tools.json +40 -0
  200. package/templates/collections-agent/agents/collections-agent/tsconfig.json +11 -0
  201. package/templates/collections-agent/package.json +35 -0
  202. package/templates/collections-agent/packages/agent-core/README.md +19 -0
  203. package/templates/collections-agent/packages/agent-core/package.json +24 -0
  204. package/templates/collections-agent/packages/agent-core/src/agent.ts +125 -0
  205. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +154 -0
  206. package/templates/collections-agent/packages/agent-core/src/api/client.ts +53 -0
  207. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
  208. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +101 -0
  209. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +45 -0
  210. package/templates/collections-agent/packages/agent-core/src/approval.ts +109 -0
  211. package/templates/collections-agent/packages/agent-core/src/bundle.ts +113 -0
  212. package/templates/collections-agent/packages/agent-core/src/check.ts +171 -0
  213. package/templates/collections-agent/packages/agent-core/src/engine.ts +784 -0
  214. package/templates/collections-agent/packages/agent-core/src/escalate.ts +85 -0
  215. package/templates/collections-agent/packages/agent-core/src/events.ts +40 -0
  216. package/templates/collections-agent/packages/agent-core/src/guardrails.ts +45 -0
  217. package/templates/collections-agent/packages/agent-core/src/hash.ts +49 -0
  218. package/templates/collections-agent/packages/agent-core/src/ids.ts +10 -0
  219. package/templates/collections-agent/packages/agent-core/src/index.ts +29 -0
  220. package/templates/collections-agent/packages/agent-core/src/mandate.ts +100 -0
  221. package/templates/collections-agent/packages/agent-core/src/manifest.ts +76 -0
  222. package/templates/collections-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  223. package/templates/collections-agent/packages/agent-core/src/providers/replay.ts +63 -0
  224. package/templates/collections-agent/packages/agent-core/src/providers/types.ts +39 -0
  225. package/templates/collections-agent/packages/agent-core/src/rail.ts +87 -0
  226. package/templates/collections-agent/packages/agent-core/src/revocation.ts +30 -0
  227. package/templates/collections-agent/packages/agent-core/src/secrets.ts +30 -0
  228. package/templates/collections-agent/packages/agent-core/src/state/store.ts +307 -0
  229. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +146 -0
  230. package/templates/collections-agent/packages/agent-core/src/stubs/charge-rail.ts +193 -0
  231. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
  232. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +124 -0
  233. package/templates/collections-agent/packages/agent-core/src/tools.ts +55 -0
  234. package/templates/collections-agent/packages/agent-core/src/types.ts +106 -0
  235. package/templates/collections-agent/packages/agent-core/test/approval.test.ts +83 -0
  236. package/templates/collections-agent/packages/agent-core/test/engine.test.ts +524 -0
  237. package/templates/collections-agent/packages/agent-core/test/escalate.test.ts +42 -0
  238. package/templates/collections-agent/packages/agent-core/test/helpers.ts +139 -0
  239. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
  240. package/templates/collections-agent/packages/agent-core/test/manifest.test.ts +45 -0
  241. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +265 -0
  242. package/templates/collections-agent/packages/agent-core/test/secrets.test.ts +28 -0
  243. package/templates/collections-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  244. package/templates/collections-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  245. package/templates/collections-agent/packages/agent-core/tsconfig.json +10 -0
  246. package/templates/collections-agent/tsconfig.base.json +16 -0
  247. package/templates/collections-agent/vitest.config.ts +12 -0
  248. package/templates/kits.lock.json +41 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `npm run reconcile [--json]`: compares local state with the rail. Names
3
+ * executions without an outcome, attempts the rail never saw, receipts the
4
+ * rail sealed that the bundle does not hold. Read-only unless an execution
5
+ * is in `executing`, which it closes from what the rail says.
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { stderr, stdout } from "node:process";
10
+ import { readDotEnv, runsDir, setup } from "../setup.js";
11
+
12
+ const json = process.argv.includes("--json");
13
+ const say = (l: string) => stderr.write(l + "\n");
14
+ readDotEnv();
15
+ const s = setup({ say, runId: `run_reconcile_${Date.now().toString(36)}` });
16
+ try {
17
+ const findings: Array<{ execution_id: string; kind: string; detail: string }> = [];
18
+ for (const e of s.engine.list()) {
19
+ if (e.state === "executing") {
20
+ const closed = await s.engine.reconcile(e.id);
21
+ findings.push({ execution_id: e.id, kind: closed.state === "executing" ? "still_uncertain" : "closed_from_rail", detail: `executing -> ${closed.state}` });
22
+ continue;
23
+ }
24
+ for (const o of e.outcomes) {
25
+ if (o.receipt_id && !existsSync(join(runsDir(), e.run_id, "receipts", `${o.receipt_id}.json`))) {
26
+ findings.push({ execution_id: e.id, kind: "receipt_missing_locally", detail: `receipt ${o.receipt_id} is not in runs/${e.run_id}/receipts` });
27
+ }
28
+ }
29
+ const outbox = s.store.getOutbox(e.idempotency_key);
30
+ if (e.state === "settled" && outbox?.status !== "done") findings.push({ execution_id: e.id, kind: "outbox_mismatch", detail: `settled but outbox is ${outbox?.status ?? "absent"}` });
31
+ }
32
+ for (const f of findings) say(`${f.kind} ${f.execution_id}: ${f.detail}`);
33
+ if (json) stdout.write(JSON.stringify({ executions: s.engine.list().length, findings }) + "\n");
34
+ else say(findings.length ? `${findings.length} finding(s)` : "local state and rail agree");
35
+ process.exit(0);
36
+ } finally {
37
+ s.close();
38
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `npm run rerun <run-id> [--json]`: replays a recorded run against the
3
+ * deterministic provider, with no network, in a fresh state, and compares
4
+ * the sequence of states with the original bundle. The model's outputs are
5
+ * the recording; every decision of the core is recomputed.
6
+ */
7
+ import { mkdtempSync, readFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { stderr, stdout } from "node:process";
11
+ import { ProofBundle, type ApprovalMode, type Execution } from "@codespar/agent-core";
12
+ import { handleExecution } from "../../channels/terminal/index.js";
13
+ import { runsDir, setup } from "../setup.js";
14
+
15
+ const argv = process.argv.slice(2);
16
+ const json = argv.includes("--json");
17
+ const runId = argv.find((a) => !a.startsWith("--"));
18
+ const say = (l: string) => stderr.write(l + "\n");
19
+ if (!runId) {
20
+ say("usage: npm run rerun <run-id> [--json]");
21
+ process.exit(2);
22
+ }
23
+ const original = ProofBundle.open(runsDir(), runId);
24
+ if (!original) {
25
+ say(`no bundle at runs/${runId}`);
26
+ process.exit(1);
27
+ }
28
+ const meta = original.readMeta() ?? {};
29
+ const mode = (meta["mode"] as ApprovalMode | undefined) ?? "human";
30
+ const originalTrail = original
31
+ .readEvents()
32
+ .filter((e) => e["type"] === "execution.transition")
33
+ .map((e) => (e["payload"] as { to: string }).to);
34
+ const originalDecisions = original.readEvents().filter((e) => e["type"] === "approval.created").map((e) => ((e["payload"] as { approver: { type: string } }).approver.type === "person" ? "approve" : "mandate"));
35
+ const userTurns = original.readTranscript().filter((l) => l.kind === "user").map((l) => l["text"] as string);
36
+ // The rail's answers are part of the recording: a payee the rail refused in the original run is refused in the rerun.
37
+ const events = original.readEvents();
38
+ const dispatched = new Map(events.filter((e) => e["type"] === "rail.dispatch").map((e) => [(e["payload"] as { attempt_id: string }).attempt_id, (e["payload"] as { payee: string }).payee]));
39
+ const refusedPayees = events
40
+ .filter((e) => e["type"] === "rail.outcome" && (e["payload"] as { status: string }).status === "failed")
41
+ .map((e) => dispatched.get((e["payload"] as { attempt_id: string }).attempt_id))
42
+ .filter((p): p is string => typeof p === "string");
43
+ const transcriptPath = join(original.dir, "transcript.jsonl");
44
+ readFileSync(transcriptPath, "utf8");
45
+
46
+ const s = setup({ mode, rail: "stub", provider: "replay", transcript: transcriptPath, runId: `${runId}_rerun_${Date.now().toString(36)}`, stateDir: mkdtempSync(join(tmpdir(), "bills-rerun-")), stubRail: { refusePayees: refusedPayees }, say });
47
+ try {
48
+ let decisionIndex = 0;
49
+ const runtime = s.makeRuntime();
50
+ const loop = s.makeLoop(runtime, (execution: Execution) => {
51
+ const decision = execution.state === "awaiting_approval" ? (originalDecisions[decisionIndex++] === "approve" ? "approve" : "deny") : "none";
52
+ return handleExecution(execution, { setup: s, approver: { id: "usr_rerun", channel: "terminal" }, decision, say });
53
+ });
54
+ for (const text of userTurns) await loop.turn(text);
55
+ const trail = s.store.listEvents({ run_id: s.runId }).filter((e) => e.type === "execution.transition").map((e) => (e.payload as { to: string }).to);
56
+ const outcomesOf = (list: Record<string, unknown>[]) => list.filter((e) => e["type"] === "rail.outcome").map((e) => (e["payload"] as { status: string }).status);
57
+ const originalOutcomes = outcomesOf(events);
58
+ const rerunOutcomes = outcomesOf(s.bundle.readEvents());
59
+ const same = JSON.stringify(trail) === JSON.stringify(originalTrail) && JSON.stringify(rerunOutcomes) === JSON.stringify(originalOutcomes);
60
+ if (json) stdout.write(JSON.stringify({ run_id: runId, rerun_id: s.runId, same_states: same, original: originalTrail, rerun: trail, original_outcomes: originalOutcomes, rerun_outcomes: rerunOutcomes, bundle_dir: s.bundle.dir }) + "\n");
61
+ say(same ? `rerun ok: ${trail.length} transition(s), same sequence as ${runId}` : `rerun DIFFERS: original ${JSON.stringify(originalTrail)} vs rerun ${JSON.stringify(trail)}`);
62
+ process.exit(same ? 0 : 1);
63
+ } finally {
64
+ s.close();
65
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `npm run resume [--json]`: after a crash or a restart. Executions left in
3
+ * `executing` are reconciled against the rail (idempotent on attempt_id),
4
+ * never repeated; stale open ones expire.
5
+ */
6
+ import { stderr, stdout } from "node:process";
7
+ import { readDotEnv, setup } from "../setup.js";
8
+
9
+ const json = process.argv.includes("--json");
10
+ const say = (l: string) => stderr.write(l + "\n");
11
+ readDotEnv();
12
+ const s = setup({ say, runId: `run_resume_${Date.now().toString(36)}` });
13
+ try {
14
+ const resumed = [];
15
+ // Only resume dispatches, and only what the outbox proves was never sent; everything else is reconciled from the rail.
16
+ for (const stuck of s.engine.list({ state: "executing" })) {
17
+ const closed = await s.engine.resumePending(stuck.id);
18
+ resumed.push({ id: closed.id, state: closed.state, detail: closed.detail ?? null, receipt_ids: closed.outcomes.filter((o) => o.receipt_id).map((o) => o.receipt_id) });
19
+ say(closed.state === "executing" ? `${closed.id}: still executing — ${closed.detail}; nothing was re-sent` : `${closed.id}: executing -> ${closed.state}`);
20
+ }
21
+ const expired = s.engine.expireStale().map((e) => ({ id: e.id, state: e.state }));
22
+ for (const e of expired) say(`${e.id}: -> expired`);
23
+ const open = s.engine.list({ state: ["awaiting_approval", "approved"] }).map((e) => ({ id: e.id, state: e.state }));
24
+ for (const e of open) say(`${e.id}: still ${e.state}; decide it with npm run approve ${e.id} / npm run deny ${e.id}`);
25
+ if (json) stdout.write(JSON.stringify({ resumed, expired, open, bundle_dir: s.bundle.dir }) + "\n");
26
+ else say(resumed.length || expired.length ? "resume done" : "nothing to resume");
27
+ process.exit(resumed.some((r) => r.state === "executing") ? 3 : 0);
28
+ } finally {
29
+ s.close();
30
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * `npm start` interactive terminal
3
+ * `npm start -- --input "pague a escola de outubro"` one turn, no prompt
4
+ * `npm start -- --scenario happy-path` a scenario pack, both modes it declares
5
+ *
6
+ * `--json` puts machine data on stdout (valid JSON, nothing else) and the
7
+ * human messages on stderr.
8
+ */
9
+ import { stderr, stdout } from "node:process";
10
+ import { relative, resolve } from "node:path";
11
+ import { NotATestKeyError, isTestKey } from "@codespar/agent-core";
12
+ import { closeTerminal, defaultAsk, handleExecution, interactive } from "../channels/terminal/index.js";
13
+ import { runEmbeddedConsent, loadLocalMandate } from "./modules/embedded-consent.js";
14
+ import { checkScenario, listScenarios, loadScenario, runScenario, scenariosDir } from "./scenarios.js";
15
+ import { AGENT_DIR, MANDATE_PATH, NoMandateError, readDotEnv, resolveRailKind, setup, type RailKind } from "./setup.js";
16
+ import { loadMandate, createCodeSparClient } from "@codespar/agent-core";
17
+
18
+ interface Args {
19
+ input?: string;
20
+ scenario?: string;
21
+ mode?: "human" | "mandate";
22
+ json: boolean;
23
+ decision?: "approve" | "deny";
24
+ provider?: "anthropic" | "replay";
25
+ transcript?: string;
26
+ rail?: RailKind;
27
+ user: string;
28
+ help: boolean;
29
+ }
30
+
31
+ export function parseArgs(argv: string[]): Args {
32
+ const args: Args = { json: false, user: "usr_terminal", help: false };
33
+ for (let i = 0; i < argv.length; i += 1) {
34
+ const a = argv[i]!;
35
+ const next = () => {
36
+ const v = argv[i + 1];
37
+ if (v === undefined) throw new Error(`${a} needs a value`);
38
+ i += 1;
39
+ return v;
40
+ };
41
+ if (a === "--input") args.input = next();
42
+ else if (a === "--scenario") args.scenario = next();
43
+ else if (a === "--mode") {
44
+ const v = next();
45
+ if (v !== "human" && v !== "mandate") throw new Error("--mode must be human or mandate");
46
+ args.mode = v;
47
+ } else if (a === "--json") args.json = true;
48
+ else if (a === "--approve") args.decision = "approve";
49
+ else if (a === "--deny") args.decision = "deny";
50
+ else if (a === "--provider") {
51
+ const v = next();
52
+ if (v !== "anthropic" && v !== "replay") throw new Error("--provider must be anthropic or replay");
53
+ args.provider = v;
54
+ } else if (a === "--transcript") args.transcript = next();
55
+ else if (a === "--rail") {
56
+ const v = next();
57
+ if (v !== "stub" && v !== "api") throw new Error("--rail must be stub or api");
58
+ args.rail = v;
59
+ } else if (a === "--user") args.user = next();
60
+ else if (a === "--help" || a === "-h") args.help = true;
61
+ else throw new Error(`unknown argument ${a}`);
62
+ }
63
+ return args;
64
+ }
65
+
66
+ const USAGE = `bills-agent
67
+ npm start interactive terminal
68
+ npm start -- --input "pague a escola de outubro" one turn (add --approve/--deny to decide, --json for machine output)
69
+ npm start -- --scenario <name> run a scenario pack (${"see scenarios/"})
70
+ options: --mode human|mandate --provider anthropic|replay --transcript <file> --rail stub|api --user <id> --json`;
71
+
72
+ export async function main(argv = process.argv.slice(2)): Promise<number> {
73
+ let args: Args;
74
+ try {
75
+ args = parseArgs(argv);
76
+ } catch (err) {
77
+ stderr.write(`${err instanceof Error ? err.message : String(err)}\n${USAGE}\n`);
78
+ return 2;
79
+ }
80
+ if (args.help) {
81
+ stderr.write(USAGE + "\n");
82
+ return 0;
83
+ }
84
+ readDotEnv();
85
+ const say = (line: string) => stderr.write(line + "\n");
86
+
87
+ if (args.scenario) return runScenarioCommand(args, say);
88
+
89
+ // Sandbox by construction: a live key dies here, before anything else runs.
90
+ const railKind = resolveRailKind(process.env, args.rail);
91
+ if (railKind === "api" && !isTestKey(process.env["CODESPAR_API_KEY"])) {
92
+ say(new NotATestKeyError().message);
93
+ return 1;
94
+ }
95
+
96
+ // Embedded consent: with a test key and no signed mandate, the mandate is born first.
97
+ if (railKind === "api" && !loadLocalMandate(MANDATE_PATH)) {
98
+ if (args.input) {
99
+ say(new NoMandateError().message);
100
+ return 1;
101
+ }
102
+ const api = createCodeSparClient({ apiKey: process.env["CODESPAR_API_KEY"], baseUrl: process.env["CODESPAR_API_URL"], projectId: process.env["CODESPAR_PROJECT_ID"] });
103
+ const example = loadMandate(resolve(AGENT_DIR, "mandate.example.json"));
104
+ await runEmbeddedConsent({ api, example, mandatePath: MANDATE_PATH, say, confirm: async (q: string) => /^(s|sim|y|yes)$/i.test((await defaultAsk(q)).trim()) });
105
+ }
106
+
107
+ // One-shot without a model: replay the recorded happy-path when the input is its first turn.
108
+ let transcript = args.transcript;
109
+ const provider = args.provider ?? (process.env["ANTHROPIC_API_KEY"] && process.env["ANTHROPIC_API_KEY"] !== "sk-ant-your_key_here" ? "anthropic" : "replay");
110
+ if (provider === "replay" && !transcript && args.input) {
111
+ const match = listScenarios().map(loadScenario).find((s) => s.turns[0].input === args.input);
112
+ if (match) transcript = resolve(scenariosDir(), match.transcript);
113
+ else {
114
+ say(`no ANTHROPIC_API_KEY and no recorded transcript for that input. Set the key, pass --transcript, or use one of: ${listScenarios().map((n) => `"${loadScenario(n).turns[0].input}"`).join(", ")}`);
115
+ return 1;
116
+ }
117
+ }
118
+
119
+ const s = setup({ mode: args.mode, rail: railKind, provider, transcript, say });
120
+ const approver = { id: args.user, channel: "terminal" };
121
+ try {
122
+ const runtime = s.makeRuntime();
123
+ if (!args.input) {
124
+ await interactive({ setup: s, approver, runtime, say });
125
+ return 0;
126
+ }
127
+ const loop = s.makeLoop(runtime, (execution) => handleExecution(execution, { setup: s, approver, decision: args.decision ?? "none", say }));
128
+ const result = await loop.turn(args.input);
129
+ const executions = s.engine.list().filter((e) => e.run_id === s.runId);
130
+ const payload = {
131
+ run_id: s.runId,
132
+ agent: `${s.manifest.manifest.name}@${s.manifest.manifest.version}`,
133
+ mode: s.mode,
134
+ rail: s.railKind,
135
+ mandate_id: s.mandate.id,
136
+ actor: s.engine.agentActor,
137
+ reply: result.reply,
138
+ tool_calls: result.tool_calls,
139
+ executions: executions.map((e) => ({
140
+ id: e.id,
141
+ state: e.state,
142
+ reason: e.reason ?? null,
143
+ escalation: e.escalation ?? null,
144
+ total_minor: e.total,
145
+ items: e.items.map((i) => ({ beneficiary: i.beneficiary, amount_minor: i.amount })),
146
+ approval_id: e.approval_id ?? null,
147
+ receipt_ids: e.outcomes.filter((o) => o.receipt_id).map((o) => o.receipt_id),
148
+ })),
149
+ receipts: s.bundle.listReceipts().map((f) => relative(process.cwd(), `${s.bundle.dir}/receipts/${f}`)),
150
+ bundle_dir: relative(process.cwd(), s.bundle.dir),
151
+ };
152
+ if (args.json) stdout.write(JSON.stringify(payload) + "\n");
153
+ else stdout.write(result.reply + "\n");
154
+ return executions.some((e) => e.state === "executing") ? 3 : 0;
155
+ } finally {
156
+ closeTerminal();
157
+ s.close();
158
+ }
159
+ }
160
+
161
+ async function runScenarioCommand(args: Args, say: (l: string) => void): Promise<number> {
162
+ const scenario = loadScenario(args.scenario!);
163
+ const modes = args.mode ? [args.mode] : scenario.modes;
164
+ const results = [];
165
+ for (const mode of modes) {
166
+ say(`== scenario ${scenario.name} — approval: ${mode}`);
167
+ say(scenario.description);
168
+ const run = await runScenario(scenario, { mode, say: args.json ? () => undefined : say });
169
+ const check = checkScenario(scenario, run);
170
+ for (const reply of run.replies) say(`agente: ${reply}`);
171
+ say(check.ok ? `== ok — bundle em ${relative(process.cwd(), run.bundle_dir)}` : `== FAIL: ${check.failures.join("; ")}`);
172
+ results.push({ mode, ok: check.ok, failures: check.failures, run });
173
+ }
174
+ if (args.json) stdout.write(JSON.stringify({ scenario: scenario.name, results }) + "\n");
175
+ return results.every((r) => r.ok) ? 0 : 1;
176
+ }
177
+
178
+ if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
179
+ main()
180
+ .then((code) => process.exit(code))
181
+ .catch((err) => {
182
+ stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
183
+ process.exit(1);
184
+ });
185
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Module `embedded-consent`: the mandate is born at a consent the CONSUMER
3
+ * authorizes. In the sandbox the kit runs the `partner` surface: it is the
4
+ * partner's backend, the titular is at the keyboard, and the submit carries
5
+ * an `in_person` attestation. That is what hands the kit the signed envelope
6
+ * `{ mandate, signature }`, which `POST /v1/consumer-payments/execute`
7
+ * presents on every spend. The `hosted` surface (a page the consumer opens)
8
+ * returns the envelope to a `callback_url` a terminal kit does not have;
9
+ * see docs/OPEN_QUESTIONS.md.
10
+ *
11
+ * With a test key and no `.codespar/mandate.json`, `npm start` runs this
12
+ * first. The stored file carries the signature: mode 0600, never in the
13
+ * bundle, never committed.
14
+ */
15
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { dirname } from "node:path";
17
+ import type { ApiClient } from "@codespar/sdk";
18
+ import { MandateSchema, windowCap, type Mandate, describeApiError } from "@codespar/agent-core";
19
+
20
+ export interface ConsentOptions {
21
+ api: ApiClient;
22
+ example: Mandate;
23
+ mandatePath: string;
24
+ /** Where to write the lines the person reads. */
25
+ say: (line: string) => void;
26
+ /** Asks the titular to authorize. Returns true on yes. */
27
+ confirm: (question: string) => Promise<boolean>;
28
+ /** The consumer this mandate is for. Defaults to the example's. */
29
+ consumerId?: string;
30
+ now?: () => Date;
31
+ }
32
+
33
+ const YEAR_SECONDS = 365 * 24 * 3600;
34
+
35
+ export function loadLocalMandate(path: string): Mandate | undefined {
36
+ if (!existsSync(path)) return undefined;
37
+ return MandateSchema.parse(JSON.parse(readFileSync(path, "utf8")));
38
+ }
39
+
40
+ export async function runEmbeddedConsent(options: ConsentOptions): Promise<Mandate> {
41
+ const { api, example, say } = options;
42
+ const now = options.now ?? (() => new Date());
43
+ const consumerId = options.consumerId ?? example.consumer_id;
44
+
45
+ const init = await api.post("/v1/consents/init", {
46
+ body: {
47
+ agent_id: example.agent_id,
48
+ intent: {
49
+ purpose: example.purpose,
50
+ cap_minor: example.cap_minor,
51
+ per_tx_cap_minor: example.per_tx_cap_minor,
52
+ currency: "BRL",
53
+ mandate_ttl_seconds: YEAR_SECONDS,
54
+ merchant_allowlist: example.beneficiaries.map((b) => b.payee),
55
+ merchant_pin_kind: "pix-key",
56
+ ...(example.periodic_cap ? { periodic_cap: example.periodic_cap } : {}),
57
+ display_name: "bills-agent: contas do mes",
58
+ intent_note: `Pagar as contas do mes aos favorecidos nomeados: ${example.beneficiaries.map((b) => b.name).join(", ")}.`,
59
+ },
60
+ surface: "partner",
61
+ },
62
+ });
63
+
64
+ say("");
65
+ say("O mandato ainda nao existe. Este e o consentimento que o titular assina (sandbox):");
66
+ say(` agente: ${example.agent_id} finalidade: ${example.purpose}`);
67
+ say(` teto por pagamento: ${example.per_tx_cap_minor} centavos teto do mes: ${windowCap(example)} centavos vitalicio: ${example.cap_minor} centavos`);
68
+ for (const b of example.beneficiaries) say(` favorecido: ${b.name} (${b.alias})`);
69
+ say(` validade: 1 ano (o token do consentimento vale ate ${init.expires_at})`);
70
+ const yes = await options.confirm(" Voce e o titular e autoriza este mandato? [s/N] ");
71
+ if (!yes) throw new Error("consent not authorized by the titular; nothing was created");
72
+
73
+ // The consumer's "browser" is this terminal: the submit is the same call the hosted page makes,
74
+ // plus the attestation the partner surface requires. Sandbox rails accept a placeholder token.
75
+ const submitted = (await api.request("post", "/v1/consents/{token}/submit" as never, {
76
+ path: { token: init.token },
77
+ body: {
78
+ consumer_id: consumerId,
79
+ rail: "pix-consent",
80
+ provider_token: `sandbox-pix-consent-${consumerId}`,
81
+ display_label: "bills-agent (sandbox)",
82
+ attestation: { method: "in_person", asserted_at: Math.floor(now().getTime() / 1000), reference: "terminal" },
83
+ },
84
+ } as never)) as { mandate_id: string; mandate: Record<string, unknown>; signature: string };
85
+
86
+ const signed = submitted.mandate;
87
+ const mandate = MandateSchema.parse({
88
+ id: submitted.mandate_id,
89
+ version: 1,
90
+ consumer_id: String(signed["consumer_id"]),
91
+ agent_id: String(signed["agent_id"]),
92
+ purpose: String(signed["purpose"]),
93
+ currency: String(signed["currency"]),
94
+ cap_minor: Number(signed["cap_minor"]),
95
+ per_tx_cap_minor: Number(signed["per_tx_cap_minor"]),
96
+ ...(signed["periodic_cap"] ? { periodic_cap: signed["periodic_cap"] } : {}),
97
+ merchant_pin_kind: String(signed["merchant_pin_kind"]),
98
+ merchant_allowlist: signed["merchant_allowlist"],
99
+ beneficiaries: example.beneficiaries.filter((b) => (signed["merchant_allowlist"] as string[]).includes(b.payee)),
100
+ status: "active",
101
+ expires_at: new Date(Number(signed["expires_at"]) * 1000).toISOString(),
102
+ signature: submitted.signature,
103
+ canonical: signed,
104
+ source: "consent",
105
+ });
106
+
107
+ mkdirSync(dirname(options.mandatePath), { recursive: true });
108
+ writeFileSync(options.mandatePath, JSON.stringify(mandate, null, 2) + "\n", { mode: 0o600 });
109
+ chmodSync(options.mandatePath, 0o600);
110
+ say(`Mandato assinado: ${mandate.id} (consumidor ${mandate.consumer_id}).`);
111
+
112
+ await fundSandbox(api, mandate, say);
113
+ return mandate;
114
+ }
115
+
116
+ /** Best effort: the sandbox account is credited with one window's cap. A `pix-consent` source has no Celcoin account to credit, and the sandbox spend does not need one. */
117
+ export async function fundSandbox(api: ApiClient, mandate: Mandate, say: (line: string) => void): Promise<void> {
118
+ try {
119
+ const funded = await api.post("/v1/test/fund", { body: { consumer_id: mandate.consumer_id, amount_minor: windowCap(mandate) } });
120
+ say(`Sandbox creditado: ${funded.amount_minor} centavos em ${funded.account} (deposit ${funded.deposit_id}).`);
121
+ } catch (err) {
122
+ const f = describeApiError(err);
123
+ say(`Sandbox nao creditado (${f.code}); o gasto de teste sob pix-consent nao depende disso.`);
124
+ }
125
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Module `pix-out`: the tool handlers the model can reach. `codespar_pay`
3
+ * hands the proposal to the engine and gets an execution in `drafted` (then
4
+ * whatever the core decided); it cannot pay. `codespar_ledger` and
5
+ * `list_bills` are reads.
6
+ */
7
+ import type { ToolHandler } from "@codespar/agent-core";
8
+ import { formatBRL } from "../bills.js";
9
+ import { BILLS, MONTH } from "../bills.js";
10
+
11
+ interface PayInput {
12
+ action?: unknown;
13
+ items?: unknown;
14
+ total_minor?: unknown;
15
+ }
16
+
17
+ export const listBills: ToolHandler = async (_input, ctx) => {
18
+ const settled = ctx.engine.list({ state: "settled" });
19
+ const paidAliases = new Set(settled.flatMap((e) => e.items.map((i) => i.alias)).filter(Boolean));
20
+ return {
21
+ month: MONTH,
22
+ bills: BILLS.map((b) => ({ ...b, amount: formatBRL(b.amount_minor), paid_this_window: paidAliases.has(b.alias) })),
23
+ payees: ctx.engine.mandate.beneficiaries.map((b) => ({ alias: b.alias, name: b.name })),
24
+ };
25
+ };
26
+
27
+ export const codesparPay: ToolHandler = async (raw, ctx) => {
28
+ const input = raw as PayInput;
29
+ if (input.action !== "pix") throw new Error(`codespar_pay: unsupported action ${String(input.action)}; this agent only does pix`);
30
+ if (!Array.isArray(input.items) || input.items.length === 0) throw new Error("codespar_pay: items must be a non-empty array");
31
+ const items = input.items.map((item, i) => {
32
+ const it = item as { payee?: unknown; amount_minor?: unknown; description?: unknown };
33
+ if (typeof it.payee !== "string" || !it.payee.trim()) throw new Error(`codespar_pay: items[${i}].payee must be a payee alias`);
34
+ if (typeof it.amount_minor !== "number" || !Number.isInteger(it.amount_minor) || it.amount_minor <= 0) throw new Error(`codespar_pay: items[${i}].amount_minor must be a positive integer`);
35
+ return { payee: it.payee, amount: it.amount_minor, ...(typeof it.description === "string" ? { description: it.description } : {}) };
36
+ });
37
+ const claimed = typeof input.total_minor === "number" ? { claimed_total: input.total_minor } : {};
38
+
39
+ const draft = await ctx.engine.draft({ items, ...claimed });
40
+ if (!draft.ok) {
41
+ return { status: "refused", reason: draft.reason, message: draft.message, paid: false };
42
+ }
43
+ const execution = await ctx.onExecution(draft.execution);
44
+ return {
45
+ status: execution.state,
46
+ execution_id: execution.id,
47
+ total_minor: execution.total,
48
+ total: formatBRL(execution.total),
49
+ items: execution.items.map((i) => ({ payee: i.beneficiary, amount: formatBRL(i.amount) })),
50
+ ...(execution.reason ? { reason: execution.reason, detail: execution.detail } : {}),
51
+ ...(execution.escalation ? { escalated_by: execution.escalation.trigger } : {}),
52
+ receipt_ids: execution.outcomes.filter((o) => o.receipt_id).map((o) => o.receipt_id),
53
+ paid: execution.state === "settled",
54
+ };
55
+ };
56
+
57
+ export const codesparLedger: ToolHandler = async (raw, ctx) => {
58
+ const input = raw as { action?: unknown; receipt_id?: unknown };
59
+ if (input.action === "executions") {
60
+ return {
61
+ executions: ctx.engine.list().map((e) => ({ id: e.id, state: e.state, total: formatBRL(e.total), reason: e.reason ?? null, receipt_ids: e.outcomes.filter((o) => o.receipt_id).map((o) => o.receipt_id) })),
62
+ };
63
+ }
64
+ if (input.action === "receipt") {
65
+ if (typeof input.receipt_id !== "string") throw new Error("codespar_ledger: receipt_id is required for action=receipt");
66
+ const owner = ctx.engine.list().find((e) => e.outcomes.some((o) => o.receipt_id === input.receipt_id));
67
+ if (!owner) return { found: false, message: "no receipt with that id in this mandate's executions" };
68
+ const outcome = owner.outcomes.find((o) => o.receipt_id === input.receipt_id)!;
69
+ const item = owner.items[outcome.index];
70
+ return { found: true, receipt_id: outcome.receipt_id, state: owner.state, payee: item?.beneficiary, amount: item ? formatBRL(item.amount) : null, mandate_id: owner.mandate.id };
71
+ }
72
+ throw new Error(`codespar_ledger: unsupported action ${String(input.action)}`);
73
+ };