@desplega.ai/agent-swarm 1.80.0 → 1.80.2
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 +399 -14
- package/package.json +3 -1
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +1 -1
- package/src/be/migrations/064_scripts.sql +39 -0
- package/src/be/migrations/065_script_embeddings.sql +7 -0
- package/src/be/migrations/066_scripts_args_json_schema.sql +1 -0
- package/src/be/scripts/db.ts +417 -0
- package/src/be/scripts/embeddings.ts +233 -0
- package/src/be/scripts/extract-schema.ts +55 -0
- package/src/be/scripts/maintenance.ts +9 -0
- package/src/be/scripts/typecheck.ts +199 -0
- package/src/cli.tsx +22 -5
- package/src/commands/artifact.ts +3 -2
- package/src/commands/claude-managed-setup.ts +2 -1
- package/src/commands/codex-login.ts +5 -3
- package/src/commands/onboard.tsx +2 -1
- package/src/commands/runner.ts +153 -20
- package/src/commands/setup.tsx +5 -3
- package/src/hooks/hook.ts +4 -3
- package/src/http/index.ts +40 -29
- 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 +388 -0
- package/src/linear/outbound.ts +9 -2
- package/src/otel.ts +5 -0
- package/src/providers/claude-adapter.ts +23 -1
- package/src/providers/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +23 -0
- package/src/scripts-runtime/eval-harness.ts +63 -0
- package/src/scripts-runtime/executors/native.ts +232 -0
- package/src/scripts-runtime/executors/registry.ts +16 -0
- package/src/scripts-runtime/executors/types.ts +63 -0
- package/src/scripts-runtime/extract-args-schema.ts +69 -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 +12 -0
- package/src/tests/api-key.test.ts +33 -0
- package/src/tests/codex-login.test.ts +1 -1
- package/src/tests/error-tracker.test.ts +44 -0
- package/src/tests/linear-outbound-sync.test.ts +109 -0
- package/src/tests/mcp-tools.test.ts +69 -0
- package/src/tests/rate-limit-event.test.ts +292 -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 +403 -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 +344 -0
- package/src/tests/sdk-allowlist.test.ts +59 -0
- package/src/tests/secret-scrubber.test.ts +35 -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/tool-config.ts +7 -0
- package/src/types.ts +61 -1
- package/src/utils/api-key.ts +28 -0
- package/src/utils/error-tracker.ts +58 -0
- package/src/utils/page-session.ts +8 -6
- package/src/utils/secret-scrubber.ts +22 -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/index.ts
CHANGED
|
@@ -14,9 +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, startSpan, withRemoteContext } from "../otel";
|
|
17
|
+
import { initOtel, isPollTracingEnabled, startSpan, withRemoteContext } from "../otel";
|
|
18
18
|
import { startSlackApp, stopSlackApp } from "../slack";
|
|
19
19
|
import { initTelemetry, telemetry } from "../telemetry";
|
|
20
|
+
import { getApiKey } from "../utils/api-key";
|
|
20
21
|
import { initWorkflows } from "../workflows";
|
|
21
22
|
import { handleActiveSessions } from "./active-sessions";
|
|
22
23
|
import { handleAgentRegister, handleAgentsRest } from "./agents";
|
|
@@ -45,6 +46,7 @@ import { handlePricing } from "./pricing";
|
|
|
45
46
|
import { handlePromptTemplates } from "./prompt-templates";
|
|
46
47
|
import { handleRepos } from "./repos";
|
|
47
48
|
import { handleSchedules } from "./schedules";
|
|
49
|
+
import { handleScripts } from "./scripts";
|
|
48
50
|
import { handleSessionData } from "./session-data";
|
|
49
51
|
import { handleSessions } from "./sessions";
|
|
50
52
|
import { handleSkills } from "./skills";
|
|
@@ -69,7 +71,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
const port = parseInt(process.env.PORT || process.argv[2] || "3013", 10);
|
|
72
|
-
const apiKey =
|
|
74
|
+
const apiKey = getApiKey();
|
|
73
75
|
|
|
74
76
|
// Use globalThis to persist state across hot reloads
|
|
75
77
|
const globalState = globalThis as typeof globalThis & {
|
|
@@ -116,33 +118,39 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
116
118
|
});
|
|
117
119
|
|
|
118
120
|
await withRemoteContext(req.headers as Record<string, unknown>, async () => {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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();
|
|
132
144
|
});
|
|
133
|
-
if (statusCode >= 500) {
|
|
134
|
-
span.setStatus({ code: 2, message: `HTTP ${statusCode}` });
|
|
135
|
-
}
|
|
136
|
-
span.end();
|
|
137
|
-
});
|
|
138
145
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
});
|
|
153
|
+
}
|
|
146
154
|
|
|
147
155
|
setCorsHeaders(req, res);
|
|
148
156
|
|
|
@@ -180,6 +188,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
180
188
|
() => handleDbQuery(req, res, pathSegments, queryParams),
|
|
181
189
|
() => handleRepos(req, res, pathSegments, queryParams),
|
|
182
190
|
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
191
|
+
() => handleScripts(req, res, pathSegments, queryParams, myAgentId),
|
|
183
192
|
() => handleMcpServers(req, res, pathSegments, queryParams),
|
|
184
193
|
() => handleMcpOAuth(req, res, pathSegments, queryParams),
|
|
185
194
|
() => handleMemory(req, res, pathSegments, myAgentId),
|
|
@@ -205,8 +214,10 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
205
214
|
res.writeHead(404);
|
|
206
215
|
res.end("Not Found");
|
|
207
216
|
} catch (err) {
|
|
208
|
-
span
|
|
209
|
-
|
|
217
|
+
if (span) {
|
|
218
|
+
span.recordException(err);
|
|
219
|
+
span.setStatus({ code: 2, message: err instanceof Error ? err.message : String(err) });
|
|
220
|
+
}
|
|
210
221
|
const message = err instanceof Error ? err.message : String(err);
|
|
211
222
|
console.error(`[HTTP] ❌ ${req.method} ${req.url} → ${message}`);
|
|
212
223
|
if (!res.headersSent) {
|
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;
|