apteva 0.4.41 → 0.4.44

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 (102) hide show
  1. package/dist/ActivityPage.c48n83h2.js +3 -0
  2. package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
  3. package/dist/App.09yb8t0b.js +1 -0
  4. package/dist/App.152mbs1r.js +4 -0
  5. package/dist/App.3a67nx9w.js +4 -0
  6. package/dist/App.9epx6785.js +4 -0
  7. package/dist/App.d8955awp.js +4 -0
  8. package/dist/App.drwb57jq.js +4 -0
  9. package/dist/App.gssbmajb.js +4 -0
  10. package/dist/App.qw70pc29.js +53 -0
  11. package/dist/{App.7fb3e7mp.js → App.qzbx5wtj.js} +1 -1
  12. package/dist/App.r5serxkt.js +8 -0
  13. package/dist/App.tpmp9020.js +20 -0
  14. package/dist/App.v2wb4d7d.js +61 -0
  15. package/dist/App.vxmaaj0m.js +13 -0
  16. package/dist/App.w4p2tda9.js +4 -0
  17. package/dist/App.wv2ng55q.js +221 -0
  18. package/dist/App.yncnrn0f.js +4 -0
  19. package/dist/ConnectionsPage.k6cspyqq.js +3 -0
  20. package/dist/McpPage.cdxm48xj.js +3 -0
  21. package/dist/SettingsPage.evpv7c2y.js +3 -0
  22. package/dist/SkillsPage.pvzp6c1a.js +3 -0
  23. package/dist/TasksPage.6jnvbpsy.js +3 -0
  24. package/dist/TelemetryPage.t7vk24zc.js +3 -0
  25. package/dist/TestsPage.5x6658aa.js +3 -0
  26. package/dist/ThreadsPage.3fvhtevh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +8 -8
  31. package/src/db.ts +19 -9
  32. package/src/integrations/agentdojo.ts +1 -0
  33. package/src/mcp-platform.ts +418 -63
  34. package/src/openapi.ts +96 -0
  35. package/src/providers.ts +50 -24
  36. package/src/routes/api/agent-utils.ts +0 -1
  37. package/src/routes/api/agents.ts +19 -1
  38. package/src/routes/api/meta-agent.ts +2 -0
  39. package/src/routes/api/system.ts +90 -1
  40. package/src/routes/api/telemetry.ts +19 -1
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +12 -0
  43. package/src/web/App.tsx +89 -11
  44. package/src/web/components/activity/ActivityPage.tsx +14 -14
  45. package/src/web/components/agents/AgentCard.tsx +14 -14
  46. package/src/web/components/agents/AgentPanel.tsx +358 -198
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  49. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  50. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  51. package/src/web/components/auth/LoginPage.tsx +10 -10
  52. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  53. package/src/web/components/common/Modal.tsx +8 -8
  54. package/src/web/components/common/Select.tsx +9 -9
  55. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  56. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  57. package/src/web/components/connections/OverviewTab.tsx +13 -13
  58. package/src/web/components/connections/TriggersTab.tsx +99 -99
  59. package/src/web/components/dashboard/Dashboard.tsx +32 -32
  60. package/src/web/components/layout/Header.tsx +50 -34
  61. package/src/web/components/layout/Sidebar.tsx +34 -15
  62. package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
  63. package/src/web/components/mcp/McpPage.tsx +208 -208
  64. package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
  65. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  66. package/src/web/components/settings/SettingsPage.tsx +258 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +339 -54
  69. package/src/web/components/telemetry/TelemetryPage.tsx +135 -64
  70. package/src/web/components/tests/TestsPage.tsx +50 -50
  71. package/src/web/components/threads/ThreadsPage.tsx +23 -21
  72. package/src/web/context/ProjectContext.tsx +6 -1
  73. package/src/web/context/ThemeContext.tsx +69 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/styles.css +5 -3
  76. package/src/web/themes.ts +99 -0
  77. package/src/web/types.ts +0 -4
  78. package/dist/ActivityPage.7907h64p.js +0 -3
  79. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  80. package/dist/App.01nq20st.js +0 -4
  81. package/dist/App.1maqvamf.js +0 -4
  82. package/dist/App.2yjrh32f.js +0 -4
  83. package/dist/App.3qw8nben.js +0 -20
  84. package/dist/App.7sy3wq8c.js +0 -4
  85. package/dist/App.apjrmctz.js +0 -57
  86. package/dist/App.av6t2yhe.js +0 -4
  87. package/dist/App.jqj5a094.js +0 -46
  88. package/dist/App.mc7xf85h.js +0 -4
  89. package/dist/App.myxqcj9x.js +0 -4
  90. package/dist/App.nm91r1mp.js +0 -13
  91. package/dist/App.p02f4ret.js +0 -1
  92. package/dist/App.qcknavjz.js +0 -221
  93. package/dist/App.vc7vfhg4.js +0 -4
  94. package/dist/App.z4s9zkw5.js +0 -4
  95. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  96. package/dist/McpPage.8vc97z0b.js +0 -3
  97. package/dist/SettingsPage.p61bz8kd.js +0 -3
  98. package/dist/SkillsPage.r9x43g3g.js +0 -3
  99. package/dist/TasksPage.1e0zkye4.js +0 -3
  100. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  101. package/dist/TestsPage.d4xy504e.js +0 -3
  102. package/dist/ThreadsPage.m016am3x.js +0 -3
