@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.
Files changed (94) hide show
  1. package/README.npm.md +212 -0
  2. package/bin/jellyos-mcp +26 -0
  3. package/dist/api/ExtensionAPI.d.ts +6 -0
  4. package/dist/cli.js +114 -48
  5. package/dist/index.d.ts +15 -2
  6. package/dist/index.js +13 -3
  7. package/dist/mcp/entry.d.ts +2 -0
  8. package/dist/mcp/entry.js +71 -0
  9. package/dist/mcp/server.d.ts +31 -0
  10. package/dist/mcp/server.js +128 -0
  11. package/dist/models/ModelRegistry.d.ts +12 -1
  12. package/dist/models/ModelRegistry.js +105 -9
  13. package/dist/runner/AgentRunner.d.ts +19 -2
  14. package/dist/runner/AgentRunner.js +247 -17
  15. package/dist/runner/ModelClient.d.ts +10 -1
  16. package/dist/runner/ModelClient.js +79 -6
  17. package/dist/runner/SwarmRouter.d.ts +6 -6
  18. package/dist/runner/SwarmRouter.js +73 -24
  19. package/dist/runner/ToolDispatcher.d.ts +10 -0
  20. package/dist/runner/ToolDispatcher.js +106 -2
  21. package/dist/scheduler/AgentScheduler.d.ts +118 -0
  22. package/dist/scheduler/AgentScheduler.js +253 -0
  23. package/dist/session/ContextStore.d.ts +96 -0
  24. package/dist/session/ContextStore.js +207 -0
  25. package/dist/session/GoalManager.d.ts +101 -0
  26. package/dist/session/GoalManager.js +167 -0
  27. package/dist/session/MemoryStore.d.ts +48 -0
  28. package/dist/session/MemoryStore.js +166 -0
  29. package/dist/session/SessionManager.d.ts +45 -4
  30. package/dist/session/SessionManager.js +151 -8
  31. package/dist/telemetry/Tracer.d.ts +48 -0
  32. package/dist/telemetry/Tracer.js +102 -0
  33. package/dist/tests/ContextStore.test.d.ts +2 -0
  34. package/dist/tests/ContextStore.test.js +74 -0
  35. package/dist/tests/ModelRegistry.test.d.ts +2 -0
  36. package/dist/tests/ModelRegistry.test.js +69 -0
  37. package/dist/tests/SessionManager.test.d.ts +2 -0
  38. package/dist/tests/SessionManager.test.js +108 -0
  39. package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
  40. package/dist/tests/TechnicalAnalysis.test.js +109 -0
  41. package/dist/tools/MarketSentiment.d.ts +166 -0
  42. package/dist/tools/MarketSentiment.js +209 -0
  43. package/dist/tools/NewsSentiment.js +40 -13
  44. package/dist/tools/PriceFeed.d.ts +2 -0
  45. package/dist/tools/PriceFeed.js +79 -27
  46. package/dist/tools/TechnicalAnalysis.d.ts +37 -0
  47. package/dist/tools/TechnicalAnalysis.js +85 -0
  48. package/dist/tui/App.d.ts +2 -2
  49. package/dist/tui/App.js +280 -117
  50. package/dist/tui/REPL.d.ts +2 -1
  51. package/dist/tui/REPL.js +11 -6
  52. package/package.json +9 -4
  53. package/dist/api/ExtensionAPI.d.ts.map +0 -1
  54. package/dist/api/ExtensionAPI.js.map +0 -1
  55. package/dist/api/Registry.d.ts.map +0 -1
  56. package/dist/api/Registry.js.map +0 -1
  57. package/dist/cli.d.ts.map +0 -1
  58. package/dist/cli.js.map +0 -1
  59. package/dist/index.d.ts.map +0 -1
  60. package/dist/index.js.map +0 -1
  61. package/dist/loader.d.ts.map +0 -1
  62. package/dist/loader.js.map +0 -1
  63. package/dist/models/CostTracker.d.ts.map +0 -1
  64. package/dist/models/CostTracker.js.map +0 -1
  65. package/dist/models/ModelRegistry.d.ts.map +0 -1
  66. package/dist/models/ModelRegistry.js.map +0 -1
  67. package/dist/models/index.d.ts.map +0 -1
  68. package/dist/models/index.js.map +0 -1
  69. package/dist/runner/AgentRunner.d.ts.map +0 -1
  70. package/dist/runner/AgentRunner.js.map +0 -1
  71. package/dist/runner/ModelClient.d.ts.map +0 -1
  72. package/dist/runner/ModelClient.js.map +0 -1
  73. package/dist/runner/SwarmRouter.d.ts.map +0 -1
  74. package/dist/runner/SwarmRouter.js.map +0 -1
  75. package/dist/runner/ToolDispatcher.d.ts.map +0 -1
  76. package/dist/runner/ToolDispatcher.js.map +0 -1
  77. package/dist/session/SessionManager.d.ts.map +0 -1
  78. package/dist/session/SessionManager.js.map +0 -1
  79. package/dist/tools/NewsSentiment.d.ts.map +0 -1
  80. package/dist/tools/NewsSentiment.js.map +0 -1
  81. package/dist/tools/PriceFeed.d.ts.map +0 -1
  82. package/dist/tools/PriceFeed.js.map +0 -1
  83. package/dist/tools/TechnicalAnalysis.d.ts.map +0 -1
  84. package/dist/tools/TechnicalAnalysis.js.map +0 -1
  85. package/dist/tools/index.d.ts.map +0 -1
  86. package/dist/tools/index.js.map +0 -1
  87. package/dist/tui/App.d.ts.map +0 -1
  88. package/dist/tui/App.js.map +0 -1
  89. package/dist/tui/REPL.d.ts.map +0 -1
  90. package/dist/tui/REPL.js.map +0 -1
  91. package/dist/tui/StatusBar.d.ts.map +0 -1
  92. package/dist/tui/StatusBar.js.map +0 -1
  93. package/dist/tui/theme.d.ts.map +0 -1
  94. package/dist/tui/theme.js.map +0 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * MCPServer — Model Context Protocol server over stdio. (#28)
3
+ *
4
+ * Exposes all JellyOS registered tools as MCP tools so they can be used
5
+ * by Claude Desktop, Cursor, Continue, and any MCP-compatible client.
6
+ *
7
+ * Protocol: JSON-RPC 2.0 over stdin/stdout (MCP stdio transport).
8
+ *
9
+ * Usage:
10
+ * jellyos-mcp # exposes built-in tools
11
+ * jellyos-mcp --extension ./my.ts # includes extension tools
12
+ *
13
+ * Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
14
+ * {
15
+ * "mcpServers": {
16
+ * "jellyos": {
17
+ * "command": "jellyos-mcp",
18
+ * "env": { "OPENROUTER_API_KEY": "sk-or-..." }
19
+ * }
20
+ * }
21
+ * }
22
+ */
23
+ import { createInterface } from "node:readline";
24
+ export class MCPServer {
25
+ registry;
26
+ constructor(registry) {
27
+ this.registry = registry;
28
+ }
29
+ async run() {
30
+ const rl = createInterface({ input: process.stdin, terminal: false });
31
+ // MCP uses newline-delimited JSON-RPC 2.0
32
+ rl.on("line", async (line) => {
33
+ const trimmed = line.trim();
34
+ if (!trimmed)
35
+ return;
36
+ let req;
37
+ try {
38
+ req = JSON.parse(trimmed);
39
+ }
40
+ catch {
41
+ this.respond({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
42
+ return;
43
+ }
44
+ const response = await this.handle(req);
45
+ this.respond(response);
46
+ });
47
+ rl.on("close", () => process.exit(0));
48
+ // MCP servers send an initialization notification on stderr
49
+ process.stderr.write("[JellyOS MCP] Server ready\n");
50
+ }
51
+ respond(res) {
52
+ process.stdout.write(JSON.stringify(res) + "\n");
53
+ }
54
+ async handle(req) {
55
+ try {
56
+ switch (req.method) {
57
+ case "initialize":
58
+ return {
59
+ jsonrpc: "2.0", id: req.id,
60
+ result: {
61
+ protocolVersion: "2024-11-05",
62
+ capabilities: { tools: {} },
63
+ serverInfo: { name: "jellyos", version: "0.1.5" },
64
+ },
65
+ };
66
+ case "notifications/initialized":
67
+ // No response needed for notifications
68
+ return { jsonrpc: "2.0", id: req.id, result: null };
69
+ case "tools/list":
70
+ return {
71
+ jsonrpc: "2.0", id: req.id,
72
+ result: {
73
+ tools: this.registry.listTools().map(t => ({
74
+ name: t.name,
75
+ description: t.description,
76
+ inputSchema: {
77
+ ...t.parameters,
78
+ type: "object", // MCP requires explicit type
79
+ },
80
+ })),
81
+ },
82
+ };
83
+ case "tools/call": {
84
+ const { name, arguments: args } = (req.params ?? {});
85
+ if (!name) {
86
+ return { jsonrpc: "2.0", id: req.id, error: { code: -32602, message: "Missing tool name" } };
87
+ }
88
+ const tool = this.registry.getTool(name);
89
+ if (!tool) {
90
+ return { jsonrpc: "2.0", id: req.id, error: { code: -32601, message: `Tool not found: ${name}` } };
91
+ }
92
+ try {
93
+ const result = await tool.execute("mcp", (args ?? {}));
94
+ return {
95
+ jsonrpc: "2.0", id: req.id,
96
+ result: {
97
+ content: result.content.map(c => ({ type: "text", text: c.text })),
98
+ isError: false,
99
+ },
100
+ };
101
+ }
102
+ catch (e) {
103
+ const msg = e instanceof Error ? e.message : String(e);
104
+ return {
105
+ jsonrpc: "2.0", id: req.id,
106
+ result: {
107
+ content: [{ type: "text", text: `Tool error: ${msg}` }],
108
+ isError: true,
109
+ },
110
+ };
111
+ }
112
+ }
113
+ case "ping":
114
+ return { jsonrpc: "2.0", id: req.id, result: {} };
115
+ default:
116
+ return {
117
+ jsonrpc: "2.0", id: req.id,
118
+ error: { code: -32601, message: `Method not found: ${req.method}` },
119
+ };
120
+ }
121
+ }
122
+ catch (e) {
123
+ const msg = e instanceof Error ? e.message : String(e);
124
+ return { jsonrpc: "2.0", id: req.id, error: { code: -32603, message: `Internal error: ${msg}` } };
125
+ }
126
+ }
127
+ }
128
+ //# sourceMappingURL=server.js.map
@@ -81,13 +81,24 @@ export declare class ModelRegistry {
81
81
  * consecutive failures (which get a 5-minute cooldown).
82
82
  */
83
83
  pick(tier: ModelTier): OpenRouterModel | null;
84
+ /**
85
+ * Per-model and per-tier temperature profiles.
86
+ * Reasoning/thinking models REQUIRE temperature=1.0 (API enforces).
87
+ * Code/structured tasks want low temp; creative analysis wants higher.
88
+ */
89
+ private getTemperature;
90
+ /**
91
+ * Per-tier max token budgets.
92
+ * Orchestrators get generous budgets; free workers get minimal.
93
+ */
94
+ private getTokenBudget;
84
95
  /**
85
96
  * Build a full ModelConfig chain from the tiered pool.
86
97
  * Uses user-configured models from env first, then fills with tiered picks.
87
98
  */
88
99
  buildModelChain(userModels: string[]): ModelConfig[];
89
100
  /** Build a single ModelConfig, preferring direct provider when possible. */
90
- buildConfig(modelId: string, maxTokens: number, temperature: number): ModelConfig | null;
101
+ buildConfig(modelId: string, maxTokens: number, temperature: number, tier?: ModelTier): ModelConfig | null;
91
102
  recordFailure(modelId: string): void;
92
103
  recordSuccess(modelId: string, latencyMs: number): void;
93
104
  /** Mark a model as permanently deprecated (404, model removed). */
@@ -20,13 +20,25 @@ import { Type } from "@sinclair/typebox";
20
20
  * are met. Fallback is "worker".
21
21
  */
22
22
  const TIER_RULES = [
23
- // Orchestrator: top-tier reasoning models
24
- { tier: "orchestrator", pattern: /claude.*opus|o1-pro|gemini-2\.[5-9]-pro|gpt-4\.5|deepseek-v4-671b/i, notFree: true },
25
- // Analyst: strong reasoning, moderate cost
26
- { tier: "analyst", pattern: /claude.*sonnet|gpt-4o(?!-mini)|gemini-(?!.*flash)|gemma-4.*(2[7-9]b|3[0-9]b)|deepseek.*(?:r1|chat)|grok.*(?:build|3)/i, notFree: true },
27
- // Free tier: zero-cost models
23
+ // ── Orchestrator: top-tier reasoning models (2024-2026) ──────────────────
24
+ // Claude Opus 4.x, GPT-5.x flagship/pro, Gemini 3.x Pro, DeepSeek V4 Pro,
25
+ // Grok 4.x, o3-pro/o4, Qwen3 Max variants, Kimi K2
26
+ {
27
+ tier: "orchestrator",
28
+ pattern: /claude.*opus[-.]?4|gpt-5\.[3-9].*pro|gpt-5\.5(?!-nano|-mini)|o3-pro|o4[-.]|gemini-3\.[0-9]-pro|deepseek-v4-pro|grok[-.]?4\.[0-9]|qwen3.*max(?!-thinking)|qwen3\.6-max|kimi-k2(?!-thinking)/i,
29
+ notFree: true,
30
+ },
31
+ // ── Analyst: strong reasoning, moderate cost (2024-2026) ─────────────────
32
+ // Claude Sonnet 4.x, GPT-5.x mid-tier, Gemini 3.x Flash,
33
+ // DeepSeek V4 (non-pro), Grok 3.x, Qwen3 235B, Mistral Medium 3
34
+ {
35
+ tier: "analyst",
36
+ pattern: /claude.*sonnet[-.]?4|gpt-5\.[0-4](?!.*-pro)|gpt-5\.5-mini|gemini-3\.[0-9]-flash|deepseek-v4(?!-pro)|grok[-.]?3|qwen3-235b|qwen3\.6-(?!max)|mistral-medium-3|claude.*haiku[-.]?4/i,
37
+ notFree: true,
38
+ },
39
+ // ── Free tier: zero-cost models ──────────────────────────────────────────
28
40
  { tier: "free", pattern: /:free$|openrouter\/free/i },
29
- // Default: worker
41
+ // ── Worker: everything else (default) ────────────────────────────────────
30
42
  { tier: "worker", pattern: /.*/ },
31
43
  ];
32
44
  export function classifyModel(model) {
@@ -155,13 +167,70 @@ export class ModelRegistry {
155
167
  }
156
168
  return null;
157
169
  }
170
+ /**
171
+ * Per-model and per-tier temperature profiles.
172
+ * Reasoning/thinking models REQUIRE temperature=1.0 (API enforces).
173
+ * Code/structured tasks want low temp; creative analysis wants higher.
174
+ */
175
+ getTemperature(modelId, tier, envTemp) {
176
+ // Exact model overrides (reasoning models MUST be 1.0)
177
+ const MODEL_TEMPS = {
178
+ // OpenAI o-series — no temperature param supported at all
179
+ "openai/o3": 1.0,
180
+ "openai/o3-pro": 1.0,
181
+ "openai/o3-mini": 1.0,
182
+ "openai/o4": 1.0,
183
+ "openai/o4-mini": 1.0,
184
+ // Thinking variants require 1.0
185
+ "qwen/qwen3-max-thinking": 1.0,
186
+ "qwen/qwen3.6-max-preview": 1.0,
187
+ "qwen/qwen3-235b-a22b-thinking-2507": 1.0,
188
+ "moonshotai/kimi-k2-thinking": 1.0,
189
+ "arcee-ai/trinity-large-thinking": 1.0,
190
+ };
191
+ if (MODEL_TEMPS[modelId] !== undefined)
192
+ return MODEL_TEMPS[modelId];
193
+ // Any model with "thinking" in the ID needs 1.0
194
+ if (/thinking/i.test(modelId))
195
+ return 1.0;
196
+ // Tier defaults
197
+ const TIER_TEMPS = {
198
+ orchestrator: 0.7, // balanced reasoning
199
+ analyst: 0.5, // more deterministic for analysis
200
+ worker: 0.3, // deterministic for structured tasks
201
+ free: 0.5,
202
+ };
203
+ // User env var overrides tier defaults (but not model-specific overrides)
204
+ if (process.env.TEMPERATURE)
205
+ return envTemp;
206
+ return TIER_TEMPS[tier] ?? 0.7;
207
+ }
208
+ /**
209
+ * Per-tier max token budgets.
210
+ * Orchestrators get generous budgets; free workers get minimal.
211
+ */
212
+ getTokenBudget(modelId, tier, envMax) {
213
+ const TIER_BUDGETS = {
214
+ orchestrator: 32_768,
215
+ analyst: 16_384,
216
+ worker: 4_096,
217
+ free: 2_048,
218
+ };
219
+ // Thinking models need at least 16K for the thinking budget
220
+ const isThinking = /thinking|o3|o4/i.test(modelId);
221
+ const base = isThinking
222
+ ? Math.max(16_384, TIER_BUDGETS[tier])
223
+ : TIER_BUDGETS[tier];
224
+ // User env var is a hard cap
225
+ return Math.min(base, envMax);
226
+ }
158
227
  /**
159
228
  * Build a full ModelConfig chain from the tiered pool.
160
229
  * Uses user-configured models from env first, then fills with tiered picks.
161
230
  */
162
231
  buildModelChain(userModels) {
163
232
  const env = process.env;
164
- const tokens = parseInt(env.MAX_TOKENS ?? "8192");
233
+ const tokens = parseInt(env.MAX_TOKENS ?? "99999"); // now used as cap, not target
165
234
  const temp = parseFloat(env.TEMPERATURE ?? "0.7");
166
235
  const results = [];
167
236
  // User-specified models always come first
@@ -196,11 +265,38 @@ export class ModelRegistry {
196
265
  return results.slice(0, 5);
197
266
  }
198
267
  /** Build a single ModelConfig, preferring direct provider when possible. */
199
- buildConfig(modelId, maxTokens, temperature) {
268
+ buildConfig(modelId, maxTokens, temperature, tier) {
269
+ // Apply per-model/per-tier temperature and token budget
270
+ const resolvedTier = tier ?? this.getTier(modelId);
200
271
  const env = process.env;
272
+ const envMax = parseInt(env.MAX_TOKENS ?? "99999");
273
+ const envTemp = parseFloat(env.TEMPERATURE ?? "0.7");
274
+ temperature = this.getTemperature(modelId, resolvedTier, envTemp);
275
+ maxTokens = this.getTokenBudget(modelId, resolvedTier, Math.min(maxTokens, envMax));
201
276
  // Direct Anthropic routing (cheaper — no OR markup)
202
277
  if (modelId.startsWith("anthropic/") && env.ANTHROPIC_API_KEY) {
203
- const model = modelId.replace("anthropic/", "");
278
+ const stripped = modelId.replace("anthropic/", "");
279
+ // OpenRouter model IDs differ from Anthropic API IDs — map them correctly
280
+ const ANTHROPIC_API_ALIASES = {
281
+ // Opus 4.x
282
+ "claude-opus-4.7": "claude-opus-4-20260101",
283
+ "claude-opus-4.7-fast": "claude-opus-4-20260101",
284
+ "claude-opus-4.6": "claude-opus-4-20251120",
285
+ "claude-opus-4.6-fast": "claude-opus-4-20251120",
286
+ "claude-opus-4.5": "claude-opus-4-20251015",
287
+ "claude-opus-4": "claude-opus-4-20250514",
288
+ // Sonnet 4.x
289
+ "claude-sonnet-4.6": "claude-sonnet-4-20251120",
290
+ "claude-sonnet-4.5": "claude-sonnet-4-20251015",
291
+ "claude-sonnet-4": "claude-sonnet-4-20250514",
292
+ // Haiku 4.x
293
+ "claude-haiku-4.5": "claude-haiku-4-20251015",
294
+ "claude-haiku-4": "claude-haiku-4-20250514",
295
+ // Legacy aliases (safe to keep)
296
+ "claude-3-5-sonnet-20241022": "claude-3-5-sonnet-20241022",
297
+ "claude-3-haiku-20240307": "claude-3-haiku-20240307",
298
+ };
299
+ const model = ANTHROPIC_API_ALIASES[stripped] ?? stripped;
204
300
  return {
205
301
  baseUrl: "https://api.anthropic.com/v1",
206
302
  apiKey: env.ANTHROPIC_API_KEY,
@@ -11,6 +11,9 @@ import type { SessionManager } from "../session/SessionManager.js";
11
11
  import type { SessionContext } from "../api/ExtensionAPI.js";
12
12
  import type { ModelRegistry } from "../models/ModelRegistry.js";
13
13
  import type { CostTracker } from "../models/CostTracker.js";
14
+ import type { GoalManager } from "../session/GoalManager.js";
15
+ import type { ContextStore } from "../session/ContextStore.js";
16
+ import { Tracer } from "../telemetry/Tracer.js";
14
17
  export type RunnerEvent = {
15
18
  type: "text_delta";
16
19
  text: string;
@@ -42,6 +45,13 @@ export type RunnerEvent = {
42
45
  } | {
43
46
  type: "error";
44
47
  message: string;
48
+ }
49
+ /** #10: Approval gate — TUI pauses and waits for user y/n */
50
+ | {
51
+ type: "approval_request";
52
+ toolName: string;
53
+ args: string;
54
+ approve: (yes: boolean) => void;
45
55
  };
46
56
  export type RunnerEventHandler = (event: RunnerEvent) => void;
47
57
  export declare class AgentRunner {
@@ -50,12 +60,17 @@ export declare class AgentRunner {
50
60
  private onEvent;
51
61
  private sessionCtx;
52
62
  private effectLevel;
63
+ private goalManager?;
64
+ private contextStore?;
53
65
  private modelChain;
54
66
  private dispatcher;
55
67
  private swarmRouter;
56
68
  private modelRegistry?;
57
69
  private costTracker?;
58
- constructor(registry: Registry, session: SessionManager, onEvent: RunnerEventHandler, sessionCtx: SessionContext, effectLevel?: string, modelReg?: ModelRegistry, costTracker?: CostTracker);
70
+ private abortController;
71
+ /** #25: Cancel the current in-flight stream immediately */
72
+ abort(): void;
73
+ constructor(registry: Registry, session: SessionManager, onEvent: RunnerEventHandler, sessionCtx: SessionContext, effectLevel?: string, modelReg?: ModelRegistry, costTracker?: CostTracker, goalManager?: GoalManager | undefined, contextStore?: ContextStore | undefined);
59
74
  /**
60
75
  * Live reconfigure effect level without recreating the runner.
61
76
  * Called by the /effect REPL command immediately on each invocation so that
@@ -64,7 +79,9 @@ export declare class AgentRunner {
64
79
  setEffectLevel(level: string): void;
65
80
  /** Run one user turn — may invoke multiple tool rounds and model fallbacks internally */
66
81
  run(userMessage: string): Promise<void>;
82
+ private buildLiveContext;
83
+ private buildDynamicSystemSuffix;
67
84
  private runSwarm;
68
- runSingleAgent(): Promise<void>;
85
+ runSingleAgent(userMessage?: string, tracer?: Tracer): Promise<void>;
69
86
  }
70
87
  //# sourceMappingURL=AgentRunner.d.ts.map