@desplega.ai/agent-swarm 1.91.0 → 1.92.1

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 (114) hide show
  1. package/README.md +3 -2
  2. package/openapi.json +1005 -152
  3. package/package.json +6 -6
  4. package/plugin/skills/pages/SKILL.md +5 -2
  5. package/src/be/db.ts +662 -19
  6. package/src/be/memory/constants.ts +2 -1
  7. package/src/be/memory/providers/openai-embedding.ts +2 -5
  8. package/src/be/memory/providers/sqlite-store.ts +293 -76
  9. package/src/be/memory/types.ts +35 -0
  10. package/src/be/migrations/083_script_workflows.sql +51 -0
  11. package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
  12. package/src/be/migrations/085_script_runs_kind.sql +9 -0
  13. package/src/be/migrations/086_pages_default_authed.sql +64 -0
  14. package/src/be/migrations/087_skill_files.sql +19 -0
  15. package/src/be/modelsdev-cache.json +42310 -38617
  16. package/src/be/scripts/typecheck.ts +49 -0
  17. package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
  18. package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
  19. package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
  20. package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
  21. package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
  22. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
  23. package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
  24. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  25. package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
  26. package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
  27. package/src/be/seed-scripts/index.ts +51 -5
  28. package/src/be/seed-skills/index.ts +3 -3
  29. package/src/be/skill-sync.ts +91 -7
  30. package/src/be/swarm-config-guard.ts +17 -0
  31. package/src/commands/runner.ts +49 -4
  32. package/src/heartbeat/templates.ts +20 -16
  33. package/src/http/db-query.ts +20 -5
  34. package/src/http/index.ts +51 -7
  35. package/src/http/mcp-user.ts +23 -0
  36. package/src/http/mcp.ts +58 -0
  37. package/src/http/memory.ts +58 -0
  38. package/src/http/pages.ts +1 -1
  39. package/src/http/script-runs.ts +557 -0
  40. package/src/http/scripts.ts +39 -2
  41. package/src/http/skills.ts +225 -0
  42. package/src/prompts/session-templates.ts +24 -4
  43. package/src/providers/claude-adapter.ts +107 -28
  44. package/src/script-workflows/executor.ts +110 -0
  45. package/src/script-workflows/harness.ts +73 -0
  46. package/src/script-workflows/label-lint.ts +51 -0
  47. package/src/script-workflows/limits.ts +22 -0
  48. package/src/script-workflows/supervisor.ts +139 -0
  49. package/src/script-workflows/workflow-ctx.ts +209 -0
  50. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  51. package/src/scripts-runtime/swarm-sdk.ts +13 -0
  52. package/src/scripts-runtime/types/stdlib.d.ts +61 -0
  53. package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
  54. package/src/server.ts +4 -0
  55. package/src/slack/handlers.ts +11 -4
  56. package/src/slack/message-text.ts +98 -0
  57. package/src/slack/thread-buffer.ts +5 -3
  58. package/src/tests/claude-adapter-binary.test.ts +271 -74
  59. package/src/tests/create-page-tool.test.ts +19 -2
  60. package/src/tests/db-query.test.ts +28 -0
  61. package/src/tests/error-tracker.test.ts +121 -0
  62. package/src/tests/harness-provider-resolution.test.ts +33 -0
  63. package/src/tests/heartbeat-checklist.test.ts +36 -0
  64. package/src/tests/mcp-tools.test.ts +6 -0
  65. package/src/tests/mcp-transport-gc.test.ts +58 -0
  66. package/src/tests/memory-health-endpoint.test.ts +78 -0
  67. package/src/tests/memory-store.test.ts +221 -1
  68. package/src/tests/pages-http.test.ts +20 -2
  69. package/src/tests/pages-storage.test.ts +26 -0
  70. package/src/tests/prompt-template-session.test.ts +34 -5
  71. package/src/tests/script-runs-http.test.ts +278 -0
  72. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  73. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  74. package/src/tests/scripts-mcp-e2e.test.ts +102 -2
  75. package/src/tests/seed-scripts.test.ts +468 -3
  76. package/src/tests/skill-files-http.test.ts +171 -0
  77. package/src/tests/skill-files.test.ts +162 -0
  78. package/src/tests/skill-get-file-tool.test.ts +110 -0
  79. package/src/tests/skill-sync.test.ts +125 -6
  80. package/src/tests/slack-message-text.test.ts +250 -0
  81. package/src/tests/system-default-skills.test.ts +40 -0
  82. package/src/tools/create-page.ts +2 -2
  83. package/src/tools/db-query.ts +16 -6
  84. package/src/tools/script-runs.ts +123 -0
  85. package/src/tools/skills/index.ts +1 -0
  86. package/src/tools/skills/skill-get-file.ts +80 -0
  87. package/src/tools/slack-read.ts +12 -3
  88. package/src/tools/tool-config.ts +6 -2
  89. package/src/types.ts +72 -0
  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/schedules/daily-blocker-digest/content.md +68 -54
  94. package/templates/schedules/daily-compounding-reflection/content.md +4 -4
  95. package/templates/schedules/daily-hn-briefing/content.md +5 -5
  96. package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
  97. package/templates/schedules/gtm-weekly-review/content.md +9 -9
  98. package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
  99. package/templates/skills/agentmail-sending/content.md +6 -7
  100. package/templates/skills/desloppify/content.md +8 -9
  101. package/templates/skills/jira-interaction/content.md +25 -33
  102. package/templates/skills/kapso-whatsapp/content.md +29 -30
  103. package/templates/skills/linear-interaction/content.md +8 -9
  104. package/templates/skills/pages/content.md +205 -55
  105. package/templates/skills/profile-corruption-escalation/content.md +44 -85
  106. package/templates/skills/script-workflows/config.json +14 -0
  107. package/templates/skills/script-workflows/content.md +68 -0
  108. package/templates/skills/sprite-cli/content.md +4 -5
  109. package/templates/skills/swarm-scripts/content.md +2 -3
  110. package/templates/skills/turso-interaction/content.md +14 -17
  111. package/templates/skills/workflow-iterate/content.md +38 -391
  112. package/templates/skills/x-api-interactions/content.md +4 -6
  113. package/templates/skills/scheduled-task-resilience/config.json +0 -14
  114. package/templates/skills/scheduled-task-resilience/content.md +0 -95
