@spfunctions/cli 0.1.7 → 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 +171 -11
- 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`;
|
|
@@ -368,9 +401,12 @@ async function agentCommand(thesisId, opts) {
|
|
|
368
401
|
{ name: 'edges', description: 'Display edge/spread table' },
|
|
369
402
|
{ name: 'pos', description: 'Display Kalshi positions' },
|
|
370
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' },
|
|
371
407
|
{ name: 'model', description: 'Switch model (e.g. /model anthropic/claude-sonnet-4)' },
|
|
372
|
-
{ name: 'clear', description: 'Clear
|
|
373
|
-
{ name: 'exit', description: 'Exit agent' },
|
|
408
|
+
{ name: 'clear', description: 'Clear screen (keeps history)' },
|
|
409
|
+
{ name: 'exit', description: 'Exit agent (auto-saves)' },
|
|
374
410
|
], process.cwd());
|
|
375
411
|
editor.setAutocompleteProvider(autocompleteProvider);
|
|
376
412
|
// Assemble TUI tree
|
|
@@ -561,6 +597,30 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
561
597
|
return undefined;
|
|
562
598
|
},
|
|
563
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
|
+
}
|
|
564
624
|
// ── Subscribe to agent events → update TUI ────────────────────────────────
|
|
565
625
|
let currentAssistantMd = null;
|
|
566
626
|
let currentAssistantText = '';
|
|
@@ -633,6 +693,7 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
633
693
|
if (event.type === 'agent_end') {
|
|
634
694
|
// Agent turn fully complete — safe to accept new input
|
|
635
695
|
isProcessing = false;
|
|
696
|
+
persistSession();
|
|
636
697
|
flushRender();
|
|
637
698
|
}
|
|
638
699
|
if (event.type === 'tool_execution_start') {
|
|
@@ -671,14 +732,17 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
671
732
|
case '/help': {
|
|
672
733
|
addSpacer();
|
|
673
734
|
addSystemText(C.zinc200(bold('Commands')) + '\n' +
|
|
674
|
-
C.emerald('/help
|
|
675
|
-
C.emerald('/tree
|
|
676
|
-
C.emerald('/edges
|
|
677
|
-
C.emerald('/pos
|
|
678
|
-
C.emerald('/eval
|
|
679
|
-
C.emerald('/
|
|
680
|
-
C.emerald('/
|
|
681
|
-
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)'));
|
|
682
746
|
addSpacer();
|
|
683
747
|
return true;
|
|
684
748
|
}
|
|
@@ -766,6 +830,98 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
766
830
|
tui.requestRender();
|
|
767
831
|
return true;
|
|
768
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
|
+
}
|
|
769
925
|
case '/clear': {
|
|
770
926
|
chatContainer.clear();
|
|
771
927
|
tui.requestRender();
|
|
@@ -821,6 +977,7 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
821
977
|
function cleanup() {
|
|
822
978
|
if (currentLoader)
|
|
823
979
|
currentLoader.stop();
|
|
980
|
+
persistSession();
|
|
824
981
|
tui.stop();
|
|
825
982
|
process.exit(0);
|
|
826
983
|
}
|
|
@@ -839,9 +996,12 @@ Do NOT make up data. Always call tools to get current state.`;
|
|
|
839
996
|
// ── Show initial welcome ───────────────────────────────────────────────────
|
|
840
997
|
const thesisText = latestContext.thesis || latestContext.rawThesis || 'N/A';
|
|
841
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');
|
|
842
1002
|
addSystemText(C.zinc600('\u2500'.repeat(50)) + '\n' +
|
|
843
1003
|
C.zinc200(bold(truncatedThesis)) + '\n' +
|
|
844
|
-
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' +
|
|
845
1005
|
C.zinc600('\u2500'.repeat(50)));
|
|
846
1006
|
addSpacer();
|
|
847
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) {
|