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,122 @@
|
|
|
1
|
+
// @dexterm/agents — the audit log (task-1345). Every agent connection, call, refusal, proposal and
|
|
2
|
+
// roster action is one row. Two stores behind one interface: in memory (tests, --fixtures) and
|
|
3
|
+
// node:sqlite in the shared dexterm DB (live / replay), so `dexterm agent audit` and the roster popup
|
|
4
|
+
// read the same rows. Detail strings are firewalled at write time (agent-supplied text can never carry
|
|
5
|
+
// an escape sequence or a 10 KB payload into the terminal or a model).
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
7
|
+
import { mkdirSync } from 'node:fs';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
import { firewallText } from '../../data/src/index.js';
|
|
10
|
+
export const AUDIT_DETAIL_MAX = 160;
|
|
11
|
+
/** Make a detail string safe for the terminal and the log: firewall, single line, capped. */
|
|
12
|
+
export function auditDetail(s) {
|
|
13
|
+
const r = firewallText(s === undefined || s === null ? '' : String(s), { max: AUDIT_DETAIL_MAX, keepUrls: false });
|
|
14
|
+
return r.text.replace(/\s+/g, ' ').trim();
|
|
15
|
+
}
|
|
16
|
+
export class MemoryAuditLog {
|
|
17
|
+
rows = [];
|
|
18
|
+
seq = 0;
|
|
19
|
+
now;
|
|
20
|
+
cap;
|
|
21
|
+
constructor(opts = {}) { this.now = opts.now ?? (() => Date.now()); this.cap = opts.cap ?? 5000; }
|
|
22
|
+
append(e) {
|
|
23
|
+
const row = { ...e, id: ++this.seq, t: e.t ?? this.now(), detail: auditDetail(e.detail), agentName: auditDetail(e.agentName).slice(0, 24), tool: auditDetail(e.tool).slice(0, 64) };
|
|
24
|
+
this.rows.push(row);
|
|
25
|
+
if (this.rows.length > this.cap)
|
|
26
|
+
this.rows.splice(0, this.rows.length - this.cap);
|
|
27
|
+
return row;
|
|
28
|
+
}
|
|
29
|
+
recent(limit = 50, filter = {}) {
|
|
30
|
+
const out = [];
|
|
31
|
+
for (let i = this.rows.length - 1; i >= 0 && out.length < limit; i--) {
|
|
32
|
+
const r = this.rows[i];
|
|
33
|
+
if (filter.agentId && r.agentId !== filter.agentId)
|
|
34
|
+
continue;
|
|
35
|
+
if (filter.agentName && r.agentName !== filter.agentName)
|
|
36
|
+
continue;
|
|
37
|
+
if (filter.kind && r.kind !== filter.kind)
|
|
38
|
+
continue;
|
|
39
|
+
if (filter.since !== undefined && r.t < filter.since)
|
|
40
|
+
continue;
|
|
41
|
+
out.push({ ...r });
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
counts(agentId) {
|
|
46
|
+
let calls = 0, refused = 0, proposals = 0, lastAt = null;
|
|
47
|
+
for (const r of this.rows) {
|
|
48
|
+
if (r.agentId !== agentId)
|
|
49
|
+
continue;
|
|
50
|
+
if (r.kind === 'call')
|
|
51
|
+
calls++;
|
|
52
|
+
else if (r.kind === 'refused')
|
|
53
|
+
refused++;
|
|
54
|
+
else if (r.kind === 'proposal')
|
|
55
|
+
proposals++;
|
|
56
|
+
if (lastAt === null || r.t > lastAt)
|
|
57
|
+
lastAt = r.t;
|
|
58
|
+
}
|
|
59
|
+
return { calls, refused, proposals, lastAt };
|
|
60
|
+
}
|
|
61
|
+
close() { }
|
|
62
|
+
}
|
|
63
|
+
export class SqliteAuditLog {
|
|
64
|
+
db;
|
|
65
|
+
now;
|
|
66
|
+
constructor(path, opts = {}) {
|
|
67
|
+
if (path !== ':memory:')
|
|
68
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
69
|
+
this.db = new DatabaseSync(path);
|
|
70
|
+
this.now = opts.now ?? (() => Date.now());
|
|
71
|
+
this.db.exec(`
|
|
72
|
+
PRAGMA journal_mode = WAL;
|
|
73
|
+
CREATE TABLE IF NOT EXISTS agent_audit (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, t INTEGER NOT NULL, agent_id TEXT, agent_name TEXT NOT NULL, session TEXT,
|
|
75
|
+
kind TEXT NOT NULL, tool TEXT NOT NULL, detail TEXT NOT NULL, ok INTEGER NOT NULL
|
|
76
|
+
);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS agent_audit_agent ON agent_audit(agent_id, t);
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
append(e) {
|
|
81
|
+
const t = e.t ?? this.now();
|
|
82
|
+
const detail = auditDetail(e.detail);
|
|
83
|
+
const name = auditDetail(e.agentName).slice(0, 24);
|
|
84
|
+
const tool = auditDetail(e.tool).slice(0, 64);
|
|
85
|
+
const r = this.db.prepare('INSERT INTO agent_audit (t, agent_id, agent_name, session, kind, tool, detail, ok) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
|
|
86
|
+
.run(t, e.agentId, name, e.session, e.kind, tool, detail, e.ok ? 1 : 0);
|
|
87
|
+
return { ...e, id: Number(r.lastInsertRowid), t, detail, agentName: name, tool };
|
|
88
|
+
}
|
|
89
|
+
recent(limit = 50, filter = {}) {
|
|
90
|
+
const where = [];
|
|
91
|
+
const params = [];
|
|
92
|
+
if (filter.agentId) {
|
|
93
|
+
where.push('agent_id = ?');
|
|
94
|
+
params.push(filter.agentId);
|
|
95
|
+
}
|
|
96
|
+
if (filter.agentName) {
|
|
97
|
+
where.push('agent_name = ?');
|
|
98
|
+
params.push(filter.agentName);
|
|
99
|
+
}
|
|
100
|
+
if (filter.kind) {
|
|
101
|
+
where.push('kind = ?');
|
|
102
|
+
params.push(filter.kind);
|
|
103
|
+
}
|
|
104
|
+
if (filter.since !== undefined) {
|
|
105
|
+
where.push('t >= ?');
|
|
106
|
+
params.push(filter.since);
|
|
107
|
+
}
|
|
108
|
+
const sql = `SELECT id, t, agent_id, agent_name, session, kind, tool, detail, ok FROM agent_audit${where.length ? ' WHERE ' + where.join(' AND ') : ''} ORDER BY id DESC LIMIT ?`;
|
|
109
|
+
const rows = this.db.prepare(sql).all(...params, Math.max(1, Math.min(limit, 2000)));
|
|
110
|
+
return rows.map((r) => ({ id: Number(r.id), t: Number(r.t), agentId: r.agent_id ?? null, agentName: String(r.agent_name), session: r.session ?? null, kind: r.kind, tool: String(r.tool), detail: String(r.detail), ok: Number(r.ok) === 1 }));
|
|
111
|
+
}
|
|
112
|
+
counts(agentId) {
|
|
113
|
+
const r = this.db.prepare(`SELECT
|
|
114
|
+
SUM(CASE WHEN kind = 'call' THEN 1 ELSE 0 END) AS calls, SUM(CASE WHEN kind = 'refused' THEN 1 ELSE 0 END) AS refused,
|
|
115
|
+
SUM(CASE WHEN kind = 'proposal' THEN 1 ELSE 0 END) AS proposals, MAX(t) AS last FROM agent_audit WHERE agent_id = ?`).get(agentId);
|
|
116
|
+
return { calls: Number(r.calls ?? 0), refused: Number(r.refused ?? 0), proposals: Number(r.proposals ?? 0), lastAt: r.last === null || r.last === undefined ? null : Number(r.last) };
|
|
117
|
+
}
|
|
118
|
+
close() { try {
|
|
119
|
+
this.db.close();
|
|
120
|
+
}
|
|
121
|
+
catch { /* ignore */ } }
|
|
122
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// @dexterm/agents — the loopback agent bus (task-1345, tier 2). A WebSocket + JSON-RPC 2.0 server on
|
|
2
|
+
// 127.0.0.1 that external agents connect to. Every connection authenticates once with a registered name
|
|
3
|
+
// + token (identity.ts); every call passes the gate (gate.ts) before any execution; every connect, call,
|
|
4
|
+
// refusal, proposal and disconnect is one audit row. Agents can subscribe to live topics (new pairs,
|
|
5
|
+
// trades, alerts, screen) and create proposals the user must confirm. No bus path can arm, sign, send,
|
|
6
|
+
// raise a cap, change the gate, or touch a wallet/mandate/signer — the gate's never-list sees to that.
|
|
7
|
+
import { writeFileSync, rmSync, mkdirSync, chmodSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { createWsServer } from "./ws.js";
|
|
10
|
+
import { AgentGate } from "./gate.js";
|
|
11
|
+
import { AgentRegistry, dextermHome, looksLikeToken, AGENT_NAME_RE } from "./identity.js";
|
|
12
|
+
import { lowerTier, isTier, toolsForTier } from "./tiers.js";
|
|
13
|
+
import { toolSchema, agentTool, toolsForTierList } from "./tools.js";
|
|
14
|
+
import { isTopic, scrubStrings } from "./executor.js";
|
|
15
|
+
import { TOOL_TO_KIND } from "./proposals.js";
|
|
16
|
+
import { firewallText } from '../../data/src/index.js';
|
|
17
|
+
export const BUS_PROTOCOL = 'dexterm-agent-bus/1';
|
|
18
|
+
/** A connection that does not authenticate within this window is closed. */
|
|
19
|
+
export const HELLO_TIMEOUT_MS = 10_000;
|
|
20
|
+
/** Hard cap on simultaneous un-authenticated sockets (a connect flood cannot exhaust memory). */
|
|
21
|
+
export const MAX_PENDING = 16;
|
|
22
|
+
export class AgentBus {
|
|
23
|
+
server = null;
|
|
24
|
+
conns = new Set();
|
|
25
|
+
registry;
|
|
26
|
+
audit;
|
|
27
|
+
executor;
|
|
28
|
+
proposals;
|
|
29
|
+
feed;
|
|
30
|
+
gate;
|
|
31
|
+
now;
|
|
32
|
+
paused = new Set();
|
|
33
|
+
discoveryPath;
|
|
34
|
+
feedOff = null;
|
|
35
|
+
sessionSeq = 0;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.registry = opts.registry;
|
|
38
|
+
this.audit = opts.audit;
|
|
39
|
+
this.executor = opts.executor;
|
|
40
|
+
this.proposals = opts.proposals;
|
|
41
|
+
this.feed = opts.feed ?? null;
|
|
42
|
+
this.now = opts.now ?? (() => Date.now());
|
|
43
|
+
this.discoveryPath = opts.discoveryPath === undefined ? join(dextermHome(opts.env), 'agent-bus.json') : opts.discoveryPath;
|
|
44
|
+
this.gate = new AgentGate({
|
|
45
|
+
audit: this.audit,
|
|
46
|
+
now: this.now,
|
|
47
|
+
isPaused: (id) => this.paused.has(id),
|
|
48
|
+
onFlood: (id, name) => { this.audit.append({ agentId: id, agentName: name, session: null, kind: 'pause', tool: '-', detail: 'auto-paused by the flood breaker', ok: false }); },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
get port() { return this.server?.port ?? 0; }
|
|
52
|
+
get connectionCount() { return this.conns.size; }
|
|
53
|
+
async start() {
|
|
54
|
+
this.server = await createWsServer({ port: undefined, onConnection: (ws) => this.onConnection(ws) });
|
|
55
|
+
if (this.feed)
|
|
56
|
+
this.feedOff = this.feed.on((ev) => this.fanout(ev));
|
|
57
|
+
this.writeDiscovery();
|
|
58
|
+
return this.server.port;
|
|
59
|
+
}
|
|
60
|
+
async close() {
|
|
61
|
+
this.feedOff?.();
|
|
62
|
+
this.feedOff = null;
|
|
63
|
+
for (const c of [...this.conns]) {
|
|
64
|
+
try {
|
|
65
|
+
c.ws.close(1001);
|
|
66
|
+
}
|
|
67
|
+
catch { /* ignore */ }
|
|
68
|
+
}
|
|
69
|
+
this.conns.clear();
|
|
70
|
+
if (this.discoveryPath) {
|
|
71
|
+
try {
|
|
72
|
+
rmSync(this.discoveryPath);
|
|
73
|
+
}
|
|
74
|
+
catch { /* ignore */ }
|
|
75
|
+
}
|
|
76
|
+
if (this.server) {
|
|
77
|
+
await this.server.close();
|
|
78
|
+
this.server = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ----- roster/CLI actions (user side; never reachable from an agent path) -----
|
|
82
|
+
pauseAgent(name) {
|
|
83
|
+
const a = this.registry.get(name);
|
|
84
|
+
if (!a)
|
|
85
|
+
return false;
|
|
86
|
+
this.paused.add(a.id);
|
|
87
|
+
this.registry.setPaused(name, true);
|
|
88
|
+
this.audit.append({ agentId: a.id, agentName: name, session: null, kind: 'pause', tool: '-', detail: 'paused by the user', ok: false });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
resumeAgent(name) {
|
|
92
|
+
const a = this.registry.get(name);
|
|
93
|
+
if (!a)
|
|
94
|
+
return false;
|
|
95
|
+
this.paused.delete(a.id);
|
|
96
|
+
this.registry.setPaused(name, false);
|
|
97
|
+
this.gate.clearFlood(a.id);
|
|
98
|
+
this.audit.append({ agentId: a.id, agentName: name, session: null, kind: 'resume', tool: '-', detail: 'resumed by the user', ok: true });
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
/** Kick: close every live session for an agent now (the token keeps working — use `rotate`/`remove` to revoke it). */
|
|
102
|
+
kickAgent(name) {
|
|
103
|
+
const a = this.registry.get(name);
|
|
104
|
+
if (!a)
|
|
105
|
+
return 0;
|
|
106
|
+
let n = 0;
|
|
107
|
+
for (const c of [...this.conns])
|
|
108
|
+
if (c.session && c.session.agentId === a.id) {
|
|
109
|
+
c.session.closed = true;
|
|
110
|
+
c.session.closedReason = 'kicked';
|
|
111
|
+
try {
|
|
112
|
+
c.ws.close(4003);
|
|
113
|
+
}
|
|
114
|
+
catch { /* ignore */ }
|
|
115
|
+
n++;
|
|
116
|
+
}
|
|
117
|
+
this.audit.append({ agentId: a.id, agentName: name, session: null, kind: 'kick', tool: '-', detail: `kicked ${n} session(s)`, ok: true });
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
/** For the roster (tier 4): live sessions + usage per connected agent. */
|
|
121
|
+
roster() {
|
|
122
|
+
const byAgent = new Map();
|
|
123
|
+
for (const c of this.conns) {
|
|
124
|
+
const s = c.session;
|
|
125
|
+
if (!s)
|
|
126
|
+
continue;
|
|
127
|
+
const cur = byAgent.get(s.agentId) ?? { name: s.agentName, tier: s.tier, sessions: 0, client: s.client, lastActivity: 0, topics: new Set() };
|
|
128
|
+
cur.sessions++;
|
|
129
|
+
cur.lastActivity = Math.max(cur.lastActivity, s.lastActivity);
|
|
130
|
+
for (const t of s.topics)
|
|
131
|
+
cur.topics.add(t);
|
|
132
|
+
byAgent.set(s.agentId, cur);
|
|
133
|
+
}
|
|
134
|
+
return [...byAgent].map(([agentId, v]) => ({ agentId, name: v.name, tier: v.tier, sessions: v.sessions, client: v.client, lastActivity: v.lastActivity, topics: [...v.topics], usage: this.gate.usage(agentId), counts: this.audit.counts(agentId), paused: this.paused.has(agentId) }));
|
|
135
|
+
}
|
|
136
|
+
// ----- connection lifecycle -----
|
|
137
|
+
onConnection(ws) {
|
|
138
|
+
if (this.conns.size >= MAX_PENDING + 64) {
|
|
139
|
+
ws.close(1013);
|
|
140
|
+
return;
|
|
141
|
+
} // overall cap (pending + authed)
|
|
142
|
+
const conn = { ws, session: null, helloTimer: null };
|
|
143
|
+
this.conns.add(conn);
|
|
144
|
+
conn.helloTimer = setTimeout(() => { if (!conn.session) {
|
|
145
|
+
try {
|
|
146
|
+
ws.close(4001);
|
|
147
|
+
}
|
|
148
|
+
catch { /* ignore */ }
|
|
149
|
+
} }, HELLO_TIMEOUT_MS);
|
|
150
|
+
conn.helloTimer.unref?.();
|
|
151
|
+
ws.onMessage = (text) => this.onMessage(conn, text);
|
|
152
|
+
ws.onClose = () => this.onClose(conn);
|
|
153
|
+
}
|
|
154
|
+
onClose(conn) {
|
|
155
|
+
if (conn.helloTimer)
|
|
156
|
+
clearTimeout(conn.helloTimer);
|
|
157
|
+
if (conn.session)
|
|
158
|
+
this.audit.append({ agentId: conn.session.agentId, agentName: conn.session.agentName, session: conn.session.id, kind: 'disconnect', tool: '-', detail: 'socket closed', ok: true });
|
|
159
|
+
this.conns.delete(conn);
|
|
160
|
+
}
|
|
161
|
+
reply(conn, id, result) { conn.ws.send({ jsonrpc: '2.0', id, result }); }
|
|
162
|
+
fail(conn, id, code, message, data) { conn.ws.send({ jsonrpc: '2.0', id, error: { code, message, ...(data ? { data } : {}) } }); }
|
|
163
|
+
onMessage(conn, text) {
|
|
164
|
+
if (text.length > 256 * 1024) {
|
|
165
|
+
conn.ws.close(1009);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
let msg;
|
|
169
|
+
try {
|
|
170
|
+
msg = JSON.parse(text);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
this.fail(conn, null, -32700, 'parse error');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string') {
|
|
177
|
+
this.fail(conn, msg?.id ?? null, -32600, 'invalid request');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const id = msg.id ?? null;
|
|
181
|
+
const params = (msg.params && typeof msg.params === 'object' && !Array.isArray(msg.params)) ? msg.params : {};
|
|
182
|
+
if (msg.method === 'hello') {
|
|
183
|
+
void this.onHello(conn, id, params);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (msg.method === 'ping') {
|
|
187
|
+
this.reply(conn, id, { pong: true, t: this.now() });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!conn.session) {
|
|
191
|
+
this.fail(conn, id, -32001, 'say hello first (authenticate with your agent name + token)');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (msg.method === 'tools/list') {
|
|
195
|
+
this.reply(conn, id, { tools: toolsForTierList(conn.session.tier) });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (msg.method === 'tools/call') {
|
|
199
|
+
void this.onCall(conn, id, params);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.fail(conn, id, -32601, `method not found: ${String(msg.method).slice(0, 40)}`);
|
|
203
|
+
}
|
|
204
|
+
async onHello(conn, id, params) {
|
|
205
|
+
if (conn.session) {
|
|
206
|
+
this.fail(conn, id, -32002, 'already authenticated on this connection');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const name = params.name;
|
|
210
|
+
const token = params.token;
|
|
211
|
+
if (typeof name !== 'string' || !AGENT_NAME_RE.test(name) || !looksLikeToken(token)) {
|
|
212
|
+
this.audit.append({ agentId: null, agentName: typeof name === 'string' ? name.slice(0, 24) : '?', session: null, kind: 'refused', tool: 'hello', detail: 'bad credentials', ok: false });
|
|
213
|
+
this.fail(conn, id, -32010, 'authentication failed');
|
|
214
|
+
try {
|
|
215
|
+
conn.ws.close(4002);
|
|
216
|
+
}
|
|
217
|
+
catch { /* ignore */ }
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const rec = this.registry.verify(name, token);
|
|
221
|
+
if (!rec) {
|
|
222
|
+
this.audit.append({ agentId: null, agentName: name.slice(0, 24), session: null, kind: 'refused', tool: 'hello', detail: 'unknown agent or wrong token', ok: false });
|
|
223
|
+
this.fail(conn, id, -32010, 'authentication failed');
|
|
224
|
+
try {
|
|
225
|
+
conn.ws.close(4002);
|
|
226
|
+
}
|
|
227
|
+
catch { /* ignore */ }
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// connection cap per agent
|
|
231
|
+
const limits = this.gate.limitsFor(rec.id);
|
|
232
|
+
const live = [...this.conns].filter((c) => c.session && c.session.agentId === rec.id).length;
|
|
233
|
+
if (live >= limits.connections) {
|
|
234
|
+
this.fail(conn, id, -32011, `too many connections for this agent (max ${limits.connections})`);
|
|
235
|
+
try {
|
|
236
|
+
conn.ws.close(4009);
|
|
237
|
+
}
|
|
238
|
+
catch { /* ignore */ }
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// requested tier can only LOWER the granted one; a bogus request is ignored
|
|
242
|
+
const reqTier = isTier(params.tier) ? params.tier : rec.tier;
|
|
243
|
+
const tier = lowerTier(rec.tier, reqTier);
|
|
244
|
+
const client = firewallText(params.client, { max: 40 }).text.replace(/\s+/g, ' ').trim() || 'unknown';
|
|
245
|
+
if (rec.paused)
|
|
246
|
+
this.paused.add(rec.id);
|
|
247
|
+
this.gate.setAgentLimits(rec.id, rec.limits);
|
|
248
|
+
const session = { id: `sess_${(++this.sessionSeq).toString(36)}`, agentId: rec.id, agentName: rec.name, tier, transport: 'bus', openedAt: this.now(), lastActivity: this.now(), closed: false, closedReason: null, client, topics: new Set() };
|
|
249
|
+
conn.session = session;
|
|
250
|
+
if (conn.helloTimer) {
|
|
251
|
+
clearTimeout(conn.helloTimer);
|
|
252
|
+
conn.helloTimer = null;
|
|
253
|
+
}
|
|
254
|
+
this.audit.append({ agentId: rec.id, agentName: rec.name, session: session.id, kind: 'connect', tool: '-', detail: `tier ${tier} · ${client} · ${conn.ws.remote}`, ok: true });
|
|
255
|
+
this.reply(conn, id, { protocol: BUS_PROTOCOL, agent: rec.name, tier, grantedTier: rec.tier, tools: toolsForTier(tier), paused: rec.paused });
|
|
256
|
+
}
|
|
257
|
+
async onCall(conn, id, params) {
|
|
258
|
+
const session = conn.session;
|
|
259
|
+
if (!session) {
|
|
260
|
+
this.fail(conn, id, -32001, 'say hello first');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const name = typeof params.name === 'string' ? params.name : '';
|
|
264
|
+
const args = (params.arguments && typeof params.arguments === 'object' && !Array.isArray(params.arguments)) ? params.arguments : {};
|
|
265
|
+
const g = this.gate.check(session, { tool: name, args, schema: toolSchema(name) });
|
|
266
|
+
if (!g.ok) {
|
|
267
|
+
this.fail(conn, id, -32020, g.error, { code: g.code });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
// bus-native observe
|
|
272
|
+
if (name === 'agent_whoami') {
|
|
273
|
+
this.reply(conn, id, this.whoami(session));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (name === 'subscribe') {
|
|
277
|
+
this.reply(conn, id, this.subscribe(conn, args));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (name === 'unsubscribe') {
|
|
281
|
+
this.reply(conn, id, this.unsubscribe(session, args));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (name === 'list_proposals') {
|
|
285
|
+
const limit = Math.min(Number(args.limit ?? 50), 100);
|
|
286
|
+
this.gate.record(session, name, `listed proposals`, 'call');
|
|
287
|
+
this.reply(conn, id, { proposals: this.proposals.list({ agentId: session.agentId, limit }) });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// proposals
|
|
291
|
+
if (TOOL_TO_KIND[name]) {
|
|
292
|
+
const { proposal, duplicate } = this.proposals.create(session, name, args);
|
|
293
|
+
if (duplicate)
|
|
294
|
+
this.gate.refundProposal(session.agentId);
|
|
295
|
+
this.audit.append({ agentId: session.agentId, agentName: session.agentName, session: session.id, kind: 'proposal', tool: name, detail: `${duplicate ? 'duplicate ' : ''}${proposal.summary}`, ok: true });
|
|
296
|
+
this.reply(conn, id, { proposal, duplicate, note: 'This is a PROPOSAL. The user confirms it with a keypress; you can never arm, confirm or send it.' });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// read / navigation / note → executor. Scrub every string leaf of the result before it reaches the
|
|
300
|
+
// agent (belt-and-braces over the source firewall): no escape/bidi/zero-width can pass through.
|
|
301
|
+
const result = scrubStrings(await this.executor.exec(session, name, args));
|
|
302
|
+
const isErr = typeof result.error === 'string';
|
|
303
|
+
this.gate.record(session, name, isErr ? `error: ${String(result.error).slice(0, 80)}` : summarise(name, args), name === 'agent_note' ? 'note' : 'call');
|
|
304
|
+
this.reply(conn, id, result);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
this.fail(conn, id, -32000, 'internal error', { detail: e.message.slice(0, 80) });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
whoami(session) {
|
|
312
|
+
return { agent: session.agentName, tier: session.tier, tools: toolsForTier(session.tier).map((t) => ({ name: t, tier: agentTool(t)?.tier })), usage: this.gate.usage(session.agentId), subscriptions: [...session.topics], note: 'Your identity and tier come from the server. You cannot change them.' };
|
|
313
|
+
}
|
|
314
|
+
subscribe(conn, args) {
|
|
315
|
+
const session = conn.session;
|
|
316
|
+
const topics = Array.isArray(args.topics) ? args.topics.filter(isTopic) : [];
|
|
317
|
+
const limit = this.gate.limitsFor(session.agentId).maxTopics;
|
|
318
|
+
const added = [];
|
|
319
|
+
for (const t of topics) {
|
|
320
|
+
if (session.topics.size >= limit)
|
|
321
|
+
break;
|
|
322
|
+
if (!session.topics.has(t)) {
|
|
323
|
+
session.topics.add(t);
|
|
324
|
+
added.push(t);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.audit.append({ agentId: session.agentId, agentName: session.agentName, session: session.id, kind: 'subscribe', tool: 'subscribe', detail: `+[${added.join(',')}]`, ok: true });
|
|
328
|
+
// push an immediate snapshot per newly-added topic, if the feed offers one
|
|
329
|
+
if (this.feed?.snapshot)
|
|
330
|
+
for (const t of added) {
|
|
331
|
+
const snap = this.feed.snapshot(t);
|
|
332
|
+
if (snap)
|
|
333
|
+
conn.ws.send({ jsonrpc: '2.0', method: 'event', params: scrubStrings({ topic: t, seq: 0, t: this.now(), data: snap, snapshot: true }) });
|
|
334
|
+
}
|
|
335
|
+
return { subscribed: [...session.topics] };
|
|
336
|
+
}
|
|
337
|
+
unsubscribe(session, args) {
|
|
338
|
+
const topics = Array.isArray(args.topics) ? args.topics.filter(isTopic) : null;
|
|
339
|
+
if (topics === null)
|
|
340
|
+
session.topics.clear();
|
|
341
|
+
else
|
|
342
|
+
for (const t of topics)
|
|
343
|
+
session.topics.delete(t);
|
|
344
|
+
return { subscribed: [...session.topics] };
|
|
345
|
+
}
|
|
346
|
+
/** Fan a feed event out to every session subscribed to its topic. */
|
|
347
|
+
fanout(ev) {
|
|
348
|
+
for (const c of this.conns) {
|
|
349
|
+
const s = c.session;
|
|
350
|
+
if (!s || s.closed || this.paused.has(s.agentId))
|
|
351
|
+
continue;
|
|
352
|
+
if (s.topics.has(ev.topic))
|
|
353
|
+
c.ws.send({ jsonrpc: '2.0', method: 'event', params: scrubStrings(ev) });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
writeDiscovery() {
|
|
357
|
+
if (!this.discoveryPath)
|
|
358
|
+
return;
|
|
359
|
+
try {
|
|
360
|
+
mkdirSync(dirname(this.discoveryPath), { recursive: true, mode: 0o700 });
|
|
361
|
+
writeFileSync(this.discoveryPath, JSON.stringify({ protocol: BUS_PROTOCOL, port: this.port, pid: process.pid, startedAt: this.now() }, null, 2) + '\n', { mode: 0o600 });
|
|
362
|
+
try {
|
|
363
|
+
chmodSync(this.discoveryPath, 0o600);
|
|
364
|
+
}
|
|
365
|
+
catch { /* ignore */ }
|
|
366
|
+
}
|
|
367
|
+
catch { /* discovery is best-effort; the port can also be passed explicitly */ }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function summarise(name, args) {
|
|
371
|
+
const mint = typeof args.mint === 'string' ? args.mint.slice(0, 6) + '…' : '';
|
|
372
|
+
if (mint)
|
|
373
|
+
return `${name} ${mint}`;
|
|
374
|
+
if (typeof args.query === 'string')
|
|
375
|
+
return `${name} "${args.query.slice(0, 24)}"`;
|
|
376
|
+
if (typeof args.preset === 'string')
|
|
377
|
+
return `${name} ${args.preset}`;
|
|
378
|
+
return name;
|
|
379
|
+
}
|