@spfunctions/cli 0.1.6 → 1.1.0
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/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +213 -33
- package/dist/index.js +3 -2
- package/package.json +1 -1
package/dist/commands/agent.d.ts
CHANGED
package/dist/commands/agent.js
CHANGED
|
@@ -13,10 +13,43 @@
|
|
|
13
13
|
* Slash commands (bypass LLM):
|
|
14
14
|
* /help /tree /edges /pos /eval /model /clear /exit
|
|
15
15
|
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
20
|
exports.agentCommand = agentCommand;
|
|
21
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
|
+
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const os_1 = __importDefault(require("os"));
|
|
18
24
|
const client_js_1 = require("../client.js");
|
|
19
25
|
const kalshi_js_1 = require("../kalshi.js");
|
|
26
|
+
// ─── Session persistence ─────────────────────────────────────────────────────
|
|
27
|
+
function getSessionDir() {
|
|
28
|
+
return path_1.default.join(os_1.default.homedir(), '.sf', 'sessions');
|
|
29
|
+
}
|
|
30
|
+
function getSessionPath(thesisId) {
|
|
31
|
+
return path_1.default.join(getSessionDir(), `${thesisId}.json`);
|
|
32
|
+
}
|
|
33
|
+
function loadSession(thesisId) {
|
|
34
|
+
const p = getSessionPath(thesisId);
|
|
35
|
+
try {
|
|
36
|
+
if (fs_1.default.existsSync(p)) {
|
|
37
|
+
return JSON.parse(fs_1.default.readFileSync(p, 'utf-8'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch { /* corrupt file, ignore */ }
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function saveSession(thesisId, model, messages) {
|
|
44
|
+
const dir = getSessionDir();
|
|
45
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
46
|
+
fs_1.default.writeFileSync(getSessionPath(thesisId), JSON.stringify({
|
|
47
|
+
thesisId,
|
|
48
|
+
model,
|
|
49
|
+
updatedAt: new Date().toISOString(),
|
|
50
|
+
messages,
|
|
51
|
+
}, null, 2));
|
|
52
|
+
}
|
|
20
53
|
// ─── ANSI 24-bit color helpers (no chalk dependency) ─────────────────────────
|
|
21
54
|
const rgb = (r, g, b) => (s) => `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m`;
|
|
22
55
|
const bgRgb = (r, g, b) => (s) => `\x1b[48;2;${r};${g};${b}m${s}\x1b[49m`;
|
|
@@ -197,16 +230,19 @@ function renderEdges(context, piTui) {
|
|
|
197
230
|
const positions = context._positions || [];
|
|
198
231
|
const lines = [];
|
|
199
232
|
for (const e of edges) {
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
233
|
+
// Context API field names: market, marketId, thesisPrice, edge, orderbook.spread, orderbook.liquidityScore
|
|
234
|
+
const name = (e.market || e.marketId || '').slice(0, 18).padEnd(18);
|
|
235
|
+
const marketStr = typeof e.marketPrice === 'number' ? `${e.marketPrice}\u00A2` : '?';
|
|
236
|
+
const thesisStr = typeof e.thesisPrice === 'number' ? `${e.thesisPrice}\u00A2` : '?';
|
|
237
|
+
const edgeVal = typeof e.edge === 'number' ? (e.edge > 0 ? `+${e.edge}` : `${e.edge}`) : '?';
|
|
238
|
+
const ob = e.orderbook || {};
|
|
239
|
+
const spreadStr = typeof ob.spread === 'number' ? `${ob.spread}\u00A2` : '?';
|
|
240
|
+
const liq = ob.liquidityScore || 'low';
|
|
206
241
|
const liqBars = liq === 'high' ? '\u25A0\u25A0\u25A0' : liq === 'medium' ? '\u25A0\u25A0 ' : '\u25A0 ';
|
|
207
242
|
const liqColor = liq === 'high' ? C.emerald : liq === 'medium' ? C.amber : C.red;
|
|
208
|
-
// Check if we have a position on this edge
|
|
209
|
-
const pos = positions.find((p) => p.ticker === e.
|
|
243
|
+
// Check if we have a position on this edge (match by marketId prefix in ticker)
|
|
244
|
+
const pos = positions.find((p) => p.ticker === e.marketId ||
|
|
245
|
+
(e.marketId && p.ticker?.includes(e.marketId)));
|
|
210
246
|
let posStr = C.zinc600('\u2014');
|
|
211
247
|
if (pos) {
|
|
212
248
|
const side = pos.side?.toUpperCase() || 'YES';
|
|
@@ -215,7 +251,7 @@ function renderEdges(context, piTui) {
|
|
|
215
251
|
: '';
|
|
216
252
|
posStr = C.emerald(`${side} (${pos.quantity}@${pos.average_price_paid}\u00A2 ${pnl})`);
|
|
217
253
|
}
|
|
218
|
-
lines.push(` ${C.zinc200(name)} ${C.zinc400(
|
|
254
|
+
lines.push(` ${C.zinc200(name)} ${C.zinc400(marketStr)} \u2192 ${C.zinc400(thesisStr)} edge ${edgeVal.includes('+') ? C.emerald(edgeVal) : C.red(edgeVal)} spread ${C.zinc600(spreadStr)} ${liqColor(liqBars)} ${liqColor(liq.padEnd(4))} ${posStr}`);
|
|
219
255
|
}
|
|
220
256
|
return lines.join('\n');
|
|
221
257
|
}
|
|
@@ -283,7 +319,7 @@ async function agentCommand(thesisId, opts) {
|
|
|
283
319
|
// ── Fetch initial context ──────────────────────────────────────────────────
|
|
284
320
|
let latestContext = await sfClient.getContext(resolvedThesisId);
|
|
285
321
|
// ── Model setup ────────────────────────────────────────────────────────────
|
|
286
|
-
const rawModelName = opts?.model || 'anthropic/claude-sonnet-4
|
|
322
|
+
const rawModelName = opts?.model || 'anthropic/claude-sonnet-4.6';
|
|
287
323
|
let currentModelName = rawModelName.replace(/^openrouter\//, '');
|
|
288
324
|
function resolveModel(name) {
|
|
289
325
|
try {
|
|
@@ -365,9 +401,12 @@ async function agentCommand(thesisId, opts) {
|
|
|
365
401
|
{ name: 'edges', description: 'Display edge/spread table' },
|
|
366
402
|
{ name: 'pos', description: 'Display Kalshi positions' },
|
|
367
403
|
{ name: 'eval', description: 'Trigger deep evaluation' },
|
|
404
|
+
{ name: 'switch', description: 'Switch thesis (e.g. /switch f582bf76)' },
|
|
405
|
+
{ name: 'compact', description: 'Compress conversation history' },
|
|
406
|
+
{ name: 'new', description: 'Start fresh session' },
|
|
368
407
|
{ name: 'model', description: 'Switch model (e.g. /model anthropic/claude-sonnet-4)' },
|
|
369
|
-
{ name: 'clear', description: 'Clear
|
|
370
|
-
{ name: 'exit', description: 'Exit agent' },
|
|
408
|
+
{ name: 'clear', description: 'Clear screen (keeps history)' },
|
|
409
|
+
{ name: 'exit', description: 'Exit agent (auto-saves)' },
|
|
371
410
|
], process.cwd());
|
|
372
411
|
editor.setAutocompleteProvider(autocompleteProvider);
|
|
373
412
|
// Assemble TUI tree
|
|
@@ -558,12 +597,53 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
558
597
|
return undefined;
|
|
559
598
|
},
|
|
560
599
|
});
|
|
600
|
+
// ── Session restore ────────────────────────────────────────────────────────
|
|
601
|
+
let sessionRestored = false;
|
|
602
|
+
if (!opts?.newSession) {
|
|
603
|
+
const saved = loadSession(resolvedThesisId);
|
|
604
|
+
if (saved?.messages?.length > 0) {
|
|
605
|
+
try {
|
|
606
|
+
agent.replaceMessages(saved.messages);
|
|
607
|
+
// Always update system prompt with fresh context
|
|
608
|
+
agent.setSystemPrompt(systemPrompt);
|
|
609
|
+
sessionRestored = true;
|
|
610
|
+
}
|
|
611
|
+
catch { /* corrupt session, start fresh */ }
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Helper to persist session after each turn
|
|
615
|
+
function persistSession() {
|
|
616
|
+
try {
|
|
617
|
+
const msgs = agent.state.messages;
|
|
618
|
+
if (msgs.length > 0) {
|
|
619
|
+
saveSession(resolvedThesisId, currentModelName, msgs);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
catch { /* best-effort save */ }
|
|
623
|
+
}
|
|
561
624
|
// ── Subscribe to agent events → update TUI ────────────────────────────────
|
|
562
625
|
let currentAssistantMd = null;
|
|
563
626
|
let currentAssistantText = '';
|
|
564
627
|
let currentLoader = null;
|
|
565
628
|
const toolStartTimes = new Map();
|
|
566
629
|
const toolLines = new Map();
|
|
630
|
+
// Throttle renders during streaming to prevent flicker (max ~15fps)
|
|
631
|
+
let renderTimer = null;
|
|
632
|
+
function throttledRender() {
|
|
633
|
+
if (renderTimer)
|
|
634
|
+
return;
|
|
635
|
+
renderTimer = setTimeout(() => {
|
|
636
|
+
renderTimer = null;
|
|
637
|
+
tui.requestRender();
|
|
638
|
+
}, 66);
|
|
639
|
+
}
|
|
640
|
+
function flushRender() {
|
|
641
|
+
if (renderTimer) {
|
|
642
|
+
clearTimeout(renderTimer);
|
|
643
|
+
renderTimer = null;
|
|
644
|
+
}
|
|
645
|
+
tui.requestRender();
|
|
646
|
+
}
|
|
567
647
|
agent.subscribe((event) => {
|
|
568
648
|
if (event.type === 'message_start') {
|
|
569
649
|
// Show loader while waiting for first text
|
|
@@ -590,30 +670,31 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
590
670
|
if (currentAssistantMd) {
|
|
591
671
|
currentAssistantMd.setText(currentAssistantText);
|
|
592
672
|
}
|
|
593
|
-
|
|
673
|
+
// Throttled render to prevent flicker during fast token streaming
|
|
674
|
+
throttledRender();
|
|
594
675
|
}
|
|
595
676
|
}
|
|
596
|
-
if (event.type === '
|
|
677
|
+
if (event.type === 'message_end') {
|
|
597
678
|
// Clean up loader if still present (no text was generated)
|
|
598
679
|
if (currentLoader) {
|
|
599
680
|
currentLoader.stop();
|
|
600
681
|
chatContainer.removeChild(currentLoader);
|
|
601
682
|
currentLoader = null;
|
|
602
683
|
}
|
|
603
|
-
//
|
|
684
|
+
// Final render of the complete message
|
|
685
|
+
if (currentAssistantMd && currentAssistantText) {
|
|
686
|
+
currentAssistantMd.setText(currentAssistantText);
|
|
687
|
+
}
|
|
604
688
|
addSpacer();
|
|
605
689
|
currentAssistantMd = null;
|
|
606
690
|
currentAssistantText = '';
|
|
691
|
+
flushRender();
|
|
692
|
+
}
|
|
693
|
+
if (event.type === 'agent_end') {
|
|
694
|
+
// Agent turn fully complete — safe to accept new input
|
|
607
695
|
isProcessing = false;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (event.usage) {
|
|
611
|
-
totalTokens += (event.usage.inputTokens || 0) + (event.usage.outputTokens || 0);
|
|
612
|
-
totalCost += event.usage.totalCost || 0;
|
|
613
|
-
footerBar.tokens = totalTokens;
|
|
614
|
-
footerBar.cost = totalCost;
|
|
615
|
-
footerBar.update();
|
|
616
|
-
}
|
|
696
|
+
persistSession();
|
|
697
|
+
flushRender();
|
|
617
698
|
}
|
|
618
699
|
if (event.type === 'tool_execution_start') {
|
|
619
700
|
const toolLine = new MutableLine(C.zinc600(` \u26A1 ${event.toolName}...`));
|
|
@@ -651,14 +732,17 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
651
732
|
case '/help': {
|
|
652
733
|
addSpacer();
|
|
653
734
|
addSystemText(C.zinc200(bold('Commands')) + '\n' +
|
|
654
|
-
C.emerald('/help
|
|
655
|
-
C.emerald('/tree
|
|
656
|
-
C.emerald('/edges
|
|
657
|
-
C.emerald('/pos
|
|
658
|
-
C.emerald('/eval
|
|
659
|
-
C.emerald('/
|
|
660
|
-
C.emerald('/
|
|
661
|
-
C.emerald('/
|
|
735
|
+
C.emerald('/help ') + C.zinc400('Show this help') + '\n' +
|
|
736
|
+
C.emerald('/tree ') + C.zinc400('Display causal tree') + '\n' +
|
|
737
|
+
C.emerald('/edges ') + C.zinc400('Display edge/spread table') + '\n' +
|
|
738
|
+
C.emerald('/pos ') + C.zinc400('Display Kalshi positions') + '\n' +
|
|
739
|
+
C.emerald('/eval ') + C.zinc400('Trigger deep evaluation') + '\n' +
|
|
740
|
+
C.emerald('/switch <id>') + C.zinc400(' Switch thesis') + '\n' +
|
|
741
|
+
C.emerald('/compact ') + C.zinc400('Compress conversation history') + '\n' +
|
|
742
|
+
C.emerald('/new ') + C.zinc400('Start fresh session') + '\n' +
|
|
743
|
+
C.emerald('/model <m> ') + C.zinc400('Switch model') + '\n' +
|
|
744
|
+
C.emerald('/clear ') + C.zinc400('Clear screen (keeps history)') + '\n' +
|
|
745
|
+
C.emerald('/exit ') + C.zinc400('Exit (auto-saves)'));
|
|
662
746
|
addSpacer();
|
|
663
747
|
return true;
|
|
664
748
|
}
|
|
@@ -746,6 +830,98 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
746
830
|
tui.requestRender();
|
|
747
831
|
return true;
|
|
748
832
|
}
|
|
833
|
+
case '/switch': {
|
|
834
|
+
const newId = parts[1]?.trim();
|
|
835
|
+
if (!newId) {
|
|
836
|
+
addSystemText(C.zinc400('Usage: /switch <thesisId>'));
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
addSpacer();
|
|
840
|
+
try {
|
|
841
|
+
// Save current session
|
|
842
|
+
persistSession();
|
|
843
|
+
// Load new thesis context
|
|
844
|
+
const newContext = await sfClient.getContext(newId);
|
|
845
|
+
resolvedThesisId = newContext.thesisId || newId;
|
|
846
|
+
latestContext = newContext;
|
|
847
|
+
// Build new system prompt
|
|
848
|
+
const newConf = typeof newContext.confidence === 'number'
|
|
849
|
+
? Math.round(newContext.confidence * 100) : 0;
|
|
850
|
+
const newSysPrompt = `You are a SimpleFunctions prediction market trading assistant.\n\nCurrent thesis: ${newContext.thesis || newContext.rawThesis || 'N/A'}\nConfidence: ${newConf}%\nStatus: ${newContext.status}\nThesis ID: ${resolvedThesisId}\n\nYou have six tools available. Use them when you need real-time data. Answer directly when you don't.\nBe concise. Use Chinese if the user writes in Chinese, English if they write in English.\nDo NOT make up data. Always call tools to get current state.`;
|
|
851
|
+
// Load saved session or start fresh
|
|
852
|
+
const saved = loadSession(resolvedThesisId);
|
|
853
|
+
if (saved?.messages?.length > 0) {
|
|
854
|
+
agent.replaceMessages(saved.messages);
|
|
855
|
+
agent.setSystemPrompt(newSysPrompt);
|
|
856
|
+
addSystemText(C.emerald(`Switched to ${resolvedThesisId.slice(0, 8)}`) + C.zinc400(` (resumed ${saved.messages.length} messages)`));
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
agent.clearMessages();
|
|
860
|
+
agent.setSystemPrompt(newSysPrompt);
|
|
861
|
+
addSystemText(C.emerald(`Switched to ${resolvedThesisId.slice(0, 8)}`) + C.zinc400(' (new session)'));
|
|
862
|
+
}
|
|
863
|
+
// Update header
|
|
864
|
+
headerBar.update(C.emerald(bold('SF Agent')) + C.zinc600(` \u2014 ${resolvedThesisId.slice(0, 8)}`), newConf > 0 ? C.zinc200(`${newConf}%`) : '', undefined);
|
|
865
|
+
chatContainer.clear();
|
|
866
|
+
const thText = (newContext.thesis || newContext.rawThesis || '').slice(0, 120);
|
|
867
|
+
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
868
|
+
C.zinc200(bold(thText)) + '\n' +
|
|
869
|
+
C.zinc600(`${newContext.status || 'active'} ${newConf > 0 ? newConf + '%' : ''} ${(newContext.edges || []).length} edges`) + '\n' +
|
|
870
|
+
C.zinc600('\u2500'.repeat(50)));
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
addSystemText(C.red(`Switch failed: ${err.message}`));
|
|
874
|
+
}
|
|
875
|
+
addSpacer();
|
|
876
|
+
tui.requestRender();
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
case '/compact': {
|
|
880
|
+
addSpacer();
|
|
881
|
+
const msgs = agent.state.messages;
|
|
882
|
+
if (msgs.length <= 10) {
|
|
883
|
+
addSystemText(C.zinc400('Conversation too short to compact'));
|
|
884
|
+
addSpacer();
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
// Keep recent 6 messages (3 turns) + create summary of the rest
|
|
888
|
+
const recentCount = 6;
|
|
889
|
+
const toCompress = msgs.slice(0, -recentCount);
|
|
890
|
+
const toKeep = msgs.slice(-recentCount);
|
|
891
|
+
// Extract text for summary (no LLM, just bullet points)
|
|
892
|
+
const bulletPoints = [];
|
|
893
|
+
for (const m of toCompress) {
|
|
894
|
+
const content = typeof m.content === 'string' ? m.content : '';
|
|
895
|
+
if (m.role === 'user' && content) {
|
|
896
|
+
bulletPoints.push(`- User: ${content.slice(0, 100)}`);
|
|
897
|
+
}
|
|
898
|
+
else if (m.role === 'assistant' && content) {
|
|
899
|
+
bulletPoints.push(`- Assistant: ${content.slice(0, 150)}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const summary = bulletPoints.slice(-20).join('\n');
|
|
903
|
+
// Replace messages: summary + recent
|
|
904
|
+
const compactedMessages = [
|
|
905
|
+
{ role: 'assistant', content: `[Conversation summary - ${toCompress.length} messages compressed]\n${summary}` },
|
|
906
|
+
...toKeep,
|
|
907
|
+
];
|
|
908
|
+
agent.replaceMessages(compactedMessages);
|
|
909
|
+
persistSession();
|
|
910
|
+
addSystemText(C.emerald(`Compacted: ${toCompress.length} messages \u2192 summary + ${toKeep.length} recent`));
|
|
911
|
+
addSpacer();
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
case '/new': {
|
|
915
|
+
addSpacer();
|
|
916
|
+
persistSession(); // save current before clearing
|
|
917
|
+
agent.clearMessages();
|
|
918
|
+
agent.setSystemPrompt(systemPrompt);
|
|
919
|
+
chatContainer.clear();
|
|
920
|
+
addSystemText(C.emerald('Session cleared') + C.zinc400(' \u2014 fresh start'));
|
|
921
|
+
addSpacer();
|
|
922
|
+
tui.requestRender();
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
749
925
|
case '/clear': {
|
|
750
926
|
chatContainer.clear();
|
|
751
927
|
tui.requestRender();
|
|
@@ -801,6 +977,7 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
801
977
|
function cleanup() {
|
|
802
978
|
if (currentLoader)
|
|
803
979
|
currentLoader.stop();
|
|
980
|
+
persistSession();
|
|
804
981
|
tui.stop();
|
|
805
982
|
process.exit(0);
|
|
806
983
|
}
|
|
@@ -819,9 +996,12 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
819
996
|
// ── Show initial welcome ───────────────────────────────────────────────────
|
|
820
997
|
const thesisText = latestContext.thesis || latestContext.rawThesis || 'N/A';
|
|
821
998
|
const truncatedThesis = thesisText.length > 120 ? thesisText.slice(0, 120) + '...' : thesisText;
|
|
999
|
+
const sessionStatus = sessionRestored
|
|
1000
|
+
? C.zinc600(` resumed (${agent.state.messages.length} messages)`)
|
|
1001
|
+
: C.zinc600(' new session');
|
|
822
1002
|
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
823
1003
|
C.zinc200(bold(truncatedThesis)) + '\n' +
|
|
824
|
-
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + '\n' +
|
|
1004
|
+
C.zinc600(`${latestContext.status || 'active'} ${confidencePct > 0 ? confidencePct + '%' : ''} ${(latestContext.edges || []).length} edges`) + sessionStatus + '\n' +
|
|
825
1005
|
C.zinc600('\u2500'.repeat(50)));
|
|
826
1006
|
addSpacer();
|
|
827
1007
|
// ── Start TUI ──────────────────────────────────────────────────────────────
|
package/dist/index.js
CHANGED
|
@@ -135,11 +135,12 @@ program
|
|
|
135
135
|
program
|
|
136
136
|
.command('agent [thesisId]')
|
|
137
137
|
.description('Interactive agent mode — natural language interface to SimpleFunctions')
|
|
138
|
-
.option('--model <model>', 'Model via OpenRouter (default: anthropic/claude-sonnet-4
|
|
138
|
+
.option('--model <model>', 'Model via OpenRouter (default: anthropic/claude-sonnet-4.6)')
|
|
139
139
|
.option('--model-key <key>', 'OpenRouter API key (or set OPENROUTER_API_KEY)')
|
|
140
|
+
.option('--new', 'Start a fresh session (default: continue last session)')
|
|
140
141
|
.action(async (thesisId, opts, cmd) => {
|
|
141
142
|
const g = cmd.optsWithGlobals();
|
|
142
|
-
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey }));
|
|
143
|
+
await run(() => (0, agent_js_1.agentCommand)(thesisId, { model: opts.model, modelKey: opts.modelKey, newSession: opts.new }));
|
|
143
144
|
});
|
|
144
145
|
// ── Error wrapper ─────────────────────────────────────────────────────────────
|
|
145
146
|
async function run(fn) {
|