apteva 0.4.41 → 0.4.48

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 (103) hide show
  1. package/dist/ActivityPage.sw9p594m.js +3 -0
  2. package/dist/ApiDocsPage.90e03bz7.js +4 -0
  3. package/dist/App.0ws87fpx.js +53 -0
  4. package/dist/App.3vnrera5.js +4 -0
  5. package/dist/App.94x6mh7f.js +20 -0
  6. package/dist/{App.7fb3e7mp.js → App.9sryp183.js} +1 -1
  7. package/dist/App.d9tny4t0.js +221 -0
  8. package/dist/App.jhb45d7r.js +8 -0
  9. package/dist/App.p7jjw1zf.js +4 -0
  10. package/dist/App.pfbdzrhh.js +4 -0
  11. package/dist/App.stgng5bx.js +13 -0
  12. package/dist/App.tm3k7h4b.js +4 -0
  13. package/dist/App.vkg121c6.js +4 -0
  14. package/dist/App.wghtdzsk.js +1 -0
  15. package/dist/App.xf7wsckg.js +4 -0
  16. package/dist/App.xva0tfzh.js +4 -0
  17. package/dist/App.ysxy7akk.js +61 -0
  18. package/dist/App.yzkh4gq2.js +4 -0
  19. package/dist/ConnectionsPage.q5f9fd37.js +3 -0
  20. package/dist/McpPage.f3ccrezb.js +3 -0
  21. package/dist/SettingsPage.3sqx6wm4.js +3 -0
  22. package/dist/SkillsPage.whxnez67.js +3 -0
  23. package/dist/TasksPage.zp4jfevw.js +3 -0
  24. package/dist/TelemetryPage.a9fmxq87.js +3 -0
  25. package/dist/TestsPage.18krj0d1.js +3 -0
  26. package/dist/ThreadsPage.nnphgy98.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 +11 -10
  31. package/src/db.ts +61 -13
  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 +55 -24
  36. package/src/routes/api/agent-utils.ts +25 -4
  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 +38 -2
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +64 -1
  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 +19 -17
  46. package/src/web/components/agents/AgentPanel.tsx +541 -220
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +24 -82
  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 +9 -9
  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 +35 -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 +291 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +539 -78
  69. package/src/web/components/telemetry/TelemetryPage.tsx +405 -65
  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 +90 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/index.html +1 -6
  76. package/src/web/styles.css +52 -3
  77. package/src/web/themes.ts +162 -0
  78. package/src/web/types.ts +0 -4
  79. package/dist/ActivityPage.7907h64p.js +0 -3
  80. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  81. package/dist/App.01nq20st.js +0 -4
  82. package/dist/App.1maqvamf.js +0 -4
  83. package/dist/App.2yjrh32f.js +0 -4
  84. package/dist/App.3qw8nben.js +0 -20
  85. package/dist/App.7sy3wq8c.js +0 -4
  86. package/dist/App.apjrmctz.js +0 -57
  87. package/dist/App.av6t2yhe.js +0 -4
  88. package/dist/App.jqj5a094.js +0 -46
  89. package/dist/App.mc7xf85h.js +0 -4
  90. package/dist/App.myxqcj9x.js +0 -4
  91. package/dist/App.nm91r1mp.js +0 -13
  92. package/dist/App.p02f4ret.js +0 -1
  93. package/dist/App.qcknavjz.js +0 -221
  94. package/dist/App.vc7vfhg4.js +0 -4
  95. package/dist/App.z4s9zkw5.js +0 -4
  96. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  97. package/dist/McpPage.8vc97z0b.js +0 -3
  98. package/dist/SettingsPage.p61bz8kd.js +0 -3
  99. package/dist/SkillsPage.r9x43g3g.js +0 -3
  100. package/dist/TasksPage.1e0zkye4.js +0 -3
  101. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  102. package/dist/TestsPage.d4xy504e.js +0 -3
  103. 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, cache_creation_cost: 3.75, cache_read_cost: 0.3 },
