@jellyos/agent 0.1.3 → 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.
Files changed (90) hide show
  1. package/README.md +9 -9
  2. package/README.npm.md +212 -0
  3. package/bin/jellyos-mcp +26 -0
  4. package/dist/api/ExtensionAPI.d.ts +6 -0
  5. package/dist/api/Registry.js +3 -1
  6. package/dist/cli.js +117 -42
  7. package/dist/index.d.ts +24 -1
  8. package/dist/index.js +19 -2
  9. package/dist/mcp/entry.d.ts +2 -0
  10. package/dist/mcp/entry.js +71 -0
  11. package/dist/mcp/server.d.ts +31 -0
  12. package/dist/mcp/server.js +128 -0
  13. package/dist/models/CostTracker.d.ts +66 -0
  14. package/dist/models/CostTracker.js +148 -0
  15. package/dist/models/ModelRegistry.d.ts +157 -0
  16. package/dist/models/ModelRegistry.js +496 -0
  17. package/dist/models/index.d.ts +5 -0
  18. package/dist/models/index.js +3 -0
  19. package/dist/runner/AgentRunner.d.ts +23 -2
  20. package/dist/runner/AgentRunner.js +264 -24
  21. package/dist/runner/ModelClient.d.ts +26 -6
  22. package/dist/runner/ModelClient.js +147 -28
  23. package/dist/runner/SwarmRouter.d.ts +10 -7
  24. package/dist/runner/SwarmRouter.js +85 -28
  25. package/dist/runner/ToolDispatcher.d.ts +10 -0
  26. package/dist/runner/ToolDispatcher.js +106 -2
  27. package/dist/scheduler/AgentScheduler.d.ts +118 -0
  28. package/dist/scheduler/AgentScheduler.js +253 -0
  29. package/dist/session/ContextStore.d.ts +96 -0
  30. package/dist/session/ContextStore.js +207 -0
  31. package/dist/session/GoalManager.d.ts +101 -0
  32. package/dist/session/GoalManager.js +167 -0
  33. package/dist/session/MemoryStore.d.ts +48 -0
  34. package/dist/session/MemoryStore.js +166 -0
  35. package/dist/session/SessionManager.d.ts +45 -4
  36. package/dist/session/SessionManager.js +151 -8
  37. package/dist/telemetry/Tracer.d.ts +48 -0
  38. package/dist/telemetry/Tracer.js +102 -0
  39. package/dist/tests/ContextStore.test.d.ts +2 -0
  40. package/dist/tests/ContextStore.test.js +74 -0
  41. package/dist/tests/ModelRegistry.test.d.ts +2 -0
  42. package/dist/tests/ModelRegistry.test.js +69 -0
  43. package/dist/tests/SessionManager.test.d.ts +2 -0
  44. package/dist/tests/SessionManager.test.js +108 -0
  45. package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
  46. package/dist/tests/TechnicalAnalysis.test.js +109 -0
  47. package/dist/tools/MarketSentiment.d.ts +166 -0
  48. package/dist/tools/MarketSentiment.js +209 -0
  49. package/dist/tools/NewsSentiment.d.ts +67 -0
  50. package/dist/tools/NewsSentiment.js +226 -0
  51. package/dist/tools/PriceFeed.d.ts +105 -0
  52. package/dist/tools/PriceFeed.js +282 -0
  53. package/dist/tools/TechnicalAnalysis.d.ts +110 -0
  54. package/dist/tools/TechnicalAnalysis.js +357 -0
  55. package/dist/tools/index.d.ts +7 -0
  56. package/dist/tools/index.js +4 -0
  57. package/dist/tui/App.d.ts +7 -5
  58. package/dist/tui/App.js +350 -65
  59. package/dist/tui/REPL.d.ts +2 -1
  60. package/dist/tui/REPL.js +11 -6
  61. package/dist/tui/StatusBar.js +1 -1
  62. package/package.json +9 -4
  63. package/dist/api/ExtensionAPI.d.ts.map +0 -1
  64. package/dist/api/ExtensionAPI.js.map +0 -1
  65. package/dist/api/Registry.d.ts.map +0 -1
  66. package/dist/api/Registry.js.map +0 -1
  67. package/dist/cli.d.ts.map +0 -1
  68. package/dist/cli.js.map +0 -1
  69. package/dist/index.d.ts.map +0 -1
  70. package/dist/index.js.map +0 -1
  71. package/dist/loader.d.ts.map +0 -1
  72. package/dist/loader.js.map +0 -1
  73. package/dist/runner/AgentRunner.d.ts.map +0 -1
  74. package/dist/runner/AgentRunner.js.map +0 -1
  75. package/dist/runner/ModelClient.d.ts.map +0 -1
  76. package/dist/runner/ModelClient.js.map +0 -1
  77. package/dist/runner/SwarmRouter.d.ts.map +0 -1
  78. package/dist/runner/SwarmRouter.js.map +0 -1
  79. package/dist/runner/ToolDispatcher.d.ts.map +0 -1
  80. package/dist/runner/ToolDispatcher.js.map +0 -1
  81. package/dist/session/SessionManager.d.ts.map +0 -1
  82. package/dist/session/SessionManager.js.map +0 -1
  83. package/dist/tui/App.d.ts.map +0 -1
  84. package/dist/tui/App.js.map +0 -1
  85. package/dist/tui/REPL.d.ts.map +0 -1
  86. package/dist/tui/REPL.js.map +0 -1
  87. package/dist/tui/StatusBar.d.ts.map +0 -1
  88. package/dist/tui/StatusBar.js.map +0 -1
  89. package/dist/tui/theme.d.ts.map +0 -1
  90. package/dist/tui/theme.js.map +0 -1
