arc402-cli 0.9.19 → 0.10.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 (359) hide show
  1. package/README.md +41 -2
  2. package/dist/abis.d.ts +1 -0
  3. package/dist/abis.d.ts.map +1 -1
  4. package/dist/abis.js +45 -14
  5. package/dist/abis.js.map +1 -1
  6. package/dist/bundler.d.ts +1 -1
  7. package/dist/bundler.d.ts.map +1 -1
  8. package/dist/bundler.js +61 -27
  9. package/dist/bundler.js.map +1 -1
  10. package/dist/client.d.ts +1 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +9 -5
  13. package/dist/client.js.map +1 -1
  14. package/dist/coinbase-smart-wallet.js +4 -1
  15. package/dist/coinbase-smart-wallet.js.map +1 -1
  16. package/dist/commands/accept.js +28 -25
  17. package/dist/commands/accept.js.map +1 -1
  18. package/dist/commands/agent-handshake.js +18 -15
  19. package/dist/commands/agent-handshake.js.map +1 -1
  20. package/dist/commands/agent.js +104 -98
  21. package/dist/commands/agent.js.map +1 -1
  22. package/dist/commands/agreements.js +98 -62
  23. package/dist/commands/agreements.js.map +1 -1
  24. package/dist/commands/arbitrator.js +81 -45
  25. package/dist/commands/arbitrator.js.map +1 -1
  26. package/dist/commands/arena-handshake.d.ts.map +1 -1
  27. package/dist/commands/arena-handshake.js +35 -53
  28. package/dist/commands/arena-handshake.js.map +1 -1
  29. package/dist/commands/arena.js +18 -12
  30. package/dist/commands/arena.js.map +1 -1
  31. package/dist/commands/backup.js +36 -30
  32. package/dist/commands/backup.js.map +1 -1
  33. package/dist/commands/cancel.js +18 -15
  34. package/dist/commands/cancel.js.map +1 -1
  35. package/dist/commands/channel.js +81 -45
  36. package/dist/commands/channel.js.map +1 -1
  37. package/dist/commands/coldstart.js +34 -31
  38. package/dist/commands/coldstart.js.map +1 -1
  39. package/dist/commands/compute.d.ts +14 -0
  40. package/dist/commands/compute.d.ts.map +1 -0
  41. package/dist/commands/compute.js +466 -0
  42. package/dist/commands/compute.js.map +1 -0
  43. package/dist/commands/config.js +30 -24
  44. package/dist/commands/config.js.map +1 -1
  45. package/dist/commands/contract-interaction.js +15 -12
  46. package/dist/commands/contract-interaction.js.map +1 -1
  47. package/dist/commands/daemon.d.ts.map +1 -1
  48. package/dist/commands/daemon.js +135 -98
  49. package/dist/commands/daemon.js.map +1 -1
  50. package/dist/commands/deliver.js +76 -37
  51. package/dist/commands/deliver.js.map +1 -1
  52. package/dist/commands/discover.js +27 -24
  53. package/dist/commands/discover.js.map +1 -1
  54. package/dist/commands/dispute.js +110 -104
  55. package/dist/commands/dispute.js.map +1 -1
  56. package/dist/commands/doctor.js +55 -16
  57. package/dist/commands/doctor.js.map +1 -1
  58. package/dist/commands/endpoint.js +95 -56
  59. package/dist/commands/endpoint.js.map +1 -1
  60. package/dist/commands/feed.js +18 -11
  61. package/dist/commands/feed.js.map +1 -1
  62. package/dist/commands/hire.js +40 -37
  63. package/dist/commands/hire.js.map +1 -1
  64. package/dist/commands/migrate.js +33 -30
  65. package/dist/commands/migrate.js.map +1 -1
  66. package/dist/commands/negotiate.d.ts.map +1 -1
  67. package/dist/commands/negotiate.js +36 -34
  68. package/dist/commands/negotiate.js.map +1 -1
  69. package/dist/commands/openshell.js +104 -68
  70. package/dist/commands/openshell.js.map +1 -1
  71. package/dist/commands/owner.js +20 -17
  72. package/dist/commands/owner.js.map +1 -1
  73. package/dist/commands/policy.js +43 -41
  74. package/dist/commands/policy.js.map +1 -1
  75. package/dist/commands/relay.d.ts.map +1 -1
  76. package/dist/commands/relay.js +51 -18
  77. package/dist/commands/relay.js.map +1 -1
  78. package/dist/commands/remediate.js +23 -20
  79. package/dist/commands/remediate.js.map +1 -1
  80. package/dist/commands/reputation.js +27 -25
  81. package/dist/commands/reputation.js.map +1 -1
  82. package/dist/commands/setup.js +104 -65
  83. package/dist/commands/setup.js.map +1 -1
  84. package/dist/commands/trust.js +20 -17
  85. package/dist/commands/trust.js.map +1 -1
  86. package/dist/commands/verify.js +21 -18
  87. package/dist/commands/verify.js.map +1 -1
  88. package/dist/commands/wallet.d.ts.map +1 -1
  89. package/dist/commands/wallet.js +645 -679
  90. package/dist/commands/wallet.js.map +1 -1
  91. package/dist/commands/watch.js +36 -33
  92. package/dist/commands/watch.js.map +1 -1
  93. package/dist/commands/watchtower.js +73 -37
  94. package/dist/commands/watchtower.js.map +1 -1
  95. package/dist/commands/workroom.d.ts.map +1 -1
  96. package/dist/commands/workroom.js +282 -143
  97. package/dist/commands/workroom.js.map +1 -1
  98. package/dist/config.d.ts +3 -0
  99. package/dist/config.d.ts.map +1 -1
  100. package/dist/config.js +71 -22
  101. package/dist/config.js.map +1 -1
  102. package/dist/daemon/compute-metering.d.ts +61 -0
  103. package/dist/daemon/compute-metering.d.ts.map +1 -0
  104. package/dist/daemon/compute-metering.js +299 -0
  105. package/dist/daemon/compute-metering.js.map +1 -0
  106. package/dist/daemon/compute-session.d.ts +100 -0
  107. package/dist/daemon/compute-session.d.ts.map +1 -0
  108. package/dist/daemon/compute-session.js +231 -0
  109. package/dist/daemon/compute-session.js.map +1 -0
  110. package/dist/daemon/config.d.ts +19 -1
  111. package/dist/daemon/config.d.ts.map +1 -1
  112. package/dist/daemon/config.js +90 -16
  113. package/dist/daemon/config.js.map +1 -1
  114. package/dist/daemon/credentials.d.ts +24 -0
  115. package/dist/daemon/credentials.d.ts.map +1 -0
  116. package/dist/daemon/credentials.js +80 -0
  117. package/dist/daemon/credentials.js.map +1 -0
  118. package/dist/daemon/delivery-client.d.ts +35 -0
  119. package/dist/daemon/delivery-client.d.ts.map +1 -0
  120. package/dist/daemon/delivery-client.js +231 -0
  121. package/dist/daemon/delivery-client.js.map +1 -0
  122. package/dist/daemon/file-delivery.d.ts +98 -0
  123. package/dist/daemon/file-delivery.d.ts.map +1 -0
  124. package/dist/daemon/file-delivery.js +461 -0
  125. package/dist/daemon/file-delivery.js.map +1 -0
  126. package/dist/daemon/hire-listener.d.ts +3 -3
  127. package/dist/daemon/hire-listener.d.ts.map +1 -1
  128. package/dist/daemon/hire-listener.js +47 -13
  129. package/dist/daemon/hire-listener.js.map +1 -1
  130. package/dist/daemon/index.d.ts +2 -1
  131. package/dist/daemon/index.d.ts.map +1 -1
  132. package/dist/daemon/index.js +526 -53
  133. package/dist/daemon/index.js.map +1 -1
  134. package/dist/daemon/job-lifecycle.d.ts +1 -1
  135. package/dist/daemon/job-lifecycle.d.ts.map +1 -1
  136. package/dist/daemon/job-lifecycle.js +51 -11
  137. package/dist/daemon/job-lifecycle.js.map +1 -1
  138. package/dist/daemon/notify.d.ts +1 -1
  139. package/dist/daemon/notify.d.ts.map +1 -1
  140. package/dist/daemon/notify.js +53 -19
  141. package/dist/daemon/notify.js.map +1 -1
  142. package/dist/daemon/token-metering.js +47 -8
  143. package/dist/daemon/token-metering.js.map +1 -1
  144. package/dist/daemon/userops.d.ts +2 -2
  145. package/dist/daemon/userops.d.ts.map +1 -1
  146. package/dist/daemon/userops.js +27 -23
  147. package/dist/daemon/userops.js.map +1 -1
  148. package/dist/daemon/wallet-monitor.d.ts +1 -1
  149. package/dist/daemon/wallet-monitor.d.ts.map +1 -1
  150. package/dist/daemon/wallet-monitor.js +12 -8
  151. package/dist/daemon/wallet-monitor.js.map +1 -1
  152. package/dist/daemon/worker-executor.d.ts +71 -0
  153. package/dist/daemon/worker-executor.d.ts.map +1 -0
  154. package/dist/daemon/worker-executor.js +382 -0
  155. package/dist/daemon/worker-executor.js.map +1 -0
  156. package/dist/drain-v4.js +64 -26
  157. package/dist/drain-v4.js.map +1 -1
  158. package/dist/endpoint-config.js +63 -20
  159. package/dist/endpoint-config.js.map +1 -1
  160. package/dist/endpoint-notify.d.ts.map +1 -1
  161. package/dist/endpoint-notify.js +49 -15
  162. package/dist/endpoint-notify.js.map +1 -1
  163. package/dist/index.js +50 -18
  164. package/dist/index.js.map +1 -1
  165. package/dist/openshell-runtime.d.ts.map +1 -1
  166. package/dist/openshell-runtime.js +82 -38
  167. package/dist/openshell-runtime.js.map +1 -1
  168. package/dist/program.d.ts.map +1 -1
  169. package/dist/program.js +85 -78
  170. package/dist/program.js.map +1 -1
  171. package/dist/repl.js +31 -25
  172. package/dist/repl.js.map +1 -1
  173. package/dist/signing.js +6 -3
  174. package/dist/signing.js.map +1 -1
  175. package/dist/telegram-notify.js +40 -3
  176. package/dist/telegram-notify.js.map +1 -1
  177. package/dist/tui/App.d.ts.map +1 -1
  178. package/dist/tui/App.js +56 -89
  179. package/dist/tui/App.js.map +1 -1
  180. package/dist/tui/Footer.js +7 -4
  181. package/dist/tui/Footer.js.map +1 -1
  182. package/dist/tui/Header.d.ts +1 -1
  183. package/dist/tui/Header.d.ts.map +1 -1
  184. package/dist/tui/Header.js +14 -9
  185. package/dist/tui/Header.js.map +1 -1
  186. package/dist/tui/InputLine.d.ts +2 -1
  187. package/dist/tui/InputLine.d.ts.map +1 -1
  188. package/dist/tui/InputLine.js +47 -97
  189. package/dist/tui/InputLine.js.map +1 -1
  190. package/dist/tui/Viewport.d.ts +1 -2
  191. package/dist/tui/Viewport.d.ts.map +1 -1
  192. package/dist/tui/Viewport.js +26 -6
  193. package/dist/tui/Viewport.js.map +1 -1
  194. package/dist/tui/WalletConnectPairing.js +19 -16
  195. package/dist/tui/WalletConnectPairing.js.map +1 -1
  196. package/dist/tui/components/Button.js +9 -6
  197. package/dist/tui/components/Button.js.map +1 -1
  198. package/dist/tui/components/CeremonyView.js +8 -5
  199. package/dist/tui/components/CeremonyView.js.map +1 -1
  200. package/dist/tui/components/CompletionDropdown.js +9 -6
  201. package/dist/tui/components/CompletionDropdown.js.map +1 -1
  202. package/dist/tui/components/ConfirmPrompt.js +8 -5
  203. package/dist/tui/components/ConfirmPrompt.js.map +1 -1
  204. package/dist/tui/components/CustomTextInput.js +14 -11
  205. package/dist/tui/components/CustomTextInput.js.map +1 -1
  206. package/dist/tui/components/InteractiveTable.js +12 -9
  207. package/dist/tui/components/InteractiveTable.js.map +1 -1
  208. package/dist/tui/components/StepSpinner.js +13 -10
  209. package/dist/tui/components/StepSpinner.js.map +1 -1
  210. package/dist/tui/components/Toast.js +12 -8
  211. package/dist/tui/components/Toast.js.map +1 -1
  212. package/dist/tui/index.d.ts.map +1 -1
  213. package/dist/tui/index.js +21 -28
  214. package/dist/tui/index.js.map +1 -1
  215. package/dist/tui/useChat.js +19 -13
  216. package/dist/tui/useChat.js.map +1 -1
  217. package/dist/tui/useCommand.d.ts +2 -3
  218. package/dist/tui/useCommand.d.ts.map +1 -1
  219. package/dist/tui/useCommand.js +24 -100
  220. package/dist/tui/useCommand.js.map +1 -1
  221. package/dist/tui/useNotifications.js +8 -5
  222. package/dist/tui/useNotifications.js.map +1 -1
  223. package/dist/tui/useScroll.d.ts.map +1 -1
  224. package/dist/tui/useScroll.js +12 -15
  225. package/dist/tui/useScroll.js.map +1 -1
  226. package/dist/ui/banner.d.ts +0 -12
  227. package/dist/ui/banner.d.ts.map +1 -1
  228. package/dist/ui/banner.js +19 -35
  229. package/dist/ui/banner.js.map +1 -1
  230. package/dist/ui/colors.js +19 -13
  231. package/dist/ui/colors.js.map +1 -1
  232. package/dist/ui/format.js +14 -6
  233. package/dist/ui/format.js.map +1 -1
  234. package/dist/ui/qr-render.js +11 -4
  235. package/dist/ui/qr-render.js.map +1 -1
  236. package/dist/ui/rpc-fallback.js +11 -6
  237. package/dist/ui/rpc-fallback.js.map +1 -1
  238. package/dist/ui/spinner.js +12 -6
  239. package/dist/ui/spinner.js.map +1 -1
  240. package/dist/ui/tree.js +6 -3
  241. package/dist/ui/tree.js.map +1 -1
  242. package/dist/utils/format.js +41 -27
  243. package/dist/utils/format.js.map +1 -1
  244. package/dist/utils/hash.js +42 -4
  245. package/dist/utils/hash.js.map +1 -1
  246. package/dist/utils/time.js +6 -2
  247. package/dist/utils/time.js.map +1 -1
  248. package/dist/wallet-router.d.ts +1 -1
  249. package/dist/wallet-router.d.ts.map +1 -1
  250. package/dist/wallet-router.js +19 -12
  251. package/dist/wallet-router.js.map +1 -1
  252. package/dist/walletconnect-session.d.ts +1 -1
  253. package/dist/walletconnect-session.d.ts.map +1 -1
  254. package/dist/walletconnect-session.js +11 -6
  255. package/dist/walletconnect-session.js.map +1 -1
  256. package/dist/walletconnect.d.ts +5 -6
  257. package/dist/walletconnect.d.ts.map +1 -1
  258. package/dist/walletconnect.js +35 -32
  259. package/dist/walletconnect.js.map +1 -1
  260. package/package.json +11 -10
  261. package/INK6-UX-SPEC.md +0 -446
  262. package/MIGRATION-SPEC.md +0 -108
  263. package/TUI-SPEC.md +0 -214
  264. package/scripts/authorize-machine-key.ts +0 -43
  265. package/scripts/drain-wallet.ts +0 -149
  266. package/scripts/execute-spend-only.ts +0 -81
  267. package/scripts/register-agent-userop.ts +0 -186
  268. package/src/abis.ts +0 -187
  269. package/src/bundler.ts +0 -235
  270. package/src/client.ts +0 -36
  271. package/src/coinbase-smart-wallet.ts +0 -51
  272. package/src/commands/accept.ts +0 -64
  273. package/src/commands/agent-handshake.ts +0 -72
  274. package/src/commands/agent.ts +0 -691
  275. package/src/commands/agreements.ts +0 -350
  276. package/src/commands/arbitrator.ts +0 -180
  277. package/src/commands/arena-handshake.ts +0 -274
  278. package/src/commands/arena.ts +0 -122
  279. package/src/commands/backup.ts +0 -117
  280. package/src/commands/cancel.ts +0 -35
  281. package/src/commands/channel.ts +0 -218
  282. package/src/commands/coldstart.ts +0 -165
  283. package/src/commands/config.ts +0 -68
  284. package/src/commands/contract-interaction.ts +0 -166
  285. package/src/commands/daemon.ts +0 -1054
  286. package/src/commands/deliver.ts +0 -148
  287. package/src/commands/discover.ts +0 -350
  288. package/src/commands/dispute.ts +0 -375
  289. package/src/commands/doctor.ts +0 -172
  290. package/src/commands/endpoint.ts +0 -620
  291. package/src/commands/feed.ts +0 -229
  292. package/src/commands/hire.ts +0 -245
  293. package/src/commands/migrate.ts +0 -177
  294. package/src/commands/negotiate.ts +0 -272
  295. package/src/commands/openshell.ts +0 -1055
  296. package/src/commands/owner.ts +0 -35
  297. package/src/commands/policy.ts +0 -263
  298. package/src/commands/relay.ts +0 -277
  299. package/src/commands/remediate.ts +0 -24
  300. package/src/commands/reputation.ts +0 -79
  301. package/src/commands/setup.ts +0 -343
  302. package/src/commands/trust.ts +0 -27
  303. package/src/commands/verify.ts +0 -91
  304. package/src/commands/wallet.ts +0 -3548
  305. package/src/commands/watch.ts +0 -220
  306. package/src/commands/watchtower.ts +0 -248
  307. package/src/commands/workroom.ts +0 -963
  308. package/src/config.ts +0 -220
  309. package/src/daemon/config.ts +0 -344
  310. package/src/daemon/hire-listener.ts +0 -226
  311. package/src/daemon/index.ts +0 -1089
  312. package/src/daemon/job-lifecycle.ts +0 -215
  313. package/src/daemon/notify.ts +0 -297
  314. package/src/daemon/token-metering.ts +0 -183
  315. package/src/daemon/userops.ts +0 -119
  316. package/src/daemon/wallet-monitor.ts +0 -90
  317. package/src/drain-v4.ts +0 -159
  318. package/src/endpoint-config.ts +0 -83
  319. package/src/endpoint-notify.ts +0 -134
  320. package/src/index.ts +0 -74
  321. package/src/openshell-runtime.ts +0 -281
  322. package/src/program.ts +0 -88
  323. package/src/repl.ts +0 -178
  324. package/src/signing.ts +0 -28
  325. package/src/telegram-notify.ts +0 -88
  326. package/src/tui/App.tsx +0 -263
  327. package/src/tui/Footer.tsx +0 -18
  328. package/src/tui/Header.tsx +0 -45
  329. package/src/tui/InputLine.tsx +0 -243
  330. package/src/tui/Viewport.tsx +0 -51
  331. package/src/tui/WalletConnectPairing.tsx +0 -114
  332. package/src/tui/components/Button.tsx +0 -38
  333. package/src/tui/components/CeremonyView.tsx +0 -39
  334. package/src/tui/components/CompletionDropdown.tsx +0 -56
  335. package/src/tui/components/ConfirmPrompt.tsx +0 -36
  336. package/src/tui/components/CustomTextInput.tsx +0 -132
  337. package/src/tui/components/InteractiveTable.tsx +0 -106
  338. package/src/tui/components/StepSpinner.tsx +0 -84
  339. package/src/tui/components/Toast.tsx +0 -59
  340. package/src/tui/index.tsx +0 -90
  341. package/src/tui/useChat.ts +0 -103
  342. package/src/tui/useCommand.ts +0 -238
  343. package/src/tui/useNotifications.ts +0 -28
  344. package/src/tui/useScroll.ts +0 -69
  345. package/src/ui/banner.ts +0 -78
  346. package/src/ui/colors.ts +0 -30
  347. package/src/ui/format.ts +0 -78
  348. package/src/ui/qr-render.ts +0 -92
  349. package/src/ui/rpc-fallback.ts +0 -59
  350. package/src/ui/spinner.ts +0 -56
  351. package/src/ui/tree.ts +0 -16
  352. package/src/utils/format.ts +0 -48
  353. package/src/utils/hash.ts +0 -5
  354. package/src/utils/time.ts +0 -15
  355. package/src/wallet-router.ts +0 -178
  356. package/src/walletconnect-session.ts +0 -27
  357. package/src/walletconnect.ts +0 -309
  358. package/test/time.test.js +0 -11
  359. package/tsconfig.json +0 -33
