@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.
- package/README.md +3 -2
- package/openapi.json +1005 -152
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +662 -19
- package/src/be/memory/constants.ts +2 -1
- package/src/be/memory/providers/openai-embedding.ts +2 -5
- package/src/be/memory/providers/sqlite-store.ts +293 -76
- package/src/be/memory/types.ts +35 -0
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +42310 -38617
- package/src/be/scripts/typecheck.ts +49 -0
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
- package/src/be/seed-scripts/index.ts +51 -5
- package/src/be/seed-skills/index.ts +3 -3
- package/src/be/skill-sync.ts +91 -7
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/runner.ts +49 -4
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +51 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +58 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +557 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +107 -28
- 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 +209 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +61 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
- package/src/server.ts +4 -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 +271 -74
- package/src/tests/create-page-tool.test.ts +19 -2
- 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/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +6 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -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 +102 -2
- package/src/tests/seed-scripts.test.ts +468 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/db-query.ts +16 -6
- package/src/tools/script-runs.ts +123 -0
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +6 -2
- package/src/types.ts +72 -0
- 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/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/pages/content.md +205 -55
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/swarm-scripts/content.md +2 -3
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- 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;
|