16
+ { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", input_cost: 3, output_cost: 15, cache_creation_cost: 3.75, cache_read_cost: 0.3 },
17
+ { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)", input_cost: 0.8, output_cost: 4, cache_creation_cost: 1, cache_read_cost: 0.08 },
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,21 @@ 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, cache_creation_cost, cache_read_cost } in USD. */
237
+ export function getModelCost(provider: string, model: string): { input_cost: number; output_cost: number; cache_creation_cost: number; cache_read_cost: number } {
238
+ const providerDef = PROVIDERS[provider as ProviderId];
239
+ if (!providerDef || !("models" in providerDef)) return { input_cost: 0, output_cost: 0, cache_creation_cost: 0, cache_read_cost: 0 };
240
+ const modelDef = (providerDef.models as ReadonlyArray<{ value: string; input_cost?: number; output_cost?: number; cache_creation_cost?: number; cache_read_cost?: number }>)
241
+ .find(m => m.value === model);
242
+ if (!modelDef) return { input_cost: 0, output_cost: 0, cache_creation_cost: 0, cache_read_cost: 0 };
243
+ return {
244
+ input_cost: modelDef.input_cost ?? 0,
245
+ output_cost: modelDef.output_cost ?? 0,
246
+ cache_creation_cost: modelDef.cache_creation_cost ?? 0,
247
+ cache_read_cost: modelDef.cache_read_cost ?? 0,
248
+ };
249
+ }
250
+
220
251
  // Provider Keys Management
