@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.
- package/README.md +2 -1
- package/openapi.json +803 -150
- package/package.json +5 -5
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +337 -1
- package/src/be/memory/providers/sqlite-store.ts +6 -1
- package/src/be/memory/types.ts +1 -0
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/modelsdev-cache.json +42352 -38595
- package/src/be/scripts/typecheck.ts +181 -1
- package/src/be/seed-scripts/catalog/compound-insights.ts +398 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +73 -0
- package/src/be/seed-scripts/catalog/smart-recall.ts +65 -0
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/tool-usage.ts +59 -0
- package/src/be/seed-scripts/index.ts +54 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/artifact.ts +3 -2
- package/src/commands/profile-sync.ts +310 -0
- package/src/commands/runner.ts +134 -3
- package/src/hooks/hook.ts +32 -9
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +57 -0
- package/src/http/integrations.ts +6 -1
- package/src/http/mcp-bridge.ts +117 -0
- package/src/http/mcp-oauth.ts +97 -39
- package/src/http/memory.ts +5 -2
- package/src/http/openapi.ts +2 -2
- package/src/http/pages-public.ts +10 -11
- package/src/http/pages.ts +7 -11
- package/src/http/script-runs.ts +555 -0
- package/src/http/scripts.ts +24 -1
- package/src/http/utils.ts +11 -4
- package/src/jira/app.ts +2 -3
- package/src/jira/webhook-lifecycle.ts +2 -1
- package/src/linear/app.ts +2 -3
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +86 -13
- package/src/script-workflows/executor.ts +110 -0
- package/src/script-workflows/harness.ts +73 -0
- package/src/script-workflows/label-lint.ts +51 -0
- package/src/script-workflows/limits.ts +22 -0
- package/src/script-workflows/supervisor.ts +139 -0
- package/src/script-workflows/workflow-ctx.ts +205 -0
- package/src/scripts-runtime/executors/native.ts +1 -0
- package/src/scripts-runtime/sdk-allowlist.ts +124 -0
- package/src/scripts-runtime/swarm-sdk.ts +198 -3
- package/src/scripts-runtime/types/stdlib.d.ts +287 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +287 -0
- package/src/server.ts +2 -0
- package/src/slack/handlers.ts +11 -4
- package/src/slack/message-text.ts +98 -0
- package/src/slack/thread-buffer.ts +5 -3
- package/src/tests/claude-adapter-binary.test.ts +147 -4
- package/src/tests/claude-adapter-otel.test.ts +85 -1
- package/src/tests/db-query.test.ts +28 -0
- package/src/tests/error-tracker.test.ts +121 -0
- package/src/tests/harness-provider-resolution.test.ts +33 -0
- package/src/tests/hook-registration-nudge.test.ts +69 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
- package/src/tests/mcp-tools.test.ts +6 -0
- package/src/tests/pages-public-html.test.ts +41 -0
- package/src/tests/pages-public-json-redirect.test.ts +37 -2
- package/src/tests/profile-sync.test.ts +282 -0
- package/src/tests/prompt-template-session.test.ts +34 -5
- package/src/tests/script-runs-http.test.ts +278 -0
- package/src/tests/script-workflows-label-lint.test.ts +43 -0
- package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
- package/src/tests/scripts-mcp-e2e.test.ts +49 -2
- package/src/tests/scripts-runtime.test.ts +33 -0
- package/src/tests/seed-scripts.test.ts +347 -2
- package/src/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-metric.ts +2 -3
- package/src/tools/create-page.ts +3 -6
- package/src/tools/db-query.ts +16 -6
- package/src/tools/memory-rate.ts +2 -1
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/register-kapso-number.ts +2 -4
- package/src/tools/request-human-input.ts +2 -1
- package/src/tools/script-common.ts +2 -4
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-runs.ts +123 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +4 -1
- package/src/types.ts +52 -0
- package/src/utils/constants.ts +58 -8
- package/src/utils/error-tracker.ts +40 -1
- package/src/utils/internal-ai/complete-structured.ts +10 -4
- package/src/workflows/executors/raw-llm.ts +76 -59
- package/templates/skills/pages/content.md +205 -55
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- 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
|
-
|
|
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
|
-
|
|
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),
|