@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,43 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { lintWorkflowLabels } from "../script-workflows/label-lint";
|
|
3
|
+
|
|
4
|
+
describe("lintWorkflowLabels", () => {
|
|
5
|
+
test("rejects a literal step label inside a loop", () => {
|
|
6
|
+
const result = lintWorkflowLabels(`
|
|
7
|
+
export default async function main(args, ctx) {
|
|
8
|
+
for (const item of args.items) {
|
|
9
|
+
await ctx.step.agentTask("process", { task: item.task });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`);
|
|
13
|
+
|
|
14
|
+
expect(result.ok).toBe(false);
|
|
15
|
+
if (!result.ok) {
|
|
16
|
+
expect(result.errors).toHaveLength(1);
|
|
17
|
+
expect(result.errors[0]?.label).toBe("process");
|
|
18
|
+
expect(result.errors[0]?.detail).toContain("inside a loop");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("allows template literal step labels inside a loop", () => {
|
|
23
|
+
const result = lintWorkflowLabels(`
|
|
24
|
+
export default async function main(args, ctx) {
|
|
25
|
+
for (const item of args.items) {
|
|
26
|
+
await ctx.step.agentTask(\`process:\${item.id}\`, { task: item.task });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
expect(result).toEqual({ ok: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("allows literal step labels outside loops", () => {
|
|
35
|
+
const result = lintWorkflowLabels(`
|
|
36
|
+
export default async function main(_args, ctx) {
|
|
37
|
+
await ctx.step.rawLlm("summarize", { prompt: "hello" });
|
|
38
|
+
}
|
|
39
|
+
`);
|
|
40
|
+
|
|
41
|
+
expect(result).toEqual({ ok: true });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { rm, unlink } from "node:fs/promises";
|
|
3
|
+
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
4
|
+
import { closeDb, createAgent, getDb, initDb, listScriptRunJournalSteps } from "../be/db";
|
|
5
|
+
import { handleCore } from "../http/core";
|
|
6
|
+
import { handleScriptRuns } from "../http/script-runs";
|
|
7
|
+
import { handleScripts } from "../http/scripts";
|
|
8
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
9
|
+
import { refreshSecretScrubberCache } from "../utils/secret-scrubber";
|
|
10
|
+
|
|
11
|
+
const TEST_DB_PATH = "./test-script-workflows-runtime-e2e.sqlite";
|
|
12
|
+
const WORKFLOW_RUNTIME_DIR = "./test-script-workflows-runtime";
|
|
13
|
+
const API_KEY = "test-script-workflows-runtime-key-1234567890";
|
|
14
|
+
|
|
15
|
+
let agentId: string;
|
|
16
|
+
let server: Server;
|
|
17
|
+
let baseUrl: string;
|
|
18
|
+
let savedEnv: NodeJS.ProcessEnv;
|
|
19
|
+
|
|
20
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
21
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
22
|
+
try {
|
|
23
|
+
await unlink(path + suffix);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function listen(server: Server): Promise<number> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
server.once("error", reject);
|
|
33
|
+
server.listen(0, "127.0.0.1", () => {
|
|
34
|
+
const address = server.address();
|
|
35
|
+
if (!address || typeof address === "string") reject(new Error("No server address"));
|
|
36
|
+
else resolve(address.port);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function closeServer(server: Server): Promise<void> {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function route(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
48
|
+
const agentId = req.headers["x-agent-id"] as string | undefined;
|
|
49
|
+
if (await handleCore(req, res, agentId, API_KEY)) return;
|
|
50
|
+
const pathSegments = getPathSegments(req.url || "");
|
|
51
|
+
const queryParams = parseQueryParams(req.url || "");
|
|
52
|
+
if (await handleScriptRuns(req, res, pathSegments, queryParams, agentId)) return;
|
|
53
|
+
if (await handleScripts(req, res, pathSegments, queryParams, agentId)) return;
|
|
54
|
+
res.writeHead(404);
|
|
55
|
+
res.end("Not Found");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function api(path: string, init: RequestInit = {}): Promise<Response> {
|
|
59
|
+
return fetch(`${baseUrl}${path}`, {
|
|
60
|
+
...init,
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
63
|
+
"X-Agent-ID": agentId,
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
...((init.headers as Record<string, string>) ?? {}),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function waitForRun(
|
|
71
|
+
id: string,
|
|
72
|
+
): Promise<{ status: string; output?: unknown; error?: string }> {
|
|
73
|
+
const deadline = Date.now() + 10_000;
|
|
74
|
+
while (Date.now() < deadline) {
|
|
75
|
+
const res = await api(`/api/script-runs/${id}`);
|
|
76
|
+
const body = (await res.json()) as {
|
|
77
|
+
run: { status: string; output?: unknown; error?: string };
|
|
78
|
+
};
|
|
79
|
+
if (["completed", "failed", "cancelled", "aborted_limit"].includes(body.run.status)) {
|
|
80
|
+
return body.run;
|
|
81
|
+
}
|
|
82
|
+
await Bun.sleep(250);
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Timed out waiting for script run");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
beforeAll(async () => {
|
|
88
|
+
savedEnv = { ...process.env };
|
|
89
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
90
|
+
initDb(TEST_DB_PATH);
|
|
91
|
+
process.env.AGENT_SWARM_API_KEY = API_KEY;
|
|
92
|
+
process.env.API_KEY = API_KEY;
|
|
93
|
+
process.env.APP_URL = "https://app.example.test";
|
|
94
|
+
delete process.env.SCRIPT_RUN_SUPERVISOR_DISABLE;
|
|
95
|
+
await rm(WORKFLOW_RUNTIME_DIR, { recursive: true, force: true });
|
|
96
|
+
await Bun.$`bun build ./src/script-workflows/harness.ts --target bun --no-splitting --outfile ${WORKFLOW_RUNTIME_DIR}/harness.bundle.js`.quiet();
|
|
97
|
+
process.env.SCRIPT_WORKFLOW_RUNTIME_DIR = WORKFLOW_RUNTIME_DIR;
|
|
98
|
+
refreshSecretScrubberCache();
|
|
99
|
+
|
|
100
|
+
const agent = createAgent({ name: "script-workflow-e2e-worker", isLead: false, status: "idle" });
|
|
101
|
+
agentId = agent.id;
|
|
102
|
+
server = createServer((req, res) => {
|
|
103
|
+
route(req, res).catch((err) => {
|
|
104
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
105
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
const port = await listen(server);
|
|
109
|
+
baseUrl = `http://127.0.0.1:${port}`;
|
|
110
|
+
process.env.MCP_BASE_URL = baseUrl;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterAll(async () => {
|
|
114
|
+
await closeServer(server);
|
|
115
|
+
closeDb();
|
|
116
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
117
|
+
await rm(WORKFLOW_RUNTIME_DIR, { recursive: true, force: true });
|
|
118
|
+
for (const key of Object.keys(process.env)) {
|
|
119
|
+
if (!(key in savedEnv)) delete process.env[key];
|
|
120
|
+
}
|
|
121
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
122
|
+
if (value === undefined) delete process.env[key];
|
|
123
|
+
else process.env[key] = value;
|
|
124
|
+
}
|
|
125
|
+
refreshSecretScrubberCache();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
getDb().run("DELETE FROM script_run_journal");
|
|
130
|
+
getDb().run("DELETE FROM script_runs");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("script workflow runtime", () => {
|
|
134
|
+
test("runs a durable one-off script and replays a completed step", async () => {
|
|
135
|
+
const source = `
|
|
136
|
+
export default async function main(args, ctx) {
|
|
137
|
+
const first = await ctx.step.swarmScript("double", {
|
|
138
|
+
source: "export default async (args) => args.value * 2;",
|
|
139
|
+
args: { value: args.value },
|
|
140
|
+
intent: "script-workflow-e2e"
|
|
141
|
+
});
|
|
142
|
+
const second = await ctx.step.swarmScript("double", {
|
|
143
|
+
source: "export default async () => 999;",
|
|
144
|
+
intent: "script-workflow-e2e-should-replay"
|
|
145
|
+
});
|
|
146
|
+
return { runId: ctx.run.id, first, second };
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
const created = await api("/api/script-runs", {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body: JSON.stringify({ source, args: { value: 7 }, background: true }),
|
|
153
|
+
});
|
|
154
|
+
expect(created.status).toBe(201);
|
|
155
|
+
const { id } = (await created.json()) as { id: string };
|
|
156
|
+
|
|
157
|
+
const run = await waitForRun(id);
|
|
158
|
+
expect(run.status).toBe("completed");
|
|
159
|
+
expect(run.output).toMatchObject({
|
|
160
|
+
runId: id,
|
|
161
|
+
first: { result: 14, exitCode: 0 },
|
|
162
|
+
second: { result: 14, exitCode: 0 },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const journal = listScriptRunJournalSteps(id);
|
|
166
|
+
expect(journal).toHaveLength(1);
|
|
167
|
+
expect(journal[0]?.stepKey).toBe("double");
|
|
168
|
+
expect(journal[0]?.stepType).toBe("swarm-script");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -6,10 +6,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
6
6
|
import { closeDb, createAgent, getDb, initDb } from "../be/db";
|
|
7
7
|
import { setScriptEmbeddingProviderForTests } from "../be/scripts/embeddings";
|
|
8
8
|
import { handleCore } from "../http/core";
|
|
9
|
+
import { handleScriptRuns } from "../http/script-runs";
|
|
9
10
|
import { handleScripts } from "../http/scripts";
|
|
10
11
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
11
12
|
import { registerScriptDeleteTool } from "../tools/script-delete";
|
|
12
13
|
import { registerScriptRunTool } from "../tools/script-run";
|
|
14
|
+
import { registerScriptRunsTools } from "../tools/script-runs";
|
|
13
15
|
import { registerScriptSearchTool } from "../tools/script-search";
|
|
14
16
|
import { registerScriptUpsertTool } from "../tools/script-upsert";
|
|
15
17
|
import { refreshSecretScrubberCache } from "../utils/secret-scrubber";
|
|
@@ -65,6 +67,7 @@ function buildToolServer() {
|
|
|
65
67
|
const server = new McpServer({ name: "scripts-mcp-e2e", version: "1.0.0" });
|
|
66
68
|
registerScriptSearchTool(server);
|
|
67
69
|
registerScriptRunTool(server);
|
|
70
|
+
registerScriptRunsTools(server);
|
|
68
71
|
registerScriptUpsertTool(server);
|
|
69
72
|
registerScriptDeleteTool(server);
|
|
70
73
|
const registered = (server as unknown as { _registeredTools: Record<string, RegisteredTool> })
|
|
@@ -74,6 +77,9 @@ function buildToolServer() {
|
|
|
74
77
|
run: registered["script-run"]!,
|
|
75
78
|
upsert: registered["script-upsert"]!,
|
|
76
79
|
del: registered["script-delete"]!,
|
|
80
|
+
launchScriptRun: registered["launch-script-run"]!,
|
|
81
|
+
getScriptRun: registered["get-script-run"]!,
|
|
82
|
+
listScriptRuns: registered["list-script-runs"]!,
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -126,7 +132,10 @@ async function dispatchScriptsApi(url: string, init: RequestInit = {}): Promise<
|
|
|
126
132
|
if (!(await handleCore(req, res, agentId, API_KEY))) {
|
|
127
133
|
const pathSegments = getPathSegments(req.url || "");
|
|
128
134
|
const queryParams = parseQueryParams(req.url || "");
|
|
129
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
!(await handleScripts(req, res, pathSegments, queryParams, agentId)) &&
|
|
137
|
+
!(await handleScriptRuns(req, res, pathSegments, queryParams, agentId))
|
|
138
|
+
) {
|
|
130
139
|
res.writeHead(404);
|
|
131
140
|
res.end("Not Found");
|
|
132
141
|
}
|
|
@@ -148,6 +157,7 @@ beforeAll(async () => {
|
|
|
148
157
|
await removeDbFiles(TEST_DB_PATH);
|
|
149
158
|
initDb(TEST_DB_PATH);
|
|
150
159
|
process.env.AGENT_SWARM_API_KEY = API_KEY;
|
|
160
|
+
process.env.SCRIPT_RUN_SUPERVISOR_DISABLE = "true";
|
|
151
161
|
delete process.env.API_KEY;
|
|
152
162
|
refreshSecretScrubberCache();
|
|
153
163
|
setScriptEmbeddingProviderForTests(fakeEmbeddingProvider);
|
|
@@ -155,7 +165,10 @@ beforeAll(async () => {
|
|
|
155
165
|
process.env.MCP_BASE_URL = "http://scripts-mcp-e2e.test";
|
|
156
166
|
globalThis.fetch = (async (input, init) => {
|
|
157
167
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
158
|
-
if (
|
|
168
|
+
if (
|
|
169
|
+
url.startsWith("http://scripts-mcp-e2e.test/api/scripts/") ||
|
|
170
|
+
url.startsWith("http://scripts-mcp-e2e.test/api/script-runs")
|
|
171
|
+
) {
|
|
159
172
|
return dispatchScriptsApi(url, init);
|
|
160
173
|
}
|
|
161
174
|
return savedFetch(input, init);
|
|
@@ -179,6 +192,8 @@ afterAll(async () => {
|
|
|
179
192
|
|
|
180
193
|
beforeEach(() => {
|
|
181
194
|
getDb().run("DELETE FROM scripts");
|
|
195
|
+
getDb().run("DELETE FROM script_run_journal");
|
|
196
|
+
getDb().run("DELETE FROM script_runs");
|
|
182
197
|
});
|
|
183
198
|
|
|
184
199
|
describe("script_ MCP HTTP proxy tools", () => {
|
|
@@ -226,6 +241,38 @@ describe("script_ MCP HTTP proxy tools", () => {
|
|
|
226
241
|
expect(result.structuredContent.error).toContain("HTTP MCP transport");
|
|
227
242
|
});
|
|
228
243
|
|
|
244
|
+
test("launches, lists, and inspects durable script workflow runs", async () => {
|
|
245
|
+
const tools = buildToolServer();
|
|
246
|
+
const source = `export default async function main() { return { ok: true }; }`;
|
|
247
|
+
|
|
248
|
+
const launched = (await tools.launchScriptRun.handler(
|
|
249
|
+
{ source, args: { input: true }, scriptName: "mcp-script-workflow" },
|
|
250
|
+
meta(workerId),
|
|
251
|
+
)) as StructuredResult<{ id: string; status: string; url: string }>;
|
|
252
|
+
expect(launched.structuredContent.success).toBe(true);
|
|
253
|
+
expect(launched.structuredContent.status).toBe(201);
|
|
254
|
+
expect(launched.structuredContent.data?.status).toBe("running");
|
|
255
|
+
const runId = launched.structuredContent.data?.id;
|
|
256
|
+
expect(runId).toBeTruthy();
|
|
257
|
+
|
|
258
|
+
const listed = (await tools.listScriptRuns.handler(
|
|
259
|
+
{ status: "running", limit: 10, offset: 0 },
|
|
260
|
+
meta(workerId),
|
|
261
|
+
)) as StructuredResult<{ runs: Array<{ id: string }>; total: number }>;
|
|
262
|
+
expect(listed.structuredContent.success).toBe(true);
|
|
263
|
+
expect(listed.structuredContent.data?.total).toBe(1);
|
|
264
|
+
expect(listed.structuredContent.data?.runs[0]?.id).toBe(runId);
|
|
265
|
+
|
|
266
|
+
const detail = (await tools.getScriptRun.handler(
|
|
267
|
+
{ id: runId },
|
|
268
|
+
meta(workerId),
|
|
269
|
+
)) as StructuredResult<{ run: { id: string; status: string }; journal: unknown[] }>;
|
|
270
|
+
expect(detail.structuredContent.success).toBe(true);
|
|
271
|
+
expect(detail.structuredContent.data?.run.id).toBe(runId);
|
|
272
|
+
expect(detail.structuredContent.data?.run.status).toBe("running");
|
|
273
|
+
expect(detail.structuredContent.data?.journal).toEqual([]);
|
|
274
|
+
});
|
|
275
|
+
|
|
229
276
|
test("typed SDK fixture passes upsert typecheck and wrong arg type fails", async () => {
|
|
230
277
|
const tools = buildToolServer();
|
|
231
278
|
const source = `
|
|
@@ -288,6 +288,39 @@ describe("runScript", () => {
|
|
|
288
288
|
}
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
+
test("zod import works in compiled binary mode (SCRIPT_RUNTIME_DIR)", async () => {
|
|
292
|
+
const tmpdir = `${process.env.TMPDIR ?? "/tmp"}/script-runtime-test-${crypto.randomUUID()}`;
|
|
293
|
+
await Bun.$`mkdir -p ${tmpdir}`;
|
|
294
|
+
try {
|
|
295
|
+
const runtimeSrc = new URL("../scripts-runtime", import.meta.url).pathname;
|
|
296
|
+
await Bun.$`bun build ${runtimeSrc}/eval-harness.ts --target bun --no-splitting --outfile ${tmpdir}/eval-harness.bundle.js`.quiet();
|
|
297
|
+
await Bun.$`bun build ${runtimeSrc}/stdlib/index.ts --target bun --no-splitting --outfile ${tmpdir}/stdlib.bundle.js`.quiet();
|
|
298
|
+
await Bun.$`bun build ${runtimeSrc}/swarm-sdk.ts --target bun --no-splitting --outfile ${tmpdir}/swarm-sdk.bundle.js`.quiet();
|
|
299
|
+
const zodEntry = Bun.resolveSync("zod", import.meta.dir);
|
|
300
|
+
await Bun.$`bun build ${zodEntry} --target bun --no-splitting --outfile ${tmpdir}/zod.bundle.js`.quiet();
|
|
301
|
+
|
|
302
|
+
process.env.SCRIPT_RUNTIME_DIR = tmpdir;
|
|
303
|
+
|
|
304
|
+
const output = await runScript({
|
|
305
|
+
agentId: "agent-1",
|
|
306
|
+
args: { name: "test" },
|
|
307
|
+
resources,
|
|
308
|
+
source: `
|
|
309
|
+
import { z } from "zod";
|
|
310
|
+
export const argsSchema = z.object({ name: z.string() });
|
|
311
|
+
export default async (args: z.infer<typeof argsSchema>) => ({ greeting: "hello " + args.name });
|
|
312
|
+
`,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(output.error).toBeUndefined();
|
|
316
|
+
expect(output.result).toEqual({ greeting: "hello test" });
|
|
317
|
+
expect(output.exitCode).toBe(0);
|
|
318
|
+
} finally {
|
|
319
|
+
delete process.env.SCRIPT_RUNTIME_DIR;
|
|
320
|
+
await Bun.$`rm -rf ${tmpdir}`;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
291
324
|
test("argsSchema rejects invalid args with a formatted Zod error", async () => {
|
|
292
325
|
const output = await runScript({
|
|
293
326
|
agentId: "agent-1",
|