@desplega.ai/agent-swarm 1.148.0 → 1.149.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/dist/{acp-adapter-grez7xhd.js → acp-adapter-4jncb126.js} +12 -5
- package/dist/{actions-kweznh5a.js → actions-31vp3ae7.js} +10 -9
- package/dist/{app-5fvvjv9m.js → app-1hh67x8w.js} +6 -6
- package/dist/{assistant-40m4w50s.js → assistant-z66dmz6p.js} +13 -11
- package/dist/{boot-reembed-cn357y5n.js → boot-reembed-fqzxxzjh.js} +7 -7
- package/dist/{boot-reembed-9rxmb26n.js → boot-reembed-wxa8ya0s.js} +8 -8
- package/dist/{boot-scrub-logs-5tpp0x3y.js → boot-scrub-logs-jnyndh79.js} +5 -5
- package/dist/{claude-adapter-9pz5q2pb.js → claude-adapter-3qpa9w0j.js} +9 -9
- package/dist/{claude-managed-adapter-sactwn31.js → claude-managed-adapter-qvsmh6mv.js} +2 -2
- package/dist/{claude-sdk-session-v8w0s7qm.js → claude-sdk-session-2yxgjs5d.js} +9 -9
- package/dist/{cli-y6b6fb76.js → cli-08b07b4r.js} +1 -1
- package/dist/{cli-8fc8de14.js → cli-0z2v4nhw.js} +1 -1
- package/dist/{cli-tebsjpk7.js → cli-19f354qr.js} +2 -2
- package/dist/{cli-kzvhgvk3.js → cli-2qts1hys.js} +3 -3
- package/dist/{cli-kpv03zhj.js → cli-3apbzgyk.js} +27 -15
- package/dist/{cli-5khqvvnq.js → cli-3q5ejx9p.js} +2 -2
- package/dist/{cli-d8brzxjb.js → cli-4wtq5jjv.js} +2 -2
- package/dist/{cli-ct0et58h.js → cli-5my3bjsd.js} +1 -1
- package/dist/{cli-cn6mmc7f.js → cli-6664w7y4.js} +1 -1
- package/dist/{cli-f8j56xv2.js → cli-6pqcb4q0.js} +53 -13
- package/dist/{cli-m1p5cysp.js → cli-7smrsr25.js} +3 -3
- package/dist/{cli-0amc6myn.js → cli-9fy6nk6g.js} +1 -1
- package/dist/{cli-dcem3pr1.js → cli-9hspd3dp.js} +2 -2
- package/dist/{cli-0er5m69z.js → cli-aae6hz6a.js} +4 -4
- package/dist/{cli-brxyat7h.js → cli-cr12paw2.js} +5 -5
- package/dist/{cli-dk3me3qs.js → cli-d8ftsp62.js} +8 -8
- package/dist/{cli-v3e51x7w.js → cli-e95cx1eb.js} +3 -3
- package/dist/{cli-ztn0ga15.js → cli-gjhjfeeg.js} +1 -1
- package/dist/{cli-xqcpg1st.js → cli-gv88e1vf.js} +1 -1
- package/dist/{cli-rvqz34y5.js → cli-jecc01cb.js} +1 -1
- package/dist/{cli-hzhhrt7b.js → cli-k0cr4kat.js} +1 -1
- package/dist/{cli-hz8xhefy.js → cli-kgz4np97.js} +4 -4
- package/dist/{cli-anedxf1w.js → cli-kjwt6hdf.js} +1 -1
- package/dist/{cli-3n7cbfjn.js → cli-mnmsfd1w.js} +6 -2
- package/dist/{cli-n9grk3yd.js → cli-n3dz2942.js} +37 -26
- package/dist/{cli-cdjezzc7.js → cli-nye12xk5.js} +8 -5
- package/dist/{cli-9216xm24.js → cli-phsxnkp6.js} +20 -20
- package/dist/{cli-7gn7c2af.js → cli-q50ef8g0.js} +1 -1
- package/dist/{cli-qy6h009w.js → cli-qaacms9y.js} +7 -6
- package/dist/{cli-xqcq9y5e.js → cli-r2ap2czm.js} +1 -0
- package/dist/{cli-hp2est46.js → cli-sd5pv3b1.js} +3 -3
- package/dist/{cli-0b6y7kxm.js → cli-xm87hzbw.js} +1 -1
- package/dist/{cli-6s2y152y.js → cli-yab68w40.js} +285 -11
- package/dist/{cli-p73drk6p.js → cli-yb9qhqam.js} +167 -40
- package/dist/{cli-56y3hch2.js → cli-ygweqcn4.js} +133 -22
- package/dist/{cli-n2cwscdd.js → cli-ykxrnd1q.js} +2 -2
- package/dist/{cli-m9sxbkhm.js → cli-zvz62chv.js} +3 -3
- package/dist/cli.js +14 -14
- package/dist/{codex-adapter-ehhj2qwc.js → codex-adapter-8meawydj.js} +4 -4
- package/dist/{codex-hook-bw8p5nh4.js → codex-hook-hfrt9shr.js} +2 -2
- package/dist/{codex-session-runner-5hne566k.js → codex-session-runner-gjgxyp5j.js} +4 -4
- package/dist/{commands-xnvh951h.js → commands-hz3nb95f.js} +5 -5
- package/dist/{db-ycr5zfr4.js → db-ggz97zbm.js} +5 -5
- package/dist/{e2b-6hb103d8.js → e2b-p04vmtx0.js} +1 -1
- package/dist/{handlers-1vt77kgq.js → handlers-nbzdzn9k.js} +14 -11
- package/dist/{hook-91ntpmff.js → hook-bks2q1f6.js} +7 -7
- package/dist/{hook-qfwjydvn.js → hook-kq9wp0ep.js} +8 -8
- package/dist/{http-h6g7bv5b.js → http-hrf77v87.js} +96 -45
- package/dist/{index-47ghesbn.js → index-6nsbg88z.js} +443 -144
- package/dist/{index-antf9t4p.js → index-8hmeg8j1.js} +14 -14
- package/dist/{index-d2mytx95.js → index-cqtebbhb.js} +13 -13
- package/dist/{index-0ez2aack.js → index-n28pw1zt.js} +15 -15
- package/dist/{keepalive-c4sp7yvd.js → keepalive-dxc12gvg.js} +7 -7
- package/dist/{lead-4r7z9e8v.js → lead-yckfn18d.js} +32 -32
- package/dist/{maintenance-yqjfdjas.js → maintenance-6kfb2m5e.js} +8 -8
- package/dist/{oauth-refresh-sweep-wtqgnh3c.js → oauth-refresh-sweep-1x1ac722.js} +6 -6
- package/dist/{onboard-a4krmh46.js → onboard-dcyt1609.js} +4 -3
- package/dist/{opencode-adapter-fef1mrn2.js → opencode-adapter-dce4wskd.js} +2 -2
- package/dist/{otel-impl-725kq0vp.js → otel-impl-tbk3e5dr.js} +2 -2
- package/dist/{pi-mono-adapter-2has4gnp.js → pi-mono-adapter-zvshk5cj.js} +2 -2
- package/dist/{pricing-refresh-kqxd7w9h.js → pricing-refresh-kaqg567e.js} +7 -7
- package/dist/{rbac-roles-gws5xej6.js → rbac-roles-ammq6rd2.js} +5 -5
- package/dist/{rbac-roles-t3wygpth.js → rbac-roles-cf97v1vd.js} +6 -6
- package/dist/{render-v2-x1wxvn51.js → render-v2-80w603vy.js} +6 -6
- package/dist/{seed-pricing-t6y8d0vf.js → seed-pricing-zbrn83xv.js} +6 -6
- package/dist/{setup-x50py6xg.js → setup-fe66kczs.js} +2 -2
- package/dist/{worker-zsz0qfxk.js → worker-qmb2d1e2.js} +32 -32
- package/dist/{x-n2phr5vm.js → x-n3e0v3xa.js} +2 -2
- package/openapi.json +46 -4
- package/package.json +1 -1
- package/src/agentmail/handlers.ts +5 -0
- package/src/automation-preflight-alert.ts +41 -0
- package/src/be/automation-preflight.ts +3 -3
- package/src/be/budget-refusal-notify.ts +1 -0
- package/src/be/db/tasks/read.ts +5 -0
- package/src/be/db.ts +132 -2
- package/src/be/migrations/150_deferred_task_waits.sql +18 -0
- package/src/be/migrations/151_repair_scheduled_task_required_params.sql +39 -0
- package/src/be/migrations/152_routing_decisions.sql +44 -0
- package/src/be/scripts/typecheck.ts +33 -22
- package/src/be/seed-scripts/catalog/delegate.ts +5 -1
- package/src/be/seed-skills/bundled-files.generated.json +10 -0
- package/src/be/seed-skills/index.ts +3 -0
- package/src/be/steering.ts +1 -0
- package/src/be/swarm-config-guard.ts +11 -0
- package/src/commands/onboard/steps/post-task.tsx +1 -0
- package/src/github/handlers.ts +9 -0
- package/src/gitlab/handlers.ts +4 -0
- package/src/heartbeat/heartbeat.ts +8 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +1 -0
- package/src/http/config.ts +15 -6
- package/src/http/mcp.ts +31 -4
- package/src/http/script-runs.ts +6 -0
- package/src/http/tasks.ts +52 -30
- package/src/integrations/kapso/inbound.ts +1 -0
- package/src/jira/sync.ts +2 -0
- package/src/linear/sync.ts +2 -0
- package/src/prompts/session-templates.ts +29 -15
- package/src/scheduler/deferred-task-waits.ts +123 -0
- package/src/scheduler/schedule-task.ts +42 -0
- package/src/scheduler/scheduler.ts +22 -31
- package/src/script-workflows/workflow-ctx.ts +2 -0
- package/src/scripts-runtime/types/stdlib.d.ts +38 -24
- package/src/scripts-runtime/types/swarm-sdk.d.ts +38 -24
- package/src/server.ts +10 -1
- package/src/slack/actions.ts +1 -0
- package/src/slack/assistant.ts +2 -0
- package/src/slack/handlers.ts +3 -0
- package/src/slack/thread-buffer.ts +2 -0
- package/src/tasks/worker-follow-up.ts +9 -0
- package/src/tests/acp-adapter.test.ts +35 -6
- package/src/tests/acp-session-token.test.ts +67 -0
- package/src/tests/asset-key-api.test.ts +15 -2
- package/src/tests/asset-key-mcp.test.ts +2 -0
- package/src/tests/automation-preflight-alert.test.ts +91 -0
- package/src/tests/claude-worker-parity.test.ts +3 -0
- package/src/tests/config-session-auth.test.ts +143 -0
- package/src/tests/defer-task.test.ts +117 -6
- package/src/tests/deferred-task-wake.test.ts +456 -0
- package/src/tests/heartbeat-reroute-decision.test.ts +1 -0
- package/src/tests/http-api-integration.test.ts +33 -4
- package/src/tests/model-control.test.ts +18 -5
- package/src/tests/opencode-adapter.test.ts +2 -2
- package/src/tests/promote-draft-task-route.test.ts +4 -0
- package/src/tests/prompt-template-session.test.ts +13 -4
- package/src/tests/rbac-wire-e2e.test.ts +2 -2
- package/src/tests/routing-decision-persistence.test.ts +520 -0
- package/src/tests/routing-reason-contract.test.ts +89 -0
- package/src/tests/routing-reason-inventory.test.ts +55 -0
- package/src/tests/schedule-target-type.test.ts +48 -1
- package/src/tests/scheduled-task-required-params-migration.test.ts +182 -0
- package/src/tests/scheduled-tasks.test.ts +9 -5
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-runs-http.test.ts +31 -0
- package/src/tests/scripts-typecheck.test.ts +11 -0
- package/src/tests/secret-scrubber.test.ts +12 -0
- package/src/tests/send-task-output-schema.test.ts +66 -1
- package/src/tests/send-task-requested-by.test.ts +1 -0
- package/src/tests/send-task-slack-routing-guard.test.ts +2 -0
- package/src/tests/store-progress-blocked-waiting-gate.test.ts +151 -0
- package/src/tests/swarm-tool-result-gate.test.ts +24 -0
- package/src/tests/task-tool-manifest.test.ts +47 -0
- package/src/tests/task-tool-preload-mcp.test.ts +125 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-output-agent-id.test.ts +1 -0
- package/src/tests/ui-followup-lead-delegation.test.ts +6 -1
- package/src/tests/workflow-agent-task.test.ts +24 -1
- package/src/tests/workflow-engine-v2.test.ts +58 -2
- package/src/tools/defer-task.ts +73 -21
- package/src/tools/send-task.ts +31 -0
- package/src/tools/skills/skill-publish.ts +1 -0
- package/src/tools/store-progress.ts +43 -0
- package/src/tools/templates.ts +2 -1
- package/src/tools/utils.ts +33 -2
- package/src/types.ts +14 -1
- package/src/utils/acp-session-token.ts +14 -4
- package/src/utils/secret-scrubber.ts +2 -0
- package/src/utils/task-tool-manifest.ts +33 -0
- package/src/workflows/engine.ts +4 -1
- package/src/workflows/executors/agent-task.ts +7 -1
- package/templates/ai-toolbox.manifest.json +6 -1
- package/templates/skills/comms/config.json +18 -0
- package/templates/skills/comms/content.md +74 -0
- package/templates/skills/comms/files/references/ste-rules.md +73 -0
- package/templates/skills/comms/files/references/visual-shapes.md +112 -0
- package/templates/skills/swarm-scripts/SKILL.md +3 -2
- package/templates/skills/swarm-scripts/content.md +3 -2
package/src/tools/defer-task.ts
CHANGED
|
@@ -4,12 +4,14 @@ import * as z from "zod";
|
|
|
4
4
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
5
5
|
import {
|
|
6
6
|
completeTask,
|
|
7
|
+
createLogEntry,
|
|
7
8
|
createScheduledTask,
|
|
8
9
|
getAgentById,
|
|
9
10
|
getDbClient,
|
|
10
11
|
getTaskById,
|
|
11
12
|
updateAgentStatusFromCapacity,
|
|
12
13
|
} from "@/be/db";
|
|
14
|
+
import { reconcileDeferredTaskWaits } from "@/scheduler/deferred-task-waits";
|
|
13
15
|
import { runTaskTerminalEffects } from "@/tasks/task-terminal-effects";
|
|
14
16
|
import { getTaskOutputValidationError } from "@/tasks/terminal-result-guard";
|
|
15
17
|
import { assertOwnsTask, ownerCtx } from "@/tools/task-tool-ctx";
|
|
@@ -32,7 +34,7 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
32
34
|
title: "Defer Task",
|
|
33
35
|
annotations: { destructiveHint: false, idempotentHint: false },
|
|
34
36
|
description:
|
|
35
|
-
"Completes this task now with status `completed` and books a wake-up for you. Use when the result needs time: a build, a deploy, a reply. The task reaches its final state on this call; the lead sees your summary as its output. A one-off schedule wakes you up later with a child task that carries this task as its parent. Provide delayMs or runAt, a summary of what you did, and a note that says what is pending and what to check.",
|
|
37
|
+
"Completes this task now with status `completed` and books a wake-up for you. Use when the result needs time: a build, a deploy, a reply. The task reaches its final state on this call; the lead sees your summary as its output unless the task has an outputSchema. For a task with an outputSchema, provide output as a JSON string matching that schema; it is stored verbatim as terminal output, while deferral details remain visible in the task log. A one-off schedule wakes you up later with a child task that carries this task as its parent. Optionally provide wakeOn to wake early when another task completes or fails; delayMs or runAt remains required as the ceiling. Provide delayMs or runAt, a summary of what you did, and a note that says what is pending and what to check.",
|
|
36
38
|
inputSchema: z.object({
|
|
37
39
|
taskId: z.string().describe("The ID of the task you are working on."),
|
|
38
40
|
delayMs: z
|
|
@@ -46,12 +48,28 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
46
48
|
.datetime()
|
|
47
49
|
.optional()
|
|
48
50
|
.describe("Wake up at this ISO datetime (e.g. '2026-03-06T15:00:00Z'). Must be future."),
|
|
51
|
+
wakeOn: z
|
|
52
|
+
.object({
|
|
53
|
+
event: z.enum(["task.completed", "task.failed", "settled"]),
|
|
54
|
+
taskId: z.string().min(1),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Wake early on this task event. settled covers completed or failed. Already-terminal tasks are rejected; a delayMs/runAt ceiling is still required.",
|
|
60
|
+
),
|
|
49
61
|
summary: z
|
|
50
62
|
.string()
|
|
51
63
|
.min(1)
|
|
52
64
|
.max(4000)
|
|
53
65
|
.describe(
|
|
54
|
-
"What you did so far and where things stand.
|
|
66
|
+
"What you did so far and where things stand. Stored in the task log for tasks with an outputSchema; otherwise becomes the task's output.",
|
|
67
|
+
),
|
|
68
|
+
output: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe(
|
|
72
|
+
"Required when the task has an outputSchema: a JSON string matching that schema, stored verbatim as terminal output. Ignored for tasks without an outputSchema.",
|
|
55
73
|
),
|
|
56
74
|
note: z
|
|
57
75
|
.string()
|
|
@@ -71,7 +89,11 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
71
89
|
nextRunAt: z.string().optional(),
|
|
72
90
|
}),
|
|
73
91
|
},
|
|
74
|
-
async (
|
|
92
|
+
async (
|
|
93
|
+
{ taskId, delayMs, runAt, wakeOn, summary, output, note, checks },
|
|
94
|
+
requestInfo,
|
|
95
|
+
_meta,
|
|
96
|
+
) => {
|
|
75
97
|
if (!requestInfo.agentId) {
|
|
76
98
|
return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
77
99
|
}
|
|
@@ -122,26 +144,37 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
122
144
|
}
|
|
123
145
|
const nextRunAt = delayMs ? new Date(Date.now() + delayMs).toISOString() : runAt!;
|
|
124
146
|
|
|
147
|
+
const wakeDescription = wakeOn
|
|
148
|
+
? `on ${wakeOn.event} for task ${wakeOn.taskId}, or by ${nextRunAt}`
|
|
149
|
+
: `at ${nextRunAt}`;
|
|
125
150
|
const checksBlock = renderChecks(checks);
|
|
126
151
|
const taskTemplate = `Resume task ${taskId}: ${note}${checksBlock}`;
|
|
127
152
|
const createdBy =
|
|
128
153
|
(await resolveTaskAuditUserId(requestInfo.sourceTaskId, requestInfo.agentId)) ?? undefined;
|
|
129
154
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return toolErr(
|
|
139
|
-
`Task ${taskId} has an outputSchema; its terminal output must satisfy it, but a deferral note cannot. ${outputValidationError}`,
|
|
140
|
-
);
|
|
155
|
+
// Validate before creating the schedule or changing the task.
|
|
156
|
+
if (task.outputSchema) {
|
|
157
|
+
if (!output) {
|
|
158
|
+
return toolErr(
|
|
159
|
+
`Task ${taskId} has an outputSchema. Call defer-task with output: a JSON string matching that schema. Summary and note are stored separately in the task log.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const outputValidationError = getTaskOutputValidationError(task.outputSchema, output);
|
|
163
|
+
if (outputValidationError) return toolErr(outputValidationError);
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
try {
|
|
144
167
|
const committed = await getDbClient().transaction(async () => {
|
|
168
|
+
if (wakeOn) {
|
|
169
|
+
if (wakeOn.taskId === taskId)
|
|
170
|
+
throw new DeferAbortedError("Cannot wake on the task being deferred.");
|
|
171
|
+
const watched = await getTaskById(wakeOn.taskId);
|
|
172
|
+
if (!watched) throw new DeferAbortedError(`Watched task ${wakeOn.taskId} not found.`);
|
|
173
|
+
if (isTerminalTaskStatus(watched.status))
|
|
174
|
+
throw new DeferAbortedError(
|
|
175
|
+
`Watched task ${wakeOn.taskId} is already ${watched.status}; read its result instead of deferring.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
145
178
|
const schedule = await createScheduledTask({
|
|
146
179
|
// Unique name (`getScheduledTaskByName` is a unique lookup). The UUID
|
|
147
180
|
// prevents concurrent deferrals of the same task from colliding.
|
|
@@ -165,12 +198,22 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
165
198
|
createdBy,
|
|
166
199
|
});
|
|
167
200
|
|
|
168
|
-
|
|
201
|
+
if (wakeOn) {
|
|
202
|
+
await getDbClient().run(
|
|
203
|
+
"INSERT INTO deferred_task_waits (scheduleId, taskId, eventName, created_by, updated_by) VALUES (?, ?, ?, ?, ?)",
|
|
204
|
+
[schedule.id, wakeOn.taskId, wakeOn.event, createdBy ?? null, createdBy ?? null],
|
|
205
|
+
);
|
|
206
|
+
getDbClient().afterCommit(() => {
|
|
207
|
+
void reconcileDeferredTaskWaits(wakeOn.taskId).catch((err) => {
|
|
208
|
+
console.error("[defer-task] Event wake reconciliation failed:", err);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const deferralDetails = `${summary}\n\nDeferred until ${nextRunAt} (schedule ${schedule.id}). Pending: ${note}${checksBlock}`;
|
|
169
214
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// redundant since the id never affects JSON-shape validity.
|
|
173
|
-
const completed = await completeTask(taskId, output);
|
|
215
|
+
const terminalOutput = task.outputSchema ? output! : deferralDetails;
|
|
216
|
+
const completed = await completeTask(taskId, terminalOutput);
|
|
174
217
|
if (!completed) {
|
|
175
218
|
// Another writer terminally completed/failed/cancelled this task
|
|
176
219
|
// between our early check and this transaction's write. Abort:
|
|
@@ -182,6 +225,15 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
182
225
|
);
|
|
183
226
|
}
|
|
184
227
|
|
|
228
|
+
if (task.outputSchema) {
|
|
229
|
+
await createLogEntry({
|
|
230
|
+
eventType: "task_progress",
|
|
231
|
+
taskId,
|
|
232
|
+
agentId: requestInfo.agentId,
|
|
233
|
+
newValue: deferralDetails,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
185
237
|
// afterCommit: the transaction can still roll back; business-use must
|
|
186
238
|
// not be told the task completed for a write that never landed.
|
|
187
239
|
getDbClient().afterCommit(() => {
|
|
@@ -207,7 +259,7 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
207
259
|
await updateAgentStatusFromCapacity(task.agentId);
|
|
208
260
|
}
|
|
209
261
|
|
|
210
|
-
return { scheduleId: schedule.id, output, completed };
|
|
262
|
+
return { scheduleId: schedule.id, output: terminalOutput, completed };
|
|
211
263
|
});
|
|
212
264
|
|
|
213
265
|
await runTaskTerminalEffects({
|
|
@@ -218,7 +270,7 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
218
270
|
});
|
|
219
271
|
|
|
220
272
|
return toolOk(
|
|
221
|
-
`Task ${taskId} completed and deferred. Wake-up
|
|
273
|
+
`Task ${taskId} completed and deferred. Wake-up ${wakeDescription} (schedule ${committed.scheduleId}). This task is final; the wake-up task continues the work.`,
|
|
222
274
|
{
|
|
223
275
|
data: {
|
|
224
276
|
yourAgentId: requestInfo.agentId,
|
package/src/tools/send-task.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
FollowUpConfigSchema,
|
|
32
32
|
ModelTierSchema,
|
|
33
33
|
ReasoningEffortSchema,
|
|
34
|
+
RoutingReasonSchema,
|
|
34
35
|
splitLegacyModelAlias,
|
|
35
36
|
} from "@/types";
|
|
36
37
|
import { findJsonSchemaShapeErrors } from "@/workflows/json-schema-validator";
|
|
@@ -61,6 +62,14 @@ export const sendTaskInputSchema = z
|
|
|
61
62
|
.string()
|
|
62
63
|
.optional()
|
|
63
64
|
.describe("The agent to assign/offer task to. Omit to create unassigned task for pool."),
|
|
65
|
+
routingReason: RoutingReasonSchema.optional().describe(
|
|
66
|
+
"Why this agent was selected. Required when agentId is supplied; omit for pool routing.",
|
|
67
|
+
),
|
|
68
|
+
routingNote: z
|
|
69
|
+
.string()
|
|
70
|
+
.max(200)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Optional routing context (maximum 200 characters)."),
|
|
64
73
|
task: z.string().min(1).describe("The task description to send."),
|
|
65
74
|
key: AssetKeySchema.optional().describe(
|
|
66
75
|
"Logical namespace key. Child tasks inherit their parent namespace when provided.",
|
|
@@ -174,6 +183,13 @@ export const sendTaskInputSchema = z
|
|
|
174
183
|
path: [hasChannel ? "slackThreadTs" : "slackChannelId"],
|
|
175
184
|
});
|
|
176
185
|
}
|
|
186
|
+
if (data.agentId !== undefined && data.routingReason === undefined) {
|
|
187
|
+
ctx.addIssue({
|
|
188
|
+
code: z.ZodIssueCode.custom,
|
|
189
|
+
message: "routingReason is required when agentId is supplied.",
|
|
190
|
+
path: ["routingReason"],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
177
193
|
checkOutputSchemaShape(data.outputSchema, ctx);
|
|
178
194
|
});
|
|
179
195
|
|
|
@@ -222,6 +238,8 @@ export async function sendTaskHandler(
|
|
|
222
238
|
ctx: ToolCtx,
|
|
223
239
|
{
|
|
224
240
|
agentId,
|
|
241
|
+
routingReason,
|
|
242
|
+
routingNote,
|
|
225
243
|
task,
|
|
226
244
|
key,
|
|
227
245
|
offerMode,
|
|
@@ -247,6 +265,10 @@ export async function sendTaskHandler(
|
|
|
247
265
|
outputSchema,
|
|
248
266
|
}: SendTaskArgs,
|
|
249
267
|
): Promise<SwarmToolResult> {
|
|
268
|
+
// Defense in depth for direct TypeScript callers that bypass MCP schema parsing.
|
|
269
|
+
if (agentId !== undefined && routingReason === undefined) {
|
|
270
|
+
return toolErr("routingReason is required when agentId is supplied.");
|
|
271
|
+
}
|
|
250
272
|
if (ctx.kind === "owner" && !ctx.agentId) {
|
|
251
273
|
return toolErr('Agent ID not found. The MCP client should define the "X-Agent-ID" header.', {
|
|
252
274
|
data: { yourAgentId: ctx.agentId },
|
|
@@ -339,6 +361,9 @@ export async function sendTaskHandler(
|
|
|
339
361
|
effectiveAgentId = effectiveParentTask.agentId;
|
|
340
362
|
}
|
|
341
363
|
}
|
|
364
|
+
const effectiveRoutingReason =
|
|
365
|
+
agentId !== undefined ? routingReason : effectiveAgentId ? "continuity" : undefined;
|
|
366
|
+
const effectiveRoutingNote = effectiveRoutingReason ? routingNote : undefined;
|
|
342
367
|
|
|
343
368
|
// The three dedup guards are pure reads, so they run twice: once here as a
|
|
344
369
|
// fast path (keeping this tool's existing early-exit responses), and once
|
|
@@ -470,6 +495,8 @@ export async function sendTaskHandler(
|
|
|
470
495
|
overrideSlackContext,
|
|
471
496
|
followUpConfig,
|
|
472
497
|
outputSchema,
|
|
498
|
+
routingReason: effectiveRoutingReason,
|
|
499
|
+
routingNote: effectiveRoutingNote,
|
|
473
500
|
routingAffinity:
|
|
474
501
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
475
502
|
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -537,6 +564,8 @@ export async function sendTaskHandler(
|
|
|
537
564
|
overrideSlackContext,
|
|
538
565
|
followUpConfig,
|
|
539
566
|
outputSchema,
|
|
567
|
+
routingReason: effectiveRoutingReason,
|
|
568
|
+
routingNote: effectiveRoutingNote,
|
|
540
569
|
routingAffinity:
|
|
541
570
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
542
571
|
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -578,6 +607,8 @@ export async function sendTaskHandler(
|
|
|
578
607
|
overrideSlackContext,
|
|
579
608
|
followUpConfig,
|
|
580
609
|
outputSchema,
|
|
610
|
+
routingReason: effectiveRoutingReason,
|
|
611
|
+
routingNote: effectiveRoutingNote,
|
|
581
612
|
routingAffinity:
|
|
582
613
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
583
614
|
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -29,6 +29,17 @@ import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
|
29
29
|
// echoed the schema example, producing noise rows keyed `mcp-<taskId>-<ts>`
|
|
30
30
|
// that double-counted alongside the harness's authoritative entry.
|
|
31
31
|
|
|
32
|
+
// Deliberately narrow and phrase-based (not a bare "wait"/"block" substring
|
|
33
|
+
// match) to under-fire rather than over-fire: measured against 196 real
|
|
34
|
+
// progress rows across all statuses, this matched 0 — see PR body for the
|
|
35
|
+
// full false-positive measurement methodology.
|
|
36
|
+
const BLOCKED_WAITING_PATTERN =
|
|
37
|
+
/\b(waiting (for|on)|blocked (on|until|by)|still waiting|awaiting)\b/i;
|
|
38
|
+
|
|
39
|
+
// Below this, two check-ins are close enough together that "blocked" reads as
|
|
40
|
+
// noise and calling defer-task buys nothing over checking in again shortly.
|
|
41
|
+
const BLOCKED_WAITING_MIN_ELAPSED_MS = 3 * 60 * 1000;
|
|
42
|
+
|
|
32
43
|
export const storeProgressOutputSchema = swarmToolOutputSchema({
|
|
33
44
|
// Bounded confirmation only. The handler keeps the full task row internally
|
|
34
45
|
// for completion memory, raters, and follow-up creation, but never echoes it
|
|
@@ -56,6 +67,12 @@ export const storeProgressOutputSchema = swarmToolOutputSchema({
|
|
|
56
67
|
.describe(
|
|
57
68
|
"True when force: true replaced output and/or failureReason on an already-terminal task without replaying completion side effects.",
|
|
58
69
|
),
|
|
70
|
+
blockedWaitingElapsedMs: z
|
|
71
|
+
.number()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe(
|
|
74
|
+
"Present only when this progress text reads as blocked-waiting: milliseconds since the task's prior update. Drives the store-progress nudge toward defer-task.",
|
|
75
|
+
),
|
|
59
76
|
});
|
|
60
77
|
|
|
61
78
|
export const registerStoreProgressTool = (server: McpServer) => {
|
|
@@ -251,6 +268,27 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
251
268
|
|
|
252
269
|
let updatedTask = existingTask;
|
|
253
270
|
const isTerminal = isTerminalTaskStatus(existingTask.status);
|
|
271
|
+
// This call's own status can finish the task even though existingTask
|
|
272
|
+
// (its state before this call) is not yet terminal — gate on both so a
|
|
273
|
+
// completing call carrying blocked-waiting-shaped text (e.g. "awaiting
|
|
274
|
+
// review") never nudges toward defer-task.
|
|
275
|
+
const goingTerminal = status !== undefined && isTerminalTaskStatus(status);
|
|
276
|
+
|
|
277
|
+
// Computed against the task's state as of BEFORE this call's update,
|
|
278
|
+
// so "elapsed" reads as time since the prior check-in, not zero.
|
|
279
|
+
let blockedWaitingElapsedMs: number | undefined;
|
|
280
|
+
if (progress && !isTerminal && !goingTerminal && BLOCKED_WAITING_PATTERN.test(progress)) {
|
|
281
|
+
const referenceIso = existingTask.lastUpdatedAt ?? existingTask.createdAt;
|
|
282
|
+
if (referenceIso) {
|
|
283
|
+
const elapsed = Date.now() - new Date(referenceIso).getTime();
|
|
284
|
+
// Below the floor, a sub-minute check-in reads as "blocked" purely
|
|
285
|
+
// from noise, and defer-task buys nothing over just checking in
|
|
286
|
+
// again shortly — so treat it as not blocked-waiting yet.
|
|
287
|
+
if (elapsed >= BLOCKED_WAITING_MIN_ELAPSED_MS) {
|
|
288
|
+
blockedWaitingElapsedMs = elapsed;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
254
292
|
|
|
255
293
|
// Attachments — pointer-based, append-only. Insert each row inside
|
|
256
294
|
// this transaction; the helper dedups by sha256 (when present) or by
|
|
@@ -422,6 +460,7 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
422
460
|
? `Task "${taskId}" marked as ${status}.`
|
|
423
461
|
: `Progress stored for task "${taskId}".`,
|
|
424
462
|
task: updatedTask,
|
|
463
|
+
blockedWaitingElapsedMs,
|
|
425
464
|
};
|
|
426
465
|
});
|
|
427
466
|
|
|
@@ -463,6 +502,10 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
463
502
|
...("wasForcedOverwrite" in result && result.wasForcedOverwrite
|
|
464
503
|
? { wasForcedOverwrite: true }
|
|
465
504
|
: {}),
|
|
505
|
+
...("blockedWaitingElapsedMs" in result &&
|
|
506
|
+
typeof result.blockedWaitingElapsedMs === "number"
|
|
507
|
+
? { blockedWaitingElapsedMs: result.blockedWaitingElapsedMs }
|
|
508
|
+
: {}),
|
|
466
509
|
};
|
|
467
510
|
return success ? toolOk(message, { data }) : toolErr(message, { data });
|
|
468
511
|
},
|
package/src/tools/templates.ts
CHANGED
|
@@ -197,6 +197,7 @@ Use the crashed agent's identity above as context for who was on it and what kin
|
|
|
197
197
|
|
|
198
198
|
Dispatch via \`send-task\` with ALL of:
|
|
199
199
|
- an explicit \`agentId\` (the chosen worker) — REQUIRED. If you omit it, \`send-task\` auto-routes to the original task's agent, which is the dead worker, and the work re-strands.
|
|
200
|
+
- \`routingReason: "reroute_fault"\`
|
|
200
201
|
- \`taskType: "resume"\`
|
|
201
202
|
- the tag \`resume-generation:{{generation_next}}\`
|
|
202
203
|
- \`parentTaskId: {{original_task_id}}\`
|
|
@@ -251,7 +252,7 @@ Required capabilities: {{required_capabilities}}{{artifacts_block}}
|
|
|
251
252
|
|
|
252
253
|
This task has been sitting unassigned because no currently-registered agent matches its required role/capabilities. Pick an agent to take this work over and RE-DELEGATE it — do NOT execute it yourself.
|
|
253
254
|
|
|
254
|
-
Dispatch via \`send-task\` with an explicit \`agentId\` (REQUIRED — omitting it re-pools the work under the same affinity tag and it will starve again) and \`parentTaskId: {{original_task_id}}\`.
|
|
255
|
+
Dispatch via \`send-task\` with an explicit \`agentId\` (REQUIRED — omitting it re-pools the work under the same affinity tag and it will starve again), \`routingReason: "overflow"\`, and \`parentTaskId: {{original_task_id}}\`.
|
|
255
256
|
|
|
256
257
|
This work will NOT fall back to the unassigned pool — you are the only re-delegation path.`,
|
|
257
258
|
variables: [
|
package/src/tools/utils.ts
CHANGED
|
@@ -246,6 +246,23 @@ const slackReadNudge = (r: SwarmToolResult): string | undefined => {
|
|
|
246
246
|
: undefined;
|
|
247
247
|
};
|
|
248
248
|
|
|
249
|
+
// Sole caller (storeProgressBlockedWaitingNudge) only reaches this once ms is
|
|
250
|
+
// past BLOCKED_WAITING_MIN_ELAPSED_MS (3 minutes), so there's no sub-minute case.
|
|
251
|
+
function formatIdleDuration(ms: number): string {
|
|
252
|
+
const minutes = Math.round(ms / 60_000);
|
|
253
|
+
if (minutes < 60) return `${minutes}m`;
|
|
254
|
+
const hours = Math.floor(minutes / 60);
|
|
255
|
+
const remMinutes = minutes % 60;
|
|
256
|
+
return remMinutes > 0 ? `${hours}h${remMinutes}m` : `${hours}h`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const storeProgressBlockedWaitingNudge = (r: SwarmToolResult): string | undefined => {
|
|
260
|
+
if (!r.ok) return undefined;
|
|
261
|
+
const ms = (r.data as { blockedWaitingElapsedMs?: unknown } | undefined)?.blockedWaitingElapsedMs;
|
|
262
|
+
if (typeof ms !== "number") return undefined;
|
|
263
|
+
return `That reads as blocked-waiting, ${formatIdleDuration(ms)} since your last update — call defer-task so a wake-up task resumes you instead of polling manually.`;
|
|
264
|
+
};
|
|
265
|
+
|
|
249
266
|
const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined => {
|
|
250
267
|
if (!r.ok) return undefined;
|
|
251
268
|
const hint = (r.data as { longScriptTimeoutHint?: unknown } | undefined)?.longScriptTimeoutHint;
|
|
@@ -260,6 +277,7 @@ const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined
|
|
|
260
277
|
export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefined> = {
|
|
261
278
|
"defer-task": (r) =>
|
|
262
279
|
r.ok ? "Stop working on this task now; the wake-up task will carry your note." : undefined,
|
|
280
|
+
"store-progress": storeProgressBlockedWaitingNudge,
|
|
263
281
|
"script-run": scriptRunNudge,
|
|
264
282
|
"script-upsert": scriptAuthoringNudge,
|
|
265
283
|
"launch-script-run": scriptAuthoringNudge,
|
|
@@ -745,6 +763,13 @@ type ToolConfig<
|
|
|
745
763
|
_meta?: Record<string, unknown>;
|
|
746
764
|
};
|
|
747
765
|
|
|
766
|
+
const preloadedToolsByServer = new WeakMap<McpServer, ReadonlySet<string>>();
|
|
767
|
+
|
|
768
|
+
/** Configure before registration. The set belongs to one MCP session, never the fleet. */
|
|
769
|
+
export function setPreloadedTools(server: McpServer, names: readonly string[]): void {
|
|
770
|
+
preloadedToolsByServer.set(server, new Set(names));
|
|
771
|
+
}
|
|
772
|
+
|
|
748
773
|
/**
|
|
749
774
|
* Creates a tool registration helper that automatically extracts request info
|
|
750
775
|
* and passes it as the second parameter to the callback.
|
|
@@ -770,10 +795,13 @@ export const createToolRegistrar = (server: McpServer) => {
|
|
|
770
795
|
config: ToolConfig<InputArgs, OutputArgs>,
|
|
771
796
|
cb: ToolCallbackWithInfo<InputArgs>,
|
|
772
797
|
) => {
|
|
798
|
+
const toolConfig = preloadedToolsByServer.get(server)?.has(name)
|
|
799
|
+
? { ...config, _meta: { ...config._meta, "anthropic/alwaysLoad": true } }
|
|
800
|
+
: config;
|
|
773
801
|
// When inputSchema is undefined, the MCP SDK calls handler(extra) with a single arg.
|
|
774
802
|
// When inputSchema is defined, it calls handler(args, extra) with two args.
|
|
775
803
|
if (config.inputSchema === undefined) {
|
|
776
|
-
return server.registerTool(name,
|
|
804
|
+
return server.registerTool(name, toolConfig, (async (meta: Meta) => {
|
|
777
805
|
const requestInfo = getRequestInfo(meta);
|
|
778
806
|
return withSpan(
|
|
779
807
|
"mcp.tool",
|
|
@@ -793,7 +821,10 @@ export const createToolRegistrar = (server: McpServer) => {
|
|
|
793
821
|
}) as Parameters<typeof server.registerTool>[2]);
|
|
794
822
|
}
|
|
795
823
|
|
|
796
|
-
return server.registerTool(name,
|
|
824
|
+
return server.registerTool(name, toolConfig, (async (
|
|
825
|
+
args: InferInput<InputArgs>,
|
|
826
|
+
meta: Meta,
|
|
827
|
+
) => {
|
|
797
828
|
const requestInfo = getRequestInfo(meta);
|
|
798
829
|
return withSpan(
|
|
799
830
|
// Span name carries the tool: a static `mcp.tool` is unreadable in a
|
package/src/types.ts
CHANGED
|
@@ -340,6 +340,15 @@ export const AgentTaskSourceSchema = z.enum([
|
|
|
340
340
|
]);
|
|
341
341
|
export type AgentTaskSource = z.infer<typeof AgentTaskSourceSchema>;
|
|
342
342
|
|
|
343
|
+
export const RoutingReasonSchema = z.enum([
|
|
344
|
+
"skill",
|
|
345
|
+
"continuity",
|
|
346
|
+
"overflow",
|
|
347
|
+
"human_pinned",
|
|
348
|
+
"reroute_fault",
|
|
349
|
+
]);
|
|
350
|
+
export type RoutingReason = z.infer<typeof RoutingReasonSchema>;
|
|
351
|
+
|
|
343
352
|
// ---------------------------------------------------------------------------
|
|
344
353
|
// Harness Provider
|
|
345
354
|
// ---------------------------------------------------------------------------
|
|
@@ -504,6 +513,8 @@ export const AgentTaskSchema = z
|
|
|
504
513
|
title: z.string().optional(), // Human-facing display title override (e.g. session rename); falls back to `task` when unset
|
|
505
514
|
status: AgentTaskStatusSchema,
|
|
506
515
|
source: AgentTaskSourceSchema.default("mcp"),
|
|
516
|
+
routingReason: RoutingReasonSchema.optional(),
|
|
517
|
+
routingNote: z.string().max(200).optional(),
|
|
507
518
|
|
|
508
519
|
// Task metadata
|
|
509
520
|
taskType: z.string().max(50).optional(), // e.g., "bug", "feature", "chore"
|
|
@@ -664,6 +675,8 @@ export const CreateTaskOptionsSchema = z.object({
|
|
|
664
675
|
agentId: z.string().nullable().optional(),
|
|
665
676
|
creatorAgentId: z.string().optional(),
|
|
666
677
|
source: AgentTaskSourceSchema.optional(),
|
|
678
|
+
routingReason: RoutingReasonSchema.optional(),
|
|
679
|
+
routingNote: z.string().max(200).optional(),
|
|
667
680
|
taskType: z.string().max(50).optional(),
|
|
668
681
|
tags: z.array(z.string()).optional(),
|
|
669
682
|
priority: z.number().int().min(0).max(100).optional(),
|
|
@@ -1936,7 +1949,7 @@ export const WorkflowNodeSchema = z
|
|
|
1936
1949
|
config: z
|
|
1937
1950
|
.record(z.string(), z.unknown())
|
|
1938
1951
|
.describe(
|
|
1939
|
-
"Executor-specific config. For agent-task: { template, outputSchema?, agentId?, tags?, priority?, dir?, vcsRepo?, model? }. " +
|
|
1952
|
+
"Executor-specific config. For agent-task: { template, outputSchema?, agentId?, routingReason?, routingNote?, tags?, priority?, dir?, vcsRepo?, model? }; configured agentId defaults routingReason to human_pinned. " +
|
|
1940
1953
|
"For script: { runtime, script, args?, timeout? }. " +
|
|
1941
1954
|
"For swarm-script: { scriptName, scope?, pinHash?, args?, fsMode?, timeoutMs? (1000-300000) }. " +
|
|
1942
1955
|
"Agent-task templates and ordinary config values support {{interpolation}} from the node's inputs context, including trigger and declared upstream aliases. " +
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* key for a short-lived aseph_ bearer before handing credentials to the
|
|
6
6
|
* ACP target process.
|
|
7
7
|
*/
|
|
8
|
+
import { scrubSecrets } from "./secret-scrubber";
|
|
9
|
+
|
|
10
|
+
const REVOKE_TIMEOUT_MS = 5000;
|
|
8
11
|
|
|
9
12
|
/** Wall-clock TTL for a session token: 24 hours. Long enough for any realistic
|
|
10
13
|
* ACP session; the token is actively revoked when the session finishes anyway. */
|
|
@@ -38,7 +41,8 @@ export async function mintAcpSessionToken(
|
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Ask the swarm API to revoke a previously-minted `aseph_` token. Best-effort:
|
|
41
|
-
* errors are logged but never propagated to the
|
|
44
|
+
* the request is bounded, and errors are logged but never propagated to the
|
|
45
|
+
* caller. ACP completion does not wait for this cleanup.
|
|
42
46
|
*/
|
|
43
47
|
export async function revokeAcpSessionToken(
|
|
44
48
|
apiUrl: string,
|
|
@@ -46,11 +50,17 @@ export async function revokeAcpSessionToken(
|
|
|
46
50
|
tokenId: string,
|
|
47
51
|
): Promise<void> {
|
|
48
52
|
try {
|
|
49
|
-
await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens/${tokenId}`, {
|
|
53
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens/${tokenId}`, {
|
|
50
54
|
method: "DELETE",
|
|
51
55
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
56
|
+
signal: AbortSignal.timeout(REVOKE_TIMEOUT_MS),
|
|
52
57
|
});
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(
|
|
61
|
+
scrubSecrets(
|
|
62
|
+
`[acp] Session token revoke failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
55
65
|
}
|
|
56
66
|
}
|
|
@@ -104,6 +104,8 @@ const TOKEN_REGEXES: ReadonlyArray<{ name: string; re: RegExp }> = [
|
|
|
104
104
|
{ name: "github_pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
|
|
105
105
|
// GitHub classic/OAuth tokens (ghp_, gho_, ghu_, ghs_, ghr_)
|
|
106
106
|
{ name: "github_token", re: new RegExp(String.raw`${TB}gh[pousr]_[A-Za-z0-9]{20,}\b`, "g") },
|
|
107
|
+
// ACP ephemeral session tokens (base62 payload)
|
|
108
|
+
{ name: "acp_session_token", re: new RegExp(String.raw`${TB}aseph_[A-Za-z0-9]{20,}\b`, "g") },
|
|
107
109
|
// GitLab personal access tokens
|
|
108
110
|
{ name: "gitlab_pat", re: new RegExp(String.raw`${TB}glpat-[A-Za-z0-9_-]{20,}\b`, "g") },
|
|
109
111
|
// Anthropic API keys (must match before the generic sk- rule below)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ALL_TOOLS } from "../tools/tool-config";
|
|
3
|
+
|
|
4
|
+
const toolNames = z
|
|
5
|
+
.array(z.string().refine((name) => ALL_TOOLS.has(name), "Unknown swarm tool"))
|
|
6
|
+
.max(16);
|
|
7
|
+
|
|
8
|
+
/** Exact task-type and schedule keys; a schedule entry overrides its task type. */
|
|
9
|
+
export const taskToolManifestSchema = z.strictObject({
|
|
10
|
+
taskTypes: z.record(z.string().min(1).max(50), toolNames).optional(),
|
|
11
|
+
schedules: z.record(z.uuid(), toolNames).optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function parseTaskToolManifest(value: string) {
|
|
15
|
+
return taskToolManifestSchema.parse(JSON.parse(value));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function selectTaskTools(
|
|
19
|
+
manifest: z.infer<typeof taskToolManifestSchema>,
|
|
20
|
+
task: { taskType?: string; scheduleId?: string; slackChannelId?: string },
|
|
21
|
+
): string[] {
|
|
22
|
+
const fromSchedule =
|
|
23
|
+
task.scheduleId && Object.hasOwn(manifest.schedules ?? {}, task.scheduleId)
|
|
24
|
+
? manifest.schedules?.[task.scheduleId]
|
|
25
|
+
: undefined;
|
|
26
|
+
const fromType =
|
|
27
|
+
task.taskType && Object.hasOwn(manifest.taskTypes ?? {}, task.taskType)
|
|
28
|
+
? manifest.taskTypes?.[task.taskType]
|
|
29
|
+
: undefined;
|
|
30
|
+
return [...new Set(fromSchedule ?? fromType ?? [])].filter(
|
|
31
|
+
(name) => !name.startsWith("slack-") || Boolean(task.slackChannelId),
|
|
32
|
+
);
|
|
33
|
+
}
|
package/src/workflows/engine.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notifyAutomationPreflightFailure } from "../automation-preflight-alert";
|
|
1
2
|
import {
|
|
2
3
|
getAutomationSetupStates,
|
|
3
4
|
preflightAutomation,
|
|
@@ -91,13 +92,15 @@ export async function startWorkflowExecution(
|
|
|
91
92
|
await getAutomationSetupStates(),
|
|
92
93
|
);
|
|
93
94
|
if (preflight.state === "needs_setup") {
|
|
94
|
-
|
|
95
|
+
const { runId, recorded } = await recordWorkflowPreflightFailure({
|
|
95
96
|
workflowId: workflow.id,
|
|
96
97
|
triggerType: options.triggerType ?? "manual",
|
|
97
98
|
triggerData,
|
|
98
99
|
failureReason: preflight.failureReason!,
|
|
99
100
|
createdBy: options.requestedByUserId,
|
|
100
101
|
});
|
|
102
|
+
if (recorded) await notifyAutomationPreflightFailure(preflight);
|
|
103
|
+
return runId;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
// Templates can consume install params outside the graph definition (most
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
FollowUpConfigSchema,
|
|
7
7
|
ModelTierSchema,
|
|
8
8
|
ReasoningEffortSchema,
|
|
9
|
+
RoutingReasonSchema,
|
|
9
10
|
splitLegacyModelAlias,
|
|
10
11
|
} from "../../types";
|
|
11
12
|
import type { ExecutorResult } from "./base";
|
|
@@ -13,11 +14,13 @@ import { BaseExecutor } from "./base";
|
|
|
13
14
|
|
|
14
15
|
// ─── Config / Output Schemas ────────────────────────────────
|
|
15
16
|
|
|
16
|
-
const AgentTaskConfigSchema = z.object({
|
|
17
|
+
export const AgentTaskConfigSchema = z.object({
|
|
17
18
|
template: z.string(),
|
|
18
19
|
// Plain string, NOT .uuid(): agents may join with custom IDs (AGENT_ID env /
|
|
19
20
|
// join-swarm agentId), so a UUID filter would reject legitimate agents.
|
|
20
21
|
agentId: z.string().optional(),
|
|
22
|
+
routingReason: RoutingReasonSchema.optional(),
|
|
23
|
+
routingNote: z.string().max(200).optional(),
|
|
21
24
|
tags: z.array(z.string()).optional(),
|
|
22
25
|
priority: z.number().int().min(0).max(100).optional(),
|
|
23
26
|
offerMode: z.boolean().optional(),
|
|
@@ -98,6 +101,9 @@ export class AgentTaskExecutor extends BaseExecutor<
|
|
|
98
101
|
{
|
|
99
102
|
key: effectiveKey,
|
|
100
103
|
agentId: config.agentId ?? null,
|
|
104
|
+
// A configured workflow target is an author pin, including existing definitions.
|
|
105
|
+
routingReason: config.routingReason ?? (config.agentId ? "human_pinned" : undefined),
|
|
106
|
+
routingNote: config.routingNote,
|
|
101
107
|
source: "workflow",
|
|
102
108
|
tags: config.tags,
|
|
103
109
|
priority: config.priority,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "9748aabf7252c258378302240d73f7516d113777",
|
|
3
3
|
"excludedSkills": [
|
|
4
4
|
"feedback"
|
|
5
5
|
],
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"templates/skills/brainstorming/files/template.md": "ae0aabc02a307db0550f9026aca33fb471608c31f7a0b65ecc781073f17cc953",
|
|
12
12
|
"templates/skills/code-reviewing/config.json": "dde1298b15d55428b898a0421033d1aa140ef152d335d49345059e29deab96f2",
|
|
13
13
|
"templates/skills/code-reviewing/content.md": "85e9aa3b276384ab3f3bc570aece2e8c103a8614c19129378cf4dfec3457a18b",
|
|
14
|
+
"templates/skills/comms/config.json": "5561ee129f68a03e89b21458afebcd79afd2a8c177c0ba5dad00ffa21ee57bc3",
|
|
15
|
+
"templates/skills/comms/content.md": "bd1d33b4af13eb36dfd87552bb35ecd200ac0a0dd40d409b50dff448708ce5ea",
|
|
16
|
+
"templates/skills/comms/files/references/ste-rules.md": "6ee2ed5d6b49d2e421b6c9d22310a5b589fa1abe81de06213e4b89fbc1271e74",
|
|
17
|
+
"templates/skills/comms/files/references/visual-shapes.md": "5e07e9250ad4cdc877af2281525185f0de89c01f90a077463a2c77c8a7b998e8",
|
|
14
18
|
"templates/skills/delegate-work/config.json": "5b1542eb0be359bb8a17117fcf87917e0607d1b939ae3266ddffacdc14efb281",
|
|
15
19
|
"templates/skills/delegate-work/content.md": "c9562bd17846d3595d40172ecfdc6f85bb99eaf23597eb9d5aa9635d5c14d079",
|
|
16
20
|
"templates/skills/delegate-work/files/scripts/codex-exec.sh": "c2222986c46ac608403f28f201230cb34c7d7ac41a2262dc2c8dbd99c8645dbb",
|
|
@@ -74,6 +78,7 @@
|
|
|
74
78
|
"ask-user",
|
|
75
79
|
"brainstorming",
|
|
76
80
|
"code-reviewing",
|
|
81
|
+
"comms",
|
|
77
82
|
"delegate-work",
|
|
78
83
|
"design-docs",
|
|
79
84
|
"engineering-standards",
|