@@ -0,0 +1,101 @@
1
+ /**
2
+ * GoalManager — cross-session persistent goal tracking. (#12)
3
+ *
4
+ * Goals set in one session survive restarts and are injected into the
5
+ * system prompt on every subsequent session. This gives the agent
6
+ * continuity — it knows what it was asked to watch or do.
7
+ *
8
+ * Stored in ~/.jelly/goals.json
9
+ * Tools: set_goal, complete_goal, list_goals, update_goal_notes
10
+ * Commands: /goals, /goal add <text>, /goal done <id>
11
+ */
12
+ import { type Static } from "@sinclair/typebox";
13
+ export interface Goal {
14
+ id: string;
15
+ text: string;
16
+ createdAt: number;
17
+ updatedAt: number;
18
+ status: "active" | "completed" | "paused";
19
+ notes: string[];
20
+ }
21
+ export declare class GoalManager {
22
+ private goals;
23
+ constructor();
24
+ private load;
25
+ private save;
26
+ add(text: string): Goal;
27
+ complete(id: string): boolean;
28
+ pause(id: string): boolean;
29
+ addNote(id: string, note: string): boolean;
30
+ getActive(): Goal[];
31
+ getAll(): Goal[];
32
+ get(id: string): Goal | undefined;
33
+ /** Build an active goals block for injection into the system prompt */
34
+ buildContextBlock(): string;
35
+ readonly setGoalParams: import("@sinclair/typebox").TObject<{
36
+ text: import("@sinclair/typebox").TString;
37
+ }>;
38
+ setGoalTool(_id: string, params: Static<typeof this.setGoalParams>): Promise<{
39
+ content: {
40
+ type: "text";
41
+ text: string;
42
+ }[];
43
+ details: {
44
+ goalId: string;
45
+ text: string;
46
+ };
47
+ }>;
48
+ readonly completeGoalParams: import("@sinclair/typebox").TObject<{
49
+ id: import("@sinclair/typebox").TString;
50
+ note: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
51
+ }>;
52
+ completeGoalTool(_id: string, params: Static<typeof this.completeGoalParams>): Promise<{
53
+ content: {
54
+ type: "text";
55
+ text: string;
56
+ }[];
57
+ details: {
58
+ goalId: string;
59
+ success: boolean;
60
+ };
61
+ }>;
62
+ readonly listGoalsParams: import("@sinclair/typebox").TObject<{
63
+ status: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
64
+ }>;
65
+ listGoalsTool(_id: string, params: Static<typeof this.listGoalsParams>): Promise<{
66
+ content: {
67
+ type: "text";
68
+ text: string;
69
+ }[];
70
+ details: {
71
+ count?: undefined;
72
+ goals?: undefined;
73
+ };
74
+ } | {
75
+ content: {
76
+ type: "text";
77
+ text: string;
78
+ }[];
79
+ details: {
80
+ count: number;
81
+ goals: Goal[];
82
+ };
83
+ }>;
84
+ readonly addGoalNoteParams: import("@sinclair/typebox").TObject<{
85
+ id: import("@sinclair/typebox").TString;
86
+ note: import("@sinclair/typebox").TString;
87
+ }>;
88
+ addGoalNoteTool(_id: string, params: Static<typeof this.addGoalNoteParams>): Promise<{
89
+ content: {
90
+ type: "text";
91
+ text: string;
92
+ }[];
93
+ details: {
94
+ goalId: string;
95
+ success: boolean;
96
+ };
97
+ }>;
98
+ }
99
+ /** Singleton */
100
+ export declare const goalManager: GoalManager;
101
+ //# sourceMappingURL=GoalManager.d.ts.map
@@ -0,0 +1,167 @@
1
+ /**
2
+ * GoalManager — cross-session persistent goal tracking. (#12)
3
+ *
4
+ * Goals set in one session survive restarts and are injected into the
5
+ * system prompt on every subsequent session. This gives the agent
6
+ * continuity — it knows what it was asked to watch or do.
7
+ *
8
+ * Stored in ~/.jelly/goals.json
9
+ * Tools: set_goal, complete_goal, list_goals, update_goal_notes
10
+ * Commands: /goals, /goal add <text>, /goal done <id>
11
+ */
12
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { randomUUID } from "node:crypto";
16
+ import { Type } from "@sinclair/typebox";
17
+ const JELLY_HOME = process.env.JELLYOS_HOME ?? join(homedir(), ".jelly");
18
+ const GOALS_FILE = join(JELLY_HOME, "goals.json");
19
+ export class GoalManager {
20
+ goals = [];
21
+ constructor() {
22
+ this.load();
23
+ }
24
+ // ── Persistence ────────────────────────────────────────────────────────────
25
+ load() {
26
+ try {
27
+ if (!existsSync(GOALS_FILE))
28
+ return;
29
+ const raw = JSON.parse(readFileSync(GOALS_FILE, "utf-8"));
30
+ if (Array.isArray(raw))
31
+ this.goals = raw;
32
+ }
33
+ catch { /* start fresh */ }
34
+ }
35
+ save() {
36
+ try {
37
+ mkdirSync(JELLY_HOME, { recursive: true });
38
+ writeFileSync(GOALS_FILE, JSON.stringify(this.goals, null, 2), "utf-8");
39
+ }
40
+ catch { /* best effort */ }
41
+ }
42
+ // ── CRUD ───────────────────────────────────────────────────────────────────
43
+ add(text) {
44
+ const goal = {
45
+ id: randomUUID().slice(0, 8),
46
+ text: text.trim().slice(0, 500),
47
+ createdAt: Date.now(),
48
+ updatedAt: Date.now(),
49
+ status: "active",
50
+ notes: [],
51
+ };
52
+ this.goals.push(goal);
53
+ this.save();
54
+ return goal;
55
+ }
56
+ complete(id) {
57
+ const goal = this.goals.find(g => g.id === id);
58
+ if (!goal)
59
+ return false;
60
+ goal.status = "completed";
61
+ goal.updatedAt = Date.now();
62
+ this.save();
63
+ return true;
64
+ }
65
+ pause(id) {
66
+ const goal = this.goals.find(g => g.id === id);
67
+ if (!goal)
68
+ return false;
69
+ goal.status = "paused";
70
+ goal.updatedAt = Date.now();
71
+ this.save();
72
+ return true;
73
+ }
74
+ addNote(id, note) {
75
+ const goal = this.goals.find(g => g.id === id);
76
+ if (!goal)
77
+ return false;
78
+ goal.notes.push(`[${new Date().toLocaleDateString()}] ${note.slice(0, 300)}`);
79
+ goal.updatedAt = Date.now();
80
+ if (goal.notes.length > 20)
81
+ goal.notes = goal.notes.slice(-20); // keep last 20
82
+ this.save();
83
+ return true;
84
+ }
85
+ getActive() {
86
+ return this.goals.filter(g => g.status === "active");
87
+ }
88
+ getAll() {
89
+ return [...this.goals];
90
+ }
91
+ get(id) {
92
+ return this.goals.find(g => g.id === id);
93
+ }
94
+ // ── System Prompt Block ────────────────────────────────────────────────────
95
+ /** Build an active goals block for injection into the system prompt */
96
+ buildContextBlock() {
97
+ const active = this.getActive();
98
+ if (active.length === 0)
99
+ return "";
100
+ const lines = ["\n## Active Goals (set by user — monitor and act on these)"];
101
+ for (const g of active) {
102
+ lines.push(`- [${g.id}] ${g.text}`);
103
+ if (g.notes.length > 0) {
104
+ lines.push(` Last note: ${g.notes[g.notes.length - 1]}`);
105
+ }
106
+ }
107
+ lines.push("");
108
+ return lines.join("\n");
109
+ }
110
+ // ── Tools ──────────────────────────────────────────────────────────────────
111
+ setGoalParams = Type.Object({
112
+ text: Type.String({ description: "Goal text e.g. 'Watch ETH for breakout above $4000'" }),
113
+ });
114
+ async setGoalTool(_id, params) {
115
+ const goal = this.add(params.text);
116
+ return {
117
+ content: [{ type: "text", text: `Goal set: [${goal.id}] ${goal.text}` }],
118
+ details: { goalId: goal.id, text: goal.text },
119
+ };
120
+ }
121
+ completeGoalParams = Type.Object({
122
+ id: Type.String({ description: "Goal ID to mark complete" }),
123
+ note: Type.Optional(Type.String({ description: "Completion note" })),
124
+ });
125
+ async completeGoalTool(_id, params) {
126
+ if (params.note)
127
+ this.addNote(params.id, params.note);
128
+ const ok = this.complete(params.id);
129
+ return {
130
+ content: [{ type: "text", text: ok ? `Goal ${params.id} marked complete.` : `Goal ${params.id} not found.` }],
131
+ details: { goalId: params.id, success: ok },
132
+ };
133
+ }
134
+ listGoalsParams = Type.Object({
135
+ status: Type.Optional(Type.String({ description: "Filter: active | completed | paused | all (default: active)" })),
136
+ });
137
+ async listGoalsTool(_id, params) {
138
+ const filter = params.status ?? "active";
139
+ const goals = filter === "all" ? this.getAll() : this.goals.filter(g => g.status === filter);
140
+ if (goals.length === 0) {
141
+ return { content: [{ type: "text", text: `No ${filter} goals.` }], details: {} };
142
+ }
143
+ const lines = goals.map(g => {
144
+ const icon = g.status === "active" ? "🎯" : g.status === "completed" ? "✅" : "⏸";
145
+ const notes = g.notes.length > 0 ? `\n Notes: ${g.notes[g.notes.length - 1]}` : "";
146
+ return `${icon} [${g.id}] ${g.text}${notes}`;
147
+ });
148
+ return {
149
+ content: [{ type: "text", text: `Goals (${filter}):\n${lines.join("\n")}` }],
150
+ details: { count: goals.length, goals },
151
+ };
152
+ }
153
+ addGoalNoteParams = Type.Object({
154
+ id: Type.String({ description: "Goal ID" }),
155
+ note: Type.String({ description: "Progress note to add" }),
156
+ });
157
+ async addGoalNoteTool(_id, params) {
158
+ const ok = this.addNote(params.id, params.note);
159
+ return {
160
+ content: [{ type: "text", text: ok ? `Note added to goal ${params.id}.` : `Goal ${params.id} not found.` }],
161
+ details: { goalId: params.id, success: ok },
162
+ };
163
+ }
164
+ }
165
+ /** Singleton */
166
+ export const goalManager = new GoalManager();
167
+ //# sourceMappingURL=GoalManager.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * MemoryStore — persistent long-term memory using Node 22+ built-in SQLite.
3
+ * (#7 — cross-session memory)
4
+ *
5
+ * Stores all conversation messages, searchable by keyword and session.
6
+ * Injected into system prompt at session_start to give the agent awareness
7
+ * of past decisions, prices seen, strategies discussed.
8
+ *
9
+ * Uses node:sqlite (experimental in Node 22, stable in Node 24) — zero deps.
10
+ */
11
+ export interface MemoryEntry {
12
+ id: number;
13
+ sessionId: string;
14
+ role: string;
15
+ content: string;
16
+ tokens: number;
17
+ ts: number;
18
+ tags: string[];
19
+ }
20
+ export interface RecentSession {
21
+ sessionId: string;
22
+ summary: string;
23
+ msgCount: number;
24
+ ts: number;
25
+ }
26
+ export declare class MemoryStore {
27
+ private db;
28
+ private available;
29
+ constructor();
30
+ private init;
31
+ get isAvailable(): boolean;
32
+ save(sessionId: string, role: string, content: string, tags?: string[]): void;
33
+ /** Keyword search across all sessions */
34
+ search(query: string, limit?: number): MemoryEntry[];
35
+ /** Get summaries of recent sessions (for system prompt injection) */
36
+ getRecentSessions(limit?: number): RecentSession[];
37
+ /** Get all messages for a specific session */
38
+ getSession(sessionId: string): MemoryEntry[];
39
+ /** Build a short memory context block for system prompt injection */
40
+ buildContextBlock(currentSessionId: string): string;
41
+ getStats(): {
42
+ totalMessages: number;
43
+ totalSessions: number;
44
+ };
45
+ }
46
+ /** Singleton */
47
+ export declare const memoryStore: MemoryStore;
48
+ //# sourceMappingURL=MemoryStore.d.ts.map
@@ -0,0 +1,166 @@
1
+ /**
2
+ * MemoryStore — persistent long-term memory using Node 22+ built-in SQLite.
3
+ * (#7 — cross-session memory)
4
+ *
5
+ * Stores all conversation messages, searchable by keyword and session.
6
+ * Injected into system prompt at session_start to give the agent awareness
7
+ * of past decisions, prices seen, strategies discussed.
8
+ *
9
+ * Uses node:sqlite (experimental in Node 22, stable in Node 24) — zero deps.
10
+ */
11
+ import { mkdirSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ const JELLY_HOME = process.env.JELLYOS_HOME ?? join(homedir(), ".jelly");
15
+ export class MemoryStore {
16
+ db = null;
17
+ available = false;
18
+ constructor() {
19
+ this.init();
20
+ }
21
+ init() {
22
+ try {
23
+ // node:sqlite is available in Node 22+ (experimental) and Node 24 (stable)
24
+ // We import it dynamically to avoid a hard crash on older Node versions
25
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
26
+ const sqlite = require("node:sqlite");
27
+ mkdirSync(JELLY_HOME, { recursive: true });
28
+ this.db = new sqlite.DatabaseSync(join(JELLY_HOME, "memory.db"));
29
+ this.db.exec(`
30
+ CREATE TABLE IF NOT EXISTS messages (
31
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
32
+ sessionId TEXT NOT NULL,
33
+ role TEXT NOT NULL,
34
+ content TEXT NOT NULL,
35
+ tokens INTEGER DEFAULT 0,
36
+ ts INTEGER NOT NULL,
37
+ tags TEXT DEFAULT '[]'
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_session ON messages(sessionId);
40
+ CREATE INDEX IF NOT EXISTS idx_ts ON messages(ts);
41
+ -- Prune old entries automatically (keep last 10k messages)
42
+ CREATE TRIGGER IF NOT EXISTS prune_old_messages
43
+ AFTER INSERT ON messages
44
+ WHEN (SELECT COUNT(*) FROM messages) > 10000
45
+ BEGIN
46
+ DELETE FROM messages WHERE id IN (
47
+ SELECT id FROM messages ORDER BY ts ASC LIMIT 500
48
+ );
49
+ END;
50
+ `);
51
+ this.available = true;
52
+ }
53
+ catch {
54
+ // SQLite not available (Node <22) — memory store degrades gracefully
55
+ this.available = false;
56
+ }
57
+ }
58
+ get isAvailable() { return this.available; }
59
+ // ── Write ──────────────────────────────────────────────────────────────────
60
+ save(sessionId, role, content, tags = []) {
61
+ if (!this.available)
62
+ return;
63
+ try {
64
+ const tokens = Math.ceil(content.length / 4);
65
+ // Cap content at 2KB per entry to keep DB lean
66
+ const capped = content.length > 2000 ? content.slice(0, 2000) + "…" : content;
67
+ this.db.prepare(`INSERT INTO messages (sessionId, role, content, tokens, ts, tags)
68
+ VALUES (?, ?, ?, ?, ?, ?)`).run(sessionId, role, capped, tokens, Date.now(), JSON.stringify(tags));
69
+ }
70
+ catch { /* best effort */ }
71
+ }
72
+ // ── Read ───────────────────────────────────────────────────────────────────
73
+ /** Keyword search across all sessions */
74
+ search(query, limit = 8) {
75
+ if (!this.available)
76
+ return [];
77
+ try {
78
+ const rows = this.db.prepare(`SELECT * FROM messages WHERE content LIKE ? ORDER BY ts DESC LIMIT ?`).all(`%${query}%`, limit);
79
+ return rows.map((r) => ({
80
+ ...r,
81
+ tags: JSON.parse(r["tags"] ?? "[]"),
82
+ }));
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ /** Get summaries of recent sessions (for system prompt injection) */
89
+ getRecentSessions(limit = 5) {
90
+ if (!this.available)
91
+ return [];
92
+ try {
93
+ const rows = this.db.prepare(`
94
+ SELECT
95
+ sessionId,
96
+ COUNT(*) as msgCount,
97
+ MAX(ts) as ts,
98
+ GROUP_CONCAT(
99
+ CASE WHEN role = 'user' THEN SUBSTR(content, 1, 120)
100
+ WHEN role = 'assistant' THEN SUBSTR(content, 1, 120)
101
+ END,
102
+ ' | '
103
+ ) as summary
104
+ FROM messages
105
+ GROUP BY sessionId
106
+ ORDER BY ts DESC
107
+ LIMIT ?
108
+ `).all(limit);
109
+ return rows.map((r) => ({
110
+ sessionId: r["sessionId"],
111
+ summary: (r["summary"] ?? "").slice(0, 400),
112
+ msgCount: r["msgCount"],
113
+ ts: r["ts"],
114
+ }));
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ }
120
+ /** Get all messages for a specific session */
121
+ getSession(sessionId) {
122
+ if (!this.available)
123
+ return [];
124
+ try {
125
+ const rows = this.db.prepare(`SELECT * FROM messages WHERE sessionId = ? ORDER BY ts ASC`).all(sessionId);
126
+ return rows.map((r) => ({
127
+ ...r,
128
+ tags: JSON.parse(r["tags"] ?? "[]"),
129
+ }));
130
+ }
131
+ catch {
132
+ return [];
133
+ }
134
+ }
135
+ /** Build a short memory context block for system prompt injection */
136
+ buildContextBlock(currentSessionId) {
137
+ if (!this.available)
138
+ return "";
139
+ const recent = this.getRecentSessions(3).filter(s => s.sessionId !== currentSessionId);
140
+ if (recent.length === 0)
141
+ return "";
142
+ const lines = ["\n## Memory from Past Sessions"];
143
+ for (const s of recent) {
144
+ const ago = Math.round((Date.now() - s.ts) / 60_000);
145
+ const agoStr = ago < 60 ? `${ago}m ago` : ago < 1440 ? `${Math.round(ago / 60)}h ago` : `${Math.round(ago / 1440)}d ago`;
146
+ lines.push(`- [${agoStr}] ${s.summary.slice(0, 200)}`);
147
+ }
148
+ lines.push("");
149
+ return lines.join("\n");
150
+ }
151
+ getStats() {
152
+ if (!this.available)
153
+ return { totalMessages: 0, totalSessions: 0 };
154
+ try {
155
+ const { count } = this.db.prepare(`SELECT COUNT(*) as count FROM messages`).get();
156
+ const { sessions } = this.db.prepare(`SELECT COUNT(DISTINCT sessionId) as sessions FROM messages`).get();
157
+ return { totalMessages: count, totalSessions: sessions };
158
+ }
159
+ catch {
160
+ return { totalMessages: 0, totalSessions: 0 };
161
+ }
162
+ }
163
+ }
164
+ /** Singleton */
165
+ export const memoryStore = new MemoryStore();
166
+ //# sourceMappingURL=MemoryStore.js.map
@@ -1,10 +1,22 @@
1
1
  /**
2
2
  * SessionManager — manages conversation history, compaction, and persistence.
3
3
  *
4
- * History is kept in memory. If it exceeds MAX_TOKENS worth of rough char count,
5
- * it compacts by summarizing the oldest messages (keeps system + last N turns).
4
+ * Compaction is done in atomic turn pairs so tool_call_id references are
5
+ * never orphaned (which causes 400 errors from every provider).
6
+ *
7
+ * Three compaction tiers:
8
+ * Tier 1 (>70%): trim long tool results to 1-line summaries
9
+ * Tier 2 (>85%): drop oldest complete turns (atomic pairs only)
10
+ * Tier 3 (>95%): hard reset keeping only last KEEP_TURNS turns
6
11
  */
7
12
  import type { Message } from "../runner/ModelClient.js";
13
+ /** Context pressure levels for UI display and swarm gating */
14
+ export interface ContextPressure {
15
+ chars: number;
16
+ pct: number;
17
+ level: "green" | "yellow" | "red" | "critical";
18
+ turboReady: boolean;
19
+ }
8
20
  export declare class SessionManager {
9
21
  private history;
10
22
  private systemPrompt;
@@ -14,10 +26,39 @@ export declare class SessionManager {
14
26
  addMessages(msgs: Message[]): void;
15
27
  /** Returns messages ready to send to the model — system prompt prepended */
16
28
  getMessages(): Message[];
17
- /** Full raw history (no system) */
29
+ /** Full raw history (no system prompt) */
18
30
  getHistory(): Message[];
19
31
  clear(): void;
32
+ charCount(): number;
33
+ getContextPressure(): ContextPressure;
20
34
  private buildSystemContent;
21
- private maybeCompact;
35
+ /**
36
+ * Split history into atomic turns.
37
+ * A turn = [user_msg, assistant_msg, ...tool_result_msgs]
38
+ * Keeping turns atomic prevents orphaned tool_call_id errors.
39
+ */
40
+ private splitIntoTurns;
41
+ /**
42
+ * Tier 1: Trim long tool results in-place. No messages dropped.
43
+ * Replaces content >TOOL_RESULT_CAP chars with a 1-line summary.
44
+ */
45
+ private trimToolResults;
46
+ /**
47
+ * Tier 2: Drop oldest turns, but ANCHOR high-value messages (prices, signals,
48
+ * decisions) so they survive compaction. (#36 semantic anchor compaction)
49
+ */
50
+ private dropOldTurns;
51
+ /** #36: Score a message's semantic importance (0-10) */
52
+ private messageWeight;
53
+ maybeCompact(): void;
54
+ /** Force compaction */
55
+ forceCompact(): void;
56
+ /**
57
+ * #32: Tier-2 summarization via a cheap model.
58
+ * Compresses the oldest 40% of turns into a single summary message.
59
+ * Call this when pct is between 70-90 to reclaim space gracefully.
60
+ * @param summarizeCallback — provided by AgentRunner which has model access
61
+ */
62
+ summarizeOldTurns(summarizeCallback: (messages: Message[]) => Promise<string>): Promise<void>;
22
63
  }
23
64
  //# sourceMappingURL=SessionManager.d.ts.map