@desplega.ai/agent-swarm 1.79.4 → 1.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openapi.json +496 -32
- package/package.json +14 -6
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +102 -31
- package/src/be/migrations/063_cost_context_schema_relax.sql +133 -0
- package/src/be/migrations/064_scripts.sql +39 -0
- package/src/be/migrations/065_script_embeddings.sql +7 -0
- package/src/be/pricing-normalize.ts +81 -0
- package/src/be/scripts/db.ts +391 -0
- package/src/be/scripts/embeddings.ts +231 -0
- package/src/be/scripts/maintenance.ts +9 -0
- package/src/be/scripts/typecheck.ts +193 -0
- package/src/be/seed-pricing.ts +293 -0
- package/src/cli.tsx +22 -5
- package/src/commands/artifact.ts +3 -2
- package/src/commands/claude-managed-setup.ts +21 -4
- package/src/commands/codex-login.ts +5 -3
- package/src/commands/onboard.tsx +2 -1
- package/src/commands/runner.ts +663 -246
- package/src/commands/setup.tsx +5 -3
- package/src/hooks/hook.ts +4 -3
- package/src/http/context.ts +6 -2
- package/src/http/index.ts +126 -68
- package/src/http/memory.ts +28 -0
- package/src/http/openapi.ts +1 -0
- package/src/http/page-proxy.ts +2 -1
- package/src/http/route-def.ts +1 -0
- package/src/http/schedules.ts +37 -0
- package/src/http/scripts.ts +381 -0
- package/src/http/session-data.ts +74 -23
- package/src/linear/outbound.ts +9 -2
- package/src/otel-impl.ts +200 -0
- package/src/otel.ts +132 -0
- package/src/providers/claude-adapter.ts +52 -6
- package/src/providers/claude-managed-adapter.ts +43 -17
- package/src/providers/claude-managed-pricing.ts +34 -0
- package/src/providers/codex-adapter.ts +38 -27
- package/src/providers/codex-models.ts +22 -3
- package/src/providers/devin-adapter.ts +11 -0
- package/src/providers/opencode-adapter.ts +31 -7
- package/src/providers/pi-mono-adapter.ts +39 -7
- package/src/providers/pricing-sources.md +52 -0
- package/src/providers/swarm-events-shared.ts +8 -4
- package/src/providers/types.ts +33 -10
- package/src/scripts-runtime/ctx.ts +23 -0
- package/src/scripts-runtime/eval-harness.ts +39 -0
- package/src/scripts-runtime/executors/native.ts +229 -0
- package/src/scripts-runtime/executors/registry.ts +16 -0
- package/src/scripts-runtime/executors/types.ts +63 -0
- package/src/scripts-runtime/extract-signature.ts +81 -0
- package/src/scripts-runtime/import-allowlist.ts +109 -0
- package/src/scripts-runtime/loader.ts +96 -0
- package/src/scripts-runtime/redacted.ts +48 -0
- package/src/scripts-runtime/sdk-allowlist.ts +29 -0
- package/src/scripts-runtime/stdlib/fetch.ts +46 -0
- package/src/scripts-runtime/stdlib/glob.ts +8 -0
- package/src/scripts-runtime/stdlib/grep.ts +34 -0
- package/src/scripts-runtime/stdlib/index.ts +16 -0
- package/src/scripts-runtime/stdlib/table.ts +17 -0
- package/src/scripts-runtime/swarm-config.ts +35 -0
- package/src/scripts-runtime/swarm-sdk.ts +197 -0
- package/src/scripts-runtime/types/stdlib.d.ts +104 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +86 -0
- package/src/server.ts +18 -0
- package/src/tests/api-key.test.ts +33 -0
- package/src/tests/claude-managed-adapter.test.ts +17 -3
- package/src/tests/claude-managed-setup.test.ts +10 -1
- package/src/tests/codex-adapter.test.ts +20 -19
- package/src/tests/codex-login.test.ts +1 -1
- package/src/tests/context-snapshot.test.ts +2 -2
- package/src/tests/context-window.test.ts +65 -1
- package/src/tests/devin-adapter.test.ts +2 -0
- package/src/tests/http/context-routes.test.ts +161 -0
- package/src/tests/linear-outbound-sync.test.ts +109 -0
- package/src/tests/mcp-tools.test.ts +69 -0
- package/src/tests/migration-063-schema-relax.test.ts +109 -0
- package/src/tests/opencode-adapter.test.ts +146 -1
- package/src/tests/otel-impl-secret-scrubbing.test.ts +33 -0
- package/src/tests/pages-view-count.test.ts +30 -5
- package/src/tests/providers/codex-cost.test.ts +18 -0
- package/src/tests/providers/opencode-cost.test.ts +74 -0
- package/src/tests/providers/pi-cost.test.ts +128 -0
- package/src/tests/redacted.test.ts +29 -0
- package/src/tests/runner-tool-spans.test.ts +268 -0
- package/src/tests/script-executor-conformance.test.ts +142 -0
- package/src/tests/script-executor-registry.test.ts +17 -0
- package/src/tests/scripts-db.test.ts +329 -0
- package/src/tests/scripts-embeddings.test.ts +291 -0
- package/src/tests/scripts-extract-signature.test.ts +47 -0
- package/src/tests/scripts-http.test.ts +350 -0
- package/src/tests/scripts-import-allowlist.test.ts +55 -0
- package/src/tests/scripts-mcp-e2e.test.ts +269 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +44 -0
- package/src/tests/scripts-runtime.test.ts +289 -0
- package/src/tests/sdk-allowlist.test.ts +59 -0
- package/src/tests/secret-scrubber.test.ts +54 -1
- package/src/tests/session-costs-codex-recompute.test.ts +35 -22
- package/src/tests/session-costs-model-key-normalize.test.ts +271 -0
- package/src/tests/session-costs-recompute-all-providers.test.ts +170 -0
- package/src/tests/store-progress-cost.test.ts +6 -1
- package/src/tests/swarm-config.test.ts +38 -0
- package/src/tests/tool-annotations.test.ts +2 -2
- package/src/tests/tool-call-progress.test.ts +30 -0
- package/src/tests/workflow-e2e.test.ts +218 -0
- package/src/tests/workflow-executors.test.ts +32 -2
- package/src/tests/workflow-input-redaction.test.ts +232 -0
- package/src/tests/workflow-swarm-script.test.ts +273 -0
- package/src/tools/memory-rate.ts +2 -1
- package/src/tools/script-common.ts +88 -0
- package/src/tools/script-delete.ts +35 -0
- package/src/tools/script-query-types.ts +37 -0
- package/src/tools/script-run.ts +43 -0
- package/src/tools/script-search.ts +32 -0
- package/src/tools/script-upsert.ts +43 -0
- package/src/tools/store-progress.ts +16 -60
- package/src/tools/tool-config.ts +7 -0
- package/src/tools/utils.ts +65 -12
- package/src/types.ts +122 -10
- package/src/utils/api-key.ts +28 -0
- package/src/utils/context-window.ts +104 -4
- package/src/utils/page-session.ts +8 -6
- package/src/utils/secret-scrubber.ts +29 -1
- package/src/workflows/engine.ts +12 -4
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/script.ts +12 -1
- package/src/workflows/executors/swarm-script.ts +170 -0
- package/src/workflows/input.ts +65 -0
- package/src/workflows/recovery.ts +31 -3
- package/src/workflows/resume.ts +43 -5
package/src/commands/setup.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Spinner, TextInput } from "@inkjs/ui";
|
|
3
3
|
import { Box, Text, useApp } from "ink";
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { getApiKey } from "../utils/api-key.ts";
|
|
5
6
|
import {
|
|
6
7
|
createDefaultMcpJson,
|
|
7
8
|
createDefaultSettingsLocal,
|
|
@@ -47,7 +48,7 @@ export function Setup({ dryRun = false, restore = false, yes = false }: SetupPro
|
|
|
47
48
|
const { exit } = useApp();
|
|
48
49
|
const [state, setState] = useState<SetupState>({
|
|
49
50
|
step: restore ? "restoring" : "check_dirs",
|
|
50
|
-
token: yes ?
|
|
51
|
+
token: yes ? getApiKey() : "",
|
|
51
52
|
agentId: yes ? process.env.AGENT_ID || "" : "",
|
|
52
53
|
existingToken: "",
|
|
53
54
|
existingAgentId: "",
|
|
@@ -258,14 +259,15 @@ export function Setup({ dryRun = false, restore = false, yes = false }: SetupPro
|
|
|
258
259
|
|
|
259
260
|
// In non-interactive mode (yes=true), skip prompts and go directly to updating
|
|
260
261
|
if (yes) {
|
|
261
|
-
const token =
|
|
262
|
+
const token = getApiKey();
|
|
262
263
|
const agentId = process.env.AGENT_ID;
|
|
263
264
|
|
|
264
265
|
if (!token) {
|
|
265
266
|
setState((s) => ({
|
|
266
267
|
...s,
|
|
267
268
|
step: "error",
|
|
268
|
-
error:
|
|
269
|
+
error:
|
|
270
|
+
"AGENT_SWARM_API_KEY (or legacy API_KEY) environment variable is required in non-interactive mode (-y/--yes)",
|
|
269
271
|
}));
|
|
270
272
|
return;
|
|
271
273
|
}
|
package/src/hooks/hook.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type RetrievalRow,
|
|
11
11
|
} from "../be/memory/raters/llm";
|
|
12
12
|
import type { Agent } from "../types";
|
|
13
|
+
import { getApiKey } from "../utils/api-key";
|
|
13
14
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
14
15
|
import { checkToolLoop, clearToolHistory } from "./tool-loop-detection";
|
|
15
16
|
|
|
@@ -150,7 +151,7 @@ async function fetchTaskDetails(
|
|
|
150
151
|
taskId: string,
|
|
151
152
|
): Promise<{ id: string; task: string; progress?: string } | null> {
|
|
152
153
|
const apiUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
153
|
-
const apiKey =
|
|
154
|
+
const apiKey = getApiKey();
|
|
154
155
|
const headers: Record<string, string> = {};
|
|
155
156
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
156
157
|
|
|
@@ -301,7 +302,7 @@ export async function runStopHookSessionSummary(
|
|
|
301
302
|
const { taskContext, taskId } = await resolveStopHookTaskContext(env);
|
|
302
303
|
|
|
303
304
|
const apiUrl = env.MCP_BASE_URL || `http://localhost:${env.PORT || "3013"}`;
|
|
304
|
-
const apiKey = env
|
|
305
|
+
const apiKey = getApiKey(env);
|
|
305
306
|
|
|
306
307
|
// Memory-rater v1.5 step-4: piggyback per-memory ratings on the
|
|
307
308
|
// existing summary call when MEMORY_RATERS includes `llm`.
|
|
@@ -1152,7 +1153,7 @@ ${hasAgentIdHeader() ? `You have a pre-defined agent ID via header: ${mcpConfig?
|
|
|
1152
1153
|
try {
|
|
1153
1154
|
const apiUrl =
|
|
1154
1155
|
process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
1155
|
-
const apiKey =
|
|
1156
|
+
const apiKey = getApiKey();
|
|
1156
1157
|
const fileContent = await Bun.file(editedPath).text();
|
|
1157
1158
|
const isShared = editedPath.startsWith("/workspace/shared/");
|
|
1158
1159
|
const fileName = editedPath.split("/").pop() ?? "unnamed";
|
package/src/http/context.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getContextSummaryByTaskId,
|
|
7
7
|
getTaskById,
|
|
8
8
|
} from "../be/db";
|
|
9
|
-
import { ContextSnapshotEventTypeSchema } from "../types";
|
|
9
|
+
import { ContextFormulaSchema, ContextSnapshotEventTypeSchema } from "../types";
|
|
10
10
|
import { route } from "./route-def";
|
|
11
11
|
import { json, jsonError } from "./utils";
|
|
12
12
|
|
|
@@ -25,10 +25,13 @@ const postContext = route({
|
|
|
25
25
|
contextUsedTokens: z.number().int().min(0).optional(),
|
|
26
26
|
contextTotalTokens: z.number().int().min(0).optional(),
|
|
27
27
|
contextPercent: z.number().min(0).max(100).optional(),
|
|
28
|
-
compactTrigger: z.enum(["auto", "manual"]).optional(),
|
|
28
|
+
compactTrigger: z.enum(["auto", "manual", "auto-inferred"]).optional(),
|
|
29
29
|
preCompactTokens: z.number().int().min(0).optional(),
|
|
30
30
|
cumulativeInputTokens: z.number().int().min(0).optional(),
|
|
31
31
|
cumulativeOutputTokens: z.number().int().min(0).optional(),
|
|
32
|
+
// Migration 063: adapters tag the formula they used so cross-provider
|
|
33
|
+
// comparisons can tell apples from oranges.
|
|
34
|
+
contextFormula: ContextFormulaSchema.optional(),
|
|
32
35
|
}),
|
|
33
36
|
responses: {
|
|
34
37
|
200: { description: "Snapshot recorded" },
|
|
@@ -91,6 +94,7 @@ export async function handleContext(
|
|
|
91
94
|
preCompactTokens: parsed.body.preCompactTokens,
|
|
92
95
|
cumulativeInputTokens: parsed.body.cumulativeInputTokens,
|
|
93
96
|
cumulativeOutputTokens: parsed.body.cumulativeOutputTokens,
|
|
97
|
+
contextFormula: parsed.body.contextFormula,
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
json(res, { ok: true, snapshotId: snapshot.id });
|
package/src/http/index.ts
CHANGED
|
@@ -14,8 +14,10 @@ import { initGitLab } from "../gitlab";
|
|
|
14
14
|
import { stopHeartbeat } from "../heartbeat";
|
|
15
15
|
import { initJira } from "../jira";
|
|
16
16
|
import { initLinear } from "../linear";
|
|
17
|
+
import { initOtel, isPollTracingEnabled, startSpan, withRemoteContext } from "../otel";
|
|
17
18
|
import { startSlackApp, stopSlackApp } from "../slack";
|
|
18
19
|
import { initTelemetry, telemetry } from "../telemetry";
|
|
20
|
+
import { getApiKey } from "../utils/api-key";
|
|
19
21
|
import { initWorkflows } from "../workflows";
|
|
20
22
|
import { handleActiveSessions } from "./active-sessions";
|
|
21
23
|
import { handleAgentRegister, handleAgentsRest } from "./agents";
|
|
@@ -44,6 +46,7 @@ import { handlePricing } from "./pricing";
|
|
|
44
46
|
import { handlePromptTemplates } from "./prompt-templates";
|
|
45
47
|
import { handleRepos } from "./repos";
|
|
46
48
|
import { handleSchedules } from "./schedules";
|
|
49
|
+
import { handleScripts } from "./scripts";
|
|
47
50
|
import { handleSessionData } from "./session-data";
|
|
48
51
|
import { handleSessions } from "./sessions";
|
|
49
52
|
import { handleSkills } from "./skills";
|
|
@@ -68,7 +71,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
68
71
|
});
|
|
69
72
|
|
|
70
73
|
const port = parseInt(process.env.PORT || process.argv[2] || "3013", 10);
|
|
71
|
-
const apiKey =
|
|
74
|
+
const apiKey = getApiKey();
|
|
72
75
|
|
|
73
76
|
// Use globalThis to persist state across hot reloads
|
|
74
77
|
const globalState = globalThis as typeof globalThis & {
|
|
@@ -89,6 +92,7 @@ const transports: Record<string, StreamableHTTPServerTransport> = globalState.__
|
|
|
89
92
|
const httpServer = createHttpServer(async (req, res) => {
|
|
90
93
|
const startTime = performance.now();
|
|
91
94
|
let statusCode = 200;
|
|
95
|
+
let spanEnded = false;
|
|
92
96
|
|
|
93
97
|
// Wrap writeHead to capture status code
|
|
94
98
|
const originalWriteHead = res.writeHead.bind(res);
|
|
@@ -113,76 +117,117 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
113
117
|
console.error(`[HTTP] ❌ ${req.method} ${req.url} → Error: ${err.message}`);
|
|
114
118
|
});
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
()
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
() => handleDbQuery(req, res, pathSegments, queryParams),
|
|
150
|
-
() => handleRepos(req, res, pathSegments, queryParams),
|
|
151
|
-
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
152
|
-
() => handleMcpServers(req, res, pathSegments, queryParams),
|
|
153
|
-
() => handleMcpOAuth(req, res, pathSegments, queryParams),
|
|
154
|
-
() => handleMemory(req, res, pathSegments, myAgentId),
|
|
155
|
-
() => handlePagesPublic(req, res, pathSegments, queryParams),
|
|
156
|
-
() => handlePageProxy(req, res),
|
|
157
|
-
() => handlePages(req, res, pathSegments, queryParams, myAgentId),
|
|
158
|
-
() => handleApiKeys(req, res, pathSegments, queryParams),
|
|
159
|
-
() => handleHeartbeat(req, res, pathSegments),
|
|
160
|
-
() => handleEvents(req, res, pathSegments, queryParams, myAgentId),
|
|
161
|
-
() => handleUsers(req, res, pathSegments, queryParams),
|
|
162
|
-
() => handleSessions(req, res, pathSegments, queryParams),
|
|
163
|
-
() => handleInboxState(req, res, pathSegments, queryParams),
|
|
164
|
-
() => handleTaskTemplates(req, res, pathSegments, queryParams),
|
|
165
|
-
() => handleMcp(req, res, transports),
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
for (const handler of handlers) {
|
|
170
|
-
if (await handler()) return;
|
|
120
|
+
await withRemoteContext(req.headers as Record<string, unknown>, async () => {
|
|
121
|
+
const reqPath = req.url?.split("?")[0] ?? "";
|
|
122
|
+
const skipSpan = reqPath === "/api/poll" && !isPollTracingEnabled();
|
|
123
|
+
const span = skipSpan
|
|
124
|
+
? null
|
|
125
|
+
: startSpan("http.server", {
|
|
126
|
+
"http.request.method": req.method ?? "",
|
|
127
|
+
"url.path": reqPath,
|
|
128
|
+
"agent.id": req.headers["x-agent-id"] as string | undefined,
|
|
129
|
+
"agentswarm.component": "api",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (span) {
|
|
133
|
+
res.on("finish", () => {
|
|
134
|
+
if (spanEnded) return;
|
|
135
|
+
spanEnded = true;
|
|
136
|
+
span.setAttributes({
|
|
137
|
+
"http.response.status_code": statusCode,
|
|
138
|
+
"agentswarm.http.duration_ms": Math.round((performance.now() - startTime) * 10) / 10,
|
|
139
|
+
});
|
|
140
|
+
if (statusCode >= 500) {
|
|
141
|
+
span.setStatus({ code: 2, message: `HTTP ${statusCode}` });
|
|
142
|
+
}
|
|
143
|
+
span.end();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
res.on("error", (err) => {
|
|
147
|
+
if (spanEnded) return;
|
|
148
|
+
spanEnded = true;
|
|
149
|
+
span.recordException(err);
|
|
150
|
+
span.setStatus({ code: 2, message: err.message });
|
|
151
|
+
span.end();
|
|
152
|
+
});
|
|
171
153
|
}
|
|
172
154
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
155
|
+
setCorsHeaders(req, res);
|
|
156
|
+
|
|
157
|
+
// ── Core routes (OPTIONS, health, auth, /me, /cancelled-tasks, /ping, /close) ──
|
|
158
|
+
if (await handleCore(req, res, req.headers["x-agent-id"] as string | undefined, apiKey)) return;
|
|
159
|
+
|
|
160
|
+
const pathSegments = getPathSegments(req.url || "");
|
|
161
|
+
const queryParams = parseQueryParams(req.url || "");
|
|
162
|
+
const myAgentId = req.headers["x-agent-id"] as string | undefined;
|
|
163
|
+
|
|
164
|
+
// ── Route handlers (order matters — first match wins) ──
|
|
165
|
+
const handlers: (() => Promise<boolean>)[] = [
|
|
166
|
+
() => handleAgentRegister(req, res, pathSegments, myAgentId),
|
|
167
|
+
() => handlePoll(req, res, pathSegments, queryParams, myAgentId),
|
|
168
|
+
() => handleSessionData(req, res, pathSegments, queryParams, myAgentId),
|
|
169
|
+
() => handleEcosystem(req, res, pathSegments, myAgentId),
|
|
170
|
+
() => handleTrackers(req, res, pathSegments),
|
|
171
|
+
() => handleWebhooks(req, res, pathSegments),
|
|
172
|
+
() => handleAgentsRest(req, res, pathSegments, queryParams, myAgentId),
|
|
173
|
+
() => handleBudgets(req, res, pathSegments, queryParams, myAgentId),
|
|
174
|
+
() => handleContext(req, res, pathSegments, queryParams, myAgentId),
|
|
175
|
+
() => handleTasks(req, res, pathSegments, queryParams, myAgentId),
|
|
176
|
+
() => handleStats(req, res, pathSegments, queryParams),
|
|
177
|
+
() => handleStatus(req, res, pathSegments, queryParams),
|
|
178
|
+
() => handleActiveSessions(req, res, pathSegments, queryParams, myAgentId),
|
|
179
|
+
() => handlePricing(req, res, pathSegments, queryParams, myAgentId),
|
|
180
|
+
() => handleSchedules(req, res, pathSegments, queryParams, myAgentId),
|
|
181
|
+
() => handleWorkflows(req, res, pathSegments, queryParams, myAgentId),
|
|
182
|
+
() => handleWorkflowEvents(req, res, pathSegments, queryParams),
|
|
183
|
+
() => handleApprovalRequests(req, res, pathSegments, queryParams),
|
|
184
|
+
() => handleConfig(req, res, pathSegments, queryParams),
|
|
185
|
+
() => handleKv(req, res, pathSegments, queryParams),
|
|
186
|
+
() => handleIntegrations(req, res, pathSegments),
|
|
187
|
+
() => handlePromptTemplates(req, res, pathSegments, queryParams),
|
|
188
|
+
() => handleDbQuery(req, res, pathSegments, queryParams),
|
|
189
|
+
() => handleRepos(req, res, pathSegments, queryParams),
|
|
190
|
+
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
191
|
+
() => handleScripts(req, res, pathSegments, queryParams, myAgentId),
|
|
192
|
+
() => handleMcpServers(req, res, pathSegments, queryParams),
|
|
193
|
+
() => handleMcpOAuth(req, res, pathSegments, queryParams),
|
|
194
|
+
() => handleMemory(req, res, pathSegments, myAgentId),
|
|
195
|
+
() => handlePagesPublic(req, res, pathSegments, queryParams),
|
|
196
|
+
() => handlePageProxy(req, res),
|
|
197
|
+
() => handlePages(req, res, pathSegments, queryParams, myAgentId),
|
|
198
|
+
() => handleApiKeys(req, res, pathSegments, queryParams),
|
|
199
|
+
() => handleHeartbeat(req, res, pathSegments),
|
|
200
|
+
() => handleEvents(req, res, pathSegments, queryParams, myAgentId),
|
|
201
|
+
() => handleUsers(req, res, pathSegments, queryParams),
|
|
202
|
+
() => handleSessions(req, res, pathSegments, queryParams),
|
|
203
|
+
() => handleInboxState(req, res, pathSegments, queryParams),
|
|
204
|
+
() => handleTaskTemplates(req, res, pathSegments, queryParams),
|
|
205
|
+
() => handleMcp(req, res, transports),
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
for (const handler of handlers) {
|
|
210
|
+
if (await handler()) return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── 404 ──
|
|
214
|
+
res.writeHead(404);
|
|
215
|
+
res.end("Not Found");
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (span) {
|
|
218
|
+
span.recordException(err);
|
|
219
|
+
span.setStatus({ code: 2, message: err instanceof Error ? err.message : String(err) });
|
|
220
|
+
}
|
|
221
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
222
|
+
console.error(`[HTTP] ❌ ${req.method} ${req.url} → ${message}`);
|
|
223
|
+
if (!res.headersSent) {
|
|
224
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
225
|
+
res.end(JSON.stringify({ error: message }));
|
|
226
|
+
} else if (!res.writableEnded) {
|
|
227
|
+
res.end();
|
|
228
|
+
}
|
|
184
229
|
}
|
|
185
|
-
}
|
|
230
|
+
});
|
|
186
231
|
});
|
|
187
232
|
|
|
188
233
|
// Store references in globalThis for hot reload persistence
|
|
@@ -250,9 +295,22 @@ try {
|
|
|
250
295
|
throw err;
|
|
251
296
|
}
|
|
252
297
|
|
|
298
|
+
// Phase 2 of the cost-tracking plan: project the vendored models.dev snapshot
|
|
299
|
+
// into pricing rows at boot. Lazy `getDb()` would also work, but doing it
|
|
300
|
+
// here surfaces the count in the boot log and makes the API ready to recompute
|
|
301
|
+
// USD before the first POST /api/session-costs lands.
|
|
302
|
+
try {
|
|
303
|
+
const { seedPricingFromModelsDev } = await import("../be/seed-pricing");
|
|
304
|
+
seedPricingFromModelsDev();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.error("[startup] Failed to seed pricing rows:", err);
|
|
307
|
+
}
|
|
308
|
+
|
|
253
309
|
// business-use initialization (no-op if envs not set)
|
|
254
310
|
initialize();
|
|
255
311
|
|
|
312
|
+
await initOtel("api");
|
|
313
|
+
|
|
256
314
|
httpServer
|
|
257
315
|
.listen(port, async () => {
|
|
258
316
|
console.log(`MCP HTTP server running on http://localhost:${port}/mcp`);
|
package/src/http/memory.ts
CHANGED
|
@@ -124,6 +124,20 @@ const deleteMemoryById = route({
|
|
|
124
124
|
},
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
const getMemoryById = route({
|
|
128
|
+
method: "get",
|
|
129
|
+
path: "/api/memory/{id}",
|
|
130
|
+
pattern: ["api", "memory", null],
|
|
131
|
+
summary: "Get a single memory by ID",
|
|
132
|
+
tags: ["Memory"],
|
|
133
|
+
auth: { apiKey: true, agentId: true },
|
|
134
|
+
params: z.object({ id: z.string().uuid() }),
|
|
135
|
+
responses: {
|
|
136
|
+
200: { description: "Memory details" },
|
|
137
|
+
404: { description: "Memory not found" },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
127
141
|
// Memory rater v1.5 — worker-facing rating endpoints. Plan:
|
|
128
142
|
// thoughts/taras/plans/2026-05-05-memory-rater-v1.5/step-3.md
|
|
129
143
|
//
|
|
@@ -618,5 +632,19 @@ export async function handleMemory(
|
|
|
618
632
|
return true;
|
|
619
633
|
}
|
|
620
634
|
|
|
635
|
+
if (getMemoryById.match(req.method, pathSegments)) {
|
|
636
|
+
const parsed = await getMemoryById.parse(req, res, pathSegments, new URLSearchParams());
|
|
637
|
+
if (!parsed) return true;
|
|
638
|
+
|
|
639
|
+
const memory = getMemoryStore().get(parsed.params.id);
|
|
640
|
+
if (!memory) {
|
|
641
|
+
jsonError(res, "Memory not found", 404);
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
json(res, { memory });
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
|
|
621
649
|
return false;
|
|
622
650
|
}
|
package/src/http/openapi.ts
CHANGED
|
@@ -64,6 +64,7 @@ export function generateOpenApiSpec(opts: OpenApiOptions): string {
|
|
|
64
64
|
registry.registerPath({
|
|
65
65
|
method: routeDef.method,
|
|
66
66
|
path: routeDef.path,
|
|
67
|
+
operationId: routeDef.operationId,
|
|
67
68
|
summary: routeDef.summary,
|
|
68
69
|
description: routeDef.description,
|
|
69
70
|
tags: routeDef.tags,
|
package/src/http/page-proxy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { getPage } from "../be/db";
|
|
3
|
+
import { getApiKey } from "../utils/api-key";
|
|
3
4
|
import { extractAndVerifyCookie } from "../utils/page-session";
|
|
4
5
|
import { route } from "./route-def";
|
|
5
6
|
import { jsonError } from "./utils";
|
|
@@ -147,7 +148,7 @@ export async function handlePageProxy(req: IncomingMessage, res: ServerResponse)
|
|
|
147
148
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
148
149
|
const targetUrl = `${baseUrl}${rewrittenPath}${queryPart}`;
|
|
149
150
|
|
|
150
|
-
const apiKey =
|
|
151
|
+
const apiKey = getApiKey();
|
|
151
152
|
// `X-Page-Id` is the trust anchor for page-scoped KV: only the page-proxy
|
|
152
153
|
// ever sets it (any external `X-Page-Id` header is dropped because we don't
|
|
153
154
|
// forward the original headers). The KV handler treats this as the highest-
|
package/src/http/route-def.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface RouteDef<
|
|
|
20
20
|
path: string; // OpenAPI-style: "/api/tasks/{id}"
|
|
21
21
|
pattern: readonly (string | null)[]; // matchRoute-style: ["api", "tasks", null]
|
|
22
22
|
exact?: boolean; // default true
|
|
23
|
+
operationId?: string;
|
|
23
24
|
summary: string;
|
|
24
25
|
description?: string;
|
|
25
26
|
tags: string[];
|
package/src/http/schedules.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getDb,
|
|
9
9
|
getScheduledTaskById,
|
|
10
10
|
getScheduledTaskByName,
|
|
11
|
+
getScheduledTasks,
|
|
11
12
|
updateScheduledTask,
|
|
12
13
|
} from "../be/db";
|
|
13
14
|
import { calculateNextRun } from "../scheduler/scheduler";
|
|
@@ -64,6 +65,29 @@ const runScheduleNow = route({
|
|
|
64
65
|
},
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
const listSchedules = route({
|
|
69
|
+
method: "get",
|
|
70
|
+
path: "/api/schedules",
|
|
71
|
+
pattern: ["api", "schedules"],
|
|
72
|
+
summary: "List schedules",
|
|
73
|
+
tags: ["Schedules"],
|
|
74
|
+
query: z.object({
|
|
75
|
+
enabled: z
|
|
76
|
+
.enum(["true", "false"])
|
|
77
|
+
.optional()
|
|
78
|
+
.transform((v) => (v === undefined ? undefined : v === "true")),
|
|
79
|
+
name: z.string().optional(),
|
|
80
|
+
scheduleType: z.enum(["recurring", "one_time"]).optional(),
|
|
81
|
+
hideCompleted: z
|
|
82
|
+
.enum(["true", "false"])
|
|
83
|
+
.optional()
|
|
84
|
+
.transform((v) => (v === undefined ? undefined : v === "true")),
|
|
85
|
+
}),
|
|
86
|
+
responses: {
|
|
87
|
+
200: { description: "List of schedules" },
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
67
91
|
const getSchedule = route({
|
|
68
92
|
method: "get",
|
|
69
93
|
path: "/api/schedules/{id}",
|
|
@@ -129,6 +153,19 @@ export async function handleSchedules(
|
|
|
129
153
|
queryParams: URLSearchParams,
|
|
130
154
|
_myAgentId: string | undefined,
|
|
131
155
|
): Promise<boolean> {
|
|
156
|
+
if (listSchedules.match(req.method, pathSegments)) {
|
|
157
|
+
const parsed = await listSchedules.parse(req, res, pathSegments, queryParams);
|
|
158
|
+
if (!parsed) return true;
|
|
159
|
+
const schedules = getScheduledTasks({
|
|
160
|
+
enabled: parsed.query.enabled,
|
|
161
|
+
name: parsed.query.name,
|
|
162
|
+
scheduleType: parsed.query.scheduleType,
|
|
163
|
+
hideCompleted: parsed.query.hideCompleted,
|
|
164
|
+
});
|
|
165
|
+
json(res, { schedules, count: schedules.length });
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
132
169
|
if (createSchedule.match(req.method, pathSegments)) {
|
|
133
170
|
const parsed = await createSchedule.parse(req, res, pathSegments, queryParams);
|
|
134
171
|
if (!parsed) return true;
|