221
252
  export const ProviderKeys = {
222
253
  // Save an API key (encrypts before storing)
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "bun";
2
2
  import { join } from "path";
3
3
  import { homedir } from "os";
4
- import { mkdirSync, existsSync, rmSync } from "fs";
4
+ import { mkdirSync, existsSync, rmSync, writeFileSync, readFileSync } from "fs";
5
5
  import { agentProcesses, agentsStarting, getBinaryPathForAgent, getBinaryStatus, BIN_DIR, telemetryBroadcaster, isShuttingDown, type TelemetryEvent } from "../../server";
6
6
  import { AgentDB, McpServerDB, SkillDB, SubscriptionDB, TelemetryDB, generateId, getMultiAgentConfig, getOperatorConfig, type Agent, type Project } from "../../db";
7
7
  import { ProviderKeys, PROVIDERS, type ProviderId } from "../../providers";
@@ -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}`,
@@ -545,7 +544,9 @@ export async function startAgentProcess(
545
544
 
546
545
  // Build environment with provider key and agent API key
547
546
  // CONFIG_PATH ensures each agent has its own config file (prevents sharing)
547
+ // Remove stale persisted config — platform is source of truth and pushes fresh config after boot
548
548
  const agentConfigPath = join(agentDataDir, "agent-config.json");
549
+ rmSync(agentConfigPath, { force: true });
549
550
  const env: Record<string, string> = {
550
551
  ...process.env as Record<string, string>,
551
552
  PORT: String(port),
@@ -566,8 +567,19 @@ export async function startAgentProcess(
566
567
  // Get binary path dynamically (allows hot-reload of new binary versions)
567
568
  const binaryPath = getBinaryPathForAgent();
568
569
 
570
+ // Write VERSION file so the binary knows its version (ldflags not set in npm builds)
571
+ try {
572
+ const { dirname } = await import("path");
573
+ const pkgJsonPath = require.resolve("@apteva/agent-linux-x64/package.json");
574
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
575
+ if (pkg.version) {
576
+ writeFileSync(join(agentDataDir, "VERSION"), pkg.version);
577
+ }
578
+ } catch {}
579
+
569
580
  const proc = spawn({
570
581
  cmd: [binaryPath],
582
+ cwd: agentDataDir,
571
583
  env,
572
584
  stdout: "ignore",
573
585
  stderr: "ignore",
@@ -645,6 +657,15 @@ export async function startAgentProcess(
645
657
  }
646
658
  }
647
659
 
660
+ // Strip legacy "mode" field from multi-agent config
661
+ function cleanFeatures(features: Agent["features"]): Agent["features"] {
662
+ if (features.agents && typeof features.agents === "object") {
663
+ const { mode, ...rest } = features.agents as any;
664
+ return { ...features, agents: rest };
665
+ }
666
+ return features;
667
+ }
668
+
648
669
  // Transform DB agent to API response format (camelCase for frontend compatibility)
649
670
  // Uses batch queries + light MCP loading (no decryption) for performance
650
671
  export function toApiAgent(agent: Agent) {
@@ -688,7 +709,7 @@ export function toApiAgent(agent: Agent) {
688
709
  systemPrompt: agent.system_prompt,
689
710
  status: agent.status,
690
711
  port: agent.port,
691
- features: agent.features,
712
+ features: cleanFeatures(agent.features),
692
713
  mcpServers: agent.mcp_servers,
693
714
  mcpServerDetails,
694
715
  skills: agent.skills,
@@ -735,7 +756,7 @@ export function toApiAgentsBatch(agents: Agent[]) {
735
756
  return {
736
757
  id: agent.id, name: agent.name, model: agent.model, provider: agent.provider,
737
758
  systemPrompt: agent.system_prompt, status: agent.status, port: agent.port,
738
- features: agent.features, mcpServers: agent.mcp_servers, mcpServerDetails,
759
+ features: cleanFeatures(agent.features), mcpServers: agent.mcp_servers, mcpServerDetails,
739
760
  skills: agent.skills, skillDetails, subscriptions, projectId: agent.project_id,
740
761
  createdAt: agent.created_at, updatedAt: agent.updated_at,
741
762
  };
@@ -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(
@@ -33,8 +34,41 @@ export async function handleTelemetryRoutes(
33
34
  return json({ error: "agent_id and events are required" }, 400);
34
35
  }
35
36
 
37
+ // Debug: log raw incoming events
38
+ for (const event of body.events) {
39
+ if (event.category === "LLM") {
40
+ console.log(`[telemetry] RAW LLM event from ${body.agent_id}: ${JSON.stringify(event)}`);
41
+ }
42
+ }
43
+
36
44
  // Filter out debug events - too noisy
37
45
  const filteredEvents = body.events.filter(e => e.level !== "debug");
46
+
47
+ // Compute cost per LLM event if cost tracking is enabled
48
+ const costTrackingEnabled = process.env.COST_TRACKING_ENABLED !== "false";
49
+ if (costTrackingEnabled) {
50
+ const agent = AgentDB.findById(body.agent_id);
51
+ if (agent) {
52
+ const pricing = getModelCost(agent.provider, agent.model);
53
+ for (const event of filteredEvents) {
54
+ if (event.category === "LLM" && event.data) {
55
+ const inputTokens = (event.data.input_tokens as number) || 0;
56
+ const outputTokens = (event.data.output_tokens as number) || 0;
57
+ const cacheCreationTokens = (event.data.cache_creation_tokens as number) || 0;
58
+ const cacheReadTokens = (event.data.cache_read_tokens as number) || 0;
59
+ const reasoningTokens = (event.data.reasoning_tokens as number) || 0;
60
+ (event as any).cost = (
61
+ inputTokens * pricing.input_cost +
62
+ outputTokens * pricing.output_cost +
63
+ cacheCreationTokens * pricing.cache_creation_cost +
64
+ cacheReadTokens * pricing.cache_read_cost +
65
+ reasoningTokens * pricing.output_cost
66
+ ) / 1_000_000;
67
+ }
68
+ }
69
+ }
70
+ }
71
+
38
72
  const inserted = TelemetryDB.insertBatch(body.agent_id, filteredEvents);
39
73
 
40
74
  // Broadcast to SSE clients
@@ -116,7 +150,7 @@ export async function handleTelemetryRoutes(
116
150
  project_id: projectIdParam === "null" ? null : projectIdParam || undefined,
117
151
  since: url.searchParams.get("since") || undefined,
118
152
  until: url.searchParams.get("until") || undefined,
119
- group_by: (url.searchParams.get("group_by") as "agent" | "day") || undefined,
153
+ group_by: (url.searchParams.get("group_by") as "agent" | "day" | "project") || undefined,
120
154
  });
121
155
  return json({ usage });
122
156
  }
@@ -129,6 +163,8 @@ export async function handleTelemetryRoutes(
129
163
  const stats = TelemetryDB.getStats({
130
164
  agentId,
131
165
  projectId: projectIdParam === "null" ? null : projectIdParam || undefined,
166
+ since: url.searchParams.get("since") || undefined,
167
+ until: url.searchParams.get("until") || undefined,
132
168
  });
133
169
  return json({ stats });
134
170
  }
@@ -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
+ }