@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.
Files changed (58) hide show
  1. package/README.md +2 -1
  2. package/openapi.json +585 -5
  3. package/package.json +1 -1
  4. package/src/be/db.ts +337 -1
  5. package/src/be/migrations/083_script_workflows.sql +51 -0
  6. package/src/be/modelsdev-cache.json +42352 -38595
  7. package/src/be/scripts/typecheck.ts +49 -0
  8. package/src/be/seed-scripts/catalog/compound-insights.ts +216 -6
  9. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -0
  10. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  11. package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
  12. package/src/be/seed-scripts/index.ts +20 -2
  13. package/src/be/seed-skills/index.ts +7 -0
  14. package/src/be/swarm-config-guard.ts +17 -0
  15. package/src/commands/runner.ts +43 -2
  16. package/src/http/db-query.ts +20 -5
  17. package/src/http/index.ts +10 -0
  18. package/src/http/script-runs.ts +555 -0
  19. package/src/prompts/session-templates.ts +24 -4
  20. package/src/providers/claude-adapter.ts +60 -13
  21. package/src/script-workflows/executor.ts +110 -0
  22. package/src/script-workflows/harness.ts +73 -0
  23. package/src/script-workflows/label-lint.ts +51 -0
  24. package/src/script-workflows/limits.ts +22 -0
  25. package/src/script-workflows/supervisor.ts +139 -0
  26. package/src/script-workflows/workflow-ctx.ts +205 -0
  27. package/src/scripts-runtime/sdk-allowlist.ts +3 -0
  28. package/src/scripts-runtime/types/stdlib.d.ts +60 -0
  29. package/src/scripts-runtime/types/swarm-sdk.d.ts +60 -0
  30. package/src/server.ts +2 -0
  31. package/src/slack/handlers.ts +11 -4
  32. package/src/slack/message-text.ts +98 -0
  33. package/src/slack/thread-buffer.ts +5 -3
  34. package/src/tests/claude-adapter-binary.test.ts +147 -4
  35. package/src/tests/db-query.test.ts +28 -0
  36. package/src/tests/error-tracker.test.ts +121 -0
  37. package/src/tests/harness-provider-resolution.test.ts +33 -0
  38. package/src/tests/mcp-tools.test.ts +6 -0
  39. package/src/tests/prompt-template-session.test.ts +34 -5
  40. package/src/tests/script-runs-http.test.ts +278 -0
  41. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  42. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  43. package/src/tests/scripts-mcp-e2e.test.ts +49 -2
  44. package/src/tests/seed-scripts.test.ts +347 -2
  45. package/src/tests/slack-message-text.test.ts +250 -0
  46. package/src/tests/system-default-skills.test.ts +40 -0
  47. package/src/tools/db-query.ts +16 -6
  48. package/src/tools/script-runs.ts +123 -0
  49. package/src/tools/slack-read.ts +12 -3
  50. package/src/tools/tool-config.ts +4 -1
  51. package/src/types.ts +52 -0
  52. package/src/utils/error-tracker.ts +40 -1
  53. package/src/utils/internal-ai/complete-structured.ts +10 -4
  54. package/src/workflows/executors/raw-llm.ts +76 -59
  55. package/templates/skills/pages/content.md +205 -55
  56. package/templates/skills/script-workflows/config.json +14 -0
  57. package/templates/skills/script-workflows/content.md +68 -0
  58. package/templates/skills/swarm-scripts/content.md +2 -3