@@ -0,0 +1,73 @@
1
+ import { buildWorkflowCtx } from "./workflow-ctx";
2
+
3
+ function requiredEnv(name: string): string {
4
+ const value = process.env[name];
5
+ if (!value) throw new Error(`Missing required env ${name}`);
6
+ return value;
7
+ }
8
+
9
+ async function postStatus(
10
+ runId: string,
11
+ baseUrl: string,
12
+ agentId: string,
13
+ apiKey: string,
14
+ body: Record<string, unknown>,
15
+ ): Promise<void> {
16
+ const res = await fetch(`${baseUrl}/api/internal/script-runs/${runId}/status`, {
17
+ method: "POST",
18
+ headers: {
19
+ Authorization: `Bearer ${apiKey}`,
20
+ "X-Agent-ID": agentId,
21
+ "Content-Type": "application/json",
22
+ },
23
+ body: JSON.stringify(body),
24
+ });
25
+ if (!res.ok) {
26
+ throw new Error(`status callback failed with ${res.status}: ${await res.text()}`);
27
+ }
28
+ }
29
+
30
+ const runId = requiredEnv("SCRIPT_RUN_ID");
31
+ const agentId = requiredEnv("SCRIPT_RUN_AGENT_ID");
32
+ const apiKey = requiredEnv("AGENT_SWARM_API_KEY");
33
+ const baseUrl = requiredEnv("MCP_BASE_URL").replace(/\/$/, "");
34
+ const sourceFile = requiredEnv("SCRIPT_RUN_SOURCE_FILE");
35
+ const argsFile = requiredEnv("SCRIPT_RUN_ARGS_FILE");
36
+ const userModulePath = `${requiredEnv("SCRIPT_RUN_TMPDIR")}/user-script.ts`;
37
+
38
+ const heartbeat = setInterval(() => {
39
+ fetch(`${baseUrl}/api/internal/script-runs/${runId}/heartbeat`, {
40
+ method: "POST",
41
+ headers: {
42
+ Authorization: `Bearer ${apiKey}`,
43
+ "X-Agent-ID": agentId,
44
+ },
45
+ }).catch(() => {});
46
+ }, 10_000);
47
+ heartbeat.unref?.();
48
+
49
+ try {
50
+ const source = await Bun.file(sourceFile).text();
51
+ const args = JSON.parse(await Bun.file(argsFile).text());
52
+ await Bun.write(userModulePath, source);
53
+ const mod = await import(userModulePath);
54
+ if (typeof mod.default !== "function") {
55
+ throw new Error("Script workflow must export a default function");
56
+ }
57
+ const ctx = buildWorkflowCtx({ runId, agentId, apiKey, baseUrl, args });
58
+ const output = await mod.default(args, ctx);
59
+ await postStatus(runId, baseUrl, agentId, apiKey, {
60
+ status: "completed",
61
+ output: output ?? null,
62
+ });
63
+ process.exit(0);
64
+ } catch (err) {
65
+ console.error(err instanceof Error ? err.stack || err.message : String(err));
66
+ await postStatus(runId, baseUrl, agentId, apiKey, {
67
+ status: "failed",
68
+ error: err instanceof Error ? err.message : String(err),
69
+ });
70
+ process.exit(1);
71
+ } finally {
72
+ clearInterval(heartbeat);
73
+ }
@@ -0,0 +1,51 @@
1
+ export type LabelLintError = {
2
+ label: string;
3
+ lineNumber: number | null;
4
+ detail: string;
5
+ };
6
+
7
+ export type LabelLintResult = { ok: true } | { ok: false; errors: LabelLintError[] };
8
+
9
+ const CTX_STEP_LITERAL_LABEL_PATTERN = /ctx\.step\.\w+\(\s*"([^"]+)"/g;
10
+ const LOOP_PATTERNS = [
11
+ /\bfor\s*\(/,
12
+ /\bwhile\s*\(/,
13
+ /\.map\s*\(/,
14
+ /\.forEach\s*\(/,
15
+ /\.reduce\s*\(/,
16
+ /\.flatMap\s*\(/,
17
+ ];
18
+
19
+ export function lintWorkflowLabels(source: string): LabelLintResult {
20
+ const errors: LabelLintError[] = [];
21
+ const lines = source.split("\n");
22
+
23
+ for (let i = 0; i < lines.length; i++) {
24
+ const line = lines[i] ?? "";
25
+ CTX_STEP_LITERAL_LABEL_PATTERN.lastIndex = 0;
26
+ let match = CTX_STEP_LITERAL_LABEL_PATTERN.exec(line);
27
+ while (match !== null) {
28
+ const label = match[1];
29
+ if (!label) {
30
+ match = CTX_STEP_LITERAL_LABEL_PATTERN.exec(line);
31
+ continue;
32
+ }
33
+ const windowStart = Math.max(0, i - 10);
34
+ const context = lines.slice(windowStart, i + 1).join("\n");
35
+ if (!LOOP_PATTERNS.some((pattern) => pattern.test(context))) {
36
+ match = CTX_STEP_LITERAL_LABEL_PATTERN.exec(line);
37
+ continue;
38
+ }
39
+ errors.push({
40
+ label,
41
+ lineNumber: i + 1,
42
+ detail:
43
+ `Literal string label "${label}" at line ${i + 1} appears inside a loop. ` +
44
+ "Labels must be unique per run; use a template literal that includes the loop variable.",
45
+ });
46
+ match = CTX_STEP_LITERAL_LABEL_PATTERN.exec(line);
47
+ }
48
+ }
49
+
50
+ return errors.length > 0 ? { ok: false, errors } : { ok: true };
51
+ }
@@ -0,0 +1,22 @@
1
+ const DEFAULT_MAX_STEPS = 1000;
2
+ const DEFAULT_MAX_WALL_MS = 24 * 60 * 60 * 1000;
3
+ const DEFAULT_MAX_AGENT_TASKS = 50;
4
+
5
+ function positiveIntEnv(name: string, fallback: number): number {
6
+ const raw = process.env[name];
7
+ if (!raw) return fallback;
8
+ const parsed = Number(raw);
9
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
10
+ }
11
+
12
+ export function scriptRunMaxSteps(): number {
13
+ return positiveIntEnv("SCRIPT_RUN_MAX_STEPS", DEFAULT_MAX_STEPS);
14
+ }
15
+
16
+ export function scriptRunMaxWallMs(): number {
17
+ return positiveIntEnv("SCRIPT_RUN_MAX_WALL_MS", DEFAULT_MAX_WALL_MS);
18
+ }
19
+
20
+ export function scriptRunMaxAgentTasks(): number {
21
+ return positiveIntEnv("SCRIPT_RUN_MAX_AGENT_TASKS", DEFAULT_MAX_AGENT_TASKS);
22
+ }
@@ -0,0 +1,139 @@
1
+ import { getRunningScriptRuns, getScriptRun, updateScriptRun } from "../be/db";
2
+ import type { ScriptRun } from "../types";
3
+ import { getApiKey } from "../utils/api-key";
4
+ import {
5
+ localProcessScriptExecutor,
6
+ type ScriptExecutionHandle,
7
+ type ScriptExecutor,
8
+ } from "./executor";
9
+ import { scriptRunMaxWallMs } from "./limits";
10
+
11
+ type ManagedRun = {
12
+ execution: ScriptExecutionHandle;
13
+ };
14
+
15
+ const managed = new Map<string, ManagedRun>();
16
+ let reconcileTimer: ReturnType<typeof setInterval> | null = null;
17
+ let scriptExecutor: ScriptExecutor = localProcessScriptExecutor;
18
+
19
+ function supervisorDisabled(): boolean {
20
+ return process.env.SCRIPT_RUN_SUPERVISOR_DISABLE === "true";
21
+ }
22
+
23
+ export function setScriptRunExecutor(executor: ScriptExecutor): void {
24
+ scriptExecutor = executor;
25
+ }
26
+
27
+ export async function startScriptRunProcess(
28
+ run: ScriptRun,
29
+ baseUrl: string,
30
+ apiKeyOverride?: string,
31
+ ): Promise<void> {
32
+ if (supervisorDisabled()) return;
33
+ if (managed.has(run.id)) return;
34
+ const apiKey = apiKeyOverride ?? getApiKey();
35
+ if (!apiKey) throw new Error("AGENT_SWARM_API_KEY is required to spawn script runs");
36
+ if (process.env.SCRIPT_WORKFLOW_DEBUG === "true") {
37
+ console.error(
38
+ `[script-workflows] spawning ${run.id} auth override=${apiKeyOverride ? "yes" : "no"} len=${apiKey.length}`,
39
+ );
40
+ }
41
+
42
+ const execution = await scriptExecutor.start({ run, baseUrl, apiKey });
43
+ managed.set(run.id, { execution });
44
+ updateScriptRun(run.id, {
45
+ status: "running",
46
+ pid: execution.pid,
47
+ lastHeartbeatAt: new Date().toISOString(),
48
+ });
49
+
50
+ execution.exited
51
+ .then(async ({ exitCode, stderr }) => {
52
+ const current = getScriptRun(run.id);
53
+ if (current && current.status === "running") {
54
+ if (exitCode !== 0) {
55
+ console.error(
56
+ `[script-workflows] run ${run.id} subprocess exited ${exitCode}: ${stderr.trim() || "(no stderr)"}`,
57
+ );
58
+ }
59
+ updateScriptRun(run.id, {
60
+ status: exitCode === 0 ? "completed" : "failed",
61
+ pid: null,
62
+ finishedAt: new Date().toISOString(),
63
+ error:
64
+ exitCode === 0
65
+ ? null
66
+ : stderr.trim() || `Script workflow subprocess exited ${exitCode}`,
67
+ });
68
+ }
69
+ })
70
+ .finally(async () => {
71
+ managed.delete(run.id);
72
+ await execution.cleanup();
73
+ });
74
+ }
75
+
76
+ export function terminateScriptRunProcess(runId: string): boolean {
77
+ const managedRun = managed.get(runId);
78
+ const run = getScriptRun(runId);
79
+ if (managedRun) {
80
+ managedRun.execution.terminate("SIGTERM");
81
+ managed.delete(runId);
82
+ return true;
83
+ }
84
+ if (run?.pid && scriptExecutor.isRunning(run.pid)) {
85
+ scriptExecutor.terminatePid(run.pid, "SIGTERM");
86
+ return true;
87
+ }
88
+ return false;
89
+ }
90
+
91
+ export function pauseScriptRunProcess(runId: string): void {
92
+ terminateScriptRunProcess(runId);
93
+ updateScriptRun(runId, { status: "paused", pid: null });
94
+ }
95
+
96
+ export function abortScriptRunLimit(runId: string, reason: string): void {
97
+ terminateScriptRunProcess(runId);
98
+ updateScriptRun(runId, {
99
+ status: "aborted_limit",
100
+ pid: null,
101
+ finishedAt: new Date().toISOString(),
102
+ error: reason,
103
+ });
104
+ }
105
+
106
+ export function reconcileScriptRuns(baseUrl: string): void {
107
+ if (supervisorDisabled()) return;
108
+ for (const run of getRunningScriptRuns()) {
109
+ if (run.status === "paused") continue;
110
+ const current = managed.get(run.id);
111
+ if (current && Date.now() - current.execution.startedAtMs > scriptRunMaxWallMs()) {
112
+ abortScriptRunLimit(run.id, `SCRIPT_RUN_MAX_WALL_MS exceeded (${scriptRunMaxWallMs()})`);
113
+ continue;
114
+ }
115
+ if (!current && (!run.pid || !scriptExecutor.isRunning(run.pid))) {
116
+ startScriptRunProcess(run, baseUrl).catch((err) => {
117
+ updateScriptRun(run.id, {
118
+ status: "failed",
119
+ pid: null,
120
+ finishedAt: new Date().toISOString(),
121
+ error: err instanceof Error ? err.message : String(err),
122
+ });
123
+ });
124
+ }
125
+ }
126
+ }
127
+
128
+ export function startScriptRunSupervisor(baseUrl: string): void {
129
+ if (supervisorDisabled() || reconcileTimer) return;
130
+ reconcileScriptRuns(baseUrl);
131
+ reconcileTimer = setInterval(() => reconcileScriptRuns(baseUrl), 15_000);
132
+ reconcileTimer.unref?.();
133
+ }
134
+
135
+ export function stopScriptRunSupervisor(): void {
136
+ if (reconcileTimer) clearInterval(reconcileTimer);
137
+ reconcileTimer = null;
138
+ for (const runId of [...managed.keys()]) terminateScriptRunProcess(runId);
139
+ }
@@ -0,0 +1,209 @@
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
+ durationMs?: number,
128
+ ): Promise<void> {
129
+ const body = (await fetchJson(`/api/internal/script-runs/${input.runId}/steps`, {
130
+ method: "POST",
131
+ body: JSON.stringify({ stepKey: label, stepType, config, status, result, error, durationMs }),
132
+ })) as StepWriteResponse;
133
+ if (!("ok" in body)) throw new Error(`Failed to write journal step ${label}`);
134
+ }
135
+
136
+ async function durableStep(
137
+ label: string,
138
+ stepType: string,
139
+ config: unknown,
140
+ execute: () => Promise<unknown>,
141
+ ): Promise<unknown> {
142
+ const replayed = await completedStep(label);
143
+ if (replayed.found) return replayed.result;
144
+ const startedAt = Date.now();
145
+ try {
146
+ const result = await execute();
147
+ const durationMs = Date.now() - startedAt;
148
+ await writeStep(label, stepType, config, "completed", result, undefined, durationMs);
149
+ return result;
150
+ } catch (err) {
151
+ const durationMs = Date.now() - startedAt;
152
+ const error = err instanceof Error ? err.message : String(err);
153
+ await writeStep(label, stepType, config, "failed", undefined, error, durationMs);
154
+ throw err;
155
+ }
156
+ }
157
+
158
+ const swarm = new Proxy({} as Record<string, (args?: unknown) => Promise<unknown>>, {
159
+ get(_target, prop) {
160
+ if (typeof prop !== "string") return undefined;
161
+ return (args?: unknown) =>
162
+ fetchJson("/api/mcp-bridge", {
163
+ method: "POST",
164
+ body: JSON.stringify({ tool: prop.replaceAll("_", "-"), args: args ?? {} }),
165
+ });
166
+ },
167
+ });
168
+
169
+ return {
170
+ run: { id: input.runId, agentId: input.agentId, args: input.args },
171
+ step: {
172
+ rawLlm: (label, config) =>
173
+ durableStep(label, "raw-llm", config, async () =>
174
+ fetchJson("/api/internal/raw-llm", {
175
+ method: "POST",
176
+ body: JSON.stringify(config),
177
+ }),
178
+ ),
179
+ agentTask: (label, config) =>
180
+ durableStep(label, "agent-task", config, async () =>
181
+ fetchJson(`/api/internal/script-runs/${input.runId}/agent-task`, {
182
+ method: "POST",
183
+ body: JSON.stringify({ stepKey: label, ...config }),
184
+ }),
185
+ ),
186
+ swarmScript: (label, config) =>
187
+ durableStep(label, "swarm-script", config, async () =>
188
+ fetchJson("/api/scripts/run", {
189
+ method: "POST",
190
+ body: JSON.stringify({
191
+ name: config.name ?? config.scriptName,
192
+ source: config.source,
193
+ args: config.args,
194
+ scope: config.scope,
195
+ fsMode: config.fsMode ?? "none",
196
+ intent: config.intent ?? `script-run:${input.runId}:${label}`,
197
+ idempotencyKey: config.idempotencyKey,
198
+ }),
199
+ }),
200
+ ),
201
+ humanInTheLoop: async () => {
202
+ throw new Error("ctx.step.humanInTheLoop is stubbed in Script Workflows v1");
203
+ },
204
+ },
205
+ swarm,
206
+ stdlib,
207
+ logger: console,
208
+ };
209
+ }
@@ -39,6 +39,9 @@ export const SDK_TOOL_NAME_MAP = {
39
39
  script_upsert: "script-upsert",
40
40
  script_delete: "script-delete", // destructive
41
41
  script_queryTypes: "script-query-types",
42
+ script_launchRun: "launch-script-run",
43
+ script_getRun: "get-script-run",
44
+ script_listRuns: "list-script-runs",
42
45
 
43
46
  // ── swarm / agent ──
44
47
  swarm_get: "get-swarm",
@@ -112,6 +115,7 @@ export const SDK_TOOL_NAME_MAP = {
112
115
  // ── skills ──
113
116
  skill_list: "skill-list",
114
117
  skill_get: "skill-get",
118
+ skill_getFile: "skill-get-file",
115
119
  skill_search: "skill-search",
116
120
  skill_create: "skill-create",
117
121
  skill_update: "skill-update",
@@ -290,6 +290,19 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
290
290
  if (!id) throw new Error("skill_get requires string `id`");
291
291
  return { method: "GET", path: `/api/skills/${encodeURIComponent(id)}` };
292
292
  }
293
+ case "skill_getFile": {
294
+ const skillId = typeof body.skillId === "string" ? body.skillId : undefined;
295
+ const path = typeof body.path === "string" ? body.path : undefined;
296
+ if (!skillId) throw new Error("skill_getFile requires string `skillId`");
297
+ if (!path) throw new Error("skill_getFile requires string `path`");
298
+ return {
299
+ method: "GET",
300
+ path: `/api/skills/${encodeURIComponent(skillId)}/files/${path
301
+ .split("/")
302
+ .map(encodeURIComponent)
303
+ .join("/")}`,
304
+ };
305
+ }
293
306
  case "skill_search":
294
307
  return { method: "POST", path: "/api/skills/search", body };
295
308
  case "skill_delete": {
@@ -262,6 +262,20 @@ declare module "swarm-sdk" {
262
262
  }): Promise<unknown>;
263
263
  script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
264
264
  script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
265
+ script_launchRun(args: {
266
+ source: string;
267
+ args?: unknown;
268
+ idempotencyKey?: string;
269
+ scriptName?: string;
270
+ requestedByUserId?: string;
271
+ }): Promise<unknown>;
272
+ script_getRun(args: { id: string }): Promise<unknown>;
273
+ script_listRuns(args?: {
274
+ status?: "running" | "paused" | "completed" | "failed" | "cancelled" | "aborted_limit";
275
+ agentId?: string;
276
+ limit?: number;
277
+ offset?: number;
278
+ }): Promise<unknown>;
265
279
 
266
280
  // --- write: repos ---
267
281
  repo_update(args: Record<string, unknown>): Promise<unknown>;
@@ -284,6 +298,7 @@ declare module "swarm-sdk" {
284
298
  includeBuiltin?: boolean;
285
299
  }): Promise<unknown>;
286
300
  skill_get(args: { id: string }): Promise<unknown>;
301
+ skill_getFile(args: { skillId: string; path: string }): Promise<unknown>;
287
302
  skill_search(args: { query: string; limit?: number }): Promise<unknown>;
288
303
  skill_create(args: Record<string, unknown>): Promise<unknown>;
289
304
  skill_update(args: Record<string, unknown>): Promise<unknown>;
@@ -320,7 +335,53 @@ declare module "swarm-sdk" {
320
335
 
321
336
  export interface ScriptLogger extends Console {}
322
337
 
338
+ export interface ScriptRunContext {
339
+ id: string;
340
+ agentId: string;
341
+ args: unknown;
342
+ }
343
+
344
+ export interface ScriptWorkflowSteps {
345
+ rawLlm(
346
+ label: string,
347
+ config: { prompt: string; model?: string; schema?: Record<string, unknown> },
348
+ ): Promise<unknown>;
349
+ agentTask(
350
+ label: string,
351
+ config: {
352
+ template?: string;
353
+ task?: string;
354
+ agentId?: string;
355
+ tags?: string[];
356
+ priority?: number;
357
+ offerMode?: boolean;
358
+ dir?: string;
359
+ vcsRepo?: string;
360
+ model?: string;
361
+ parentTaskId?: string;
362
+ requestedByUserId?: string;
363
+ outputSchema?: Record<string, unknown>;
364
+ },
365
+ ): Promise<unknown>;
366
+ swarmScript(
367
+ label: string,
368
+ config: {
369
+ name?: string;
370
+ scriptName?: string;
371
+ source?: string;
372
+ args?: unknown;
373
+ scope?: ScriptScope;
374
+ fsMode?: ScriptFsMode;
375
+ intent?: string;
376
+ idempotencyKey?: string;
377
+ },
378
+ ): Promise<unknown>;
379
+ humanInTheLoop(): Promise<never>;
380
+ }
381
+
323
382
  export interface ScriptContext {
383
+ run?: ScriptRunContext;
384
+ step?: ScriptWorkflowSteps;
324
385
  swarm: SwarmSdk & { config: SwarmConfig };
325
386
  stdlib: ScriptStdlib;
326
387
  logger: ScriptLogger;
@@ -244,6 +244,20 @@ declare module "swarm-sdk" {
244
244
  }): Promise<unknown>;
245
245
  script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
246
246
  script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
247
+ script_launchRun(args: {
248
+ source: string;
249
+ args?: unknown;
250
+ idempotencyKey?: string;
251
+ scriptName?: string;
252
+ requestedByUserId?: string;
253
+ }): Promise<unknown>;
254
+ script_getRun(args: { id: string }): Promise<unknown>;
255
+ script_listRuns(args?: {
256
+ status?: "running" | "paused" | "completed" | "failed" | "cancelled" | "aborted_limit";
257
+ agentId?: string;
258
+ limit?: number;
259
+ offset?: number;
260
+ }): Promise<unknown>;
247
261
 
248
262
  // --- write: repos ---
249
263
  repo_update(args: Record<string, unknown>): Promise<unknown>;
@@ -266,6 +280,7 @@ declare module "swarm-sdk" {
266
280
  includeBuiltin?: boolean;
267
281
  }): Promise<unknown>;
268
282
  skill_get(args: { id: string }): Promise<unknown>;
283
+ skill_getFile(args: { skillId: string; path: string }): Promise<unknown>;
269
284
  skill_search(args: { query: string; limit?: number }): Promise<unknown>;
270
285
  skill_create(args: Record<string, unknown>): Promise<unknown>;
271
286
  skill_update(args: Record<string, unknown>): Promise<unknown>;
@@ -302,7 +317,53 @@ declare module "swarm-sdk" {
302
317
 
303
318
  export interface ScriptLogger extends Console {}
304
319
 
320
+ export interface ScriptRunContext {
321
+ id: string;
322
+ agentId: string;
323
+ args: unknown;
324
+ }
325
+
326
+ export interface ScriptWorkflowSteps {
327
+ rawLlm(
328
+ label: string,
329
+ config: { prompt: string; model?: string; schema?: Record<string, unknown> },
330
+ ): Promise<unknown>;
331
+ agentTask(
332
+ label: string,
333
+ config: {
334
+ template?: string;
335
+ task?: string;
336
+ agentId?: string;
337
+ tags?: string[];
338
+ priority?: number;
339
+ offerMode?: boolean;
340
+ dir?: string;
341
+ vcsRepo?: string;
342
+ model?: string;
343
+ parentTaskId?: string;
344
+ requestedByUserId?: string;
345
+ outputSchema?: Record<string, unknown>;
346
+ },
347
+ ): Promise<unknown>;
348
+ swarmScript(
349
+ label: string,
350
+ config: {
351
+ name?: string;
352
+ scriptName?: string;
353
+ source?: string;
354
+ args?: unknown;
355
+ scope?: ScriptScope;
356
+ fsMode?: ScriptFsMode;
357
+ intent?: string;
358
+ idempotencyKey?: string;
359
+ },
360
+ ): Promise<unknown>;
361
+ humanInTheLoop(): Promise<never>;
362
+ }
363
+
305
364
  export interface ScriptContext {
365
+ run?: ScriptRunContext;
366
+ step?: ScriptWorkflowSteps;
306
367
  swarm: SwarmSdk & { config: SwarmConfig };
307
368
  stdlib: ScriptStdlib;
308
369
  logger: ScriptLogger;