@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
|
@@ -154,6 +154,7 @@ export async function routeKapsoInbound(payload: KapsoWebhookPayload): Promise<K
|
|
|
154
154
|
|
|
155
155
|
const task = await createTaskWithSiblingAwareness(await buildTaskDescription(payload), {
|
|
156
156
|
agentId: mapping.agentId ?? null,
|
|
157
|
+
routingReason: mapping.agentId ? "human_pinned" : undefined,
|
|
157
158
|
source: "system",
|
|
158
159
|
taskType: "kapso-inbound",
|
|
159
160
|
tags: ["kapso-whatsapp", "inbound"],
|
package/src/jira/sync.ts
CHANGED
|
@@ -582,6 +582,7 @@ async function createInitialJiraTask(input: {
|
|
|
582
582
|
|
|
583
583
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
584
584
|
agentId: lead?.id ?? "",
|
|
585
|
+
routingReason: lead ? "skill" : undefined,
|
|
585
586
|
source: "jira",
|
|
586
587
|
taskType: "jira-issue",
|
|
587
588
|
requestedByUserId: input.requestedByUserId,
|
|
@@ -627,6 +628,7 @@ async function createCommentMentionTask(input: {
|
|
|
627
628
|
|
|
628
629
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
629
630
|
agentId: lead?.id ?? "",
|
|
631
|
+
routingReason: lead ? "skill" : undefined,
|
|
630
632
|
source: "jira",
|
|
631
633
|
taskType: "jira-issue",
|
|
632
634
|
requestedByUserId: input.requestedByUserId,
|
package/src/linear/sync.ts
CHANGED
|
@@ -614,6 +614,7 @@ export async function handleAgentSessionEvent(event: Record<string, unknown>): P
|
|
|
614
614
|
|
|
615
615
|
const task = await createTaskWithSiblingAwareness(templateResult.text, {
|
|
616
616
|
agentId: lead?.id ?? "",
|
|
617
|
+
routingReason: lead ? "skill" : undefined,
|
|
617
618
|
source: "linear",
|
|
618
619
|
taskType: "linear-issue",
|
|
619
620
|
requestedByUserId,
|
|
@@ -897,6 +898,7 @@ export async function handleAgentSessionPrompted(event: Record<string, unknown>)
|
|
|
897
898
|
|
|
898
899
|
const task = await createTaskWithSiblingAwareness(followupResult.text, {
|
|
899
900
|
agentId: lead?.id ?? "",
|
|
901
|
+
routingReason: lead ? "skill" : undefined,
|
|
900
902
|
source: "linear",
|
|
901
903
|
taskType: "linear-issue",
|
|
902
904
|
requestedByUserId: promptedRequestedByUserId,
|
|
@@ -90,14 +90,15 @@ registerTemplate({
|
|
|
90
90
|
Your output is delegation and review. Workers implement, research, analyze, and write. Data gathering, even a quick query, goes to a worker. You answer simple factual questions yourself.
|
|
91
91
|
|
|
92
92
|
\`get-swarm\` is the roster. Route by capability and load.
|
|
93
|
-
|
|
93
|
+
\`send-task\`: include \`routingReason\` with \`agentId\`. Read results via \`get-task-details\`.
|
|
94
94
|
A task states the goal, the repo URL when there is one, and the constraints. Workers know git, the skills, and \`store-progress\`.
|
|
95
95
|
Delegate by the shape of the work: a workflow for multi-step or fan-out work, a schedule for recurring work, a script for bulk data, an inline \`script-run\` for a one-off bulk job you can run yourself. The \`workflow-iterate\`, \`scheduling\`, and \`swarm-scripts\` skills build them.
|
|
96
96
|
Research or exploration: tell the worker to use the \`researching\` skill. A large feature: a task for the \`planning\` skill first, then a task for the \`implementing\` skill with \`parentTaskId\`. A small fix: direct implementation.
|
|
97
97
|
A follow-up that continues earlier work carries \`parentTaskId\`. The worker receives the prior context.
|
|
98
|
-
A task whose result depends on the workers' output: wait for the children with the \`wait-for-task\` script, then merge and complete the task yourself. A turn that ends with children still running leaves the task unfinished.
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
Worker completion/failure triggers a follow-up by default. For longer work, \`defer-task\` with a wake-up or complete this task. Review the worker's answer and complete the follow-up. Escalate only failures needing a person.
|
|
100
|
+
|
|
101
|
+
Wait inline via \`wait-for-task\` only for an essential result expected within ~1 minute. Set \`send-task.followUpConfig.disabled=true\`; cap all waits at ~1 minute total. If still running, \`defer-task\` with a wake-up.
|
|
101
102
|
|
|
102
103
|
A task from an unknown user: register them with \`manage-user\`, then continue.
|
|
103
104
|
Your heartbeat runbook is the \`heartbeatMd\` profile field. Edit it with \`update-profile\`. You MUST use the \`heartbeat-runbook\` skill when you handle a heartbeat checklist task.
|
|
@@ -229,16 +230,20 @@ registerTemplate({
|
|
|
229
230
|
defaultBody: `
|
|
230
231
|
## How you write
|
|
231
232
|
|
|
232
|
-
|
|
233
|
+
For human-facing replies and artifacts: lead with the result. Use plain words, active voice, one idea per sentence, and sentence case headings.
|
|
234
|
+
State blockers and reasons. Preserve meaningful uncertainty.
|
|
235
|
+
Omit em dashes, filler, sign-offs, praise, repetition, step narration, and generic offers to elaborate.
|
|
236
|
+
|
|
237
|
+
Simple replies: one to three sentences. Routine replies: under 120 words, using short paragraphs or up to three bullets.
|
|
238
|
+
Use more detail for requested depth or essential evidence, caveats, or instructions.
|
|
239
|
+
Include context only when it changes understanding or action. Link extensive supporting material when useful, but make the reply self-contained.
|
|
240
|
+
Do not shorten investigation, required artifacts, or schema-defined output to meet reply limits.
|
|
233
241
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Reply in the requester's language, at the depth they asked for. A one-line question gets the answer first.
|
|
240
|
-
A Requester Profile section, when present, wins on tone, depth, and format. Correctness wins over style.
|
|
241
|
-
Em dashes, filler, sign-offs, and praise of the question are out.
|
|
242
|
+
Follow the current request and Requester Profile for language, tone, depth, and format. Correctness wins over style.
|
|
243
|
+
Use the \`comms\` skill when available for requested simplification or clarity rewrites. Ordinary replies need no skill invocation.
|
|
244
|
+
Use Visual mode only for explicit visual requests with delivery tools available in the active channel.
|
|
245
|
+
Requests to show data are not visual requests. New facts, diagnosis, and actions still require work.
|
|
246
|
+
Output schemas and channel delivery rules, including Slack's engine-owned delivery, override skill defaults.
|
|
242
247
|
`,
|
|
243
248
|
variables: [],
|
|
244
249
|
category: "system",
|
|
@@ -316,6 +321,15 @@ registerTemplate({
|
|
|
316
321
|
// lists are rendered at the call site and interpolated into the static text)
|
|
317
322
|
// ============================================================================
|
|
318
323
|
|
|
324
|
+
registerTemplate({
|
|
325
|
+
eventType: "system.agent.tool_preload",
|
|
326
|
+
header: "",
|
|
327
|
+
defaultBody:
|
|
328
|
+
"This session preloads selected swarm tools. If a tool definition is already available in context, call it directly. Use your harness tool search only for tools whose definitions are not yet available.",
|
|
329
|
+
variables: [],
|
|
330
|
+
category: "system",
|
|
331
|
+
});
|
|
332
|
+
|
|
319
333
|
registerTemplate({
|
|
320
334
|
eventType: "system.agent.tools_skills",
|
|
321
335
|
header: "",
|
|
@@ -386,8 +400,8 @@ This swarm runs in **scripts-only mode**. The ONLY swarm MCP tools available are
|
|
|
386
400
|
The script authoring contract in the \`swarm-scripts\` skill (entry signature, \`ctx\` shape, secret handling) applies here unchanged. The full SDK is \`ctx.swarm.*\`: task lifecycle (\`task_get\`, \`task_send\`, \`task_storeProgress\`, \`task_action\`, \`task_list\`), Slack (\`slack_reply\`, \`slack_post\`, \`slack_read\`), memory, kv, swarm info (\`swarm_get\`, \`agent_info\`), and more. Responses are usually wrapped; prefer \`res?.data ?? res\`.
|
|
387
401
|
|
|
388
402
|
**Built-in coordination scripts, USE THESE FIRST (\`script-run\` with \`name\` + \`args\`):**
|
|
389
|
-
- \`delegate\` {agentName, task, parentTaskId?} → subtask for an agent by name; returns {taskId}
|
|
390
|
-
- \`wait-for-task\` {taskId} → waits up to ~25s for a terminal state; returns {done, status, output};
|
|
403
|
+
- \`delegate\` {agentName, task, routingReason, parentTaskId?} → subtask for an agent by name; returns {taskId}
|
|
404
|
+
- \`wait-for-task\` {taskId} → waits up to ~25s for a terminal state; returns {done, status, output}; only for a child expected to finish within about a minute whose result the answer requires. Bound all calls to about a minute total; if still done=false, use \`defer-task\` with a wake-up to collect the result.
|
|
391
405
|
- \`get-child-outputs\` {parentTaskId} → all children with status+output
|
|
392
406
|
- \`complete-task\` {taskId, output} → THE way to finish your assigned task
|
|
393
407
|
- \`report-progress\` {taskId, note} → progress update
|
|
@@ -397,7 +411,7 @@ Rules of the road:
|
|
|
397
411
|
- Prefer a built-in script over inline source; write inline TypeScript only for logic no built-in covers. Check \`script-search\` first, and \`script-query-types\` for the live \`swarm-sdk.d.ts\` before authoring anything non-trivial.
|
|
398
412
|
- \`taskId\` is NOT ambient inside scripts; pass it explicitly via \`args\`.
|
|
399
413
|
- Report progress and completion via \`complete-task\` / \`report-progress\` (or \`ctx.swarm.task_storeProgress\` inline). This is how you update, complete, or fail your task; there is no other way.
|
|
400
|
-
- Scripts are killed after ~30s and stdout is capped at 1 MB. Never sleep/loop longer than ~25s inside one script;
|
|
414
|
+
- Scripts are killed after ~30s and stdout is capped at 1 MB. Never sleep/loop longer than ~25s inside one script. Default to the child's automatic follow-up; chained \`wait-for-task\` calls are only for the inline exception above and must stay within about a minute total.
|
|
401
415
|
- Aggregate inside the script and return only the derived result; never dump raw data.
|
|
402
416
|
- Batch related SDK calls into a single script when it reduces round trips.
|
|
403
417
|
`,
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getDbClient, getScheduledTaskById, getUserById, updateScheduledTask } from "@/be/db";
|
|
2
|
+
import type { AgentTask } from "@/types";
|
|
3
|
+
import { workflowEventBus } from "@/workflows/event-bus";
|
|
4
|
+
import { createStandaloneScheduleTask } from "./schedule-task";
|
|
5
|
+
|
|
6
|
+
export type TaskWakeEvent = "task.completed" | "task.failed" | "settled";
|
|
7
|
+
|
|
8
|
+
type TaskWait = {
|
|
9
|
+
scheduleId: string;
|
|
10
|
+
taskId: string;
|
|
11
|
+
eventName: TaskWakeEvent;
|
|
12
|
+
status: "pending" | "fired";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Undefined means a normal schedule; an empty result means another caller won. */
|
|
16
|
+
export async function dispatchDeferredTaskWait(
|
|
17
|
+
scheduleId: string,
|
|
18
|
+
reason: "ceiling" | "task.completed" | "task.failed",
|
|
19
|
+
extraTags: string[] = [],
|
|
20
|
+
): Promise<{ task?: AgentTask } | undefined> {
|
|
21
|
+
const client = getDbClient();
|
|
22
|
+
// Keep ordinary schedule dispatch off the write-lock path.
|
|
23
|
+
if (
|
|
24
|
+
!(await client.get<TaskWait>("SELECT * FROM deferred_task_waits WHERE scheduleId = ?", [
|
|
25
|
+
scheduleId,
|
|
26
|
+
]))
|
|
27
|
+
)
|
|
28
|
+
return undefined;
|
|
29
|
+
return client.transaction(async () => {
|
|
30
|
+
const wait = await client.get<TaskWait>(
|
|
31
|
+
"SELECT * FROM deferred_task_waits WHERE scheduleId = ?",
|
|
32
|
+
[scheduleId],
|
|
33
|
+
);
|
|
34
|
+
if (!wait || wait.status !== "pending") return {};
|
|
35
|
+
if (reason !== "ceiling" && wait.eventName !== "settled" && wait.eventName !== reason)
|
|
36
|
+
return {};
|
|
37
|
+
let schedule = await getScheduledTaskById(scheduleId);
|
|
38
|
+
if (!schedule?.enabled) return {};
|
|
39
|
+
const now = new Date().toISOString();
|
|
40
|
+
const claimed = await client.run(
|
|
41
|
+
"UPDATE deferred_task_waits SET status = 'fired', firedBy = ?, resolvedAt = ?, updated_at = ? WHERE scheduleId = ? AND status = 'pending'",
|
|
42
|
+
[reason, now, now, scheduleId],
|
|
43
|
+
);
|
|
44
|
+
if (!claimed.changes) return {};
|
|
45
|
+
if (schedule.createdBy && !(await getUserById(schedule.createdBy))) {
|
|
46
|
+
schedule = { ...schedule, createdBy: undefined };
|
|
47
|
+
}
|
|
48
|
+
if (!schedule.taskTemplate) throw new Error(`Schedule "${schedule.name}" has no taskTemplate`);
|
|
49
|
+
const cause = reason === "ceiling" ? "ceiling expired" : `${reason} for task ${wait.taskId}`;
|
|
50
|
+
const task = await createStandaloneScheduleTask(
|
|
51
|
+
{
|
|
52
|
+
...schedule,
|
|
53
|
+
taskTemplate: `${schedule.taskTemplate}\n\nWake-up cause: ${cause}.`,
|
|
54
|
+
},
|
|
55
|
+
extraTags,
|
|
56
|
+
);
|
|
57
|
+
await client.run("UPDATE deferred_task_waits SET childTaskId = ? WHERE scheduleId = ?", [
|
|
58
|
+
task.id,
|
|
59
|
+
scheduleId,
|
|
60
|
+
]);
|
|
61
|
+
await updateScheduledTask(scheduleId, {
|
|
62
|
+
enabled: false,
|
|
63
|
+
nextRunAt: null,
|
|
64
|
+
lastRunAt: now,
|
|
65
|
+
lastUpdatedAt: now,
|
|
66
|
+
consecutiveErrors: 0,
|
|
67
|
+
lastErrorAt: null,
|
|
68
|
+
lastErrorMessage: null,
|
|
69
|
+
});
|
|
70
|
+
return { task };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Reconcile durable producer state, including terminal transitions missed by this process. */
|
|
75
|
+
export async function reconcileDeferredTaskWaits(taskId?: string): Promise<void> {
|
|
76
|
+
const rows = await getDbClient().query<TaskWait & { taskStatus: "completed" | "failed" }>(
|
|
77
|
+
`SELECT w.*, t.status AS taskStatus FROM deferred_task_waits w
|
|
78
|
+
JOIN agent_tasks t ON t.id = w.taskId
|
|
79
|
+
JOIN scheduled_tasks s ON s.id = w.scheduleId
|
|
80
|
+
WHERE w.status = 'pending' AND s.enabled = 1 AND t.status IN ('completed', 'failed')
|
|
81
|
+
AND NOT EXISTS (
|
|
82
|
+
SELECT 1 FROM scheduled_tasks d
|
|
83
|
+
WHERE d.parentTaskId = t.id AND d.taskType = 'deferred' AND d.enabled = 1
|
|
84
|
+
)
|
|
85
|
+
AND (w.eventName = 'settled' OR w.eventName = 'task.' || t.status)
|
|
86
|
+
${taskId ? "AND w.taskId = ?" : ""}`,
|
|
87
|
+
taskId ? [taskId] : [],
|
|
88
|
+
);
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
try {
|
|
91
|
+
await dispatchDeferredTaskWait(row.scheduleId, `task.${row.taskStatus}`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// A broken wait must not stop other event wakes or the ceiling poller.
|
|
94
|
+
console.error(`[Scheduler] Deferred task wake failed for ${row.scheduleId}:`, err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function onTaskTerminal(payload: unknown): void {
|
|
100
|
+
if (
|
|
101
|
+
!payload ||
|
|
102
|
+
typeof payload !== "object" ||
|
|
103
|
+
!("taskId" in payload) ||
|
|
104
|
+
typeof payload.taskId !== "string"
|
|
105
|
+
)
|
|
106
|
+
return;
|
|
107
|
+
void reconcileDeferredTaskWaits(payload.taskId).catch((err) => {
|
|
108
|
+
console.error("[Scheduler] Deferred task event wake failed:", err);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Two fixed listeners read durable waits, so boot needs no per-wait in-memory registry. */
|
|
113
|
+
export async function initDeferredTaskWaits(): Promise<void> {
|
|
114
|
+
stopDeferredTaskWaits();
|
|
115
|
+
workflowEventBus.on("task.completed", onTaskTerminal);
|
|
116
|
+
workflowEventBus.on("task.failed", onTaskTerminal);
|
|
117
|
+
await reconcileDeferredTaskWaits();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function stopDeferredTaskWaits(): void {
|
|
121
|
+
workflowEventBus.off("task.completed", onTaskTerminal);
|
|
122
|
+
workflowEventBus.off("task.failed", onTaskTerminal);
|
|
123
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getDbClient } from "@/be/db";
|
|
2
|
+
import { scheduleContextKey } from "@/tasks/context-key";
|
|
3
|
+
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
4
|
+
import type { AgentTask, ScheduledTask } from "@/types";
|
|
5
|
+
|
|
6
|
+
export async function createStandaloneScheduleTask(
|
|
7
|
+
schedule: ScheduledTask,
|
|
8
|
+
extraTags: string[] = [],
|
|
9
|
+
): Promise<AgentTask> {
|
|
10
|
+
if (!schedule.taskTemplate) {
|
|
11
|
+
throw new Error(`Schedule "${schedule.name}" has no taskTemplate (targetType=agent-task)`);
|
|
12
|
+
}
|
|
13
|
+
const task = await createTaskWithSiblingAwareness(schedule.taskTemplate, {
|
|
14
|
+
key: schedule.key,
|
|
15
|
+
creatorAgentId: schedule.createdByAgentId,
|
|
16
|
+
taskType: schedule.taskType,
|
|
17
|
+
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, ...extraTags],
|
|
18
|
+
priority: schedule.priority,
|
|
19
|
+
agentId: schedule.targetAgentId,
|
|
20
|
+
routingReason: schedule.targetAgentId ? "human_pinned" : undefined,
|
|
21
|
+
model: schedule.model,
|
|
22
|
+
modelTier: schedule.modelTier,
|
|
23
|
+
scheduleId: schedule.id,
|
|
24
|
+
source: "schedule",
|
|
25
|
+
requestedByUserId: schedule.createdBy,
|
|
26
|
+
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
27
|
+
// Set only by `defer-task`. An explicit parent wins over the sibling-awareness
|
|
28
|
+
// auto-wiring (see withSiblingAwareness in src/tasks/sibling-awareness.ts), so
|
|
29
|
+
// the wake-up run continues the deferred task rather than a random sibling.
|
|
30
|
+
parentTaskId: schedule.parentTaskId,
|
|
31
|
+
});
|
|
32
|
+
// Both timer and event dispatch wrap this helper in a transaction. Transfer
|
|
33
|
+
// pending watchers before the resume task becomes visible or its schedule is
|
|
34
|
+
// disabled; their own schedules retain the original ceilings.
|
|
35
|
+
if (schedule.taskType === "deferred" && schedule.parentTaskId) {
|
|
36
|
+
await getDbClient().run(
|
|
37
|
+
"UPDATE deferred_task_waits SET taskId = ?, updated_at = ? WHERE status = 'pending' AND taskId = ?",
|
|
38
|
+
[task.id, new Date().toISOString(), schedule.parentTaskId],
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return task;
|
|
42
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensure } from "@desplega.ai/business-use";
|
|
2
2
|
import { CronExpressionParser } from "cron-parser";
|
|
3
|
+
import { notifyAutomationPreflightFailure } from "@/automation-preflight-alert";
|
|
3
4
|
import {
|
|
4
5
|
getAutomationSetupStates,
|
|
5
6
|
isNeedsSetupFailure,
|
|
@@ -24,8 +25,6 @@ import {
|
|
|
24
25
|
import { buildScriptCredentialBindings } from "@/be/script-credential-broker";
|
|
25
26
|
import { getScript } from "@/be/scripts/db";
|
|
26
27
|
import { runScript } from "@/scripts-runtime/loader";
|
|
27
|
-
import { scheduleContextKey } from "@/tasks/context-key";
|
|
28
|
-
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
29
28
|
import { telemetry } from "@/telemetry";
|
|
30
29
|
import type { AgentTask, ScheduledTask } from "@/types";
|
|
31
30
|
import { getExecutorRegistry as getWorkflowExecutorRegistry } from "@/workflows";
|
|
@@ -33,6 +32,16 @@ import { startWorkflowExecution } from "@/workflows/engine";
|
|
|
33
32
|
import type { ExecutorRegistry } from "@/workflows/executors/registry";
|
|
34
33
|
import { handleScheduleTrigger } from "@/workflows/triggers";
|
|
35
34
|
|
|
35
|
+
import {
|
|
36
|
+
dispatchDeferredTaskWait,
|
|
37
|
+
initDeferredTaskWaits,
|
|
38
|
+
reconcileDeferredTaskWaits,
|
|
39
|
+
stopDeferredTaskWaits,
|
|
40
|
+
} from "./deferred-task-waits";
|
|
41
|
+
import { createStandaloneScheduleTask } from "./schedule-task";
|
|
42
|
+
|
|
43
|
+
export { createStandaloneScheduleTask } from "./schedule-task";
|
|
44
|
+
|
|
36
45
|
let schedulerInterval: ReturnType<typeof setInterval> | null = null;
|
|
37
46
|
let isProcessing = false;
|
|
38
47
|
let executorRegistry: ExecutorRegistry | null = null;
|
|
@@ -53,33 +62,6 @@ function resolveExecutorRegistry(): ExecutorRegistry | null {
|
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
export async function createStandaloneScheduleTask(
|
|
57
|
-
schedule: ScheduledTask,
|
|
58
|
-
extraTags: string[] = [],
|
|
59
|
-
): Promise<AgentTask> {
|
|
60
|
-
if (!schedule.taskTemplate) {
|
|
61
|
-
throw new Error(`Schedule "${schedule.name}" has no taskTemplate (targetType=agent-task)`);
|
|
62
|
-
}
|
|
63
|
-
return await createTaskWithSiblingAwareness(schedule.taskTemplate, {
|
|
64
|
-
key: schedule.key,
|
|
65
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
66
|
-
taskType: schedule.taskType,
|
|
67
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, ...extraTags],
|
|
68
|
-
priority: schedule.priority,
|
|
69
|
-
agentId: schedule.targetAgentId,
|
|
70
|
-
model: schedule.model,
|
|
71
|
-
modelTier: schedule.modelTier,
|
|
72
|
-
scheduleId: schedule.id,
|
|
73
|
-
source: "schedule",
|
|
74
|
-
requestedByUserId: schedule.createdBy,
|
|
75
|
-
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
76
|
-
// Set only by `defer-task`. An explicit parent wins over the sibling-awareness
|
|
77
|
-
// auto-wiring (see withSiblingAwareness in src/tasks/sibling-awareness.ts), so
|
|
78
|
-
// the wake-up run continues the deferred task rather than a random sibling.
|
|
79
|
-
parentTaskId: schedule.parentTaskId,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
65
|
/**
|
|
84
66
|
* Execute a schedule's `targetType='script'` target directly via the
|
|
85
67
|
* scripts-runtime — no agent/LLM in the loop. Reuses the same `runScript()`
|
|
@@ -151,12 +133,16 @@ export async function dispatchScheduleTarget(
|
|
|
151
133
|
schedule: ScheduledTask,
|
|
152
134
|
extraTags: string[] = [],
|
|
153
135
|
): Promise<DispatchScheduleResult> {
|
|
136
|
+
const deferred = await dispatchDeferredTaskWait(schedule.id, "ceiling", extraTags);
|
|
137
|
+
if (deferred) return { triggeredWorkflows: false, ...deferred };
|
|
138
|
+
|
|
154
139
|
const preflight = preflightAutomation(
|
|
155
140
|
schedulePreflightInput(schedule),
|
|
156
141
|
await getAutomationSetupStates(),
|
|
157
142
|
);
|
|
158
143
|
if (preflight.state === "needs_setup") {
|
|
159
|
-
await recordSchedulePreflightFailure(schedule.id, preflight.failureReason!);
|
|
144
|
+
const recorded = await recordSchedulePreflightFailure(schedule.id, preflight.failureReason!);
|
|
145
|
+
if (recorded) await notifyAutomationPreflightFailure(preflight);
|
|
160
146
|
throw new AutomationNeedsSetupError(preflight.failureReason!);
|
|
161
147
|
}
|
|
162
148
|
// Params are safe and integrations are ready. Only now render values into
|
|
@@ -489,7 +475,10 @@ export function startScheduler(
|
|
|
489
475
|
console.log(`[Scheduler] Starting with ${intervalMs}ms polling interval`);
|
|
490
476
|
|
|
491
477
|
// Recover missed schedules from downtime, then run normal processing
|
|
492
|
-
void
|
|
478
|
+
void initDeferredTaskWaits()
|
|
479
|
+
.then(() => recoverMissedSchedules())
|
|
480
|
+
.then(() => processSchedules())
|
|
481
|
+
.catch((err) => console.error("[Scheduler] Recovery failed:", err));
|
|
493
482
|
|
|
494
483
|
schedulerInterval = setInterval(async () => {
|
|
495
484
|
await processSchedules();
|
|
@@ -523,6 +512,7 @@ async function processSchedules(): Promise<void> {
|
|
|
523
512
|
isProcessing = true;
|
|
524
513
|
|
|
525
514
|
try {
|
|
515
|
+
await reconcileDeferredTaskWaits();
|
|
526
516
|
const dueSchedules = await getDueScheduledTasks();
|
|
527
517
|
|
|
528
518
|
for (const schedule of dueSchedules) {
|
|
@@ -541,6 +531,7 @@ async function processSchedules(): Promise<void> {
|
|
|
541
531
|
* Stop the scheduler polling loop.
|
|
542
532
|
*/
|
|
543
533
|
export function stopScheduler(): void {
|
|
534
|
+
stopDeferredTaskWaits();
|
|
544
535
|
if (schedulerInterval) {
|
|
545
536
|
clearInterval(schedulerInterval);
|
|
546
537
|
schedulerInterval = null;
|
|
@@ -32,6 +32,8 @@ type AgentTaskConfig = {
|
|
|
32
32
|
template?: string;
|
|
33
33
|
task?: string;
|
|
34
34
|
agentId?: string;
|
|
35
|
+
routingReason?: "skill" | "continuity" | "overflow" | "human_pinned" | "reroute_fault";
|
|
36
|
+
routingNote?: string;
|
|
35
37
|
tags?: string[];
|
|
36
38
|
priority?: number;
|
|
37
39
|
offerMode?: boolean;
|
|
@@ -28,6 +28,41 @@ declare module "swarm-sdk" {
|
|
|
28
28
|
export type ScriptFsMode = "none" | "workspace-rw";
|
|
29
29
|
export type ScriptApiRawOptions = { raw: true };
|
|
30
30
|
export type ScriptApiDefaultOptions = { raw?: false };
|
|
31
|
+
export type RoutingReason =
|
|
32
|
+
| "skill"
|
|
33
|
+
| "continuity"
|
|
34
|
+
| "overflow"
|
|
35
|
+
| "human_pinned"
|
|
36
|
+
| "reroute_fault";
|
|
37
|
+
export type TaskSendArgs = Record<string, unknown> & {
|
|
38
|
+
task: string;
|
|
39
|
+
routingNote?: string;
|
|
40
|
+
} & (
|
|
41
|
+
| { agentId: string; routingReason: RoutingReason }
|
|
42
|
+
| { agentId?: never; routingReason?: never }
|
|
43
|
+
);
|
|
44
|
+
export type AgentTaskStepConfig = {
|
|
45
|
+
template?: string;
|
|
46
|
+
task?: string;
|
|
47
|
+
agentId?: string;
|
|
48
|
+
routingReason?: RoutingReason;
|
|
49
|
+
routingNote?: string;
|
|
50
|
+
tags?: string[];
|
|
51
|
+
priority?: number;
|
|
52
|
+
offerMode?: boolean;
|
|
53
|
+
dir?: string;
|
|
54
|
+
vcsRepo?: string;
|
|
55
|
+
model?: string;
|
|
56
|
+
parentTaskId?: string;
|
|
57
|
+
requestedByUserId?: string;
|
|
58
|
+
outputSchema?: Record<string, unknown>;
|
|
59
|
+
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
60
|
+
waitForCompletion?: boolean;
|
|
61
|
+
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
64
|
+
failOnTaskFailure?: boolean;
|
|
65
|
+
};
|
|
31
66
|
|
|
32
67
|
export interface ScriptApiRawResult {
|
|
33
68
|
ok: boolean;
|
|
@@ -147,6 +182,7 @@ declare module "swarm-sdk" {
|
|
|
147
182
|
taskId: string;
|
|
148
183
|
delayMs?: number;
|
|
149
184
|
runAt?: string;
|
|
185
|
+
wakeOn?: { event: "task.completed" | "task.failed" | "settled"; taskId: string };
|
|
150
186
|
summary: string;
|
|
151
187
|
note: string;
|
|
152
188
|
checks?: string[];
|
|
@@ -373,7 +409,7 @@ declare module "swarm-sdk" {
|
|
|
373
409
|
}): Promise<unknown>;
|
|
374
410
|
|
|
375
411
|
// --- write: tasks ---
|
|
376
|
-
task_send(args:
|
|
412
|
+
task_send(args: TaskSendArgs): Promise<unknown>;
|
|
377
413
|
task_cancel(args: { taskId: string }): Promise<unknown>;
|
|
378
414
|
task_steer(args: {
|
|
379
415
|
taskId: string;
|
|
@@ -579,29 +615,7 @@ declare module "swarm-sdk" {
|
|
|
579
615
|
label: string,
|
|
580
616
|
config: { prompt: string; model?: string; schema?: Record<string, unknown> },
|
|
581
617
|
): Promise<unknown>;
|
|
582
|
-
agentTask(
|
|
583
|
-
label: string,
|
|
584
|
-
config: {
|
|
585
|
-
template?: string;
|
|
586
|
-
task?: string;
|
|
587
|
-
agentId?: string;
|
|
588
|
-
tags?: string[];
|
|
589
|
-
priority?: number;
|
|
590
|
-
offerMode?: boolean;
|
|
591
|
-
dir?: string;
|
|
592
|
-
vcsRepo?: string;
|
|
593
|
-
model?: string;
|
|
594
|
-
parentTaskId?: string;
|
|
595
|
-
requestedByUserId?: string;
|
|
596
|
-
outputSchema?: Record<string, unknown>;
|
|
597
|
-
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
598
|
-
waitForCompletion?: boolean;
|
|
599
|
-
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
600
|
-
timeoutMs?: number;
|
|
601
|
-
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
602
|
-
failOnTaskFailure?: boolean;
|
|
603
|
-
},
|
|
604
|
-
): Promise<unknown>;
|
|
618
|
+
agentTask(label: string, config: AgentTaskStepConfig): Promise<unknown>;
|
|
605
619
|
swarmScript(
|
|
606
620
|
label: string,
|
|
607
621
|
config: {
|
|
@@ -10,6 +10,41 @@ declare module "swarm-sdk" {
|
|
|
10
10
|
export type ScriptFsMode = "none" | "workspace-rw";
|
|
11
11
|
export type ScriptApiRawOptions = { raw: true };
|
|
12
12
|
export type ScriptApiDefaultOptions = { raw?: false };
|
|
13
|
+
export type RoutingReason =
|
|
14
|
+
| "skill"
|
|
15
|
+
| "continuity"
|
|
16
|
+
| "overflow"
|
|
17
|
+
| "human_pinned"
|
|
18
|
+
| "reroute_fault";
|
|
19
|
+
export type TaskSendArgs = Record<string, unknown> & {
|
|
20
|
+
task: string;
|
|
21
|
+
routingNote?: string;
|
|
22
|
+
} & (
|
|
23
|
+
| { agentId: string; routingReason: RoutingReason }
|
|
24
|
+
| { agentId?: never; routingReason?: never }
|
|
25
|
+
);
|
|
26
|
+
export type AgentTaskStepConfig = {
|
|
27
|
+
template?: string;
|
|
28
|
+
task?: string;
|
|
29
|
+
agentId?: string;
|
|
30
|
+
routingReason?: RoutingReason;
|
|
31
|
+
routingNote?: string;
|
|
32
|
+
tags?: string[];
|
|
33
|
+
priority?: number;
|
|
34
|
+
offerMode?: boolean;
|
|
35
|
+
dir?: string;
|
|
36
|
+
vcsRepo?: string;
|
|
37
|
+
model?: string;
|
|
38
|
+
parentTaskId?: string;
|
|
39
|
+
requestedByUserId?: string;
|
|
40
|
+
outputSchema?: Record<string, unknown>;
|
|
41
|
+
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
42
|
+
waitForCompletion?: boolean;
|
|
43
|
+
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
46
|
+
failOnTaskFailure?: boolean;
|
|
47
|
+
};
|
|
13
48
|
|
|
14
49
|
export interface ScriptApiRawResult {
|
|
15
50
|
ok: boolean;
|
|
@@ -129,6 +164,7 @@ declare module "swarm-sdk" {
|
|
|
129
164
|
taskId: string;
|
|
130
165
|
delayMs?: number;
|
|
131
166
|
runAt?: string;
|
|
167
|
+
wakeOn?: { event: "task.completed" | "task.failed" | "settled"; taskId: string };
|
|
132
168
|
summary: string;
|
|
133
169
|
note: string;
|
|
134
170
|
checks?: string[];
|
|
@@ -355,7 +391,7 @@ declare module "swarm-sdk" {
|
|
|
355
391
|
}): Promise<unknown>;
|
|
356
392
|
|
|
357
393
|
// --- write: tasks ---
|
|
358
|
-
task_send(args:
|
|
394
|
+
task_send(args: TaskSendArgs): Promise<unknown>;
|
|
359
395
|
task_cancel(args: { taskId: string }): Promise<unknown>;
|
|
360
396
|
task_steer(args: {
|
|
361
397
|
taskId: string;
|
|
@@ -561,29 +597,7 @@ declare module "swarm-sdk" {
|
|
|
561
597
|
label: string,
|
|
562
598
|
config: { prompt: string; model?: string; schema?: Record<string, unknown> },
|
|
563
599
|
): Promise<unknown>;
|
|
564
|
-
agentTask(
|
|
565
|
-
label: string,
|
|
566
|
-
config: {
|
|
567
|
-
template?: string;
|
|
568
|
-
task?: string;
|
|
569
|
-
agentId?: string;
|
|
570
|
-
tags?: string[];
|
|
571
|
-
priority?: number;
|
|
572
|
-
offerMode?: boolean;
|
|
573
|
-
dir?: string;
|
|
574
|
-
vcsRepo?: string;
|
|
575
|
-
model?: string;
|
|
576
|
-
parentTaskId?: string;
|
|
577
|
-
requestedByUserId?: string;
|
|
578
|
-
outputSchema?: Record<string, unknown>;
|
|
579
|
-
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
580
|
-
waitForCompletion?: boolean;
|
|
581
|
-
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
582
|
-
timeoutMs?: number;
|
|
583
|
-
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
584
|
-
failOnTaskFailure?: boolean;
|
|
585
|
-
},
|
|
586
|
-
): Promise<unknown>;
|
|
600
|
+
agentTask(label: string, config: AgentTaskStepConfig): Promise<unknown>;
|
|
587
601
|
swarmScript(
|
|
588
602
|
label: string,
|
|
589
603
|
config: {
|
package/src/server.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { seedPricingFromModelsDev } from "./be/seed-pricing";
|
|
|
7
7
|
import { isSteeringEnabled } from "./be/steering";
|
|
8
8
|
import { registerGithubTaskReactions } from "./github/task-reactions";
|
|
9
9
|
import { loadGlobalConfigsIntoEnv } from "./http/core";
|
|
10
|
+
import { resolveTemplate } from "./prompts/resolver";
|
|
10
11
|
import { isRbacEnabled } from "./rbac";
|
|
11
12
|
import { registerAcceptSteerTool } from "./tools/accept-steer";
|
|
12
13
|
import { registerAppDiffTool } from "./tools/app-diff";
|
|
@@ -163,6 +164,7 @@ import { registerUnregisterServiceTool } from "./tools/unregister-service";
|
|
|
163
164
|
// Profiles capability
|
|
164
165
|
import { registerUpdateProfileTool } from "./tools/update-profile";
|
|
165
166
|
import { registerUpdateServiceStatusTool } from "./tools/update-service-status";
|
|
167
|
+
import { setPreloadedTools } from "./tools/utils";
|
|
166
168
|
import {
|
|
167
169
|
registerReplyWhatsappMessageTool,
|
|
168
170
|
registerSendWhatsappMessageTool,
|
|
@@ -274,7 +276,9 @@ export function isScriptsOnlyMcp(): boolean {
|
|
|
274
276
|
return resolveScriptsOnlyMode({ env: process.env.SCRIPTS_ONLY_MCP });
|
|
275
277
|
}
|
|
276
278
|
|
|
277
|
-
export async function createServer(
|
|
279
|
+
export async function createServer(
|
|
280
|
+
opts: { scriptsOnly?: boolean; fullSurface?: boolean; preloadedTools?: readonly string[] } = {},
|
|
281
|
+
) {
|
|
278
282
|
// Reload env
|
|
279
283
|
await loadGlobalConfigsIntoEnv(true);
|
|
280
284
|
|
|
@@ -316,12 +320,17 @@ export async function createServer(opts: { scriptsOnly?: boolean; fullSurface?:
|
|
|
316
320
|
description: pkg.description,
|
|
317
321
|
},
|
|
318
322
|
{
|
|
323
|
+
...(opts.preloadedTools?.length
|
|
324
|
+
? { instructions: resolveTemplate("system.agent.tool_preload", {}).text }
|
|
325
|
+
: {}),
|
|
319
326
|
capabilities: {
|
|
320
327
|
logging: {},
|
|
321
328
|
},
|
|
322
329
|
},
|
|
323
330
|
);
|
|
324
331
|
|
|
332
|
+
if (opts.preloadedTools?.length) setPreloadedTools(server, opts.preloadedTools);
|
|
333
|
+
|
|
325
334
|
// Scripts-only surface (experimental code-mode): register just the script
|
|
326
335
|
// catalog tools and stop. script-connections / script-apis stay out — they
|
|
327
336
|
// are lead-only security admin and excluded from the scripts SDK too.
|
package/src/slack/actions.ts
CHANGED
|
@@ -84,6 +84,7 @@ export function registerActionHandlers(app: App): void {
|
|
|
84
84
|
});
|
|
85
85
|
const followUpTask = await createTaskWithSiblingAwareness(followUpText, {
|
|
86
86
|
agentId: lead?.id,
|
|
87
|
+
routingReason: lead ? "skill" : undefined,
|
|
87
88
|
source: "slack",
|
|
88
89
|
parentTaskId: taskId,
|
|
89
90
|
slackChannelId: originalTask.slackChannelId,
|
package/src/slack/assistant.ts
CHANGED
|
@@ -166,6 +166,7 @@ export function createAssistant(): Assistant {
|
|
|
166
166
|
renderedMessageText,
|
|
167
167
|
{
|
|
168
168
|
agentId: workingAgent.id,
|
|
169
|
+
routingReason: "continuity",
|
|
169
170
|
source: "slack",
|
|
170
171
|
slackChannelId: channelId,
|
|
171
172
|
slackThreadTs: threadTs,
|
|
@@ -247,6 +248,7 @@ export function createAssistant(): Assistant {
|
|
|
247
248
|
renderedMessageText + channelContext,
|
|
248
249
|
{
|
|
249
250
|
agentId: lead.id,
|
|
251
|
+
routingReason: "skill",
|
|
250
252
|
source: "slack",
|
|
251
253
|
slackChannelId: channelId,
|
|
252
254
|
slackThreadTs: threadTs,
|