@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.
Files changed (100) hide show
  1. package/openapi.json +399 -14
  2. package/package.json +3 -1
  3. package/src/artifact-sdk/server.ts +2 -1
  4. package/src/be/db.ts +1 -1
  5. package/src/be/migrations/064_scripts.sql +39 -0
  6. package/src/be/migrations/065_script_embeddings.sql +7 -0
  7. package/src/be/migrations/066_scripts_args_json_schema.sql +1 -0
  8. package/src/be/scripts/db.ts +417 -0
  9. package/src/be/scripts/embeddings.ts +233 -0
  10. package/src/be/scripts/extract-schema.ts +55 -0
  11. package/src/be/scripts/maintenance.ts +9 -0
  12. package/src/be/scripts/typecheck.ts +199 -0
  13. package/src/cli.tsx +22 -5
  14. package/src/commands/artifact.ts +3 -2
  15. package/src/commands/claude-managed-setup.ts +2 -1
  16. package/src/commands/codex-login.ts +5 -3
  17. package/src/commands/onboard.tsx +2 -1
  18. package/src/commands/runner.ts +153 -20
  19. package/src/commands/setup.tsx +5 -3
  20. package/src/hooks/hook.ts +4 -3
  21. package/src/http/index.ts +40 -29
  22. package/src/http/memory.ts +28 -0
  23. package/src/http/openapi.ts +1 -0
  24. package/src/http/page-proxy.ts +2 -1
  25. package/src/http/route-def.ts +1 -0
  26. package/src/http/schedules.ts +37 -0
  27. package/src/http/scripts.ts +388 -0
  28. package/src/linear/outbound.ts +9 -2
  29. package/src/otel.ts +5 -0
  30. package/src/providers/claude-adapter.ts +23 -1
  31. package/src/providers/types.ts +8 -0
  32. package/src/scripts-runtime/ctx.ts +23 -0
  33. package/src/scripts-runtime/eval-harness.ts +63 -0
  34. package/src/scripts-runtime/executors/native.ts +232 -0
  35. package/src/scripts-runtime/executors/registry.ts +16 -0
  36. package/src/scripts-runtime/executors/types.ts +63 -0
  37. package/src/scripts-runtime/extract-args-schema.ts +69 -0
  38. package/src/scripts-runtime/extract-signature.ts +81 -0
  39. package/src/scripts-runtime/import-allowlist.ts +109 -0
  40. package/src/scripts-runtime/loader.ts +96 -0
  41. package/src/scripts-runtime/redacted.ts +48 -0
  42. package/src/scripts-runtime/sdk-allowlist.ts +29 -0
  43. package/src/scripts-runtime/stdlib/fetch.ts +46 -0
  44. package/src/scripts-runtime/stdlib/glob.ts +8 -0
  45. package/src/scripts-runtime/stdlib/grep.ts +34 -0
  46. package/src/scripts-runtime/stdlib/index.ts +16 -0
  47. package/src/scripts-runtime/stdlib/table.ts +17 -0
  48. package/src/scripts-runtime/swarm-config.ts +35 -0
  49. package/src/scripts-runtime/swarm-sdk.ts +197 -0
  50. package/src/scripts-runtime/types/stdlib.d.ts +104 -0
  51. package/src/scripts-runtime/types/swarm-sdk.d.ts +86 -0
  52. package/src/server.ts +12 -0
  53. package/src/tests/api-key.test.ts +33 -0
  54. package/src/tests/codex-login.test.ts +1 -1
  55. package/src/tests/error-tracker.test.ts +44 -0
  56. package/src/tests/linear-outbound-sync.test.ts +109 -0
  57. package/src/tests/mcp-tools.test.ts +69 -0
  58. package/src/tests/rate-limit-event.test.ts +292 -0
  59. package/src/tests/redacted.test.ts +29 -0
  60. package/src/tests/runner-tool-spans.test.ts +268 -0
  61. package/src/tests/script-executor-conformance.test.ts +142 -0
  62. package/src/tests/script-executor-registry.test.ts +17 -0
  63. package/src/tests/scripts-db.test.ts +329 -0
  64. package/src/tests/scripts-embeddings.test.ts +291 -0
  65. package/src/tests/scripts-extract-signature.test.ts +47 -0
  66. package/src/tests/scripts-http.test.ts +403 -0
  67. package/src/tests/scripts-import-allowlist.test.ts +55 -0
  68. package/src/tests/scripts-mcp-e2e.test.ts +269 -0
  69. package/src/tests/scripts-runtime-secret-egress.test.ts +44 -0
  70. package/src/tests/scripts-runtime.test.ts +344 -0
  71. package/src/tests/sdk-allowlist.test.ts +59 -0
  72. package/src/tests/secret-scrubber.test.ts +35 -1
  73. package/src/tests/swarm-config.test.ts +38 -0
  74. package/src/tests/tool-annotations.test.ts +2 -2
  75. package/src/tests/tool-call-progress.test.ts +30 -0
  76. package/src/tests/workflow-e2e.test.ts +218 -0
  77. package/src/tests/workflow-executors.test.ts +32 -2
  78. package/src/tests/workflow-input-redaction.test.ts +232 -0
  79. package/src/tests/workflow-swarm-script.test.ts +273 -0
  80. package/src/tools/memory-rate.ts +2 -1
  81. package/src/tools/script-common.ts +88 -0
  82. package/src/tools/script-delete.ts +35 -0
  83. package/src/tools/script-query-types.ts +37 -0
  84. package/src/tools/script-run.ts +43 -0
  85. package/src/tools/script-search.ts +32 -0
  86. package/src/tools/script-upsert.ts +43 -0
  87. package/src/tools/tool-config.ts +7 -0
  88. package/src/types.ts +61 -1
  89. package/src/utils/api-key.ts +28 -0
  90. package/src/utils/error-tracker.ts +58 -0
  91. package/src/utils/page-session.ts +8 -6
  92. package/src/utils/secret-scrubber.ts +22 -1
  93. package/src/workflows/engine.ts +12 -4
  94. package/src/workflows/executors/index.ts +1 -0
  95. package/src/workflows/executors/registry.ts +2 -0
  96. package/src/workflows/executors/script.ts +12 -1
  97. package/src/workflows/executors/swarm-script.ts +170 -0
  98. package/src/workflows/input.ts +65 -0
  99. package/src/workflows/recovery.ts +31 -3
  100. package/src/workflows/resume.ts +43 -5
