dexterm-cli 0.1.5 → 0.1.7

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 (379) hide show
  1. package/dist/packages/agents/src/index.js +2 -0
  2. package/dist/packages/agents/src/schema.js +22 -1
  3. package/dist/packages/agents/src/spectator.js +54 -0
  4. package/dist/packages/agents/src/story.js +532 -0
  5. package/dist/packages/agents/src/tiers.js +28 -2
  6. package/dist/packages/agents/src/tools.js +31 -2
  7. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  8. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  9. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  10. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  11. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  12. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  13. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  14. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  15. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  16. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  17. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  18. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  19. package/dist/packages/autotrade/src/account.js +74 -0
  20. package/dist/packages/autotrade/src/backtest.js +357 -0
  21. package/dist/packages/autotrade/src/basis-text.js +32 -0
  22. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  23. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  24. package/dist/packages/autotrade/src/chat/config.js +93 -0
  25. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  26. package/dist/packages/autotrade/src/chat/control.js +103 -0
  27. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  28. package/dist/packages/autotrade/src/chat/format.js +71 -0
  29. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  30. package/dist/packages/autotrade/src/chat/index.js +18 -0
  31. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  32. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  33. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  34. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  35. package/dist/packages/autotrade/src/chat/service.js +248 -0
  36. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  37. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  38. package/dist/packages/autotrade/src/engine.js +387 -0
  39. package/dist/packages/autotrade/src/execution-port.js +97 -0
  40. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  41. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  42. package/dist/packages/autotrade/src/fleet.js +639 -0
  43. package/dist/packages/autotrade/src/index.js +53 -0
  44. package/dist/packages/autotrade/src/ledger.js +108 -0
  45. package/dist/packages/autotrade/src/live-source.js +182 -0
  46. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  47. package/dist/packages/autotrade/src/monitor.js +190 -0
  48. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  49. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  50. package/dist/packages/autotrade/src/replay.js +198 -0
  51. package/dist/packages/autotrade/src/runner-config.js +99 -0
  52. package/dist/packages/autotrade/src/runner.js +872 -0
  53. package/dist/packages/autotrade/src/safety.js +243 -0
  54. package/dist/packages/autotrade/src/session.js +269 -0
  55. package/dist/packages/autotrade/src/sessions.js +503 -0
  56. package/dist/packages/autotrade/src/store.js +250 -0
  57. package/dist/packages/autotrade/src/strategy.js +256 -0
  58. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  59. package/dist/packages/autotrade/src/yolo.js +140 -0
  60. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  61. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
  62. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
  63. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  64. package/dist/packages/cli/src/agent-cmd.js +6 -4
  65. package/dist/packages/cli/src/agent-spectator.js +17 -1
  66. package/dist/packages/cli/src/ai-cmd.js +412 -0
  67. package/dist/packages/cli/src/analyst/backend.js +105 -15
  68. package/dist/packages/cli/src/analyst/codex.js +14 -9
  69. package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
  70. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  71. package/dist/packages/cli/src/analyst/session.js +17 -3
  72. package/dist/packages/cli/src/app-help-gen.js +51 -15
  73. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  74. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  75. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  76. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  77. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  78. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  79. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  80. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  81. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  82. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  83. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  84. package/dist/packages/cli/src/backtest-cmd.js +13 -2
  85. package/dist/packages/cli/src/display.js +208 -23
  86. package/dist/packages/cli/src/doctor.js +62 -3
  87. package/dist/packages/cli/src/export.js +10 -2
  88. package/dist/packages/cli/src/index.js +1 -1
  89. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  90. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  91. package/dist/packages/cli/src/live/dev-history.js +31 -15
  92. package/dist/packages/cli/src/live/index.js +28 -90
  93. package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
  94. package/dist/packages/cli/src/live/screens/token-live.js +48 -15
  95. package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
  96. package/dist/packages/cli/src/live/trade.js +30 -453
  97. package/dist/packages/cli/src/live/watchlist.js +1 -1
  98. package/dist/packages/cli/src/main.js +290 -199
  99. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  100. package/dist/packages/cli/src/mcp-follow.js +103 -6
  101. package/dist/packages/cli/src/plan-cmd.js +15 -5
  102. package/dist/packages/cli/src/pnl-app.js +31 -21
  103. package/dist/packages/cli/src/pnl-card.js +158 -21
  104. package/dist/packages/cli/src/popup.js +38 -6
  105. package/dist/packages/cli/src/record-cmd.js +141 -20
  106. package/dist/packages/cli/src/scan/ui.js +7 -1
  107. package/dist/packages/cli/src/screens/agent-hud.js +773 -0
  108. package/dist/packages/cli/src/screens/analyst-session.js +11 -4
  109. package/dist/packages/cli/src/screens/analyst.js +3 -2
  110. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  111. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  112. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  113. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  114. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  115. package/dist/packages/cli/src/screens/brain.js +634 -0
  116. package/dist/packages/cli/src/screens/newpairs.js +1 -1
  117. package/dist/packages/cli/src/screens/overlay.js +5 -2
  118. package/dist/packages/cli/src/screens/plans.js +5 -17
  119. package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
  120. package/dist/packages/cli/src/screens/positions.js +6 -28
  121. package/dist/packages/cli/src/screens/screener.js +1 -0
  122. package/dist/packages/cli/src/screens/ticket.js +38 -205
  123. package/dist/packages/cli/src/screens/token.js +18 -9
  124. package/dist/packages/cli/src/screens/trending.js +1 -1
  125. package/dist/packages/cli/src/state.js +88 -18
  126. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  127. package/dist/packages/cli/src/trade.js +3 -128
  128. package/dist/packages/cli/src/version.js +1 -1
  129. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  130. package/dist/packages/cli/src/wallet-command.js +8 -11
  131. package/dist/packages/cli/src/widgets.js +0 -12
  132. package/dist/packages/custom/src/config.js +20 -2
  133. package/dist/packages/custom/src/schema.js +41 -10
  134. package/dist/packages/custom/src/workspace.js +3 -3
  135. package/dist/packages/data/src/basis.js +191 -0
  136. package/dist/packages/data/src/detail.js +53 -2
  137. package/dist/packages/data/src/index.js +2 -14
  138. package/dist/packages/data/src/larp/score.js +220 -0
  139. package/dist/packages/data/src/larp/x-account.js +316 -0
  140. package/dist/packages/data/src/store.js +14 -5
  141. package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
  142. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  143. package/dist/packages/data/src/trade/bridge.js +4 -102
  144. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  145. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  146. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  147. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  148. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  149. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  150. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  151. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  152. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  153. package/dist/packages/data/src/trade/plans/engine.js +7 -0
  154. package/dist/packages/data/src/trade/plans/index.js +0 -1
  155. package/dist/packages/data/src/trade/plans/mandate.js +28 -15
  156. package/dist/packages/data/src/trade/plans/service.js +4 -28
  157. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  158. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  159. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  160. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  161. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  162. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  163. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  164. package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
  165. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
  166. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  167. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  168. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  169. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  170. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  171. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
  172. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  173. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  174. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  175. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  176. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  177. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  178. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
  179. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  180. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  181. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
  182. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  183. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  184. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
  186. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  188. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  189. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  194. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  195. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  198. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  200. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  201. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  219. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  220. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  223. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  225. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
  226. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  227. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  228. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  229. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  230. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  233. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  234. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  235. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
  236. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  237. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
  240. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
  241. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
  242. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  243. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  244. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  246. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  248. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  250. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  264. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
  266. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  267. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  269. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  271. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  273. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  274. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  275. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
  277. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
  278. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
  281. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  282. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
  284. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
  288. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
  289. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  290. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  291. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  292. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  293. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  295. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  297. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  298. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  299. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  302. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  309. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  310. package/dist/packages/mcp/src/app-help.js +1 -1
  311. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  312. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  313. package/dist/packages/mcp/src/engine/config.js +159 -0
  314. package/dist/packages/mcp/src/engine/index.js +10 -0
  315. package/dist/packages/mcp/src/engine/loop.js +101 -0
  316. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  317. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  318. package/dist/packages/mcp/src/engine/probe.js +110 -0
  319. package/dist/packages/mcp/src/engine/reason.js +35 -0
  320. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  321. package/dist/packages/mcp/src/index.js +7 -1
  322. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  323. package/dist/packages/mcp/src/paper-agent.js +5 -2
  324. package/dist/packages/mcp/src/server.js +285 -36
  325. package/dist/packages/mcp/src/spectator-sink.js +85 -0
  326. package/dist/packages/packs/src/backtest.js +3 -1
  327. package/dist/packages/packs/src/index.js +1 -0
  328. package/dist/packages/packs/src/larp-score-backtest.js +119 -0
  329. package/dist/packages/popup/src/pages/chart-lib.js +169 -35
  330. package/dist/packages/popup/src/pages/chart.css +18 -0
  331. package/dist/packages/popup/src/pages/chart.html +6 -1
  332. package/dist/packages/popup/src/pages/chart.js +136 -10
  333. package/dist/packages/popup/src/pages/spectator.css +45 -0
  334. package/dist/packages/popup/src/pages/spectator.html +1 -0
  335. package/dist/packages/popup/src/pages/spectator.js +69 -0
  336. package/dist/packages/popup/src/protocol.js +4 -0
  337. package/dist/packages/popup/src/server.js +9 -1
  338. package/dist/packages/recorder/src/dev-history.js +7 -3
  339. package/dist/packages/recorder/src/index.js +2 -0
  340. package/dist/packages/recorder/src/recorder.js +63 -6
  341. package/dist/packages/recorder/src/retention.js +100 -0
  342. package/dist/packages/recorder/src/schema.js +34 -1
  343. package/dist/packages/recorder/src/ticks.js +237 -0
  344. package/dist/packages/tui/src/canvas.js +7 -0
  345. package/dist/packages/tui/src/fmt.js +53 -0
  346. package/dist/packages/tui/src/index.js +1 -0
  347. package/dist/packages/tui/src/keymap.js +25 -1
  348. package/dist/packages/tui/src/markers.js +219 -0
  349. package/dist/packages/tui/src/widgets.js +158 -6
  350. package/package.json +2 -1
  351. package/dist/packages/data/src/trade/caps.js +0 -323
  352. package/dist/packages/data/src/trade/executor.js +0 -933
  353. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  354. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  355. package/dist/packages/data/src/trade/signer/client.js +0 -197
  356. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  357. package/dist/packages/data/src/trade/signer/host.js +0 -97
  358. package/dist/packages/data/src/trade/signer/main.js +0 -121
  359. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  360. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  361. package/dist/packages/data/src/trade/signer/service.js +0 -400
  362. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  363. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  364. package/dist/packages/data/src/trade/swap-build.js +0 -108
  365. package/dist/packages/data/src/trade/token-delta.js +0 -201
  366. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  367. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  368. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  369. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  370. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  371. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  372. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  373. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  374. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  375. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  376. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  377. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  378. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  379. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -0,0 +1,307 @@