@@ -1,215 +0,0 @@
1
- /**
2
- * ARC-402 Job Lifecycle — Post-delivery processing.
3
- *
4
- * Handles everything that happens after a worker completes a job:
5
- * 1. Execution receipt generation
6
- * 2. Learning extraction from completed work
7
- * 3. Worker memory update
8
- * 4. Per-agreement job directory management
9
- *
10
- * These functions are called by the daemon after a successful delivery.
11
- */
12
- import * as fs from "fs";
13
- import * as path from "path";
14
- import * as os from "os";
15
- import * as crypto from "crypto";
16
- import { readUsageReport, type AggregatedTokenUsage } from "./token-metering.js";
17
-
18
- const ARC402_DIR = path.join(os.homedir(), ".arc402");
19
- const RECEIPTS_DIR = path.join(ARC402_DIR, "receipts");
20
- const WORKER_DIR = path.join(ARC402_DIR, "worker");
21
- const WORKER_MEMORY_DIR = path.join(WORKER_DIR, "memory");
22
- const WORKER_CONFIG = path.join(WORKER_DIR, "config.json");
23
- const JOBS_DIR = path.join(ARC402_DIR, "jobs");
24
-
25
- // ─── Execution Receipt ────────────────────────────────────────────────────────
26
-
27
- export interface ExecutionReceipt {
28
- schema: "arc402.execution-receipt.v1";
29
- agreement_id: string;
30
- workroom_policy_hash: string;
31
- started_at: string;
32
- completed_at: string;
33
- deliverable_hash: string;
34
- worker_address: string;
35
- metrics: {
36
- wall_clock_seconds: number;
37
- network_hosts_contacted: string[];
38
- policy_violations_attempted: number;
39
- };
40
- token_usage: AggregatedTokenUsage | null;
41
- receipt_hash: string;
42
- }
43
-
44
- /**
45
- * Generate and persist an execution receipt after job delivery.
46
- */
47
- export function generateReceipt(params: {
48
- agreementId: string;
49
- deliverableHash: string;
50
- walletAddress: string;
51
- startedAt: string;
52
- completedAt: string;
53
- policyFilePath?: string;
54
- networkHosts?: string[];
55
- }): ExecutionReceipt {
56
- fs.mkdirSync(RECEIPTS_DIR, { recursive: true });
57
-
58
- // Compute policy hash
59
- let policyHash = "0x0";
60
- const policyPath = params.policyFilePath ?? path.join(ARC402_DIR, "openshell-policy.yaml");
61
- if (fs.existsSync(policyPath)) {
62
- const content = fs.readFileSync(policyPath, "utf-8");
63
- policyHash = "0x" + crypto.createHash("sha256").update(content).digest("hex");
64
- }
65
-
66
- // Calculate wall clock time
67
- const started = new Date(params.startedAt).getTime();
68
- const completed = new Date(params.completedAt).getTime();
69
- const wallClockSeconds = Math.max(0, Math.floor((completed - started) / 1000));
70
-
71
- // Read token usage report (if the worker wrote one)
72
- const tokenUsage = readUsageReport(params.agreementId);
73
-
74
- const receipt: ExecutionReceipt = {
75
- schema: "arc402.execution-receipt.v1",
76
- agreement_id: params.agreementId,
77
- workroom_policy_hash: policyHash,
78
- started_at: params.startedAt,
79
- completed_at: params.completedAt,
80
- deliverable_hash: params.deliverableHash,
81
- worker_address: params.walletAddress,
82
- metrics: {
83
- wall_clock_seconds: wallClockSeconds,
84
- network_hosts_contacted: params.networkHosts ?? [],
85
- policy_violations_attempted: 0,
86
- },
87
- token_usage: tokenUsage,
88
- receipt_hash: "", // filled below
89
- };
90
-
91
- // Receipt hash = SHA-256 of the receipt content (without the hash field)
92
- const hashInput = JSON.stringify({ ...receipt, receipt_hash: undefined });
93
- receipt.receipt_hash = "0x" + crypto.createHash("sha256").update(hashInput).digest("hex");
94
-
95
- // Persist
96
- const receiptPath = path.join(RECEIPTS_DIR, `${params.agreementId}.json`);
97
- fs.writeFileSync(receiptPath, JSON.stringify(receipt, null, 2));
98
-
99
- return receipt;
100
- }
101
-
102
- // ─── Learning Extraction ──────────────────────────────────────────────────────
103
-
104
- /**
105
- * Extract learnings from a completed job and update worker memory.
106
- *
107
- * This creates:
108
- * 1. A per-job memory file: memory/job-<id>.md
109
- * 2. Updates the cumulative learnings.md
110
- * 3. Updates the worker config (job count, earnings)
111
- */
112
- export function extractLearnings(params: {
113
- agreementId: string;
114
- taskDescription: string;
115
- deliverableHash: string;
116
- priceEth: string;
117
- capability: string;
118
- wallClockSeconds: number;
119
- success: boolean;
120
- }): void {
121
- fs.mkdirSync(WORKER_MEMORY_DIR, { recursive: true });
122
-
123
- const now = new Date().toISOString();
124
- const dateStr = now.split("T")[0];
125
-
126
- // 1. Per-job memory file
127
- const jobMemory = `# Job: ${params.agreementId}
128
- *Completed: ${now}*
129
- *Capability: ${params.capability}*
130
- *Price: ${params.priceEth} ETH*
131
- *Duration: ${params.wallClockSeconds}s*
132
- *Outcome: ${params.success ? "delivered" : "failed"}*
133
-
134
- ## Task
135
- ${params.taskDescription}
136
-
137
- ## Deliverable
138
- Hash: ${params.deliverableHash}
139
-
140
- ## Learnings
141
- - Completed ${params.capability} task in ${params.wallClockSeconds}s
142
- - ${params.success ? "Delivered successfully" : "Delivery failed — review required"}
143
- `;
144
-
145
- const jobFile = path.join(WORKER_MEMORY_DIR, `job-${params.agreementId}.md`);
146
- fs.writeFileSync(jobFile, jobMemory);
147
-
148
- // 2. Update cumulative learnings.md
149
- const learningsPath = path.join(WORKER_MEMORY_DIR, "learnings.md");
150
- let learnings = "";
151
- if (fs.existsSync(learningsPath)) {
152
- learnings = fs.readFileSync(learningsPath, "utf-8");
153
- }
154
-
155
- const newEntry = `\n### ${dateStr} — ${params.capability} (${params.agreementId.slice(0, 8)}...)
156
- - Duration: ${params.wallClockSeconds}s | Price: ${params.priceEth} ETH | Outcome: ${params.success ? "✓" : "✗"}
157
- `;
158
-
159
- // Append to learnings
160
- if (learnings.includes("No learnings yet")) {
161
- learnings = `# Accumulated Learnings\n\n*Auto-updated after each completed job.*\n${newEntry}`;
162
- } else {
163
- learnings += newEntry;
164
- }
165
- fs.writeFileSync(learningsPath, learnings);
166
-
167
- // 3. Update worker config
168
- if (fs.existsSync(WORKER_CONFIG)) {
169
- try {
170
- const config = JSON.parse(fs.readFileSync(WORKER_CONFIG, "utf-8"));
171
- config.job_count = (config.job_count || 0) + 1;
172
- const currentEarnings = parseFloat(config.total_earned_eth || "0");
173
- const jobEarnings = parseFloat(params.priceEth || "0");
174
- config.total_earned_eth = (currentEarnings + jobEarnings).toFixed(6);
175
- config.last_job_at = now;
176
- fs.writeFileSync(WORKER_CONFIG, JSON.stringify(config, null, 2));
177
- } catch { /* non-fatal — config may be malformed */ }
178
- }
179
- }
180
-
181
- // ─── Per-Agreement Job Directory ──────────────────────────────────────────────
182
-
183
- /**
184
- * Create an isolated job directory for a specific agreement.
185
- * Returns the path to the job workspace.
186
- */
187
- export function createJobDirectory(agreementId: string): string {
188
- const jobDir = path.join(JOBS_DIR, `agreement-${agreementId}`);
189
- fs.mkdirSync(jobDir, { recursive: true });
190
- return jobDir;
191
- }
192
-
193
- /**
194
- * Clean up a job directory after settlement.
195
- * Preserves the receipt and job memory — only removes working files.
196
- */
197
- export function cleanJobDirectory(agreementId: string): void {
198
- const jobDir = path.join(JOBS_DIR, `agreement-${agreementId}`);
199
- if (fs.existsSync(jobDir)) {
200
- fs.rmSync(jobDir, { recursive: true, force: true });
201
- }
202
- }
203
-
204
- // ─── Policy Hash ──────────────────────────────────────────────────────────────
205
-
206
- /**
207
- * Compute the SHA-256 hash of the current workroom policy.
208
- * This hash can be registered in AgentRegistry for verifiability.
209
- */
210
- export function computePolicyHash(policyFilePath?: string): string {
211
- const policyPath = policyFilePath ?? path.join(ARC402_DIR, "openshell-policy.yaml");
212
- if (!fs.existsSync(policyPath)) return "0x0";
213
- const content = fs.readFileSync(policyPath, "utf-8");
214
- return "0x" + crypto.createHash("sha256").update(content).digest("hex");
215
- }
@@ -1,297 +0,0 @@
1
- /**
2
- * Multi-channel notification module for daemon events.
3
- * Supports Telegram, Discord webhooks, generic webhooks, and email (via nodemailer).
4
- */
5
- import * as https from "https";
6
- import * as http from "http";
7
- import type { DaemonConfig } from "./config.js";
8
-
9
- export type NotifyEvent =
10
- | "hire_request"
11
- | "hire_accepted"
12
- | "hire_rejected"
13
- | "delivery"
14
- | "dispute"
15
- | "channel_challenge"
16
- | "low_balance"
17
- | "daemon_started"
18
- | "daemon_stopped";
19
-
20
- // ─── Channel interface ────────────────────────────────────────────────────────
21
-
22
- export interface NotificationChannel {
23
- send(title: string, body: string): Promise<void>;
24
- }
25
-
26
- // ─── HTTP helper ──────────────────────────────────────────────────────────────
27
-
28
- function httpPost(url: string, payload: string, extraHeaders: Record<string, string>): Promise<void> {
29
- return new Promise((resolve, reject) => {
30
- const parsed = new URL(url);
31
- const mod = parsed.protocol === "https:" ? https : http;
32
- const options = {
33
- hostname: parsed.hostname,
34
- port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
35
- path: parsed.pathname + parsed.search,
36
- method: "POST",
37
- headers: {
38
- "Content-Type": "application/json",
39
- "Content-Length": Buffer.byteLength(payload),
40
- ...extraHeaders,
41
- },
42
- };
43
- const req = mod.request(options, (res) => {
44
- res.resume();
45
- res.on("end", () => {
46
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
47
- resolve();
48
- } else {
49
- reject(new Error(`HTTP ${res.statusCode}`));
50
- }
51
- });
52
- });
53
- req.on("error", reject);
54
- req.write(payload);
55
- req.end();
56
- });
57
- }
58
-
59
- // ─── Telegram channel ─────────────────────────────────────────────────────────
60
-
61
- export class TelegramChannel implements NotificationChannel {
62
- constructor(private botToken: string, private chatId: string) {}
63
-
64
- async send(title: string, body: string): Promise<void> {
65
- const text = body ? `<b>${title}</b>\n${body}` : `<b>${title}</b>`;
66
- const payload = JSON.stringify({ chat_id: this.chatId, text, parse_mode: "HTML" });
67
- const options: https.RequestOptions = {
68
- hostname: "api.telegram.org",
69
- port: 443,
70
- path: `/bot${this.botToken}/sendMessage`,
71
- method: "POST",
72
- headers: {
73
- "Content-Type": "application/json",
74
- "Content-Length": Buffer.byteLength(payload),
75
- },
76
- };
77
- await new Promise<void>((resolve, reject) => {
78
- const req = https.request(options, (res) => {
79
- let data = "";
80
- res.on("data", (c: Buffer) => { data += c.toString(); });
81
- res.on("end", () => {
82
- const parsed = JSON.parse(data) as { ok: boolean; description?: string };
83
- if (!parsed.ok) {
84
- reject(new Error(`Telegram API error: ${parsed.description}`));
85
- } else {
86
- resolve();
87
- }
88
- });
89
- });
90
- req.on("error", reject);
91
- req.write(payload);
92
- req.end();
93
- });
94
- }
95
- }
96
-
97
- // ─── Discord channel ──────────────────────────────────────────────────────────
98
-
99
- export class DiscordChannel implements NotificationChannel {
100
- constructor(private webhookUrl: string) {}
101
-
102
- async send(title: string, body: string): Promise<void> {
103
- const content = body ? `**${title}**\n${body}` : `**${title}**`;
104
- await httpPost(this.webhookUrl, JSON.stringify({ content }), {});
105
- }
106
- }
107
-
108
- // ─── Generic webhook channel ──────────────────────────────────────────────────
109
-
110
- export class WebhookChannel implements NotificationChannel {
111
- constructor(private url: string, private headers: Record<string, string> = {}) {}
112
-
113
- async send(title: string, body: string): Promise<void> {
114
- await httpPost(
115
- this.url,
116
- JSON.stringify({ title, body, timestamp: new Date().toISOString() }),
117
- this.headers
118
- );
119
- }
120
- }
121
-
122
- // ─── Email channel (optional — requires nodemailer) ───────────────────────────
123
-
124
- export class EmailChannel implements NotificationChannel {
125
- constructor(private cfg: {
126
- smtpHost: string;
127
- smtpPort: number;
128
- smtpUser: string;
129
- smtpPass: string;
130
- to: string;
131
- }) {}
132
-
133
- async send(title: string, body: string): Promise<void> {
134
- // nodemailer is an optional runtime dependency — load via require to skip
135
- // compile-time module resolution. Throws a clear message if not installed.
136
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
- let nodemailer: any;
138
- try {
139
- // @ts-expect-error nodemailer is an optional dependency
140
- nodemailer = await import("nodemailer");
141
- } catch {
142
- throw new Error("nodemailer is not installed. Run: npm install nodemailer");
143
- }
144
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
145
- const transport = nodemailer.createTransport({
146
- host: this.cfg.smtpHost,
147
- port: this.cfg.smtpPort,
148
- auth: { user: this.cfg.smtpUser, pass: this.cfg.smtpPass },
149
- });
150
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
151
- await transport.sendMail({
152
- from: this.cfg.smtpUser,
153
- to: this.cfg.to,
154
- subject: title,
155
- text: body,
156
- });
157
- }
158
- }
159
-
160
- // ─── Notifier ─────────────────────────────────────────────────────────────────
161
-
162
- export class Notifier {
163
- private channels: NotificationChannel[];
164
- private notifyFlags: Record<NotifyEvent, boolean>;
165
-
166
- constructor(
167
- channels: NotificationChannel[],
168
- flags: Partial<Record<NotifyEvent, boolean>> = {}
169
- ) {
170
- this.channels = channels;
171
- this.notifyFlags = {
172
- hire_request: flags.hire_request ?? true,
173
- hire_accepted: flags.hire_accepted ?? true,
174
- hire_rejected: flags.hire_rejected ?? true,
175
- delivery: flags.delivery ?? true,
176
- dispute: flags.dispute ?? true,
177
- channel_challenge: flags.channel_challenge ?? true,
178
- low_balance: flags.low_balance ?? true,
179
- daemon_started: true,
180
- daemon_stopped: true,
181
- };
182
- }
183
-
184
- isEnabled(): boolean {
185
- return this.channels.length > 0;
186
- }
187
-
188
- async send(event: NotifyEvent, title: string, body: string): Promise<void> {
189
- if (!this.notifyFlags[event]) return;
190
- await Promise.all(
191
- this.channels.map(async (ch) => {
192
- try {
193
- await ch.send(title, body);
194
- } catch (err) {
195
- process.stderr.write(`[notify] Channel send failed: ${err}\n`);
196
- }
197
- })
198
- );
199
- }
200
-
201
- async notifyHireRequest(hireId: string, hirerAddress: string, priceEth: string, capability: string): Promise<void> {
202
- const short = hirerAddress.slice(0, 10);
203
- await this.send("hire_request", "Hire Request", [
204
- `ID: ${hireId}`,
205
- `From: ${short}...`,
206
- `Capability: ${capability || "unspecified"}`,
207
- `Price: ${priceEth} ETH`,
208
- ``,
209
- `Approve: arc402 daemon approve ${hireId}`,
210
- `Reject: arc402 daemon reject ${hireId}`,
211
- ].join("\n"));
212
- }
213
-
214
- async notifyHireAccepted(hireId: string, agreementId: string): Promise<void> {
215
- await this.send("hire_accepted", "Hire Accepted",
216
- `ID: ${hireId}\nAgreement: ${agreementId}`
217
- );
218
- }
219
-
220
- async notifyHireRejected(hireId: string, reason: string): Promise<void> {
221
- await this.send("hire_rejected", "Hire Rejected",
222
- `ID: ${hireId}\nReason: ${reason}`
223
- );
224
- }
225
-
226
- async notifyDelivery(agreementId: string, deliveryHash: string, userOpHash: string): Promise<void> {
227
- await this.send("delivery", "Delivery Submitted", [
228
- `Agreement: ${agreementId}`,
229
- `Delivery hash: ${deliveryHash.slice(0, 16)}...`,
230
- `UserOp: ${userOpHash.slice(0, 16)}...`,
231
- ].join("\n"));
232
- }
233
-
234
- async notifyDispute(agreementId: string, raisedBy: string): Promise<void> {
235
- await this.send("dispute", "Dispute Raised",
236
- `Agreement: ${agreementId}\nBy: ${raisedBy}`
237
- );
238
- }
239
-
240
- async notifyChannelChallenge(channelId: string, txHash: string): Promise<void> {
241
- await this.send("channel_challenge", "Channel Challenged",
242
- `Channel: ${channelId.slice(0, 16)}...\nTx: ${txHash.slice(0, 16)}...`
243
- );
244
- }
245
-
246
- async notifyLowBalance(balanceEth: string, thresholdEth: string): Promise<void> {
247
- await this.send("low_balance", "Low Balance Alert",
248
- `Current: ${balanceEth} ETH\nThreshold: ${thresholdEth} ETH`
249
- );
250
- }
251
-
252
- async notifyStarted(walletAddress: string, subsystems: string[]): Promise<void> {
253
- await this.send("daemon_started", "ARC-402 Daemon Started",
254
- `Wallet: ${walletAddress}\nSubsystems: ${subsystems.join(", ")}`
255
- );
256
- }
257
-
258
- async notifyStopped(): Promise<void> {
259
- await this.send("daemon_stopped", "ARC-402 Daemon Stopped", "");
260
- }
261
- }
262
-
263
- // ─── Factory ──────────────────────────────────────────────────────────────────
264
-
265
- export function buildNotifier(config: DaemonConfig): Notifier {
266
- const notif = config.notifications;
267
- const channels: NotificationChannel[] = [];
268
-
269
- if (notif.telegram_bot_token && notif.telegram_chat_id) {
270
- channels.push(new TelegramChannel(notif.telegram_bot_token, notif.telegram_chat_id));
271
- }
272
- if (notif.discord?.webhook_url) {
273
- channels.push(new DiscordChannel(notif.discord.webhook_url));
274
- }
275
- if (notif.webhook?.url) {
276
- channels.push(new WebhookChannel(notif.webhook.url, notif.webhook.headers ?? {}));
277
- }
278
- if (notif.email?.smtp_host && notif.email?.smtp_user && notif.email?.to) {
279
- channels.push(new EmailChannel({
280
- smtpHost: notif.email.smtp_host,
281
- smtpPort: notif.email.smtp_port,
282
- smtpUser: notif.email.smtp_user,
283
- smtpPass: notif.email.smtp_pass,
284
- to: notif.email.to,
285
- }));
286
- }
287
-
288
- return new Notifier(channels, {
289
- hire_request: notif.notify_on_hire_request,
290
- hire_accepted: notif.notify_on_hire_accepted,
291
- hire_rejected: notif.notify_on_hire_rejected,
292
- delivery: notif.notify_on_delivery,
293
- dispute: notif.notify_on_dispute,
294
- channel_challenge: notif.notify_on_channel_challenge,
295
- low_balance: notif.notify_on_low_balance,
296
- });
297
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * ARC-402 Token Usage Metering
3
- *
4
- * Tracks LLM token consumption per agreement. The worker writes a usage report
5
- * file during execution; the daemon reads it after delivery and includes the
6
- * data in the execution receipt.
7
- *
8
- * Architecture:
9
- * - Before execution: daemon creates a usage report path for the agreement
10
- * - During execution: worker appends usage entries as it calls LLM APIs
11
- * - After execution: daemon reads the report, aggregates totals, includes in receipt
12
- *
13
- * The worker writes entries in JSON Lines format to:
14
- * /workroom/jobs/agreement-<id>/token-usage.jsonl
15
- *
16
- * Each line:
17
- * {"model":"claude-sonnet-4-6","provider":"anthropic","input":1200,"output":450,"ts":"..."}
18
- *
19
- * This is non-invasive — no proxy, no network interception. The worker
20
- * is responsible for reporting its own usage. OpenClaw agents can do this
21
- * natively via session metadata.
22
- */
23
- import * as fs from "fs";
24
- import * as path from "path";
25
- import * as os from "os";
26
-
27
- const ARC402_DIR = path.join(os.homedir(), ".arc402");
28
- const JOBS_DIR = path.join(ARC402_DIR, "jobs");
29
-
30
- // ─── Types ────────────────────────────────────────────────────────────────────
31
-
32
- export interface TokenUsageEntry {
33
- model: string;
34
- provider: string;
35
- input: number;
36
- output: number;
37
- ts: string;
38
- cost_usd?: number;
39
- }
40
-
41
- export interface AggregatedTokenUsage {
42
- total_input: number;
43
- total_output: number;
44
- total_tokens: number;
45
- estimated_cost_usd: number;
46
- models_used: string[];
47
- entries: number;
48
- by_model: Record<string, {
49
- input: number;
50
- output: number;
51
- calls: number;
52
- cost_usd: number;
53
- }>;
54
- }
55
-
56
- // ─── Known model pricing (USD per 1M tokens, as of 2026-03) ──────────────────
57
-
58
- const MODEL_PRICING: Record<string, { input: number; output: number }> = {
59
- // Anthropic
60
- "claude-opus-4-6": { input: 15.0, output: 75.0 },
61
- "claude-sonnet-4-6": { input: 3.0, output: 15.0 },
62
- "claude-haiku-4-5": { input: 0.8, output: 4.0 },
63
- // OpenAI
64
- "gpt-5.4": { input: 2.5, output: 10.0 },
65
- "gpt-5.3-codex": { input: 2.5, output: 10.0 },
66
- "gpt-4o": { input: 2.5, output: 10.0 },
67
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
68
- // Google
69
- "gemini-2.5-pro": { input: 1.25, output: 10.0 },
70
- "gemini-2.5-flash": { input: 0.15, output: 0.6 },
71
- // Defaults
72
- "default": { input: 2.0, output: 8.0 },
73
- };
74
-
75
- function estimateCost(model: string, inputTokens: number, outputTokens: number): number {
76
- const pricing = MODEL_PRICING[model] ?? MODEL_PRICING["default"];
77
- return (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
78
- }
79
-
80
- // ─── Usage report path ────────────────────────────────────────────────────────
81
-
82
- /**
83
- * Get the path where the worker should write token usage for an agreement.
84
- * The daemon sets this as an env var before calling exec_command.
85
- */
86
- export function getUsageReportPath(agreementId: string): string {
87
- return path.join(JOBS_DIR, `agreement-${agreementId}`, "token-usage.jsonl");
88
- }
89
-
90
- /**
91
- * Create the parent directory and return the path.
92
- * Called by the daemon before spawning the worker.
93
- */
94
- export function prepareUsageReport(agreementId: string): string {
95
- const reportPath = getUsageReportPath(agreementId);
96
- fs.mkdirSync(path.dirname(reportPath), { recursive: true });
97
- return reportPath;
98
- }
99
-
100
- // ─── Read and aggregate ───────────────────────────────────────────────────────
101
-
102
- /**
103
- * Read the token usage report for an agreement and aggregate totals.
104
- * Returns null if no report exists.
105
- */
106
- export function readUsageReport(agreementId: string): AggregatedTokenUsage | null {
107
- const reportPath = getUsageReportPath(agreementId);
108
-
109
- if (!fs.existsSync(reportPath)) return null;
110
-
111
- const content = fs.readFileSync(reportPath, "utf-8").trim();
112
- if (!content) return null;
113
-
114
- const lines = content.split("\n").filter(Boolean);
115
- const entries: TokenUsageEntry[] = [];
116
-
117
- for (const line of lines) {
118
- try {
119
- entries.push(JSON.parse(line) as TokenUsageEntry);
120
- } catch {
121
- // Skip malformed lines
122
- }
123
- }
124
-
125
- if (entries.length === 0) return null;
126
-
127
- // Aggregate
128
- const byModel: Record<string, { input: number; output: number; calls: number; cost_usd: number }> = {};
129
- let totalInput = 0;
130
- let totalOutput = 0;
131
- let totalCost = 0;
132
-
133
- for (const entry of entries) {
134
- totalInput += entry.input;
135
- totalOutput += entry.output;
136
-
137
- const model = entry.model || "unknown";
138
- if (!byModel[model]) {
139
- byModel[model] = { input: 0, output: 0, calls: 0, cost_usd: 0 };
140
- }
141
- byModel[model].input += entry.input;
142
- byModel[model].output += entry.output;
143
- byModel[model].calls += 1;
144
-
145
- const cost = entry.cost_usd ?? estimateCost(model, entry.input, entry.output);
146
- byModel[model].cost_usd += cost;
147
- totalCost += cost;
148
- }
149
-
150
- return {
151
- total_input: totalInput,
152
- total_output: totalOutput,
153
- total_tokens: totalInput + totalOutput,
154
- estimated_cost_usd: Math.round(totalCost * 10000) / 10000, // 4 decimal places
155
- models_used: Object.keys(byModel),
156
- entries: entries.length,
157
- by_model: byModel,
158
- };
159
- }
160
-
161
- // ─── CLI display helper ───────────────────────────────────────────────────────
162
-
163
- /**
164
- * Format token usage for CLI display.
165
- */
166
- export function formatUsageReport(usage: AggregatedTokenUsage): string {
167
- const lines: string[] = [];
168
- lines.push(`Token Usage`);
169
- lines.push(`───────────`);
170
- lines.push(`Total tokens: ${usage.total_tokens.toLocaleString()} (${usage.total_input.toLocaleString()} in / ${usage.total_output.toLocaleString()} out)`);
171
- lines.push(`Est. cost: $${usage.estimated_cost_usd.toFixed(4)}`);
172
- lines.push(`LLM calls: ${usage.entries}`);
173
- lines.push(``);
174
-
175
- if (Object.keys(usage.by_model).length > 1) {
176
- lines.push(`By model:`);
177
- for (const [model, data] of Object.entries(usage.by_model)) {
178
- lines.push(` ${model.padEnd(24)} ${data.calls} calls ${(data.input + data.output).toLocaleString()} tokens $${data.cost_usd.toFixed(4)}`);
179
- }
180
- }
181
-
182
- return lines.join("\n");
183
- }