@@ -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 ? process.env.API_KEY || "" : "",
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 = process.env.API_KEY;
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: "API_KEY environment variable is required in non-interactive mode (-y/--yes)",
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 = process.env.API_KEY || "";
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.API_KEY || "";
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 = process.env.API_KEY || "";
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 = process.env.API_KEY || "";
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 span = startSpan("http.server", {
120
- "http.request.method": req.method ?? "",
121
- "url.path": req.url?.split("?")[0] ?? "",
122
- "agent.id": req.headers["x-agent-id"] as string | undefined,
123
- "agentswarm.component": "api",
124
- });
125
-
126
- res.on("finish", () => {
127
- if (spanEnded) return;
128
- spanEnded = true;
129
- span.setAttributes({
130
- "http.response.status_code": statusCode,
131
- "agentswarm.http.duration_ms": Math.round((performance.now() - startTime) * 10) / 10,
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
- res.on("error", (err) => {
140
- if (spanEnded) return;
141
- spanEnded = true;
142
- span.recordException(err);
143
- span.setStatus({ code: 2, message: err.message });
144
- span.end();
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.recordException(err);
209
- span.setStatus({ code: 2, message: err instanceof Error ? err.message : String(err) });
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) {
@@ -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
  }
@@ -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,
@@ -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 = process.env.API_KEY ?? "";
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-
@@ -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[];
@@ -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;