@desplega.ai/agent-swarm 1.90.0 → 1.92.0

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 (96) hide show
  1. package/README.md +2 -1
  2. package/openapi.json +803 -150
  3. package/package.json +5 -5
  4. package/src/artifact-sdk/server.ts +2 -1
  5. package/src/be/db.ts +337 -1
  6. package/src/be/memory/providers/sqlite-store.ts +6 -1
  7. package/src/be/memory/types.ts +1 -0
  8. package/src/be/migrations/083_script_workflows.sql +51 -0
  9. package/src/be/modelsdev-cache.json +42352 -38595
  10. package/src/be/scripts/typecheck.ts +181 -1
  11. package/src/be/seed-scripts/catalog/compound-insights.ts +398 -0
  12. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -0
  13. package/src/be/seed-scripts/catalog/schedule-health.ts +73 -0
  14. package/src/be/seed-scripts/catalog/smart-recall.ts +65 -0
  15. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  16. package/src/be/seed-scripts/catalog/tool-usage.ts +59 -0
  17. package/src/be/seed-scripts/index.ts +54 -0
  18. package/src/be/seed-skills/index.ts +7 -0
  19. package/src/be/swarm-config-guard.ts +17 -0
  20. package/src/commands/artifact.ts +3 -2
  21. package/src/commands/profile-sync.ts +310 -0
  22. package/src/commands/runner.ts +134 -3
  23. package/src/hooks/hook.ts +32 -9
  24. package/src/http/db-query.ts +20 -5
  25. package/src/http/index.ts +57 -0
  26. package/src/http/integrations.ts +6 -1
  27. package/src/http/mcp-bridge.ts +117 -0
  28. package/src/http/mcp-oauth.ts +97 -39
  29. package/src/http/memory.ts +5 -2
  30. package/src/http/openapi.ts +2 -2
  31. package/src/http/pages-public.ts +10 -11
  32. package/src/http/pages.ts +7 -11
  33. package/src/http/script-runs.ts +555 -0
  34. package/src/http/scripts.ts +24 -1
  35. package/src/http/utils.ts +11 -4
  36. package/src/jira/app.ts +2 -3
  37. package/src/jira/webhook-lifecycle.ts +2 -1
  38. package/src/linear/app.ts +2 -3
  39. package/src/prompts/session-templates.ts +24 -4
  40. package/src/providers/claude-adapter.ts +86 -13
  41. package/src/script-workflows/executor.ts +110 -0
  42. package/src/script-workflows/harness.ts +73 -0
  43. package/src/script-workflows/label-lint.ts +51 -0
  44. package/src/script-workflows/limits.ts +22 -0
  45. package/src/script-workflows/supervisor.ts +139 -0
  46. package/src/script-workflows/workflow-ctx.ts +205 -0
  47. package/src/scripts-runtime/executors/native.ts +1 -0
  48. package/src/scripts-runtime/sdk-allowlist.ts +124 -0
  49. package/src/scripts-runtime/swarm-sdk.ts +198 -3
  50. package/src/scripts-runtime/types/stdlib.d.ts +287 -0
  51. package/src/scripts-runtime/types/swarm-sdk.d.ts +287 -0
  52. package/src/server.ts +2 -0
  53. package/src/slack/handlers.ts +11 -4
  54. package/src/slack/message-text.ts +98 -0
  55. package/src/slack/thread-buffer.ts +5 -3
  56. package/src/tests/claude-adapter-binary.test.ts +147 -4
  57. package/src/tests/claude-adapter-otel.test.ts +85 -1
  58. package/src/tests/db-query.test.ts +28 -0
  59. package/src/tests/error-tracker.test.ts +121 -0
  60. package/src/tests/harness-provider-resolution.test.ts +33 -0
  61. package/src/tests/hook-registration-nudge.test.ts +69 -0
  62. package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
  63. package/src/tests/mcp-tools.test.ts +6 -0
  64. package/src/tests/pages-public-html.test.ts +41 -0
  65. package/src/tests/pages-public-json-redirect.test.ts +37 -2
  66. package/src/tests/profile-sync.test.ts +282 -0
  67. package/src/tests/prompt-template-session.test.ts +34 -5
  68. package/src/tests/script-runs-http.test.ts +278 -0
  69. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  70. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  71. package/src/tests/scripts-mcp-e2e.test.ts +49 -2
  72. package/src/tests/scripts-runtime.test.ts +33 -0
  73. package/src/tests/seed-scripts.test.ts +347 -2
  74. package/src/tests/slack-message-text.test.ts +250 -0
  75. package/src/tests/system-default-skills.test.ts +40 -0
  76. package/src/tools/create-metric.ts +2 -3
  77. package/src/tools/create-page.ts +3 -6
  78. package/src/tools/db-query.ts +16 -6
  79. package/src/tools/memory-rate.ts +2 -1
  80. package/src/tools/memory-search.ts +1 -0
  81. package/src/tools/register-kapso-number.ts +2 -4
  82. package/src/tools/request-human-input.ts +2 -1
  83. package/src/tools/script-common.ts +2 -4
  84. package/src/tools/script-run.ts +7 -0
  85. package/src/tools/script-runs.ts +123 -0
  86. package/src/tools/slack-read.ts +12 -3
  87. package/src/tools/tool-config.ts +4 -1
  88. package/src/types.ts +52 -0
  89. package/src/utils/constants.ts +58 -8
  90. package/src/utils/error-tracker.ts +40 -1
  91. package/src/utils/internal-ai/complete-structured.ts +10 -4
  92. package/src/workflows/executors/raw-llm.ts +76 -59
  93. package/templates/skills/pages/content.md +205 -55
  94. package/templates/skills/script-workflows/config.json +14 -0
  95. package/templates/skills/script-workflows/content.md +68 -0
  96. package/templates/skills/swarm-scripts/content.md +45 -7
