@jellyos/agent 0.1.4 → 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/README.npm.md +212 -0
- package/bin/jellyos-mcp +26 -0
- package/dist/api/ExtensionAPI.d.ts +11 -0
- package/dist/cli.js +127 -49
- package/dist/index.d.ts +15 -2
- package/dist/index.js +13 -3
- package/dist/loader.d.ts +2 -9
- package/dist/loader.js +2 -1
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +71 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.js +128 -0
- package/dist/models/ModelRegistry.d.ts +12 -1
- package/dist/models/ModelRegistry.js +105 -9
- package/dist/runner/AgentRunner.d.ts +19 -2
- package/dist/runner/AgentRunner.js +247 -17
- package/dist/runner/ModelClient.d.ts +10 -1
- package/dist/runner/ModelClient.js +79 -6
- package/dist/runner/SwarmRouter.d.ts +6 -6
- package/dist/runner/SwarmRouter.js +73 -24
- package/dist/runner/ToolDispatcher.d.ts +10 -0
- package/dist/runner/ToolDispatcher.js +106 -2
- package/dist/scheduler/AgentScheduler.d.ts +118 -0
- package/dist/scheduler/AgentScheduler.js +253 -0
- package/dist/session/ContextStore.d.ts +96 -0
- package/dist/session/ContextStore.js +207 -0
- package/dist/session/GoalManager.d.ts +101 -0
- package/dist/session/GoalManager.js +167 -0
- package/dist/session/MemoryStore.d.ts +48 -0
- package/dist/session/MemoryStore.js +166 -0
- package/dist/session/SessionManager.d.ts +45 -4
- package/dist/session/SessionManager.js +151 -8
- package/dist/telemetry/Tracer.d.ts +48 -0
- package/dist/telemetry/Tracer.js +102 -0
- package/dist/tools/MarketSentiment.d.ts +166 -0
- package/dist/tools/MarketSentiment.js +209 -0
- package/dist/tools/NewsSentiment.js +40 -13
- package/dist/tools/PriceFeed.d.ts +2 -0
- package/dist/tools/PriceFeed.js +79 -27
- package/dist/tools/TechnicalAnalysis.d.ts +37 -0
- package/dist/tools/TechnicalAnalysis.js +85 -0
- package/dist/tui/App.d.ts +4 -3
- package/dist/tui/App.js +346 -119
- package/dist/tui/ModelSelector.d.ts +22 -0
- package/dist/tui/ModelSelector.js +86 -0
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +11 -6
- package/package.json +10 -6
- package/dist/api/ExtensionAPI.d.ts.map +0 -1
- package/dist/api/ExtensionAPI.js.map +0 -1
- package/dist/api/Registry.d.ts.map +0 -1
- package/dist/api/Registry.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js.map +0 -1
- package/dist/models/CostTracker.d.ts.map +0 -1
- package/dist/models/CostTracker.js.map +0 -1
- package/dist/models/ModelRegistry.d.ts.map +0 -1
- package/dist/models/ModelRegistry.js.map +0 -1
- package/dist/models/index.d.ts.map +0 -1
- package/dist/models/index.js.map +0 -1
- package/dist/runner/AgentRunner.d.ts.map +0 -1
- package/dist/runner/AgentRunner.js.map +0 -1
- package/dist/runner/ModelClient.d.ts.map +0 -1
- package/dist/runner/ModelClient.js.map +0 -1
- package/dist/runner/SwarmRouter.d.ts.map +0 -1
- package/dist/runner/SwarmRouter.js.map +0 -1
- package/dist/runner/ToolDispatcher.d.ts.map +0 -1
- package/dist/runner/ToolDispatcher.js.map +0 -1
- package/dist/session/SessionManager.d.ts.map +0 -1
- package/dist/session/SessionManager.js.map +0 -1
- package/dist/tools/NewsSentiment.d.ts.map +0 -1
- package/dist/tools/NewsSentiment.js.map +0 -1
- package/dist/tools/PriceFeed.d.ts.map +0 -1
- package/dist/tools/PriceFeed.js.map +0 -1
- package/dist/tools/TechnicalAnalysis.d.ts.map +0 -1
- package/dist/tools/TechnicalAnalysis.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tui/App.d.ts.map +0 -1
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/REPL.d.ts.map +0 -1
- package/dist/tui/REPL.js.map +0 -1
- package/dist/tui/StatusBar.d.ts.map +0 -1
- package/dist/tui/StatusBar.js.map +0 -1
- package/dist/tui/theme.d.ts.map +0 -1
- package/dist/tui/theme.js.map +0 -1
package/dist/tui/App.js
CHANGED
|
@@ -1,25 +1,89 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* App — root Ink component.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Multi-pane TUI with context bar, syntax-highlighted tool output,
|
|
5
|
+
* live side panel with ticker/prices, and command palette triggered via /palette.
|
|
6
6
|
*/
|
|
7
|
-
import { useState, useCallback, useEffect, useRef } from "react";
|
|
8
|
-
import { Box, useApp, useInput } from "ink";
|
|
7
|
+
import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
|
8
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
9
9
|
import { StatusBar } from "./StatusBar.js";
|
|
10
10
|
import { REPL } from "./REPL.js";
|
|
11
|
-
import {
|
|
11
|
+
import { ModelSelector } from "./ModelSelector.js";
|
|
12
|
+
import { makeTheme, T, JELLY_COLORS } from "./theme.js";
|
|
12
13
|
import { AgentRunner } from "../runner/AgentRunner.js";
|
|
13
14
|
import { SessionManager } from "../session/SessionManager.js";
|
|
14
15
|
import { resolveModelConfig } from "../runner/ModelClient.js";
|
|
15
16
|
import { priceFeed, getPricesTool, topMoversTool, marketOverviewTool, getPricesParams, topMoversParams, marketOverviewParams } from "../tools/PriceFeed.js";
|
|
16
17
|
import { getNewsTool, getNewsParams } from "../tools/NewsSentiment.js";
|
|
17
|
-
import { analyzeTAParams } from "../tools/TechnicalAnalysis.js";
|
|
18
|
+
import { analyzeTAParams, getCandlesParams, getCandlesTool } from "../tools/TechnicalAnalysis.js";
|
|
18
19
|
import { fullAnalysis } from "../tools/TechnicalAnalysis.js";
|
|
19
20
|
import { newsFeed } from "../tools/NewsSentiment.js";
|
|
21
|
+
import { getFearGreedTool, fearGreedParams, getFundingRatesTool, fundingRatesParams, getBtcMempoolTool, btcMempoolParams, getDefiTvlTool, defiTvlParams, getSolanaStatsTool, solanaStatsParams, } from "../tools/MarketSentiment.js";
|
|
22
|
+
import { contextStore } from "../session/ContextStore.js";
|
|
23
|
+
import { goalManager } from "../session/GoalManager.js";
|
|
24
|
+
import { memoryStore } from "../session/MemoryStore.js";
|
|
25
|
+
import { agentScheduler } from "../scheduler/AgentScheduler.js";
|
|
26
|
+
import { Tracer } from "../telemetry/Tracer.js";
|
|
27
|
+
// ── Context window tracking (#33) ───────────────────────────────────────────
|
|
28
|
+
function getContextBar(session) {
|
|
29
|
+
if (!session)
|
|
30
|
+
return { pct: 0, bar: "░".repeat(20), color: JELLY_COLORS.dim, turboReady: true };
|
|
31
|
+
const pressure = session.getContextPressure();
|
|
32
|
+
const filled = Math.round(pressure.pct / 5);
|
|
33
|
+
const color = pressure.level === "critical" ? JELLY_COLORS.error
|
|
34
|
+
: pressure.level === "red" ? JELLY_COLORS.warn
|
|
35
|
+
: pressure.level === "yellow" ? JELLY_COLORS.header
|
|
36
|
+
: JELLY_COLORS.success;
|
|
37
|
+
return {
|
|
38
|
+
pct: pressure.pct,
|
|
39
|
+
bar: "█".repeat(Math.min(filled, 20)) + "░".repeat(Math.max(0, 20 - filled)),
|
|
40
|
+
color,
|
|
41
|
+
turboReady: pressure.turboReady,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// ── Syntax-highlighted JSON formatter ───────────────────────────────────────
|
|
45
|
+
function highlightJson(obj, indent = 0) {
|
|
46
|
+
const pad = " ".repeat(indent);
|
|
47
|
+
if (obj === null)
|
|
48
|
+
return T.dim("null");
|
|
49
|
+
if (typeof obj === "boolean")
|
|
50
|
+
return T.warn(String(obj));
|
|
51
|
+
if (typeof obj === "number")
|
|
52
|
+
return T.success(String(obj));
|
|
53
|
+
if (typeof obj === "string") {
|
|
54
|
+
if (obj.startsWith("0x") && obj.length > 10)
|
|
55
|
+
return T.header(obj.slice(0, 10) + "…");
|
|
56
|
+
return T.accent(`"${obj}"`);
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(obj)) {
|
|
59
|
+
if (obj.length === 0)
|
|
60
|
+
return "[]";
|
|
61
|
+
if (obj.length <= 3)
|
|
62
|
+
return `[${obj.map(v => highlightJson(v, 0)).join(", ")}]`;
|
|
63
|
+
return `[\n${obj.slice(0, 5).map(v => `${pad} ${highlightJson(v, indent + 1)}`).join(",\n")}${obj.length > 5 ? `\n${pad} …${obj.length - 5} more` : ""}\n${pad}]`;
|
|
64
|
+
}
|
|
65
|
+
if (typeof obj === "object") {
|
|
66
|
+
const entries = Object.entries(obj);
|
|
67
|
+
if (entries.length === 0)
|
|
68
|
+
return "{}";
|
|
69
|
+
return `{\n${entries.slice(0, 8).map(([k, v]) => `${pad} ${T.accent(k)}: ${highlightJson(v, indent + 1)}`).join(",\n")}${entries.length > 8 ? `\n${pad} …${entries.length - 8} more` : ""}\n${pad}}`;
|
|
70
|
+
}
|
|
71
|
+
return String(obj);
|
|
72
|
+
}
|
|
73
|
+
function formatToolContent(text) {
|
|
74
|
+
try {
|
|
75
|
+
const obj = JSON.parse(text);
|
|
76
|
+
return highlightJson(obj);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return text.length > 300 ? text.slice(0, 300) + T.dim("\n…[truncated]") : text;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
20
82
|
let _msgIdCounter = 0;
|
|
21
83
|
function nextId() { return String(++_msgIdCounter); }
|
|
22
|
-
|
|
84
|
+
// Unique session ID for memory persistence
|
|
85
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
86
|
+
export function App({ registry, systemPrompt, effectLevel: initialEffect = "normal", chain: initialChain = "ethereum", modelReg, costTracker, onNotifyReady, onStatusReady, onModelSelectorReady, }) {
|
|
23
87
|
const { exit } = useApp();
|
|
24
88
|
const [messages, setMessages] = useState([]);
|
|
25
89
|
const [streaming, setStreaming] = useState("");
|
|
@@ -32,6 +96,9 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
32
96
|
const [ticker, setTicker] = useState("");
|
|
33
97
|
const [costBadge, setCostBadge] = useState("");
|
|
34
98
|
const [newsBadge, setNewsBadge] = useState("");
|
|
99
|
+
// ── Model selector overlay state ──────────────────────────────────────
|
|
100
|
+
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
101
|
+
const [modelSelectorInitialQuery, setModelSelectorInitialQuery] = useState("");
|
|
35
102
|
const runnerRef = useRef(null);
|
|
36
103
|
const sessionRef = useRef(null);
|
|
37
104
|
const sessionCtxRef = useRef(null);
|
|
@@ -41,7 +108,6 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
41
108
|
modelName = resolveModelConfig(modelReg).model;
|
|
42
109
|
}
|
|
43
110
|
catch { /* shown via banner */ }
|
|
44
|
-
// ── Push helpers ─────────────────────────────────────────────────────────
|
|
45
111
|
const push = useCallback((msg) => {
|
|
46
112
|
setMessages(prev => [...prev, { ...msg, id: nextId(), ts: Date.now() }]);
|
|
47
113
|
}, []);
|
|
@@ -57,123 +123,114 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
57
123
|
if (key === "effect_level")
|
|
58
124
|
setEffectLevel(value);
|
|
59
125
|
}, []);
|
|
60
|
-
const uiCtx = {
|
|
61
|
-
|
|
126
|
+
const uiCtx = {
|
|
127
|
+
notify, setStatus,
|
|
128
|
+
setTheme(_n) { },
|
|
129
|
+
setHeader(_f) { },
|
|
130
|
+
showModelSelector: (query) => {
|
|
131
|
+
setModelSelectorInitialQuery(query ?? "");
|
|
132
|
+
setShowModelSelector(true);
|
|
133
|
+
},
|
|
134
|
+
theme,
|
|
135
|
+
};
|
|
136
|
+
// Register built-in tools
|
|
62
137
|
function registerBuiltinTools() {
|
|
63
138
|
if (!modelReg)
|
|
64
139
|
return;
|
|
65
|
-
|
|
66
|
-
registry.addTool({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
parameters: modelReg.listModelsParams,
|
|
70
|
-
execute: (id, p) => modelReg.listModelsTool(id, p),
|
|
71
|
-
});
|
|
72
|
-
registry.addTool({
|
|
73
|
-
name: "pick_model", label: "Pick Model",
|
|
74
|
-
description: "Find the cheapest available model meeting requirements (tier, context length, max cost).",
|
|
75
|
-
parameters: modelReg.pickModelParams,
|
|
76
|
-
execute: (id, p) => modelReg.pickModelTool(id, p),
|
|
77
|
-
});
|
|
78
|
-
registry.addTool({
|
|
79
|
-
name: "model_summary", label: "Model Summary",
|
|
80
|
-
description: "Get a summary of available model tiers and counts.",
|
|
81
|
-
parameters: modelReg.summaryParams,
|
|
82
|
-
execute: (_id) => modelReg.summaryTool(),
|
|
83
|
-
});
|
|
84
|
-
// Cost tools
|
|
140
|
+
const mk = modelReg;
|
|
141
|
+
registry.addTool({ name: "list_models", label: "List Models", description: "Search and filter available AI models by name, provider, or tier.", parameters: mk.listModelsParams, execute: (id, p) => mk.listModelsTool(id, p) });
|
|
142
|
+
registry.addTool({ name: "pick_model", label: "Pick Model", description: "Find the cheapest available model meeting requirements.", parameters: mk.pickModelParams, execute: (id, p) => mk.pickModelTool(id, p) });
|
|
143
|
+
registry.addTool({ name: "model_summary", label: "Model Summary", description: "Get a summary of available model tiers and counts.", parameters: mk.summaryParams, execute: () => mk.summaryTool() });
|
|
85
144
|
if (costTracker) {
|
|
86
|
-
registry.addTool({
|
|
87
|
-
name: "cost_report", label: "Cost Report",
|
|
88
|
-
description: "Show current session and lifetime token usage and estimated cost.",
|
|
89
|
-
parameters: costTracker.costReportParams,
|
|
90
|
-
execute: (_id) => costTracker.costReportTool(),
|
|
91
|
-
});
|
|
145
|
+
registry.addTool({ name: "cost_report", label: "Cost Report", description: "Show session and lifetime token usage.", parameters: costTracker.costReportParams, execute: () => costTracker.costReportTool() });
|
|
92
146
|
}
|
|
93
|
-
|
|
94
|
-
registry.addTool({
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
registry.addTool({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
registry.addTool({
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
registry.addTool({
|
|
114
|
-
name: "get_news", label: "Get News",
|
|
115
|
-
description: "Get latest crypto news headlines with sentiment scoring. Use to gauge market sentiment.",
|
|
116
|
-
parameters: getNewsParams,
|
|
117
|
-
execute: (id, p) => getNewsTool(id, p),
|
|
118
|
-
});
|
|
119
|
-
// Technical Analysis tools
|
|
120
|
-
registry.addTool({
|
|
121
|
-
name: "analyze_ta", label: "Technical Analysis",
|
|
122
|
-
description: "Run full technical analysis on price data: RSI, MACD, Bollinger Bands, EMA crossover, ATR, volume profile. Returns a summary with buy/sell signals.",
|
|
123
|
-
parameters: analyzeTAParams,
|
|
124
|
-
execute: async (_id, p) => {
|
|
147
|
+
registry.addTool({ name: "get_prices", label: "Get Prices", description: "Get current prices and 24h change.", parameters: getPricesParams, execute: (id, p) => getPricesTool(id, p) });
|
|
148
|
+
registry.addTool({ name: "get_top_movers", label: "Top Movers", description: "Assets with largest 24h price movement.", parameters: topMoversParams, execute: (id, p) => topMoversTool(id, p) });
|
|
149
|
+
registry.addTool({ name: "get_market_overview", label: "Market Overview", description: "Aggregated market data.", parameters: marketOverviewParams, execute: () => marketOverviewTool() });
|
|
150
|
+
registry.addTool({ name: "get_news", label: "Get News", description: "Crypto news headlines with sentiment scoring.", parameters: getNewsParams, execute: (id, p) => getNewsTool(id, p) });
|
|
151
|
+
// #18: OHLCV candles from Binance + TA analysis
|
|
152
|
+
registry.addTool({ name: "get_candles", label: "Get Candles", description: "Fetch OHLCV candlestick data from Binance and run technical analysis (RSI, MACD, Bollinger, EMA). Use this before analyze_ta.", parameters: getCandlesParams, execute: (id, p) => getCandlesTool(id, p) });
|
|
153
|
+
// #20: Free market sentiment tools
|
|
154
|
+
registry.addTool({ name: "get_fear_greed", label: "Fear & Greed", description: "Crypto Fear & Greed Index with 7-day history.", parameters: fearGreedParams, execute: (id, p) => getFearGreedTool(id, p) });
|
|
155
|
+
registry.addTool({ name: "get_funding_rates", label: "Funding Rates", description: "Binance perpetual funding rates — shows long/short bias.", parameters: fundingRatesParams, execute: (id, p) => getFundingRatesTool(id, p) });
|
|
156
|
+
registry.addTool({ name: "get_btc_mempool", label: "BTC Mempool", description: "Bitcoin mempool stats and recommended fee rates.", parameters: btcMempoolParams, execute: () => getBtcMempoolTool() });
|
|
157
|
+
registry.addTool({ name: "get_defi_tvl", label: "DeFi TVL", description: "DeFiLlama TVL by chain or all chains.", parameters: defiTvlParams, execute: (id, p) => getDefiTvlTool(id, p) });
|
|
158
|
+
registry.addTool({ name: "get_solana_stats", label: "Solana Stats", description: "Solana network TPS and health.", parameters: solanaStatsParams, execute: () => getSolanaStatsTool() });
|
|
159
|
+
// #31: Ephemeral task context
|
|
160
|
+
registry.addTool({ name: "read_task_context", label: "Read Task Context", description: "Read saved task context from a previous multi-step operation.", parameters: require("@sinclair/typebox").Type.Object({ taskId: require("@sinclair/typebox").Type.String({ description: "Task ID from a previous task reference" }) }), execute: (id, p) => contextStore.readContextTool(id, p) });
|
|
161
|
+
registry.addTool({ name: "list_tasks", label: "List Tasks", description: "List active and recent task context folders.", parameters: require("@sinclair/typebox").Type.Object({}), execute: () => contextStore.listTasksTool() });
|
|
162
|
+
// #12: Goal management
|
|
163
|
+
registry.addTool({ name: "set_goal", label: "Set Goal", description: "Set a persistent cross-session goal for the agent to monitor.", parameters: goalManager.setGoalParams, execute: (id, p) => goalManager.setGoalTool(id, p) });
|
|
164
|
+
registry.addTool({ name: "complete_goal", label: "Complete Goal", description: "Mark a goal as completed.", parameters: goalManager.completeGoalParams, execute: (id, p) => goalManager.completeGoalTool(id, p) });
|
|
165
|
+
registry.addTool({ name: "list_goals", label: "List Goals", description: "List active or all persistent goals.", parameters: goalManager.listGoalsParams, execute: (id, p) => goalManager.listGoalsTool(id, p) });
|
|
166
|
+
registry.addTool({ name: "add_goal_note", label: "Add Goal Note", description: "Add a progress note to an existing goal.", parameters: goalManager.addGoalNoteParams, execute: (id, p) => goalManager.addGoalNoteTool(id, p) });
|
|
167
|
+
registry.addTool({ name: "analyze_ta", label: "Technical Analysis", description: "RSI, MACD, Bollinger, EMA crossover, ATR. Tip: use get_candles first to fetch real price data.", parameters: analyzeTAParams, execute: async (_id, p) => {
|
|
125
168
|
const closes = p.prices;
|
|
126
|
-
const
|
|
127
|
-
const lows = p.lows ?? [];
|
|
128
|
-
const volumes = p.volumes ?? [];
|
|
129
|
-
const candles = closes.map((c, i) => ({
|
|
130
|
-
timestamp: 0, open: c, high: highs[i] ?? c, low: lows[i] ?? c, close: c, volume: volumes[i] ?? 0,
|
|
131
|
-
}));
|
|
169
|
+
const candles = closes.map((c, i) => ({ timestamp: 0, open: c, high: p.highs?.[i] ?? c, low: p.lows?.[i] ?? c, close: c, volume: p.volumes?.[i] ?? 0 }));
|
|
132
170
|
const results = fullAnalysis(candles);
|
|
133
|
-
const
|
|
171
|
+
const summary = results.find((r) => r.indicator === "SUMMARY");
|
|
172
|
+
const text = results.map((r) => {
|
|
134
173
|
const s = r.signal === "bullish" ? "🟢" : r.signal === "bearish" ? "🔴" : "⚪";
|
|
135
|
-
const v = Array.isArray(r.value) ? `[${r.value.length}
|
|
174
|
+
const v = Array.isArray(r.value) ? `[${r.value.length}]` : typeof r.value === "number" ? r.value : "-";
|
|
136
175
|
return `${s} ${r.indicator}: ${v}`;
|
|
137
176
|
}).join("\n");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
content: [{ type: "text", text: `Technical Analysis Results:\n${text}` }],
|
|
141
|
-
details: { results, overall_signal: summary?.signal, overall_score: summary?.value },
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
});
|
|
177
|
+
return { content: [{ type: "text", text: `Technical Analysis:\n${text}` }], details: { results, overall_signal: summary?.signal, overall_score: summary?.value } };
|
|
178
|
+
} });
|
|
145
179
|
}
|
|
146
180
|
useEffect(() => { onNotifyReady?.(notify); onStatusReady?.(setStatus); }, []);
|
|
147
|
-
// ──
|
|
181
|
+
// ── Expose model selector trigger to extensions ──────────────────────────
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
onModelSelectorReady?.((initialQuery) => {
|
|
184
|
+
setModelSelectorInitialQuery(initialQuery ?? "");
|
|
185
|
+
setShowModelSelector(true);
|
|
186
|
+
});
|
|
187
|
+
}, [onModelSelectorReady]);
|
|
148
188
|
useEffect(() => {
|
|
149
189
|
registerBuiltinTools();
|
|
150
|
-
// Start background feeds
|
|
151
190
|
priceFeed.track("btc", "eth", "sol", "bnb", "matic", "arb", "op", "avax", "link", "uni", "doge", "xrp", "ada", "dot", "atom", "near", "sui", "apt", "pepe", "aave");
|
|
152
191
|
priceFeed.start();
|
|
153
192
|
newsFeed.start();
|
|
154
|
-
//
|
|
193
|
+
// #11: Register scheduler tools
|
|
194
|
+
registry.addTool({ name: "schedule_task", label: "Schedule Task", description: "Schedule a recurring or one-shot agent task (cron or price trigger).", parameters: agentScheduler.addTaskParams, execute: (id, p) => agentScheduler.addTaskTool(id, p) });
|
|
195
|
+
registry.addTool({ name: "list_schedule", label: "List Schedule", description: "List all scheduled tasks.", parameters: agentScheduler.listTasksParams, execute: (id, p) => agentScheduler.listTasksTool(id, p) });
|
|
196
|
+
registry.addTool({ name: "remove_schedule", label: "Remove Schedule", description: "Remove a scheduled task by ID.", parameters: agentScheduler.removeTaskParams, execute: (id, p) => agentScheduler.removeTaskTool(id, p) });
|
|
197
|
+
// Start scheduler — fires agent turns autonomously
|
|
198
|
+
agentScheduler.start((task) => {
|
|
199
|
+
push({ role: "system", content: T.muted(`⏰ Scheduler: running "${task.name}"`) });
|
|
200
|
+
// Only fire if agent is not currently busy
|
|
201
|
+
if (!disabled && runnerRef.current) {
|
|
202
|
+
setDisabled(true);
|
|
203
|
+
setStreaming("");
|
|
204
|
+
runnerRef.current.run(`[SCHEDULED TASK: ${task.name}] ${task.prompt}`)
|
|
205
|
+
.catch((e) => notify(T.error(`Scheduler error: ${e.message}`)));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
155
208
|
const tickerInterval = setInterval(() => {
|
|
156
|
-
setTicker(priceFeed.tickerLine(
|
|
209
|
+
setTicker(priceFeed.tickerLine(6));
|
|
157
210
|
if (costTracker)
|
|
158
211
|
setCostBadge(costTracker.statusLine());
|
|
159
212
|
setNewsBadge(newsFeed.statusBadge());
|
|
160
213
|
}, 5_000);
|
|
161
|
-
|
|
162
|
-
const saveInterval = setInterval(() => {
|
|
163
|
-
costTracker?.saveLifetime();
|
|
164
|
-
}, 60_000);
|
|
165
|
-
// Agent session
|
|
214
|
+
const saveInterval = setInterval(() => { costTracker?.saveLifetime(); }, 60_000);
|
|
166
215
|
const session = new SessionManager();
|
|
167
|
-
session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt ||
|
|
168
|
-
"You are JellyOS, an autonomous AI trading agent.");
|
|
216
|
+
session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt || "You are JellyOS, an autonomous AI trading agent.");
|
|
169
217
|
sessionRef.current = session;
|
|
170
|
-
const sessionCtx = {
|
|
171
|
-
ui: uiCtx, hasUI: true,
|
|
172
|
-
config: { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY, ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, ALCHEMY_KEY: process.env.ALCHEMY_KEY, DEFAULT_MODEL: process.env.DEFAULT_MODEL },
|
|
173
|
-
};
|
|
218
|
+
const sessionCtx = { ui: uiCtx, hasUI: true, config: { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY, ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, ALCHEMY_KEY: process.env.ALCHEMY_KEY, DEFAULT_MODEL: process.env.DEFAULT_MODEL } };
|
|
174
219
|
sessionCtxRef.current = sessionCtx;
|
|
175
220
|
registry.fireHook("session_start", sessionCtx).then(() => {
|
|
176
|
-
|
|
221
|
+
// #7: Inject memory context into system prompt
|
|
222
|
+
if (memoryStore.isAvailable) {
|
|
223
|
+
const memCtx = memoryStore.buildContextBlock(sessionId);
|
|
224
|
+
if (memCtx) {
|
|
225
|
+
session.setSystemPrompt((session.getSystemPrompt() || "") + memCtx);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// #12: Inject active goals into system prompt
|
|
229
|
+
const goalCtx = goalManager.buildContextBlock();
|
|
230
|
+
if (goalCtx) {
|
|
231
|
+
session.setSystemPrompt((session.getSystemPrompt() || "") + goalCtx);
|
|
232
|
+
}
|
|
233
|
+
push({ role: "system", content: T.muted("Session started. Type /help for commands.") });
|
|
177
234
|
});
|
|
178
235
|
const runner = new AgentRunner(registry, session, (event) => {
|
|
179
236
|
if (event.type === "text_delta")
|
|
@@ -182,13 +239,20 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
182
239
|
setToolRunning(event.name);
|
|
183
240
|
else if (event.type === "tool_done") {
|
|
184
241
|
setToolRunning(null);
|
|
185
|
-
|
|
242
|
+
// Format tool output with syntax highlighting
|
|
243
|
+
const formatted = formatToolContent(event.result);
|
|
244
|
+
push({ role: "tool", content: formatted, toolName: event.name, isError: event.isError });
|
|
186
245
|
}
|
|
187
246
|
else if (event.type === "turn_done") {
|
|
188
247
|
setDisabled(false);
|
|
189
248
|
setToolRunning(null);
|
|
190
|
-
setStreaming(prev => {
|
|
191
|
-
|
|
249
|
+
setStreaming(prev => {
|
|
250
|
+
if (prev.trim()) {
|
|
251
|
+
push({ role: "assistant", content: prev });
|
|
252
|
+
memoryStore.save(sessionId, "assistant", prev); // #7 persist to memory
|
|
253
|
+
}
|
|
254
|
+
return "";
|
|
255
|
+
});
|
|
192
256
|
}
|
|
193
257
|
else if (event.type === "error") {
|
|
194
258
|
setDisabled(false);
|
|
@@ -196,10 +260,27 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
196
260
|
setStreaming("");
|
|
197
261
|
notify(T.error(`Error: ${event.message}`));
|
|
198
262
|
}
|
|
199
|
-
|
|
263
|
+
else if (event.type === "approval_request") {
|
|
264
|
+
// #10: Show approval prompt — user must type y or n
|
|
265
|
+
setToolRunning(null);
|
|
266
|
+
const { toolName, args, approve } = event;
|
|
267
|
+
let parsed = "";
|
|
268
|
+
try {
|
|
269
|
+
parsed = JSON.stringify(JSON.parse(args), null, 2).slice(0, 200);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
parsed = args.slice(0, 200);
|
|
273
|
+
}
|
|
274
|
+
push({
|
|
275
|
+
role: "notify",
|
|
276
|
+
content: `⚠️ APPROVAL REQUIRED\n\nTool: ${toolName}\nArgs: ${parsed}\n\nType /approve or /deny to continue.`,
|
|
277
|
+
});
|
|
278
|
+
// Store the approve callback so /approve /deny commands can resolve it
|
|
279
|
+
runnerRef.current._pendingApproval = approve;
|
|
280
|
+
}
|
|
281
|
+
}, sessionCtx, effectLevel, modelReg, costTracker, goalManager, contextStore);
|
|
200
282
|
runnerRef.current = runner;
|
|
201
|
-
|
|
202
|
-
setTicker(priceFeed.tickerLine(5));
|
|
283
|
+
setTicker(priceFeed.tickerLine(6));
|
|
203
284
|
if (costTracker)
|
|
204
285
|
setCostBadge(costTracker.statusLine());
|
|
205
286
|
setNewsBadge(newsFeed.statusBadge());
|
|
@@ -208,13 +289,14 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
208
289
|
registry.fireHook("session_end", sessionCtxRef.current).catch(() => { });
|
|
209
290
|
priceFeed.stop();
|
|
210
291
|
newsFeed.stop();
|
|
292
|
+
agentScheduler.stop();
|
|
211
293
|
clearInterval(tickerInterval);
|
|
212
294
|
clearInterval(saveInterval);
|
|
213
295
|
costTracker?.saveLifetime();
|
|
214
296
|
};
|
|
215
297
|
}, []);
|
|
216
298
|
useEffect(() => { runnerRef.current?.setEffectLevel(effectLevel); }, [effectLevel]);
|
|
217
|
-
// ── Ctrl-C
|
|
299
|
+
// ── Ctrl-C to exit ──────────────────────────────────────────────────────
|
|
218
300
|
useInput((_input, key) => {
|
|
219
301
|
if (key.ctrl && _input === "c") {
|
|
220
302
|
push({ role: "system", content: T.muted("Goodbye 🪼") });
|
|
@@ -227,7 +309,6 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
227
309
|
const input = raw.trim();
|
|
228
310
|
if (!input)
|
|
229
311
|
return;
|
|
230
|
-
// ── Slash commands ────────────────────────────────────────────────────
|
|
231
312
|
if (input.startsWith("/")) {
|
|
232
313
|
const [cmd, ...rest] = input.slice(1).split(" ");
|
|
233
314
|
const args = rest.join(" ");
|
|
@@ -239,21 +320,18 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
239
320
|
}
|
|
240
321
|
if (cmd === "help") {
|
|
241
322
|
const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
|
|
242
|
-
notify("
|
|
323
|
+
notify("Commands:\n" + lines.join("\n") + "\n\nTools: list_models, pick_model, get_prices, get_news, analyze_ta, cost_report, get_market_overview, get_top_movers, model_summary");
|
|
243
324
|
return;
|
|
244
325
|
}
|
|
245
|
-
// Built-in /models command
|
|
246
326
|
if (cmd === "models") {
|
|
247
327
|
if (!modelReg) {
|
|
248
328
|
notify(T.error("Model registry not available"));
|
|
249
329
|
return;
|
|
250
330
|
}
|
|
251
|
-
const
|
|
252
|
-
const result = await modelReg.listModelsTool("", { query: query, limit: 20, available_only: true });
|
|
331
|
+
const result = await modelReg.listModelsTool("", { query: rest.join(" ") || undefined, limit: 20, available_only: true });
|
|
253
332
|
notify(result.content[0].text);
|
|
254
333
|
return;
|
|
255
334
|
}
|
|
256
|
-
// Built-in /cost command
|
|
257
335
|
if (cmd === "cost") {
|
|
258
336
|
if (!costTracker) {
|
|
259
337
|
notify(T.error("Cost tracker not available"));
|
|
@@ -263,28 +341,120 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
263
341
|
notify(result.content[0].text);
|
|
264
342
|
return;
|
|
265
343
|
}
|
|
266
|
-
// Built-in /news command
|
|
267
344
|
if (cmd === "news") {
|
|
268
345
|
const result = await getNewsTool("", { limit: 15 });
|
|
269
346
|
notify(result.content[0].text);
|
|
270
347
|
return;
|
|
271
348
|
}
|
|
272
|
-
// Built-in /prices command
|
|
273
349
|
if (cmd === "prices") {
|
|
274
350
|
const result = await getPricesTool("", { symbols: args ? args.split(/\s+/) : ["btc", "eth", "sol"] });
|
|
275
351
|
notify(result.content[0].text);
|
|
276
352
|
return;
|
|
277
353
|
}
|
|
354
|
+
if (cmd === "palette") {
|
|
355
|
+
// Command palette — list all commands and tools
|
|
356
|
+
const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
|
|
357
|
+
const tools = registry.listTools().map(t => T.success(`🔧 ${t.name.padEnd(22)}`) + " " + T.muted(t.description.slice(0, 50)));
|
|
358
|
+
notify("Command Palette\n\n" + lines.join("\n") + "\n\nTools:\n" + tools.join("\n"));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (cmd === "effect" && ["eco", "normal", "turbo", "max"].includes(args.trim().toLowerCase())) {
|
|
362
|
+
setEffectLevel(args.trim().toLowerCase());
|
|
363
|
+
notify(T.accent(`Effect → ${args.trim().toUpperCase()}`));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// #12: Goal commands
|
|
367
|
+
if (cmd === "goals" || cmd === "goal") {
|
|
368
|
+
const subCmd = args.trim().split(" ")[0];
|
|
369
|
+
if (subCmd === "add") {
|
|
370
|
+
const text = args.trim().slice(4).trim();
|
|
371
|
+
if (!text) {
|
|
372
|
+
notify(T.error("Usage: /goal add <text>"));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const result = await goalManager.setGoalTool("", { text });
|
|
376
|
+
notify(result.content[0].text);
|
|
377
|
+
}
|
|
378
|
+
else if (subCmd === "done" || subCmd === "complete") {
|
|
379
|
+
const id = args.trim().split(" ")[1];
|
|
380
|
+
if (!id) {
|
|
381
|
+
notify(T.error("Usage: /goal done <id>"));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const result = await goalManager.completeGoalTool("", { id });
|
|
385
|
+
notify(result.content[0].text);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
const result = await goalManager.listGoalsTool("", { status: "all" });
|
|
389
|
+
notify(result.content[0].text);
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// #31: Task context commands
|
|
394
|
+
if (cmd === "tasks") {
|
|
395
|
+
const result = await contextStore.listTasksTool();
|
|
396
|
+
notify(result.content[0].text);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (cmd === "keep" && args.trim()) {
|
|
400
|
+
const ok = contextStore.keepTask(args.trim());
|
|
401
|
+
notify(ok ? T.success(`Task ${args.trim()} marked for permanent retention.`) : T.error(`Task ${args.trim()} not found.`));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
// #10: Approval gate responses
|
|
405
|
+
if (cmd === "approve" || cmd === "y") {
|
|
406
|
+
const pending = runnerRef.current?._pendingApproval;
|
|
407
|
+
if (pending) {
|
|
408
|
+
pending(true);
|
|
409
|
+
push({ role: "system", content: T.success("✅ Tool approved.") });
|
|
410
|
+
}
|
|
411
|
+
else
|
|
412
|
+
notify(T.error("No pending approval."));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (cmd === "deny" || cmd === "n") {
|
|
416
|
+
const pending = runnerRef.current?._pendingApproval;
|
|
417
|
+
if (pending) {
|
|
418
|
+
pending(false);
|
|
419
|
+
push({ role: "system", content: T.error("❌ Tool denied.") });
|
|
420
|
+
}
|
|
421
|
+
else
|
|
422
|
+
notify(T.error("No pending approval."));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// #30: Traces command
|
|
426
|
+
if (cmd === "traces") {
|
|
427
|
+
const recent = Tracer.readRecent(5);
|
|
428
|
+
notify(Tracer.formatSummary(recent));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// #11: Scheduler commands
|
|
432
|
+
if (cmd === "schedule" || cmd === "sched") {
|
|
433
|
+
const result = await agentScheduler.listTasksTool("", { enabled_only: false });
|
|
434
|
+
notify(result.content[0].text);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// #7: Memory search
|
|
438
|
+
if (cmd === "memory" && args.trim()) {
|
|
439
|
+
const results = memoryStore.search(args.trim(), 8);
|
|
440
|
+
if (results.length === 0) {
|
|
441
|
+
notify(T.muted("No memories found for: " + args.trim()));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const lines = results.map(r => `[${new Date(r.ts).toLocaleDateString()} ${r.role}] ${r.content.slice(0, 120)}`);
|
|
445
|
+
notify(`Memory search: "${args.trim()}"\n${lines.join("\n")}`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
278
448
|
const def = registry.getCommand(cmd);
|
|
279
449
|
if (!def) {
|
|
280
|
-
notify(T.error(`Unknown
|
|
450
|
+
notify(T.error(`Unknown: /${cmd} — try /help or /palette`));
|
|
281
451
|
return;
|
|
282
452
|
}
|
|
283
453
|
try {
|
|
284
454
|
await def.handler(args, { ui: uiCtx });
|
|
285
455
|
}
|
|
286
456
|
catch (e) {
|
|
287
|
-
notify(T.error(`
|
|
457
|
+
notify(T.error(`Error: ${e.message}`));
|
|
288
458
|
}
|
|
289
459
|
return;
|
|
290
460
|
}
|
|
@@ -293,6 +463,7 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
293
463
|
return;
|
|
294
464
|
}
|
|
295
465
|
push({ role: "user", content: input });
|
|
466
|
+
memoryStore.save(sessionId, "user", input); // #7 persist user message
|
|
296
467
|
setDisabled(true);
|
|
297
468
|
setStreaming("");
|
|
298
469
|
try {
|
|
@@ -300,11 +471,67 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
300
471
|
}
|
|
301
472
|
catch (e) {
|
|
302
473
|
setDisabled(false);
|
|
303
|
-
notify(T.error(`
|
|
474
|
+
notify(T.error(`Error: ${e.message}`));
|
|
304
475
|
}
|
|
305
476
|
}, [registry, exit, push, notify, uiCtx, modelReg, costTracker]);
|
|
306
|
-
//
|
|
477
|
+
// ── Handle model selection ───────────────────────────────────────────
|
|
478
|
+
const handleModelSelect = useCallback((modelId) => {
|
|
479
|
+
setShowModelSelector(false);
|
|
480
|
+
try {
|
|
481
|
+
const { writeFileSync, readFileSync, existsSync, mkdirSync } = require("node:fs");
|
|
482
|
+
const { join } = require("node:path");
|
|
483
|
+
const { homedir } = require("node:os");
|
|
484
|
+
const JELLY_HOME = process.env.JELLYOS_HOME ?? join(homedir(), ".jelly");
|
|
485
|
+
const envFile = join(JELLY_HOME, ".env");
|
|
486
|
+
mkdirSync(JELLY_HOME, { recursive: true });
|
|
487
|
+
const content = existsSync(envFile) ? readFileSync(envFile, "utf-8") : "";
|
|
488
|
+
const re = /^DEFAULT_MODEL=.*$/m;
|
|
489
|
+
const line = `DEFAULT_MODEL=${modelId}`;
|
|
490
|
+
writeFileSync(envFile, re.test(content) ? content.replace(re, line) : content + "\n" + line + "\n", "utf-8");
|
|
491
|
+
process.env.DEFAULT_MODEL = modelId;
|
|
492
|
+
const ctxPath = join(JELLY_HOME, "context.json");
|
|
493
|
+
const store = existsSync(ctxPath) ? JSON.parse(readFileSync(ctxPath, "utf-8")) : {};
|
|
494
|
+
store.model = modelId;
|
|
495
|
+
writeFileSync(ctxPath, JSON.stringify(store, null, 2), "utf-8");
|
|
496
|
+
}
|
|
497
|
+
catch { /* non-fatal */ }
|
|
498
|
+
notify(T.accent(`Model set to: ${modelId}\nRestart jellyos to apply.`));
|
|
499
|
+
}, [notify]);
|
|
500
|
+
const handleModelCancel = useCallback(() => {
|
|
501
|
+
setShowModelSelector(false);
|
|
502
|
+
}, []);
|
|
503
|
+
const modelList = useMemo(() => {
|
|
504
|
+
if (!modelReg)
|
|
505
|
+
return [];
|
|
506
|
+
const tiers = ["orchestrator", "analyst", "worker", "free"];
|
|
507
|
+
const items = [];
|
|
508
|
+
for (const tier of tiers) {
|
|
509
|
+
const pool = modelReg.getPool(tier);
|
|
510
|
+
for (const tm of pool) {
|
|
511
|
+
if (tm.available && tm.failures < 3) {
|
|
512
|
+
items.push({ id: tm.model.id, tier });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return items;
|
|
517
|
+
}, [modelReg]);
|
|
518
|
+
const ctx = getContextBar(sessionRef.current);
|
|
307
519
|
const statusLine = [ticker, costBadge, newsBadge, ...Object.values(statusBadges)].filter(Boolean).join(" ") || null;
|
|
308
|
-
|
|
520
|
+
// ── Overlay: model selector ────────────────────────────────────────────
|
|
521
|
+
if (showModelSelector) {
|
|
522
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(StatusBar, { model: modelName, chain: chain, vaultLocked: vaultLocked, effectLevel: effectLevel, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsx(ModelSelector, { models: modelList, currentModelId: process.env.DEFAULT_MODEL ?? "", onSelect: handleModelSelect, onCancel: handleModelCancel, initialQuery: modelSelectorInitialQuery })] }));
|
|
523
|
+
}
|
|
524
|
+
// ── Multi-pane layout ────────────────────────────────────────────────────
|
|
525
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(StatusBar, { model: modelName, chain: chain, vaultLocked: vaultLocked, effectLevel: effectLevel, toolRunning: toolRunning, connected: true, statusLine: statusLine }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", width: 32, borderStyle: "single", borderColor: JELLY_COLORS.dim, paddingX: 1, flexShrink: 0, children: [_jsx(Text, { color: JELLY_COLORS.accent, bold: true, children: "\uD83D\uDCE1 Ticker" }), _jsx(Text, { color: JELLY_COLORS.muted, wrap: "truncate", children: ticker || "Loading…" }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsxs(Text, { color: JELLY_COLORS.accent, children: ["Context ", ctx.turboReady ? "" : "⚠"] }), _jsxs(Text, { color: ctx.color, children: [ctx.bar, " ", ctx.pct, "%", ctx.turboReady ? "" : " no turbo"] }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsx(Text, { color: JELLY_COLORS.accent, children: "Effect" }), _jsx(Text, { color: effectLevel === "eco" ? "#22c55e" : effectLevel === "turbo" ? "#f59e0b" : effectLevel === "max" ? "#ef4444" : JELLY_COLORS.accent, children: effectLevel.toUpperCase() }), _jsx(Text, { color: JELLY_COLORS.dim, children: "─".repeat(28) }), _jsx(Text, { color: JELLY_COLORS.accent, children: "News" }), _jsx(Text, { color: JELLY_COLORS.muted, wrap: "truncate", children: newsBadge || "…" })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(REPL, { messages: messages, streamingText: streaming, toolRunning: toolRunning, onSubmit: handleSubmit, disabled: disabled, onAbort: () => {
|
|
526
|
+
runnerRef.current?.abort();
|
|
527
|
+
setDisabled(false);
|
|
528
|
+
setToolRunning(null);
|
|
529
|
+
setStreaming(prev => {
|
|
530
|
+
if (prev.trim())
|
|
531
|
+
push({ role: "assistant", content: prev + " \u2014 [interrupted]" });
|
|
532
|
+
return "";
|
|
533
|
+
});
|
|
534
|
+
push({ role: "system", content: T.dim("— stream interrupted —") });
|
|
535
|
+
} }) })] })] }));
|
|
309
536
|
}
|
|
310
537
|
//# sourceMappingURL=App.js.map
|