@desplega.ai/agent-swarm 1.152.2 → 1.154.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 -2
- package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
- package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
- package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
- package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
- package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
- package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
- package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
- package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
- package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
- package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
- package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
- package/dist/cli-0x5qzvkg.js +4279 -0
- package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
- package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
- package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
- package/dist/cli-3bkz2p7j.js +2828 -0
- package/dist/cli-3er6gcsn.js +1849 -0
- package/dist/cli-4k7jazkq.js +3339 -0
- package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
- package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
- package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
- package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
- package/dist/cli-8sea27q4.js +14075 -0
- package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
- package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
- package/dist/cli-95jnpnqg.js +230 -0
- package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
- package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
- package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
- package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
- package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
- package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
- package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
- package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
- package/dist/cli-h38x0qhg.js +6912 -0
- package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
- package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
- package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
- package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
- package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
- package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
- package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
- package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
- package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
- package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
- package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
- package/dist/cli.js +28 -35
- package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
- package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
- package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
- package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
- package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
- package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
- package/dist/dsh-adapter-gsb2qqf9.js +19 -0
- package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
- package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
- package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
- package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
- package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
- package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
- package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
- package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
- package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
- package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
- package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
- package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
- package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
- package/dist/index-w097eg0x.js +5 -0
- package/dist/jiti-loader-b4837mtm.js +34 -0
- package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
- package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
- package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
- package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
- package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
- package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
- package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
- package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
- package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
- package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
- package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
- package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
- package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
- package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
- package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
- package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
- package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
- package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
- package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
- package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
- package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
- package/dist/virtual-modules-2q5b2pk4.js +54 -0
- package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
- package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
- package/openapi.json +109 -16
- package/package.json +7 -6
- package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
- package/src/be/automation-preflight.ts +6 -4
- package/src/be/db/agents.ts +75 -2
- package/src/be/db/tasks/read.ts +2 -0
- package/src/be/db/tasks/write.ts +7 -1
- package/src/be/db.ts +140 -18
- package/src/be/extensions/db.ts +35 -4
- package/src/be/memory/raters/llm.ts +2 -6
- package/src/be/memory/raters/noop.ts +2 -3
- package/src/be/memory/raters/registry.ts +4 -10
- package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +213 -0
- package/src/be/rbac-audit.ts +2 -0
- package/src/be/seed/agent-fs-provision.ts +23 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/onboard/compose-generator.ts +16 -1
- package/src/commands/onboard/env-generator.ts +14 -1
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/integration-slack.tsx +56 -16
- package/src/commands/onboard/steps/review.tsx +5 -0
- package/src/commands/onboard/types.ts +6 -0
- package/src/commands/onboard.tsx +5 -1
- package/src/commands/provider-credentials.ts +14 -2
- package/src/commands/runner.ts +18 -2
- package/src/extensions/identity.ts +15 -7
- package/src/extensions/lifecycle.ts +15 -21
- package/src/hooks/codex-hook.ts +7 -1
- package/src/http/agents.ts +12 -4
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +5 -3
- package/src/http/extensions.ts +108 -34
- package/src/http/poll.ts +7 -0
- package/src/http/schedules.ts +10 -0
- package/src/http/script-runs.ts +35 -26
- package/src/http/status.ts +24 -11
- package/src/http/tasks.ts +6 -1
- package/src/prompts/base-prompt.ts +15 -7
- package/src/prompts/memories.ts +5 -7
- package/src/prompts/session-templates.ts +3 -2
- package/src/prompts/steering-delivery.ts +2 -0
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-adapter.ts +3 -6
- package/src/providers/codex-models.ts +15 -1
- package/src/providers/dsh-adapter.ts +248 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/modelsdev-reasoning.json +50 -0
- package/src/providers/pi-mono-extension.ts +2 -5
- package/src/rbac/legacy-policy.ts +15 -1
- package/src/rbac/types.ts +2 -0
- package/src/server.ts +7 -1
- package/src/slack/ack.ts +43 -8
- package/src/slack/app.ts +32 -10
- package/src/slack/config.ts +29 -0
- package/src/slack/handlers.ts +7 -1
- package/src/slack/render-v2.ts +186 -17
- package/src/slack/responses.ts +1 -3
- package/src/slack/task-output.ts +66 -8
- package/src/slack/thread-buffer.ts +27 -3
- package/src/telemetry.ts +2 -3
- package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
- package/src/tests/agents-harness-provider.test.ts +19 -0
- package/src/tests/agents-list-model-display.test.ts +60 -1
- package/src/tests/base-prompt.test.ts +25 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-gpt6-release.test.ts +97 -0
- package/src/tests/codex-steering-hook.test.ts +2 -2
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +9 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/defer-task.test.ts +115 -16
- package/src/tests/dsh-adapter.test.ts +276 -0
- package/src/tests/e2e-nightly-report.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +2 -0
- package/src/tests/env-flag.test.ts +6 -0
- package/src/tests/extension-agent-visibility.test.ts +474 -0
- package/src/tests/extensions-db.test.ts +41 -0
- package/src/tests/extensions-http.test.ts +148 -4
- package/src/tests/extensions-lifecycle.test.ts +21 -1
- package/src/tests/extensions-mcp-tools.test.ts +9 -5
- package/src/tests/fetch-resolved-env.test.ts +43 -0
- package/src/tests/internal-ai/complete-structured.test.ts +32 -9
- package/src/tests/internal-ai/credentials.test.ts +4 -4
- package/src/tests/internal-ai/models.test.ts +20 -0
- package/src/tests/memory-rate-tool.test.ts +7 -10
- package/src/tests/memory-rater-defaults.test.ts +23 -0
- package/src/tests/memory-rater-e2e.test.ts +2 -3
- package/src/tests/migration-046-budgets.test.ts +2 -0
- package/src/tests/model-control.test.ts +2 -2
- package/src/tests/onboard-compose.test.ts +14 -0
- package/src/tests/onboard-env.test.ts +17 -0
- package/src/tests/onboard-manifest.test.ts +12 -0
- package/src/tests/opus-5-5-registry.test.ts +48 -0
- package/src/tests/pricing-refresh.test.ts +1 -1
- package/src/tests/pricing-routes.test.ts +15 -3
- package/src/tests/provider-registration.test.ts +5 -2
- package/src/tests/rbac-admission-e2e.test.ts +54 -0
- package/src/tests/rbac-e2e-helpers.ts +2 -0
- package/src/tests/rbac-engine.test.ts +32 -0
- package/src/tests/rbac-wire-e2e.test.ts +33 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/reload-config.test.ts +33 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/slack-config.test.ts +136 -0
- package/src/tests/slack-mode-lifecycle.test.ts +69 -0
- package/src/tests/slack-render-v2.test.ts +446 -16
- package/src/tests/slack-steering.test.ts +69 -1
- package/src/tests/slack-task-output.test.ts +33 -12
- package/src/tests/status.test.ts +22 -2
- package/src/tests/steering-core.test.ts +35 -0
- package/src/tests/steering-delivery.test.ts +39 -0
- package/src/tests/steering-transport.test.ts +2 -2
- package/src/tests/task-tool-preload-mcp.test.ts +3 -1
- package/src/tests/telemetry-init.test.ts +18 -0
- package/src/tests/workflow-live-recovery.test.ts +126 -0
- package/src/tests/workflow-llm.test.ts +3 -3
- package/src/tools/defer-task.ts +64 -26
- package/src/tools/extension-common.ts +4 -0
- package/src/tools/extension-install.ts +1 -1
- package/src/tools/extension-list.ts +4 -3
- package/src/tools/join-swarm.ts +1 -1
- package/src/tools/schedules/create-schedule.ts +11 -1
- package/src/tools/schedules/patch-schedule.ts +5 -0
- package/src/tools/schedules/update-schedule.ts +5 -0
- package/src/tools/send-task.ts +6 -0
- package/src/tools/utils.ts +1 -1
- package/src/types.ts +30 -8
- package/src/utils/context-window.ts +1 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/internal-ai/complete-structured.ts +8 -0
- package/src/utils/internal-ai/models.ts +5 -5
- package/src/utils/memory-raters.ts +3 -0
- package/src/workflows/engine.ts +28 -0
- package/src/workflows/recovery.ts +7 -4
- package/templates/schedules/daily-status-report/content.md +25 -12
- package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
- package/templates/skills/swarm-extensions/content.md +9 -5
- package/dist/cli-4wppfkcm.js +0 -16384
- package/dist/cli-55qrx3q8.js +0 -6041
- package/dist/cli-cg4vnyh4.js +0 -305
- package/dist/cli-jyetr64j.js +0 -52
- package/dist/cli-pp9ax9pa.js +0 -520
- package/dist/cli-qa66qaf0.js +0 -6076
- package/dist/cli-qghpvca2.js +0 -590
- package/dist/cli-rembwgt8.js +0 -861
- package/dist/cli-tcvj7j2r.js +0 -1971
- package/dist/cli-vfcth11k.js +0 -36
- package/dist/cli-w1d1bgkk.js +0 -1422
- package/dist/cli-y6x0gqtg.js +0 -5977
- package/dist/es5-tj3tn4jj.js +0 -844
- package/dist/index-1kq5hw2p.js +0 -467
- package/dist/index-1p8wdstt.js +0 -7
- package/dist/index-8fdjzb2f.js +0 -100
- package/dist/index-bxmwzbgw.js +0 -365
- package/dist/index-nzerxd5z.js +0 -7
- package/dist/index-pyymnzat.js +0 -1168
- package/dist/index-qv6atbab.js +0 -619
- package/dist/index-rja32qa7.js +0 -801
- package/dist/index-tzg5srfp.js +0 -211
- package/dist/index-vhncw983.js +0 -90
- package/dist/index-xk036zsk.js +0 -859
- package/dist/index-zyazbzcb.js +0 -6
- package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
package/src/tools/defer-task.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { getTaskOutputValidationError } from "@/tasks/terminal-result-guard";
|
|
|
18
18
|
import { assertOwnsTask, ownerCtx } from "@/tools/task-tool-ctx";
|
|
19
19
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
20
20
|
import { isTerminalTaskStatus } from "@/types";
|
|
21
|
-
import { getAppUrl } from "@/utils/constants";
|
|
22
21
|
|
|
23
22
|
/** Thrown inside the transaction to abort and roll back the schedule INSERT. */
|
|
24
23
|
class DeferAbortedError extends Error {}
|
|
@@ -55,7 +54,7 @@ function partsInTz(date: Date, tz: string) {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
/**
|
|
58
|
-
* "today"/"tomorrow" or "MM-dd" plus "HH:MM
|
|
57
|
+
* "today"/"tomorrow" or "MM-dd" plus "HH:MM", both computed in `tz`.
|
|
59
58
|
* Falls back to UTC (parts still computed correctly) if `tz` is not a valid
|
|
60
59
|
* IANA zone — the caller decides whether to label the fallback.
|
|
61
60
|
*/
|
|
@@ -71,7 +70,9 @@ function formatDeferralDateTime(
|
|
|
71
70
|
zone = "UTC";
|
|
72
71
|
}
|
|
73
72
|
const t = partsInTz(target, zone);
|
|
74
|
-
|
|
73
|
+
// Minute precision: this string is read by a human deciding whether to wait,
|
|
74
|
+
// and the wake-up is scheduler-polled anyway, so seconds were false precision.
|
|
75
|
+
const time = `${t.hour}:${t.minute}`;
|
|
75
76
|
const n = partsInTz(now, zone);
|
|
76
77
|
if (t.year === n.year && t.month === n.month && t.day === n.day) return { date: "today", time };
|
|
77
78
|
const tm = partsInTz(new Date(now.getTime() + 86_400_000), zone);
|
|
@@ -98,38 +99,59 @@ async function resolveRequesterTimezone(
|
|
|
98
99
|
return { tz: "UTC", isFallback: true };
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
/**
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
/** "Researcher", "Researcher and Picateclas", "A, B and C". */
|
|
103
|
+
function joinNames(names: string[]): string {
|
|
104
|
+
if (names.length < 2) return names[0] ?? "";
|
|
105
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Who a `wakeOn` deferral is waiting on, in the card's words: the distinct
|
|
110
|
+
* agent names behind the watched tasks, or a bare count when none of them is
|
|
111
|
+
* assigned (an unassigned watched task has no name a human would recognise).
|
|
112
|
+
*/
|
|
113
|
+
export function renderWaitingOn(agentNames: string[], watchedCount: number): string {
|
|
114
|
+
const named = joinNames([...new Set(agentNames.filter((name) => name.trim()))]);
|
|
115
|
+
if (named) return named;
|
|
116
|
+
return `${watchedCount} task${watchedCount === 1 ? "" : "s"}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** "today at 18:38" / "tomorrow at 18:38" / "on 09-24 at 18:38". */
|
|
120
|
+
function renderWhen(nextRunAt: string, tz: string, isFallbackTz: boolean): string {
|
|
121
|
+
const { date, time } = formatDeferralDateTime(new Date(nextRunAt), new Date(), tz);
|
|
122
|
+
const timeLabel = isFallbackTz ? `${time} UTC` : time;
|
|
123
|
+
const day = date === "today" || date === "tomorrow" ? date : `on ${date}`;
|
|
124
|
+
return `${day} at ${timeLabel}`;
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
/**
|
|
109
128
|
* Human-facing deferral text for tasks without an outputSchema — this is what
|
|
110
|
-
* lands verbatim in a human's Slack thread as the task's terminal output
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
129
|
+
* lands verbatim in a human's Slack thread as the task's terminal output, and
|
|
130
|
+
* in the UI's task-detail output row.
|
|
131
|
+
*
|
|
132
|
+
* It answers one question — when does this come back — in the two shapes a
|
|
133
|
+
* deferral actually has:
|
|
134
|
+
*
|
|
135
|
+
* time-based (no wakeOn) `Checking back today at 18:38`
|
|
136
|
+
* event-based (wakeOn) `Waiting on Researcher — or today at 18:38 at the latest`
|
|
137
|
+
*
|
|
138
|
+
* A wakeOn deferral always carries a time ceiling too (`delayMs`/`runAt` is
|
|
139
|
+
* required), so the "both" wording is the only event shape reachable — which
|
|
140
|
+
* is also the honest one: the event resumes it, the ceiling guarantees it.
|
|
141
|
+
*
|
|
142
|
+
* Deliberately absent: the agent's `note` (written for the wake-up agent, not
|
|
143
|
+
* for a human — it goes to the task log and the wake-up task's template), the
|
|
144
|
+
* `checks` list, the schedule UUID and its app.agent-swarm.dev link. The glyph
|
|
145
|
+
* is added by the Slack renderer, matching how ✅/❌ outcomes are composed.
|
|
117
146
|
*/
|
|
118
147
|
function renderHumanFacingDeferral(
|
|
119
|
-
note: string,
|
|
120
148
|
nextRunAt: string,
|
|
121
|
-
scheduleId: string,
|
|
122
149
|
tz: string,
|
|
123
150
|
isFallbackTz: boolean,
|
|
151
|
+
waitingOn: string | undefined,
|
|
124
152
|
): string {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const { date, time } = formatDeferralDateTime(target, now, tz);
|
|
128
|
-
const timeLabel = isFallbackTz ? `${time} UTC` : time;
|
|
129
|
-
const shortId = scheduleId.slice(0, 8);
|
|
130
|
-
const link = `[${shortId}](${getAppUrl()}/schedules/${scheduleId})`;
|
|
131
|
-
const shortDesc = renderShortDesc(note);
|
|
132
|
-
return `Deferred until ${date} ${timeLabel} (${link}) -> ${shortDesc}`;
|
|
153
|
+
const when = renderWhen(nextRunAt, tz, isFallbackTz);
|
|
154
|
+
return waitingOn ? `Waiting on ${waitingOn} — or ${when} at the latest` : `Checking back ${when}`;
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
export const registerDeferTaskTool = (server: McpServer) => {
|
|
@@ -284,6 +306,7 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
284
306
|
|
|
285
307
|
try {
|
|
286
308
|
const committed = await getDbClient().transaction(async () => {
|
|
309
|
+
const watchedAgentNames: string[] = [];
|
|
287
310
|
for (const watchedId of watchedTaskIds) {
|
|
288
311
|
if (watchedId === taskId)
|
|
289
312
|
throw new DeferAbortedError("Cannot wake on the task being deferred.");
|
|
@@ -293,6 +316,11 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
293
316
|
throw new DeferAbortedError(
|
|
294
317
|
`Watched task ${watchedId} is already ${watched.status}; read its result instead of deferring.`,
|
|
295
318
|
);
|
|
319
|
+
// Resolved now, not at render time: the card is baked into the
|
|
320
|
+
// task's terminal output, and an agent can be renamed or removed
|
|
321
|
+
// between the deferral and whoever reads the thread later.
|
|
322
|
+
const watchedAgent = watched.agentId ? await getAgentById(watched.agentId) : null;
|
|
323
|
+
if (watchedAgent?.name) watchedAgentNames.push(watchedAgent.name);
|
|
296
324
|
}
|
|
297
325
|
const schedule = await createScheduledTask({
|
|
298
326
|
// Unique name (`getScheduledTaskByName` is a unique lookup). The UUID
|
|
@@ -308,6 +336,10 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
308
336
|
taskType: "deferred",
|
|
309
337
|
tags: ["deferred"],
|
|
310
338
|
priority: task.priority,
|
|
339
|
+
// Provenance: `nextRunAt` is cleared once this one_time schedule
|
|
340
|
+
// fires, so the requested delay is only recoverable from here.
|
|
341
|
+
requestedDelayMs: delayMs,
|
|
342
|
+
requestedRunAt: runAt,
|
|
311
343
|
// No `model`: a concrete provider model pinned now can be
|
|
312
344
|
// incompatible with the assignee/provider at wake-up, especially
|
|
313
345
|
// after a delay. Only the portable modelTier travels — mirrors
|
|
@@ -344,9 +376,15 @@ export const registerDeferTaskTool = (server: McpServer) => {
|
|
|
344
376
|
|
|
345
377
|
const terminalOutput = task.outputSchema
|
|
346
378
|
? output!
|
|
347
|
-
: renderHumanFacingDeferral(
|
|
379
|
+
: renderHumanFacingDeferral(
|
|
380
|
+
nextRunAt,
|
|
381
|
+
requesterTz,
|
|
382
|
+
isFallbackTz,
|
|
383
|
+
wakeOn ? renderWaitingOn(watchedAgentNames, watchedTaskIds.length) : undefined,
|
|
384
|
+
);
|
|
348
385
|
const completed = await completeTask(taskId, terminalOutput, {
|
|
349
386
|
addTags: ["deferred"],
|
|
387
|
+
deferredAt: new Date().toISOString(),
|
|
350
388
|
});
|
|
351
389
|
if (!completed) {
|
|
352
390
|
// Another writer terminally completed/failed/cancelled this task
|
|
@@ -10,6 +10,7 @@ const extensionListItemSchema = z.looseObject({
|
|
|
10
10
|
name: z.string().optional(),
|
|
11
11
|
version: z.number().optional(),
|
|
12
12
|
activeVersion: z.number().optional(),
|
|
13
|
+
createdByAgentId: z.string().nullable().optional(),
|
|
13
14
|
enabled: z.boolean().optional(),
|
|
14
15
|
status: z.string().optional(),
|
|
15
16
|
priority: z.number().optional(),
|
|
@@ -21,6 +22,7 @@ export const extensionToolOutputSchema = swarmToolOutputSchema({
|
|
|
21
22
|
name: z.string().optional(),
|
|
22
23
|
version: z.number().optional(),
|
|
23
24
|
activeVersion: z.number().optional(),
|
|
25
|
+
createdByAgentId: z.string().nullable().optional(),
|
|
24
26
|
enabled: z.boolean().optional(),
|
|
25
27
|
status: z.string().optional(),
|
|
26
28
|
priority: z.number().optional(),
|
|
@@ -38,6 +40,8 @@ export function coerceExtensionSummary(entry: unknown) {
|
|
|
38
40
|
const extension = isRecord(entry) ? entry : {};
|
|
39
41
|
return {
|
|
40
42
|
id: typeof extension.id === "string" ? extension.id : undefined,
|
|
43
|
+
createdByAgentId:
|
|
44
|
+
typeof extension.createdByAgentId === "string" ? extension.createdByAgentId : null,
|
|
41
45
|
name: typeof extension.name === "string" ? extension.name : undefined,
|
|
42
46
|
version: typeof extension.version === "number" ? extension.version : undefined,
|
|
43
47
|
activeVersion:
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./extension-common";
|
|
10
10
|
|
|
11
11
|
export const EXTENSION_INSTALL_DESCRIPTION =
|
|
12
|
-
"
|
|
12
|
+
"Any authenticated agent may create and install its own TypeScript extension bundle (manifest plus a files map with one hooks.ts). Fetch the hook contract first: GET /api/extensions/type-defs returns swarm-extension.d.ts with every event and modify shape. The installing agent is recorded as createdByAgentId. Workers may install subsequent versions only for their own extensions; those versions remain inactive. A new extension remains disabled until a lead, operator, or dashboard user enables it with extension-enable.";
|
|
13
13
|
|
|
14
14
|
function extensionInstallResult(data: unknown, fallbackName: string) {
|
|
15
15
|
const body = data as {
|
|
@@ -24,11 +24,12 @@ function extensionListItems(data: unknown): ExtensionListItem[] {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function renderExtensionsTable(extensions: ExtensionListItem[]): string {
|
|
27
|
-
const header =
|
|
28
|
-
|
|
27
|
+
const header =
|
|
28
|
+
"| Name | Owner agent | Version | Active | Enabled | Status | Priority | Failures |";
|
|
29
|
+
const divider = "| --- | --- | ---: | ---: | --- | --- | ---: | ---: |";
|
|
29
30
|
const rows = extensions.map(
|
|
30
31
|
(extension) =>
|
|
31
|
-
`| ${extension.name ?? "?"} | ${extension.version ?? "?"} | ${extension.activeVersion ?? "?"} | ${extension.enabled ?? false} | ${extension.status ?? "unknown"} | ${extension.priority ?? "?"} | ${extension.consecutiveFailures ?? 0} |`,
|
|
32
|
+
`| ${extension.name ?? "?"} | ${extension.createdByAgentId ?? "operator/user"} | ${extension.version ?? "?"} | ${extension.activeVersion ?? "?"} | ${extension.enabled ?? false} | ${extension.status ?? "unknown"} | ${extension.priority ?? "?"} | ${extension.consecutiveFailures ?? 0} |`,
|
|
32
33
|
);
|
|
33
34
|
return [header, divider, ...rows].join("\n");
|
|
34
35
|
}
|
package/src/tools/join-swarm.ts
CHANGED
|
@@ -84,7 +84,7 @@ export const registerJoinSwarmTool = (server: McpServer) => {
|
|
|
84
84
|
|
|
85
85
|
try {
|
|
86
86
|
const agent = await getDbClient().transaction(async () => {
|
|
87
|
-
const agents = await getAllAgents();
|
|
87
|
+
const agents = await getAllAgents({ includeExtensions: true });
|
|
88
88
|
|
|
89
89
|
const existingIdAgent = agents.find((agent) => agent.id === agentId);
|
|
90
90
|
|
|
@@ -3,7 +3,14 @@ import { CronExpressionParser } from "cron-parser";
|
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
|
|
5
5
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createScheduledTask,
|
|
8
|
+
extensionAgentAssignmentError,
|
|
9
|
+
getAgentById,
|
|
10
|
+
getScheduledTaskByName,
|
|
11
|
+
getWorkflow,
|
|
12
|
+
isExtensionAgent,
|
|
13
|
+
} from "@/be/db";
|
|
7
14
|
import { getScript } from "@/be/scripts/db";
|
|
8
15
|
import { calculateNextRun } from "@/scheduler";
|
|
9
16
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
@@ -229,6 +236,9 @@ export const registerCreateScheduleTool = (server: McpServer) => {
|
|
|
229
236
|
if (!agent) {
|
|
230
237
|
return toolErr(`Target agent not found: ${targetAgentId}`);
|
|
231
238
|
}
|
|
239
|
+
if (isExtensionAgent(agent)) {
|
|
240
|
+
return toolErr(extensionAgentAssignmentError(agent));
|
|
241
|
+
}
|
|
232
242
|
}
|
|
233
243
|
|
|
234
244
|
// Cross-field targetType validation
|
|
@@ -4,10 +4,12 @@ import * as z from "zod";
|
|
|
4
4
|
import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
|
|
5
5
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
6
6
|
import {
|
|
7
|
+
extensionAgentAssignmentError,
|
|
7
8
|
getAgentById,
|
|
8
9
|
getScheduledTaskById,
|
|
9
10
|
getScheduledTaskByName,
|
|
10
11
|
getWorkflow,
|
|
12
|
+
isExtensionAgent,
|
|
11
13
|
updateScheduledTask,
|
|
12
14
|
} from "@/be/db";
|
|
13
15
|
import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
|
|
@@ -187,6 +189,9 @@ export const registerPatchScheduleTool = (server: McpServer) => {
|
|
|
187
189
|
if (!agent) {
|
|
188
190
|
return toolErr(`Target agent not found: ${targetAgentId}`);
|
|
189
191
|
}
|
|
192
|
+
if (isExtensionAgent(agent)) {
|
|
193
|
+
return toolErr(extensionAgentAssignmentError(agent));
|
|
194
|
+
}
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
// Check if new name conflicts with existing
|
|
@@ -4,10 +4,12 @@ import * as z from "zod";
|
|
|
4
4
|
import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
|
|
5
5
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
6
6
|
import {
|
|
7
|
+
extensionAgentAssignmentError,
|
|
7
8
|
getAgentById,
|
|
8
9
|
getScheduledTaskById,
|
|
9
10
|
getScheduledTaskByName,
|
|
10
11
|
getWorkflow,
|
|
12
|
+
isExtensionAgent,
|
|
11
13
|
updateScheduledTask,
|
|
12
14
|
} from "@/be/db";
|
|
13
15
|
import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
|
|
@@ -186,6 +188,9 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
|
|
|
186
188
|
if (!agent) {
|
|
187
189
|
return toolErr(`Target agent not found: ${targetAgentId}`);
|
|
188
190
|
}
|
|
191
|
+
if (isExtensionAgent(agent)) {
|
|
192
|
+
return toolErr(extensionAgentAssignmentError(agent));
|
|
193
|
+
}
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
// Check if new name conflicts with existing
|
package/src/tools/send-task.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { AssetKeyAuthorizationError, authorizeAssetKeyWrite } from "@/be/asset-k
|
|
|
4
4
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
5
5
|
import {
|
|
6
6
|
createTaskExtended,
|
|
7
|
+
extensionAgentAssignmentError,
|
|
7
8
|
findCompletedTaskInThread,
|
|
8
9
|
findExistingLinearTrackerContextWork,
|
|
9
10
|
findRecentCancelledTaskInThread,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
getTaskById,
|
|
14
15
|
getUserById,
|
|
15
16
|
hasCapacity,
|
|
17
|
+
isExtensionAgent,
|
|
16
18
|
} from "@/be/db";
|
|
17
19
|
import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
|
|
18
20
|
import { applyPreTaskCreate } from "@/extensions/apply-task-create";
|
|
@@ -562,6 +564,10 @@ export async function sendTaskHandler(
|
|
|
562
564
|
};
|
|
563
565
|
}
|
|
564
566
|
|
|
567
|
+
if (isExtensionAgent(agent)) {
|
|
568
|
+
return { success: false, message: extensionAgentAssignmentError(agent) };
|
|
569
|
+
}
|
|
570
|
+
|
|
565
571
|
if (taskOptions.routingAffinity?.leadOnly && !agent.isLead) {
|
|
566
572
|
return {
|
|
567
573
|
success: false,
|
package/src/tools/utils.ts
CHANGED
|
@@ -275,7 +275,7 @@ const storeProgressBlockedWaitingNudge = (r: SwarmToolResult): string | undefine
|
|
|
275
275
|
if (!r.ok) return undefined;
|
|
276
276
|
const ms = (r.data as { blockedWaitingElapsedMs?: unknown } | undefined)?.blockedWaitingElapsedMs;
|
|
277
277
|
if (typeof ms !== "number") return undefined;
|
|
278
|
-
return `That reads as blocked-waiting, ${formatIdleDuration(ms)} since your last update —
|
|
278
|
+
return `That reads as blocked-waiting, ${formatIdleDuration(ms)} since your last update — keep working if you can, and if the wait is genuinely long, defer-task is one way to resume later instead of polling.`;
|
|
279
279
|
};
|
|
280
280
|
|
|
281
281
|
const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined => {
|
package/src/types.ts
CHANGED
|
@@ -136,16 +136,22 @@ export const DEFAULT_MODEL_TIER_MAP: Record<ProviderName, Record<ModelTier, stri
|
|
|
136
136
|
ultra: "gpt-5.6-sol",
|
|
137
137
|
},
|
|
138
138
|
pi: {
|
|
139
|
-
smol: "openrouter/deepseek/deepseek-v4-flash",
|
|
140
|
-
regular: "openrouter/deepseek/deepseek-v4-flash",
|
|
141
|
-
smart: "openrouter/deepseek/deepseek-v4-pro",
|
|
142
|
-
ultra: "openrouter/anthropic/claude-opus-
|
|
139
|
+
smol: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
140
|
+
regular: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
141
|
+
smart: "openrouter/deepseek/deepseek-v4-pro-0813",
|
|
142
|
+
ultra: "openrouter/anthropic/claude-opus-5.5",
|
|
143
143
|
},
|
|
144
144
|
opencode: {
|
|
145
|
-
smol: "openrouter/deepseek/deepseek-v4-flash",
|
|
146
|
-
regular: "openrouter/deepseek/deepseek-v4-flash",
|
|
147
|
-
smart: "openrouter/deepseek/deepseek-v4-pro",
|
|
148
|
-
ultra: "openrouter/anthropic/claude-opus-
|
|
145
|
+
smol: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
146
|
+
regular: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
147
|
+
smart: "openrouter/deepseek/deepseek-v4-pro-0813",
|
|
148
|
+
ultra: "openrouter/anthropic/claude-opus-5.5",
|
|
149
|
+
},
|
|
150
|
+
dsh: {
|
|
151
|
+
smol: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
152
|
+
regular: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
153
|
+
smart: "openrouter/deepseek/deepseek-v4-pro-0813",
|
|
154
|
+
ultra: "openrouter/anthropic/claude-opus-5.5",
|
|
149
155
|
},
|
|
150
156
|
devin: {
|
|
151
157
|
smol: "devin",
|
|
@@ -367,6 +373,7 @@ export const ProviderNameSchema = z.enum([
|
|
|
367
373
|
"claude-managed",
|
|
368
374
|
"opencode",
|
|
369
375
|
"acp",
|
|
376
|
+
"dsh",
|
|
370
377
|
]);
|
|
371
378
|
export type ProviderName = z.infer<typeof ProviderNameSchema>;
|
|
372
379
|
|
|
@@ -401,6 +408,8 @@ export const SteeringMessageSchema = z
|
|
|
401
408
|
status: SteeringStatusSchema,
|
|
402
409
|
deliveredMode: SteerModeSchema.optional(),
|
|
403
410
|
source: SteeringSourceSchema,
|
|
411
|
+
/** Display label resolved by the API from the sender identity. */
|
|
412
|
+
senderLabel: z.string().optional(),
|
|
404
413
|
createdByKind: z.enum(["user", "agent", "system"]),
|
|
405
414
|
createdByUserId: z.string().optional(),
|
|
406
415
|
createdByAgentId: z.string().optional(),
|
|
@@ -455,6 +464,7 @@ export const PROVIDER_STEER_CAPABILITIES: Record<ProviderName, SteerMode[]> = {
|
|
|
455
464
|
// single in-flight turn and the only interrupt is `session/cancel` (abort).
|
|
456
465
|
// Advertise nothing rather than promise semantics we can't honor.
|
|
457
466
|
acp: [],
|
|
467
|
+
dsh: [],
|
|
458
468
|
};
|
|
459
469
|
|
|
460
470
|
export type DevinProviderMeta = {
|
|
@@ -474,6 +484,7 @@ export type ProviderMetaMap = {
|
|
|
474
484
|
"claude-managed": NoProviderMeta;
|
|
475
485
|
opencode: NoProviderMeta;
|
|
476
486
|
acp: NoProviderMeta;
|
|
487
|
+
dsh: NoProviderMeta;
|
|
477
488
|
};
|
|
478
489
|
|
|
479
490
|
export const FollowUpConfigSchema = z
|
|
@@ -624,6 +635,12 @@ export const AgentTaskSchema = z
|
|
|
624
635
|
// Pause tracking
|
|
625
636
|
wasPaused: z.boolean().default(false),
|
|
626
637
|
|
|
638
|
+
// Set by `defer-task` when this task ends in a deferral. The task's status
|
|
639
|
+
// is `completed` either way, so this is what tells "parked, resuming in a
|
|
640
|
+
// wake-up task" apart from "done" — see `isDeferredTask` in
|
|
641
|
+
// src/slack/task-output.ts.
|
|
642
|
+
deferredAt: z.iso.datetime().optional(),
|
|
643
|
+
|
|
627
644
|
// Context usage aggregates
|
|
628
645
|
compactionCount: z.number().int().min(0).optional(),
|
|
629
646
|
peakContextPercent: z.number().min(0).max(100).optional(),
|
|
@@ -1644,6 +1661,11 @@ export const ScheduledTaskSchema = z
|
|
|
1644
1661
|
// Set only by `defer-task`: the task this schedule wakes up to continue.
|
|
1645
1662
|
// Passed through as the created task's `parentTaskId`.
|
|
1646
1663
|
parentTaskId: z.string().optional(),
|
|
1664
|
+
// Set only by `defer-task`: what the caller asked for, kept because the
|
|
1665
|
+
// scheduler clears `nextRunAt` once a one_time schedule fires. Exactly one
|
|
1666
|
+
// of the two is present. Provenance only — never read to schedule a run.
|
|
1667
|
+
requestedDelayMs: z.number().int().positive().optional(),
|
|
1668
|
+
requestedRunAt: z.iso.datetime().optional(),
|
|
1647
1669
|
timezone: z.string().default("UTC"),
|
|
1648
1670
|
consecutiveErrors: z.number().int().min(0).default(0),
|
|
1649
1671
|
lastErrorAt: z.iso.datetime().optional(),
|
|
@@ -29,6 +29,7 @@ const CONTEXT_WINDOW_DEFAULTS: Record<string, number> = {
|
|
|
29
29
|
// Anthropic Fable / Mythos tier
|
|
30
30
|
"claude-fable-5-1": 1_000_000,
|
|
31
31
|
"claude-mythos-5-1": 1_000_000,
|
|
32
|
+
"claude-opus-5-5": 1_000_000,
|
|
32
33
|
"claude-opus-5": 1_000_000,
|
|
33
34
|
"claude-fable-5": 1_000_000,
|
|
34
35
|
"claude-mythos-5": 1_000_000,
|
package/src/utils/credentials.ts
CHANGED
|
@@ -8,6 +8,7 @@ export const CREDENTIAL_POOL_VARS = [
|
|
|
8
8
|
"OPENAI_API_KEY",
|
|
9
9
|
"CODEX_OAUTH",
|
|
10
10
|
"DEVIN_API_KEY",
|
|
11
|
+
"DEEPSEEK_API_KEY",
|
|
11
12
|
] as const;
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -26,6 +27,7 @@ export const PROVIDER_CREDENTIAL_VARS: Record<string, readonly string[]> = {
|
|
|
26
27
|
pi: ["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
|
27
28
|
codex: ["OPENAI_API_KEY", "CODEX_OAUTH"],
|
|
28
29
|
devin: ["DEVIN_API_KEY"],
|
|
30
|
+
dsh: ["OPENROUTER_API_KEY", "DEEPSEEK_API_KEY"],
|
|
29
31
|
opencode: ["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
|
30
32
|
};
|
|
31
33
|
|
|
@@ -57,6 +59,10 @@ const SLASH_MODEL_PROVIDERS = new Set(["opencode", "pi"]);
|
|
|
57
59
|
export function getModelAwareCredentialVars(provider: string, model?: string): readonly string[] {
|
|
58
60
|
const base = PROVIDER_CREDENTIAL_VARS[provider];
|
|
59
61
|
if (!base) return CREDENTIAL_POOL_VARS;
|
|
62
|
+
// dsh has exact prefix routing; the generic slash rule only filters OpenAI keys.
|
|
63
|
+
if (provider === "dsh" && model) {
|
|
64
|
+
return model.startsWith("openrouter/") ? ["OPENROUTER_API_KEY"] : ["DEEPSEEK_API_KEY"];
|
|
65
|
+
}
|
|
60
66
|
if (!SLASH_MODEL_PROVIDERS.has(provider) || !model) return base;
|
|
61
67
|
if (model.includes("/")) {
|
|
62
68
|
return base.filter((v) => v !== "OPENAI_API_KEY");
|
|
@@ -82,6 +88,8 @@ export function deriveProviderFromKeyType(keyType: string): string {
|
|
|
82
88
|
case "OPENAI_API_KEY":
|
|
83
89
|
case "CODEX_OAUTH":
|
|
84
90
|
return "codex";
|
|
91
|
+
case "DEEPSEEK_API_KEY":
|
|
92
|
+
return "dsh";
|
|
85
93
|
case "DEVIN_API_KEY":
|
|
86
94
|
return "devin";
|
|
87
95
|
default:
|
|
@@ -284,6 +284,14 @@ export async function completeStructured<TZod extends z.ZodTypeAny>(
|
|
|
284
284
|
return null;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// Dynamic IDs can be absent even though the typed catalog overload is non-nullable.
|
|
288
|
+
if (!model) {
|
|
289
|
+
console.error(
|
|
290
|
+
scrubSecrets(`internal-ai: unknown model ${provider}/${modelId} (callerTag=${callerTag})`),
|
|
291
|
+
);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
287
295
|
// The builtin catalog hardcodes openrouter.ai; OPENROUTER_BASE_URL reroutes
|
|
288
296
|
// through a gateway (pi-ai's stream path uses `model.baseUrl` verbatim).
|
|
289
297
|
// Resolved from the caller's session env when provided — swarm_config
|
|
@@ -17,10 +17,10 @@ export type CredentialKind = "openrouter" | "anthropic" | "openai" | "openai-cod
|
|
|
17
17
|
* shellout, not pi-ai's `getModel`.
|
|
18
18
|
*/
|
|
19
19
|
export const DEFAULT_MODEL: Record<CredentialKind, string> = {
|
|
20
|
-
openrouter: "openrouter/
|
|
20
|
+
openrouter: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
21
21
|
anthropic: "anthropic/claude-haiku-4-5",
|
|
22
|
-
openai: "openai/gpt-
|
|
23
|
-
"openai-codex": "openai-codex/gpt-
|
|
22
|
+
openai: "openai/gpt-6-luna",
|
|
23
|
+
"openai-codex": "openai-codex/gpt-6-luna",
|
|
24
24
|
"claude-cli": "haiku",
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -35,8 +35,8 @@ export function resolveModelString(kind: CredentialKind): string {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Split a `provider/model-id` string on the FIRST `/` so that OpenRouter
|
|
38
|
-
* compound IDs like `openrouter/
|
|
39
|
-
* `("openrouter", "
|
|
38
|
+
* compound IDs like `openrouter/deepseek/deepseek-v4.1-flash` parse as
|
|
39
|
+
* `("openrouter", "deepseek/deepseek-v4.1-flash")`. Mirrors the existing
|
|
40
40
|
* convention in `src/providers/pi-mono-adapter.ts:161-170`.
|
|
41
41
|
*/
|
|
42
42
|
export function parseModelStr(modelStr: string): [provider: string, modelId: string] {
|
package/src/workflows/engine.ts
CHANGED
|
@@ -198,6 +198,14 @@ interface StepResult {
|
|
|
198
198
|
successors: WorkflowNode[];
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// A run may have overlapping resume walks. Keep ownership until the last walk
|
|
202
|
+
// settles, including checkpointing and routing between executor calls.
|
|
203
|
+
const activeWalks = new Map<string, number>();
|
|
204
|
+
|
|
205
|
+
export function isWorkflowRunActive(runId: string): boolean {
|
|
206
|
+
return activeWalks.has(runId);
|
|
207
|
+
}
|
|
208
|
+
|
|
201
209
|
/**
|
|
202
210
|
* Event-loop style graph walker.
|
|
203
211
|
*
|
|
@@ -214,6 +222,26 @@ export async function walkGraph(
|
|
|
214
222
|
workflowId?: string,
|
|
215
223
|
secretKeys: Set<string> = new Set(),
|
|
216
224
|
options: WorkflowExecutionOptions = {},
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
activeWalks.set(runId, (activeWalks.get(runId) ?? 0) + 1);
|
|
227
|
+
try {
|
|
228
|
+
await walkGraphOwned(def, runId, ctx, startNodes, registry, workflowId, secretKeys, options);
|
|
229
|
+
} finally {
|
|
230
|
+
const remaining = activeWalks.get(runId)! - 1;
|
|
231
|
+
if (remaining === 0) activeWalks.delete(runId);
|
|
232
|
+
else activeWalks.set(runId, remaining);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function walkGraphOwned(
|
|
237
|
+
def: WorkflowDefinition,
|
|
238
|
+
runId: string,
|
|
239
|
+
ctx: Record<string, unknown>,
|
|
240
|
+
startNodes: WorkflowNode[],
|
|
241
|
+
registry: ExecutorRegistry,
|
|
242
|
+
workflowId?: string,
|
|
243
|
+
secretKeys: Set<string> = new Set(),
|
|
244
|
+
options: WorkflowExecutionOptions = {},
|
|
217
245
|
): Promise<void> {
|
|
218
246
|
// Initial triggers carry the requester in options. Resumes, retries, and
|
|
219
247
|
// recovery start from persisted state, so rehydrate it from the run.
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
updateWorkflowRun,
|
|
12
12
|
} from "../be/db";
|
|
13
13
|
import { FAILED_TASK_OUTPUT_PREFIX } from "./constants";
|
|
14
|
-
import { findReadyNodes, walkGraph } from "./engine";
|
|
14
|
+
import { findReadyNodes, isWorkflowRunActive, walkGraph } from "./engine";
|
|
15
15
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
16
16
|
import { getSecretInputKeys } from "./input";
|
|
17
17
|
import { finalizeOrWait, resumeWaitState } from "./resume";
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
} from "./task-step-routing";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Recover incomplete workflow runs on
|
|
25
|
+
* Recover incomplete workflow runs on startup and during heartbeat sweeps.
|
|
26
26
|
*
|
|
27
27
|
* Two cases:
|
|
28
|
-
* 1. `running` runs
|
|
28
|
+
* 1. `running` runs without a live graph walk — interrupted execution.
|
|
29
29
|
* Find completed steps, compute ready nodes, continue walking.
|
|
30
30
|
* 2. `waiting` runs — were waiting for a task that may have finished while we were down.
|
|
31
31
|
* Check if the linked task is done and resume/fail accordingly.
|
|
@@ -46,7 +46,7 @@ export async function recoverIncompleteRuns(registry: ExecutorRegistry): Promise
|
|
|
46
46
|
recovered += await recoverWaitStates(registry);
|
|
47
47
|
|
|
48
48
|
if (recovered > 0) {
|
|
49
|
-
console.log(`[workflows] Recovered ${recovered} incomplete run(s)
|
|
49
|
+
console.log(`[workflows] Recovered ${recovered} incomplete run(s)`);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
return recovered;
|
|
@@ -64,6 +64,7 @@ async function recoverRunningRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
64
64
|
|
|
65
65
|
for (const runId of runningRunIds) {
|
|
66
66
|
try {
|
|
67
|
+
if (isWorkflowRunActive(runId)) continue;
|
|
67
68
|
const run = await getWorkflowRun(runId);
|
|
68
69
|
if (!run || run.status !== "running") continue;
|
|
69
70
|
|
|
@@ -75,6 +76,8 @@ async function recoverRunningRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
75
76
|
|
|
76
77
|
// Find the next nodes that are ready to execute
|
|
77
78
|
const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
|
|
79
|
+
// DB reads above yield; a trigger/resume may have acquired the run meanwhile.
|
|
80
|
+
if (isWorkflowRunActive(runId)) continue;
|
|
78
81
|
if (readyNodes.length === 0) {
|
|
79
82
|
// All nodes completed or nothing is ready — mark as completed
|
|
80
83
|
await updateWorkflowRun(runId, {
|