dexterm-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +379 -0
- package/dist/packages/agents/src/executor.js +177 -0
- package/dist/packages/agents/src/gate.js +176 -0
- package/dist/packages/agents/src/identity.js +179 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +114 -0
- package/dist/packages/agents/src/tools.js +69 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +303 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +149 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +361 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +509 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5583 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +339 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +515 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +208 -0
- package/dist/packages/data/src/intel/helius-history.js +59 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +170 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +247 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +849 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +19 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +15 -0
- package/dist/packages/tui/src/keymap.js +355 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +395 -0
- package/dist/packages/voice/src/doctor.js +63 -0
- package/dist/packages/voice/src/engines.js +44 -0
- package/dist/packages/voice/src/index.js +16 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +102 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/say.js +96 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +42 -4
- package/README.md +0 -3
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// @dexterm/agents — the proposal store (task-1345, tier 2 interface; tier 3 enriches it).
|
|
2
|
+
//
|
|
3
|
+
// An agent can only ever create a PROPOSAL: a draft alert / order plan / watchlist add, labelled with the
|
|
4
|
+
// agent that made it, that the user confirms with a physical keypress. The store NEVER confirms a proposal
|
|
5
|
+
// on an agent's behalf (`confirm`/`dismiss` are user actions, keyed by a separate caller), enforces
|
|
6
|
+
// idempotency (the same agent + key returns the same proposal instead of a second one), and firewalls the
|
|
7
|
+
// agent-authored display text before it reaches the terminal or the roster. Nothing here arms, signs or
|
|
8
|
+
// sends anything; an order-plan proposal is a draft with `armed: false`, full stop.
|
|
9
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
10
|
+
import { mkdirSync } from 'node:fs';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
12
|
+
import { firewallText } from '../../data/src/index.js';
|
|
13
|
+
export const TOOL_TO_KIND = Object.freeze({
|
|
14
|
+
propose_alert: 'alert', propose_order_plan: 'order_plan', add_to_watchlist: 'watchlist',
|
|
15
|
+
});
|
|
16
|
+
const fw = (s, max = 200) => firewallText(s === undefined || s === null ? '' : String(s), { max }).text.replace(/\s+/g, ' ').trim();
|
|
17
|
+
/** Build the firewalled summary + detail for a proposal. Pure. */
|
|
18
|
+
export function proposalView(kind, mint, args) {
|
|
19
|
+
const short = mint.slice(0, 6);
|
|
20
|
+
if (kind === 'alert') {
|
|
21
|
+
const when = fw(args.when, 120);
|
|
22
|
+
return { summary: `alert ${short}… when ${when}`, detail: { mint, when, note: fw(args.note) } };
|
|
23
|
+
}
|
|
24
|
+
if (kind === 'order_plan') {
|
|
25
|
+
const side = args.side === 'sell' ? 'sell' : 'buy';
|
|
26
|
+
const size = fw(args.size, 40);
|
|
27
|
+
const triggers = Array.isArray(args.triggers) ? args.triggers.slice(0, 8).map((t) => fw(t, 80)) : [];
|
|
28
|
+
return { summary: `PAPER ${side} ${short}… size ${size} (DRAFT — not armed)`, detail: { mint, side, size, triggers, note: fw(args.note), armed: false } };
|
|
29
|
+
}
|
|
30
|
+
return { summary: `watchlist add ${short}…`, detail: { mint, note: fw(args.note) } };
|
|
31
|
+
}
|
|
32
|
+
export class MemoryProposalStore {
|
|
33
|
+
rows = [];
|
|
34
|
+
seq = 0;
|
|
35
|
+
now;
|
|
36
|
+
constructor(opts = {}) { this.now = opts.now ?? (() => Date.now()); }
|
|
37
|
+
create(session, tool, args) {
|
|
38
|
+
const kind = TOOL_TO_KIND[tool];
|
|
39
|
+
if (!kind)
|
|
40
|
+
throw new Error(`not a proposal tool: ${tool}`);
|
|
41
|
+
const key = String(args.idempotency_key);
|
|
42
|
+
const existing = this.rows.find((p) => p.agentId === session.agentId && p.idempotencyKey === key);
|
|
43
|
+
if (existing)
|
|
44
|
+
return { proposal: { ...existing }, duplicate: true };
|
|
45
|
+
const mint = String(args.mint);
|
|
46
|
+
const { summary, detail } = proposalView(kind, mint, args);
|
|
47
|
+
const proposal = {
|
|
48
|
+
id: `prop_${(++this.seq).toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
49
|
+
agentId: session.agentId, agentName: session.agentName, kind, mint, idempotencyKey: key,
|
|
50
|
+
createdAt: this.now(), status: 'pending', summary, detail, armed: false,
|
|
51
|
+
};
|
|
52
|
+
this.rows.push(proposal);
|
|
53
|
+
return { proposal: { ...proposal }, duplicate: false };
|
|
54
|
+
}
|
|
55
|
+
list(filter = {}) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (let i = this.rows.length - 1; i >= 0 && out.length < (filter.limit ?? 100); i--) {
|
|
58
|
+
const p = this.rows[i];
|
|
59
|
+
if (filter.agentId && p.agentId !== filter.agentId)
|
|
60
|
+
continue;
|
|
61
|
+
if (filter.status && p.status !== filter.status)
|
|
62
|
+
continue;
|
|
63
|
+
out.push({ ...p });
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
get(id) { const p = this.rows.find((x) => x.id === id); return p ? { ...p } : undefined; }
|
|
68
|
+
setStatus(id, status) {
|
|
69
|
+
const p = this.rows.find((x) => x.id === id);
|
|
70
|
+
if (!p)
|
|
71
|
+
return undefined;
|
|
72
|
+
p.status = status; // confirming NEVER arms: an order plan still needs a physical keypress in the signer flow
|
|
73
|
+
return { ...p };
|
|
74
|
+
}
|
|
75
|
+
close() { }
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Persistent store in the shared dexterm SQLite DB so the TUI roster, `dexterm agent proposals` and a
|
|
79
|
+
* later confirm flow read the same rows across processes. The UNIQUE (agent_id, idempotency_key) index
|
|
80
|
+
* makes idempotency a database guarantee, not just a scan. Display strings are firewalled before insert.
|
|
81
|
+
*/
|
|
82
|
+
export class SqliteProposalStore {
|
|
83
|
+
db;
|
|
84
|
+
now;
|
|
85
|
+
seq = 0;
|
|
86
|
+
constructor(path, opts = {}) {
|
|
87
|
+
if (path !== ':memory:')
|
|
88
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
89
|
+
this.db = new DatabaseSync(path);
|
|
90
|
+
this.now = opts.now ?? (() => Date.now());
|
|
91
|
+
this.db.exec(`
|
|
92
|
+
PRAGMA journal_mode = WAL;
|
|
93
|
+
CREATE TABLE IF NOT EXISTS agent_proposal (
|
|
94
|
+
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, agent_name TEXT NOT NULL, kind TEXT NOT NULL,
|
|
95
|
+
mint TEXT NOT NULL, idem TEXT NOT NULL, created_at INTEGER NOT NULL, status TEXT NOT NULL,
|
|
96
|
+
summary TEXT NOT NULL, detail TEXT NOT NULL
|
|
97
|
+
);
|
|
98
|
+
CREATE UNIQUE INDEX IF NOT EXISTS agent_proposal_idem ON agent_proposal(agent_id, idem);
|
|
99
|
+
CREATE INDEX IF NOT EXISTS agent_proposal_agent ON agent_proposal(agent_id, created_at);
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
row(r) {
|
|
103
|
+
return { id: String(r.id), agentId: String(r.agent_id), agentName: String(r.agent_name), kind: r.kind, mint: String(r.mint), idempotencyKey: String(r.idem), createdAt: Number(r.created_at), status: r.status, summary: String(r.summary), detail: JSON.parse(String(r.detail)), armed: false };
|
|
104
|
+
}
|
|
105
|
+
create(session, tool, args) {
|
|
106
|
+
const kind = TOOL_TO_KIND[tool];
|
|
107
|
+
if (!kind)
|
|
108
|
+
throw new Error(`not a proposal tool: ${tool}`);
|
|
109
|
+
const key = String(args.idempotency_key);
|
|
110
|
+
const existingRow = this.db.prepare('SELECT * FROM agent_proposal WHERE agent_id = ? AND idem = ?').get(session.agentId, key);
|
|
111
|
+
if (existingRow)
|
|
112
|
+
return { proposal: this.row(existingRow), duplicate: true };
|
|
113
|
+
const mint = String(args.mint);
|
|
114
|
+
const { summary, detail } = proposalView(kind, mint, args);
|
|
115
|
+
const id = `prop_${(++this.seq).toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
116
|
+
const createdAt = this.now();
|
|
117
|
+
try {
|
|
118
|
+
this.db.prepare('INSERT INTO agent_proposal (id, agent_id, agent_name, kind, mint, idem, created_at, status, summary, detail) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
119
|
+
.run(id, session.agentId, session.agentName, kind, mint, key, createdAt, 'pending', summary, JSON.stringify(detail));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// a concurrent insert won the UNIQUE race: return the row that landed
|
|
123
|
+
const r = this.db.prepare('SELECT * FROM agent_proposal WHERE agent_id = ? AND idem = ?').get(session.agentId, key);
|
|
124
|
+
if (r)
|
|
125
|
+
return { proposal: this.row(r), duplicate: true };
|
|
126
|
+
throw new Error('proposal insert failed');
|
|
127
|
+
}
|
|
128
|
+
return { proposal: { id, agentId: session.agentId, agentName: session.agentName, kind, mint, idempotencyKey: key, createdAt, status: 'pending', summary, detail, armed: false }, duplicate: false };
|
|
129
|
+
}
|
|
130
|
+
list(filter = {}) {
|
|
131
|
+
const where = [];
|
|
132
|
+
const params = [];
|
|
133
|
+
if (filter.agentId) {
|
|
134
|
+
where.push('agent_id = ?');
|
|
135
|
+
params.push(filter.agentId);
|
|
136
|
+
}
|
|
137
|
+
if (filter.status) {
|
|
138
|
+
where.push('status = ?');
|
|
139
|
+
params.push(filter.status);
|
|
140
|
+
}
|
|
141
|
+
const sql = `SELECT * FROM agent_proposal${where.length ? ' WHERE ' + where.join(' AND ') : ''} ORDER BY created_at DESC LIMIT ?`;
|
|
142
|
+
return this.db.prepare(sql).all(...params, Math.max(1, Math.min(filter.limit ?? 100, 500))).map((r) => this.row(r));
|
|
143
|
+
}
|
|
144
|
+
get(id) { const r = this.db.prepare('SELECT * FROM agent_proposal WHERE id = ?').get(id); return r ? this.row(r) : undefined; }
|
|
145
|
+
setStatus(id, status) {
|
|
146
|
+
this.db.prepare('UPDATE agent_proposal SET status = ? WHERE id = ?').run(status, id);
|
|
147
|
+
return this.get(id);
|
|
148
|
+
}
|
|
149
|
+
close() { try {
|
|
150
|
+
this.db.close();
|
|
151
|
+
}
|
|
152
|
+
catch { /* ignore */ } }
|
|
153
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @dexterm/agents — a small, total JSON-schema checker for tool arguments (task-1345).
|
|
2
|
+
//
|
|
3
|
+
// Covers the subset the dexterm tool table uses: object/string/integer/number/boolean, enum, minimum,
|
|
4
|
+
// maximum, maxLength, required, additionalProperties:false, nested objects. Anything the schema does
|
|
5
|
+
// not mention is an error (schemas are closed: `additionalProperties` defaults to false for agent
|
|
6
|
+
// calls even when a schema omits it). Pure and never throws.
|
|
7
|
+
const MAX_STRING = 2000;
|
|
8
|
+
const MAX_DEPTH = 6;
|
|
9
|
+
const MAX_KEYS = 32;
|
|
10
|
+
export function validateArgs(schema, value, path = 'args', depth = 0) {
|
|
11
|
+
if (depth > MAX_DEPTH)
|
|
12
|
+
return `${path}: too deeply nested`;
|
|
13
|
+
if (!schema)
|
|
14
|
+
return value === undefined || (typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) ? null : `${path}: this tool takes no arguments`;
|
|
15
|
+
const t = schema.type ?? (schema.properties ? 'object' : undefined);
|
|
16
|
+
if (schema.enum && !schema.enum.includes(value))
|
|
17
|
+
return `${path}: must be one of ${schema.enum.map((v) => JSON.stringify(v)).join(', ')}`;
|
|
18
|
+
switch (t) {
|
|
19
|
+
case 'object': {
|
|
20
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
21
|
+
return `${path}: must be an object`;
|
|
22
|
+
const obj = value;
|
|
23
|
+
const keys = Object.keys(obj);
|
|
24
|
+
if (keys.length > MAX_KEYS)
|
|
25
|
+
return `${path}: too many fields`;
|
|
26
|
+
const props = schema.properties ?? {};
|
|
27
|
+
for (const k of keys) {
|
|
28
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype')
|
|
29
|
+
return `${path}.${k}: not allowed`;
|
|
30
|
+
if (!Object.hasOwn(props, k))
|
|
31
|
+
return `${path}.${k}: unknown field`;
|
|
32
|
+
}
|
|
33
|
+
for (const r of schema.required ?? [])
|
|
34
|
+
if (!Object.hasOwn(obj, r) || obj[r] === undefined)
|
|
35
|
+
return `${path}.${r}: required`;
|
|
36
|
+
for (const k of keys) {
|
|
37
|
+
const err = validateArgs(props[k], obj[k], `${path}.${k}`, depth + 1);
|
|
38
|
+
if (err)
|
|
39
|
+
return err;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
case 'string': {
|
|
44
|
+
if (typeof value !== 'string')
|
|
45
|
+
return `${path}: must be a string`;
|
|
46
|
+
const max = Math.min(schema.maxLength ?? MAX_STRING, MAX_STRING);
|
|
47
|
+
if (value.length > max)
|
|
48
|
+
return `${path}: longer than ${max} characters`;
|
|
49
|
+
if (schema.minLength !== undefined && value.length < schema.minLength)
|
|
50
|
+
return `${path}: shorter than ${schema.minLength} characters`;
|
|
51
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(value))
|
|
52
|
+
return `${path}: does not match the expected format`;
|
|
53
|
+
// eslint-disable-next-line no-control-regex
|
|
54
|
+
if (/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value))
|
|
55
|
+
return `${path}: control characters are not allowed`;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
case 'integer':
|
|
59
|
+
case 'number': {
|
|
60
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
61
|
+
return `${path}: must be a finite number`;
|
|
62
|
+
if (t === 'integer' && !Number.isInteger(value))
|
|
63
|
+
return `${path}: must be an integer`;
|
|
64
|
+
if (schema.minimum !== undefined && value < schema.minimum)
|
|
65
|
+
return `${path}: below ${schema.minimum}`;
|
|
66
|
+
if (schema.maximum !== undefined && value > schema.maximum)
|
|
67
|
+
return `${path}: above ${schema.maximum}`;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
case 'boolean':
|
|
71
|
+
return typeof value === 'boolean' ? null : `${path}: must be true or false`;
|
|
72
|
+
case 'array': {
|
|
73
|
+
if (!Array.isArray(value))
|
|
74
|
+
return `${path}: must be an array`;
|
|
75
|
+
if (value.length > (schema.maxItems ?? 64))
|
|
76
|
+
return `${path}: too many items`;
|
|
77
|
+
for (let i = 0; i < value.length; i++) {
|
|
78
|
+
const err = validateArgs(schema.items ?? {}, value[i], `${path}[${i}]`, depth + 1);
|
|
79
|
+
if (err)
|
|
80
|
+
return err;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
return value === undefined ? null : `${path}: unexpected value`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// @dexterm/agents/sdk — the small TypeScript SDK (task-1345, tier 2). A bring-your-own-agent connects to
|
|
2
|
+
// a running dexterm's loopback bus with its registered name + token, lists the tools its tier allows,
|
|
3
|
+
// calls read/navigation tools, subscribes to live topics, and creates proposals the user confirms. Uses
|
|
4
|
+
// Node's built-in global `WebSocket` client (Node ≥ 22) so the SDK ships with zero dependencies. It can
|
|
5
|
+
// never arm, sign, send or raise a cap — those tools do not exist and the server refuses the shape.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// const agent = await connectAgent({ name: 'scanbot', token: process.env.DEXTERM_AGENT_TOKEN! });
|
|
9
|
+
// const tools = await agent.listTools();
|
|
10
|
+
// const rows = await agent.call('screener_query', { preset: 'new', limit: 8 });
|
|
11
|
+
// const off = agent.on('new_pairs', (ev) => console.log(ev.data));
|
|
12
|
+
// await agent.subscribe(['new_pairs']);
|
|
13
|
+
// await agent.propose('propose_alert', { mint, when: 'mcap crosses 1M', idempotency_key: 'k-'+Date.now() });
|
|
14
|
+
// agent.close();
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
/** Read the bus discovery file a running dexterm writes (<home>/agent-bus.json). */
|
|
19
|
+
export function loadBusDiscovery(opts = {}) {
|
|
20
|
+
const env = opts.env ?? process.env;
|
|
21
|
+
const home = opts.home ?? (env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(homedir(), '.dexterm'));
|
|
22
|
+
try {
|
|
23
|
+
const d = JSON.parse(readFileSync(join(home, 'agent-bus.json'), 'utf8'));
|
|
24
|
+
return typeof d.port === 'number' && d.port > 0 ? { port: d.port } : null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class DextermAgent {
|
|
31
|
+
ws;
|
|
32
|
+
pending = new Map();
|
|
33
|
+
listeners = new Map();
|
|
34
|
+
seq = 0;
|
|
35
|
+
closed = false;
|
|
36
|
+
hello = null;
|
|
37
|
+
constructor(ws) {
|
|
38
|
+
this.ws = ws;
|
|
39
|
+
ws.addEventListener('message', (e) => this.onMessage(String(e.data)));
|
|
40
|
+
ws.addEventListener('close', () => { this.closed = true; for (const p of this.pending.values())
|
|
41
|
+
p.reject(new Error('connection closed')); this.pending.clear(); });
|
|
42
|
+
}
|
|
43
|
+
static async connect(opts) {
|
|
44
|
+
let port = opts.port;
|
|
45
|
+
let url = opts.url;
|
|
46
|
+
if (!url) {
|
|
47
|
+
if (!port) {
|
|
48
|
+
const d = loadBusDiscovery({ home: opts.home });
|
|
49
|
+
if (!d)
|
|
50
|
+
throw new Error('no --port/--url and no agent-bus.json discovery file: is dexterm running with the agent bus enabled?');
|
|
51
|
+
port = d.port;
|
|
52
|
+
}
|
|
53
|
+
url = `ws://127.0.0.1:${port}`;
|
|
54
|
+
}
|
|
55
|
+
const ws = new WebSocket(url);
|
|
56
|
+
await new Promise((resolve, reject) => {
|
|
57
|
+
const to = setTimeout(() => reject(new Error('timed out opening the bus socket')), opts.timeoutMs ?? 5000);
|
|
58
|
+
ws.addEventListener('open', () => { clearTimeout(to); resolve(); }, { once: true });
|
|
59
|
+
ws.addEventListener('error', () => { clearTimeout(to); reject(new Error('could not open the bus socket')); }, { once: true });
|
|
60
|
+
});
|
|
61
|
+
const agent = new DextermAgent(ws);
|
|
62
|
+
const hello = await agent.request('hello', { name: opts.name, token: opts.token, tier: opts.tier, client: opts.client }, opts.timeoutMs);
|
|
63
|
+
agent.hello = hello;
|
|
64
|
+
return agent;
|
|
65
|
+
}
|
|
66
|
+
onMessage(text) {
|
|
67
|
+
let msg;
|
|
68
|
+
try {
|
|
69
|
+
msg = JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (msg.method === 'event' && msg.params) {
|
|
75
|
+
const ev = msg.params;
|
|
76
|
+
for (const l of this.listeners.get(ev.topic) ?? []) {
|
|
77
|
+
try {
|
|
78
|
+
l(ev);
|
|
79
|
+
}
|
|
80
|
+
catch { /* ignore */ }
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (typeof msg.id === 'number') {
|
|
85
|
+
const p = this.pending.get(msg.id);
|
|
86
|
+
if (!p)
|
|
87
|
+
return;
|
|
88
|
+
this.pending.delete(msg.id);
|
|
89
|
+
if (msg.error) {
|
|
90
|
+
const err = new Error(msg.error.message ?? 'bus error');
|
|
91
|
+
err.data = msg.error.data;
|
|
92
|
+
p.reject(err);
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
p.resolve(msg.result ?? {});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
request(method, params, timeoutMs = 10_000) {
|
|
99
|
+
if (this.closed)
|
|
100
|
+
return Promise.reject(new Error('connection closed'));
|
|
101
|
+
const id = ++this.seq;
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const to = setTimeout(() => { this.pending.delete(id); reject(new Error(`timed out waiting for ${method}`)); }, timeoutMs);
|
|
104
|
+
this.pending.set(id, { resolve: (v) => { clearTimeout(to); resolve(v); }, reject: (e) => { clearTimeout(to); reject(e); } });
|
|
105
|
+
this.ws.send(JSON.stringify({ jsonrpc: '2.0', id, method, params }));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** List the tools this agent's tier may call. */
|
|
109
|
+
async listTools() {
|
|
110
|
+
const r = await this.request('tools/list', {});
|
|
111
|
+
return r.tools ?? [];
|
|
112
|
+
}
|
|
113
|
+
/** Call a read or navigation tool and return its result (or an object with an `error` field). */
|
|
114
|
+
async call(name, args = {}) {
|
|
115
|
+
return this.request('tools/call', { name, arguments: args });
|
|
116
|
+
}
|
|
117
|
+
/** Create a proposal the user must confirm. Alias of call() with a clearer name. */
|
|
118
|
+
async propose(name, args) {
|
|
119
|
+
return this.call(name, args);
|
|
120
|
+
}
|
|
121
|
+
async whoami() { return this.call('agent_whoami'); }
|
|
122
|
+
/** Register a listener for a topic. Call subscribe() to actually start the server-side stream. */
|
|
123
|
+
on(topic, listener) {
|
|
124
|
+
let set = this.listeners.get(topic);
|
|
125
|
+
if (!set) {
|
|
126
|
+
set = new Set();
|
|
127
|
+
this.listeners.set(topic, set);
|
|
128
|
+
}
|
|
129
|
+
set.add(listener);
|
|
130
|
+
return () => set.delete(listener);
|
|
131
|
+
}
|
|
132
|
+
async subscribe(topics) { const r = await this.call('subscribe', { topics }); return r.subscribed ?? []; }
|
|
133
|
+
async unsubscribe(topics) { const r = await this.call('unsubscribe', topics ? { topics } : {}); return r.subscribed ?? []; }
|
|
134
|
+
async ping() { const r = await this.request('ping', {}); return r.pong === true; }
|
|
135
|
+
close() { this.closed = true; try {
|
|
136
|
+
this.ws.close();
|
|
137
|
+
}
|
|
138
|
+
catch { /* ignore */ } }
|
|
139
|
+
}
|
|
140
|
+
export function connectAgent(opts) { return DextermAgent.connect(opts); }
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @dexterm/agents — capability tiers (task-1345, design task-1295 §2).
|
|
2
|
+
//
|
|
3
|
+
// Three tiers, strictly ordered, enforced SERVER-SIDE per agent identity by the gate (gate.ts). The
|
|
4
|
+
// tier an agent has comes from the user's registry (identity.ts), never from anything the agent sends:
|
|
5
|
+
// a client may only ask to run BELOW its granted tier.
|
|
6
|
+
//
|
|
7
|
+
// observe read tools and live streams
|
|
8
|
+
// annotate + non-financial UI navigation on the user's screen, agent-owned notes/tags on coins
|
|
9
|
+
// propose + proposals: alerts, order plans, watchlist adds — they appear labelled with the agent and
|
|
10
|
+
// need the user's keypress; nothing an agent proposes is ever applied by itself
|
|
11
|
+
//
|
|
12
|
+
// NEVER, for any agent at any tier (ROADMAP hard rule, SPEC §6.0): arm, sign, confirm, send, raise or
|
|
13
|
+
// change a cap, change the confirmation gate, touch the wallet, a mandate or the signer. No such tool
|
|
14
|
+
// exists in the table below, and `neverAllowed()` refuses the *shape* of such a request before any
|
|
15
|
+
// lookup, so a future tool with a dangerous name cannot be added by accident.
|
|
16
|
+
export const TIERS = ['observe', 'annotate', 'propose'];
|
|
17
|
+
export const TIER_RANK = { observe: 0, annotate: 1, propose: 2 };
|
|
18
|
+
export function isTier(x) {
|
|
19
|
+
return typeof x === 'string' && TIERS.includes(x);
|
|
20
|
+
}
|
|
21
|
+
/** True when `have` is at least `need`. */
|
|
22
|
+
export function tierAtLeast(have, need) {
|
|
23
|
+
return TIER_RANK[have] >= TIER_RANK[need];
|
|
24
|
+
}
|
|
25
|
+
/** The lower of two tiers (a client may request to run below its grant, never above). */
|
|
26
|
+
export function lowerTier(a, b) {
|
|
27
|
+
return TIER_RANK[a] <= TIER_RANK[b] ? a : b;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every tool or bus method an agent may call, with the minimum tier. Anything absent is refused as
|
|
31
|
+
* `unknown-tool`. The TUI's own analyst keeps its separate task-1313 channel; this table governs
|
|
32
|
+
* external agents only (bus and bridged MCP).
|
|
33
|
+
*/
|
|
34
|
+
export const TOOL_TIERS = Object.freeze({
|
|
35
|
+
// ---- observe: read tools (the task-1302/1314/1315 MCP surface) + bus reads
|
|
36
|
+
screen_state: 'observe',
|
|
37
|
+
screener_query: 'observe',
|
|
38
|
+
get_token: 'observe',
|
|
39
|
+
get_dossier: 'observe',
|
|
40
|
+
get_holders: 'observe',
|
|
41
|
+
get_trades: 'observe',
|
|
42
|
+
get_candles: 'observe',
|
|
43
|
+
rugcheck: 'observe',
|
|
44
|
+
get_dev_history: 'observe',
|
|
45
|
+
kb_lookup: 'observe',
|
|
46
|
+
kb_search: 'observe',
|
|
47
|
+
case_lookup: 'observe',
|
|
48
|
+
get_links: 'observe',
|
|
49
|
+
fetch_page: 'observe',
|
|
50
|
+
check_domain: 'observe',
|
|
51
|
+
check_github: 'observe',
|
|
52
|
+
agent_whoami: 'observe',
|
|
53
|
+
list_proposals: 'observe',
|
|
54
|
+
subscribe: 'observe',
|
|
55
|
+
unsubscribe: 'observe',
|
|
56
|
+
// ---- annotate: non-financial navigation + agent-owned annotations
|
|
57
|
+
open_token: 'annotate',
|
|
58
|
+
show_in_screener: 'annotate',
|
|
59
|
+
switch_feed: 'annotate',
|
|
60
|
+
agent_note: 'annotate',
|
|
61
|
+
// ---- propose: proposals the user must confirm with a keypress
|
|
62
|
+
propose_alert: 'propose',
|
|
63
|
+
propose_order_plan: 'propose',
|
|
64
|
+
add_to_watchlist: 'propose',
|
|
65
|
+
});
|
|
66
|
+
/** Tools that are proposals (write-shaped): they need an idempotency key and count against the proposal quota. */
|
|
67
|
+
export const PROPOSAL_TOOLS = new Set(['propose_alert', 'propose_order_plan', 'add_to_watchlist']);
|
|
68
|
+
/** UI-navigation tools (count against the shared ACTION_RATE window like the analyst's). */
|
|
69
|
+
export const NAVIGATION_TOOLS = new Set(['open_token', 'show_in_screener', 'switch_feed']);
|
|
70
|
+
/**
|
|
71
|
+
* Names and name-shapes no agent may ever reach, whatever the table says. Checked FIRST. Deliberately
|
|
72
|
+
* broad: a false positive costs an agent one odd tool name; a false negative is money.
|
|
73
|
+
*/
|
|
74
|
+
export const NEVER_PATTERNS = [
|
|
75
|
+
/\barm\b|arm_|_arm|arming/i,
|
|
76
|
+
/\bsign\b|sign_|_sign|signer|signature/i,
|
|
77
|
+
/\bconfirm\b|confirm_|_confirm/i,
|
|
78
|
+
/\bsend\b|send_|_send|submit|broadcast/i,
|
|
79
|
+
/\bswap\b|swap_|_swap|transfer|withdraw|deposit|approve/i,
|
|
80
|
+
/\bcap\b|caps?_|_caps?|raise|limit_set|set_limit|budget_set|set_budget|ceiling/i,
|
|
81
|
+
/\bgate\b|gate_|_gate|keypress|opt_?out|less_?safe/i,
|
|
82
|
+
/mandate|wallet|panic|resume_live|go_?live|set_?mode|live_mode|mainnet/i,
|
|
83
|
+
/\bkey\b|keys?_|_keys?|secret|seed|mnemonic|private|credential|token_?file|helius/i,
|
|
84
|
+
/\bexec\b|exec_|shell|spawn|eval|file|path|read_?dir|env|process/i,
|
|
85
|
+
/\bticket\b|ticket_|_ticket|execute|trade_now|buy_now|sell_now|\bfill\b/i,
|
|
86
|
+
/set_tier|grant|escalate|promote|admin|sudo|root|kick|pause_agent|unpause/i,
|
|
87
|
+
];
|
|
88
|
+
/** True when a requested tool/method name is on the never-list (refused before any lookup). */
|
|
89
|
+
export function neverAllowed(name) {
|
|
90
|
+
if (typeof name !== 'string')
|
|
91
|
+
return true;
|
|
92
|
+
if (name.length > 64)
|
|
93
|
+
return true;
|
|
94
|
+
if (!/^[a-z][a-z0-9_./-]*$/i.test(name))
|
|
95
|
+
return true; // odd shapes are refused, never parsed
|
|
96
|
+
if (name in TOOL_TIERS)
|
|
97
|
+
return false; // table names are vetted by hand and tested below the never-list
|
|
98
|
+
return NEVER_PATTERNS.some((re) => re.test(name));
|
|
99
|
+
}
|
|
100
|
+
/** The minimum tier for a tool, or null when it is not a tool an agent may call at all. */
|
|
101
|
+
export function tierFor(name) {
|
|
102
|
+
if (neverAllowed(name))
|
|
103
|
+
return null;
|
|
104
|
+
return Object.hasOwn(TOOL_TIERS, name) ? TOOL_TIERS[name] : null;
|
|
105
|
+
}
|
|
106
|
+
/** Tools a tier may call (for `tools/list` filtering and the roster's tier legend). */
|
|
107
|
+
export function toolsForTier(tier) {
|
|
108
|
+
return Object.keys(TOOL_TIERS).filter((t) => tierAtLeast(tier, TOOL_TIERS[t]));
|
|
109
|
+
}
|
|
110
|
+
export const TIER_LEGEND = {
|
|
111
|
+
observe: 'read tools + streams',
|
|
112
|
+
annotate: 'navigation, notes, tags',
|
|
113
|
+
propose: 'alerts, order plans, watch adds — as proposals',
|
|
114
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @dexterm/agents — the agent tool table (task-1345, tier 2). One source of truth for what an external
|
|
2
|
+
// agent may call over the bus or a bridged MCP child: the advertised name + description + closed input
|
|
3
|
+
// schema + minimum tier. The gate validates arguments against these schemas; `tools/list` filters them
|
|
4
|
+
// to the session's tier. Every tool here is read-only, navigation, or a PROPOSAL (user confirms with a
|
|
5
|
+
// keypress). Nothing here builds, signs, sends, arms, raises a cap, or touches a wallet/mandate/signer —
|
|
6
|
+
// the never-list (tiers.ts) refuses that shape before this table is ever consulted.
|
|
7
|
+
import { TOOL_TIERS } from "./tiers.js";
|
|
8
|
+
const MINT = { type: 'string', pattern: '^[1-9A-HJ-NP-Za-km-z]{32,44}$', maxLength: 44 };
|
|
9
|
+
const SYMBOL = { type: 'string', maxLength: 32 };
|
|
10
|
+
const IDEMPOTENCY = { type: 'string', minLength: 8, maxLength: 64, pattern: '^[A-Za-z0-9_.:-]+$' };
|
|
11
|
+
const URL = { type: 'string', maxLength: 512, pattern: '^https?://' };
|
|
12
|
+
/** Advertised tool table. Order is the order `tools/list` returns. */
|
|
13
|
+
const TOOLS_DEF = [
|
|
14
|
+
// ---------------------------------------------------------------- observe: bus-native
|
|
15
|
+
{ name: 'agent_whoami', tier: 'observe', description: 'Who am I to the server: my name, granted tier, the tools my tier may call, my current rate/quota usage, and my live subscriptions. Identity and tier come from the server, never from me.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
|
|
16
|
+
{ name: 'subscribe', tier: 'observe', description: 'Subscribe to live topics. Each pushed event is server-authored and firewalled. Topics: new_pairs, trades, alerts, screen. Events arrive as JSON-RPC notifications with method "event".', inputSchema: { type: 'object', properties: { topics: { type: 'array', items: { type: 'string', enum: ['new_pairs', 'trades', 'alerts', 'screen'] }, maxItems: 4 } }, required: ['topics'], additionalProperties: false } },
|
|
17
|
+
{ name: 'unsubscribe', tier: 'observe', description: 'Stop receiving events for the given topics (or all when topics is omitted).', inputSchema: { type: 'object', properties: { topics: { type: 'array', items: { type: 'string', enum: ['new_pairs', 'trades', 'alerts', 'screen'] }, maxItems: 4 } }, additionalProperties: false } },
|
|
18
|
+
{ name: 'list_proposals', tier: 'observe', description: 'List the proposals this agent has created and their status (pending / confirmed / dismissed). An agent can never confirm a proposal itself; the user confirms with a keypress.', inputSchema: { type: 'object', properties: { limit: { type: 'integer', minimum: 1, maximum: 100 } }, additionalProperties: false } },
|
|
19
|
+
// ---------------------------------------------------------------- observe: read tools (mirror of the MCP surface)
|
|
20
|
+
{ name: 'screen_state', tier: 'observe', description: 'What is on screen in dexterm right now: screen, filter, sort, selected token, visible mints, watchlist. Gives you the same context the user has.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
|
|
21
|
+
{ name: 'screener_query', tier: 'observe', description: 'Screener rows with provenance. preset: "new" (youngest), "trending" (volume), "all".', inputSchema: { type: 'object', properties: { preset: { type: 'string', enum: ['new', 'trending', 'all'] }, limit: { type: 'integer', minimum: 1, maximum: 50 } }, additionalProperties: false } },
|
|
22
|
+
{ name: 'get_token', tier: 'observe', description: 'Token + Pair + Stats + Audit for a mint or symbol, each with source and age. Ambiguous symbols return candidates.', inputSchema: { type: 'object', properties: { mint: MINT, symbol: SYMBOL }, additionalProperties: false } },
|
|
23
|
+
{ name: 'get_dossier', tier: 'observe', description: 'One pre-assembled dossier (market, holders, security, candles, tape, creator) for a mint or symbol in a single round trip. Read `availability` for which sections are present.', inputSchema: { type: 'object', properties: { mint: MINT, symbol: SYMBOL, interval: { type: 'string', enum: ['1m', '5m', '15m', '1h', '4h', '1d'] } }, additionalProperties: false } },
|
|
24
|
+
{ name: 'get_holders', tier: 'observe', description: 'Top holders (≤50) with the top10 share excluding LP.', inputSchema: { type: 'object', properties: { mint: MINT, top: { type: 'integer', minimum: 1, maximum: 50 } }, required: ['mint'], additionalProperties: false } },
|
|
25
|
+
{ name: 'get_trades', tier: 'observe', description: 'Recent trades of the most liquid pool with buy/sell aggregates and a wash signal.', inputSchema: { type: 'object', properties: { mint: MINT, window: { type: 'string', enum: ['m5', 'h1'] } }, required: ['mint'], additionalProperties: false } },
|
|
26
|
+
{ name: 'get_candles', tier: 'observe', description: 'Candles plus computed summaries (range, drawdown, VWAP, change%).', inputSchema: { type: 'object', properties: { mint: MINT, interval: { type: 'string', enum: ['1m', '5m', '15m', '1h', '4h', '1d'] }, limit: { type: 'integer', minimum: 1, maximum: 300 } }, required: ['mint'], additionalProperties: false } },
|
|
27
|
+
{ name: 'rugcheck', tier: 'observe', description: 'RugCheck-style summary: risks[] (firewalled), normalised score, lpLockedPct, tokenProgram.', inputSchema: { type: 'object', properties: { mint: MINT }, required: ['mint'], additionalProperties: false } },
|
|
28
|
+
{ name: 'get_dev_history', tier: 'observe', description: 'Creator address and prior-launch forensics. Tier-1 fields need a key and are null on the keyless tier.', inputSchema: { type: 'object', properties: { mint: MINT }, required: ['mint'], additionalProperties: false } },
|
|
29
|
+
{ name: 'kb_lookup', tier: 'observe', description: 'One knowledge-base entry by id (mechanics / patterns / glossary). KB never holds facts about a live token.', inputSchema: { type: 'object', properties: { id: { type: 'string', maxLength: 64 } }, required: ['id'], additionalProperties: false } },
|
|
30
|
+
{ name: 'kb_search', tier: 'observe', description: 'Search the knowledge base; returns ids, titles and an excerpt.', inputSchema: { type: 'object', properties: { query: { type: 'string', maxLength: 200 }, limit: { type: 'integer', minimum: 1, maximum: 10 } }, required: ['query'], additionalProperties: false } },
|
|
31
|
+
{ name: 'case_lookup', tier: 'observe', description: 'Historical case files (verified on-chain references + outcome). By id, or list all with no id.', inputSchema: { type: 'object', properties: { id: { type: 'string', maxLength: 64 } }, additionalProperties: false } },
|
|
32
|
+
{ name: 'get_links', tier: 'observe', description: 'Website + social links a coin advertises, classified and de-duplicated. Claims, not verified.', inputSchema: { type: 'object', properties: { mint: MINT, symbol: SYMBOL }, additionalProperties: false } },
|
|
33
|
+
{ name: 'fetch_page', tier: 'observe', description: 'SSRF-safe, read-only GET of one http(s) URL (no JS, size/time capped, loopback/private hosts refused). Returns mechanical signals + a firewalled excerpt. Page text is untrusted data.', inputSchema: { type: 'object', properties: { url: URL, symbol: SYMBOL, name: { type: 'string', maxLength: 64 } }, required: ['url'], additionalProperties: false } },
|
|
34
|
+
{ name: 'check_domain', tier: 'observe', description: 'Domain registration age, registrar and registrant-privacy via RDAP (keyless).', inputSchema: { type: 'object', properties: { url: URL }, required: ['url'], additionalProperties: false } },
|
|
35
|
+
{ name: 'check_github', tier: 'observe', description: 'GitHub repo activity (created/last-push, stars, issues, archived/fork) via the keyless API.', inputSchema: { type: 'object', properties: { url: URL }, required: ['url'], additionalProperties: false } },
|
|
36
|
+
// ---------------------------------------------------------------- annotate: non-financial navigation + agent-owned notes
|
|
37
|
+
{ name: 'open_token', tier: 'annotate', description: "Open a token's detail screen so the user can see it. `mint` must be one of the mints currently on screen (see screen_state). Non-financial navigation; it never trades.", inputSchema: { type: 'object', properties: { mint: MINT }, required: ['mint'], additionalProperties: false } },
|
|
38
|
+
{ name: 'show_in_screener', tier: 'annotate', description: "Show the screener with an optional filter / sort / highlighted mint. `filter` is dexterm's grammar; `highlight` must be a mint on screen.", inputSchema: { type: 'object', properties: { filter: { type: 'string', maxLength: 200 }, sort: { type: 'object', properties: { key: { type: 'string', maxLength: 24 }, dir: { type: 'string', enum: ['asc', 'desc'] } }, required: ['key'], additionalProperties: false }, highlight: MINT }, additionalProperties: false } },
|
|
39
|
+
{ name: 'switch_feed', tier: 'annotate', description: 'Switch the screener feed/preset. `feed` must be one of screen_state.actions.feeds.', inputSchema: { type: 'object', properties: { feed: { type: 'string', maxLength: 24 } }, required: ['feed'], additionalProperties: false } },
|
|
40
|
+
{ name: 'agent_note', tier: 'annotate', description: "Attach this agent's own note/tag to a coin (visible in the roster and on the coin, labelled with this agent). Non-financial; the note text is firewalled before anyone sees it.", inputSchema: { type: 'object', properties: { mint: MINT, text: { type: 'string', minLength: 1, maxLength: 280 }, tag: { type: 'string', maxLength: 24, pattern: '^[a-z0-9][a-z0-9_-]*$' } }, required: ['mint', 'text'], additionalProperties: false } },
|
|
41
|
+
// ---------------------------------------------------------------- propose: proposals the user must confirm with a keypress
|
|
42
|
+
{ name: 'propose_alert', tier: 'propose', description: 'Propose a watch alert on a mint (e.g. "mcap crosses 1M"). Appears as a PROPOSAL labelled with this agent; the user confirms with a keypress. You can never arm or fire it. Needs idempotency_key.', inputSchema: { type: 'object', properties: { mint: MINT, when: { type: 'string', minLength: 1, maxLength: 120 }, note: { type: 'string', maxLength: 200 }, idempotency_key: IDEMPOTENCY }, required: ['mint', 'when', 'idempotency_key'], additionalProperties: false } },
|
|
43
|
+
{ name: 'propose_order_plan', tier: 'propose', description: 'Propose a PAPER order plan (side, size, triggers) as a DRAFT proposal labelled with this agent. It is never armed, signed or sent; arming needs a physical keypress by the user and signer-side caps still apply. Needs idempotency_key.', inputSchema: { type: 'object', properties: { mint: MINT, side: { type: 'string', enum: ['buy', 'sell'] }, size: { type: 'string', maxLength: 40 }, triggers: { type: 'array', items: { type: 'string', maxLength: 80 }, maxItems: 8 }, note: { type: 'string', maxLength: 200 }, idempotency_key: IDEMPOTENCY }, required: ['mint', 'side', 'size', 'idempotency_key'], additionalProperties: false } },
|
|
44
|
+
{ name: 'add_to_watchlist', tier: 'propose', description: "Propose adding a token to the user's watchlist. Appears as a PROPOSAL labelled with this agent. Needs idempotency_key.", inputSchema: { type: 'object', properties: { mint: MINT, note: { type: 'string', maxLength: 200 }, idempotency_key: IDEMPOTENCY }, required: ['mint', 'idempotency_key'], additionalProperties: false } },
|
|
45
|
+
];
|
|
46
|
+
export const AGENT_TOOLS = Object.freeze(TOOLS_DEF);
|
|
47
|
+
const BY_NAME = new Map(AGENT_TOOLS.map((t) => [t.name, t]));
|
|
48
|
+
/** The closed input schema the gate validates a call's arguments against, or undefined for an unknown tool. */
|
|
49
|
+
export function toolSchema(name) {
|
|
50
|
+
return BY_NAME.get(name)?.inputSchema;
|
|
51
|
+
}
|
|
52
|
+
export function agentTool(name) {
|
|
53
|
+
return BY_NAME.get(name);
|
|
54
|
+
}
|
|
55
|
+
/** The advertised tools a tier may call (for `tools/list`). */
|
|
56
|
+
export function toolsForTierList(have, rank = { observe: 0, annotate: 1, propose: 2 }) {
|
|
57
|
+
return AGENT_TOOLS.filter((t) => rank[have] >= rank[t.tier]);
|
|
58
|
+
}
|
|
59
|
+
// Belt-and-braces: the advertised table and the tier table (tiers.ts) must not drift. Every advertised
|
|
60
|
+
// tool must carry the same tier the gate reads; a mismatch would let a call pass the gate at the wrong
|
|
61
|
+
// tier. Checked once at module load (throws loudly in tests / at startup rather than mis-serving).
|
|
62
|
+
for (const t of AGENT_TOOLS) {
|
|
63
|
+
if (TOOL_TIERS[t.name] !== t.tier)
|
|
64
|
+
throw new Error(`agent tool "${t.name}" tier ${t.tier} disagrees with TOOL_TIERS ${TOOL_TIERS[t.name]}`);
|
|
65
|
+
}
|
|
66
|
+
for (const name of Object.keys(TOOL_TIERS)) {
|
|
67
|
+
if (!BY_NAME.has(name))
|
|
68
|
+
throw new Error(`TOOL_TIERS has "${name}" but AGENT_TOOLS does not advertise it`);
|
|
69
|
+
}
|