@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
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Defense-in-depth, not isolation. Accidental leaks through console.log,
3
+ * JSON.stringify, util.inspect, or returned JSON emit "<redacted>". A malicious
4
+ * script can still call Redacted.value() and exfiltrate the string; host-side
5
+ * env stripping plus scrubObject are the v1 safety net.
6
+ */
7
+ declare const redactedType: unique symbol;
8
+
9
+ export type Redacted<A> = object & { readonly [redactedType]?: A };
10
+
11
+ export type RedactedMeta = { type: "system" | "user"; isSecret: boolean };
12
+
13
+ const registry = new WeakMap<Redacted<unknown>, { value: unknown; meta: RedactedMeta }>();
14
+
15
+ const proto = {
16
+ toString() {
17
+ return "<redacted>";
18
+ },
19
+ toJSON() {
20
+ return "<redacted>";
21
+ },
22
+ [Symbol.for("nodejs.util.inspect.custom")]() {
23
+ return "<redacted>";
24
+ },
25
+ };
26
+
27
+ function getEntry<A>(self: Redacted<A>): { value: unknown; meta: RedactedMeta } {
28
+ const entry = registry.get(self);
29
+ if (!entry) throw new Error("Redacted value was not in registry");
30
+ return entry;
31
+ }
32
+
33
+ export const Redacted = {
34
+ make<A>(value: A, meta: RedactedMeta = { type: "user", isSecret: false }): Redacted<A> {
35
+ const redacted = Object.create(proto) as Redacted<A>;
36
+ registry.set(redacted, { value, meta });
37
+ return redacted;
38
+ },
39
+ value<A>(self: Redacted<A>): A {
40
+ return getEntry(self).value as A;
41
+ },
42
+ meta<A>(self: Redacted<A>): RedactedMeta {
43
+ return getEntry(self).meta;
44
+ },
45
+ isSecret<A>(self: Redacted<A>): boolean {
46
+ return Redacted.meta(self).isSecret;
47
+ },
48
+ } as const;
@@ -0,0 +1,29 @@
1
+ export const SDK_TOOL_NAME_MAP = {
2
+ memory_search: "memory-search",
3
+ memory_get: "memory-get",
4
+ memory_rate: "memory_rate",
5
+ task_list: "get-tasks",
6
+ task_get: "get-task-details",
7
+ task_storeProgress: "store-progress",
8
+ kv_get: "kv-get",
9
+ kv_set: "kv-set",
10
+ kv_del: "kv-delete",
11
+ kv_incr: "kv-incr",
12
+ kv_list: "kv-list",
13
+ repo_list: "get-repos",
14
+ schedule_list: "list-schedules",
15
+ script_search: "script-search",
16
+ script_run: "script-run",
17
+ } as const;
18
+
19
+ export const SDK_ALLOWLIST = Object.keys(SDK_TOOL_NAME_MAP) as Array<
20
+ keyof typeof SDK_TOOL_NAME_MAP
21
+ >;
22
+
23
+ export function isSdkToolAllowed(name: string): boolean {
24
+ return (SDK_ALLOWLIST as readonly string[]).includes(name);
25
+ }
26
+
27
+ export function mcpToolNameForSdkMethod(name: string): string {
28
+ return SDK_TOOL_NAME_MAP[name as keyof typeof SDK_TOOL_NAME_MAP] ?? name;
29
+ }
@@ -0,0 +1,46 @@
1
+ export type RuntimeFetchOptions = RequestInit & {
2
+ retries?: number;
3
+ timeoutMs?: number;
4
+ };
5
+
6
+ export async function runtimeFetch(
7
+ input: string | URL | Request,
8
+ options: RuntimeFetchOptions = {},
9
+ ): Promise<Response> {
10
+ const { retries = 3, timeoutMs = 30_000, signal, ...init } = options;
11
+ let lastError: unknown;
12
+
13
+ for (let attempt = 0; attempt < retries; attempt++) {
14
+ const controller = new AbortController();
15
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
16
+ const onAbort = () => controller.abort();
17
+ signal?.addEventListener("abort", onAbort, { once: true });
18
+
19
+ try {
20
+ const res = await fetch(input, { ...init, signal: controller.signal });
21
+ if (!res.ok && attempt < retries - 1 && res.status >= 500) {
22
+ lastError = new Error(`fetch failed with ${res.status}`);
23
+ continue;
24
+ }
25
+ return res;
26
+ } catch (error) {
27
+ lastError = error;
28
+ if (attempt === retries - 1) break;
29
+ } finally {
30
+ clearTimeout(timeout);
31
+ signal?.removeEventListener("abort", onAbort);
32
+ }
33
+ }
34
+
35
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
36
+ }
37
+
38
+ export async function runtimeFetchJson(
39
+ input: string | URL | Request,
40
+ options: RuntimeFetchOptions = {},
41
+ ): Promise<unknown> {
42
+ const res = await runtimeFetch(input, options);
43
+ const contentType = res.headers.get("content-type") ?? "";
44
+ if (contentType.includes("application/json")) return await res.json();
45
+ return await res.text();
46
+ }
@@ -0,0 +1,8 @@
1
+ export async function glob(pattern: string, cwd = process.cwd()): Promise<string[]> {
2
+ const globber = new Bun.Glob(pattern);
3
+ const matches: string[] = [];
4
+ for await (const match of globber.scan({ cwd })) {
5
+ matches.push(match);
6
+ }
7
+ return matches;
8
+ }
@@ -0,0 +1,34 @@
1
+ export type GrepMatch = {
2
+ path: string;
3
+ line: number;
4
+ text: string;
5
+ };
6
+
7
+ export async function grep(pattern: string, cwd = process.cwd()): Promise<GrepMatch[]> {
8
+ const proc = Bun.spawn(["rg", "--line-number", "--no-heading", pattern, "."], {
9
+ cwd,
10
+ stdout: "pipe",
11
+ stderr: "pipe",
12
+ });
13
+ const [stdout, stderr, exitCode] = await Promise.all([
14
+ new Response(proc.stdout).text(),
15
+ new Response(proc.stderr).text(),
16
+ proc.exited,
17
+ ]);
18
+
19
+ if (exitCode === 1) return [];
20
+ if (exitCode !== 0) {
21
+ const message = stderr.includes("No such file or directory")
22
+ ? "rg is not available on PATH"
23
+ : stderr.trim() || `rg exited with ${exitCode}`;
24
+ throw new Error(message);
25
+ }
26
+
27
+ return stdout
28
+ .split("\n")
29
+ .filter(Boolean)
30
+ .map((line) => {
31
+ const [path = "", lineNo = "0", ...rest] = line.split(":");
32
+ return { path, line: Number(lineNo), text: rest.join(":") };
33
+ });
34
+ }
@@ -0,0 +1,16 @@
1
+ import { Redacted } from "../redacted";
2
+ import { runtimeFetch, runtimeFetchJson } from "./fetch";
3
+ import { glob } from "./glob";
4
+ import { grep } from "./grep";
5
+ import { table } from "./table";
6
+
7
+ export const stdlib = {
8
+ fetch: runtimeFetch,
9
+ fetchJson: runtimeFetchJson,
10
+ grep,
11
+ glob,
12
+ table,
13
+ Redacted,
14
+ };
15
+
16
+ export { runtimeFetch as fetch, runtimeFetchJson as fetchJson, glob, grep, table, Redacted };
@@ -0,0 +1,17 @@
1
+ export function table(rows: Array<Record<string, unknown>>): string {
2
+ if (rows.length === 0) return "";
3
+
4
+ const headers = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
5
+ const renderedRows = rows.map((row) => headers.map((header) => String(row[header] ?? "")));
6
+ const widths = headers.map((header, index) =>
7
+ Math.max(header.length, ...renderedRows.map((row) => row[index]?.length ?? 0)),
8
+ );
9
+
10
+ const render = (cells: string[]) =>
11
+ cells.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(" ");
12
+ return [
13
+ render(headers),
14
+ render(widths.map((width) => "-".repeat(width))),
15
+ ...renderedRows.map(render),
16
+ ].join("\n");
17
+ }
@@ -0,0 +1,35 @@
1
+ import type { SwarmConfigPayload } from "./executors/types";
2
+ import { Redacted, type Redacted as RedactedValue } from "./redacted";
3
+
4
+ export class SwarmConfig {
5
+ readonly apiKey: RedactedValue<string>;
6
+ readonly agentId: RedactedValue<string>;
7
+ readonly mcpBaseUrl: RedactedValue<string>;
8
+
9
+ private readonly userValues: Map<string, RedactedValue<string>>;
10
+
11
+ constructor(payload: SwarmConfigPayload) {
12
+ this.apiKey = Redacted.make(payload.system.apiKey.value, {
13
+ type: "system",
14
+ isSecret: payload.system.apiKey.isSecret,
15
+ });
16
+ this.agentId = Redacted.make(payload.system.agentId.value, {
17
+ type: "system",
18
+ isSecret: payload.system.agentId.isSecret,
19
+ });
20
+ this.mcpBaseUrl = Redacted.make(payload.system.mcpBaseUrl.value, {
21
+ type: "system",
22
+ isSecret: payload.system.mcpBaseUrl.isSecret,
23
+ });
24
+ this.userValues = new Map(
25
+ Object.entries(payload.user ?? {}).map(([key, value]) => [
26
+ key,
27
+ Redacted.make(value.value, { type: "user", isSecret: value.isSecret }),
28
+ ]),
29
+ );
30
+ }
31
+
32
+ get<T = string>(key: string): RedactedValue<T> | undefined {
33
+ return this.userValues.get(key) as RedactedValue<T> | undefined;
34
+ }
35
+ }
@@ -0,0 +1,197 @@
1
+ import { scrubObject } from "../utils/secret-scrubber";
2
+ import { Redacted } from "./redacted";
3
+ import { isSdkToolAllowed } from "./sdk-allowlist";
4
+ import type { SwarmConfig } from "./swarm-config";
5
+
6
+ type BridgeRequest = {
7
+ method: string;
8
+ path: string;
9
+ body?: unknown;
10
+ };
11
+
12
+ function headers(config: SwarmConfig): Record<string, string> {
13
+ return {
14
+ Authorization: `Bearer ${Redacted.value(config.apiKey)}`,
15
+ "X-Agent-ID": Redacted.value(config.agentId),
16
+ "Content-Type": "application/json",
17
+ };
18
+ }
19
+
20
+ function argsRecord(args: unknown): Record<string, unknown> {
21
+ return args && typeof args === "object" ? (args as Record<string, unknown>) : {};
22
+ }
23
+
24
+ function appendQuery(path: string, query: Record<string, unknown>): string {
25
+ const params = new URLSearchParams();
26
+ for (const [key, value] of Object.entries(query)) {
27
+ if (value === undefined || value === null) continue;
28
+ params.set(key, String(value));
29
+ }
30
+ const encoded = params.toString();
31
+ return encoded ? `${path}?${encoded}` : path;
32
+ }
33
+
34
+ function kvPath(args: Record<string, unknown>, keyRequired = true): string {
35
+ const key = typeof args.key === "string" ? args.key : undefined;
36
+ if (keyRequired && !key) throw new Error("kv tool requires string `key`");
37
+ const namespace = typeof args.namespace === "string" ? args.namespace : undefined;
38
+ if (namespace) {
39
+ return key
40
+ ? `/api/kv/_/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`
41
+ : `/api/kv/_/${encodeURIComponent(namespace)}`;
42
+ }
43
+ return key ? `/api/kv/${encodeURIComponent(key)}` : "/api/kv";
44
+ }
45
+
46
+ function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
47
+ const body = argsRecord(args);
48
+ switch (name) {
49
+ case "memory_search":
50
+ return { method: "POST", path: "/api/memory/search", body };
51
+ case "memory_get": {
52
+ const memoryId = typeof body.memoryId === "string" ? body.memoryId : undefined;
53
+ if (!memoryId) throw new Error("memory_get requires string `memoryId`");
54
+ return { method: "GET", path: `/api/memory/${encodeURIComponent(memoryId)}` };
55
+ }
56
+ case "memory_rate": {
57
+ const event = {
58
+ memoryId: body.id,
59
+ signal: body.useful === false ? -1 : 1,
60
+ weight: 1,
61
+ source: "explicit-self",
62
+ reasoning: body.note ?? "",
63
+ ...(typeof body.taskId === "string" ? { taskId: body.taskId } : {}),
64
+ ...(typeof body.referencesSource === "string"
65
+ ? { referencesSource: body.referencesSource }
66
+ : {}),
67
+ };
68
+ return { method: "POST", path: "/api/memory/rate", body: { events: [event] } };
69
+ }
70
+ case "task_list":
71
+ return { method: "GET", path: appendQuery("/api/tasks", body) };
72
+ case "task_get": {
73
+ const taskId = typeof body.taskId === "string" ? body.taskId : undefined;
74
+ if (!taskId) throw new Error("task_get requires string `taskId`");
75
+ return { method: "GET", path: `/api/tasks/${encodeURIComponent(taskId)}` };
76
+ }
77
+ case "task_storeProgress": {
78
+ const taskId = typeof body.taskId === "string" ? body.taskId : undefined;
79
+ if (!taskId) throw new Error("task_storeProgress requires string `taskId`");
80
+ if (body.status === "completed" || body.status === "failed") {
81
+ return {
82
+ method: "POST",
83
+ path: `/api/tasks/${encodeURIComponent(taskId)}/finish`,
84
+ body: {
85
+ status: body.status,
86
+ output: body.output,
87
+ failureReason: body.failureReason,
88
+ },
89
+ };
90
+ }
91
+ return {
92
+ method: "POST",
93
+ path: `/api/tasks/${encodeURIComponent(taskId)}/progress`,
94
+ body: { progress: body.progress ?? "" },
95
+ };
96
+ }
97
+ case "kv_get":
98
+ return { method: "GET", path: kvPath(body) };
99
+ case "kv_set":
100
+ return {
101
+ method: "PUT",
102
+ path: kvPath(body),
103
+ body: {
104
+ value: body.value,
105
+ valueType: body.valueType,
106
+ expiresInSec: body.expiresInSec ?? body.ttlSeconds,
107
+ },
108
+ };
109
+ case "kv_del":
110
+ return { method: "DELETE", path: kvPath(body) };
111
+ case "kv_incr":
112
+ return { method: "POST", path: `${kvPath(body)}/incr`, body: { by: body.by } };
113
+ case "kv_list":
114
+ return {
115
+ method: "GET",
116
+ path: appendQuery(kvPath(body, false), {
117
+ prefix: body.prefix,
118
+ limit: body.limit,
119
+ offset: body.offset,
120
+ }),
121
+ };
122
+ case "repo_list":
123
+ return {
124
+ method: "GET",
125
+ path: appendQuery("/api/repos", { autoClone: body.autoClone, name: body.name }),
126
+ };
127
+ case "schedule_list":
128
+ return {
129
+ method: "GET",
130
+ path: appendQuery("/api/schedules", {
131
+ enabled: body.enabled,
132
+ name: body.name,
133
+ scheduleType: body.scheduleType,
134
+ hideCompleted: body.hideCompleted,
135
+ }),
136
+ };
137
+ case "script_search":
138
+ return { method: "POST", path: "/api/scripts/search", body };
139
+ case "script_run":
140
+ return { method: "POST", path: "/api/scripts/run", body };
141
+ default:
142
+ throw new Error(`Tool '${name}' is not exposed through the scripts SDK bridge`);
143
+ }
144
+ }
145
+
146
+ async function callBridgeApi(
147
+ name: string,
148
+ args: unknown,
149
+ config: SwarmConfig,
150
+ options: { throwOnError?: boolean } = {},
151
+ ): Promise<unknown> {
152
+ const baseUrl = Redacted.value(config.mcpBaseUrl).replace(/\/$/, "");
153
+ const request = bridgeRequestFor(name, args);
154
+
155
+ const res = await fetch(`${baseUrl}${request.path}`, {
156
+ method: request.method,
157
+ headers: headers(config),
158
+ body: request.body === undefined ? undefined : JSON.stringify(request.body),
159
+ });
160
+ const text = await res.text();
161
+ const data = text ? JSON.parse(text) : {};
162
+ if (!res.ok && options.throwOnError) {
163
+ const message =
164
+ data && typeof data === "object" && "error" in data
165
+ ? String((data as { error: unknown }).error)
166
+ : `api failed with ${res.status}`;
167
+ throw new Error(`swarm-sdk: ${name} failed with ${res.status}: ${message}`);
168
+ }
169
+ return scrubObject({ success: res.ok, status: res.status, data });
170
+ }
171
+
172
+ async function callTool(name: string, args: unknown, config: SwarmConfig): Promise<unknown> {
173
+ if (!isSdkToolAllowed(name)) {
174
+ throw new Error(
175
+ `Tool '${name}' is not exposed to scripts (lifecycle/cred tool); use the MCP surface directly if you're an agent`,
176
+ );
177
+ }
178
+
179
+ if (name === "script_search" || name === "script_run") {
180
+ return callBridgeApi(name, args, config, { throwOnError: true });
181
+ }
182
+
183
+ return callBridgeApi(name, args, config);
184
+ }
185
+
186
+ export function createSwarmSdk(
187
+ config: SwarmConfig,
188
+ ): Record<string, (args?: unknown) => Promise<unknown>> {
189
+ const target: Record<string, unknown> = {};
190
+ return new Proxy(target, {
191
+ get(target, prop) {
192
+ if (typeof prop !== "string") return undefined;
193
+ if (prop in target) return target[prop];
194
+ return (args?: unknown) => callTool(prop, args, config);
195
+ },
196
+ }) as Record<string, (args?: unknown) => Promise<unknown>>;
197
+ }
@@ -0,0 +1,104 @@
1
+ declare module "stdlib" {
2
+ export interface Redacted<T> {
3
+ readonly __redactedBrand?: T;
4
+ toString(): "<redacted>";
5
+ toJSON(): "<redacted>";
6
+ }
7
+ export const Redacted: {
8
+ value<T>(self: Redacted<T>): T;
9
+ meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
10
+ isSecret<T>(self: Redacted<T>): boolean;
11
+ };
12
+ export function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
13
+ export function fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
14
+ export function grep(pattern: string, files?: string | string[]): Promise<string>;
15
+ export function glob(pattern: string): Promise<string[]>;
16
+ export function table(rows: Array<Record<string, unknown>>): string;
17
+ }
18
+
19
+ declare module "swarm-sdk" {
20
+ export type JsonValue =
21
+ | null
22
+ | boolean
23
+ | number
24
+ | string
25
+ | JsonValue[]
26
+ | { [key: string]: JsonValue };
27
+ export type ScriptScope = "agent" | "global";
28
+ export type ScriptFsMode = "none" | "workspace-rw";
29
+
30
+ export interface Redacted<T> {
31
+ readonly __redactedBrand?: T;
32
+ toString(): "<redacted>";
33
+ toJSON(): "<redacted>";
34
+ }
35
+
36
+ export interface RedactedStatic {
37
+ value<T>(self: Redacted<T>): T;
38
+ meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
39
+ isSecret<T>(self: Redacted<T>): boolean;
40
+ }
41
+
42
+ export interface SwarmConfig {
43
+ apiKey: Redacted<string>;
44
+ agentId: Redacted<string>;
45
+ mcpBaseUrl: Redacted<string>;
46
+ get<T = string>(key: string): Redacted<T> | undefined;
47
+ }
48
+
49
+ export interface SwarmSdk {
50
+ memory_search(args: {
51
+ query: string;
52
+ scope?: "all" | "agent" | "swarm";
53
+ limit?: number;
54
+ source?: string;
55
+ }): Promise<unknown>;
56
+ memory_get(args: { memoryId: string }): Promise<unknown>;
57
+ memory_rate(args: { id: string; useful: boolean; note?: string }): Promise<unknown>;
58
+ task_list(args?: Record<string, unknown>): Promise<unknown>;
59
+ task_get(args: { taskId: string }): Promise<unknown>;
60
+ task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
61
+ kv_get(args: { key: string; namespace?: string }): Promise<unknown>;
62
+ kv_set(args: {
63
+ key: string;
64
+ value: unknown;
65
+ namespace?: string;
66
+ ttlSeconds?: number;
67
+ valueType?: "string" | "json" | "integer";
68
+ }): Promise<unknown>;
69
+ kv_del(args: { key: string; namespace?: string }): Promise<unknown>;
70
+ kv_incr(args: { key: string; by?: number; namespace?: string }): Promise<unknown>;
71
+ kv_list(args?: { prefix?: string; namespace?: string; limit?: number }): Promise<unknown>;
72
+ repo_list(args?: Record<string, unknown>): Promise<unknown>;
73
+ schedule_list(args?: Record<string, unknown>): Promise<unknown>;
74
+ script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
75
+ script_run(args: {
76
+ name?: string;
77
+ source?: string;
78
+ args?: unknown;
79
+ intent?: string;
80
+ scope?: ScriptScope;
81
+ fsMode?: ScriptFsMode;
82
+ }): Promise<unknown>;
83
+ }
84
+
85
+ export interface ScriptStdlib {
86
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
87
+ fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
88
+ grep(pattern: string, files?: string | string[]): Promise<string>;
89
+ glob(pattern: string): Promise<string[]>;
90
+ table(rows: Array<Record<string, unknown>>): string;
91
+ Redacted: RedactedStatic;
92
+ }
93
+
94
+ export interface ScriptLogger extends Console {}
95
+
96
+ export interface ScriptContext {
97
+ swarm: SwarmSdk & { config: SwarmConfig };
98
+ stdlib: ScriptStdlib;
99
+ logger: ScriptLogger;
100
+ }
101
+
102
+ // biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
103
+ export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
104
+ }
@@ -0,0 +1,86 @@
1
+ declare module "swarm-sdk" {
2
+ export type JsonValue =
3
+ | null
4
+ | boolean
5
+ | number
6
+ | string
7
+ | JsonValue[]
8
+ | { [key: string]: JsonValue };
9
+ export type ScriptScope = "agent" | "global";
10
+ export type ScriptFsMode = "none" | "workspace-rw";
11
+
12
+ export interface Redacted<T> {
13
+ readonly __redactedBrand?: T;
14
+ toString(): "<redacted>";
15
+ toJSON(): "<redacted>";
16
+ }
17
+
18
+ export interface RedactedStatic {
19
+ value<T>(self: Redacted<T>): T;
20
+ meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
21
+ isSecret<T>(self: Redacted<T>): boolean;
22
+ }
23
+
24
+ export interface SwarmConfig {
25
+ apiKey: Redacted<string>;
26
+ agentId: Redacted<string>;
27
+ mcpBaseUrl: Redacted<string>;
28
+ get<T = string>(key: string): Redacted<T> | undefined;
29
+ }
30
+
31
+ export interface SwarmSdk {
32
+ memory_search(args: {
33
+ query: string;
34
+ scope?: "all" | "agent" | "swarm";
35
+ limit?: number;
36
+ source?: string;
37
+ }): Promise<unknown>;
38
+ memory_get(args: { memoryId: string }): Promise<unknown>;
39
+ memory_rate(args: { id: string; useful: boolean; note?: string }): Promise<unknown>;
40
+ task_list(args?: Record<string, unknown>): Promise<unknown>;
41
+ task_get(args: { taskId: string }): Promise<unknown>;
42
+ task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
43
+ kv_get(args: { key: string; namespace?: string }): Promise<unknown>;
44
+ kv_set(args: {
45
+ key: string;
46
+ value: unknown;
47
+ namespace?: string;
48
+ ttlSeconds?: number;
49
+ valueType?: "string" | "json" | "integer";
50
+ }): Promise<unknown>;
51
+ kv_del(args: { key: string; namespace?: string }): Promise<unknown>;
52
+ kv_incr(args: { key: string; by?: number; namespace?: string }): Promise<unknown>;
53
+ kv_list(args?: { prefix?: string; namespace?: string; limit?: number }): Promise<unknown>;
54
+ repo_list(args?: Record<string, unknown>): Promise<unknown>;
55
+ schedule_list(args?: Record<string, unknown>): Promise<unknown>;
56
+ script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
57
+ script_run(args: {
58
+ name?: string;
59
+ source?: string;
60
+ args?: unknown;
61
+ intent?: string;
62
+ scope?: ScriptScope;
63
+ fsMode?: ScriptFsMode;
64
+ }): Promise<unknown>;
65
+ }
66
+
67
+ export interface ScriptStdlib {
68
+ fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
69
+ fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
70
+ grep(pattern: string, files?: string | string[]): Promise<string>;
71
+ glob(pattern: string): Promise<string[]>;
72
+ table(rows: Array<Record<string, unknown>>): string;
73
+ Redacted: RedactedStatic;
74
+ }
75
+
76
+ export interface ScriptLogger extends Console {}
77
+
78
+ export interface ScriptContext {
79
+ swarm: SwarmSdk & { config: SwarmConfig };
80
+ stdlib: ScriptStdlib;
81
+ logger: ScriptLogger;
82
+ }
83
+
84
+ // biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
85
+ export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
86
+ }
package/src/server.ts CHANGED
@@ -68,6 +68,11 @@ import {
68
68
  registerRunScheduleNowTool,
69
69
  registerUpdateScheduleTool,
70
70
  } from "./tools/schedules";
71
+ import { registerScriptDeleteTool } from "./tools/script-delete";
72
+ import { registerScriptQueryTypesTool } from "./tools/script-query-types";
73
+ import { registerScriptRunTool } from "./tools/script-run";
74
+ import { registerScriptSearchTool } from "./tools/script-search";
75
+ import { registerScriptUpsertTool } from "./tools/script-upsert";
71
76
  import { registerSendTaskTool } from "./tools/send-task";
72
77
  // Skills capability
73
78
  import {
@@ -202,6 +207,13 @@ export function createServer() {
202
207
  registerDeletePromptTemplateTool(server);
203
208
  registerPreviewPromptTemplateTool(server);
204
209
 
210
+ // Reusable script catalog tools - always registered (HTTP MCP only in v1).
211
+ registerScriptSearchTool(server);
212
+ registerScriptRunTool(server);
213
+ registerScriptUpsertTool(server);
214
+ registerScriptDeleteTool(server);
215
+ registerScriptQueryTypesTool(server);
216
+
205
217
  // Slack integration tools (always registered, will no-op if Slack not configured)
206
218
  registerSlackReplyTool(server);
207
219
  registerSlackReadTool(server);