dexterm-cli 0.1.6 → 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.
- package/dist/packages/agents/src/schema.js +22 -1
- package/dist/packages/agents/src/story.js +11 -2
- package/dist/packages/agents/src/tiers.js +27 -2
- package/dist/packages/agents/src/tools.js +30 -2
- package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
- package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
- package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
- package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
- package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
- package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
- package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
- package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
- package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
- package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
- package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
- package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
- package/dist/packages/autotrade/src/account.js +74 -0
- package/dist/packages/autotrade/src/backtest.js +357 -0
- package/dist/packages/autotrade/src/basis-text.js +32 -0
- package/dist/packages/autotrade/src/chat/approvals.js +484 -0
- package/dist/packages/autotrade/src/chat/audit.js +140 -0
- package/dist/packages/autotrade/src/chat/config.js +93 -0
- package/dist/packages/autotrade/src/chat/connector.js +24 -0
- package/dist/packages/autotrade/src/chat/control.js +103 -0
- package/dist/packages/autotrade/src/chat/fake.js +100 -0
- package/dist/packages/autotrade/src/chat/format.js +71 -0
- package/dist/packages/autotrade/src/chat/hub.js +187 -0
- package/dist/packages/autotrade/src/chat/index.js +18 -0
- package/dist/packages/autotrade/src/chat/notify.js +292 -0
- package/dist/packages/autotrade/src/chat/pairing.js +46 -0
- package/dist/packages/autotrade/src/chat/redact.js +48 -0
- package/dist/packages/autotrade/src/chat/secrets.js +99 -0
- package/dist/packages/autotrade/src/chat/service.js +248 -0
- package/dist/packages/autotrade/src/chat/telegram.js +290 -0
- package/dist/packages/autotrade/src/desk-executor.js +188 -0
- package/dist/packages/autotrade/src/engine.js +387 -0
- package/dist/packages/autotrade/src/execution-port.js +97 -0
- package/dist/packages/autotrade/src/fleet-config.js +169 -0
- package/dist/packages/autotrade/src/fleet-control.js +276 -0
- package/dist/packages/autotrade/src/fleet.js +639 -0
- package/dist/packages/autotrade/src/index.js +53 -0
- package/dist/packages/autotrade/src/ledger.js +108 -0
- package/dist/packages/autotrade/src/live-source.js +182 -0
- package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
- package/dist/packages/autotrade/src/monitor.js +190 -0
- package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
- package/dist/packages/autotrade/src/paper-accounts.js +72 -0
- package/dist/packages/autotrade/src/replay.js +198 -0
- package/dist/packages/autotrade/src/runner-config.js +99 -0
- package/dist/packages/autotrade/src/runner.js +872 -0
- package/dist/packages/autotrade/src/safety.js +243 -0
- package/dist/packages/autotrade/src/session.js +269 -0
- package/dist/packages/autotrade/src/sessions.js +503 -0
- package/dist/packages/autotrade/src/store.js +250 -0
- package/dist/packages/autotrade/src/strategy.js +256 -0
- package/dist/packages/autotrade/src/trade-ledger.js +241 -0
- package/dist/packages/autotrade/src/yolo.js +140 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
- package/dist/packages/cli/src/ai-cmd.js +412 -0
- package/dist/packages/cli/src/analyst/backend.js +82 -15
- package/dist/packages/cli/src/analyst/codex.js +14 -9
- package/dist/packages/cli/src/analyst/ollama.js +263 -0
- package/dist/packages/cli/src/analyst/session.js +5 -2
- package/dist/packages/cli/src/app-help-gen.js +44 -8
- package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
- package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
- package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
- package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
- package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
- package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
- package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
- package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
- package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
- package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
- package/dist/packages/cli/src/autotrade-cmd.js +168 -0
- package/dist/packages/cli/src/display.js +156 -17
- package/dist/packages/cli/src/doctor.js +62 -3
- package/dist/packages/cli/src/export.js +10 -2
- package/dist/packages/cli/src/index.js +1 -1
- package/dist/packages/cli/src/ledger-cmd.js +288 -0
- package/dist/packages/cli/src/live/chart-fills.js +46 -0
- package/dist/packages/cli/src/live/index.js +23 -89
- package/dist/packages/cli/src/live/screens/token-live.js +9 -6
- package/dist/packages/cli/src/live/trade.js +12 -457
- package/dist/packages/cli/src/live/watchlist.js +1 -1
- package/dist/packages/cli/src/main.js +147 -190
- package/dist/packages/cli/src/mcp-cmd.js +7 -1
- package/dist/packages/cli/src/plan-cmd.js +1 -1
- package/dist/packages/cli/src/pnl-app.js +31 -21
- package/dist/packages/cli/src/pnl-card.js +158 -21
- package/dist/packages/cli/src/popup.js +22 -6
- package/dist/packages/cli/src/record-cmd.js +141 -20
- package/dist/packages/cli/src/screens/agent-hud.js +34 -2
- package/dist/packages/cli/src/screens/analyst-session.js +7 -4
- package/dist/packages/cli/src/screens/analyst.js +3 -2
- package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
- package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
- package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
- package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
- package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
- package/dist/packages/cli/src/screens/brain.js +37 -11
- package/dist/packages/cli/src/screens/overlay.js +5 -2
- package/dist/packages/cli/src/screens/plans.js +5 -17
- package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
- package/dist/packages/cli/src/screens/positions.js +6 -28
- package/dist/packages/cli/src/screens/ticket.js +38 -205
- package/dist/packages/cli/src/screens/token.js +3 -3
- package/dist/packages/cli/src/state.js +25 -19
- package/dist/packages/cli/src/telegram-cmd.js +451 -0
- package/dist/packages/cli/src/trade.js +3 -128
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/wallet-cmd.js +39 -340
- package/dist/packages/cli/src/wallet-command.js +8 -11
- package/dist/packages/cli/src/widgets.js +0 -12
- package/dist/packages/custom/src/config.js +20 -2
- package/dist/packages/custom/src/schema.js +35 -10
- package/dist/packages/custom/src/workspace.js +3 -3
- package/dist/packages/data/src/basis.js +191 -0
- package/dist/packages/data/src/detail.js +53 -2
- package/dist/packages/data/src/index.js +0 -14
- package/dist/packages/data/src/store.js +14 -5
- package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
- package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
- package/dist/packages/data/src/trade/bridge.js +4 -102
- package/dist/packages/data/src/trade/ledger/export.js +101 -0
- package/dist/packages/data/src/trade/ledger/file.js +135 -0
- package/dist/packages/data/src/trade/ledger/fold.js +191 -0
- package/dist/packages/data/src/trade/ledger/index.js +9 -0
- package/dist/packages/data/src/trade/ledger/schema.js +83 -0
- package/dist/packages/data/src/trade/ledger/sources.js +307 -0
- package/dist/packages/data/src/trade/ledger/stats.js +123 -0
- package/dist/packages/data/src/trade/ledger/tags.js +116 -0
- package/dist/packages/data/src/trade/ledger/view.js +51 -0
- package/dist/packages/data/src/trade/plans/index.js +0 -1
- package/dist/packages/data/src/trade/plans/service.js +3 -27
- package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
- package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
- package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
- package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
- package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
- package/dist/packages/mcp/src/app-help.js +1 -1
- package/dist/packages/mcp/src/autotrade-tools.js +944 -0
- package/dist/packages/mcp/src/backtest-requests.js +71 -0
- package/dist/packages/mcp/src/engine/config.js +159 -0
- package/dist/packages/mcp/src/engine/index.js +10 -0
- package/dist/packages/mcp/src/engine/loop.js +101 -0
- package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
- package/dist/packages/mcp/src/engine/ollama.js +258 -0
- package/dist/packages/mcp/src/engine/probe.js +110 -0
- package/dist/packages/mcp/src/engine/reason.js +35 -0
- package/dist/packages/mcp/src/engine/restrictions.js +111 -0
- package/dist/packages/mcp/src/index.js +7 -1
- package/dist/packages/mcp/src/ledger-tools.js +346 -0
- package/dist/packages/mcp/src/paper-agent.js +2 -0
- package/dist/packages/mcp/src/server.js +127 -19
- package/dist/packages/packs/src/backtest.js +3 -1
- package/dist/packages/popup/src/pages/chart-lib.js +134 -21
- package/dist/packages/popup/src/pages/chart.css +9 -0
- package/dist/packages/popup/src/pages/chart.html +3 -1
- package/dist/packages/popup/src/pages/chart.js +104 -7
- package/dist/packages/popup/src/protocol.js +2 -0
- package/dist/packages/popup/src/server.js +4 -0
- package/dist/packages/recorder/src/index.js +2 -0
- package/dist/packages/recorder/src/recorder.js +63 -6
- package/dist/packages/recorder/src/retention.js +100 -0
- package/dist/packages/recorder/src/schema.js +34 -1
- package/dist/packages/recorder/src/ticks.js +237 -0
- package/dist/packages/tui/src/index.js +1 -0
- package/dist/packages/tui/src/keymap.js +21 -1
- package/dist/packages/tui/src/markers.js +219 -0
- package/dist/packages/tui/src/widgets.js +149 -2
- package/package.json +2 -1
- package/dist/packages/data/src/trade/caps.js +0 -323
- package/dist/packages/data/src/trade/executor.js +0 -933
- package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
- package/dist/packages/data/src/trade/rpc-exec.js +0 -229
- package/dist/packages/data/src/trade/signer/client.js +0 -197
- package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
- package/dist/packages/data/src/trade/signer/host.js +0 -97
- package/dist/packages/data/src/trade/signer/main.js +0 -121
- package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
- package/dist/packages/data/src/trade/signer/protocol.js +0 -92
- package/dist/packages/data/src/trade/signer/service.js +0 -400
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
- package/dist/packages/data/src/trade/sol-usd.js +0 -86
- package/dist/packages/data/src/trade/swap-build.js +0 -108
- package/dist/packages/data/src/trade/token-delta.js +0 -201
- package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @dexterm/mcp — BACKTEST REQUESTS (task-1530): an agent PROPOSES a longer backtest; the user runs it.
|
|
2
|
+
//
|
|
3
|
+
// `strategy_backtest` (task-1507) runs at most 14 days inline and is rate-limited per server. A bigger question
|
|
4
|
+
// ("run moonbag over the last 45 days, every candidate") is the user's machine time, so the agent only files a
|
|
5
|
+
// request: `backtest_request` writes `$DEXTERM_HOME/autotrade/requests/<br_…>.json` (status pending). Nothing runs
|
|
6
|
+
// until the user types `dexterm autotrade requests run <requestId>` (or dismisses it); the result line is then
|
|
7
|
+
// written back into the same file, where `backtest_requests` lets the agent read it.
|
|
8
|
+
//
|
|
9
|
+
// A request can name only a stored strategy id and a day count. It can never change a strategy, a cap or a mode,
|
|
10
|
+
// and the run is the ordinary read-only backtest on the recorder history.
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { randomBytes } from 'node:crypto';
|
|
14
|
+
export const BACKTEST_REQUEST_SCHEMA = 'dexterm-backtest-request/1';
|
|
15
|
+
export const REQUEST_MAX_DAYS = 60;
|
|
16
|
+
export const MAX_PENDING_REQUESTS = 10;
|
|
17
|
+
/** Requests per hour per server process (a request is cheap, the run is not). */
|
|
18
|
+
export const REQUEST_RATE_PER_HOUR = 6;
|
|
19
|
+
export const isRequestId = (x) => typeof x === 'string' && /^br_[0-9a-f]{12}$/.test(x);
|
|
20
|
+
export const requestsDir = (home) => join(home, 'autotrade', 'requests');
|
|
21
|
+
export class BacktestRequests {
|
|
22
|
+
home;
|
|
23
|
+
constructor(home) { this.home = home; }
|
|
24
|
+
dir() { return requestsDir(this.home); }
|
|
25
|
+
path(id) { return join(this.dir(), `${id}.json`); }
|
|
26
|
+
get(id) {
|
|
27
|
+
if (!isRequestId(id) || !existsSync(this.path(id)))
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
const r = JSON.parse(readFileSync(this.path(id), 'utf8'));
|
|
31
|
+
return r && r.schema === BACKTEST_REQUEST_SCHEMA && r.requestId === id ? r : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Newest first. */
|
|
38
|
+
list(o = {}) {
|
|
39
|
+
if (!existsSync(this.dir()))
|
|
40
|
+
return [];
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const f of readdirSync(this.dir())) {
|
|
43
|
+
const m = /^(br_[0-9a-f]{12})\.json$/.exec(f);
|
|
44
|
+
const r = m ? this.get(m[1]) : null;
|
|
45
|
+
if (r && (!o.status || r.status === o.status))
|
|
46
|
+
out.push(r);
|
|
47
|
+
}
|
|
48
|
+
return out.sort((a, b) => b.createdAt - a.createdAt || a.requestId.localeCompare(b.requestId));
|
|
49
|
+
}
|
|
50
|
+
create(r) {
|
|
51
|
+
mkdirSync(this.dir(), { recursive: true, mode: 0o700 });
|
|
52
|
+
const rec = { schema: BACKTEST_REQUEST_SCHEMA, requestId: `br_${randomBytes(6).toString('hex')}`, status: 'pending', ...r };
|
|
53
|
+
this.write(rec);
|
|
54
|
+
return rec;
|
|
55
|
+
}
|
|
56
|
+
/** The user's side only (`dexterm autotrade requests run|dismiss`): mark a request done / failed / dismissed. */
|
|
57
|
+
resolve(id, status, result, now = Date.now()) {
|
|
58
|
+
const r = this.get(id);
|
|
59
|
+
if (!r)
|
|
60
|
+
return null;
|
|
61
|
+
const next = { ...r, status, resolvedAt: now, ...(result ? { result: result.slice(0, 400) } : {}) };
|
|
62
|
+
this.write(next);
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
write(r) {
|
|
66
|
+
const p = this.path(r.requestId);
|
|
67
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
68
|
+
writeFileSync(tmp, `${JSON.stringify(r, null, 2)}\n`, { mode: 0o600 });
|
|
69
|
+
renameSync(tmp, p);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// dexterm local engine: the AI engine settings file, $DEXTERM_HOME/ai.json (task-1543).
|
|
2
|
+
//
|
|
3
|
+
// { "schema": "dexterm.ai/1",
|
|
4
|
+
// "provider": "auto" | "claude" | "codex" | "ollama", the global engine
|
|
5
|
+
// "surfaces": { "analyst": …, "autotrade": …, "packs": …, "agent": … }, per-surface overrides
|
|
6
|
+
// "restrictions": "observe" | "propose" | "paper" | "full", the local-engine profile
|
|
7
|
+
// "full": { "model": "<model>", "confirmedAt": "<iso>" } | null, the typed --full confirmation
|
|
8
|
+
// "ollama": { "host": "http://127.0.0.1:11434", "model": "<model>", "numCtx": 16384 } }
|
|
9
|
+
//
|
|
10
|
+
// Only the user writes it, with `dexterm ai …` (or by hand). No MCP tool, chat message, Telegram command or model
|
|
11
|
+
// output reaches this file. `full` counts only with a confirmation recorded for the SAME model; otherwise the
|
|
12
|
+
// profile falls back to `paper` and every surface says why. The environment can lower the profile
|
|
13
|
+
// (DEXTERM_AI_RESTRICTIONS), never raise it.
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { dextermHome } from "../user-content.js";
|
|
17
|
+
import { DEFAULT_OLLAMA_HOST, parseOllamaHost } from "./ollama.js";
|
|
18
|
+
import { DEFAULT_RESTRICTION, isRestriction, lowerOf, SURFACES } from "./restrictions.js";
|
|
19
|
+
export const AI_CONFIG_SCHEMA = 'dexterm.ai/1';
|
|
20
|
+
export const ENGINES = ['claude', 'codex', 'ollama'];
|
|
21
|
+
export const ENGINE_CHOICES = ['auto', ...ENGINES];
|
|
22
|
+
/** The phrase `dexterm ai engine ollama --full` asks the user to type. */
|
|
23
|
+
export const FULL_CONFIRM_PHRASE = 'ENABLE FULL';
|
|
24
|
+
/** Room for the analyst system prompt, the tool catalog, the live context and a few tool results (the probe clamps it to the model). */
|
|
25
|
+
export const DEFAULT_NUM_CTX = 32768;
|
|
26
|
+
export function defaultAiConfig() {
|
|
27
|
+
return { schema: AI_CONFIG_SCHEMA, provider: 'auto', surfaces: {}, restrictions: DEFAULT_RESTRICTION, full: null, ollama: { host: DEFAULT_OLLAMA_HOST, model: null, numCtx: DEFAULT_NUM_CTX } };
|
|
28
|
+
}
|
|
29
|
+
export const aiConfigPath = (env = process.env) => path.join(dextermHome(env), 'ai.json');
|
|
30
|
+
export function parseEngineChoice(v) {
|
|
31
|
+
const s = typeof v === 'string' ? v.trim().toLowerCase() : '';
|
|
32
|
+
return ENGINE_CHOICES.includes(s) ? s : null;
|
|
33
|
+
}
|
|
34
|
+
/** An Ollama model name as `ollama pull` takes it (name[:tag], optional namespace/host path). */
|
|
35
|
+
export const MODEL_NAME = /^[a-z0-9][a-z0-9._\-/]{0,120}(:[a-z0-9._-]{1,64})?$/i;
|
|
36
|
+
export function loadAiConfig(env = process.env) {
|
|
37
|
+
const p = aiConfigPath(env);
|
|
38
|
+
const c = defaultAiConfig();
|
|
39
|
+
const errors = [];
|
|
40
|
+
let raw;
|
|
41
|
+
try {
|
|
42
|
+
raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
if (e.code === 'ENOENT')
|
|
46
|
+
return { config: c, exists: false, path: p, errors };
|
|
47
|
+
return { config: c, exists: true, path: p, errors: [`${p} is not valid JSON; using the defaults`] };
|
|
48
|
+
}
|
|
49
|
+
const o = (raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {});
|
|
50
|
+
if (o.provider !== undefined) {
|
|
51
|
+
const v = parseEngineChoice(o.provider);
|
|
52
|
+
if (v)
|
|
53
|
+
c.provider = v;
|
|
54
|
+
else
|
|
55
|
+
errors.push('provider must be auto|claude|codex|ollama');
|
|
56
|
+
}
|
|
57
|
+
if (o.surfaces && typeof o.surfaces === 'object') {
|
|
58
|
+
for (const [k, v] of Object.entries(o.surfaces)) {
|
|
59
|
+
const e = parseEngineChoice(v);
|
|
60
|
+
if (SURFACES.includes(k) && e)
|
|
61
|
+
c.surfaces[k] = e;
|
|
62
|
+
else
|
|
63
|
+
errors.push(`surfaces.${k.slice(0, 24)} is not a surface (${SURFACES.join('|')}) with an engine`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (o.restrictions !== undefined) {
|
|
67
|
+
if (isRestriction(o.restrictions))
|
|
68
|
+
c.restrictions = o.restrictions;
|
|
69
|
+
else
|
|
70
|
+
errors.push('restrictions must be observe|propose|paper|full');
|
|
71
|
+
}
|
|
72
|
+
const full = o.full;
|
|
73
|
+
if (full && typeof full === 'object' && typeof full.model === 'string' && typeof full.confirmedAt === 'string')
|
|
74
|
+
c.full = { model: full.model, confirmedAt: full.confirmedAt };
|
|
75
|
+
const ol = o.ollama;
|
|
76
|
+
if (ol && typeof ol === 'object') {
|
|
77
|
+
if (typeof ol.host === 'string') {
|
|
78
|
+
const h = parseOllamaHost(ol.host);
|
|
79
|
+
if ('error' in h)
|
|
80
|
+
errors.push(`ollama.host: ${h.error}`);
|
|
81
|
+
else
|
|
82
|
+
c.ollama.host = h.url;
|
|
83
|
+
}
|
|
84
|
+
if (typeof ol.model === 'string' && MODEL_NAME.test(ol.model))
|
|
85
|
+
c.ollama.model = ol.model;
|
|
86
|
+
else if (ol.model !== undefined && ol.model !== null)
|
|
87
|
+
errors.push('ollama.model is not a model name');
|
|
88
|
+
if (typeof ol.numCtx === 'number' && Number.isInteger(ol.numCtx) && ol.numCtx >= 2048 && ol.numCtx <= 1_048_576)
|
|
89
|
+
c.ollama.numCtx = ol.numCtx;
|
|
90
|
+
else if (ol.numCtx !== undefined)
|
|
91
|
+
errors.push('ollama.numCtx must be an integer 2048..1048576');
|
|
92
|
+
}
|
|
93
|
+
return { config: c, exists: true, path: p, errors };
|
|
94
|
+
}
|
|
95
|
+
/** Write ai.json (0600, atomic). Only the `dexterm ai` command calls this. */
|
|
96
|
+
export function saveAiConfig(c, env = process.env) {
|
|
97
|
+
const p = aiConfigPath(env);
|
|
98
|
+
fs.mkdirSync(path.dirname(p), { recursive: true, mode: 0o700 });
|
|
99
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
100
|
+
fs.writeFileSync(tmp, JSON.stringify(c, null, 2) + '\n', { mode: 0o600 });
|
|
101
|
+
fs.renameSync(tmp, p);
|
|
102
|
+
return p;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The profile that applies now. `full` needs a confirmation for the same model; the env may only lower it.
|
|
106
|
+
*/
|
|
107
|
+
export function effectiveRestriction(c, model, env = process.env) {
|
|
108
|
+
let level = c.restrictions;
|
|
109
|
+
let note = null;
|
|
110
|
+
if (level === 'full' && (!c.full || !model || c.full.model !== model)) {
|
|
111
|
+
level = 'paper';
|
|
112
|
+
note = c.full && model && c.full.model !== model
|
|
113
|
+
? `full was confirmed for ${c.full.model}, not ${model}: running at paper until you confirm again (dexterm ai engine ollama --full)`
|
|
114
|
+
: 'full needs the typed confirmation (dexterm ai engine ollama --full): running at paper';
|
|
115
|
+
}
|
|
116
|
+
const lower = env.DEXTERM_AI_RESTRICTIONS;
|
|
117
|
+
if (isRestriction(lower) && lowerOf(lower, level) !== level) {
|
|
118
|
+
level = lower;
|
|
119
|
+
note = `DEXTERM_AI_RESTRICTIONS lowers the profile to ${lower}`;
|
|
120
|
+
}
|
|
121
|
+
return { level, note };
|
|
122
|
+
}
|
|
123
|
+
/** Host / model / context, with the env overrides (DEXTERM_OLLAMA_HOST, DEXTERM_OLLAMA_MODEL). */
|
|
124
|
+
export function ollamaSettings(c, env = process.env) {
|
|
125
|
+
let hostError = null;
|
|
126
|
+
let host = parseOllamaHost(env.DEXTERM_OLLAMA_HOST || c.ollama.host);
|
|
127
|
+
if ('error' in host) {
|
|
128
|
+
hostError = `DEXTERM_OLLAMA_HOST: ${host.error}`;
|
|
129
|
+
host = parseOllamaHost(DEFAULT_OLLAMA_HOST);
|
|
130
|
+
}
|
|
131
|
+
const envModel = env.DEXTERM_OLLAMA_MODEL && MODEL_NAME.test(env.DEXTERM_OLLAMA_MODEL) ? env.DEXTERM_OLLAMA_MODEL : null;
|
|
132
|
+
return { host, model: envModel ?? c.ollama.model, numCtx: c.ollama.numCtx, hostError };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The engine CHOICE for a surface: DEXTERM_AI_PROVIDER, then the per-surface override, then the global provider.
|
|
136
|
+
* (The analyst's own flag / DEXTERM_ANALYST_ENGINE / config analyst.engine still win for the analyst; main.ts.)
|
|
137
|
+
*/
|
|
138
|
+
export function engineChoiceFor(surface, c, env = process.env) {
|
|
139
|
+
return parseEngineChoice(env.DEXTERM_AI_PROVIDER) ?? c.surfaces[surface] ?? c.provider;
|
|
140
|
+
}
|
|
141
|
+
/** Is a local Ollama setup configured (a model chosen)? `auto` uses it only then, after claude and codex. */
|
|
142
|
+
export function ollamaConfigured(c, env = process.env) {
|
|
143
|
+
return !!ollamaSettings(c, env).model;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Resolve a choice to an engine. `auto`: claude when installed, else codex, else ollama when a model is set up,
|
|
147
|
+
* else claude (whose missing-CLI error explains how to set up an AI backend).
|
|
148
|
+
*/
|
|
149
|
+
export function resolveEngineChoice(choice, has) {
|
|
150
|
+
if (choice !== 'auto')
|
|
151
|
+
return choice;
|
|
152
|
+
if (has('claude'))
|
|
153
|
+
return 'claude';
|
|
154
|
+
if (has('codex'))
|
|
155
|
+
return 'codex';
|
|
156
|
+
if (has('ollama'))
|
|
157
|
+
return 'ollama';
|
|
158
|
+
return 'claude';
|
|
159
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @dexterm/mcp/engine — the local AI engine (Ollama) behind the same tools and limits as the claude path (task-1543).
|
|
2
|
+
// It never builds, signs or sends a transaction: it reads, proposes, and (by the user's profile) requests paper or
|
|
3
|
+
// live orders that the engine-independent caps, mandates, kill switch and live gating decide.
|
|
4
|
+
export { DEFAULT_OLLAMA_HOST, DEFAULT_OLLAMA_PORT, OllamaClient, OllamaError, REDIRECT_REFUSED, isLoopbackHost, parseOllamaHost, remoteHostWarning, stripThink } from "./ollama.js";
|
|
5
|
+
export { RESTRICTION_LEVELS, RESTRICTION_RANK, RESTRICTION_HELP, DEFAULT_RESTRICTION, CAPABILITY_LEVEL, NEVER_TOOL, SURFACES, ENGINE_PARITY, isRestriction, atLeast, lowerOf, allows, engineToolAllowlist, requiredLevel } from "./restrictions.js";
|
|
6
|
+
export { AI_CONFIG_SCHEMA, ENGINES, ENGINE_CHOICES, FULL_CONFIRM_PHRASE, DEFAULT_NUM_CTX, MODEL_NAME, aiConfigPath, defaultAiConfig, loadAiConfig, saveAiConfig, effectiveRestriction, ollamaSettings, engineChoiceFor, ollamaConfigured, parseEngineChoice, resolveEngineChoice } from "./config.js";
|
|
7
|
+
export { McpStdioClient, toOllamaTools } from "./mcp-client.js";
|
|
8
|
+
export { runToolLoop, structuredAnswer, DEFAULT_MAX_STEPS, DEFAULT_MAX_REFUSALS } from "./loop.js";
|
|
9
|
+
export { probeModel, belowBar, pickModel, BAR, SUGGESTED_MODELS } from "./probe.js";
|
|
10
|
+
export { ollamaReasonRunner } from "./reason.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// dexterm local engine: the tool loop (task-1543).
|
|
2
|
+
//
|
|
3
|
+
// The claude CLI runs its own agent loop; on a local engine dexterm runs it: send the conversation and the
|
|
4
|
+
// allowlisted tools to Ollama, execute each tool call the model asks for through the dexterm MCP server, feed
|
|
5
|
+
// the results back, repeat until the model answers without a tool call (or a step cap is hit).
|
|
6
|
+
//
|
|
7
|
+
// A call to a tool outside the allowlist is NEVER executed: the model gets "refused" back and the attempt is
|
|
8
|
+
// counted; after `maxRefusals` the turn stops (the codex path's tripwire, softened because small local models
|
|
9
|
+
// often invent tool names). Tool results are the MCP server's output, already firewalled.
|
|
10
|
+
import { OllamaError, stripThink } from "./ollama.js";
|
|
11
|
+
export const DEFAULT_MAX_STEPS = 8;
|
|
12
|
+
export const DEFAULT_MAX_REFUSALS = 3;
|
|
13
|
+
export const DEFAULT_MAX_TOOL_RESULT = 24_000;
|
|
14
|
+
export async function runToolLoop(o) {
|
|
15
|
+
const maxSteps = o.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
16
|
+
const maxRefusals = o.maxRefusals ?? DEFAULT_MAX_REFUSALS;
|
|
17
|
+
const cap = o.maxToolResultChars ?? DEFAULT_MAX_TOOL_RESULT;
|
|
18
|
+
const allowed = new Set(o.allowed);
|
|
19
|
+
const tools = o.tools && o.tools.length ? o.tools : undefined;
|
|
20
|
+
const res = { text: '', steps: 0, toolCalls: 0, refusals: 0, promptTokens: 0, evalTokens: 0, model: null, stopped: null };
|
|
21
|
+
const options = o.numCtx ? { num_ctx: o.numCtx } : undefined;
|
|
22
|
+
for (;;) {
|
|
23
|
+
if (o.signal?.aborted)
|
|
24
|
+
throw new OllamaError('aborted', 'cancelled');
|
|
25
|
+
const lastStep = res.steps >= maxSteps;
|
|
26
|
+
res.steps++;
|
|
27
|
+
// A think block streams as content on some models: hold it back from the user, show only the answer.
|
|
28
|
+
let shown = 0;
|
|
29
|
+
let raw = '';
|
|
30
|
+
const onDelta = o.onText ? (d) => {
|
|
31
|
+
raw += d;
|
|
32
|
+
const visible = stripThink(raw);
|
|
33
|
+
if (raw.includes('<think>') && !/<\/think>/.test(raw))
|
|
34
|
+
return;
|
|
35
|
+
if (visible.length > shown) {
|
|
36
|
+
o.onText(visible.slice(shown));
|
|
37
|
+
shown = visible.length;
|
|
38
|
+
}
|
|
39
|
+
} : undefined;
|
|
40
|
+
const r = await o.client.chat({ model: o.model, messages: o.messages, ...(tools && !lastStep ? { tools } : {}), ...(options ? { options } : {}) }, { onDelta, signal: o.signal });
|
|
41
|
+
res.promptTokens += r.promptTokens;
|
|
42
|
+
res.evalTokens += r.evalTokens;
|
|
43
|
+
res.model = r.model ?? res.model;
|
|
44
|
+
const content = stripThink(r.content).trim();
|
|
45
|
+
const calls = tools && !lastStep ? r.toolCalls : [];
|
|
46
|
+
o.messages.push({ role: 'assistant', content: r.content, ...(calls.length ? { tool_calls: calls } : {}) });
|
|
47
|
+
if (!calls.length) {
|
|
48
|
+
res.text = content;
|
|
49
|
+
if (lastStep && r.toolCalls.length)
|
|
50
|
+
res.stopped = 'steps';
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
for (const c of calls) {
|
|
54
|
+
const name = c.function.name;
|
|
55
|
+
if (!allowed.has(name)) {
|
|
56
|
+
res.refusals++;
|
|
57
|
+
o.onRefused?.(name);
|
|
58
|
+
o.messages.push({ role: 'tool', tool_name: name, content: JSON.stringify({ error: `refused: ${name.slice(0, 64)} is not a tool you have in this session` }) });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
res.toolCalls++;
|
|
62
|
+
o.onToolCall?.(name, c.function.arguments);
|
|
63
|
+
let text;
|
|
64
|
+
try {
|
|
65
|
+
text = await o.callTool(name, c.function.arguments);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
text = JSON.stringify({ error: e.message.slice(0, 300) });
|
|
69
|
+
}
|
|
70
|
+
o.onToolResult?.(name, text);
|
|
71
|
+
o.messages.push({ role: 'tool', tool_name: name, content: text.length > cap ? `${text.slice(0, cap)}…(truncated)` : text });
|
|
72
|
+
}
|
|
73
|
+
if (res.refusals >= maxRefusals) {
|
|
74
|
+
res.stopped = 'refusals';
|
|
75
|
+
res.text = content;
|
|
76
|
+
return res;
|
|
77
|
+
}
|
|
78
|
+
// At the step cap the next round is the last one: no tools, answer with what you have.
|
|
79
|
+
if (res.steps >= maxSteps)
|
|
80
|
+
o.messages.push({ role: 'user', content: 'Tool budget reached for this turn. Answer now from the tool results above; do not call another tool.' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Ask for the final answer as JSON matching a schema (Ollama structured outputs), after a tool loop. Returns the
|
|
85
|
+
* parsed object, or null when the model did not produce valid JSON.
|
|
86
|
+
*/
|
|
87
|
+
export async function structuredAnswer(o) {
|
|
88
|
+
o.messages.push({ role: 'user', content: o.instruction ?? 'Now return ONLY the final JSON object that the output schema requires, filled from the tool results and the context above. No prose.' });
|
|
89
|
+
const r = await o.client.chat({ model: o.model, messages: o.messages, format: o.schema, ...(o.numCtx ? { options: { num_ctx: o.numCtx } } : {}) }, { signal: o.signal });
|
|
90
|
+
const raw = stripThink(r.content).trim();
|
|
91
|
+
o.messages.push({ role: 'assistant', content: r.content });
|
|
92
|
+
let value = null;
|
|
93
|
+
try {
|
|
94
|
+
const v = JSON.parse(raw.replace(/^```(?:json)?\s*|\s*```$/g, ''));
|
|
95
|
+
value = v && typeof v === 'object' && !Array.isArray(v) ? v : null;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
value = null;
|
|
99
|
+
}
|
|
100
|
+
return { value, raw, promptTokens: r.promptTokens, evalTokens: r.evalTokens };
|
|
101
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// dexterm local engine: a small MCP client over stdio (task-1543).
|
|
2
|
+
//
|
|
3
|
+
// A local engine has no MCP client of its own, so dexterm is the client: it starts the SAME read-only dexterm
|
|
4
|
+
// MCP server the claude path starts (same binary, same env: screen state, the authenticated UI-action channel,
|
|
5
|
+
// the user's DEXTERM_HOME), lists its tools and calls them on the model's behalf. The server's own checks (the
|
|
6
|
+
// prompt firewall on every token/social string, the UI-action validator, the paper desk gate) therefore apply
|
|
7
|
+
// to both engines byte for byte.
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import readline from 'node:readline';
|
|
10
|
+
export class McpStdioClient {
|
|
11
|
+
child;
|
|
12
|
+
pending = new Map();
|
|
13
|
+
seq = 0;
|
|
14
|
+
closed = false;
|
|
15
|
+
stderr = '';
|
|
16
|
+
constructor(child) {
|
|
17
|
+
this.child = child;
|
|
18
|
+
const rl = readline.createInterface({ input: child.stdout });
|
|
19
|
+
rl.on('line', (line) => {
|
|
20
|
+
if (!line.trim())
|
|
21
|
+
return;
|
|
22
|
+
let msg;
|
|
23
|
+
try {
|
|
24
|
+
msg = JSON.parse(line);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (typeof msg.id !== 'number')
|
|
30
|
+
return;
|
|
31
|
+
const p = this.pending.get(msg.id);
|
|
32
|
+
if (!p)
|
|
33
|
+
return;
|
|
34
|
+
this.pending.delete(msg.id);
|
|
35
|
+
if (msg.error)
|
|
36
|
+
p.reject(new Error(String(msg.error.message ?? 'MCP error')));
|
|
37
|
+
else
|
|
38
|
+
p.resolve(msg.result ?? {});
|
|
39
|
+
});
|
|
40
|
+
child.stderr?.on('data', (d) => { this.stderr = (this.stderr + d.toString()).slice(-4000); });
|
|
41
|
+
child.stdin?.on('error', () => { });
|
|
42
|
+
const die = (why) => {
|
|
43
|
+
this.closed = true;
|
|
44
|
+
for (const p of this.pending.values())
|
|
45
|
+
p.reject(new Error(`${why}${this.stderr ? ` · ${this.stderr.trim().split('\n').slice(-2).join(' ').slice(0, 200)}` : ''}`));
|
|
46
|
+
this.pending.clear();
|
|
47
|
+
};
|
|
48
|
+
child.on('exit', (code) => die(`the dexterm MCP server exited (${code})`));
|
|
49
|
+
child.on('error', (e) => die(`could not start the dexterm MCP server (${e.message})`));
|
|
50
|
+
}
|
|
51
|
+
static async start(o) {
|
|
52
|
+
const child = (o.spawnImpl ?? spawn)(o.command, o.args, { cwd: o.cwd, env: o.env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
53
|
+
const c = new McpStdioClient(child);
|
|
54
|
+
const timer = setTimeout(() => c.close(), o.timeoutMs ?? 30_000);
|
|
55
|
+
timer.unref?.();
|
|
56
|
+
try {
|
|
57
|
+
await c.request('initialize', { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'dexterm-local-engine', version: '1' } });
|
|
58
|
+
c.notify('notifications/initialized');
|
|
59
|
+
return c;
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
c.close();
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
request(method, params) {
|
|
70
|
+
if (this.closed)
|
|
71
|
+
return Promise.reject(new Error('the dexterm MCP server is not running'));
|
|
72
|
+
const id = ++this.seq;
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
this.pending.set(id, { resolve, reject });
|
|
75
|
+
this.child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, ...(params ? { params } : {}) }) + '\n');
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
notify(method) {
|
|
79
|
+
if (!this.closed)
|
|
80
|
+
this.child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method }) + '\n');
|
|
81
|
+
}
|
|
82
|
+
async listTools() {
|
|
83
|
+
const r = await this.request('tools/list');
|
|
84
|
+
const tools = Array.isArray(r.tools) ? r.tools : [];
|
|
85
|
+
return tools.map((t) => ({ name: String(t.name), description: String(t.description ?? ''), inputSchema: (t.inputSchema && typeof t.inputSchema === 'object' ? t.inputSchema : { type: 'object', properties: {} }) }));
|
|
86
|
+
}
|
|
87
|
+
/** Call a tool; returns its text content (the dexterm server answers with one JSON text block). */
|
|
88
|
+
async callTool(name, args) {
|
|
89
|
+
const r = await this.request('tools/call', { name, arguments: args });
|
|
90
|
+
const content = Array.isArray(r.content) ? r.content : [];
|
|
91
|
+
return content.filter((c) => c.type === 'text').map((c) => String(c.text ?? '')).join('');
|
|
92
|
+
}
|
|
93
|
+
close() {
|
|
94
|
+
if (this.closed)
|
|
95
|
+
return;
|
|
96
|
+
this.closed = true;
|
|
97
|
+
try {
|
|
98
|
+
this.child.stdin?.end();
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
try {
|
|
102
|
+
this.child.kill('SIGTERM');
|
|
103
|
+
}
|
|
104
|
+
catch { /* ignore */ }
|
|
105
|
+
try {
|
|
106
|
+
this.child.stdout?.destroy();
|
|
107
|
+
}
|
|
108
|
+
catch { /* ignore */ }
|
|
109
|
+
try {
|
|
110
|
+
this.child.stderr?.destroy();
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
try {
|
|
114
|
+
this.child.unref();
|
|
115
|
+
}
|
|
116
|
+
catch { /* ignore */ }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** MCP tool definitions as Ollama function tools, limited to the allowlist (order kept). */
|
|
120
|
+
export function toOllamaTools(defs, allowed) {
|
|
121
|
+
const ok = new Set(allowed);
|
|
122
|
+
return defs.filter((d) => ok.has(d.name)).map((d) => ({ type: 'function', function: { name: d.name, description: d.description.slice(0, 400), parameters: d.inputSchema } }));
|
|
123
|
+
}
|