@desplega.ai/agent-swarm 1.147.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 +13 -1
- package/dist/{acp-adapter-9n319wqc.js → acp-adapter-4jncb126.js} +12 -5
- package/dist/{actions-2vxqvpr9.js → actions-31vp3ae7.js} +10 -9
- package/dist/{app-w066xfy0.js → app-1hh67x8w.js} +6 -6
- package/dist/{assistant-kt31dj2h.js → assistant-z66dmz6p.js} +13 -11
- package/dist/{boot-reembed-az5rassp.js → boot-reembed-fqzxxzjh.js} +7 -7
- package/dist/{boot-reembed-j3mm3rfz.js → boot-reembed-wxa8ya0s.js} +8 -8
- package/dist/{boot-scrub-logs-b6h54817.js → boot-scrub-logs-jnyndh79.js} +5 -5
- package/dist/{claude-adapter-w1z04ann.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-f0cc1kec.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-de14znh8.js → cli-19f354qr.js} +2 -2
- package/dist/{cli-hbv0kq6w.js → cli-2qts1hys.js} +3 -3
- package/dist/{cli-kpv03zhj.js → cli-3apbzgyk.js} +27 -15
- package/dist/{cli-1th1728g.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-yz3djrm0.js → cli-6pqcb4q0.js} +53 -13
- package/dist/{cli-719k9j2c.js → cli-7smrsr25.js} +3 -3
- package/dist/{cli-rw51bq3j.js → cli-9fy6nk6g.js} +1 -1
- package/dist/{cli-zhjmqxzh.js → cli-9hspd3dp.js} +2 -2
- package/dist/{cli-3tgwnf43.js → cli-aae6hz6a.js} +4 -4
- package/dist/{cli-89xfd7f9.js → cli-cr12paw2.js} +5 -5
- package/dist/{cli-b5bq9crk.js → cli-d8ftsp62.js} +8 -8
- package/dist/{cli-4nr988h6.js → cli-e95cx1eb.js} +3 -3
- package/dist/{cli-n8508nre.js → cli-gjhjfeeg.js} +1 -1
- package/dist/{cli-p1d5b073.js → cli-gv88e1vf.js} +1 -1
- package/dist/{cli-rvqz34y5.js → cli-jecc01cb.js} +1 -1
- package/dist/{cli-8v0g1yc8.js → cli-k0cr4kat.js} +1 -1
- package/dist/{cli-mmemxxdc.js → cli-kgz4np97.js} +4 -4
- package/dist/{cli-w9kb3bk1.js → cli-kjwt6hdf.js} +3 -1
- package/dist/{cli-f7dsy61y.js → cli-mnmsfd1w.js} +6 -2
- package/dist/{cli-54wwve05.js → cli-n3dz2942.js} +37 -26
- package/dist/{cli-12fz972k.js → cli-nye12xk5.js} +8 -5
- package/dist/{cli-tzvk9haz.js → cli-phsxnkp6.js} +20 -20
- package/dist/{cli-53s590z8.js → cli-q50ef8g0.js} +1 -1
- package/dist/{cli-f146mn5s.js → cli-qaacms9y.js} +7 -6
- package/dist/{cli-xqcq9y5e.js → cli-r2ap2czm.js} +1 -0
- package/dist/{cli-m8yrsg97.js → cli-sd5pv3b1.js} +3 -3
- package/dist/{cli-0b6y7kxm.js → cli-xm87hzbw.js} +1 -1
- package/dist/{cli-ftht3zzj.js → cli-yab68w40.js} +285 -11
- package/dist/{cli-atcve9y8.js → cli-yb9qhqam.js} +227 -59
- package/dist/{cli-smby5m17.js → cli-ygweqcn4.js} +133 -22
- package/dist/{cli-dttph1bw.js → cli-ykxrnd1q.js} +2 -2
- package/dist/{cli-m9sxbkhm.js → cli-zvz62chv.js} +3 -3
- package/dist/cli.js +16 -14
- package/dist/{codex-adapter-zw4dwsx7.js → codex-adapter-8meawydj.js} +4 -4
- package/dist/{codex-hook-bw8p5nh4.js → codex-hook-hfrt9shr.js} +2 -2
- package/dist/{codex-session-runner-k981s65v.js → codex-session-runner-gjgxyp5j.js} +4 -4
- package/dist/{commands-sykrxx7e.js → commands-hz3nb95f.js} +5 -5
- package/dist/{db-sfbsc8wt.js → db-ggz97zbm.js} +5 -5
- package/dist/{e2b-6hb103d8.js → e2b-p04vmtx0.js} +1 -1
- package/dist/{handlers-0aspcejm.js → handlers-nbzdzn9k.js} +14 -11
- package/dist/{hook-hxccs7m5.js → hook-bks2q1f6.js} +7 -7
- package/dist/{hook-4nvqj2sb.js → hook-kq9wp0ep.js} +8 -8
- package/dist/{http-y06z517d.js → http-hrf77v87.js} +96 -45
- package/dist/{index-tce1rss4.js → index-6nsbg88z.js} +443 -144
- package/dist/{index-x5j894zf.js → index-8hmeg8j1.js} +14 -14
- package/dist/{index-k8znfx0z.js → index-cqtebbhb.js} +13 -13
- package/dist/{index-hssww7kj.js → index-n28pw1zt.js} +15 -15
- package/dist/{keepalive-w733ax66.js → keepalive-dxc12gvg.js} +7 -7
- package/dist/{lead-75bjqa9d.js → lead-yckfn18d.js} +32 -32
- package/dist/{maintenance-w4f1zjck.js → maintenance-6kfb2m5e.js} +8 -8
- package/dist/{oauth-refresh-sweep-wdetky26.js → oauth-refresh-sweep-1x1ac722.js} +6 -6
- package/dist/{onboard-bb5net8s.js → onboard-dcyt1609.js} +4 -3
- package/dist/{opencode-adapter-fef1mrn2.js → opencode-adapter-dce4wskd.js} +2 -2
- package/dist/{otel-impl-0w7c14ap.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-9nzsrgcq.js → pricing-refresh-kaqg567e.js} +7 -7
- package/dist/{rbac-roles-z1jrffbt.js → rbac-roles-ammq6rd2.js} +5 -5
- package/dist/{rbac-roles-r3hqjp09.js → rbac-roles-cf97v1vd.js} +6 -6
- package/dist/{render-v2-vbr89fnb.js → render-v2-80w603vy.js} +6 -6
- package/dist/{seed-pricing-g62hy1pk.js → seed-pricing-zbrn83xv.js} +6 -6
- package/dist/{setup-gnfqnptp.js → setup-fe66kczs.js} +2 -2
- package/dist/{worker-kznxp4qm.js → worker-qmb2d1e2.js} +32 -32
- package/dist/{x-n2phr5vm.js → x-n3e0v3xa.js} +2 -2
- package/openapi.json +46 -4
- package/package.json +3 -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/mcp-input-ergonomics.test.ts +386 -0
- 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/accept-steer.ts +0 -1
- package/src/tools/defer-task.ts +73 -21
- package/src/tools/memory-get.ts +10 -3
- package/src/tools/memory-rate.ts +19 -7
- package/src/tools/memory-search.ts +2 -1
- package/src/tools/memory-store.ts +25 -5
- package/src/tools/send-task.ts +40 -2
- package/src/tools/skills/skill-publish.ts +1 -0
- package/src/tools/store-progress.ts +78 -4
- 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/memory-get.ts
CHANGED
|
@@ -80,12 +80,14 @@ export const registerMemoryGetTool = (server: McpServer) => {
|
|
|
80
80
|
annotations: { readOnlyHint: true },
|
|
81
81
|
|
|
82
82
|
inputSchema: z.object({
|
|
83
|
-
memoryId: z.uuid().describe("The ID of the memory to retrieve."),
|
|
83
|
+
memoryId: z.uuid().optional().describe("The ID of the memory to retrieve (or use id)."),
|
|
84
|
+
id: z.uuid().optional().describe("Alias for memoryId, matching memory-search results."),
|
|
84
85
|
intent: z
|
|
85
86
|
.string()
|
|
86
87
|
.min(1)
|
|
88
|
+
.optional()
|
|
87
89
|
.describe(
|
|
88
|
-
"
|
|
90
|
+
"Optional reason for retrieving this memory. E.g. 'need full details of the auth fix pattern'.",
|
|
89
91
|
),
|
|
90
92
|
}),
|
|
91
93
|
outputSchema: swarmToolOutputSchema({
|
|
@@ -102,7 +104,12 @@ export const registerMemoryGetTool = (server: McpServer) => {
|
|
|
102
104
|
rateHint: z.string().optional(),
|
|
103
105
|
}),
|
|
104
106
|
},
|
|
105
|
-
async ({ memoryId, intent }, requestInfo, _meta) => {
|
|
107
|
+
async ({ memoryId: requestedMemoryId, id, intent }, requestInfo, _meta) => {
|
|
108
|
+
if (requestedMemoryId && id && requestedMemoryId !== id) {
|
|
109
|
+
return toolErr("memoryId and id must identify the same memory; supply only one.");
|
|
110
|
+
}
|
|
111
|
+
const memoryId = requestedMemoryId ?? id;
|
|
112
|
+
if (!memoryId) return toolErr("Supply memoryId (or its alias id) from memory-search.");
|
|
106
113
|
const store = getMemoryStore();
|
|
107
114
|
const memoryForAuth = await store.peek(memoryId);
|
|
108
115
|
|
package/src/tools/memory-rate.ts
CHANGED
|
@@ -42,15 +42,21 @@ export const registerMemoryRateTool = (server: McpServer) => {
|
|
|
42
42
|
"the swarm learns to surface better memories next time.",
|
|
43
43
|
annotations: { destructiveHint: false },
|
|
44
44
|
inputSchema: z.object({
|
|
45
|
-
id: z
|
|
45
|
+
id: z
|
|
46
|
+
.string()
|
|
47
|
+
.min(1)
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Memory ID returned by memory-search (or use memoryId)."),
|
|
50
|
+
memoryId: z.string().min(1).optional().describe("Alias for id, matching memory-get."),
|
|
46
51
|
useful: z
|
|
47
52
|
.boolean()
|
|
48
53
|
.describe("true = this memory helped solve the task; false = misled or wasted time."),
|
|
49
54
|
note: z
|
|
50
55
|
.string()
|
|
51
|
-
.max(280)
|
|
52
56
|
.optional()
|
|
53
|
-
.describe(
|
|
57
|
+
.describe(
|
|
58
|
+
"Reason, stored up to 500 characters for telemetry; not surfaced to other agents.",
|
|
59
|
+
),
|
|
54
60
|
referencesSource: z
|
|
55
61
|
.string()
|
|
56
62
|
.min(1)
|
|
@@ -60,7 +66,13 @@ export const registerMemoryRateTool = (server: McpServer) => {
|
|
|
60
66
|
}),
|
|
61
67
|
outputSchema: swarmToolOutputSchema(),
|
|
62
68
|
},
|
|
63
|
-
async ({ id, useful, note, referencesSource }, requestInfo, _meta) => {
|
|
69
|
+
async ({ id, memoryId, useful, note, referencesSource }, requestInfo, _meta) => {
|
|
70
|
+
if (id && memoryId && id !== memoryId) {
|
|
71
|
+
return toolErr("id and memoryId must identify the same memory; supply only one.");
|
|
72
|
+
}
|
|
73
|
+
const resolvedMemoryId = id ?? memoryId;
|
|
74
|
+
if (!resolvedMemoryId)
|
|
75
|
+
return toolErr("Supply id (or its alias memoryId) from memory-search.");
|
|
64
76
|
if (!requestInfo.agentId) {
|
|
65
77
|
return toolErr("Agent ID required. Are you registered in the swarm?");
|
|
66
78
|
}
|
|
@@ -85,11 +97,11 @@ export const registerMemoryRateTool = (server: McpServer) => {
|
|
|
85
97
|
const apiKey = getApiKey();
|
|
86
98
|
|
|
87
99
|
const event = {
|
|
88
|
-
memoryId:
|
|
100
|
+
memoryId: resolvedMemoryId,
|
|
89
101
|
signal: useful ? 1 : -1,
|
|
90
102
|
weight: 1.0,
|
|
91
103
|
source: "explicit-self" as const,
|
|
92
|
-
reasoning: note ?? "",
|
|
104
|
+
reasoning: (note ?? "").slice(0, 500),
|
|
93
105
|
taskId: requestInfo.sourceTaskId,
|
|
94
106
|
...(cleanedReferencesSource !== undefined
|
|
95
107
|
? { referencesSource: cleanedReferencesSource }
|
|
@@ -129,7 +141,7 @@ export const registerMemoryRateTool = (server: McpServer) => {
|
|
|
129
141
|
return toolErr(`Memory rating failed (HTTP ${response.status}).`);
|
|
130
142
|
}
|
|
131
143
|
|
|
132
|
-
return toolOk(`Memory ${
|
|
144
|
+
return toolOk(`Memory ${resolvedMemoryId} rated as ${useful ? "useful" : "not useful"}.`);
|
|
133
145
|
} catch (err) {
|
|
134
146
|
return toolErr(`Memory rating failed: ${(err as Error).message}`);
|
|
135
147
|
}
|
|
@@ -84,8 +84,9 @@ export const registerMemorySearchTool = (server: McpServer) => {
|
|
|
84
84
|
intent: z
|
|
85
85
|
.string()
|
|
86
86
|
.min(1)
|
|
87
|
+
.optional()
|
|
87
88
|
.describe(
|
|
88
|
-
"
|
|
89
|
+
"Optional reason for searching for this memory. E.g. 'looking for auth pattern to fix login bug'.",
|
|
89
90
|
),
|
|
90
91
|
scope: z
|
|
91
92
|
.enum(["all", "agent", "swarm"])
|
|
@@ -24,14 +24,19 @@ export const registerMemoryStoreTool = (server: McpServer) => {
|
|
|
24
24
|
.string()
|
|
25
25
|
.min(1)
|
|
26
26
|
.max(200)
|
|
27
|
-
.
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"Short title used in search results and the UI. Defaults to the first non-empty content line (up to 200 characters).",
|
|
30
|
+
),
|
|
28
31
|
scope: AgentMemoryScopeSchema.default("agent").describe(
|
|
29
32
|
"'agent' (default): only you can recall it. 'swarm': every agent can recall it.",
|
|
30
33
|
),
|
|
31
34
|
tags: z
|
|
32
|
-
.array(z.string())
|
|
35
|
+
.union([z.array(z.string()), z.string()])
|
|
33
36
|
.optional()
|
|
34
|
-
.describe(
|
|
37
|
+
.describe(
|
|
38
|
+
"Free-form tags as an array or a comma-separated string, for example a repo name or a topic.",
|
|
39
|
+
),
|
|
35
40
|
taskId: z
|
|
36
41
|
.uuid()
|
|
37
42
|
.optional()
|
|
@@ -49,7 +54,22 @@ export const registerMemoryStoreTool = (server: McpServer) => {
|
|
|
49
54
|
queued: z.boolean().optional(),
|
|
50
55
|
}),
|
|
51
56
|
},
|
|
52
|
-
async ({ content, name, scope, tags, taskId, intent }, requestInfo, _meta) => {
|
|
57
|
+
async ({ content, name: requestedName, scope, tags, taskId, intent }, requestInfo, _meta) => {
|
|
58
|
+
const name =
|
|
59
|
+
requestedName ??
|
|
60
|
+
content
|
|
61
|
+
.split(/\r?\n/)
|
|
62
|
+
.find((line) => line.trim())
|
|
63
|
+
?.trim()
|
|
64
|
+
.slice(0, 200) ??
|
|
65
|
+
"Untitled memory";
|
|
66
|
+
const normalizedTags =
|
|
67
|
+
typeof tags === "string"
|
|
68
|
+
? tags
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((tag) => tag.trim())
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
: tags;
|
|
53
73
|
if (!requestInfo.agentId) {
|
|
54
74
|
return toolErr("Agent ID required. Are you registered in the swarm?");
|
|
55
75
|
}
|
|
@@ -64,7 +84,7 @@ export const registerMemoryStoreTool = (server: McpServer) => {
|
|
|
64
84
|
scope,
|
|
65
85
|
source: "manual",
|
|
66
86
|
sourceTaskId: taskId ?? null,
|
|
67
|
-
tags,
|
|
87
|
+
tags: normalizedTags,
|
|
68
88
|
intent,
|
|
69
89
|
});
|
|
70
90
|
|
package/src/tools/send-task.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getAgentById,
|
|
12
12
|
getDbClient,
|
|
13
13
|
getTaskById,
|
|
14
|
+
getUserById,
|
|
14
15
|
hasCapacity,
|
|
15
16
|
} from "@/be/db";
|
|
16
17
|
import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
FollowUpConfigSchema,
|
|
31
32
|
ModelTierSchema,
|
|
32
33
|
ReasoningEffortSchema,
|
|
34
|
+
RoutingReasonSchema,
|
|
33
35
|
splitLegacyModelAlias,
|
|
34
36
|
} from "@/types";
|
|
35
37
|
import { findJsonSchemaShapeErrors } from "@/workflows/json-schema-validator";
|
|
@@ -60,6 +62,14 @@ export const sendTaskInputSchema = z
|
|
|
60
62
|
.string()
|
|
61
63
|
.optional()
|
|
62
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)."),
|
|
63
73
|
task: z.string().min(1).describe("The task description to send."),
|
|
64
74
|
key: AssetKeySchema.optional().describe(
|
|
65
75
|
"Logical namespace key. Child tasks inherit their parent namespace when provided.",
|
|
@@ -148,10 +158,10 @@ export const sendTaskInputSchema = z
|
|
|
148
158
|
),
|
|
149
159
|
requestedByUserId: z
|
|
150
160
|
.string()
|
|
151
|
-
.
|
|
161
|
+
.regex(/^[a-f0-9]{32}$/, "Expected a registry user ID (32 lowercase hexadecimal characters).")
|
|
152
162
|
.optional()
|
|
153
163
|
.describe(
|
|
154
|
-
"
|
|
164
|
+
"Registered requester ID (32 lowercase hexadecimal characters). When omitted, inherited from the caller's current task so the attribution flows through multi-hop delegation automatically.",
|
|
155
165
|
),
|
|
156
166
|
followUpConfig: FollowUpConfigSchema.optional().describe(
|
|
157
167
|
"Control the lead follow-up created when this task finishes. When to use `followUpConfig`: set `disabled: true` when you'll wait for this task to complete inline and no follow-up is needed; set `onCompleted` / `onFailed` with specific instructions when you need to follow up effectively on a particular outcome of a long-running flow; for normal one-shot tasks, leave it unset because defaults are fine. It is most valuable for long-running / complex flows.",
|
|
@@ -173,6 +183,13 @@ export const sendTaskInputSchema = z
|
|
|
173
183
|
path: [hasChannel ? "slackThreadTs" : "slackChannelId"],
|
|
174
184
|
});
|
|
175
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
|
+
}
|
|
176
193
|
checkOutputSchemaShape(data.outputSchema, ctx);
|
|
177
194
|
});
|
|
178
195
|
|
|
@@ -221,6 +238,8 @@ export async function sendTaskHandler(
|
|
|
221
238
|
ctx: ToolCtx,
|
|
222
239
|
{
|
|
223
240
|
agentId,
|
|
241
|
+
routingReason,
|
|
242
|
+
routingNote,
|
|
224
243
|
task,
|
|
225
244
|
key,
|
|
226
245
|
offerMode,
|
|
@@ -246,6 +265,10 @@ export async function sendTaskHandler(
|
|
|
246
265
|
outputSchema,
|
|
247
266
|
}: SendTaskArgs,
|
|
248
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
|
+
}
|
|
249
272
|
if (ctx.kind === "owner" && !ctx.agentId) {
|
|
250
273
|
return toolErr('Agent ID not found. The MCP client should define the "X-Agent-ID" header.', {
|
|
251
274
|
data: { yourAgentId: ctx.agentId },
|
|
@@ -257,6 +280,12 @@ export async function sendTaskHandler(
|
|
|
257
280
|
const requestedByUserId =
|
|
258
281
|
ctx.kind === "user" ? ctx.userId : (inputRequestedByUserId ?? undefined);
|
|
259
282
|
|
|
283
|
+
if (ctx.kind === "owner" && requestedByUserId && !(await getUserById(requestedByUserId))) {
|
|
284
|
+
return toolErr("requestedByUserId must identify an existing registered user.", {
|
|
285
|
+
data: { yourAgentId: creatorAgentId },
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
260
289
|
if (ctx.kind === "owner" && agentId === ctx.agentId) {
|
|
261
290
|
return toolErr("Cannot send a task to yourself, are you drunk?", {
|
|
262
291
|
data: { yourAgentId: ctx.agentId },
|
|
@@ -332,6 +361,9 @@ export async function sendTaskHandler(
|
|
|
332
361
|
effectiveAgentId = effectiveParentTask.agentId;
|
|
333
362
|
}
|
|
334
363
|
}
|
|
364
|
+
const effectiveRoutingReason =
|
|
365
|
+
agentId !== undefined ? routingReason : effectiveAgentId ? "continuity" : undefined;
|
|
366
|
+
const effectiveRoutingNote = effectiveRoutingReason ? routingNote : undefined;
|
|
335
367
|
|
|
336
368
|
// The three dedup guards are pure reads, so they run twice: once here as a
|
|
337
369
|
// fast path (keeping this tool's existing early-exit responses), and once
|
|
@@ -463,6 +495,8 @@ export async function sendTaskHandler(
|
|
|
463
495
|
overrideSlackContext,
|
|
464
496
|
followUpConfig,
|
|
465
497
|
outputSchema,
|
|
498
|
+
routingReason: effectiveRoutingReason,
|
|
499
|
+
routingNote: effectiveRoutingNote,
|
|
466
500
|
routingAffinity:
|
|
467
501
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
468
502
|
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -530,6 +564,8 @@ export async function sendTaskHandler(
|
|
|
530
564
|
overrideSlackContext,
|
|
531
565
|
followUpConfig,
|
|
532
566
|
outputSchema,
|
|
567
|
+
routingReason: effectiveRoutingReason,
|
|
568
|
+
routingNote: effectiveRoutingNote,
|
|
533
569
|
routingAffinity:
|
|
534
570
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
535
571
|
? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -571,6 +607,8 @@ export async function sendTaskHandler(
|
|
|
571
607
|
overrideSlackContext,
|
|
572
608
|
followUpConfig,
|
|
573
609
|
outputSchema,
|
|
610
|
+
routingReason: effectiveRoutingReason,
|
|
611
|
+
routingNote: effectiveRoutingNote,
|
|
574
612
|
routingAffinity:
|
|
575
613
|
effectiveLeadOnly || requiredCapabilities?.length
|
|
576
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) => {
|
|
@@ -68,12 +85,19 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
68
85
|
annotations: { idempotentHint: true },
|
|
69
86
|
|
|
70
87
|
inputSchema: z.object({
|
|
71
|
-
taskId: z
|
|
88
|
+
taskId: z
|
|
89
|
+
.uuid()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe(
|
|
92
|
+
"Full task UUID. Defaults to the caller-owned task in X-Source-Task-Id; required outside task context.",
|
|
93
|
+
),
|
|
72
94
|
progress: z.string().optional().describe("The progress update to store."),
|
|
73
95
|
status: z
|
|
74
|
-
.enum(["completed", "failed"])
|
|
96
|
+
.enum(["completed", "failed", "in_progress", "pending"])
|
|
75
97
|
.optional()
|
|
76
|
-
.describe(
|
|
98
|
+
.describe(
|
|
99
|
+
"Set to 'completed' or 'failed' to finish the task. 'in_progress' and 'pending' store progress only and do not change task status.",
|
|
100
|
+
),
|
|
77
101
|
output: z
|
|
78
102
|
.string()
|
|
79
103
|
.optional()
|
|
@@ -111,7 +135,16 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
111
135
|
outputSchema: storeProgressOutputSchema,
|
|
112
136
|
},
|
|
113
137
|
async (
|
|
114
|
-
{
|
|
138
|
+
{
|
|
139
|
+
taskId: requestedTaskId,
|
|
140
|
+
progress,
|
|
141
|
+
status: requestedStatus,
|
|
142
|
+
output,
|
|
143
|
+
failureReason,
|
|
144
|
+
attachments,
|
|
145
|
+
persistMemory,
|
|
146
|
+
force,
|
|
147
|
+
},
|
|
115
148
|
requestInfo,
|
|
116
149
|
_meta,
|
|
117
150
|
) => {
|
|
@@ -119,6 +152,21 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
119
152
|
return toolErr('Agent ID not found. The MCP client should define the "X-Agent-ID" header.');
|
|
120
153
|
}
|
|
121
154
|
|
|
155
|
+
const taskId = requestedTaskId ?? requestInfo.sourceTaskId;
|
|
156
|
+
if (!taskId || !z.uuid().safeParse(taskId).success) {
|
|
157
|
+
return toolErr("Supply taskId as the full task UUID; no valid task context is available.");
|
|
158
|
+
}
|
|
159
|
+
if (!requestedTaskId) {
|
|
160
|
+
const contextTask = await getTaskById(taskId);
|
|
161
|
+
if (!contextTask || contextTask.agentId !== requestInfo.agentId) {
|
|
162
|
+
return toolErr("Omitted taskId requires a source task assigned to the calling agent.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const status =
|
|
166
|
+
requestedStatus === "completed" || requestedStatus === "failed"
|
|
167
|
+
? requestedStatus
|
|
168
|
+
: undefined;
|
|
169
|
+
|
|
122
170
|
// Verify agent-fs pointers before opening the write transaction. The
|
|
123
171
|
// registering agent's resolved config selects both credentials and the
|
|
124
172
|
// exact org/drive; never let the provider fall back to a personal drive.
|
|
@@ -220,6 +268,27 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
220
268
|
|
|
221
269
|
let updatedTask = existingTask;
|
|
222
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
|
+
}
|
|
223
292
|
|
|
224
293
|
// Attachments — pointer-based, append-only. Insert each row inside
|
|
225
294
|
// this transaction; the helper dedups by sha256 (when present) or by
|
|
@@ -391,6 +460,7 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
391
460
|
? `Task "${taskId}" marked as ${status}.`
|
|
392
461
|
: `Progress stored for task "${taskId}".`,
|
|
393
462
|
task: updatedTask,
|
|
463
|
+
blockedWaitingElapsedMs,
|
|
394
464
|
};
|
|
395
465
|
});
|
|
396
466
|
|
|
@@ -432,6 +502,10 @@ export const registerStoreProgressTool = (server: McpServer) => {
|
|
|
432
502
|
...("wasForcedOverwrite" in result && result.wasForcedOverwrite
|
|
433
503
|
? { wasForcedOverwrite: true }
|
|
434
504
|
: {}),
|
|
505
|
+
...("blockedWaitingElapsedMs" in result &&
|
|
506
|
+
typeof result.blockedWaitingElapsedMs === "number"
|
|
507
|
+
? { blockedWaitingElapsedMs: result.blockedWaitingElapsedMs }
|
|
508
|
+
: {}),
|
|
435
509
|
};
|
|
436
510
|
return success ? toolOk(message, { data }) : toolErr(message, { data });
|
|
437
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: [
|