package/src/openapi.ts CHANGED
@@ -1006,6 +1006,40 @@ while (true) {
1006
1006
  },
1007
1007
  },
1008
1008
  },
1009
+ "/tasks/{agentId}": {
1010
+ post: {
1011
+ tags: ["Tasks"],
1012
+ summary: "Create a task on an agent",
1013
+ description: "Create a new task on a running agent. The agent must have the tasks feature enabled.",
1014
+ parameters: [
1015
+ { name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID to create the task on" },
1016
+ ],
1017
+ requestBody: {
1018
+ required: true,
1019
+ content: {
1020
+ "application/json": {
1021
+ schema: {
1022
+ type: "object",
1023
+ properties: {
1024
+ title: { type: "string", description: "Task title" },
1025
+ description: { type: "string", description: "Task description" },
1026
+ type: { type: "string", enum: ["once", "recurring"], default: "once" },
1027
+ priority: { type: "integer", minimum: 1, maximum: 10, default: 5 },
1028
+ execute_at: { type: "string", format: "date-time", description: "Scheduled execution time (for one-time tasks)" },
1029
+ recurrence: { type: "string", description: "Cron expression (for recurring tasks)" },
1030
+ },
1031
+ required: ["title"],
1032
+ },
1033
+ },
1034
+ },
1035
+ },
1036
+ responses: {
1037
+ "201": { description: "Task created successfully" },
1038
+ "400": { description: "Agent is not running or invalid input" },
1039
+ "404": { description: "Agent not found" },
1040
+ },
1041
+ },
1042
+ },
1009
1043
  "/tasks/{agentId}/{taskId}": {
1010
1044
  get: {
1011
1045
  tags: ["Tasks"],
@@ -1033,6 +1067,68 @@ while (true) {
1033
1067
  "404": { description: "Agent not found" },
1034
1068
  },
1035
1069
  },
1070
+ put: {
1071
+ tags: ["Tasks"],
1072
+ summary: "Update a task on an agent",
1073
+ description: "Update an existing task on a running agent.",
1074
+ parameters: [
1075
+ { name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
1076
+ { name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to update" },
1077
+ ],
1078
+ requestBody: {
1079
+ required: true,
1080
+ content: {
1081
+ "application/json": {
1082
+ schema: {
1083
+ type: "object",
1084
+ properties: {
1085
+ title: { type: "string" },
1086
+ description: { type: "string" },
1087
+ type: { type: "string", enum: ["once", "recurring"] },
1088
+ priority: { type: "integer", minimum: 1, maximum: 10 },
1089
+ execute_at: { type: "string", format: "date-time" },
1090
+ recurrence: { type: "string" },
1091
+ },
1092
+ },
1093
+ },
1094
+ },
1095
+ },
1096
+ responses: {
1097
+ "200": { description: "Task updated" },
1098
+ "400": { description: "Agent is not running or invalid input" },
1099
+ "404": { description: "Agent or task not found" },
1100
+ },
1101
+ },
1102
+ delete: {
1103
+ tags: ["Tasks"],
1104
+ summary: "Delete a task on an agent",
1105
+ description: "Delete a task from a running agent.",
1106
+ parameters: [
1107
+ { name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
1108
+ { name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to delete" },
1109
+ ],
1110
+ responses: {
1111
+ "200": { description: "Task deleted" },
1112
+ "400": { description: "Agent is not running" },
1113
+ "404": { description: "Agent or task not found" },
1114
+ },
1115
+ },
1116
+ },
1117
+ "/tasks/{agentId}/{taskId}/execute": {
1118
+ post: {
1119
+ tags: ["Tasks"],
1120
+ summary: "Execute a task immediately",
1121
+ description: "Immediately execute a task on a running agent, regardless of its schedule.",
1122
+ parameters: [
1123
+ { name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
1124
+ { name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to execute" },
1125
+ ],
1126
+ responses: {
1127
+ "200": { description: "Task execution started" },
1128
+ "400": { description: "Agent is not running" },
1129
+ "404": { description: "Agent or task not found" },
1130
+ },
1131
+ },
1036
1132
  },
1037
1133
  "/mcp/servers": {
1038
1134
  get: {
package/src/providers.ts CHANGED
@@ -12,9 +12,9 @@ export const PROVIDERS = {
12
12
  docsUrl: "https://console.anthropic.com/settings/keys",
13
13
  testEndpoint: "https://api.anthropic.com/v1/messages",
14
14
  models: [
15
- { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", recommended: true },
16
- { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" },
17
- { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)" },
15
+ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", recommended: true, input_cost: 3, output_cost: 15 },
16
+ { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", input_cost: 3, output_cost: 15 },
17
+ { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)", input_cost: 0.8, output_cost: 4 },
18
18
  ],
19
19
  },
20
20
  openai: {
@@ -26,8 +26,8 @@ export const PROVIDERS = {
26
26
  docsUrl: "https://platform.openai.com/api-keys",
27
27
  testEndpoint: "https://api.openai.com/v1/models",
28
28
  models: [
29
- { value: "gpt-4o", label: "GPT-4o", recommended: true },
30
- { value: "gpt-4o-mini", label: "GPT-4o Mini (Fast)" },
29
+ { value: "gpt-4o", label: "GPT-4o", recommended: true, input_cost: 2.5, output_cost: 10 },
30
+ { value: "gpt-4o-mini", label: "GPT-4o Mini (Fast)", input_cost: 0.15, output_cost: 0.6 },
31
31
  ],
32
32
  },
33
33
  groq: {
@@ -39,8 +39,8 @@ export const PROVIDERS = {
39
39
  docsUrl: "https://console.groq.com/keys",
40
40
  testEndpoint: "https://api.groq.com/openai/v1/models",
41
41
  models: [
42
- { value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", recommended: true },
43
- { value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Fast)" },
42
+ { value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", recommended: true, input_cost: 0, output_cost: 0 },
43
+ { value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Fast)", input_cost: 0, output_cost: 0 },
44
44
  ],
45
45
  },
46
46
  gemini: {
@@ -52,8 +52,8 @@ export const PROVIDERS = {
52
52
  docsUrl: "https://aistudio.google.com/app/apikey",
53
53
  testEndpoint: "https://generativelanguage.googleapis.com/v1/models",
54
54
  models: [
55
- { value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview (Latest)", recommended: true },
56
- { value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview (Fast)" },
55
+ { value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview (Latest)", recommended: true, input_cost: 2, output_cost: 12 },
56
+ { value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview (Fast)", input_cost: 0.5, output_cost: 3 },
57
57
  ],
58
58
  },
59
59
  xai: {
@@ -65,8 +65,8 @@ export const PROVIDERS = {
65
65
  docsUrl: "https://console.x.ai/",
66
66
  testEndpoint: "https://api.x.ai/v1/models",
67
67
  models: [
68
- { value: "grok-2", label: "Grok 2", recommended: true },
69
- { value: "grok-2-mini", label: "Grok 2 Mini (Fast)" },
68
+ { value: "grok-2", label: "Grok 2", recommended: true, input_cost: 2, output_cost: 10 },
69
+ { value: "grok-2-mini", label: "Grok 2 Mini (Fast)", input_cost: 0.3, output_cost: 1 },
70
70
  ],
71
71
  },
72
72
  together: {
@@ -78,8 +78,8 @@ export const PROVIDERS = {
78
78
  docsUrl: "https://api.together.xyz/settings/api-keys",
79
79
  testEndpoint: "https://api.together.xyz/v1/models",
80
80
  models: [
81
- { value: "moonshotai/Kimi-K2.5", label: "Kimi K2.5", recommended: true },
82
- { value: "moonshotai/Kimi-K2-Thinking", label: "Kimi K2 Thinking (Reasoning)" },
81
+ { value: "moonshotai/Kimi-K2.5", label: "Kimi K2.5", recommended: true, input_cost: 1, output_cost: 4 },
82
+ { value: "moonshotai/Kimi-K2-Thinking", label: "Kimi K2 Thinking (Reasoning)", input_cost: 1, output_cost: 4 },
83
83
  ],
84
84
  },
85
85
  fireworks: {
@@ -91,10 +91,10 @@ export const PROVIDERS = {
91
91
  docsUrl: "https://fireworks.ai/api-keys",
92
92
  testEndpoint: "https://api.fireworks.ai/inference/v1/models",
93
93
  models: [
94
- { value: "accounts/fireworks/models/kimi-k2p5", label: "Kimi K2.5", recommended: true },
95
- { value: "accounts/fireworks/models/kimi-k2-thinking", label: "Kimi K2 Thinking (Reasoning)" },
96
- { value: "accounts/fireworks/models/minimax-m2p5", label: "MiniMax M2.5" },
97
- { value: "accounts/fireworks/models/glm-5", label: "GLM 5" },
94
+ { value: "accounts/fireworks/models/kimi-k2p5", label: "Kimi K2.5", recommended: true, input_cost: 1, output_cost: 4 },
95
+ { value: "accounts/fireworks/models/kimi-k2-thinking", label: "Kimi K2 Thinking (Reasoning)", input_cost: 1, output_cost: 4 },
96
+ { value: "accounts/fireworks/models/minimax-m2p5", label: "MiniMax M2.5", input_cost: 1, output_cost: 4 },
97
+ { value: "accounts/fireworks/models/glm-5", label: "GLM 5", input_cost: 1, output_cost: 4 },
98
98
  ],
99
99
  },
100
100
  moonshot: {
@@ -106,8 +106,24 @@ export const PROVIDERS = {
106
106
  docsUrl: "https://platform.moonshot.cn/console/api-keys",
107
107
  testEndpoint: "https://api.moonshot.cn/v1/models",
108
108
  models: [
109
- { value: "moonshot-v1-128k", label: "Kimi 128K", recommended: true },
110
- { value: "moonshot-v1-32k", label: "Kimi 32K (Fast)" },
109
+ { value: "moonshot-v1-128k", label: "Kimi 128K", recommended: true, input_cost: 1, output_cost: 4 },
110
+ { value: "moonshot-v1-32k", label: "Kimi 32K (Fast)", input_cost: 0.5, output_cost: 2 },
111
+ ],
112
+ },
113
+ venice: {
114
+ id: "venice",
115
+ name: "Venice",
116
+ displayName: "Venice AI",
117
+ type: "llm" as const,
118
+ envVar: "VENICE_API_KEY",
119
+ docsUrl: "https://docs.venice.ai/overview/pricing",
120
+ testEndpoint: "https://api.venice.ai/api/v1/models",
121
+ models: [
122
+ { value: "llama-3.3-70b", label: "Llama 3.3 70B", recommended: true, input_cost: 0.7, output_cost: 2.8 },
123
+ { value: "olafangensan-glm-4.7-flash-heretic", label: "GLM 4.7 Flash Heretic", input_cost: 0.14, output_cost: 0.8 },
124
+ { value: "qwen3-235b-a22b-instruct-2507", label: "Qwen 3 235B Instruct", input_cost: 0.15, output_cost: 0.75 },
125
+ { value: "deepseek-v3.2", label: "DeepSeek V3.2", input_cost: 0.4, output_cost: 1 },
126
+ { value: "venice-uncensored", label: "Venice Uncensored 1.1", input_cost: 0.2, output_cost: 0.9 },
111
127
  ],
112
128
  },
113
129
  ollama: {
@@ -122,11 +138,11 @@ export const PROVIDERS = {
122
138
  defaultBaseUrl: "http://localhost:11434",
123
139
  models: [
124
140
  // Default models - actual list fetched dynamically from Ollama
125
- { value: "llama3.3", label: "Llama 3.3 (70B)", recommended: true },
126
- { value: "llama3.2", label: "Llama 3.2 (3B)" },
127
- { value: "qwen2.5", label: "Qwen 2.5" },
128
- { value: "mistral", label: "Mistral" },
129
- { value: "deepseek-r1", label: "DeepSeek R1" },
141
+ { value: "llama3.3", label: "Llama 3.3 (70B)", recommended: true, input_cost: 0, output_cost: 0 },
142
+ { value: "llama3.2", label: "Llama 3.2 (3B)", input_cost: 0, output_cost: 0 },
143
+ { value: "qwen2.5", label: "Qwen 2.5", input_cost: 0, output_cost: 0 },
144
+ { value: "mistral", label: "Mistral", input_cost: 0, output_cost: 0 },
145
+ { value: "deepseek-r1", label: "DeepSeek R1", input_cost: 0, output_cost: 0 },
130
146
  ],
131
147
  },
132
148
  // Browser Providers
@@ -217,6 +233,16 @@ export const PROVIDERS = {
217
233
 
218
234
  export type ProviderId = keyof typeof PROVIDERS;
219
235
 
236
+ /** Get cost per 1M tokens for a given provider + model. Returns { input_cost, output_cost } in USD. */
237
+ export function getModelCost(provider: string, model: string): { input_cost: number; output_cost: number } {
238
+ const providerDef = PROVIDERS[provider as ProviderId];
239
+ if (!providerDef || !("models" in providerDef)) return { input_cost: 0, output_cost: 0 };
240
+ const modelDef = (providerDef.models as ReadonlyArray<{ value: string; input_cost?: number; output_cost?: number }>)
241
+ .find(m => m.value === model);
242
+ if (!modelDef) return { input_cost: 0, output_cost: 0 };
243
+ return { input_cost: modelDef.input_cost ?? 0, output_cost: modelDef.output_cost ?? 0 };
244
+ }
245
+
220
246
  // Provider Keys Management
221
247
  export const ProviderKeys = {
222
248
  // Save an API key (encrypts before storing)
@@ -337,7 +337,6 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
337
337
  const baseUrl = process.env.PUBLIC_URL || `http://localhost:${process.env.PORT || 4280}`;
338
338
  return {
339
339
  enabled: multiAgentConfig.enabled,
340
- mode: multiAgentConfig.mode || "worker",
341
340
  group: multiAgentConfig.group || agent.project_id || undefined,
342
341
  // This agent's reachable URL for peer communication
343
342
  url: `http://localhost:${agent.port}`,
@@ -270,6 +270,25 @@ export async function handleAgentRoutes(
270
270
  });
271
271
  }
272
272
 
273
+ // ==================== SHARE LINK ====================
274
+
275
+ // GET /api/agents/:id/share-token - Get the share token for this agent
276
+ const shareTokenMatch = path.match(/^\/api\/agents\/([^/]+)\/share-token$/);
277
+ if (shareTokenMatch && method === "GET") {
278
+ const agent = AgentDB.findById(shareTokenMatch[1]);
279
+ if (!agent) {
280
+ return json({ error: "Agent not found" }, 404);
281
+ }
282
+
283
+ const { getShareToken } = await import("../share");
284
+ const token = getShareToken(agent.id);
285
+ if (!token) {
286
+ return json({ error: "Could not generate share token" }, 500);
287
+ }
288
+
289
+ return json({ token });
290
+ }
291
+
273
292
  // ==================== AGENT LIFECYCLE ====================
274
293
 
275
294
  // POST /api/agents/:id/start - Start an agent
@@ -840,7 +859,6 @@ export async function handleAgentRoutes(
840
859
  id: a.id,
841
860
  name: a.name,
842
861
  url: `http://localhost:${a.port}`,
843
- mode: agentConfig.mode || "worker",
844
862
  group: agentConfig.group || a.project_id,
845
863
  };
846
864
  });
@@ -117,6 +117,8 @@ Be concise. Use markdown formatting.`,
117
117
  port: metaAgent.port,
118
118
  provider: metaAgent.provider,
119
119
  model: metaAgent.model,
120
+ features: metaAgent.features,
121
+ systemPrompt: metaAgent.system_prompt,
120
122
  },
121
123
  });
122
124
  }
@@ -1,5 +1,5 @@
1
1
  import { json } from "./helpers";
2
- import { META_AGENT_ENABLED, fetchFromAgent, startAgentProcess, setAgentStatus } from "./agent-utils";
2
+ import { META_AGENT_ENABLED, fetchFromAgent, agentFetch, startAgentProcess, setAgentStatus } from "./agent-utils";
3
3
  import { AgentDB } from "../../db";
4
4
  import { ProviderKeys } from "../../providers";
5
5
  import { agentProcesses, getBinaryStatus, BIN_DIR } from "../../server";
@@ -44,6 +44,7 @@ export async function handleSystemRoutes(
44
44
  return json({
45
45
  projects: process.env.PROJECTS_ENABLED === "true",
46
46
  metaAgent: process.env.META_AGENT_ENABLED === "true",
47
+ costTracking: process.env.COST_TRACKING_ENABLED !== "false",
47
48
  });
48
49
  }
49
50
 
@@ -180,6 +181,94 @@ export async function handleSystemRoutes(
180
181
  return json({ task: { ...data, agentId: agent.id, agentName: agent.name } });
181
182
  }
182
183
 
184
+ // POST /api/tasks/:agentId/:taskId/execute - Execute a task immediately
185
+ const executeTaskMatch = path.match(/^\/api\/tasks\/([^/]+)\/([^/]+)\/execute$/);
186
+ if (executeTaskMatch && method === "POST") {
187
+ const [, agentId, taskId] = executeTaskMatch;
188
+ const agent = AgentDB.findById(agentId);
189
+ if (!agent) return json({ error: "Agent not found" }, 404);
190
+ if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
191
+
192
+ try {
193
+ const res = await agentFetch(agentId, agent.port, `/tasks/${taskId}/execute`, {
194
+ method: "POST",
195
+ signal: AbortSignal.timeout(5000),
196
+ });
197
+ const data = await res.json();
198
+ if (!res.ok) return json({ error: data.error || `HTTP ${res.status}` }, res.status);
199
+ return json(data);
200
+ } catch (err) {
201
+ return json({ error: `Failed to execute task: ${err}` }, 500);
202
+ }
203
+ }
204
+
205
+ // POST /api/tasks/:agentId - Create a task on an agent
206
+ const createTaskMatch = path.match(/^\/api\/tasks\/([^/]+)$/);
207
+ if (createTaskMatch && method === "POST") {
208
+ const agentId = createTaskMatch[1];
209
+ const agent = AgentDB.findById(agentId);
210
+ if (!agent) return json({ error: "Agent not found" }, 404);
211
+ if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
212
+
213
+ try {
214
+ const body = await req.json();
215
+ const res = await agentFetch(agentId, agent.port, "/tasks", {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body: JSON.stringify(body),
219
+ signal: AbortSignal.timeout(5000),
220
+ });
221
+ const data = await res.json();
222
+ if (!res.ok) return json({ error: data.error || `HTTP ${res.status}` }, res.status);
223
+ return json(data, 201);
224
+ } catch (err) {
225
+ return json({ error: `Failed to create task: ${err}` }, 500);
226
+ }
227
+ }
228
+
229
+ // PUT /api/tasks/:agentId/:taskId - Update a task on an agent
230
+ if (singleTaskMatch && method === "PUT") {
231
+ const [, agentId, taskId] = singleTaskMatch;
232
+ const agent = AgentDB.findById(agentId);
233
+ if (!agent) return json({ error: "Agent not found" }, 404);
234
+ if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
235
+
236
+ try {
237
+ const body = await req.json();
238
+ const res = await agentFetch(agentId, agent.port, `/tasks/${taskId}`, {
239
+ method: "PUT",
240
+ headers: { "Content-Type": "application/json" },
241
+ body: JSON.stringify(body),
242
+ signal: AbortSignal.timeout(5000),
243
+ });
244
+ const data = await res.json();
245
+ if (!res.ok) return json({ error: data.error || `HTTP ${res.status}` }, res.status);
246
+ return json(data);
247
+ } catch (err) {
248
+ return json({ error: `Failed to update task: ${err}` }, 500);
249
+ }
250
+ }
251
+
252
+ // DELETE /api/tasks/:agentId/:taskId - Delete a task on an agent
253
+ if (singleTaskMatch && method === "DELETE") {
254
+ const [, agentId, taskId] = singleTaskMatch;
255
+ const agent = AgentDB.findById(agentId);
256
+ if (!agent) return json({ error: "Agent not found" }, 404);
257
+ if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
258
+
259
+ try {
260
+ const res = await agentFetch(agentId, agent.port, `/tasks/${taskId}`, {
261
+ method: "DELETE",
262
+ signal: AbortSignal.timeout(5000),
263
+ });
264
+ const data = await res.json();
265
+ if (!res.ok) return json({ error: data.error || `HTTP ${res.status}` }, res.status);
266
+ return json(data);
267
+ } catch (err) {
268
+ return json({ error: `Failed to delete task: ${err}` }, 500);
269
+ }
270
+ }
271
+
183
272
  // GET /api/dashboard - Get dashboard statistics
184
273
  if (path === "/api/dashboard" && method === "GET") {
185
274
  const url = new URL(req.url);
@@ -1,5 +1,6 @@
1
1
  import { json } from "./helpers";
2
- import { TelemetryDB } from "../../db";
2
+ import { TelemetryDB, AgentDB } from "../../db";
3
+ import { getModelCost } from "../../providers";
3
4
  import { telemetryBroadcaster, type TelemetryEvent } from "../../server";
4
5
 
5
6
  export async function handleTelemetryRoutes(
@@ -35,6 +36,23 @@ export async function handleTelemetryRoutes(
35
36
 
36
37
  // Filter out debug events - too noisy
37
38
  const filteredEvents = body.events.filter(e => e.level !== "debug");
39
+
40
+ // Compute cost per LLM event if cost tracking is enabled
41
+ const costTrackingEnabled = process.env.COST_TRACKING_ENABLED !== "false";
42
+ if (costTrackingEnabled) {
43
+ const agent = AgentDB.findById(body.agent_id);
44
+ if (agent) {
45
+ const pricing = getModelCost(agent.provider, agent.model);
46
+ for (const event of filteredEvents) {
47
+ if (event.category === "LLM" && event.data) {
48
+ const inputTokens = (event.data.input_tokens as number) || 0;
49
+ const outputTokens = (event.data.output_tokens as number) || 0;
50
+ (event as any).cost = (inputTokens * pricing.input_cost + outputTokens * pricing.output_cost) / 1_000_000;
51
+ }
52
+ }
53
+ }
54
+ }
55
+
38
56
  const inserted = TelemetryDB.insertBatch(body.agent_id, filteredEvents);
39
57
 
40
58
  // Broadcast to SSE clients
@@ -0,0 +1,85 @@
1
+ import { createHash } from "crypto";
2
+ import { AgentDB, type Agent } from "../db";
3
+ import { agentFetch } from "./api/agent-utils";
4
+
5
+ function deriveShareToken(apiKey: string, agentId: string): string {
6
+ return createHash("sha256")
7
+ .update(apiKey + ":" + agentId + ":share")
8
+ .digest("hex")
9
+ .substring(0, 32);
10
+ }
11
+
12
+ function findAgentByShareToken(token: string): Agent | null {
13
+ const agents = AgentDB.findAll();
14
+ for (const agent of agents) {
15
+ const apiKey = AgentDB.getApiKey(agent.id);
16
+ if (!apiKey) continue;
17
+ if (deriveShareToken(apiKey, agent.id) === token) return agent;
18
+ }
19
+ return null;
20
+ }
21
+
22
+ /** Get the share token for an agent (used by API route for the UI) */
23
+ export function getShareToken(agentId: string): string | null {
24
+ const apiKey = AgentDB.getApiKey(agentId);
25
+ if (!apiKey) return null;
26
+ return deriveShareToken(apiKey, agentId);
27
+ }
28
+
29
+ export async function handleShareRequest(req: Request, path: string): Promise<Response | null> {
30
+ // Match /share/<32 hex chars> sub-paths for API calls
31
+ const infoMatch = path.match(/^\/share\/([a-f0-9]{32})\/info$/);
32
+ const chatMatch = path.match(/^\/share\/([a-f0-9]{32})\/chat$/);
33
+
34
+ if (!infoMatch && !chatMatch) return null;
35
+
36
+ const token = (infoMatch || chatMatch)![1];
37
+ const agent = findAgentByShareToken(token);
38
+
39
+ // Intentionally vague 404 — don't reveal whether token exists
40
+ if (!agent) {
41
+ return new Response("Not found", { status: 404 });
42
+ }
43
+
44
+ // GET /share/:token/info — agent info (no secrets)
45
+ if (infoMatch && req.method === "GET") {
46
+ return Response.json({
47
+ name: agent.name,
48
+ status: agent.status,
49
+ });
50
+ }
51
+
52
+ // POST /share/:token/chat — proxy to agent
53
+ if (chatMatch && req.method === "POST") {
54
+ if (agent.status !== "running" || !agent.port) {
55
+ return Response.json({ error: "Agent is currently offline" }, { status: 503 });
56
+ }
57
+
58
+ try {
59
+ const body = await req.json();
60
+ const response = await agentFetch(agent.id, agent.port, "/chat", {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify(body),
64
+ });
65
+
66
+ if (!response.ok) {
67
+ const errorText = await response.text();
68
+ return Response.json({ error: `Agent error: ${errorText}` }, { status: response.status });
69
+ }
70
+
71
+ return new Response(response.body, {
72
+ status: 200,
73
+ headers: {
74
+ "Content-Type": response.headers.get("Content-Type") || "text/event-stream",
75
+ "Cache-Control": "no-cache",
76
+ "Connection": "keep-alive",
77
+ },
78
+ });
79
+ } catch (err) {
80
+ return Response.json({ error: "Failed to connect to agent" }, { status: 500 });
81
+ }
82
+ }
83
+
84
+ return null;
85
+ }
package/src/server.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type Server, type Subprocess } from "bun";
2
2
  import { handleApiRequest } from "./routes/api";
3
3
  import { handleAuthRequest } from "./routes/auth";
4
+ import { handleShareRequest } from "./routes/share";
4
5
  import { serveStatic } from "./routes/static";
5
6
  import { join } from "path";
6
7
  import { homedir } from "os";
@@ -421,6 +422,17 @@ const server = Bun.serve({
421
422
  return response;
422
423
  }
423
424
 
425
+ // Share routes (public, token-authenticated)
426
+ if (path.startsWith("/share/")) {
427
+ const shareResponse = await handleShareRequest(req, path);
428
+ if (shareResponse) {
429
+ Object.entries(corsHeaders).forEach(([key, value]) => {
430
+ shareResponse.headers.set(key, value);
431
+ });
432
+ return shareResponse;
433
+ }
434
+ }
435
+
424
436
  // Serve static files (React app)
425
437
  return serveStatic(req, path);
426
438
  },