@jellyos/agent 0.1.4 → 0.1.5
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 +6 -0
- package/dist/cli.js +114 -48
- package/dist/index.d.ts +15 -2
- package/dist/index.js +13 -3
- 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/tests/ContextStore.test.d.ts +2 -0
- package/dist/tests/ContextStore.test.js +74 -0
- package/dist/tests/ModelRegistry.test.d.ts +2 -0
- package/dist/tests/ModelRegistry.test.js +69 -0
- package/dist/tests/SessionManager.test.d.ts +2 -0
- package/dist/tests/SessionManager.test.js +108 -0
- package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
- package/dist/tests/TechnicalAnalysis.test.js +109 -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 +2 -2
- package/dist/tui/App.js +280 -117
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +11 -6
- package/package.json +9 -4
- 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,24 +1,87 @@
|
|
|
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
7
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
8
|
-
import { Box, useApp, useInput } from "ink";
|
|
8
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
9
9
|
import { StatusBar } from "./StatusBar.js";
|
|
10
10
|
import { REPL } from "./REPL.js";
|
|
11
|
-
import { makeTheme, T } from "./theme.js";
|
|
11
|
+
import { makeTheme, T, JELLY_COLORS } from "./theme.js";
|
|
12
12
|
import { AgentRunner } from "../runner/AgentRunner.js";
|
|
13
13
|
import { SessionManager } from "../session/SessionManager.js";
|
|
14
14
|
import { resolveModelConfig } from "../runner/ModelClient.js";
|
|
15
15
|
import { priceFeed, getPricesTool, topMoversTool, marketOverviewTool, getPricesParams, topMoversParams, marketOverviewParams } from "../tools/PriceFeed.js";
|
|
16
16
|
import { getNewsTool, getNewsParams } from "../tools/NewsSentiment.js";
|
|
17
|
-
import { analyzeTAParams } from "../tools/TechnicalAnalysis.js";
|
|
17
|
+
import { analyzeTAParams, getCandlesParams, getCandlesTool } from "../tools/TechnicalAnalysis.js";
|
|
18
18
|
import { fullAnalysis } from "../tools/TechnicalAnalysis.js";
|
|
19
19
|
import { newsFeed } from "../tools/NewsSentiment.js";
|
|
20
|
+
import { getFearGreedTool, fearGreedParams, getFundingRatesTool, fundingRatesParams, getBtcMempoolTool, btcMempoolParams, getDefiTvlTool, defiTvlParams, getSolanaStatsTool, solanaStatsParams, } from "../tools/MarketSentiment.js";
|
|
21
|
+
import { contextStore } from "../session/ContextStore.js";
|
|
22
|
+
import { goalManager } from "../session/GoalManager.js";
|
|
23
|
+
import { memoryStore } from "../session/MemoryStore.js";
|
|
24
|
+
import { agentScheduler } from "../scheduler/AgentScheduler.js";
|
|
25
|
+
import { Tracer } from "../telemetry/Tracer.js";
|
|
26
|
+
// ── Context window tracking (#33) ───────────────────────────────────────────
|
|
27
|
+
function getContextBar(session) {
|
|
28
|
+
if (!session)
|
|
29
|
+
return { pct: 0, bar: "░".repeat(20), color: JELLY_COLORS.dim, turboReady: true };
|
|
30
|
+
const pressure = session.getContextPressure();
|
|
31
|
+
const filled = Math.round(pressure.pct / 5);
|
|
32
|
+
const color = pressure.level === "critical" ? JELLY_COLORS.error
|
|
33
|
+
: pressure.level === "red" ? JELLY_COLORS.warn
|
|
34
|
+
: pressure.level === "yellow" ? JELLY_COLORS.header
|
|
35
|
+
: JELLY_COLORS.success;
|
|
36
|
+
return {
|
|
37
|
+
pct: pressure.pct,
|
|
38
|
+
bar: "█".repeat(Math.min(filled, 20)) + "░".repeat(Math.max(0, 20 - filled)),
|
|
39
|
+
color,
|
|
40
|
+
turboReady: pressure.turboReady,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// ── Syntax-highlighted JSON formatter ───────────────────────────────────────
|
|
44
|
+
function highlightJson(obj, indent = 0) {
|
|
45
|
+
const pad = " ".repeat(indent);
|
|
46
|
+
if (obj === null)
|
|
47
|
+
return T.dim("null");
|
|
48
|
+
if (typeof obj === "boolean")
|
|
49
|
+
return T.warn(String(obj));
|
|
50
|
+
if (typeof obj === "number")
|
|
51
|
+
return T.success(String(obj));
|
|
52
|
+
if (typeof obj === "string") {
|
|
53
|
+
if (obj.startsWith("0x") && obj.length > 10)
|
|
54
|
+
return T.header(obj.slice(0, 10) + "…");
|
|
55
|
+
return T.accent(`"${obj}"`);
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(obj)) {
|
|
58
|
+
if (obj.length === 0)
|
|
59
|
+
return "[]";
|
|
60
|
+
if (obj.length <= 3)
|
|
61
|
+
return `[${obj.map(v => highlightJson(v, 0)).join(", ")}]`;
|
|
62
|
+
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}]`;
|
|
63
|
+
}
|
|
64
|
+
if (typeof obj === "object") {
|
|
65
|
+
const entries = Object.entries(obj);
|
|
66
|
+
if (entries.length === 0)
|
|
67
|
+
return "{}";
|
|
68
|
+
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}}`;
|
|
69
|
+
}
|
|
70
|
+
return String(obj);
|
|
71
|
+
}
|
|
72
|
+
function formatToolContent(text) {
|
|
73
|
+
try {
|
|
74
|
+
const obj = JSON.parse(text);
|
|
75
|
+
return highlightJson(obj);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return text.length > 300 ? text.slice(0, 300) + T.dim("\n…[truncated]") : text;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
20
81
|
let _msgIdCounter = 0;
|
|
21
82
|
function nextId() { return String(++_msgIdCounter); }
|
|
83
|
+
// Unique session ID for memory persistence
|
|
84
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
22
85
|
export function App({ registry, systemPrompt, effectLevel: initialEffect = "normal", chain: initialChain = "ethereum", modelReg, costTracker, onNotifyReady, onStatusReady, }) {
|
|
23
86
|
const { exit } = useApp();
|
|
24
87
|
const [messages, setMessages] = useState([]);
|
|
@@ -41,7 +104,6 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
41
104
|
modelName = resolveModelConfig(modelReg).model;
|
|
42
105
|
}
|
|
43
106
|
catch { /* shown via banner */ }
|
|
44
|
-
// ── Push helpers ─────────────────────────────────────────────────────────
|
|
45
107
|
const push = useCallback((msg) => {
|
|
46
108
|
setMessages(prev => [...prev, { ...msg, id: nextId(), ts: Date.now() }]);
|
|
47
109
|
}, []);
|
|
@@ -57,123 +119,98 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
57
119
|
if (key === "effect_level")
|
|
58
120
|
setEffectLevel(value);
|
|
59
121
|
}, []);
|
|
60
|
-
const uiCtx = { notify, setStatus, setTheme(
|
|
61
|
-
//
|
|
122
|
+
const uiCtx = { notify, setStatus, setTheme(_n) { }, setHeader(_f) { }, theme };
|
|
123
|
+
// Register built-in tools
|
|
62
124
|
function registerBuiltinTools() {
|
|
63
125
|
if (!modelReg)
|
|
64
126
|
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
|
|
127
|
+
const mk = modelReg;
|
|
128
|
+
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) });
|
|
129
|
+
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) });
|
|
130
|
+
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
131
|
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
|
-
});
|
|
132
|
+
registry.addTool({ name: "cost_report", label: "Cost Report", description: "Show session and lifetime token usage.", parameters: costTracker.costReportParams, execute: () => costTracker.costReportTool() });
|
|
92
133
|
}
|
|
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) => {
|
|
134
|
+
registry.addTool({ name: "get_prices", label: "Get Prices", description: "Get current prices and 24h change.", parameters: getPricesParams, execute: (id, p) => getPricesTool(id, p) });
|
|
135
|
+
registry.addTool({ name: "get_top_movers", label: "Top Movers", description: "Assets with largest 24h price movement.", parameters: topMoversParams, execute: (id, p) => topMoversTool(id, p) });
|
|
136
|
+
registry.addTool({ name: "get_market_overview", label: "Market Overview", description: "Aggregated market data.", parameters: marketOverviewParams, execute: () => marketOverviewTool() });
|
|
137
|
+
registry.addTool({ name: "get_news", label: "Get News", description: "Crypto news headlines with sentiment scoring.", parameters: getNewsParams, execute: (id, p) => getNewsTool(id, p) });
|
|
138
|
+
// #18: OHLCV candles from Binance + TA analysis
|
|
139
|
+
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) });
|
|
140
|
+
// #20: Free market sentiment tools
|
|
141
|
+
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) });
|
|
142
|
+
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) });
|
|
143
|
+
registry.addTool({ name: "get_btc_mempool", label: "BTC Mempool", description: "Bitcoin mempool stats and recommended fee rates.", parameters: btcMempoolParams, execute: () => getBtcMempoolTool() });
|
|
144
|
+
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) });
|
|
145
|
+
registry.addTool({ name: "get_solana_stats", label: "Solana Stats", description: "Solana network TPS and health.", parameters: solanaStatsParams, execute: () => getSolanaStatsTool() });
|
|
146
|
+
// #31: Ephemeral task context
|
|
147
|
+
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) });
|
|
148
|
+
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() });
|
|
149
|
+
// #12: Goal management
|
|
150
|
+
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) });
|
|
151
|
+
registry.addTool({ name: "complete_goal", label: "Complete Goal", description: "Mark a goal as completed.", parameters: goalManager.completeGoalParams, execute: (id, p) => goalManager.completeGoalTool(id, p) });
|
|
152
|
+
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) });
|
|
153
|
+
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) });
|
|
154
|
+
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
155
|
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
|
-
}));
|
|
156
|
+
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
157
|
const results = fullAnalysis(candles);
|
|
133
|
-
const
|
|
158
|
+
const summary = results.find((r) => r.indicator === "SUMMARY");
|
|
159
|
+
const text = results.map((r) => {
|
|
134
160
|
const s = r.signal === "bullish" ? "🟢" : r.signal === "bearish" ? "🔴" : "⚪";
|
|
135
|
-
const v = Array.isArray(r.value) ? `[${r.value.length}
|
|
161
|
+
const v = Array.isArray(r.value) ? `[${r.value.length}]` : typeof r.value === "number" ? r.value : "-";
|
|
136
162
|
return `${s} ${r.indicator}: ${v}`;
|
|
137
163
|
}).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
|
-
});
|
|
164
|
+
return { content: [{ type: "text", text: `Technical Analysis:\n${text}` }], details: { results, overall_signal: summary?.signal, overall_score: summary?.value } };
|
|
165
|
+
} });
|
|
145
166
|
}
|
|
146
167
|
useEffect(() => { onNotifyReady?.(notify); onStatusReady?.(setStatus); }, []);
|
|
147
|
-
// ── Boot: start feeds, register tools, fire session_start ────────────────
|
|
148
168
|
useEffect(() => {
|
|
149
169
|
registerBuiltinTools();
|
|
150
|
-
// Start background feeds
|
|
151
170
|
priceFeed.track("btc", "eth", "sol", "bnb", "matic", "arb", "op", "avax", "link", "uni", "doge", "xrp", "ada", "dot", "atom", "near", "sui", "apt", "pepe", "aave");
|
|
152
171
|
priceFeed.start();
|
|
153
172
|
newsFeed.start();
|
|
154
|
-
//
|
|
173
|
+
// #11: Register scheduler tools
|
|
174
|
+
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) });
|
|
175
|
+
registry.addTool({ name: "list_schedule", label: "List Schedule", description: "List all scheduled tasks.", parameters: agentScheduler.listTasksParams, execute: (id, p) => agentScheduler.listTasksTool(id, p) });
|
|
176
|
+
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) });
|
|
177
|
+
// Start scheduler — fires agent turns autonomously
|
|
178
|
+
agentScheduler.start((task) => {
|
|
179
|
+
push({ role: "system", content: T.muted(`⏰ Scheduler: running "${task.name}"`) });
|
|
180
|
+
// Only fire if agent is not currently busy
|
|
181
|
+
if (!disabled && runnerRef.current) {
|
|
182
|
+
setDisabled(true);
|
|
183
|
+
setStreaming("");
|
|
184
|
+
runnerRef.current.run(`[SCHEDULED TASK: ${task.name}] ${task.prompt}`)
|
|
185
|
+
.catch((e) => notify(T.error(`Scheduler error: ${e.message}`)));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
155
188
|
const tickerInterval = setInterval(() => {
|
|
156
|
-
setTicker(priceFeed.tickerLine(
|
|
189
|
+
setTicker(priceFeed.tickerLine(6));
|
|
157
190
|
if (costTracker)
|
|
158
191
|
setCostBadge(costTracker.statusLine());
|
|
159
192
|
setNewsBadge(newsFeed.statusBadge());
|
|
160
193
|
}, 5_000);
|
|
161
|
-
|
|
162
|
-
const saveInterval = setInterval(() => {
|
|
163
|
-
costTracker?.saveLifetime();
|
|
164
|
-
}, 60_000);
|
|
165
|
-
// Agent session
|
|
194
|
+
const saveInterval = setInterval(() => { costTracker?.saveLifetime(); }, 60_000);
|
|
166
195
|
const session = new SessionManager();
|
|
167
|
-
session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt ||
|
|
168
|
-
"You are JellyOS, an autonomous AI trading agent.");
|
|
196
|
+
session.setSystemPrompt(registry.getSystemPrompt() || systemPrompt || "You are JellyOS, an autonomous AI trading agent.");
|
|
169
197
|
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
|
-
};
|
|
198
|
+
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
199
|
sessionCtxRef.current = sessionCtx;
|
|
175
200
|
registry.fireHook("session_start", sessionCtx).then(() => {
|
|
176
|
-
|
|
201
|
+
// #7: Inject memory context into system prompt
|
|
202
|
+
if (memoryStore.isAvailable) {
|
|
203
|
+
const memCtx = memoryStore.buildContextBlock(sessionId);
|
|
204
|
+
if (memCtx) {
|
|
205
|
+
session.setSystemPrompt((session.getSystemPrompt() || "") + memCtx);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// #12: Inject active goals into system prompt
|
|
209
|
+
const goalCtx = goalManager.buildContextBlock();
|
|
210
|
+
if (goalCtx) {
|
|
211
|
+
session.setSystemPrompt((session.getSystemPrompt() || "") + goalCtx);
|
|
212
|
+
}
|
|
213
|
+
push({ role: "system", content: T.muted("Session started. Type /help for commands.") });
|
|
177
214
|
});
|
|
178
215
|
const runner = new AgentRunner(registry, session, (event) => {
|
|
179
216
|
if (event.type === "text_delta")
|
|
@@ -182,13 +219,20 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
182
219
|
setToolRunning(event.name);
|
|
183
220
|
else if (event.type === "tool_done") {
|
|
184
221
|
setToolRunning(null);
|
|
185
|
-
|
|
222
|
+
// Format tool output with syntax highlighting
|
|
223
|
+
const formatted = formatToolContent(event.result);
|
|
224
|
+
push({ role: "tool", content: formatted, toolName: event.name, isError: event.isError });
|
|
186
225
|
}
|
|
187
226
|
else if (event.type === "turn_done") {
|
|
188
227
|
setDisabled(false);
|
|
189
228
|
setToolRunning(null);
|
|
190
|
-
setStreaming(prev => {
|
|
191
|
-
|
|
229
|
+
setStreaming(prev => {
|
|
230
|
+
if (prev.trim()) {
|
|
231
|
+
push({ role: "assistant", content: prev });
|
|
232
|
+
memoryStore.save(sessionId, "assistant", prev); // #7 persist to memory
|
|
233
|
+
}
|
|
234
|
+
return "";
|
|
235
|
+
});
|
|
192
236
|
}
|
|
193
237
|
else if (event.type === "error") {
|
|
194
238
|
setDisabled(false);
|
|
@@ -196,10 +240,27 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
196
240
|
setStreaming("");
|
|
197
241
|
notify(T.error(`Error: ${event.message}`));
|
|
198
242
|
}
|
|
199
|
-
|
|
243
|
+
else if (event.type === "approval_request") {
|
|
244
|
+
// #10: Show approval prompt — user must type y or n
|
|
245
|
+
setToolRunning(null);
|
|
246
|
+
const { toolName, args, approve } = event;
|
|
247
|
+
let parsed = "";
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.stringify(JSON.parse(args), null, 2).slice(0, 200);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
parsed = args.slice(0, 200);
|
|
253
|
+
}
|
|
254
|
+
push({
|
|
255
|
+
role: "notify",
|
|
256
|
+
content: `⚠️ APPROVAL REQUIRED\n\nTool: ${toolName}\nArgs: ${parsed}\n\nType /approve or /deny to continue.`,
|
|
257
|
+
});
|
|
258
|
+
// Store the approve callback so /approve /deny commands can resolve it
|
|
259
|
+
runnerRef.current._pendingApproval = approve;
|
|
260
|
+
}
|
|
261
|
+
}, sessionCtx, effectLevel, modelReg, costTracker, goalManager, contextStore);
|
|
200
262
|
runnerRef.current = runner;
|
|
201
|
-
|
|
202
|
-
setTicker(priceFeed.tickerLine(5));
|
|
263
|
+
setTicker(priceFeed.tickerLine(6));
|
|
203
264
|
if (costTracker)
|
|
204
265
|
setCostBadge(costTracker.statusLine());
|
|
205
266
|
setNewsBadge(newsFeed.statusBadge());
|
|
@@ -208,13 +269,14 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
208
269
|
registry.fireHook("session_end", sessionCtxRef.current).catch(() => { });
|
|
209
270
|
priceFeed.stop();
|
|
210
271
|
newsFeed.stop();
|
|
272
|
+
agentScheduler.stop();
|
|
211
273
|
clearInterval(tickerInterval);
|
|
212
274
|
clearInterval(saveInterval);
|
|
213
275
|
costTracker?.saveLifetime();
|
|
214
276
|
};
|
|
215
277
|
}, []);
|
|
216
278
|
useEffect(() => { runnerRef.current?.setEffectLevel(effectLevel); }, [effectLevel]);
|
|
217
|
-
// ── Ctrl-C
|
|
279
|
+
// ── Ctrl-C to exit ──────────────────────────────────────────────────────
|
|
218
280
|
useInput((_input, key) => {
|
|
219
281
|
if (key.ctrl && _input === "c") {
|
|
220
282
|
push({ role: "system", content: T.muted("Goodbye 🪼") });
|
|
@@ -227,7 +289,6 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
227
289
|
const input = raw.trim();
|
|
228
290
|
if (!input)
|
|
229
291
|
return;
|
|
230
|
-
// ── Slash commands ────────────────────────────────────────────────────
|
|
231
292
|
if (input.startsWith("/")) {
|
|
232
293
|
const [cmd, ...rest] = input.slice(1).split(" ");
|
|
233
294
|
const args = rest.join(" ");
|
|
@@ -239,21 +300,18 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
239
300
|
}
|
|
240
301
|
if (cmd === "help") {
|
|
241
302
|
const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
|
|
242
|
-
notify("
|
|
303
|
+
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
304
|
return;
|
|
244
305
|
}
|
|
245
|
-
// Built-in /models command
|
|
246
306
|
if (cmd === "models") {
|
|
247
307
|
if (!modelReg) {
|
|
248
308
|
notify(T.error("Model registry not available"));
|
|
249
309
|
return;
|
|
250
310
|
}
|
|
251
|
-
const
|
|
252
|
-
const result = await modelReg.listModelsTool("", { query: query, limit: 20, available_only: true });
|
|
311
|
+
const result = await modelReg.listModelsTool("", { query: rest.join(" ") || undefined, limit: 20, available_only: true });
|
|
253
312
|
notify(result.content[0].text);
|
|
254
313
|
return;
|
|
255
314
|
}
|
|
256
|
-
// Built-in /cost command
|
|
257
315
|
if (cmd === "cost") {
|
|
258
316
|
if (!costTracker) {
|
|
259
317
|
notify(T.error("Cost tracker not available"));
|
|
@@ -263,28 +321,120 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
263
321
|
notify(result.content[0].text);
|
|
264
322
|
return;
|
|
265
323
|
}
|
|
266
|
-
// Built-in /news command
|
|
267
324
|
if (cmd === "news") {
|
|
268
325
|
const result = await getNewsTool("", { limit: 15 });
|
|
269
326
|
notify(result.content[0].text);
|
|
270
327
|
return;
|
|
271
328
|
}
|
|
272
|
-
// Built-in /prices command
|
|
273
329
|
if (cmd === "prices") {
|
|
274
330
|
const result = await getPricesTool("", { symbols: args ? args.split(/\s+/) : ["btc", "eth", "sol"] });
|
|
275
331
|
notify(result.content[0].text);
|
|
276
332
|
return;
|
|
277
333
|
}
|
|
334
|
+
if (cmd === "palette") {
|
|
335
|
+
// Command palette — list all commands and tools
|
|
336
|
+
const lines = registry.listCommands().map(([n, d]) => T.accent(`/${n}`.padEnd(16)) + " " + d.description);
|
|
337
|
+
const tools = registry.listTools().map(t => T.success(`🔧 ${t.name.padEnd(22)}`) + " " + T.muted(t.description.slice(0, 50)));
|
|
338
|
+
notify("Command Palette\n\n" + lines.join("\n") + "\n\nTools:\n" + tools.join("\n"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (cmd === "effect" && ["eco", "normal", "turbo", "max"].includes(args.trim().toLowerCase())) {
|
|
342
|
+
setEffectLevel(args.trim().toLowerCase());
|
|
343
|
+
notify(T.accent(`Effect → ${args.trim().toUpperCase()}`));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// #12: Goal commands
|
|
347
|
+
if (cmd === "goals" || cmd === "goal") {
|
|
348
|
+
const subCmd = args.trim().split(" ")[0];
|
|
349
|
+
if (subCmd === "add") {
|
|
350
|
+
const text = args.trim().slice(4).trim();
|
|
351
|
+
if (!text) {
|
|
352
|
+
notify(T.error("Usage: /goal add <text>"));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const result = await goalManager.setGoalTool("", { text });
|
|
356
|
+
notify(result.content[0].text);
|
|
357
|
+
}
|
|
358
|
+
else if (subCmd === "done" || subCmd === "complete") {
|
|
359
|
+
const id = args.trim().split(" ")[1];
|
|
360
|
+
if (!id) {
|
|
361
|
+
notify(T.error("Usage: /goal done <id>"));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const result = await goalManager.completeGoalTool("", { id });
|
|
365
|
+
notify(result.content[0].text);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const result = await goalManager.listGoalsTool("", { status: "all" });
|
|
369
|
+
notify(result.content[0].text);
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
// #31: Task context commands
|
|
374
|
+
if (cmd === "tasks") {
|
|
375
|
+
const result = await contextStore.listTasksTool();
|
|
376
|
+
notify(result.content[0].text);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (cmd === "keep" && args.trim()) {
|
|
380
|
+
const ok = contextStore.keepTask(args.trim());
|
|
381
|
+
notify(ok ? T.success(`Task ${args.trim()} marked for permanent retention.`) : T.error(`Task ${args.trim()} not found.`));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// #10: Approval gate responses
|
|
385
|
+
if (cmd === "approve" || cmd === "y") {
|
|
386
|
+
const pending = runnerRef.current?._pendingApproval;
|
|
387
|
+
if (pending) {
|
|
388
|
+
pending(true);
|
|
389
|
+
push({ role: "system", content: T.success("✅ Tool approved.") });
|
|
390
|
+
}
|
|
391
|
+
else
|
|
392
|
+
notify(T.error("No pending approval."));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (cmd === "deny" || cmd === "n") {
|
|
396
|
+
const pending = runnerRef.current?._pendingApproval;
|
|
397
|
+
if (pending) {
|
|
398
|
+
pending(false);
|
|
399
|
+
push({ role: "system", content: T.error("❌ Tool denied.") });
|
|
400
|
+
}
|
|
401
|
+
else
|
|
402
|
+
notify(T.error("No pending approval."));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// #30: Traces command
|
|
406
|
+
if (cmd === "traces") {
|
|
407
|
+
const recent = Tracer.readRecent(5);
|
|
408
|
+
notify(Tracer.formatSummary(recent));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// #11: Scheduler commands
|
|
412
|
+
if (cmd === "schedule" || cmd === "sched") {
|
|
413
|
+
const result = await agentScheduler.listTasksTool("", { enabled_only: false });
|
|
414
|
+
notify(result.content[0].text);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// #7: Memory search
|
|
418
|
+
if (cmd === "memory" && args.trim()) {
|
|
419
|
+
const results = memoryStore.search(args.trim(), 8);
|
|
420
|
+
if (results.length === 0) {
|
|
421
|
+
notify(T.muted("No memories found for: " + args.trim()));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const lines = results.map(r => `[${new Date(r.ts).toLocaleDateString()} ${r.role}] ${r.content.slice(0, 120)}`);
|
|
425
|
+
notify(`Memory search: "${args.trim()}"\n${lines.join("\n")}`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
278
428
|
const def = registry.getCommand(cmd);
|
|
279
429
|
if (!def) {
|
|
280
|
-
notify(T.error(`Unknown
|
|
430
|
+
notify(T.error(`Unknown: /${cmd} — try /help or /palette`));
|
|
281
431
|
return;
|
|
282
432
|
}
|
|
283
433
|
try {
|
|
284
434
|
await def.handler(args, { ui: uiCtx });
|
|
285
435
|
}
|
|
286
436
|
catch (e) {
|
|
287
|
-
notify(T.error(`
|
|
437
|
+
notify(T.error(`Error: ${e.message}`));
|
|
288
438
|
}
|
|
289
439
|
return;
|
|
290
440
|
}
|
|
@@ -293,6 +443,7 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
293
443
|
return;
|
|
294
444
|
}
|
|
295
445
|
push({ role: "user", content: input });
|
|
446
|
+
memoryStore.save(sessionId, "user", input); // #7 persist user message
|
|
296
447
|
setDisabled(true);
|
|
297
448
|
setStreaming("");
|
|
298
449
|
try {
|
|
@@ -300,11 +451,23 @@ export function App({ registry, systemPrompt, effectLevel: initialEffect = "norm
|
|
|
300
451
|
}
|
|
301
452
|
catch (e) {
|
|
302
453
|
setDisabled(false);
|
|
303
|
-
notify(T.error(`
|
|
454
|
+
notify(T.error(`Error: ${e.message}`));
|
|
304
455
|
}
|
|
305
456
|
}, [registry, exit, push, notify, uiCtx, modelReg, costTracker]);
|
|
306
|
-
|
|
457
|
+
const ctx = getContextBar(sessionRef.current);
|
|
307
458
|
const statusLine = [ticker, costBadge, newsBadge, ...Object.values(statusBadges)].filter(Boolean).join(" ") || null;
|
|
308
|
-
|
|
459
|
+
// ── Multi-pane layout ────────────────────────────────────────────────────
|
|
460
|
+
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: () => {
|
|
461
|
+
// #25: Escape aborts in-flight stream
|
|
462
|
+
runnerRef.current?.abort();
|
|
463
|
+
setDisabled(false);
|
|
464
|
+
setToolRunning(null);
|
|
465
|
+
setStreaming(prev => {
|
|
466
|
+
if (prev.trim())
|
|
467
|
+
push({ role: "assistant", content: prev + " \u2014 [interrupted]" });
|
|
468
|
+
return "";
|
|
469
|
+
});
|
|
470
|
+
push({ role: "system", content: T.dim("— stream interrupted —") });
|
|
471
|
+
} }) })] })] }));
|
|
309
472
|
}
|
|
310
473
|
//# sourceMappingURL=App.js.map
|
package/dist/tui/REPL.d.ts
CHANGED
|
@@ -16,7 +16,8 @@ export interface REPLProps {
|
|
|
16
16
|
streamingText: string;
|
|
17
17
|
toolRunning: string | null;
|
|
18
18
|
onSubmit(input: string): void;
|
|
19
|
+
onAbort?(): void;
|
|
19
20
|
disabled: boolean;
|
|
20
21
|
}
|
|
21
|
-
export declare function REPL({ messages, streamingText, toolRunning, onSubmit, disabled }: REPLProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }: REPLProps): import("react/jsx-runtime").JSX.Element;
|
|
22
23
|
//# sourceMappingURL=REPL.d.ts.map
|
package/dist/tui/REPL.js
CHANGED
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* Renders assistant text, tool calls, tool results, and user messages.
|
|
5
5
|
*/
|
|
6
6
|
import { useState, useCallback } from "react";
|
|
7
|
-
import { Box, Text, useStdout } from "ink";
|
|
7
|
+
import { Box, Text, useStdout, useInput } from "ink";
|
|
8
8
|
import TextInput from "ink-text-input";
|
|
9
9
|
import { JELLY_COLORS } from "./theme.js";
|
|
10
10
|
const MAX_VISIBLE = 40;
|
|
@@ -19,9 +19,8 @@ function MessageLine({ msg }) {
|
|
|
19
19
|
if (msg.role === "tool") {
|
|
20
20
|
const icon = msg.isError ? "✗" : "✓";
|
|
21
21
|
const col = msg.isError ? JELLY_COLORS.error : JELLY_COLORS.success;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
: _jsxs(Text, { color: JELLY_COLORS.muted, children: [" (", msg.content.length, " chars)"] })] }));
|
|
22
|
+
const isLong = msg.content.length > 120;
|
|
23
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 0, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: JELLY_COLORS.muted, children: time }), _jsxs(Text, { color: col, children: [icon, " ", msg.toolName ?? "tool"] }), isLong && _jsxs(Text, { color: JELLY_COLORS.dim, children: [" (", msg.content.length, " chars)"] })] }), !isLong && (_jsx(Box, { marginLeft: 6, children: _jsx(Text, { color: JELLY_COLORS.muted, wrap: "wrap", children: msg.content }) }))] }));
|
|
25
24
|
}
|
|
26
25
|
if (msg.role === "notify") {
|
|
27
26
|
return (_jsx(Box, { borderStyle: "round", borderColor: JELLY_COLORS.accent, marginY: 1, paddingX: 1, children: _jsx(Text, { wrap: "wrap", children: msg.content }) }));
|
|
@@ -29,7 +28,7 @@ function MessageLine({ msg }) {
|
|
|
29
28
|
// system messages — dimmed
|
|
30
29
|
return (_jsx(Box, { flexDirection: "row", gap: 1, children: _jsx(Text, { color: JELLY_COLORS.dim, wrap: "wrap", children: msg.content }) }));
|
|
31
30
|
}
|
|
32
|
-
export function REPL({ messages, streamingText, toolRunning, onSubmit, disabled }) {
|
|
31
|
+
export function REPL({ messages, streamingText, toolRunning, onSubmit, onAbort, disabled }) {
|
|
33
32
|
const [input, setInput] = useState("");
|
|
34
33
|
const { stdout } = useStdout();
|
|
35
34
|
const termWidth = stdout?.columns ?? 80;
|
|
@@ -40,7 +39,13 @@ export function REPL({ messages, streamingText, toolRunning, onSubmit, disabled
|
|
|
40
39
|
setInput("");
|
|
41
40
|
onSubmit(trimmed);
|
|
42
41
|
}, [onSubmit, disabled]);
|
|
42
|
+
// #25: Escape key aborts in-flight stream
|
|
43
|
+
useInput((_input, key) => {
|
|
44
|
+
if (key.escape && disabled && onAbort) {
|
|
45
|
+
onAbort();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
43
48
|
const visible = messages.slice(-MAX_VISIBLE);
|
|
44
|
-
return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.muted, children: " " }), _jsx(Text, { color: JELLY_COLORS.header, bold: true, children: "\uD83E\uDEBC " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: JELLY_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? JELLY_COLORS.dim : JELLY_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] }), _jsx(Box, { paddingX: 2, children:
|
|
49
|
+
return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [visible.map(m => _jsx(MessageLine, { msg: m }, m.id)), streamingText && (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.muted, children: " " }), _jsx(Text, { color: JELLY_COLORS.header, bold: true, children: "\uD83E\uDEBC " }), _jsx(Text, { wrap: "wrap", children: streamingText })] })), toolRunning && (_jsx(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: _jsxs(Text, { color: JELLY_COLORS.warn, children: ["\u2699 running ", toolRunning, "\u2026"] }) }))] }), _jsxs(Box, { borderStyle: "round", borderColor: disabled ? JELLY_COLORS.dim : JELLY_COLORS.accent, paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: JELLY_COLORS.accent, children: "\u203A " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: disabled ? "thinking…" : "message or /command" })] }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { color: JELLY_COLORS.dim, children: ["/help \u00B7 /palette \u00B7 /cost \u00B7 /prices \u00B7 /news \u00B7 /goals \u00B7 /tasks \u00B7 ", disabled ? "Esc=abort · " : "", "Ctrl-C to exit"] }) })] }));
|
|
45
50
|
}
|
|
46
51
|
//# sourceMappingURL=REPL.js.map
|