dexterm-cli 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/agents/src/index.js +2 -0
- package/dist/packages/agents/src/spectator.js +54 -0
- package/dist/packages/agents/src/story.js +523 -0
- package/dist/packages/agents/src/tiers.js +1 -0
- package/dist/packages/agents/src/tools.js +1 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
- package/dist/packages/cli/src/agent-cmd.js +6 -4
- package/dist/packages/cli/src/agent-spectator.js +17 -1
- package/dist/packages/cli/src/analyst/backend.js +23 -0
- package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
- package/dist/packages/cli/src/analyst/session.js +12 -1
- package/dist/packages/cli/src/app-help-gen.js +7 -7
- package/dist/packages/cli/src/backtest-cmd.js +13 -2
- package/dist/packages/cli/src/display.js +55 -9
- package/dist/packages/cli/src/live/dev-history.js +31 -15
- package/dist/packages/cli/src/live/index.js +5 -1
- package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
- package/dist/packages/cli/src/live/screens/token-live.js +41 -11
- package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
- package/dist/packages/cli/src/live/trade.js +22 -0
- package/dist/packages/cli/src/main.js +146 -12
- package/dist/packages/cli/src/mcp-follow.js +103 -6
- package/dist/packages/cli/src/plan-cmd.js +15 -5
- package/dist/packages/cli/src/popup.js +17 -1
- package/dist/packages/cli/src/scan/ui.js +7 -1
- package/dist/packages/cli/src/screens/agent-hud.js +741 -0
- package/dist/packages/cli/src/screens/analyst-session.js +4 -0
- package/dist/packages/cli/src/screens/brain.js +608 -0
- package/dist/packages/cli/src/screens/newpairs.js +1 -1
- package/dist/packages/cli/src/screens/pnl-chip.js +143 -0
- package/dist/packages/cli/src/screens/screener.js +1 -0
- package/dist/packages/cli/src/screens/token.js +16 -7
- package/dist/packages/cli/src/screens/trending.js +1 -1
- package/dist/packages/cli/src/state.js +69 -5
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/custom/src/schema.js +8 -2
- package/dist/packages/data/src/index.js +2 -0
- package/dist/packages/data/src/larp/score.js +220 -0
- package/dist/packages/data/src/larp/x-account.js +316 -0
- package/dist/packages/data/src/trade/agent/paper-desk.js +77 -12
- package/dist/packages/data/src/trade/plans/engine.js +7 -0
- package/dist/packages/data/src/trade/plans/mandate.js +28 -15
- package/dist/packages/data/src/trade/plans/service.js +1 -1
- package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +3 -2
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +1 -9
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +1 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
- package/dist/packages/mcp/src/paper-agent.js +3 -2
- package/dist/packages/mcp/src/server.js +162 -21
- package/dist/packages/mcp/src/spectator-sink.js +85 -0
- package/dist/packages/packs/src/index.js +1 -0
- package/dist/packages/packs/src/larp-score-backtest.js +119 -0
- package/dist/packages/popup/src/pages/chart-lib.js +36 -15
- package/dist/packages/popup/src/pages/chart.css +9 -0
- package/dist/packages/popup/src/pages/chart.html +4 -1
- package/dist/packages/popup/src/pages/chart.js +34 -5
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +1 -0
- package/dist/packages/popup/src/pages/spectator.js +69 -0
- package/dist/packages/popup/src/protocol.js +2 -0
- package/dist/packages/popup/src/server.js +5 -1
- package/dist/packages/recorder/src/dev-history.js +7 -3
- package/dist/packages/tui/src/canvas.js +7 -0
- package/dist/packages/tui/src/fmt.js +53 -0
- package/dist/packages/tui/src/keymap.js +4 -0
- package/dist/packages/tui/src/widgets.js +9 -4
- package/package.json +1 -1
|
@@ -14,11 +14,12 @@ import { renderScanWhy, renderScanTicker } from "../../scan/ui.js";
|
|
|
14
14
|
import { drawCoin, imagesOn } from "../../screens/coin.js";
|
|
15
15
|
import { COIN_HERO_W, COIN_HERO_H } from '../../../../tui/src/index.js';
|
|
16
16
|
import { renderSocialPanel } from "../../screens/social-panel.js";
|
|
17
|
+
import { brainFor, factsFromView, renderBrainPanel } from "../../screens/brain.js";
|
|
17
18
|
import { launchIntelCells, holderTags } from "../intel.js";
|
|
18
19
|
import { devBookOf, devRows } from "../dev-history.js";
|
|
19
20
|
import { EX_POOL_TAGS } from '../../../../data/src/index.js';
|
|
20
21
|
import { layoutFor } from "../../custom.js";
|
|
21
|
-
import { chartMode, priceModeOf, mcapAt, positionPriceCells } from "../../display.js";
|
|
22
|
+
import { chartMode, chartTitle, priceModeOf, chartAxisOf, mcapAt, positionPriceCells } from "../../display.js";
|
|
22
23
|
const dash = '—';
|
|
23
24
|
const EX_POOL_TAG_SET = EX_POOL_TAGS;
|
|
24
25
|
/** task-1367 holder-class badge colours: venues/escrows accent, the creator/linked warn, the rest muted. */
|
|
@@ -87,13 +88,13 @@ export function renderTokenLive(c, state, v) {
|
|
|
87
88
|
const shownPx = livePx ?? v.price;
|
|
88
89
|
// task-1366: mcap mode leads with the market cap (the live price × the polled supply ratio) and keeps the price beside it;
|
|
89
90
|
// without a supply it shows the price and says so.
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
x = c.text(x0, y, fmtMcap(mcapAt(shownPx,
|
|
91
|
+
const pm = chartMode(priceModeOf(state), v.price, v.mcap);
|
|
92
|
+
if (pm.kind === 'mcap' && shownPx !== null) {
|
|
93
|
+
x = c.text(x0, y, fmtMcap(mcapAt(shownPx, pm.scale)), { fg: t.text, bg: t.bg, b: true });
|
|
93
94
|
x = c.text(x + 1, y, `(${fmtPrice(shownPx)})`, { fg: t.dim, bg: t.bg });
|
|
94
95
|
}
|
|
95
96
|
else
|
|
96
|
-
x = c.text(x0, y, shownPx === null ? dash : fmtPrice(shownPx), { fg:
|
|
97
|
+
x = c.text(x0, y, shownPx === null ? dash : fmtPrice(shownPx), { fg: pm.label && shownPx !== null ? t.warn : t.text, bg: t.bg, b: true });
|
|
97
98
|
if (state.live?.fastLane)
|
|
98
99
|
x = badge(c, x + 1, y, liveBadge, liveBadge === 'LIVE' ? t.up : t.muted, { on: t.bg });
|
|
99
100
|
const deltas = [['5m', v.chg.m5], ['1h', v.chg.h1], ['6h', v.chg.h6], ['24h', v.chg.h24]];
|
|
@@ -131,7 +132,9 @@ export function renderTokenLive(c, state, v) {
|
|
|
131
132
|
const chartH = Math.max(10, Math.floor((bottom - y - posH) * lay.chartFrac));
|
|
132
133
|
const cp = v.panels.candles;
|
|
133
134
|
const chartCap = clip(cp.state === 'ok' || cp.fetchedAt !== null ? `ohlcv: geckoterminal${cp.stale || cp.state === 'error' ? ' · stale' : ''}` : panelCaption(cp, now, 'ohlcv unavailable'), 40);
|
|
134
|
-
|
|
135
|
+
// task-1472: the chart's Y axis is its own setting (default MARKET CAP, `A` toggles); the header above keeps `$`.
|
|
136
|
+
const cm = chartMode(chartAxisOf(state), v.price, v.mcap);
|
|
137
|
+
const cb = c.box(0, y, mainW, chartH, { title: chartTitle(v.symbol, v.interval, cm), caption: chartCap, captionFg: cp.state === 'error' && cp.fetchedAt === null ? t.warn : undefined });
|
|
135
138
|
// timeframe tabs sit on the top border, left of the caption (same spot as the fixture screen)
|
|
136
139
|
let tx = cb.x + cb.w - 2 - (chartCap.length + 4) - INTERVALS.reduce((s, f) => s + f.length + 1, 0);
|
|
137
140
|
for (const f of INTERVALS)
|
|
@@ -142,9 +145,12 @@ export function renderTokenLive(c, state, v) {
|
|
|
142
145
|
c.textC(cb.x, cb.y + Math.floor(cb.h / 2), cb.w, cp.state === 'loading' ? 'loading candles…' : `no candles · ${cp.reason ?? 'source did not answer'}`, { fg: t.muted, bg: cb.bg, i: true });
|
|
143
146
|
y += chartH;
|
|
144
147
|
if (prow && src) {
|
|
145
|
-
positionPanel(c, 0, y, mainW, prow, held?.kind ?? src.kind, priceModeOf(state),
|
|
148
|
+
positionPanel(c, 0, y, mainW, prow, held?.kind ?? src.kind, priceModeOf(state), pm.scale);
|
|
146
149
|
y += posH;
|
|
147
150
|
}
|
|
151
|
+
// task-1473: what the ON-CHAIN band derived this frame, for the brain view's vetting list (null under SOCIAL)
|
|
152
|
+
let brainTop10 = null;
|
|
153
|
+
let brainDev = null;
|
|
148
154
|
// ---- task-1330: the SOCIAL panel (`s`) replaces the ON-CHAIN / HOLDERS band; the chart + position stay.
|
|
149
155
|
if (state.tokenSocial) {
|
|
150
156
|
renderSocialPanel(c, 0, y, mainW, bottom - y, state, { mint: v.mint, symbol: v.symbol, now });
|
|
@@ -182,9 +188,11 @@ export function renderTokenLive(c, state, v) {
|
|
|
182
188
|
// 0.1.5 dev history (task-1425): the creator's recorded track record from the local recorder (keyless). The rugs
|
|
183
189
|
// row sits with the other rug signals; launches / bag-exit / linked wallets follow the safety list.
|
|
184
190
|
const book = devBookOf(state);
|
|
185
|
-
const devAll = book ? devRows(book.forMint(v.mint, v.devAddress ?? null, state.live?.intel?.analysis(v.mint) ?? null)) : [];
|
|
191
|
+
const devAll = book ? devRows(book.forMint(v.mint, v.devAddress ?? null, state.live?.intel?.analysis(v.mint) ?? null, state.live?.intel?.entry(v.mint)?.at ?? null), now) : [];
|
|
186
192
|
const devHead = devAll.slice(0, 1);
|
|
187
193
|
const devTail = devAll.slice(1);
|
|
194
|
+
brainTop10 = tags.top10 ? tags.top10.exPool : v.top10;
|
|
195
|
+
brainDev = (devHead[0] ? [`${devHead[0][0]}: ${devHead[0][1]}`, devHead[0][2]] : v.devMints === null ? null : [`${v.devMints} mints by dev (jup)`, v.devMints > 3 ? 'warn' : 'ok']);
|
|
188
196
|
const sniperRow = [cells.snipers.text, cells.snipers.level];
|
|
189
197
|
const bundleRow = [cells.bundle.text, cells.bundle.level];
|
|
190
198
|
const rows = [
|
|
@@ -201,7 +209,7 @@ export function renderTokenLive(c, state, v) {
|
|
|
201
209
|
['GoPlus', ...goplusRow(v)],
|
|
202
210
|
...devTail,
|
|
203
211
|
];
|
|
204
|
-
for (const [label, val0, lvl] of rows) {
|
|
212
|
+
for (const [label, val0, lvl] of fitRows(rows, sb.y + sb.h - sy)) {
|
|
205
213
|
if (sy >= sb.y + sb.h)
|
|
206
214
|
break;
|
|
207
215
|
const color = lvl === 'ok' ? t.up : lvl === 'warn' ? t.warn : lvl === 'bad' ? t.down : t.dim;
|
|
@@ -327,8 +335,15 @@ export function renderTokenLive(c, state, v) {
|
|
|
327
335
|
}
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
|
-
if (showAi)
|
|
331
|
-
|
|
338
|
+
if (showAi) {
|
|
339
|
+
// task-1473: in an auto mode (following an agent / AI scan) the panel is the BRAIN VIEW; otherwise the analyst
|
|
340
|
+
const facts = factsFromView(v, { top10: brainTop10, dev: brainDev, larpVerdict: state.larpByMint?.get(v.mint)?.verdict ?? null, price: shownPx });
|
|
341
|
+
const brain = brainFor(state, facts, isCalm(state.vfx), state.live?.scanMeterLabel?.() ?? '');
|
|
342
|
+
if (brain)
|
|
343
|
+
renderBrainPanel(c, mainW, top, aiW, bottom - top, brain.model, brain.opts);
|
|
344
|
+
else
|
|
345
|
+
analystPlaceholder(c, mainW, top, aiW, bottom - top, v, state);
|
|
346
|
+
}
|
|
332
347
|
const watched = state.live?.watch?.has(v.mint) ? 'unwatch' : 'watch';
|
|
333
348
|
// task-1311: `i` asks claude about this token (chat), `a` runs the paid dossier on demand, `y`
|
|
334
349
|
// copies the full contract address.
|
|
@@ -402,6 +417,21 @@ function positionPanel(c, x, y, w, r, kind, mode = 'price', supply = null) {
|
|
|
402
417
|
function clip(s, max) {
|
|
403
418
|
return s.length > max ? s.slice(0, Math.max(1, max - 1)) + '…' : s;
|
|
404
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* task-1463: the ON-CHAIN rows for a panel `space` lines tall. When they all fit they are returned unchanged. When
|
|
422
|
+
* they do not, a short panel must not drop a red flag silently (e.g. `Dev rugs 2/3`, the last rows of the list):
|
|
423
|
+
* bad rows are kept first, then warn rows, then the rest in order, the kept rows stay in their order, and the last
|
|
424
|
+
* line says how many rows are hidden.
|
|
425
|
+
*/
|
|
426
|
+
export function fitRows(rows, space) {
|
|
427
|
+
if (rows.length <= space || space <= 0)
|
|
428
|
+
return rows;
|
|
429
|
+
const slots = space - 1;
|
|
430
|
+
const rank = (l) => (l === 'bad' ? 0 : l === 'warn' ? 1 : 2);
|
|
431
|
+
const keep = new Set(rows.map((r, i) => [rank(r[2]), i]).sort((a, b) => a[0] - b[0] || a[1] - b[1]).slice(0, slots).map(([, i]) => i));
|
|
432
|
+
const hidden = rows.length - keep.size;
|
|
433
|
+
return [...rows.filter((_, i) => keep.has(i)), [`+${hidden} rows hidden`, 'taller window', 'na']];
|
|
434
|
+
}
|
|
405
435
|
/** Token amounts up to trillions (fmtNum stops at B). */
|
|
406
436
|
function fmtAmt(n) {
|
|
407
437
|
if (n >= 1e12)
|
|
@@ -89,7 +89,7 @@ export function renderWatchlistLive(c, state, live) {
|
|
|
89
89
|
} });
|
|
90
90
|
}
|
|
91
91
|
if (rows.length)
|
|
92
|
-
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: promoteMcap(cols, mode), rows, selected: sel, bg: box.bg });
|
|
92
|
+
table(c, box.x + 1, box.y, box.w - 2, box.h, { columns: promoteMcap(cols, mode), rows, selected: sel, bg: box.bg, rowKey: (r) => r.entry.mint });
|
|
93
93
|
else
|
|
94
94
|
c.textC(box.x, box.y + Math.min(3, Math.floor(box.h / 2)), box.w, 'watchlist is empty · press w on any token (screener, new pairs, token screen) to add it', { fg: t.muted, bg: box.bg, i: true });
|
|
95
95
|
const y2 = top + listH;
|
|
@@ -669,6 +669,8 @@ export function positionRow(h, tickUsd, solUsd) {
|
|
|
669
669
|
}
|
|
670
670
|
/** How long a `held()` lookup is reused before re-reading the ledger (the render loop calls it per frame). */
|
|
671
671
|
export const HELD_MEMO_MS = 250;
|
|
672
|
+
/** task-1473 r2: how long the open-mints list is reused (a ledger read; the chip samples it every tick). */
|
|
673
|
+
export const OPEN_MINTS_MEMO_MS = 1000;
|
|
672
674
|
/** Minimum spacing of the token screen's automatic mark pass for a held coin missing SOL/USD or a mark. */
|
|
673
675
|
export const HELD_AUTOMARK_MS = 30_000;
|
|
674
676
|
/** Live PAPER positions + PnL: marks held tokens from the keyless price feed. */
|
|
@@ -738,6 +740,22 @@ export class PositionsLive {
|
|
|
738
740
|
}
|
|
739
741
|
return v;
|
|
740
742
|
}
|
|
743
|
+
/** task-1473 r2: mints of the open paper positions (the AI-scan PnL chip), memoised OPEN_MINTS_MEMO_MS. */
|
|
744
|
+
openMints() {
|
|
745
|
+
const now = this.now();
|
|
746
|
+
if (this.openMemo && now - this.openMemo.at >= 0 && now - this.openMemo.at < OPEN_MINTS_MEMO_MS)
|
|
747
|
+
return this.openMemo.v;
|
|
748
|
+
let v = [];
|
|
749
|
+
try {
|
|
750
|
+
v = this.wallet.positions().filter((p) => p.tokensRaw > 0n).map((p) => p.mint);
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
v = [];
|
|
754
|
+
}
|
|
755
|
+
this.openMemo = { at: now, v };
|
|
756
|
+
return v;
|
|
757
|
+
}
|
|
758
|
+
openMemo = null;
|
|
741
759
|
/**
|
|
742
760
|
* Mark held tokens in SOL. Prices come from DexScreener (its own fast 300 rpm bucket), not the
|
|
743
761
|
* keyless `jupiter` bucket the screener feed and quotes share — so a mark never queues behind them.
|
|
@@ -917,6 +935,8 @@ export class LivePositions {
|
|
|
917
935
|
this.memo.set(mint, { at: now, v });
|
|
918
936
|
return v;
|
|
919
937
|
}
|
|
938
|
+
/** task-1473 r2: mints of the open live positions (the AI-scan PnL chip). Memoised with positions(). */
|
|
939
|
+
openMints() { return this.positions().filter((p) => p.tokensRaw > 0n).map((p) => p.mint); }
|
|
920
940
|
/** Forget memoised reads (after a fill). */
|
|
921
941
|
invalidate() { this.memo.clear(); this.rowsMemo = null; }
|
|
922
942
|
}
|
|
@@ -936,6 +956,8 @@ export class CombinedPositions {
|
|
|
936
956
|
return p;
|
|
937
957
|
}
|
|
938
958
|
solUsd() { return this.paper.solUsd() ?? this.live?.solUsd() ?? null; }
|
|
959
|
+
/** task-1473 r2: every open mint, live first (the AI-scan PnL chip picks one). */
|
|
960
|
+
openMints() { return [...new Set([...(this.live?.openMints() ?? []), ...this.paper.openMints()])]; }
|
|
939
961
|
}
|
|
940
962
|
function msg(e) {
|
|
941
963
|
return e instanceof Error ? e.message : String(e);
|
|
@@ -25,7 +25,7 @@ import { parseMintTarget, parseCoinTarget, resolveCoinTarget, isBareOpen } from
|
|
|
25
25
|
import { THEMES, isThemeName, DEFAULT_THEME, THEME_NAMES, Flusher, ENTER_SCREEN, LEAVE_SCREEN, DEFAULT_KEYMAP, Keymap, nextTheme, parseKeys, keyId, detectColorDepth, parseColorDepth, blinkFrame, blinkFrames, stepVfx, pulse, isCalm, fxBusy, blendActivity, ImageLayer, queryTerminal, resolveProtocol, fireMoment, fireInflight, clearInflight, skipMoment, momentKindFor, playMoment, soundOn, breakpoint, fmtUsd as fmtUsdTui, } from '../../tui/src/index.js';
|
|
26
26
|
import { resolveRpcUrl, redactSecrets, defaultDbPath, extraRpcUrls, wsUrlFor, SOLANA_PUBLIC_RPC_WS, parseHttpUrl, validateAnalystAction, ACTION_RATE, ACTION_TIER, configChangeForAction, ImageService, encodePng, SummaryLimiter, buildSummaryWindow, KolStore, Cache, PackError, normalizeHandle, terminalSafe } from '../../data/src/index.js';
|
|
27
27
|
import { Recorder, recorderEnabled, MaintenanceSchedule } from '../../recorder/src/index.js';
|
|
28
|
-
import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, activityFor, SCREEN_NAMES, analystKey, syncAnalyst, pinNewpairsSelection, ANALYST_TAB_KEY, computeNarrativeView, newPlansUi, plansService, plansRows, newFollowsUi, copyService, copyAutoService, MIRROR_DEFAULT_CAP_USD } from "./state.js";
|
|
28
|
+
import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, activityFor, SCREEN_NAMES, analystKey, syncAnalyst, pinNewpairsSelection, ANALYST_TAB_KEY, computeNarrativeView, newPlansUi, plansService, plansRows, newFollowsUi, copyService, copyAutoService, MIRROR_DEFAULT_CAP_USD, pnlChipFor } from "./state.js";
|
|
29
29
|
import { mulberry32, tick, spawnPair, shock } from "./fixtures.js";
|
|
30
30
|
import { createLiveFeed, createReplayFeed } from "./feed.js";
|
|
31
31
|
import { LiveContext, INTERVALS, WATCH_REFRESH_MS, POSITIONS_REFRESH_MS, popupBubbleVM } from "./live/index.js";
|
|
@@ -39,7 +39,9 @@ import { parseFilter } from "./screener/filter.js";
|
|
|
39
39
|
import { screenerView, setFilter, setSort, setPreset, stepPreset, clampSelection, openFilterEditor, openSortMenu, filterEditorKey, sortMenuKey, } from "./screener/view.js";
|
|
40
40
|
import { readBootState, bootPlan, recordBoot, bootTrace, dextermHome } from "./boot-state.js";
|
|
41
41
|
import { socialOf, kolOf, trendingRows, kolRows, nextSocialFilter, feedsPopupModel } from "./social/view.js";
|
|
42
|
-
import { TokenPopups, tokenDetailVMFromView } from "./popup.js";
|
|
42
|
+
import { TokenPopups, tokenDetailVMFromView, currentPositionFills } from "./popup.js";
|
|
43
|
+
import { OwnPnlTracker } from "./screens/pnl-chip.js";
|
|
44
|
+
import { positionRow } from "./live/trade.js";
|
|
43
45
|
import { widgetPage, widgetEnabled, syncWidgetPopups } from "./widgets.js";
|
|
44
46
|
import { recordLarpVerdict } from "./larp-record.js";
|
|
45
47
|
import { rulesUiOf, rulesTick, rulesMove, beginRuleInput, saveRuleInput, dryRunNow, removeSelectedRule, toggleSelectedRule, dismissSelectedProposal, actOnSelectedProposal, RULE_INPUT_MAX } from "./rules.js";
|
|
@@ -53,7 +55,7 @@ import { resolveAnalystResults } from "./analyst/results.js";
|
|
|
53
55
|
import { parseScanCommand, SCAN_FLAGS, checkScanFlag, SCAN_HELP } from "./scan/command.js";
|
|
54
56
|
import { parseWalletCommand, WALLET_HELP } from "./wallet-command.js";
|
|
55
57
|
import { X_HELP } from "./x-cmd.js";
|
|
56
|
-
import { makeScanUi, MODE_LABEL, SCAN_ACTION_TYPES, SCAN_TICK_MS, SCAN_RECOMPUTE_MS } from "./scan/ui.js";
|
|
58
|
+
import { makeScanUi, scanLog, MODE_LABEL, SCAN_ACTION_TYPES, SCAN_TICK_MS, SCAN_RECOMPUTE_MS } from "./scan/ui.js";
|
|
57
59
|
import { buildObservations, observeSig } from "./scan/observe.js";
|
|
58
60
|
import { cleanText } from "./screener/rows.js";
|
|
59
61
|
import { resolveImagesConfig } from "./images-config.js";
|
|
@@ -62,7 +64,8 @@ import { VoiceController, selectStt, selectTts, SayTts, voiceStyleDefaults } fro
|
|
|
62
64
|
import { LivePlanExecutor, defaultPlansJournalPath } from '../../data/src/trade/plans/index.js';
|
|
63
65
|
import { isWalletAddress, shortAddr, buildMirror, leaderSoldSince, autoSizeCap } from '../../data/src/trade/copy/index.js';
|
|
64
66
|
import { FOLLOW_INPUT_MAX } from "./screens/follows.js";
|
|
65
|
-
import {
|
|
67
|
+
import { HUD_MIN_H, hudAnimating } from "./screens/agent-hud.js";
|
|
68
|
+
import { isPriceMode, configPriceMode, togglePriceMode, priceModeOf, planLevels, configChartAxis, setChartAxis, chartAxisOf } from "./display.js";
|
|
66
69
|
import { VERSION } from "./version.js";
|
|
67
70
|
import { initCustom, planWorkspace, planPopupOpens, findWorkspace, nextWorkspaceName, workspaceSlots, saveWorkspace, themeFor, themeByName, cycleTheme, themeNames, CHORD_TTL_MS, MAX_WORKSPACE_NAME } from "./custom.js";
|
|
68
71
|
import { DEFAULT_WORKSPACE, MORNING_HUD, applyAndJournal, policyCheck } from '../../custom/src/index.js';
|
|
@@ -124,19 +127,35 @@ export function buildScreenState(state) {
|
|
|
124
127
|
const sc = state.screener;
|
|
125
128
|
// The screener and the Analyst-tab coin list both show the filtered/sorted screener view, so the
|
|
126
129
|
// selection + visible rows must come from that same view (task-1313); other screens use the raw pool.
|
|
127
|
-
|
|
130
|
+
// The new-pairs screen shows the live PumpPortal list (task-1443 capture r2: it used to report the screener pool
|
|
131
|
+
// here, so an agent watching new pairs could not read what was on screen or what was highlighted).
|
|
132
|
+
const newPairs = state.screen === 'newpairs' && live ? live.newpairs.list() : null;
|
|
133
|
+
const rows = state.screen === 'screener' || state.screen === 'analyst' ? screenerView(sc) : newPairs ?? sc.rows;
|
|
128
134
|
const toRow = (r) => {
|
|
129
135
|
const x = r;
|
|
130
136
|
if (!x?.mint)
|
|
131
137
|
return null;
|
|
132
138
|
return { mint: x.mint, symbol: x.symbol ?? '', priceUsd: x.price ?? null, chg1h: x.chg?.h1 ?? null, chg24h: x.chg?.h24 ?? null, liq: x.liq ?? null, vol24: x.vol24 ?? null };
|
|
133
139
|
};
|
|
134
|
-
const
|
|
140
|
+
const toNewPairRow = (r) => {
|
|
141
|
+
const x = r;
|
|
142
|
+
if (!x?.mint)
|
|
143
|
+
return null;
|
|
144
|
+
return {
|
|
145
|
+
mint: x.mint, symbol: x.symbol ?? '', priceUsd: x.price ?? null, chg1h: x.chg?.h1 ?? null, chg24h: null, liq: x.liq ?? null, vol24: x.vol ?? null,
|
|
146
|
+
ageSec: Number.isFinite(x.createdAt) ? Math.max(0, Math.round((Date.now() - x.createdAt) / 1000)) : null,
|
|
147
|
+
mcap: x.mcap ?? null, chg5m: x.chg?.m5 ?? null, bonding: x.bonding ?? null, buys: x.buys ?? null, sells: x.sells ?? null,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const mapRow = newPairs ? toNewPairRow : toRow;
|
|
151
|
+
const visible = rows.slice(0, 12).map(mapRow).filter((r) => r !== null);
|
|
135
152
|
let selected = null;
|
|
136
153
|
if (state.screen === 'token' && live?.token?.view)
|
|
137
154
|
selected = { mint: live.token.view.mint, symbol: live.token.view.symbol };
|
|
138
155
|
else {
|
|
139
|
-
|
|
156
|
+
// Same pick as the new-pairs screen's own highlight (selectedMint first, then the cursor row).
|
|
157
|
+
const hit = newPairs && state.selectedMint ? newPairs.find((x) => x.mint === state.selectedMint) : undefined;
|
|
158
|
+
const r = mapRow(hit ?? rows[state.selected || 0]);
|
|
140
159
|
if (r)
|
|
141
160
|
selected = { mint: r.mint, symbol: r.symbol };
|
|
142
161
|
}
|
|
@@ -267,7 +286,7 @@ export function shouldDrawFrame(g) {
|
|
|
267
286
|
return true;
|
|
268
287
|
if (g.calm)
|
|
269
288
|
return g.onTick;
|
|
270
|
-
if (g.fxBusy)
|
|
289
|
+
if (g.fxBusy || g.hud)
|
|
271
290
|
return true;
|
|
272
291
|
return g.frame % g.idleEvery === 0;
|
|
273
292
|
}
|
|
@@ -306,6 +325,8 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
306
325
|
--record FILE save every live response (and the PumpPortal session) to FILE (replay it with --replay)
|
|
307
326
|
--spectate-mcp follow an MCP agent's allowed navigation in this terminal; Ctrl+P pauses, Ctrl+K stops,
|
|
308
327
|
and any normal user keypress takes control (refused actions never move the UI)
|
|
328
|
+
--spectate-thoughts FILE (with --spectate-mcp) stream the agent's own words into the thought ribbon from a JSONL
|
|
329
|
+
file it writes (e.g. claude -p --output-format stream-json); display only, scrubbed
|
|
309
330
|
--agent-paper MANDATE_ID (experimental, with --spectate-mcp) let that MCP agent PAPER-trade inside a mandate you
|
|
310
331
|
armed: simulated fills only, never signs or sends, never raises a cap; Ctrl+P / Ctrl+K /
|
|
311
332
|
any key also pause, stop or take over its paper trading
|
|
@@ -419,7 +440,7 @@ export function parseArgs(argv) {
|
|
|
419
440
|
sound: false,
|
|
420
441
|
trade: [], live: true,
|
|
421
442
|
workspace: null, priceMode: null,
|
|
422
|
-
spectateMcp: false, agentPaper: null,
|
|
443
|
+
spectateMcp: false, spectateThoughts: null, agentPaper: null,
|
|
423
444
|
help: false, version: false, unknown: [],
|
|
424
445
|
};
|
|
425
446
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -611,6 +632,8 @@ export function parseArgs(argv) {
|
|
|
611
632
|
// task-1421: follow an agent driving over MCP (the running terminal moves with it; a read-only stream opens).
|
|
612
633
|
else if (k === '--spectate-mcp')
|
|
613
634
|
a.spectateMcp = true;
|
|
635
|
+
else if (k === '--spectate-thoughts')
|
|
636
|
+
a.spectateThoughts = str();
|
|
614
637
|
// task-1443: agent PAPER trading inside a mandate the user armed (validated against the plans journal at start).
|
|
615
638
|
else if (k === '--agent-paper') {
|
|
616
639
|
const s = str();
|
|
@@ -655,6 +678,8 @@ export function parseArgs(argv) {
|
|
|
655
678
|
throw new Error('--scan needs live data or a recording (drop --fixtures)');
|
|
656
679
|
if (a.scan && a.screen)
|
|
657
680
|
throw new Error('--scan is interactive (drop --screen)');
|
|
681
|
+
if (a.spectateThoughts && !a.spectateMcp)
|
|
682
|
+
throw new Error('--spectate-thoughts needs --spectate-mcp (the thought ribbon is part of the follow view)');
|
|
658
683
|
if (a.agentPaper && !a.spectateMcp)
|
|
659
684
|
throw new Error('--agent-paper needs --spectate-mcp (every agent paper trade must be visible in this terminal)');
|
|
660
685
|
if (a.agentPaper && a.db === ':memory:')
|
|
@@ -1127,6 +1152,7 @@ export async function main(argv, io = process, proc = process) {
|
|
|
1127
1152
|
for (const e of state.custom.themeErrors)
|
|
1128
1153
|
io.stderr.write(`dexterm: ${e}\n`);
|
|
1129
1154
|
state.priceMode = args.priceMode ?? configPriceMode(state.custom.config); // task-1366: --price-mode wins for the run
|
|
1155
|
+
state.chartAxis = configChartAxis(state.custom.config); // task-1472: default MARKET CAP, `A` toggles + saves
|
|
1130
1156
|
if (args.workspace) {
|
|
1131
1157
|
const r = switchWorkspace(state, args.workspace, []);
|
|
1132
1158
|
if (!r.ok) {
|
|
@@ -2471,6 +2497,9 @@ export function applyAction(state, action, stack) {
|
|
|
2471
2497
|
case 'display.priceMode':
|
|
2472
2498
|
state.pendingToast = togglePriceMode(state);
|
|
2473
2499
|
break; // task-1366: display only, persisted
|
|
2500
|
+
case 'display.chartAxis':
|
|
2501
|
+
state.pendingToast = setChartAxis(state);
|
|
2502
|
+
break; // task-1472: both charts' Y axis, display only, persisted
|
|
2474
2503
|
case 'theme.next':
|
|
2475
2504
|
state.theme = state.custom ? cycleTheme(state.custom, state.theme) : nextTheme(state.theme);
|
|
2476
2505
|
break; // task-1358: user themes join the cycle
|
|
@@ -2634,6 +2663,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2634
2663
|
if (!e.start(now))
|
|
2635
2664
|
return `AI scan is already on (${MODE_LABEL[e.mode]})`;
|
|
2636
2665
|
live.scanMeter = scanMeterOf(e, now);
|
|
2666
|
+
scanLog(scan, now, 'start', '', `scan on · observe-only · locks at score ≥ ${e.cfg.minScore} · liquidity ≥ $${Math.round(e.cfg.minLiqUsd / 1000)}K`);
|
|
2637
2667
|
return `AI scan ON · observe-only · warming up ${e.cfg.warmupS}s · locks at score ≥ ${e.cfg.minScore} · ai ≤ ${e.cfg.maxAiPerHour}/h · Z stop`;
|
|
2638
2668
|
case 'off': {
|
|
2639
2669
|
if (!e.on)
|
|
@@ -2641,6 +2671,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2641
2671
|
stopTurn();
|
|
2642
2672
|
const was = e.lock?.symbol;
|
|
2643
2673
|
e.stop(now);
|
|
2674
|
+
scanLog(scan, now, 'stop', was ?? '', was ? 'scan stopped · released' : 'scan stopped');
|
|
2644
2675
|
live.scanMeter = null;
|
|
2645
2676
|
scan.returnTo = null;
|
|
2646
2677
|
return `AI scan stopped${was ? ` (released ${sym(was)})` : ''}`;
|
|
@@ -2653,6 +2684,7 @@ export function scanCommand(state, cmd, stack, now = Date.now()) {
|
|
|
2653
2684
|
const locked = e.lock;
|
|
2654
2685
|
stopTurn();
|
|
2655
2686
|
e.resume(now);
|
|
2687
|
+
scanLog(scan, now, 'release', locked?.symbol ?? '', `released · cooling down ${Math.round(e.cfg.coinCooldownS / 60)}m · watching for the next coin`);
|
|
2656
2688
|
live.scanMeter = scanMeterOf(e, now);
|
|
2657
2689
|
// back from the coin the lock pulled up to where the user was, so the ticker is in view
|
|
2658
2690
|
if (locked && state.screen === 'token' && live.token?.view.mint === locked.mint)
|
|
@@ -2995,6 +3027,61 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2995
3027
|
times.push(now);
|
|
2996
3028
|
return { ok: true, label: res.label };
|
|
2997
3029
|
}
|
|
3030
|
+
/**
|
|
3031
|
+
* task-1466: the price this terminal itself shows for a coin, to mark the followed agent's open PAPER position (the
|
|
3032
|
+
* HUD's live PnL): the open token's push price (LIVE) or polled price, else the screener row's price. null if the
|
|
3033
|
+
* terminal has no price for it. Never the agent's own number.
|
|
3034
|
+
*/
|
|
3035
|
+
export function markPriceFor(state, mint) {
|
|
3036
|
+
const live = state.live;
|
|
3037
|
+
if (live?.token?.view.mint === mint) {
|
|
3038
|
+
const px = (live.badgeFor(mint) === 'LIVE' ? live.livePrice(mint) : null) ?? live.token.view.price;
|
|
3039
|
+
if (typeof px === 'number' && Number.isFinite(px) && px > 0)
|
|
3040
|
+
return px;
|
|
3041
|
+
}
|
|
3042
|
+
const row = state.screener.rows.find((r) => r.mint === mint);
|
|
3043
|
+
if (row && typeof row.price === 'number' && Number.isFinite(row.price) && row.price > 0)
|
|
3044
|
+
return row.price;
|
|
3045
|
+
// task-1473: the held coin scrolled out of the list (run25): its fast-lane push price (the held mint is pinned there)
|
|
3046
|
+
const fast = live?.livePrice(mint) ?? null;
|
|
3047
|
+
return typeof fast === 'number' && Number.isFinite(fast) && fast > 0 ? fast : null;
|
|
3048
|
+
}
|
|
3049
|
+
let ownOpenedMemo = null;
|
|
3050
|
+
/**
|
|
3051
|
+
* task-1473 r2: the user's OWN held position for the AI-scan PnL chip, marked with this terminal's own price (same
|
|
3052
|
+
* task-1323 position row the token screen uses): the scan's locked coin when held, else the open token, else the
|
|
3053
|
+
* first open mint (live first). null when flat. Reads the ledgers only (memoised); never trades, sizes or signs.
|
|
3054
|
+
*/
|
|
3055
|
+
export function ownHeldSample(state, now) {
|
|
3056
|
+
const live = state.live;
|
|
3057
|
+
if (!live)
|
|
3058
|
+
return null;
|
|
3059
|
+
const src = live.positionSource;
|
|
3060
|
+
const open = src.openMints();
|
|
3061
|
+
if (open.length === 0)
|
|
3062
|
+
return null;
|
|
3063
|
+
const prefer = [state.scan?.engine.lock?.mint, live.token?.view.mint].filter((m) => !!m);
|
|
3064
|
+
const mint = prefer.find((m) => open.includes(m)) ?? open[0];
|
|
3065
|
+
const held = src.held(mint);
|
|
3066
|
+
if (!held)
|
|
3067
|
+
return null;
|
|
3068
|
+
const r = positionRow(held, markPriceFor(state, mint), state.sol?.price ?? src.solUsd());
|
|
3069
|
+
if (!ownOpenedMemo || ownOpenedMemo.mint !== mint || now - ownOpenedMemo.at > 5000) {
|
|
3070
|
+
let t = null;
|
|
3071
|
+
try {
|
|
3072
|
+
t = currentPositionFills([...(live.livePositions?.records(200, mint) ?? []), ...live.wallet.trades(500).filter((x) => x.mint === mint)])[0]?.t ?? null;
|
|
3073
|
+
}
|
|
3074
|
+
catch {
|
|
3075
|
+
t = null;
|
|
3076
|
+
}
|
|
3077
|
+
ownOpenedMemo = { mint, at: now, t };
|
|
3078
|
+
}
|
|
3079
|
+
return {
|
|
3080
|
+
mint, symbol: held.symbol, kind: held.kind === 'live' ? 'live' : 'paper',
|
|
3081
|
+
entryUsd: r.avgEntryUsd, markUsd: r.priceUsd, pnlPct: r.unrealizedPct, pnlUsd: r.unrealizedUsd,
|
|
3082
|
+
openedAt: ownOpenedMemo.t, targetUsd: r.targetUsd,
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
2998
3085
|
/**
|
|
2999
3086
|
* task-1421: move the REAL terminal to mirror an agent's navigation (follow mode, on the owner's own
|
|
3000
3087
|
* machine). This is the same VIEW-ONLY navigation the analyst / go-to path uses — open a token, switch
|
|
@@ -3912,6 +3999,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3912
3999
|
// task-1421: the MCP follow session (set below when `--spectate-mcp` is on). Declared here so the quit /
|
|
3913
4000
|
// terminal-guard teardown closures can stop it.
|
|
3914
4001
|
let followSession = null;
|
|
4002
|
+
let lastHudMark = null; // task-1466: the last terminal price that marked the agent's PAPER position
|
|
3915
4003
|
// task-1443: `--agent-paper <mandate-id>` (with --spectate-mcp) lets that agent PAPER-trade inside the mandate.
|
|
3916
4004
|
const agentPaper = args.spectateMcp && args.agentPaper ? new AgentPaperSwitch({ env: io.env, mandateId: args.agentPaper, db: args.db ?? defaultDbPath(io.env) }) : null;
|
|
3917
4005
|
// Idle economy (task-1303, retuned for task-1319 AC3): between events the ambient rain redraws at
|
|
@@ -4142,13 +4230,20 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4142
4230
|
}
|
|
4143
4231
|
scheduleDraw();
|
|
4144
4232
|
},
|
|
4145
|
-
|
|
4233
|
+
// task-1466: the HUD dock says what the agent did; the toast is only the fallback for a terminal too short for it.
|
|
4234
|
+
onStatus: (line) => { if (size()[1] < HUD_MIN_H)
|
|
4235
|
+
say(line, 'ai', 4); scheduleDraw(); },
|
|
4146
4236
|
onEvent: () => scheduleDraw(),
|
|
4147
4237
|
onControl: agentPaper ? (st) => agentPaper.control(st) : undefined,
|
|
4148
4238
|
snapshot: () => followSnapshot(state),
|
|
4239
|
+
thoughtsFile: args.spectateThoughts ?? undefined,
|
|
4149
4240
|
});
|
|
4241
|
+
// task-1466 spectator v2: the agent HUD (phase rail, heartbeat, thought ribbon, call timeline, the PAPER
|
|
4242
|
+
// execution moment, pause / kill) draws over this terminal while it follows. Calm mode = reduced motion.
|
|
4243
|
+
state.agentHud = { story: followSession.story, clock: () => Date.now(), dataLabel: args.replay ? 'REPLAY DATA' : null };
|
|
4150
4244
|
void followSession.start();
|
|
4151
|
-
|
|
4245
|
+
if (size()[1] < HUD_MIN_H)
|
|
4246
|
+
say('following agents over MCP · ctrl+p pause · ctrl+k stop · any key takes over', 'ai', 6);
|
|
4152
4247
|
if (agentPaper)
|
|
4153
4248
|
say(agentPaper.start(), 'ai', 8);
|
|
4154
4249
|
}
|
|
@@ -4256,6 +4351,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4256
4351
|
scan.blocked.push(type);
|
|
4257
4352
|
if (scan.blocked.length > 6)
|
|
4258
4353
|
scan.blocked.shift();
|
|
4354
|
+
scanLog(scan, Date.now(), 'blocked', scan.engine.lock?.symbol ?? '', `blocked analyst action "${type}" · observe-only`);
|
|
4259
4355
|
}
|
|
4260
4356
|
scanSay(`AI scan blocked analyst action "${type}" · observe-only (${error})`, 'warn', 5);
|
|
4261
4357
|
scheduleDraw();
|
|
@@ -4287,6 +4383,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4287
4383
|
state.aiFocused = false;
|
|
4288
4384
|
state.aiScroll = 0;
|
|
4289
4385
|
state.aiRevealAll = false;
|
|
4386
|
+
scanLog(scan, Date.now(), 'lock', d.symbol, `locked · score ${d.score} · ${d.hits.map((h) => h.reason).join(' · ')}`);
|
|
4290
4387
|
startScanSession(d.mint, d.symbol, d.question, `AI scan · score ${d.score} · ${d.hits.map((h) => h.id).join(' + ')} → short read`);
|
|
4291
4388
|
syncAnalyst(state);
|
|
4292
4389
|
live.syncFastSet();
|
|
@@ -4304,6 +4401,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4304
4401
|
const sess = scan.session;
|
|
4305
4402
|
if (!sess)
|
|
4306
4403
|
return;
|
|
4404
|
+
scanLog(scan, Date.now(), 'refresh', d.symbol, `moved: ${d.moved.join(', ')} · refreshing the read`);
|
|
4307
4405
|
sess.ask(d.question, scanScreenState(state, d.mint, d.symbol), `AI scan refresh · ${d.moved.join(' · ')}`);
|
|
4308
4406
|
if (state.analyst === sess) {
|
|
4309
4407
|
state.aiScroll = 0;
|
|
@@ -4342,7 +4440,8 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4342
4440
|
return;
|
|
4343
4441
|
if (scan.session?.busy)
|
|
4344
4442
|
scan.session.interrupt();
|
|
4345
|
-
scan.engine.cancelAnalysis(Date.now())
|
|
4443
|
+
if (scan.engine.cancelAnalysis(Date.now()))
|
|
4444
|
+
scanLog(scan, Date.now(), 'cancel', scan.engine.lock?.symbol ?? '', 'analysis cancelled by you · coin held, nothing else locks');
|
|
4346
4445
|
if (live)
|
|
4347
4446
|
live.scanMeter = scanMeterOf(scan.engine, Date.now());
|
|
4348
4447
|
scanSay(`analysis cancelled · scan holds $${cleanText(scan.engine.lock?.symbol ?? '', 12)} · R resume scan · Z stop`, 'ai', 5);
|
|
@@ -4428,6 +4527,10 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4428
4527
|
solUsd: () => state.sol?.price ?? live.positions.solUsd(),
|
|
4429
4528
|
imagePath: (v) => popupImagePath(state, v.image),
|
|
4430
4529
|
priceMode: () => priceModeOf(state), // task-1366
|
|
4530
|
+
chartAxis: () => chartAxisOf(state), // task-1472: the popup's Y axis
|
|
4531
|
+
setChartAxis: (a) => { say(setChartAxis(state, a), 'info', 3); if (!done)
|
|
4532
|
+
draw(); }, // task-1472: the popup's MCAP | PRICE control
|
|
4533
|
+
pnlChip: () => pnlChipFor(state, state.agentHud?.clock() ?? Date.now()), // task-1473 r2: the chip on the chart popup
|
|
4431
4534
|
} : null,
|
|
4432
4535
|
});
|
|
4433
4536
|
}
|
|
@@ -6199,6 +6302,36 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
6199
6302
|
}
|
|
6200
6303
|
// market → rain: EMA on activity every tick, mood from the data every 4 s unless an event holds it
|
|
6201
6304
|
blendActivity(v, activityFor(state), v.frame % (tickEvery * 8) === 0 && v.frame >= moodHoldUntil);
|
|
6305
|
+
// task-1466: the agent's open PAPER position is marked with this terminal's own live price (the HUD's PnL).
|
|
6306
|
+
// Only a NEW price marks (a 10 s-old view price must not overwrite a fresher one a tool result carried).
|
|
6307
|
+
if (followSession && state.live && state.live.heldMint !== followSession.story.heldMint) {
|
|
6308
|
+
state.live.heldMint = followSession.story.heldMint;
|
|
6309
|
+
state.live.syncFastSet();
|
|
6310
|
+
} // task-1473
|
|
6311
|
+
if (followSession) {
|
|
6312
|
+
const held = followSession.story.heldMint;
|
|
6313
|
+
const px = held ? markPriceFor(state, held) : null;
|
|
6314
|
+
if (held && px !== null && px !== lastHudMark) {
|
|
6315
|
+
followSession.story.mark(held, px, Date.now());
|
|
6316
|
+
lastHudMark = px;
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
// task-1473 r2: AI scan mode + the user's OWN held position → the PnL chip (band row, dock, chart popup)
|
|
6320
|
+
if (state.scan?.engine.on && state.live) {
|
|
6321
|
+
state.ownPnl ??= new OwnPnlTracker();
|
|
6322
|
+
state.ownPnl.update(ownHeldSample(state, Date.now()), Date.now());
|
|
6323
|
+
if (!followSession && state.live.heldMint !== state.ownPnl.mint) {
|
|
6324
|
+
state.live.heldMint = state.ownPnl.mint;
|
|
6325
|
+
state.live.syncFastSet();
|
|
6326
|
+
} // run25: pin it
|
|
6327
|
+
}
|
|
6328
|
+
else if (state.ownPnl?.mint) {
|
|
6329
|
+
state.ownPnl.update(null, 0);
|
|
6330
|
+
if (!followSession && state.live?.heldMint) {
|
|
6331
|
+
state.live.heldMint = null;
|
|
6332
|
+
state.live.syncFastSet();
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6202
6335
|
}
|
|
6203
6336
|
if (state.ai && state.ai.phase === 'thinking') {
|
|
6204
6337
|
state.ai.ticks += 1;
|
|
@@ -6228,6 +6361,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
6228
6361
|
aiLive: !!(state.ai && state.ai.phase !== 'done'),
|
|
6229
6362
|
awake: v.frame < wakeUntil,
|
|
6230
6363
|
fxBusy: fxBusy(v),
|
|
6364
|
+
hud: followSession !== null && hudAnimating(followSession.story.view(Date.now()), Date.now()),
|
|
6231
6365
|
frame: v.frame,
|
|
6232
6366
|
idleEvery,
|
|
6233
6367
|
}))
|