1
+ // task-1530 — read-only IMPORTERS: every trade journal dexterm already keeps, as unified ledger drafts.
2
+ //
3
+ // Nothing here writes to a source. The existing journals stay exactly as they are (and keep being written the way
4
+ // they always were), so the unified ledger is fully backward compatible: an old journal with none of the new fields
5
+ // imports with those fields null, and the "migration" of an existing install is one `dexterm ledger sync`.
6
+ //
7
+ // desk $DEXTERM_HOME/paper-agent.journal: the agent paper desk (MCP paper tools, the autotrade desk executor).
8
+ // agent.paper.fill events, their structured tag (task-1530), the exit rule journaled just before a monitor
9
+ // sell (agent.paper.exit.<rule>), and late PROFILE / REPROFILE / EXIT declarations (agent.paper.tag).
10
+ // The journal's own hash chain is verified and its head recorded in the import event. An autotrade run on
11
+ // the desk (note.by "autotrade") is imported here too, with its strategy / profile source / model from
12
+ // the run ledger (`RunHint`).
13
+ // paper the paper wallet's paper_trades table (the TUI ticket). Rows the desk booked are joined to the desk
14
+ // fills (fees, impact, slippage, the book before the fill) and not imported twice.
15
+ // task-1571: there is no live source. dexterm holds no key and places no live order, so it keeps no live fills.
16
+ // Autotrade run ledgers are imported by @dexterm/autotrade (`importRunLedgers`), which owns that format.
17
+ //
18
+ // A round trip is every fill on one mint from a buy while flat until the position is empty again (the source's own
19
+ // token count). Its key is the ref of its first fill, so re-importing never re-keys a trade.
20
+ import { createHash } from 'node:crypto';
21
+ import { existsSync, readFileSync } from 'node:fs';
22
+ import { DatabaseSync } from 'node:sqlite';
23
+ import { eventHash } from "../plans/journal.js";
24
+ import { buildFillTag, buildLateTag, tagCode } from "./tags.js";
25
+ import { cleanCode, cleanLedgerText, finite } from "./schema.js";
26
+ const LAMPORTS = 1e9;
27
+ const num = (x) => { const n = typeof x === 'string' && x.trim() !== '' ? Number(x) : x; return finite(n); };
28
+ const big = (x) => { try {
29
+ return typeof x === 'bigint' ? x : typeof x === 'string' && /^-?\d+$/.test(x) ? BigInt(x) : typeof x === 'number' && Number.isInteger(x) ? BigInt(x) : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ } };
34
+ const absBig = (b) => (b < 0n ? -b : b);
35
+ const solOf = (l) => (l === null ? null : Number(l) / LAMPORTS);
36
+ const mintOk = (x) => typeof x === 'string' && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(x);
37
+ const sigOk = (x) => (typeof x === 'string' && /^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(x) ? x : null);
38
+ const sha = (s) => createHash('sha256').update(s).digest('hex');
39
+ /** Track the token count per key; flags the fill that empties the position. */
40
+ class RoundTrips {
41
+ held = new Map();
42
+ /** The trade key for this fill, and whether the position is flat after it. A sell while flat has no trade (null). */
43
+ step(scope, side, tokenDeltaAbs, ref) {
44
+ const cur = this.held.get(scope);
45
+ if (side === 'buy') {
46
+ if (!cur || cur.tokens <= 0n) {
47
+ this.held.set(scope, { tokens: tokenDeltaAbs, key: ref });
48
+ return { tradeKey: ref, closes: false };
49
+ }
50
+ cur.tokens += tokenDeltaAbs;
51
+ return { tradeKey: cur.key, closes: false };
52
+ }
53
+ if (!cur || cur.tokens <= 0n)
54
+ return { tradeKey: null, closes: false };
55
+ cur.tokens -= tokenDeltaAbs;
56
+ const closes = cur.tokens <= 0n;
57
+ if (closes)
58
+ this.held.delete(scope);
59
+ return { tradeKey: cur.key, closes };
60
+ }
61
+ }
62
+ /** pump.fun mints every coin with 6 decimals and a fixed 1,000,000,000 supply (the PnL card's rule, task-1518). */
63
+ export const PUMP_FUN_DECIMALS = 6;
64
+ export const PUMP_FUN_SUPPLY_TOKENS = 1_000_000_000;
65
+ const isPumpFun = (mint) => mint.endsWith('pump');
66
+ function fillDraft(p) {
67
+ const d = {
68
+ kind: 'fill', decimals: null, sol: null, usd: null, tokens: null, priceSol: null, priceUsd: null, supply: null, feeSol: null,
69
+ impactPct: null, slippageBps: null, realizedSol: null, realizedUsd: null, bookSol: null, bookUsd: null, closes: null, txSig: null,
70
+ runId: null, agent: null, strategy: null, sessionId: null, model: null, tag: null, ...p,
71
+ };
72
+ // a source that recorded no supply: a pump.fun coin's is fixed, so its market cap is price × 1B; any other stays null
73
+ if (!d.supply && isPumpFun(d.mint))
74
+ d.supply = { tokens: PUMP_FUN_SUPPLY_TOKENS, source: 'pump.fun fixed supply' };
75
+ return d;
76
+ }
77
+ function importDraft(source, label, head, verified, events, t, note) {
78
+ return { kind: 'import', t, ref: `import:${source}:${label}:${head ?? `n${events}`}`, source, label, sourceHead: head, sourceVerified: verified, events, ...(note ? { note } : {}) };
79
+ }
80
+ function openRo(path) {
81
+ if (!path || path === ':memory:' || !existsSync(path))
82
+ return null;
83
+ try {
84
+ return new DatabaseSync(path, { readOnly: true });
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ const hasTable = (db, name) => Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name));
91
+ function readWallet(path) {
92
+ const db = openRo(path);
93
+ if (!db)
94
+ return null;
95
+ try {
96
+ if (!hasTable(db, 'paper_trades'))
97
+ return null;
98
+ const w = hasTable(db, 'paper_wallet') ? db.prepare('SELECT start_lamports, created_at FROM paper_wallet WHERE id = 1').get() : undefined;
99
+ const rows = db.prepare('SELECT * FROM paper_trades ORDER BY id ASC').all().map((r) => ({
100
+ id: Number(r.id), t: Number(r.t), mint: String(r.mint), symbol: String(r.symbol), side: r.side === 'sell' ? 'sell' : 'buy', decimals: Number(r.decimals),
101
+ solDelta: big(r.sol_delta) ?? 0n, tokenDelta: big(r.token_delta) ?? 0n, fee: big(r.fee_lamports) ?? 0n, priceSol: Number(r.price_sol),
102
+ priceUsd: r.price_usd === null ? null : Number(r.price_usd), impact: r.price_impact_pct === null ? null : Number(r.price_impact_pct),
103
+ slippage: Number(r.slippage_bps), realized: big(r.realized_lamports) ?? 0n,
104
+ }));
105
+ return { rows, startLamports: w ? big(w.start_lamports) : null, createdAt: w ? Number(w.created_at) : null };
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ finally {
111
+ try {
112
+ db.close();
113
+ }
114
+ catch { /* ignore */ }
115
+ }
116
+ }
117
+ /** Per wallet row: the book (cash + open cost) just BEFORE it, replayed from the wallet's start balance. */
118
+ function walletFacts(w) {
119
+ const out = new Map();
120
+ let cash = w.startLamports;
121
+ const cost = new Map();
122
+ for (const r of w.rows) {
123
+ const openCost = [...cost.values()].reduce((s, c) => s + c.cost, 0n);
124
+ out.set(r.id, { decimals: r.decimals, feeSol: Number(r.fee) / LAMPORTS, impactPct: r.impact, slippageBps: Number.isFinite(r.slippage) ? r.slippage : null, bookSolBefore: cash === null ? null : Number(cash + openCost) / LAMPORTS, priceUsd: r.priceUsd });
125
+ if (cash !== null)
126
+ cash += r.solDelta;
127
+ const c = cost.get(r.mint) ?? { tokens: 0n, cost: 0n };
128
+ if (r.side === 'buy') {
129
+ c.tokens += absBig(r.tokenDelta);
130
+ c.cost += absBig(r.solDelta);
131
+ }
132
+ else if (c.tokens > 0n) {
133
+ const sold = absBig(r.tokenDelta) > c.tokens ? c.tokens : absBig(r.tokenDelta);
134
+ c.cost -= (c.cost * sold) / c.tokens;
135
+ c.tokens -= sold;
136
+ }
137
+ if (c.tokens <= 0n)
138
+ cost.delete(r.mint);
139
+ else
140
+ cost.set(r.mint, c);
141
+ }
142
+ return out;
143
+ }
144
+ function verifyDeskChain(events) {
145
+ let prev = 'genesis';
146
+ for (let i = 0; i < events.length; i++) {
147
+ const e = events[i];
148
+ if (e.seq !== i + 1 || e.prev !== prev)
149
+ return { ok: false, at: i + 1 };
150
+ const { hash, ...rest } = e;
151
+ if (eventHash(rest) !== hash)
152
+ return { ok: false, at: i + 1 };
153
+ prev = hash;
154
+ }
155
+ return { ok: true, at: null };
156
+ }
157
+ export function importDeskJournal(path, o = {}) {
158
+ const label = 'paper-agent.journal';
159
+ const res = { label, source: 'desk', drafts: [], events: 0, note: null, problem: null, fillIds: new Set() };
160
+ if (!existsSync(path))
161
+ return res;
162
+ const events = [];
163
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
164
+ if (!line.trim())
165
+ continue;
166
+ try {
167
+ events.push(JSON.parse(line));
168
+ }
169
+ catch {
170
+ res.problem = `a line of ${label} is not JSON; imported the ${events.length} events before it`;
171
+ break;
172
+ }
173
+ }
174
+ res.events = events.length;
175
+ const chain = verifyDeskChain(events);
176
+ const head = events.length ? events[events.length - 1].hash : null;
177
+ const wallet = o.walletDb ? readWallet(o.walletDb) : null;
178
+ const facts = wallet ? walletFacts(wallet) : new Map();
179
+ const trips = new RoundTrips();
180
+ const lastExitRule = new Map();
181
+ const tradeOfMint = new Map();
182
+ for (const e of events) {
183
+ const d = (e.data ?? {});
184
+ const ref = `desk:${e.seq}:${String(e.hash).slice(0, 12)}`;
185
+ if (e.type.startsWith('agent.paper.exit.') && e.type !== 'agent.paper.exit.plan' && e.type !== 'agent.paper.exit.done' && mintOk(d.mint)) {
186
+ lastExitRule.set(d.mint, { rule: e.type.slice('agent.paper.exit.'.length), detail: cleanLedgerText(d.detail, 200), t: e.ts });
187
+ continue;
188
+ }
189
+ if (e.type === 'agent.paper.tag' && mintOk(d.mint)) {
190
+ const g = buildLateTag({ kind: d.tagKind, profile: d.profile, from: d.from, to: d.to, exitReason: d.exitReason, reason: d.reason, line: d.line });
191
+ if (!g.ok)
192
+ continue;
193
+ res.drafts.push({
194
+ kind: 'tag', t: e.ts, ref, tradeKey: tradeOfMint.get(d.mint) ?? null, source: 'desk', mint: d.mint, tagKind: g.tagKind,
195
+ profile: g.tagKind === 'profile' ? g.profile : null, from: g.tagKind === 'reprofile' ? g.from : null, to: g.tagKind === 'reprofile' ? g.to : null,
196
+ exitReason: g.tagKind === 'exit' ? g.exitReason : null, reason: g.reason, line: g.line, tagSource: g.tagKind === 'reprofile' ? 'structured' : 'late',
197
+ });
198
+ continue;
199
+ }
200
+ if (e.type !== 'agent.paper.fill' || !mintOk(d.mint))
201
+ continue;
202
+ const side = d.side === 'sell' ? 'sell' : 'buy';
203
+ const solDelta = big(d.solDelta);
204
+ const tokenDelta = big(d.tokenDelta);
205
+ if (solDelta === null || tokenDelta === null)
206
+ continue;
207
+ const fillId = num(d.fillId);
208
+ if (fillId !== null)
209
+ res.fillIds.add(fillId);
210
+ const note = (d.note && typeof d.note === 'object' ? d.note : {});
211
+ const runId = typeof note.runId === 'string' ? cleanCode(note.runId, 80) : null;
212
+ const byRun = note.by === 'autotrade';
213
+ const hint = byRun && runId ? o.runs?.get(runId) ?? null : null;
214
+ const trip = trips.step(d.mint, side, absBig(tokenDelta), ref);
215
+ if (trip.tradeKey)
216
+ tradeOfMint.set(d.mint, trip.tradeKey);
217
+ if (!trip.tradeKey)
218
+ continue;
219
+ const f = fillId !== null ? facts.get(fillId) : undefined;
220
+ // journals before task-1530 carry no decimals: a pump.fun coin's are fixed; any other mint stays unknown (no tokens)
221
+ const decimals = num(d.decimals) ?? f?.decimals ?? (isPumpFun(d.mint) ? PUMP_FUN_DECIMALS : null);
222
+ const tokens = decimals !== null ? Number(absBig(tokenDelta)) / 10 ** decimals : null;
223
+ const sol = Number(absBig(solDelta)) / LAMPORTS;
224
+ const priceSol = num(d.priceSol);
225
+ const priceUsd = num(d.priceUsd) ?? f?.priceUsd ?? null;
226
+ const solUsd = priceSol && priceUsd ? priceUsd / priceSol : null;
227
+ // an autotrade run's session ids are per run (s1, s2, …): scope them so two runs never share one
228
+ const rawSid = typeof d.sessionId === 'string' ? d.sessionId : typeof note.sessionId === 'string' ? note.sessionId : null;
229
+ const sessionId = cleanCode(byRun && runId && rawSid ? `${runId}:${rawSid}` : rawSid, 80);
230
+ // the tag: the structured one the desk now persists; else what an autotrade note carries; else the monitor exit
231
+ // rule journaled just before the sell
232
+ let tag = d.tag && typeof d.tag === 'object' ? sanitizeStoredTag(side, d.tag) : null;
233
+ if (side === 'buy' && !tag && typeof note.profile === 'string') {
234
+ const reason = cleanLedgerText(note.reason, 200);
235
+ const named = sessionId !== null && (hint?.declared.has(sessionId) ?? false);
236
+ tag = { profile: tagCode(note.profile), reason, exitReason: null, line: null, source: named ? 'structured' : 'default', ...(reason && !named ? { reasonSource: 'structured' } : {}) };
237
+ }
238
+ const exitNote = typeof note.exit === 'string' ? tagCode(note.exit) : null;
239
+ const seen = lastExitRule.get(d.mint);
240
+ const rule = side === 'sell' ? (exitNote ? { rule: exitNote, detail: null } : seen && e.ts - seen.t < 600_000 ? seen : null) : null;
241
+ if (side === 'sell' && rule && !(tag && tag.exitReason)) {
242
+ const code = tagCode(rule.rule);
243
+ const agentWords = cleanLedgerText(note.agentReason, 200);
244
+ tag = { profile: null, reason: tag?.reason ?? agentWords ?? rule.detail ?? null, exitReason: code, line: tag?.line ?? null, source: code === 'agent-exit' || code === 'agent-trim' ? 'structured' : 'rule' };
245
+ }
246
+ if (side === 'sell')
247
+ lastExitRule.delete(d.mint);
248
+ const noteStrategy = byRun && typeof note.strategy === 'string' ? { id: cleanCode(note.strategy, 32) ?? 'unknown', version: null, hash: cleanCode(note.strategyHash, 64) } : null;
249
+ res.drafts.push(fillDraft({
250
+ ref, tradeKey: trip.tradeKey, t: e.ts, source: 'desk', walletMode: 'paper', side, mint: d.mint, symbol: cleanLedgerText(d.symbol, 24) ?? '?', decimals,
251
+ sol, usd: solUsd ? sol * solUsd : null, tokens, priceSol, priceUsd,
252
+ feeSol: num(d.feeLamports) !== null ? num(d.feeLamports) / LAMPORTS : f?.feeSol ?? null,
253
+ impactPct: num(d.impactPct) ?? f?.impactPct ?? null, slippageBps: num(d.slippageBps) ?? f?.slippageBps ?? null,
254
+ realizedSol: side === 'sell' ? solOf(big(d.realizedLamports)) : null,
255
+ bookSol: num(d.bookLamports) !== null ? num(d.bookLamports) / LAMPORTS : f?.bookSolBefore ?? null,
256
+ bookUsd: null, closes: trip.closes, runId, agent: cleanLedgerText(d.agent, 40),
257
+ strategy: hint?.strategy ?? noteStrategy, sessionId,
258
+ model: cleanLedgerText(d.model, 40) ?? (sessionId ? hint?.models.get(sessionId) ?? null : null), tag,
259
+ }));
260
+ }
261
+ if (events.length)
262
+ res.drafts.unshift(importDraft('desk', label, head, chain.ok, events.length, events[events.length - 1].ts, chain.ok ? undefined : `the journal's chain breaks at event ${chain.at}`));
263
+ if (!chain.ok)
264
+ res.problem = `${label}: its hash chain breaks at event ${chain.at} (imported as found; \`dexterm ledger verify\` shows the import)`;
265
+ return res;
266
+ }
267
+ /** A tag read back from a journal: re-cleaned (the journal is a file; never trust what it holds). */
268
+ function sanitizeStoredTag(side, g) {
269
+ const t = buildFillTag(side, { profile: g.profile, reason: g.reason, exitReason: g.exitReason, line: g.line });
270
+ if (!t)
271
+ return null;
272
+ const src = g.source;
273
+ const known = src === 'structured' || src === 'tag-line' || src === 'rule' || src === 'late' || src === 'default';
274
+ return { ...t, source: known ? src : t.source };
275
+ }
276
+ // ───────────────────────────── paper wallet (the TUI ticket) ─────────────────────────────
277
+ export function importPaperWallet(dbPath, deskFillIds = new Set()) {
278
+ const label = 'paper-wallet';
279
+ const res = { label, source: 'paper', drafts: [], events: 0, note: null, problem: null };
280
+ const w = readWallet(dbPath);
281
+ if (!w)
282
+ return res;
283
+ res.events = w.rows.length;
284
+ const facts = walletFacts(w);
285
+ const trips = new RoundTrips();
286
+ const scope = `w${w.createdAt ?? 0}`;
287
+ let n = 0;
288
+ for (const r of w.rows) {
289
+ const ref = `paper:${scope}:${r.id}`;
290
+ const trip = trips.step(r.mint, r.side, absBig(r.tokenDelta), ref);
291
+ if (deskFillIds.has(r.id) || !trip.tradeKey || !mintOk(r.mint))
292
+ continue; // the desk's fills come from its journal
293
+ const f = facts.get(r.id);
294
+ const sol = Number(absBig(r.solDelta)) / LAMPORTS;
295
+ const solUsd = r.priceUsd && r.priceSol ? r.priceUsd / r.priceSol : null;
296
+ res.drafts.push(fillDraft({
297
+ ref, tradeKey: trip.tradeKey, t: r.t, source: 'paper', walletMode: 'paper', side: r.side, mint: r.mint, symbol: cleanLedgerText(r.symbol, 24) ?? '?',
298
+ decimals: r.decimals, sol, usd: solUsd ? sol * solUsd : null, tokens: Number(absBig(r.tokenDelta)) / 10 ** r.decimals, priceSol: r.priceSol, priceUsd: r.priceUsd,
299
+ feeSol: f.feeSol, impactPct: r.impact, slippageBps: r.slippage, realizedSol: r.side === 'sell' ? Number(r.realized) / LAMPORTS : null,
300
+ bookSol: f.bookSolBefore, closes: trip.closes,
301
+ }));
302
+ n++;
303
+ }
304
+ if (w.rows.length)
305
+ res.drafts.unshift(importDraft('paper', label, `${scope}:${w.rows[w.rows.length - 1].id}`, null, w.rows.length, w.rows[w.rows.length - 1].t, n ? undefined : 'no fills outside the agent desk'));
306
+ return res;
307
+ }
@@ -0,0 +1,123 @@
1
+ // task-1530 — ledger statistics: filters, the summary (win rate, P/L, drawdown, rug rate, breakdowns by profile,
2
+ // exit reason, strategy, source and wallet mode), the equity curve and the per-strategy comparison.
3
+ //
4
+ // Money is summed in ONE unit per report: SOL when every closed trade has a SOL figure, else USD (an autotrade run
5
+ // books in USD). The report names its unit. Paper, devnet and live are never blended silently: `modes` lists what
6
+ // is in the set, and the CLI says so when there is more than one.
7
+ /** `strategy` matches `id`, `id@version` or `id@hash-prefix`. */
8
+ export function matchStrategy(t, q) {
9
+ const s = t.strategy;
10
+ if (!s)
11
+ return q === 'none' || q === 'manual';
12
+ const [id, at] = q.split('@');
13
+ if (s.id !== id)
14
+ return false;
15
+ return !at || s.version === at || (s.hash ?? '').startsWith(at);
16
+ }
17
+ export function filterTrades(rows, f = {}) {
18
+ return rows.filter((t) => (!f.source || t.source === f.source) && (!f.mode || t.walletMode === f.mode) && (!f.status || t.status === f.status)
19
+ && (!f.strategy || matchStrategy(t, f.strategy)) && (!f.profile || (t.profile.id ?? 'undeclared') === f.profile)
20
+ && (!f.exitReason || (t.exitReason.code ?? 'untagged') === f.exitReason) && (!f.mint || t.mint === f.mint)
21
+ && (!f.runId || t.runId === f.runId) && (!f.account || t.account === f.account) && (f.since === undefined || t.openedAt >= f.since) && (f.until === undefined || t.openedAt < f.until));
22
+ }
23
+ export function unitFor(rows) {
24
+ const closed = rows.filter((t) => t.status === 'closed');
25
+ return closed.length && closed.every((t) => t.pnlSol !== null) ? 'SOL' : closed.some((t) => t.pnlUsd !== null) ? 'USD' : 'SOL';
26
+ }
27
+ const pnlIn = (t, u) => (u === 'SOL' ? t.pnlSol : t.pnlUsd);
28
+ const r = (x, p = 6) => (x === null || !Number.isFinite(x) ? null : Number(x.toPrecision(p)));
29
+ /** Wilson 95 % interval for k wins in n trades. */
30
+ export function wilson(k, n) {
31
+ if (!n)
32
+ return null;
33
+ const z = 1.96, p = k / n, d = 1 + (z * z) / n;
34
+ const c = (p + (z * z) / (2 * n)) / d, h = (z * Math.sqrt((p * (1 - p)) / n + (z * z) / (4 * n * n))) / d;
35
+ return { value: r(p, 4), lo: r(Math.max(0, c - h), 4), hi: r(Math.min(1, c + h), 4) };
36
+ }
37
+ /** Cumulative realized P/L by close time. Drawdown is from the running peak; its % is of (starting book + peak). */
38
+ export function equityCurve(rows, unit = unitFor(rows)) {
39
+ const closed = rows.filter((t) => t.status === 'closed' && pnlIn(t, unit) !== null).sort((a, b) => (a.closedAt ?? 0) - (b.closedAt ?? 0) || a.tradeId.localeCompare(b.tradeId));
40
+ const firstBook = rows.map((t) => (unit === 'SOL' ? t.bookSol : t.bookUsd)).find((x) => x !== null && x > 0) ?? null;
41
+ let cum = 0, peak = 0, maxDd = 0, maxDdPct = firstBook ? 0 : null;
42
+ const points = [];
43
+ for (const t of closed) {
44
+ const p = pnlIn(t, unit);
45
+ cum += p;
46
+ peak = Math.max(peak, cum);
47
+ const dd = peak - cum;
48
+ maxDd = Math.max(maxDd, dd);
49
+ if (firstBook)
50
+ maxDdPct = Math.max(maxDdPct ?? 0, (dd / (firstBook + peak)) * 100);
51
+ points.push({ t: t.closedAt ?? t.openedAt, tradeId: t.tradeId, symbol: t.symbol, pnl: r(p), cum: r(cum), peak: r(peak), drawdown: r(dd) });
52
+ }
53
+ return { unit, start: firstBook, points, maxDrawdown: r(maxDd), maxDrawdownPct: maxDdPct === null ? null : r(maxDdPct, 4), final: r(cum) };
54
+ }
55
+ function group(rows, key, unit) {
56
+ const m = new Map();
57
+ for (const t of rows) {
58
+ const k = key(t);
59
+ m.set(k, [...(m.get(k) ?? []), t]);
60
+ }
61
+ return [...m.entries()].map(([k, ts]) => {
62
+ const c = ts.filter((t) => t.status === 'closed');
63
+ const wins = c.filter((t) => (pnlIn(t, unit) ?? 0) > 0).length;
64
+ const pcts = c.map((t) => t.pnlPct).filter((x) => x !== null);
65
+ const rugs = c.filter((t) => t.rug).length;
66
+ return {
67
+ key: k, trades: c.length, open: ts.length - c.length, wins, losses: c.length - wins, winRate: c.length ? r(wins / c.length, 4) : null,
68
+ pnl: r(c.reduce((s, t) => s + (pnlIn(t, unit) ?? 0), 0)), avgPnlPct: pcts.length ? r(pcts.reduce((a, b) => a + b, 0) / pcts.length, 4) : null,
69
+ rugs, rugRate: c.length ? r(rugs / c.length, 4) : null,
70
+ };
71
+ }).sort((a, b) => b.trades - a.trades || a.key.localeCompare(b.key));
72
+ }
73
+ export const strategyKey = (t) => (t.strategy ? `${t.strategy.id}@${t.strategy.version ?? (t.strategy.hash ?? '').slice(0, 8)}` : 'manual');
74
+ const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null);
75
+ export function summarize(rows, unit = unitFor(rows)) {
76
+ const closed = rows.filter((t) => t.status === 'closed');
77
+ const pnls = closed.map((t) => pnlIn(t, unit) ?? 0);
78
+ const wins = pnls.filter((p) => p > 0).length;
79
+ const pcts = closed.map((t) => t.pnlPct).filter((x) => x !== null).sort((a, b) => a - b);
80
+ const gain = pnls.filter((p) => p > 0).reduce((a, b) => a + b, 0);
81
+ const loss = -pnls.filter((p) => p < 0).reduce((a, b) => a + b, 0);
82
+ const curve = equityCurve(rows, unit);
83
+ const rugs = closed.filter((t) => t.rug).length;
84
+ const sumOf = (f) => { const xs = closed.map(f).filter((x) => x !== null); return xs.length ? r(xs.reduce((a, b) => a + b, 0)) : null; };
85
+ const declared = (f, set) => (set.length ? r(set.filter(f).length / set.length, 4) : null);
86
+ const good = (s) => s !== 'missing' && s !== 'default';
87
+ return {
88
+ unit, modes: [...new Set(rows.map((t) => t.walletMode))],
89
+ trades: closed.length, open: rows.length - closed.length, wins, losses: closed.length - wins,
90
+ winRate: wilson(wins, closed.length),
91
+ pnl: r(pnls.reduce((a, b) => a + b, 0)), pnlSol: sumOf((t) => t.pnlSol), pnlUsd: sumOf((t) => t.pnlUsd),
92
+ avgPnlPct: r(mean(pcts), 4), medianPnlPct: pcts.length ? r(pcts.length % 2 ? pcts[(pcts.length - 1) / 2] : (pcts[pcts.length / 2 - 1] + pcts[pcts.length / 2]) / 2, 4) : null,
93
+ bestPct: pcts.length ? r(pcts[pcts.length - 1], 4) : null, worstPct: pcts.length ? r(pcts[0], 4) : null,
94
+ profitFactor: loss > 0 ? r(gain / loss, 4) : null,
95
+ maxDrawdown: curve.maxDrawdown, maxDrawdownPct: curve.maxDrawdownPct,
96
+ rugs, rugRate: closed.length ? r(rugs / closed.length, 4) : null,
97
+ avgHoldSec: closed.length ? Math.round(mean(closed.map((t) => t.holdSec))) : null,
98
+ avgMfePct: r(mean(closed.map((t) => t.mfePct).filter((x) => x !== null)), 4),
99
+ avgMaePct: r(mean(closed.map((t) => t.maePct).filter((x) => x !== null)), 4),
100
+ feesSol: sumOf((t) => t.feesSol), modelCostUsd: (() => { const xs = rows.map((t) => t.model.costUsd).filter((x) => x !== null); return xs.length ? r(xs.reduce((a, b) => a + b, 0)) : null; })(),
101
+ tagCoverage: { profileDeclared: declared((t) => good(t.profile.source), rows), exitDeclared: declared((t) => good(t.exitReason.source), closed) },
102
+ byProfile: group(rows, (t) => t.profile.id ?? 'undeclared', unit),
103
+ byExitReason: group(closed, (t) => t.exitReason.code ?? 'untagged', unit),
104
+ byStrategy: group(rows, strategyKey, unit),
105
+ bySource: group(rows, (t) => t.source, unit),
106
+ byMode: group(rows, (t) => t.walletMode, unit),
107
+ };
108
+ }
109
+ /** One row per strategy version (a run of the same id at another version is another row): the forward-test table. */
110
+ export function compareStrategies(rows, unit = unitFor(rows)) {
111
+ const m = new Map();
112
+ for (const t of rows) {
113
+ const k = strategyKey(t);
114
+ m.set(k, [...(m.get(k) ?? []), t]);
115
+ }
116
+ return {
117
+ unit,
118
+ rows: [...m.entries()].map(([key, ts]) => {
119
+ const s = summarize(ts, unit);
120
+ return { key, strategy: ts[0].strategy, modes: s.modes, trades: s.trades, open: s.open, winRate: s.winRate, pnl: s.pnl, avgPnlPct: s.avgPnlPct, maxDrawdown: s.maxDrawdown, rugRate: s.rugRate, avgHoldSec: s.avgHoldSec, avgMfePct: s.avgMfePct, avgMaePct: s.avgMaePct, modelCostUsd: s.modelCostUsd };
121
+ }).sort((a, b) => b.pnl - a.pnl || a.key.localeCompare(b.key)),
122
+ };
123
+ }
@@ -0,0 +1,116 @@
1
+ // task-1530 — the agent's PROFILE / REPROFILE / EXIT declarations as STRUCTURED fields.
2
+ //
3
+ // Before this card the only record of "why this entry, under which exit profile" and "why this exit" was the
4
+ // agent's free text, scraped afterwards by whoever read the transcript (a challenge driver read thinking blocks
5
+ // that the CLI streams as a paraphrase, so every fill of two sessions came out "undeclared profile / untagged
6
+ // exit" although the agent had written both lines). Now the trade tools take the declaration as typed arguments
7
+ // and the desk persists it on the fill itself (`agent.paper.fill` data.tag), so nothing is ever recovered from
8
+ // text again:
9
+ //
10
+ // paper_confirm profile, reason (and/or tag_line: the verbatim PROFILE line, parsed here)
11
+ // paper_close exit_reason, reason (and/or tag_line: the verbatim EXIT line)
12
+ // paper_tag a REPROFILE, or a PROFILE / EXIT line that was not passed with its fill (journaled as late)
13
+ //
14
+ // A typed field always wins over a parsed line; a line that does not parse is kept verbatim (capped) with source
15
+ // 'tag-line' only when it yields a profile or reason. Nothing here is an instruction: every string is one line,
16
+ // capped, control characters out, key-shaped runs redacted.
17
+ import { cleanCode, cleanLedgerText } from "./schema.js";
18
+ const B58 = '[1-9A-HJ-NP-Za-km-z]';
19
+ const stripMd = (s) => s.replace(/^[\s>*_`#-]+/, '').replace(/[*_`]+/g, '').trim();
20
+ const symKey = (s) => s.replace(/^\$/, '').replace(/[.,;:]+$/, '').toUpperCase();
21
+ /**
22
+ * Parse the tagged lines in a block of agent text (the format the exit-profile skills teach):
23
+ * PROFILE <profile> for <SYMBOL> (<first 4>…<last 4>): <why> · size <x> SOL (<p>% of bankroll)
24
+ * REPROFILE <SYMBOL> <from> -> <to>: <why>
25
+ * EXIT <SYMBOL> <reason>: <why>
26
+ */
27
+ export function parseTagLines(text) {
28
+ const out = [];
29
+ if (typeof text !== 'string')
30
+ return out;
31
+ for (const raw of text.slice(0, 4000).split('\n')) {
32
+ const line = stripMd(raw);
33
+ let m;
34
+ if ((m = line.match(/^PROFILE\s+([a-z][a-z0-9-]*)\s+for\s+(\S+?)\s*(?:\(([^)]*)\))?\s*:\s*(.+)$/i))) {
35
+ const hint = (m[3] ?? '').match(new RegExp(`^(${B58}{3,8})\\s*(?:…|\\.\\.\\.)\\s*(${B58}{3,8})$`));
36
+ const size = m[4].match(/size\s+([0-9]*\.?[0-9]+)\s*SOL/i);
37
+ out.push({ kind: 'profile', profile: m[1].toLowerCase(), symbol: symKey(m[2]), mintHead: hint?.[1] ?? null, mintTail: hint?.[2] ?? null, why: m[4].trim(), sizeSol: size ? Number(size[1]) : null, line });
38
+ }
39
+ else if ((m = line.match(/^REPROFILE\s+(\S+)\s+([a-z][a-z0-9-]*)\s*(?:->|→)\s*([a-z][a-z0-9-]*)\s*:\s*(.+)$/i))) {
40
+ out.push({ kind: 'reprofile', symbol: symKey(m[1]), from: m[2].toLowerCase(), to: m[3].toLowerCase(), why: m[4].trim(), line });
41
+ }
42
+ else if ((m = line.match(/^EXIT\s+(\S+)\s+([a-z0-9-]+)\s*:\s*(.+)$/i))) {
43
+ out.push({ kind: 'exit', symbol: symKey(m[1]), reason: m[2].toLowerCase(), why: m[3].trim(), line });
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ /** The profile / exit-reason alphabet: the strategy id rule (`^[a-z][a-z0-9-]{1,31}$`). Anything else is dropped. */
49
+ export function tagCode(x) {
50
+ const c = cleanCode(x, 32);
51
+ return c && /^[a-z][a-z0-9-]{0,31}$/.test(c) ? c : null;
52
+ }
53
+ /**
54
+ * The structured tag a fill carries, or null when the agent declared nothing. Typed fields win; a `line` fills
55
+ * whatever the typed fields left empty (source 'tag-line' when the line supplied the profile / reason code).
56
+ */
57
+ export function buildFillTag(side, input) {
58
+ if (!input || typeof input !== 'object')
59
+ return null;
60
+ let profile = side === 'buy' ? tagCode(input.profile) : null;
61
+ let exitReason = side === 'sell' ? tagCode(input.exitReason) : null;
62
+ let reason = cleanLedgerText(input.reason, 200);
63
+ const line = cleanLedgerText(input.line, 300);
64
+ const structured = side === 'buy' ? profile !== null : exitReason !== null;
65
+ let fromLine = false;
66
+ if (line) {
67
+ const tags = parseTagLines(line);
68
+ const p = side === 'buy' ? tags.find((g) => g.kind === 'profile') : tags.find((g) => g.kind === 'exit');
69
+ if (p && p.kind === 'profile') {
70
+ if (!profile) {
71
+ profile = tagCode(p.profile);
72
+ fromLine = profile !== null;
73
+ }
74
+ if (!reason)
75
+ reason = cleanLedgerText(p.why, 200);
76
+ }
77
+ if (p && p.kind === 'exit') {
78
+ if (!exitReason) {
79
+ exitReason = tagCode(p.reason);
80
+ fromLine = exitReason !== null;
81
+ }
82
+ if (!reason)
83
+ reason = cleanLedgerText(p.why, 200);
84
+ }
85
+ }
86
+ if (!profile && !exitReason && !reason && !line)
87
+ return null;
88
+ const source = structured ? 'structured' : fromLine ? 'tag-line' : 'missing';
89
+ return { profile, reason, exitReason, line, source };
90
+ }
91
+ export function buildLateTag(input) {
92
+ const line = cleanLedgerText(input.line, 300);
93
+ const reason = cleanLedgerText(input.reason, 200);
94
+ const kind = input.kind;
95
+ const parsed = line ? parseTagLines(line)[0] ?? null : null;
96
+ const k = kind === 'profile' || kind === 'reprofile' || kind === 'exit' ? kind : parsed?.kind ?? null;
97
+ if (!k)
98
+ return { ok: false, why: 'say what this declares: kind profile | reprofile | exit, or a PROFILE / REPROFILE / EXIT line' };
99
+ if (k === 'profile') {
100
+ const profile = tagCode(input.profile) ?? (parsed?.kind === 'profile' ? tagCode(parsed.profile) : null);
101
+ if (!profile)
102
+ return { ok: false, why: 'a profile tag needs a profile id (or a PROFILE line that names one)' };
103
+ return { ok: true, tagKind: 'profile', profile, reason: reason ?? (parsed?.kind === 'profile' ? cleanLedgerText(parsed.why, 200) : null), line };
104
+ }
105
+ if (k === 'reprofile') {
106
+ const to = tagCode(input.to) ?? (parsed?.kind === 'reprofile' ? tagCode(parsed.to) : null);
107
+ if (!to)
108
+ return { ok: false, why: 'a reprofile tag needs `to` (or a REPROFILE line)' };
109
+ const from = tagCode(input.from) ?? (parsed?.kind === 'reprofile' ? tagCode(parsed.from) : null);
110
+ return { ok: true, tagKind: 'reprofile', from, to, reason: reason ?? (parsed?.kind === 'reprofile' ? cleanLedgerText(parsed.why, 200) : null), line };
111
+ }
112
+ const exitReason = tagCode(input.exitReason) ?? (parsed?.kind === 'exit' ? tagCode(parsed.reason) : null);
113
+ if (!exitReason)
114
+ return { ok: false, why: 'an exit tag needs an exit reason code (or an EXIT line)' };
115
+ return { ok: true, tagKind: 'exit', exitReason, reason: reason ?? (parsed?.kind === 'exit' ? cleanLedgerText(parsed.why, 200) : null), line };
116
+ }
@@ -0,0 +1,51 @@
1
+ // task-1530 — the ledger VIEW: what the file holds plus what the sources hold that it does not have yet.
2
+ //
3
+ // Reads (`dexterm ledger trades|summary|export`, the MCP ledger tools, the PnL card) never wait for a sync and
4
+ // never write: they fold the file's events together with fresh drafts from the sources, in memory. Only
5
+ // `dexterm ledger sync` appends those drafts to the file (and so gives them a place in the hash chain).
6
+ import { join } from 'node:path';
7
+ import { paperAgentJournalPath } from "../agent/rendezvous.js";
8
+ import { dextermHomeDir, readTradeLedger, tradeLedgerPath } from "./file.js";
9
+ import { foldTrades } from "./fold.js";
10
+ import { importDeskJournal, importPaperWallet } from "./sources.js";
11
+ import { LEDGER_VERSION } from "./schema.js";
12
+ export function defaultSourcePaths(env = process.env, walletDb = null) {
13
+ return {
14
+ deskJournal: paperAgentJournalPath(env),
15
+ walletDb,
16
+ };
17
+ }
18
+ /** Drafts from every data-layer source (run ledgers are added by the caller that can read them). */
19
+ export function collectDataSources(p, o = {}) {
20
+ const desk = importDeskJournal(p.deskJournal, { walletDb: p.walletDb, ...(o.runs ? { runs: o.runs } : {}) });
21
+ const out = [desk];
22
+ if (p.walletDb)
23
+ out.push(importPaperWallet(p.walletDb, desk.fillIds));
24
+ return out;
25
+ }
26
+ /** Fold the file plus the pending drafts. A file that fails its check still folds (the view says so). */
27
+ export function buildLedgerView(path, sources, now = Date.now()) {
28
+ const file = readTradeLedger(path);
29
+ const have = new Set();
30
+ for (const e of file.events)
31
+ if ('ref' in e && typeof e.ref === 'string')
32
+ have.add(e.ref);
33
+ const pending = [];
34
+ const meta = [];
35
+ for (const s of sources) {
36
+ let n = 0;
37
+ for (const d of s.drafts) {
38
+ const ref = d.ref;
39
+ if (!ref || have.has(ref))
40
+ continue;
41
+ have.add(ref);
42
+ pending.push(d);
43
+ n++;
44
+ }
45
+ meta.push({ label: s.label, source: s.source, events: s.events, pending: n, note: s.note, problem: s.problem });
46
+ }
47
+ let seq = file.events.length;
48
+ const events = [...file.events, ...pending.map((d) => ({ ...d, seq: ++seq, v: LEDGER_VERSION, prev: 'pending', hash: 'pending' }))];
49
+ return { path, file, pending, sources: meta, events, trades: foldTrades(events, { now }) };
50
+ }
51
+ export { tradeLedgerPath };
@@ -268,6 +268,8 @@ export class PlanEngine {
268
268
  authorizeCopy(mandateId, proof, wallet) {
269
269
  if (!this.gate.handsFreeAllowed())
270
270
  throw new PolicyRefusal('gate-on', 'copy automation needs the less-safe opt-in');
271
+ if (this.mandates.get(mandateId)?.m.paperOnly)
272
+ throw new PolicyRefusal('paper-only-mandate', 'a paper-only mandate is for the agent paper desk; copy automation cannot use it');
271
273
  this.armMandate(mandateId, proof);
272
274
  return (input, supply, ref) => {
273
275
  const check = () => {
@@ -315,6 +317,9 @@ export class PlanEngine {
315
317
  const alive = mandateAlive(x.m, this.clock.now());
316
318
  if (!alive.ok)
317
319
  refuse(alive.reason, 'mandate is not alive');
320
+ // task-1469: a paper-only mandate (PAPER_HARD ceilings) feeds the agent paper desk only; no plan arms into it.
321
+ if (x.m.paperOnly)
322
+ refuse('paper-only-mandate', 'a paper-only mandate is for the agent paper desk; plans cannot arm into it');
318
323
  if (TIERS[tier].rank > TIERS[x.m.maxTier].rank)
319
324
  refuse('tier-not-allowed', `this mandate accepts up to ${x.m.maxTier}; ${tier} is weaker`);
320
325
  const v = this.admit(r, x, tier);
@@ -599,6 +604,8 @@ export class PlanEngine {
599
604
  const alive = mandateAlive(m, now);
600
605
  if (!alive.ok)
601
606
  return no(alive.reason, 'mandate not alive');
607
+ if (m.paperOnly)
608
+ return no('paper-only-mandate', 'a paper-only mandate never fires a plan');
602
609
  if (now > p.expiresAt)
603
610
  return no('plan-expired', 'plan expired');
604
611
  if (!m.allowed.actions.includes(side))
@@ -7,6 +7,5 @@ export { HOLD_MS, CHALLENGE_TTL_MS, ECHO_GUARD_MS, RISK_ACCEPT_TTL_MS, ConfirmEr
7
7
  export { Journal, redact, eventHash } from "./journal.js";
8
8
  export { RpcOutage } from "./executor.js";
9
9
  export { PaperPlanExecutor } from "./executor-paper.js";
10
- export { LivePlanExecutor, LIVE_BUILD_TTL_MS } from "./executor-live.js";
11
10
  export { PlanEngine, PolicyRefusal, TERMINAL } from "./engine.js";
12
11
  export { PlanService, parseAmount, parseTrigger, parseExit, planStatusLabel, defaultPlansJournalPath } from "./service.js";