@@ -0,0 +1,92 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ taskId: z.string().describe("Task ID to fetch details for"),
5
+ queries: z
6
+ .array(z.string())
7
+ .min(1)
8
+ .describe("Search queries from the task description — 2-4 recommended"),
9
+ scope: z
10
+ .enum(["all", "agent", "swarm"])
11
+ .optional()
12
+ .describe("Memory scope filter (default all)"),
13
+ memoryLimit: z
14
+ .number()
15
+ .int()
16
+ .positive()
17
+ .optional()
18
+ .describe("Max memories per query before dedup (default 8)"),
19
+ });
20
+
21
+ function slimTask(task: any) {
22
+ if (!task || typeof task !== "object") return null;
23
+ return {
24
+ id: task.id,
25
+ status: task.status,
26
+ description: task.task,
27
+ dependsOn: task.dependsOn,
28
+ slackChannelId: task.slackChannelId,
29
+ slackThreadTs: task.slackThreadTs,
30
+ createdAt: task.createdAt,
31
+ finishedAt: task.finishedAt,
32
+ agentId: task.agentId,
33
+ output: task.output,
34
+ failureReason: task.failureReason,
35
+ };
36
+ }
37
+
38
+ /** Fetch slim task details plus deduped multi-query memories for task onboarding. */
39
+ export default async function taskContextGathering(args: any, ctx: any) {
40
+ const parsed = argsSchema.safeParse(args);
41
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
42
+ const { taskId, queries, scope = "all", memoryLimit = 8 } = parsed.data;
43
+
44
+ const taskRes: any = await ctx.swarm.task_get({ taskId });
45
+ const taskPayload = taskRes?.data ?? taskRes;
46
+ if (taskPayload?.success === false) {
47
+ return { error: taskPayload.message ?? "task_get failed", taskId };
48
+ }
49
+
50
+ const allResults: any[] = [];
51
+ for (const query of queries) {
52
+ const res: any = await ctx.swarm.memory_search({ query, scope, limit: memoryLimit });
53
+ const payload = res?.data ?? res;
54
+ const results = payload?.results ?? [];
55
+ for (const memory of results) {
56
+ allResults.push({ ...memory, querySource: query });
57
+ }
58
+ }
59
+
60
+ const byId = new Map<string, any>();
61
+ const hitCounts = new Map<string, number>();
62
+ for (const memory of allResults) {
63
+ const id = typeof memory.id === "string" ? memory.id : JSON.stringify(memory);
64
+ hitCounts.set(id, (hitCounts.get(id) ?? 0) + 1);
65
+ const existing = byId.get(id);
66
+ const similarity = typeof memory.similarity === "number" ? memory.similarity : 0;
67
+ const existingSimilarity =
68
+ existing && typeof existing.similarity === "number" ? existing.similarity : -Infinity;
69
+ if (!existing || similarity > existingSimilarity) byId.set(id, memory);
70
+ }
71
+
72
+ const memories = Array.from(byId.entries()).map(([id, memory]) => {
73
+ const hits = hitCounts.get(id) ?? 1;
74
+ const similarity = typeof memory.similarity === "number" ? memory.similarity : 0;
75
+ return {
76
+ ...memory,
77
+ hits,
78
+ compositeScore: similarity + 0.05 * hits,
79
+ };
80
+ });
81
+ memories.sort((a: any, b: any) => b.compositeScore - a.compositeScore);
82
+
83
+ return {
84
+ task: slimTask(taskPayload?.task),
85
+ requestedBy: taskPayload?.requestedBy,
86
+ attachments: taskPayload?.attachments ?? [],
87
+ queriesRun: queries.length,
88
+ totalCandidates: allResults.length,
89
+ uniqueMemories: memories.length,
90
+ memories: memories.slice(0, memoryLimit * 2),
91
+ };
92
+ }
@@ -25,7 +25,7 @@ export default async function toolUsage(args: any, ctx: any) {
25
25
  const agentId = parsed.data.agentId;
26
26
 
27
27
  const agentFilter = agentId ? `AND agent_id = '${agentId}'` : "";
28
- const query = `
28
+ const sql = `
29
29
  SELECT tool_name, count(*) as calls
30
30
  FROM session_logs
31
31
  WHERE tool_name IS NOT NULL
@@ -36,9 +36,12 @@ export default async function toolUsage(args: any, ctx: any) {
36
36
  LIMIT ${limit}
37
37
  `;
38
38
 
39
- const res: any = await ctx.swarm.db_query({ query });
39
+ const res: any = await ctx.swarm.db_query({ sql });
40
40
  const payload = res?.data ?? res;
41
- const rows: any[] = payload?.rows ?? [];
41
+ const columns: string[] = payload?.columns ?? [];
42
+ const rows: any[] = (payload?.rows ?? []).map((row: any[]) =>
43
+ Object.fromEntries(columns.map((column, index) => [column, row[index]])),
44
+ );
42
45
 
43
46
  const totalCalls = rows.reduce((sum: number, r: any) => sum + (r.calls ?? 0), 0);
44
47
 
@@ -30,9 +30,11 @@ import groupCountSrc from "./catalog/group-count.ts" with { type: "text" };
30
30
  import jsonQuerySrc from "./catalog/json-query.ts" with { type: "text" };
31
31
  import linearIssueSrc from "./catalog/linear-issue.ts" with { type: "text" };
32
32
  import memoryDedupCheckSrc from "./catalog/memory-dedup-check.ts" with { type: "text" };
33
+ import opsCatalogAuditSrc from "./catalog/ops-catalog-audit.ts" with { type: "text" };
33
34
  import scheduleHealthSrc from "./catalog/schedule-health.ts" with { type: "text" };
34
35
  import slackThreadFlattenSrc from "./catalog/slack-thread-flatten.ts" with { type: "text" };
35
36
  import smartRecallSrc from "./catalog/smart-recall.ts" with { type: "text" };
37
+ import taskContextGatheringSrc from "./catalog/task-context-gathering.ts" with { type: "text" };
36
38
  import taskFailureAuditSrc from "./catalog/task-failure-audit.ts" with { type: "text" };
37
39
  import textDiffSrc from "./catalog/text-diff.ts" with { type: "text" };
38
40
  import toolUsageSrc from "./catalog/tool-usage.ts" with { type: "text" };
@@ -134,6 +136,14 @@ export const SEED_SCRIPTS: SeedScript[] = [
134
136
  "Recall relevant memories using multiple search angles — better coverage than a single query. Use for task onboarding, context gathering, or before writing new memories.",
135
137
  source: asText(smartRecallSrc),
136
138
  },
139
+ {
140
+ name: "task-context-gathering",
141
+ description:
142
+ "Get task details and recall relevant memories in one call — returns a slimmed task projection plus deduped and reranked memories from multi-query fan-out.",
143
+ intent:
144
+ "Task onboarding: one call instead of task_get plus multiple memory_search calls. Pass the task description split into 2-4 natural-language queries.",
145
+ source: asText(taskContextGatheringSrc),
146
+ },
137
147
  {
138
148
  name: "schedule-health",
139
149
  description:
@@ -153,11 +163,19 @@ export const SEED_SCRIPTS: SeedScript[] = [
153
163
  {
154
164
  name: "compound-insights",
155
165
  description:
156
- "All-in-one swarm-wide daily ops snapshot: task completion/failure summary, real failure clusters (excludes superseded/cancelled bookkeeping), schedule health flags, tool usage top-25, memory health stats, and a per-agent breakdown. Aggregates across ALL agents via direct read-only SQL.",
166
+ "All-in-one swarm-wide daily ops snapshot: task completion/failure summary, real failure clusters (excludes superseded/cancelled bookkeeping), schedule health flags, tool usage top-25, memory health/pollution stats, seed-script candidate tool triplets, and a per-agent breakdown. Aggregates across ALL agents via direct read-only SQL.",
157
167
  intent:
158
- "Single-call daily compounding Phase 0 helper — replaces ~25 raw tool roundtrips with one compressed JSON result covering every agent. For daily evolution, ops reviews, or heartbeat context.",
168
+ "Single-call daily compounding Phase 0 helper — replaces ~25 raw tool roundtrips with one compressed JSON result covering every agent. For daily evolution, self-scripting candidates, ops reviews, or heartbeat context.",
159
169
  source: asText(compoundInsightsSrc),
160
170
  },
171
+ {
172
+ name: "ops-catalog-audit",
173
+ description:
174
+ "Audit-as-code catalog check for schedules, workflows, and prompt/template drift. Clusters actionable findings by goal and can publish an authed HTML report page.",
175
+ intent:
176
+ "Re-run the ops inventory audit in one call: duplicate/dead schedules, code-work routing risks, enabled workflow fixtures, structured-output gate gaps, prompt registry drift, stale hosts, and systemDefault skill duplicates.",
177
+ source: asText(opsCatalogAuditSrc),
178
+ },
161
179
  ];
162
180
 
163
181
  /** A catalog entry resolved into a generic {@link SeedItem}. */
@@ -31,6 +31,12 @@ import scheduledTaskResilienceConfig from "../../../templates/skills/scheduled-t
31
31
  import scheduledTaskResilienceContent from "../../../templates/skills/scheduled-task-resilience/content.md" with {
32
32
  type: "text",
33
33
  };
34
+ import scriptWorkflowsConfig from "../../../templates/skills/script-workflows/config.json" with {
35
+ type: "text",
36
+ };
37
+ import scriptWorkflowsContent from "../../../templates/skills/script-workflows/content.md" with {
38
+ type: "text",
39
+ };
34
40
  import swarmScriptsConfig from "../../../templates/skills/swarm-scripts/config.json" with {
35
41
  type: "text",
36
42
  };
@@ -72,6 +78,7 @@ const BUILT_IN_SKILL_SOURCES = [
72
78
  { config: kvStorageConfig, body: kvStorageContent },
73
79
  { config: pagesConfig, body: pagesContent },
74
80
  { config: scheduledTaskResilienceConfig, body: scheduledTaskResilienceContent },
81
+ { config: scriptWorkflowsConfig, body: scriptWorkflowsContent },
75
82
  { config: swarmScriptsConfig, body: swarmScriptsContent },
76
83
  { config: workflowIterateConfig, body: workflowIterateContent },
77
84
  { config: workflowStructuredOutputConfig, body: workflowStructuredOutputContent },
@@ -41,6 +41,23 @@ const VALIDATED_KEYS: Record<string, (value: unknown) => string | null> = {
41
41
  if (parsed.success) return null;
42
42
  return `Invalid HARNESS_PROVIDER value (must be one of: ${ProviderNameSchema.options.join(", ")})`;
43
43
  },
44
+ // Codex credits-exhausted cooldown (ms). Permissive on range here (positive
45
+ // integer) — the worker clamps to [5m, 7d] via resolveCodexCreditsExhaustedCooldownMs.
46
+ CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS: (value) => {
47
+ const str = String(value).trim();
48
+ if (!/^\d+$/.test(str) || Number(str) <= 0) {
49
+ return "Invalid CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS (must be a positive integer of milliseconds)";
50
+ }
51
+ return null;
52
+ },
53
+ SWARM_USE_CLAUDE_BRIDGE: (value) => {
54
+ if (typeof value !== "string") {
55
+ return "Invalid SWARM_USE_CLAUDE_BRIDGE value (must be one of: true, false, 1, 0)";
56
+ }
57
+ const normalized = value.trim().toLowerCase();
58
+ if (["true", "false", "1", "0"].includes(normalized)) return null;
59
+ return "Invalid SWARM_USE_CLAUDE_BRIDGE value (must be one of: true, false, 1, 0)";
60
+ },
44
61
  };
45
62
 
46
63
  export function validateConfigValue(key: string, value: unknown): string | null {
@@ -40,9 +40,11 @@ import { getMcpBaseUrl } from "../utils/constants.ts";
40
40
  import { getContextWindowSize } from "../utils/context-window.ts";
41
41
  import { type CredentialSelection, resolveCredentialPools } from "../utils/credentials.ts";
42
42
  import {
43
+ isCodexCreditsExhaustedMessage,
43
44
  isRateLimitMessage,
44
45
  MAX_RATE_LIMIT_RESET_MS,
45
46
  parseRateLimitResetTime,
47
+ resolveCodexCreditsExhaustedCooldownMs,
46
48
  } from "../utils/error-tracker.ts";
47
49
  import { resolveHarnessProvider } from "../utils/harness-provider.ts";
48
50
  import { prettyPrintLine, prettyPrintStderr } from "../utils/pretty-print.ts";
@@ -424,6 +426,7 @@ async function fetchResolvedEnv(
424
426
  const RELOADABLE_ENV_KEYS: ReadonlySet<string> = new Set([
425
427
  "MODEL_OVERRIDE",
426
428
  "AGENT_FS_SHARED_ORG_ID",
429
+ "SWARM_USE_CLAUDE_BRIDGE",
427
430
  ]);
428
431
 
429
432
  /**
@@ -1483,6 +1486,13 @@ interface RunnerState {
1483
1486
  * (per-task live re-resolution) will mutate this between tasks.
1484
1487
  */
1485
1488
  harnessProvider: ProviderName;
1489
+ /**
1490
+ * Effective Codex credits-exhausted cooldown (ms), resolved from `swarm_config`
1491
+ * (key `CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS`) > default 2h constant, clamped to
1492
+ * [5m, 7d]. Reconciled live by `applySwarmConfigDrift` — read at the cooldown
1493
+ * application site so a fresh value applies to the next credits-exhausted failure.
1494
+ */
1495
+ codexCreditsExhaustedCooldownMs: number;
1486
1496
  }
1487
1497
 
1488
1498
  /** Buffer for session logs */
@@ -3209,6 +3219,12 @@ async function checkCompletedProcesses(
3209
3219
  console.log(
3210
3220
  `[credentials] Parsed rate limit reset time from error: ${rateLimitedUntil}`,
3211
3221
  );
3222
+ } else if (isCodexCreditsExhaustedMessage(failureReason)) {
3223
+ const cooldownMs = state.codexCreditsExhaustedCooldownMs;
3224
+ rateLimitedUntil = new Date(Date.now() + cooldownMs).toISOString();
3225
+ console.log(
3226
+ `[credentials] Codex credits exhausted — applying cooldown (${cooldownMs}ms): ${rateLimitedUntil}`,
3227
+ );
3212
3228
  } else {
3213
3229
  rateLimitedUntil = new Date(Date.now() + 5 * 60 * 1000).toISOString();
3214
3230
  }
@@ -3403,10 +3419,22 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
3403
3419
  // Failures (network, API down, malformed value) fall back to env then "claude"
3404
3420
  // so a swarm_config outage cannot wedge boot.
3405
3421
  let bootProvider: ProviderName;
3422
+ // Codex credits-exhausted cooldown is sourced solely from the global swarm_config
3423
+ // (key `CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS`). Initialize to the default constant
3424
+ // for the case where it is unset, then apply the swarm_config value below; on a
3425
+ // boot-fetch failure it stays at the default. Reconciled live thereafter by
3426
+ // `applySwarmConfigDrift`.
3427
+ let bootCooldownMs = resolveCodexCreditsExhaustedCooldownMs(undefined);
3406
3428
  try {
3407
- bootProvider = (await fetchResolvedEnv(apiUrl, apiKey, agentId)).resolvedProvider;
3429
+ const bootEnv = await fetchResolvedEnv(apiUrl, apiKey, agentId);
3430
+ bootProvider = bootEnv.resolvedProvider;
3431
+ bootCooldownMs = resolveCodexCreditsExhaustedCooldownMs(
3432
+ bootEnv.env.CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS,
3433
+ );
3408
3434
  } catch (err) {
3409
- console.warn(`[runner] fetchResolvedEnv failed at boot, falling back to env: ${err}`);
3435
+ console.warn(
3436
+ `[runner] fetchResolvedEnv failed at boot, falling back to env for provider and the default cooldown: ${err}`,
3437
+ );
3410
3438
  bootProvider = resolveHarnessProvider({}, process.env);
3411
3439
  }
3412
3440
  console.log(`[runner] Resolved HARNESS_PROVIDER: ${bootProvider}`);
@@ -3610,6 +3638,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
3610
3638
  activeTasks: new Map(),
3611
3639
  maxConcurrent,
3612
3640
  harnessProvider: bootProvider,
3641
+ codexCreditsExhaustedCooldownMs: bootCooldownMs,
3613
3642
  };
3614
3643
 
3615
3644
  // Track tasks already signaled for cancellation to avoid repeated SIGTERM
@@ -3690,6 +3719,18 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
3690
3719
  agentVisibleChanged = true;
3691
3720
  }
3692
3721
 
3722
+ // (2b) Codex credits-exhausted cooldown — operator-tunable live. Not
3723
+ // agent-visible (doesn't change provider/maxConcurrent → no re-register).
3724
+ const nextCooldown = resolveCodexCreditsExhaustedCooldownMs(
3725
+ freshEnv.CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS,
3726
+ );
3727
+ if (nextCooldown !== state.codexCreditsExhaustedCooldownMs) {
3728
+ console.log(
3729
+ `[${role}] [config] codexCreditsExhaustedCooldownMs: ${state.codexCreditsExhaustedCooldownMs} → ${nextCooldown}`,
3730
+ );
3731
+ state.codexCreditsExhaustedCooldownMs = nextCooldown;
3732
+ }
3733
+
3693
3734
  // (3) Apply the small allowlist of safe-to-mutate env keys to process.env.
3694
3735
  const changedKeys = applyResolvedEnvToProcessEnv(freshEnv);
3695
3736
  if (changedKeys.length > 0) {
@@ -11,6 +11,24 @@ export interface DbQueryResult {
11
11
  total: number;
12
12
  }
13
13
 
14
+ export const DbQueryInputShape = {
15
+ sql: z.string().min(1).max(10_000).optional(),
16
+ query: z.string().min(1).max(10_000).optional().describe("Deprecated runtime alias for sql."),
17
+ params: z.array(z.any()).optional().default([]),
18
+ };
19
+
20
+ export const DbQueryInputSchema = z
21
+ .object(DbQueryInputShape)
22
+ .refine((body) => body.sql !== undefined || body.query !== undefined, {
23
+ message: "Either sql or query is required",
24
+ });
25
+
26
+ export type DbQueryInput = z.infer<typeof DbQueryInputSchema>;
27
+
28
+ export function resolveDbQuerySql(input: Pick<DbQueryInput, "sql" | "query">): string {
29
+ return input.sql ?? input.query ?? "";
30
+ }
31
+
14
32
  function stripTrailingSemicolon(sql: string): string {
15
33
  return sql.trim().replace(/;\s*$/, "").trim();
16
34
  }
@@ -67,10 +85,7 @@ const dbQueryRoute = route({
67
85
  pattern: ["api", "db-query"],
68
86
  summary: "Execute a read-only SQL query",
69
87
  tags: ["Debug"],
70
- body: z.object({
71
- sql: z.string().min(1).max(10_000),
72
- params: z.array(z.any()).optional().default([]),
73
- }),
88
+ body: DbQueryInputSchema,
74
89
  responses: {
75
90
  200: {
76
91
  description: "Query results",
@@ -100,7 +115,7 @@ export async function handleDbQuery(
100
115
  if (!parsed) return true;
101
116
 
102
117
  try {
103
- const result = executeReadOnlyQuery(parsed.body.sql, parsed.body.params);
118
+ const result = executeReadOnlyQuery(resolveDbQuerySql(parsed.body), parsed.body.params);
104
119
  json(res, result);
105
120
  } catch (err: unknown) {
106
121
  const message = err instanceof Error ? err.message : String(err);
package/src/http/index.ts CHANGED
@@ -21,9 +21,11 @@ import {
21
21
  withRemoteContext,
22
22
  withSpanContext,
23
23
  } from "../otel";
24
+ import { startScriptRunSupervisor, stopScriptRunSupervisor } from "../script-workflows/supervisor";
24
25
  import { startSlackApp, stopSlackApp } from "../slack";
25
26
  import { initTelemetry, telemetry } from "../telemetry";
26
27
  import { getApiKey } from "../utils/api-key";
28
+ import { getMcpBaseUrl } from "../utils/constants";
27
29
  import { scrubSecrets } from "../utils/secret-scrubber";
28
30
  import { initWorkflows } from "../workflows";
29
31
  import { handleActiveSessions } from "./active-sessions";
@@ -57,6 +59,7 @@ import { handlePromptTemplates } from "./prompt-templates";
57
59
  import { handleRepos } from "./repos";
58
60
  import { describeRequestRoute } from "./route-def";
59
61
  import { handleSchedules } from "./schedules";
62
+ import { handleScriptRuns } from "./script-runs";
60
63
  import { handleScripts } from "./scripts";
61
64
  import { handleSessionData } from "./session-data";
62
65
  import { handleSessions } from "./sessions";
@@ -272,6 +275,7 @@ const httpServer = createHttpServer(async (req, res) => {
272
275
  () => handleMetrics(req, res, pathSegments, queryParams, myAgentId),
273
276
  () => handleRepos(req, res, pathSegments, queryParams),
274
277
  () => handleSkills(req, res, pathSegments, queryParams, myAgentId),
278
+ () => handleScriptRuns(req, res, pathSegments, queryParams, myAgentId),
275
279
  () => handleScripts(req, res, pathSegments, queryParams, myAgentId),
276
280
  () => handleMcpBridge(req, res, pathSegments, queryParams, myAgentId),
277
281
  () => handleMcpServers(req, res, pathSegments, queryParams),
@@ -343,6 +347,9 @@ async function shutdown() {
343
347
  // Stop heartbeat triage
344
348
  stopHeartbeat();
345
349
 
350
+ // Stop durable script workflow subprocesses
351
+ stopScriptRunSupervisor();
352
+
346
353
  // Stop Slack bot
347
354
  await stopSlackApp();
348
355
 
@@ -487,6 +494,9 @@ httpServer
487
494
  // Initialize workflow engine (trigger subscriptions + resume listener)
488
495
  initWorkflows();
489
496
 
497
+ // Reconcile durable script workflow subprocesses
498
+ startScriptRunSupervisor(getMcpBaseUrl());
499
+
490
500
  // Start scheduler (if enabled)
491
501
  if (hasCapability("scheduling")) {
492
502
  const { startScheduler } = await import("../scheduler");