@@ -0,0 +1,205 @@
1
+ import { stdlib } from "../scripts-runtime/stdlib";
2
+
3
+ type StepStatusResponse =
4
+ | { stepKey: string; stepType: string; result: unknown }
5
+ | { error: string };
6
+
7
+ type StepWriteResponse = { ok: true } | { error: string };
8
+
9
+ type RawLlmConfig = {
10
+ prompt: string;
11
+ model?: string;
12
+ schema?: Record<string, unknown>;
13
+ };
14
+
15
+ type AgentTaskConfig = {
16
+ template?: string;
17
+ task?: string;
18
+ agentId?: string;
19
+ tags?: string[];
20
+ priority?: number;
21
+ offerMode?: boolean;
22
+ dir?: string;
23
+ vcsRepo?: string;
24
+ model?: string;
25
+ parentTaskId?: string;
26
+ requestedByUserId?: string;
27
+ outputSchema?: Record<string, unknown>;
28
+ };
29
+
30
+ type SwarmScriptConfig = {
31
+ name?: string;
32
+ scriptName?: string;
33
+ source?: string;
34
+ args?: unknown;
35
+ scope?: "agent" | "global";
36
+ fsMode?: "none" | "workspace-rw";
37
+ intent?: string;
38
+ idempotencyKey?: string;
39
+ };
40
+
41
+ type WorkflowRunInfo = {
42
+ id: string;
43
+ agentId: string;
44
+ args: unknown;
45
+ };
46
+
47
+ export type WorkflowCtx = {
48
+ run: WorkflowRunInfo;
49
+ step: {
50
+ rawLlm: (label: string, config: RawLlmConfig) => Promise<unknown>;
51
+ agentTask: (label: string, config: AgentTaskConfig) => Promise<unknown>;
52
+ swarmScript: (label: string, config: SwarmScriptConfig) => Promise<unknown>;
53
+ humanInTheLoop: () => Promise<never>;
54
+ };
55
+ swarm: Record<string, (args?: unknown) => Promise<unknown>>;
56
+ stdlib: typeof stdlib;
57
+ logger: Console;
58
+ };
59
+
60
+ function encodeStepKey(label: string): string {
61
+ return encodeURIComponent(label);
62
+ }
63
+
64
+ function headers(apiKey: string, agentId: string): Record<string, string> {
65
+ return {
66
+ Authorization: `Bearer ${apiKey}`,
67
+ "X-Agent-ID": agentId,
68
+ "Content-Type": "application/json",
69
+ };
70
+ }
71
+
72
+ async function readJson(res: Response): Promise<unknown> {
73
+ const text = await res.text();
74
+ return text ? JSON.parse(text) : {};
75
+ }
76
+
77
+ function apiError(prefix: string, status: number, body: unknown): Error {
78
+ const message =
79
+ body && typeof body === "object" && "error" in body
80
+ ? String((body as { error: unknown }).error)
81
+ : JSON.stringify(body);
82
+ return new Error(`${prefix} failed with ${status}: ${message}`);
83
+ }
84
+
85
+ export function buildWorkflowCtx(input: {
86
+ runId: string;
87
+ agentId: string;
88
+ apiKey: string;
89
+ baseUrl: string;
90
+ args: unknown;
91
+ }): WorkflowCtx {
92
+ const baseUrl = input.baseUrl.replace(/\/$/, "");
93
+ const authHeaders = headers(input.apiKey, input.agentId);
94
+
95
+ async function fetchJson(path: string, init: RequestInit = {}): Promise<unknown> {
96
+ const res = await fetch(`${baseUrl}${path}`, {
97
+ ...init,
98
+ headers: { ...authHeaders, ...((init.headers as Record<string, string>) ?? {}) },
99
+ });
100
+ const body = await readJson(res);
101
+ if (!res.ok) throw apiError(path, res.status, body);
102
+ return body;
103
+ }
104
+
105
+ async function completedStep(
106
+ label: string,
107
+ ): Promise<{ found: true; result: unknown } | { found: false }> {
108
+ const res = await fetch(
109
+ `${baseUrl}/api/internal/script-runs/${input.runId}/steps/${encodeStepKey(label)}`,
110
+ {
111
+ headers: authHeaders,
112
+ },
113
+ );
114
+ if (res.status === 404) return { found: false };
115
+ const body = (await readJson(res)) as StepStatusResponse;
116
+ if (!res.ok) throw apiError(`step ${label}`, res.status, body);
117
+ return { found: true, result: "result" in body ? body.result : undefined };
118
+ }
119
+
120
+ async function writeStep(
121
+ label: string,
122
+ stepType: string,
123
+ config: unknown,
124
+ status: "completed" | "failed",
125
+ result?: unknown,
126
+ error?: string,
127
+ ): Promise<void> {
128
+ const body = (await fetchJson(`/api/internal/script-runs/${input.runId}/steps`, {
129
+ method: "POST",
130
+ body: JSON.stringify({ stepKey: label, stepType, config, status, result, error }),
131
+ })) as StepWriteResponse;
132
+ if (!("ok" in body)) throw new Error(`Failed to write journal step ${label}`);
133
+ }
134
+
135
+ async function durableStep(
136
+ label: string,
137
+ stepType: string,
138
+ config: unknown,
139
+ execute: () => Promise<unknown>,
140
+ ): Promise<unknown> {
141
+ const replayed = await completedStep(label);
142
+ if (replayed.found) return replayed.result;
143
+ try {
144
+ const result = await execute();
145
+ await writeStep(label, stepType, config, "completed", result);
146
+ return result;
147
+ } catch (err) {
148
+ const error = err instanceof Error ? err.message : String(err);
149
+ await writeStep(label, stepType, config, "failed", undefined, error);
150
+ throw err;
151
+ }
152
+ }
153
+
154
+ const swarm = new Proxy({} as Record<string, (args?: unknown) => Promise<unknown>>, {
155
+ get(_target, prop) {
156
+ if (typeof prop !== "string") return undefined;
157
+ return (args?: unknown) =>
158
+ fetchJson("/api/mcp-bridge", {
159
+ method: "POST",
160
+ body: JSON.stringify({ tool: prop.replaceAll("_", "-"), args: args ?? {} }),
161
+ });
162
+ },
163
+ });
164
+
165
+ return {
166
+ run: { id: input.runId, agentId: input.agentId, args: input.args },
167
+ step: {
168
+ rawLlm: (label, config) =>
169
+ durableStep(label, "raw-llm", config, async () =>
170
+ fetchJson("/api/internal/raw-llm", {
171
+ method: "POST",
172
+ body: JSON.stringify(config),
173
+ }),
174
+ ),
175
+ agentTask: (label, config) =>
176
+ durableStep(label, "agent-task", config, async () =>
177
+ fetchJson(`/api/internal/script-runs/${input.runId}/agent-task`, {
178
+ method: "POST",
179
+ body: JSON.stringify({ stepKey: label, ...config }),
180
+ }),
181
+ ),
182
+ swarmScript: (label, config) =>
183
+ durableStep(label, "swarm-script", config, async () =>
184
+ fetchJson("/api/scripts/run", {
185
+ method: "POST",
186
+ body: JSON.stringify({
187
+ name: config.name ?? config.scriptName,
188
+ source: config.source,
189
+ args: config.args,
190
+ scope: config.scope,
191
+ fsMode: config.fsMode ?? "none",
192
+ intent: config.intent ?? `script-run:${input.runId}:${label}`,
193
+ idempotencyKey: config.idempotencyKey,
194
+ }),
195
+ }),
196
+ ),
197
+ humanInTheLoop: async () => {
198
+ throw new Error("ctx.step.humanInTheLoop is stubbed in Script Workflows v1");
199
+ },
200
+ },
201
+ swarm,
202
+ stdlib,
203
+ logger: console,
204
+ };
205
+ }
@@ -90,6 +90,7 @@ async function writeBareImportShims(tmpdir: string): Promise<void> {
90
90
  const shims: [string, string][] = [
91
91
  ["stdlib", `${runtimeDir}/stdlib.bundle.js`],
92
92
  ["swarm-sdk", `${runtimeDir}/swarm-sdk.bundle.js`],
93
+ ["zod", `${runtimeDir}/zod.bundle.js`],
93
94
  ];
94
95
  for (const [name, bundlePath] of shims) {
95
96
  const dir = `${tmpdir}/node_modules/${name}`;
@@ -1,19 +1,143 @@
1
1
  export const SDK_TOOL_NAME_MAP = {
2
+ // ── memory ──
2
3
  memory_search: "memory-search",
3
4
  memory_get: "memory-get",
4
5
  memory_rate: "memory_rate",
6
+ memory_delete: "memory-delete", // destructive
7
+ inject_learning: "inject-learning",
8
+
9
+ // ── tasks ──
5
10
  task_list: "get-tasks",
6
11
  task_get: "get-task-details",
7
12
  task_storeProgress: "store-progress",
13
+ task_poll: "poll-task",
14
+ task_send: "send-task",
15
+ task_cancel: "cancel-task", // destructive
16
+ task_action: "task-action",
17
+
18
+ // ── kv ──
8
19
  kv_get: "kv-get",
9
20
  kv_set: "kv-set",
10
21
  kv_del: "kv-delete",
11
22
  kv_incr: "kv-incr",
12
23
  kv_list: "kv-list",
24
+
25
+ // ── repos ──
13
26
  repo_list: "get-repos",
27
+ repo_update: "update-repo",
28
+
29
+ // ── schedules ──
14
30
  schedule_list: "list-schedules",
31
+ schedule_create: "create-schedule",
32
+ schedule_update: "update-schedule",
33
+ schedule_delete: "delete-schedule", // destructive
34
+ schedule_runNow: "run-schedule-now",
35
+
36
+ // ── scripts ──
15
37
  script_search: "script-search",
16
38
  script_run: "script-run",
39
+ script_upsert: "script-upsert",
40
+ script_delete: "script-delete", // destructive
41
+ script_queryTypes: "script-query-types",
42
+ script_launchRun: "launch-script-run",
43
+ script_getRun: "get-script-run",
44
+ script_listRuns: "list-script-runs",
45
+
46
+ // ── swarm / agent ──
47
+ swarm_get: "get-swarm",
48
+ agent_info: "my-agent-info",
49
+ agent_join: "join-swarm",
50
+ metrics_get: "get-metrics",
51
+ user_resolve: "resolve-user",
52
+ user_manage: "manage-user",
53
+ db_query: "db-query",
54
+
55
+ // ── config ──
56
+ config_get: "get-config",
57
+ config_list: "list-config",
58
+ config_set: "set-config",
59
+ config_delete: "delete-config", // destructive
60
+
61
+ // ── slack ──
62
+ slack_read: "slack-read",
63
+ slack_listChannels: "slack-list-channels",
64
+ slack_post: "slack-post", // external: sends to Slack
65
+ slack_reply: "slack-reply", // external: sends to Slack
66
+ slack_startThread: "slack-start-thread", // external: sends to Slack
67
+ slack_uploadFile: "slack-upload-file", // external: sends to Slack
68
+ slack_downloadFile: "slack-download-file",
69
+
70
+ // ── messaging (internal) ──
71
+ message_read: "read-messages",
72
+ message_post: "post-message",
73
+
74
+ // ── profiles ──
75
+ profile_update: "update-profile",
76
+
77
+ // ── context / profiles ──
78
+ context_history: "context-history",
79
+ context_diff: "context-diff",
80
+
81
+ // ── services ──
82
+ service_list: "list-services",
83
+ service_register: "register-service",
84
+ service_unregister: "unregister-service", // destructive
85
+ service_updateStatus: "update-service-status",
86
+
87
+ // ── workflows ──
88
+ workflow_list: "list-workflows",
89
+ workflow_get: "get-workflow",
90
+ workflow_create: "create-workflow",
91
+ workflow_update: "update-workflow",
92
+ workflow_patch: "patch-workflow",
93
+ workflow_patchNode: "patch-workflow-node",
94
+ workflow_delete: "delete-workflow", // destructive
95
+ workflow_trigger: "trigger-workflow",
96
+ workflow_listRuns: "list-workflow-runs",
97
+ workflow_getRun: "get-workflow-run",
98
+ workflow_retryRun: "retry-workflow-run",
99
+ workflow_cancelRun: "cancel-workflow-run", // destructive
100
+
101
+ // ── prompt templates ──
102
+ prompt_list: "list-prompt-templates",
103
+ prompt_get: "get-prompt-template",
104
+ prompt_set: "set-prompt-template",
105
+ prompt_delete: "delete-prompt-template", // destructive
106
+ prompt_preview: "preview-prompt-template",
107
+
108
+ // ── tracker ──
109
+ tracker_status: "tracker-status",
110
+ tracker_syncStatus: "tracker-sync-status",
111
+ tracker_linkTask: "tracker-link-task",
112
+ tracker_unlink: "tracker-unlink", // destructive
113
+ tracker_mapAgent: "tracker-map-agent",
114
+
115
+ // ── skills ──
116
+ skill_list: "skill-list",
117
+ skill_get: "skill-get",
118
+ skill_search: "skill-search",
119
+ skill_create: "skill-create",
120
+ skill_update: "skill-update",
121
+ skill_delete: "skill-delete", // destructive
122
+ skill_install: "skill-install",
123
+ skill_uninstall: "skill-uninstall", // destructive
124
+ skill_publish: "skill-publish",
125
+
126
+ // ── mcp servers ──
127
+ mcpServer_list: "mcp-server-list",
128
+ mcpServer_get: "mcp-server-get",
129
+ mcpServer_create: "mcp-server-create",
130
+ mcpServer_update: "mcp-server-update",
131
+ mcpServer_delete: "mcp-server-delete", // destructive
132
+ mcpServer_install: "mcp-server-install",
133
+ mcpServer_uninstall: "mcp-server-uninstall", // destructive
134
+
135
+ // ── pages & metrics ──
136
+ page_create: "create_page",
137
+ metric_create: "create_metric",
138
+
139
+ // ── human input ──
140
+ request_humanInput: "request-human-input",
17
141
  } as const;
18
142
 
19
143
  export const SDK_ALLOWLIST = Object.keys(SDK_TOOL_NAME_MAP) as Array<
@@ -1,6 +1,6 @@
1
1
  import { scrubObject } from "../utils/secret-scrubber";
2
2
  import { Redacted } from "./redacted";
3
- import { isSdkToolAllowed } from "./sdk-allowlist";
3
+ import { isSdkToolAllowed, mcpToolNameForSdkMethod } from "./sdk-allowlist";
4
4
  import type { SwarmConfig } from "./swarm-config";
5
5
 
6
6
  type BridgeRequest = {
@@ -43,9 +43,14 @@ function kvPath(args: Record<string, unknown>, keyRequired = true): string {
43
43
  return key ? `/api/kv/${encodeURIComponent(key)}` : "/api/kv";
44
44
  }
45
45
 
46
- function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
46
+ /**
47
+ * Maps SDK method names to specific REST endpoints where they exist.
48
+ * Returns null for tools that should fall through to the generic MCP bridge.
49
+ */
50
+ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
47
51
  const body = argsRecord(args);
48
52
  switch (name) {
53
+ // ── memory ──
49
54
  case "memory_search":
50
55
  return { method: "POST", path: "/api/memory/search", body };
51
56
  case "memory_get": {
@@ -67,6 +72,13 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
67
72
  };
68
73
  return { method: "POST", path: "/api/memory/rate", body: { events: [event] } };
69
74
  }
75
+ case "memory_delete": {
76
+ const id = typeof body.id === "string" ? body.id : undefined;
77
+ if (!id) throw new Error("memory_delete requires string `id`");
78
+ return { method: "DELETE", path: `/api/memory/${encodeURIComponent(id)}` };
79
+ }
80
+
81
+ // ── tasks ──
70
82
  case "task_list":
71
83
  return { method: "GET", path: appendQuery("/api/tasks", body) };
72
84
  case "task_get": {
@@ -94,6 +106,13 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
94
106
  body: { progress: body.progress ?? "" },
95
107
  };
96
108
  }
109
+ case "task_cancel": {
110
+ const taskId = typeof body.taskId === "string" ? body.taskId : undefined;
111
+ if (!taskId) throw new Error("task_cancel requires string `taskId`");
112
+ return { method: "POST", path: `/api/tasks/${encodeURIComponent(taskId)}/cancel` };
113
+ }
114
+
115
+ // ── kv ──
97
116
  case "kv_get":
98
117
  return { method: "GET", path: kvPath(body) };
99
118
  case "kv_set":
@@ -119,11 +138,15 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
119
138
  offset: body.offset,
120
139
  }),
