@desplega.ai/agent-swarm 1.91.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 +585 -5
- package/package.json +1 -1
- package/src/be/db.ts +337 -1
- package/src/be/migrations/083_script_workflows.sql +51 -0
- package/src/be/modelsdev-cache.json +42352 -38595
- package/src/be/scripts/typecheck.ts +49 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +216 -6
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -0
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
- package/src/be/seed-scripts/index.ts +20 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/runner.ts +43 -2
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +10 -0
- package/src/http/script-runs.ts +555 -0
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +60 -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/sdk-allowlist.ts +3 -0
- package/src/scripts-runtime/types/stdlib.d.ts +60 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +60 -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/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/mcp-tools.test.ts +6 -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/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/db-query.ts +16 -6
- 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/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 +2 -3
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
countActiveScriptRuns,
|
|
5
|
+
countScriptRunJournalAgentTaskSteps,
|
|
6
|
+
countScriptRunJournalSteps,
|
|
7
|
+
countScriptRuns,
|
|
8
|
+
createScriptRun,
|
|
9
|
+
createTaskExtended,
|
|
10
|
+
getAgentById,
|
|
11
|
+
getLatestTaskByContextKey,
|
|
12
|
+
getScriptRun,
|
|
13
|
+
getScriptRunByIdempotencyKey,
|
|
14
|
+
getScriptRunJournalStep,
|
|
15
|
+
listScriptRunJournalSteps,
|
|
16
|
+
listScriptRuns,
|
|
17
|
+
updateScriptRun,
|
|
18
|
+
upsertScriptRunJournalStep,
|
|
19
|
+
} from "../be/db";
|
|
20
|
+
import { lintWorkflowLabels } from "../script-workflows/label-lint";
|
|
21
|
+
import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
|
|
22
|
+
import {
|
|
23
|
+
abortScriptRunLimit,
|
|
24
|
+
startScriptRunProcess,
|
|
25
|
+
terminateScriptRunProcess,
|
|
26
|
+
} from "../script-workflows/supervisor";
|
|
27
|
+
import { ScriptRunStatusSchema, TERMINAL_SCRIPT_RUN_STATUSES } from "../types";
|
|
28
|
+
import { getAppUrl } from "../utils/constants";
|
|
29
|
+
import { executeRawLlm, RawLlmConfigSchema } from "../workflows/executors/raw-llm";
|
|
30
|
+
import { route } from "./route-def";
|
|
31
|
+
import { deriveApiBaseUrl, json, jsonError } from "./utils";
|
|
32
|
+
|
|
33
|
+
const DEFAULT_SCRIPT_RUN_CONCURRENCY_CAP = 10;
|
|
34
|
+
|
|
35
|
+
const runIdParamsSchema = z.object({ runId: z.string().uuid() });
|
|
36
|
+
const idParamsSchema = z.object({ id: z.string().uuid() });
|
|
37
|
+
const stepParamsSchema = z.object({
|
|
38
|
+
runId: z.string().uuid(),
|
|
39
|
+
stepKey: z.string().min(1),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const createScriptRunBodySchema = z.object({
|
|
43
|
+
source: z.string().min(1),
|
|
44
|
+
args: z.unknown().optional(),
|
|
45
|
+
background: z.boolean().default(true),
|
|
46
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
47
|
+
scriptName: z.string().min(1).max(200).optional(),
|
|
48
|
+
requestedByUserId: z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const listScriptRunsQuerySchema = z.object({
|
|
52
|
+
status: ScriptRunStatusSchema.optional(),
|
|
53
|
+
agentId: z.string().optional(),
|
|
54
|
+
limit: z.coerce.number().int().min(1).max(500).optional(),
|
|
55
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const journalStepBodySchema = z.object({
|
|
59
|
+
stepKey: z.string().min(1),
|
|
60
|
+
stepType: z.string().min(1),
|
|
61
|
+
config: z.unknown().optional(),
|
|
62
|
+
status: z.enum(["completed", "failed"]),
|
|
63
|
+
result: z.unknown().optional(),
|
|
64
|
+
error: z.string().optional(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const statusBodySchema = z.discriminatedUnion("status", [
|
|
68
|
+
z.object({ status: z.literal("completed"), output: z.unknown().optional() }),
|
|
69
|
+
z.object({ status: z.literal("failed"), error: z.string().optional() }),
|
|
70
|
+
z.object({ status: z.literal("paused") }),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
const agentTaskBodySchema = z.object({
|
|
74
|
+
stepKey: z.string().min(1),
|
|
75
|
+
template: z.string().optional(),
|
|
76
|
+
task: z.string().optional(),
|
|
77
|
+
agentId: z.string().uuid().optional(),
|
|
78
|
+
tags: z.array(z.string()).optional(),
|
|
79
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
80
|
+
offerMode: z.boolean().optional(),
|
|
81
|
+
dir: z.string().min(1).optional(),
|
|
82
|
+
vcsRepo: z.string().min(1).optional(),
|
|
83
|
+
model: z.string().min(1).optional(),
|
|
84
|
+
parentTaskId: z.string().uuid().optional(),
|
|
85
|
+
requestedByUserId: z.string().optional(),
|
|
86
|
+
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const createScriptRunRoute = route({
|
|
90
|
+
method: "post",
|
|
91
|
+
path: "/api/script-runs",
|
|
92
|
+
pattern: ["api", "script-runs"],
|
|
93
|
+
operationId: "script_runs_create",
|
|
94
|
+
summary: "Launch a durable script workflow run",
|
|
95
|
+
description:
|
|
96
|
+
"Foundation endpoint for Script Workflows v1. In PR 1 it persists the run and returns its dashboard URL; spawning is added by the supervisor PR.",
|
|
97
|
+
tags: ["Script Runs"],
|
|
98
|
+
body: createScriptRunBodySchema,
|
|
99
|
+
responses: {
|
|
100
|
+
201: { description: "Script run created" },
|
|
101
|
+
400: { description: "Validation or label-lint failure" },
|
|
102
|
+
409: { description: "Existing idempotent run returned" },
|
|
103
|
+
429: { description: "Script run concurrency cap reached" },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const listScriptRunsRoute = route({
|
|
108
|
+
method: "get",
|
|
109
|
+
path: "/api/script-runs",
|
|
110
|
+
pattern: ["api", "script-runs"],
|
|
111
|
+
operationId: "script_runs_list",
|
|
112
|
+
summary: "List script workflow runs",
|
|
113
|
+
tags: ["Script Runs"],
|
|
114
|
+
query: listScriptRunsQuerySchema,
|
|
115
|
+
responses: {
|
|
116
|
+
200: { description: "Paginated script run list" },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const getScriptRunRoute = route({
|
|
121
|
+
method: "get",
|
|
122
|
+
path: "/api/script-runs/{id}",
|
|
123
|
+
pattern: ["api", "script-runs", null],
|
|
124
|
+
operationId: "script_runs_get",
|
|
125
|
+
summary: "Get a script workflow run with journal",
|
|
126
|
+
tags: ["Script Runs"],
|
|
127
|
+
params: idParamsSchema,
|
|
128
|
+
responses: {
|
|
129
|
+
200: { description: "Script run detail" },
|
|
130
|
+
404: { description: "Script run not found" },
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const deleteScriptRunRoute = route({
|
|
135
|
+
method: "delete",
|
|
136
|
+
path: "/api/script-runs/{id}",
|
|
137
|
+
pattern: ["api", "script-runs", null],
|
|
138
|
+
operationId: "script_runs_cancel",
|
|
139
|
+
summary: "Cancel a script workflow run",
|
|
140
|
+
tags: ["Script Runs"],
|
|
141
|
+
params: idParamsSchema,
|
|
142
|
+
responses: {
|
|
143
|
+
204: { description: "Script run cancelled, or already terminal" },
|
|
144
|
+
404: { description: "Script run not found" },
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const getInternalStepRoute = route({
|
|
149
|
+
method: "get",
|
|
150
|
+
path: "/api/internal/script-runs/{runId}/steps/{stepKey}",
|
|
151
|
+
pattern: ["api", "internal", "script-runs", null, "steps", null],
|
|
152
|
+
operationId: "script_runs_internal_step_get",
|
|
153
|
+
summary: "Get a script run journal step",
|
|
154
|
+
tags: ["Script Runs"],
|
|
155
|
+
params: stepParamsSchema,
|
|
156
|
+
responses: {
|
|
157
|
+
200: { description: "Journal step found" },
|
|
158
|
+
404: { description: "Journal step not found" },
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const postInternalStepRoute = route({
|
|
163
|
+
method: "post",
|
|
164
|
+
path: "/api/internal/script-runs/{runId}/steps",
|
|
165
|
+
pattern: ["api", "internal", "script-runs", null, "steps"],
|
|
166
|
+
operationId: "script_runs_internal_step_create",
|
|
167
|
+
summary: "Write a script run journal step",
|
|
168
|
+
tags: ["Script Runs"],
|
|
169
|
+
params: runIdParamsSchema,
|
|
170
|
+
body: journalStepBodySchema,
|
|
171
|
+
responses: {
|
|
172
|
+
201: { description: "Journal step written" },
|
|
173
|
+
404: { description: "Script run not found" },
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const heartbeatRoute = route({
|
|
178
|
+
method: "post",
|
|
179
|
+
path: "/api/internal/script-runs/{runId}/heartbeat",
|
|
180
|
+
pattern: ["api", "internal", "script-runs", null, "heartbeat"],
|
|
181
|
+
operationId: "script_runs_internal_heartbeat",
|
|
182
|
+
summary: "Record a script run heartbeat",
|
|
183
|
+
tags: ["Script Runs"],
|
|
184
|
+
params: runIdParamsSchema,
|
|
185
|
+
responses: {
|
|
186
|
+
204: { description: "Heartbeat recorded" },
|
|
187
|
+
404: { description: "Script run not found" },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const statusRoute = route({
|
|
192
|
+
method: "post",
|
|
193
|
+
path: "/api/internal/script-runs/{runId}/status",
|
|
194
|
+
pattern: ["api", "internal", "script-runs", null, "status"],
|
|
195
|
+
operationId: "script_runs_internal_status",
|
|
196
|
+
summary: "Update script run status from subprocess",
|
|
197
|
+
tags: ["Script Runs"],
|
|
198
|
+
params: runIdParamsSchema,
|
|
199
|
+
body: statusBodySchema,
|
|
200
|
+
responses: {
|
|
201
|
+
204: { description: "Status updated" },
|
|
202
|
+
404: { description: "Script run not found" },
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const rawLlmRoute = route({
|
|
207
|
+
method: "post",
|
|
208
|
+
path: "/api/internal/raw-llm",
|
|
209
|
+
pattern: ["api", "internal", "raw-llm"],
|
|
210
|
+
operationId: "script_runs_internal_raw_llm",
|
|
211
|
+
summary: "Execute a raw LLM call for a script workflow",
|
|
212
|
+
tags: ["Script Runs"],
|
|
213
|
+
body: RawLlmConfigSchema,
|
|
214
|
+
responses: {
|
|
215
|
+
200: { description: "LLM call completed" },
|
|
216
|
+
500: { description: "LLM call failed" },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const agentTaskRoute = route({
|
|
221
|
+
method: "post",
|
|
222
|
+
path: "/api/internal/script-runs/{runId}/agent-task",
|
|
223
|
+
pattern: ["api", "internal", "script-runs", null, "agent-task"],
|
|
224
|
+
operationId: "script_runs_internal_agent_task",
|
|
225
|
+
summary: "Create or wait for a script workflow agent task step",
|
|
226
|
+
tags: ["Script Runs"],
|
|
227
|
+
params: runIdParamsSchema,
|
|
228
|
+
body: agentTaskBodySchema,
|
|
229
|
+
responses: {
|
|
230
|
+
200: { description: "Agent task completed" },
|
|
231
|
+
202: { description: "Agent task created or still running" },
|
|
232
|
+
404: { description: "Script run not found" },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
237
|
+
if (!agentId) {
|
|
238
|
+
jsonError(res, "X-Agent-ID required for script runs API", 400);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const agent = getAgentById(agentId);
|
|
242
|
+
if (!agent) {
|
|
243
|
+
jsonError(res, "Agent not found", 404);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return agent;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function scriptRunUrl(id: string): string {
|
|
250
|
+
return `${getAppUrl()}/script-runs/${id}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function scriptRunConcurrencyCap(): number {
|
|
254
|
+
const raw = process.env.SCRIPT_RUN_CONCURRENCY_CAP;
|
|
255
|
+
if (!raw) return DEFAULT_SCRIPT_RUN_CONCURRENCY_CAP;
|
|
256
|
+
const parsed = Number(raw);
|
|
257
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
258
|
+
? Math.floor(parsed)
|
|
259
|
+
: DEFAULT_SCRIPT_RUN_CONCURRENCY_CAP;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function bearerToken(req: IncomingMessage): string | undefined {
|
|
263
|
+
const raw = req.headers.authorization;
|
|
264
|
+
const header = Array.isArray(raw) ? raw[0] : raw;
|
|
265
|
+
return header?.startsWith("Bearer ") ? header.slice("Bearer ".length).trim() : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function sleep(ms: number): Promise<void> {
|
|
269
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function assertRunWithinLimits(runId: string): { ok: true } | { ok: false; error: string } {
|
|
273
|
+
const maxSteps = scriptRunMaxSteps();
|
|
274
|
+
const stepCount = countScriptRunJournalSteps(runId);
|
|
275
|
+
if (stepCount > maxSteps) {
|
|
276
|
+
const error = `SCRIPT_RUN_MAX_STEPS exceeded (${stepCount}/${maxSteps})`;
|
|
277
|
+
abortScriptRunLimit(runId, error);
|
|
278
|
+
return { ok: false, error };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const maxAgentTasks = scriptRunMaxAgentTasks();
|
|
282
|
+
const agentTaskCount = countScriptRunJournalAgentTaskSteps(runId);
|
|
283
|
+
if (agentTaskCount > maxAgentTasks) {
|
|
284
|
+
const error = `SCRIPT_RUN_MAX_AGENT_TASKS exceeded (${agentTaskCount}/${maxAgentTasks})`;
|
|
285
|
+
abortScriptRunLimit(runId, error);
|
|
286
|
+
return { ok: false, error };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { ok: true };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function handleScriptRuns(
|
|
293
|
+
req: IncomingMessage,
|
|
294
|
+
res: ServerResponse,
|
|
295
|
+
pathSegments: string[],
|
|
296
|
+
queryParams: URLSearchParams,
|
|
297
|
+
agentId: string | undefined,
|
|
298
|
+
): Promise<boolean> {
|
|
299
|
+
if (createScriptRunRoute.match(req.method, pathSegments)) {
|
|
300
|
+
const parsed = await createScriptRunRoute.parse(req, res, pathSegments, queryParams);
|
|
301
|
+
if (!parsed) return true;
|
|
302
|
+
const agent = requireAgent(res, agentId);
|
|
303
|
+
if (!agent) return true;
|
|
304
|
+
|
|
305
|
+
const lint = lintWorkflowLabels(parsed.body.source);
|
|
306
|
+
if (!lint.ok) {
|
|
307
|
+
json(
|
|
308
|
+
res,
|
|
309
|
+
{
|
|
310
|
+
error: "label_lint_violation",
|
|
311
|
+
message: "Launch rejected: loop step label collision detected",
|
|
312
|
+
violations: lint.errors,
|
|
313
|
+
},
|
|
314
|
+
400,
|
|
315
|
+
);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (parsed.body.idempotencyKey) {
|
|
320
|
+
const existingRun = getScriptRunByIdempotencyKey(parsed.body.idempotencyKey);
|
|
321
|
+
if (existingRun) {
|
|
322
|
+
json(
|
|
323
|
+
res,
|
|
324
|
+
{ id: existingRun.id, status: existingRun.status, url: scriptRunUrl(existingRun.id) },
|
|
325
|
+
409,
|
|
326
|
+
);
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const cap = scriptRunConcurrencyCap();
|
|
332
|
+
if (countActiveScriptRuns() >= cap) {
|
|
333
|
+
json(res, { error: "script_run_concurrency_cap", cap }, 429);
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const { run, existing } = createScriptRun({
|
|
338
|
+
id: crypto.randomUUID(),
|
|
339
|
+
agentId: agent.id,
|
|
340
|
+
source: parsed.body.source,
|
|
341
|
+
args: parsed.body.args ?? null,
|
|
342
|
+
scriptName: parsed.body.scriptName,
|
|
343
|
+
idempotencyKey: parsed.body.idempotencyKey,
|
|
344
|
+
requestedByUserId: parsed.body.requestedByUserId,
|
|
345
|
+
createdBy: parsed.body.requestedByUserId,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!existing && parsed.body.background) {
|
|
349
|
+
startScriptRunProcess(run, deriveApiBaseUrl(req), bearerToken(req)).catch((err) => {
|
|
350
|
+
updateScriptRun(run.id, {
|
|
351
|
+
status: "failed",
|
|
352
|
+
pid: null,
|
|
353
|
+
finishedAt: new Date().toISOString(),
|
|
354
|
+
error: err instanceof Error ? err.message : String(err),
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
json(res, { id: run.id, status: run.status, url: scriptRunUrl(run.id) }, existing ? 409 : 201);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (listScriptRunsRoute.match(req.method, pathSegments)) {
|
|
364
|
+
const parsed = await listScriptRunsRoute.parse(req, res, pathSegments, queryParams);
|
|
365
|
+
if (!parsed) return true;
|
|
366
|
+
const opts = {
|
|
367
|
+
status: parsed.query.status,
|
|
368
|
+
agentId: parsed.query.agentId,
|
|
369
|
+
limit: parsed.query.limit ?? 50,
|
|
370
|
+
offset: parsed.query.offset ?? 0,
|
|
371
|
+
};
|
|
372
|
+
json(res, {
|
|
373
|
+
runs: listScriptRuns(opts),
|
|
374
|
+
total: countScriptRuns({ status: opts.status, agentId: opts.agentId }),
|
|
375
|
+
});
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (getScriptRunRoute.match(req.method, pathSegments)) {
|
|
380
|
+
const parsed = await getScriptRunRoute.parse(req, res, pathSegments, queryParams);
|
|
381
|
+
if (!parsed) return true;
|
|
382
|
+
const run = getScriptRun(parsed.params.id);
|
|
383
|
+
if (!run) {
|
|
384
|
+
jsonError(res, "Script run not found", 404);
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
json(res, { run, journal: listScriptRunJournalSteps(run.id) });
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (deleteScriptRunRoute.match(req.method, pathSegments)) {
|
|
392
|
+
const parsed = await deleteScriptRunRoute.parse(req, res, pathSegments, queryParams);
|
|
393
|
+
if (!parsed) return true;
|
|
394
|
+
const run = getScriptRun(parsed.params.id);
|
|
395
|
+
if (!run) {
|
|
396
|
+
jsonError(res, "Script run not found", 404);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
if (TERMINAL_SCRIPT_RUN_STATUSES.some((status) => status === run.status)) {
|
|
400
|
+
res.writeHead(204);
|
|
401
|
+
res.end();
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
terminateScriptRunProcess(run.id);
|
|
405
|
+
updateScriptRun(run.id, {
|
|
406
|
+
status: "cancelled",
|
|
407
|
+
pid: null,
|
|
408
|
+
finishedAt: new Date().toISOString(),
|
|
409
|
+
});
|
|
410
|
+
res.writeHead(204);
|
|
411
|
+
res.end();
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (getInternalStepRoute.match(req.method, pathSegments)) {
|
|
416
|
+
const parsed = await getInternalStepRoute.parse(req, res, pathSegments, queryParams);
|
|
417
|
+
if (!parsed) return true;
|
|
418
|
+
const step = getScriptRunJournalStep(parsed.params.runId, parsed.params.stepKey);
|
|
419
|
+
if (!step) {
|
|
420
|
+
jsonError(res, "Script run journal step not found", 404);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
json(res, { stepKey: step.stepKey, stepType: step.stepType, result: step.result });
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (postInternalStepRoute.match(req.method, pathSegments)) {
|
|
428
|
+
const parsed = await postInternalStepRoute.parse(req, res, pathSegments, queryParams);
|
|
429
|
+
if (!parsed) return true;
|
|
430
|
+
const run = getScriptRun(parsed.params.runId);
|
|
431
|
+
if (!run) {
|
|
432
|
+
jsonError(res, "Script run not found", 404);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
upsertScriptRunJournalStep({
|
|
436
|
+
runId: run.id,
|
|
437
|
+
stepKey: parsed.body.stepKey,
|
|
438
|
+
stepType: parsed.body.stepType,
|
|
439
|
+
config: parsed.body.config ?? {},
|
|
440
|
+
status: parsed.body.status,
|
|
441
|
+
result: parsed.body.result,
|
|
442
|
+
error: parsed.body.error,
|
|
443
|
+
});
|
|
444
|
+
const limit = assertRunWithinLimits(run.id);
|
|
445
|
+
if (!limit.ok) {
|
|
446
|
+
json(res, { error: "script_run_limit", message: limit.error }, 429);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
json(res, { ok: true }, 201);
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (heartbeatRoute.match(req.method, pathSegments)) {
|
|
454
|
+
const parsed = await heartbeatRoute.parse(req, res, pathSegments, queryParams);
|
|
455
|
+
if (!parsed) return true;
|
|
456
|
+
if (!getScriptRun(parsed.params.runId)) {
|
|
457
|
+
jsonError(res, "Script run not found", 404);
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
updateScriptRun(parsed.params.runId, { lastHeartbeatAt: new Date().toISOString() });
|
|
461
|
+
res.writeHead(204);
|
|
462
|
+
res.end();
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (statusRoute.match(req.method, pathSegments)) {
|
|
467
|
+
const parsed = await statusRoute.parse(req, res, pathSegments, queryParams);
|
|
468
|
+
if (!parsed) return true;
|
|
469
|
+
const run = getScriptRun(parsed.params.runId);
|
|
470
|
+
if (!run) {
|
|
471
|
+
jsonError(res, "Script run not found", 404);
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
if (TERMINAL_SCRIPT_RUN_STATUSES.some((status) => status === run.status)) {
|
|
475
|
+
res.writeHead(204);
|
|
476
|
+
res.end();
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
updateScriptRun(parsed.params.runId, {
|
|
480
|
+
status: parsed.body.status,
|
|
481
|
+
pid: null,
|
|
482
|
+
finishedAt: parsed.body.status === "paused" ? null : new Date().toISOString(),
|
|
483
|
+
output: "output" in parsed.body ? parsed.body.output : undefined,
|
|
484
|
+
error: "error" in parsed.body ? (parsed.body.error ?? null) : undefined,
|
|
485
|
+
});
|
|
486
|
+
res.writeHead(204);
|
|
487
|
+
res.end();
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (rawLlmRoute.match(req.method, pathSegments)) {
|
|
492
|
+
const parsed = await rawLlmRoute.parse(req, res, pathSegments, queryParams);
|
|
493
|
+
if (!parsed) return true;
|
|
494
|
+
const result = await executeRawLlm(parsed.body);
|
|
495
|
+
if (result.status === "failed") {
|
|
496
|
+
json(res, { error: result.error }, 500);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
json(res, result.output);
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (agentTaskRoute.match(req.method, pathSegments)) {
|
|
504
|
+
const parsed = await agentTaskRoute.parse(req, res, pathSegments, queryParams);
|
|
505
|
+
if (!parsed) return true;
|
|
506
|
+
const run = getScriptRun(parsed.params.runId);
|
|
507
|
+
if (!run) {
|
|
508
|
+
jsonError(res, "Script run not found", 404);
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const contextKey = `script-run:${run.id}:${parsed.body.stepKey}`;
|
|
513
|
+
let task = getLatestTaskByContextKey(contextKey);
|
|
514
|
+
if (!task) {
|
|
515
|
+
task = createTaskExtended(parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey, {
|
|
516
|
+
agentId: parsed.body.agentId,
|
|
517
|
+
tags: parsed.body.tags,
|
|
518
|
+
priority: parsed.body.priority,
|
|
519
|
+
offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
|
|
520
|
+
taskType: "script-run-step",
|
|
521
|
+
source: "mcp",
|
|
522
|
+
dir: parsed.body.dir,
|
|
523
|
+
vcsRepo: parsed.body.vcsRepo,
|
|
524
|
+
model: parsed.body.model,
|
|
525
|
+
parentTaskId: parsed.body.parentTaskId,
|
|
526
|
+
requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
|
|
527
|
+
outputSchema: parsed.body.outputSchema,
|
|
528
|
+
contextKey,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const deadline = Date.now() + 30_000;
|
|
533
|
+
while (Date.now() < deadline) {
|
|
534
|
+
const latest = getLatestTaskByContextKey(contextKey) ?? task;
|
|
535
|
+
if (latest.status === "completed") {
|
|
536
|
+
json(res, { taskId: latest.id, taskOutput: latest.output ?? null });
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
if (
|
|
540
|
+
latest.status === "failed" ||
|
|
541
|
+
latest.status === "cancelled" ||
|
|
542
|
+
latest.status === "superseded"
|
|
543
|
+
) {
|
|
544
|
+
json(res, { error: `Agent task ${latest.status}`, taskId: latest.id }, 409);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
await sleep(1000);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
json(res, { taskId: task.id, status: task.status }, 202);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
@@ -378,11 +378,9 @@ registerTemplate({
|
|
|
378
378
|
|
|
379
379
|
You have access to the \`context-mode\` MCP tools (\`batch_execute\`, \`execute\`, \`execute_file\`, \`search\`, \`fetch_and_index\`, \`index\`) which compress tool output to save context window space. For data-heavy operations (web fetches, large file reads, CLI output processing), prefer these over raw Bash/WebFetch to avoid flooding your context window with raw output.
|
|
380
380
|
|
|
381
|
-
When a tool returns more than a few dozen lines — JSON payloads, log tails, search results, API responses — route it through \`ctx_execute\` or \`ctx_batch_execute\` so only the derived answer enters your conversation. This is especially important for tasks that make many Bash/Read/MCP calls in sequence; each raw response compounds context pressure.
|
|
382
|
-
|
|
383
381
|
### Agent Scripts — for bulk, repetitive, or data-heavy work
|
|
384
382
|
|
|
385
|
-
Use **scripts** (\`script-upsert\` + \`script-run\`) when a task involves repetitive SDK calls, large data processing, or deterministic multi-step pipelines. Scripts run out-of-process and return only their final result
|
|
383
|
+
Use **scripts** (\`script-upsert\` + \`script-run\`) when a task involves repetitive SDK calls, large data processing, or deterministic multi-step pipelines. Scripts run out-of-process and return only their final result.
|
|
386
384
|
|
|
387
385
|
**Decision rubric — when to use scripts vs. other approaches:**
|
|
388
386
|
|
|
@@ -394,7 +392,6 @@ Use **scripts** (\`script-upsert\` + \`script-run\`) when a task involves repeti
|
|
|
394
392
|
| Single expensive web fetch | \`ctx_fetch_and_index\` (context-mode) |
|
|
395
393
|
| Multi-agent fan-out, parallel work, deterministic pipeline | **Workflow** |
|
|
396
394
|
| One-off bash/TS with no reuse needed | \`code-mode run\` (Bash) |
|
|
397
|
-
| Same logic needed across sessions/agents | **Named script** (\`script-upsert\` + reuse) |
|
|
398
395
|
|
|
399
396
|
The 5 script tools (\`script-search\`, \`script-run\`, \`script-upsert\`, \`script-delete\`, \`script-query-types\`) are deferred tools. Call ToolSearch to load \`script-upsert\`, \`script-run\`, and \`script-query-types\` before using them.
|
|
400
397
|
|
|
@@ -407,6 +404,27 @@ The 5 script tools (\`script-search\`, \`script-run\`, \`script-upsert\`, \`scri
|
|
|
407
404
|
category: "system",
|
|
408
405
|
});
|
|
409
406
|
|
|
407
|
+
registerTemplate({
|
|
408
|
+
eventType: "system.agent.seed_scripts",
|
|
409
|
+
header: "",
|
|
410
|
+
defaultBody: `
|
|
411
|
+
### Pre-built Seed Scripts
|
|
412
|
+
|
|
413
|
+
The swarm ships named scripts at global scope — each replaces a multi-step tool chain.
|
|
414
|
+
See the \`swarm-scripts\` skill for the full catalog, signatures, and usage patterns.
|
|
415
|
+
|
|
416
|
+
**At every task start (do this FIRST):** Run \`task-context-gathering\` with the task ID and 2-4 queries from the task description. Returns a slimmed task projection + deduped memories in one call — replaces task_get + memory_search loops.
|
|
417
|
+
|
|
418
|
+
**At task start when you only need memory:** Run \`smart-recall\` with 2-4 search angles from the task description.
|
|
419
|
+
|
|
420
|
+
**For any other repetitive multi-tool pattern:** Call \`script-search\` with a natural-language description. If a script exists, use it.
|
|
421
|
+
|
|
422
|
+
Script tools are deferred — load via \`ToolSearch("select:script-search,script-run")\` before first use.
|
|
423
|
+
`,
|
|
424
|
+
variables: [],
|
|
425
|
+
category: "system",
|
|
426
|
+
});
|
|
427
|
+
|
|
410
428
|
// system.agent.guidelines removed — its content (communication etiquette, todos)
|
|
411
429
|
// is covered by worker/lead templates and filesystem template respectively.
|
|
412
430
|
|
|
@@ -602,6 +620,7 @@ registerTemplate({
|
|
|
602
620
|
{{@template[system.agent.filesystem]}}
|
|
603
621
|
{{@template[system.agent.self_awareness]}}
|
|
604
622
|
{{@template[system.agent.context_mode]}}
|
|
623
|
+
{{@template[system.agent.seed_scripts]}}
|
|
605
624
|
|
|
606
625
|
{{@template[system.agent.system]}}
|
|
607
626
|
{{@template[system.agent.share_urls]}}
|
|
@@ -623,6 +642,7 @@ registerTemplate({
|
|
|
623
642
|
{{@template[system.agent.filesystem]}}
|
|
624
643
|
{{@template[system.agent.self_awareness]}}
|
|
625
644
|
{{@template[system.agent.context_mode]}}
|
|
645
|
+
{{@template[system.agent.seed_scripts]}}
|
|
626
646
|
|
|
627
647
|
{{@template[system.agent.system]}}
|
|
628
648
|
{{@template[system.agent.share_urls]}}
|