121
140
  };
141
+
142
+ // ── repos ──
122
143
  case "repo_list":
123
144
  return {
124
145
  method: "GET",
125
146
  path: appendQuery("/api/repos", { autoClone: body.autoClone, name: body.name }),
126
147
  };
148
+
149
+ // ── schedules ──
127
150
  case "schedule_list":
128
151
  return {
129
152
  method: "GET",
@@ -134,12 +157,164 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest {
134
157
  hideCompleted: body.hideCompleted,
135
158
  }),
136
159
  };
160
+
161
+ // ── scripts ──
137
162
  case "script_search":
138
163
  return { method: "POST", path: "/api/scripts/search", body };
139
164
  case "script_run":
140
165
  return { method: "POST", path: "/api/scripts/run", body };
166
+
167
+ // ── swarm / agent ──
168
+ case "db_query":
169
+ return { method: "POST", path: "/api/db-query", body };
170
+ case "swarm_get":
171
+ return {
172
+ method: "GET",
173
+ path: appendQuery("/api/agents", {
174
+ fields: body.includeFull ? "full" : "slim",
175
+ }),
176
+ };
177
+ case "agent_info":
178
+ return { method: "GET", path: "/me" };
179
+ case "metrics_get":
180
+ return { method: "GET", path: "/api/metrics" };
181
+ case "task_poll":
182
+ return { method: "GET", path: "/api/poll" };
183
+
184
+ // ── config ──
185
+ case "config_get":
186
+ return {
187
+ method: "GET",
188
+ path: appendQuery("/api/config/resolved", {
189
+ agentId: body.agentId,
190
+ repoId: body.repoId,
191
+ key: body.key,
192
+ includeSecrets: body.includeSecrets,
193
+ }),
194
+ };
195
+ case "config_list":
196
+ return {
197
+ method: "GET",
198
+ path: appendQuery("/api/config", {
199
+ scope: body.scope,
200
+ scopeId: body.scopeId,
201
+ key: body.key,
202
+ includeSecrets: body.includeSecrets,
203
+ }),
204
+ };
205
+ case "config_delete": {
206
+ const id = typeof body.id === "string" ? body.id : undefined;
207
+ if (!id) throw new Error("config_delete requires string `id`");
208
+ return { method: "DELETE", path: `/api/config/${encodeURIComponent(id)}` };
209
+ }
210
+
211
+ // ── services ──
212
+ case "service_list":
213
+ return {
214
+ method: "GET",
215
+ path: appendQuery("/api/services", {
216
+ agentId: body.agentId,
217
+ name: body.name,
218
+ status: body.status,
219
+ }),
220
+ };
221
+
222
+ // ── workflows ──
223
+ case "workflow_list":
224
+ return {
225
+ method: "GET",
226
+ path: appendQuery("/api/workflows", {
227
+ fields: body.includeFull ? "full" : "slim",
228
+ }),
229
+ };
230
+ case "workflow_get": {
231
+ const id = typeof body.id === "string" ? body.id : undefined;
232
+ if (!id) throw new Error("workflow_get requires string `id`");
233
+ return { method: "GET", path: `/api/workflows/${encodeURIComponent(id)}` };
234
+ }
235
+ case "workflow_listRuns": {
236
+ const wfId = typeof body.workflowId === "string" ? body.workflowId : undefined;
237
+ if (!wfId) throw new Error("workflow_listRuns requires string `workflowId`");
238
+ return {
239
+ method: "GET",
240
+ path: appendQuery(`/api/workflows/${encodeURIComponent(wfId)}/runs`, {
241
+ status: body.status,
242
+ }),
243
+ };
244
+ }
245
+ case "workflow_getRun": {
246
+ const id = typeof body.id === "string" ? body.id : undefined;
247
+ if (!id) throw new Error("workflow_getRun requires string `id`");
248
+ return { method: "GET", path: `/api/workflow-runs/${encodeURIComponent(id)}` };
249
+ }
250
+ case "workflow_delete": {
251
+ const id = typeof body.id === "string" ? body.id : undefined;
252
+ if (!id) throw new Error("workflow_delete requires string `id`");
253
+ return { method: "DELETE", path: `/api/workflows/${encodeURIComponent(id)}` };
254
+ }
255
+
256
+ // ── prompt templates ──
257
+ case "prompt_list":
258
+ return {
259
+ method: "GET",
260
+ path: appendQuery("/api/prompt-templates", {
261
+ eventType: body.eventType,
262
+ scope: body.scope,
263
+ scopeId: body.scopeId,
264
+ isDefault: body.isDefault,
265
+ }),
266
+ };
267
+ case "prompt_get": {
268
+ const id = typeof body.id === "string" ? body.id : undefined;
269
+ if (!id) throw new Error("prompt_get requires string `id`");
270
+ return { method: "GET", path: `/api/prompt-templates/${encodeURIComponent(id)}` };
271
+ }
272
+ case "prompt_delete": {
273
+ const id = typeof body.id === "string" ? body.id : undefined;
274
+ if (!id) throw new Error("prompt_delete requires string `id`");
275
+ return { method: "DELETE", path: `/api/prompt-templates/${encodeURIComponent(id)}` };
276
+ }
277
+
278
+ // ── skills ──
279
+ case "skill_list":
280
+ return {
281
+ method: "GET",
282
+ path: appendQuery("/api/skills", {
283
+ scope: body.scope,
284
+ scopeId: body.scopeId,
285
+ includeBuiltin: body.includeBuiltin,
286
+ }),
287
+ };
288
+ case "skill_get": {
289
+ const id = typeof body.id === "string" ? body.id : undefined;
290
+ if (!id) throw new Error("skill_get requires string `id`");
291
+ return { method: "GET", path: `/api/skills/${encodeURIComponent(id)}` };
292
+ }
293
+ case "skill_search":
294
+ return { method: "POST", path: "/api/skills/search", body };
295
+ case "skill_delete": {
296
+ const id = typeof body.id === "string" ? body.id : undefined;
297
+ if (!id) throw new Error("skill_delete requires string `id`");
298
+ return { method: "DELETE", path: `/api/skills/${encodeURIComponent(id)}` };
299
+ }
300
+
301
+ // ── mcp servers ──
302
+ case "mcpServer_list":
303
+ return { method: "GET", path: "/api/mcp-servers" };
304
+ case "mcpServer_get": {
305
+ const id = typeof body.id === "string" ? body.id : undefined;
306
+ if (!id) throw new Error("mcpServer_get requires string `id`");
307
+ return { method: "GET", path: `/api/mcp-servers/${encodeURIComponent(id)}` };
308
+ }
309
+ case "mcpServer_delete": {
310
+ const id = typeof body.id === "string" ? body.id : undefined;
311
+ if (!id) throw new Error("mcpServer_delete requires string `id`");
312
+ return { method: "DELETE", path: `/api/mcp-servers/${encodeURIComponent(id)}` };
313
+ }
314
+
315
+ // ── fallthrough: proxy via generic MCP bridge ──
141
316
  default:
142
- throw new Error(`Tool '${name}' is not exposed through the scripts SDK bridge`);
317
+ return null;
143
318
  }
144
319
  }
145
320
 
@@ -152,6 +327,26 @@ async function callBridgeApi(
152
327
  const baseUrl = Redacted.value(config.mcpBaseUrl).replace(/\/$/, "");
153
328
  const request = bridgeRequestFor(name, args);
154
329
 
330
+ // Tools without a specific REST route go through the generic MCP bridge
331
+ if (!request) {
332
+ const mcpToolName = mcpToolNameForSdkMethod(name);
333
+ const res = await fetch(`${baseUrl}/api/mcp-bridge`, {
334
+ method: "POST",
335
+ headers: headers(config),
336
+ body: JSON.stringify({ tool: mcpToolName, args: args ?? {} }),
337
+ });
338
+ const text = await res.text();
339
+ const data = text ? JSON.parse(text) : {};
340
+ if (!res.ok && options.throwOnError) {
341
+ const message =
342
+ data && typeof data === "object" && "error" in data
343
+ ? String((data as { error: unknown }).error)
344
+ : `api failed with ${res.status}`;
345
+ throw new Error(`swarm-sdk: ${name} failed with ${res.status}: ${message}`);
346
+ }
347
+ return scrubObject({ success: res.ok, status: res.status, data });
348
+ }
349
+
155
350
  const res = await fetch(`${baseUrl}${request.path}`, {
156
351
  method: request.method,
157
352
  headers: headers(config),