@desplega.ai/agent-swarm 1.131.0 → 1.133.1
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/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
- package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
- package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
- package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
- package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
- package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
- package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
- package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
- package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
- package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
- package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
- package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
- package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
- package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
- package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
- package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
- package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
- package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
- package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
- package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
- package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
- package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
- package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
- package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
- package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
- package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
- package/dist/cli-et1bbwqf.js +13 -0
- package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
- package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
- package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
- package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
- package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
- package/dist/cli-hkwfe3qj.js +530 -0
- package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
- package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
- package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
- package/dist/cli-pzq5rjym.js +15 -0
- package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
- package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
- package/dist/cli-qys3rhhz.js +27 -0
- package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
- package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
- package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
- package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
- package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
- package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
- package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
- package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
- package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
- package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
- package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
- package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
- package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
- package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
- package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
- package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
- package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
- package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
- package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
- package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
- package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
- package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
- package/dist/index-0gr8vd6k.js +39 -0
- package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
- package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
- package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
- package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
- package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
- package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
- package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
- package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
- package/dist/mistral-conversations-00zrp7g4.js +717 -0
- package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
- package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
- package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
- package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
- package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
- package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
- package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
- package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
- package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
- package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
- package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
- package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
- package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
- package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
- package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
- package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
- package/openapi.json +542 -16
- package/package.json +6 -6
- package/src/be/budget-refusal-notify.ts +14 -7
- package/src/be/db-queries/task-artifacts.ts +182 -0
- package/src/be/db.ts +783 -169
- package/src/be/events.ts +5 -0
- package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
- package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
- package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
- package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
- package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
- package/src/be/migrations/136_task_requester_provenance.sql +37 -0
- package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
- package/src/be/multi-runtime.ts +430 -0
- package/src/be/scripts/db.ts +43 -1
- package/src/be/scripts/extract-schema.ts +3 -1
- package/src/be/scripts/retention.ts +164 -0
- package/src/be/scripts/run-saved.ts +16 -1
- package/src/be/scripts/typecheck.ts +3 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -1
- package/src/be/swarm-config-guard.ts +18 -0
- package/src/commands/credential-wait.ts +54 -0
- package/src/commands/onboard/steps/core-credentials.tsx +1 -1
- package/src/commands/onboard/steps/custom-templates.tsx +1 -1
- package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
- package/src/commands/onboard/steps/health-check.tsx +1 -1
- package/src/commands/onboard/steps/start.tsx +1 -1
- package/src/commands/profile-sync-audit.ts +56 -0
- package/src/commands/profile-sync.ts +287 -15
- package/src/commands/provider-credentials.ts +46 -19
- package/src/commands/runner.ts +196 -33
- package/src/commands/templates.ts +22 -0
- package/src/github/handlers.ts +55 -9
- package/src/heartbeat/heartbeat.ts +71 -12
- package/src/hooks/hook.ts +49 -33
- package/src/http/active-sessions.ts +11 -1
- package/src/http/agents.ts +261 -30
- package/src/http/all-routes.ts +1 -0
- package/src/http/config.ts +13 -2
- package/src/http/core.ts +102 -4
- package/src/http/db-query-bounded.ts +313 -0
- package/src/http/db-query-shared.ts +249 -0
- package/src/http/db-query.ts +54 -42
- package/src/http/events.ts +2 -0
- package/src/http/index.ts +27 -2
- package/src/http/mcp-bridge.ts +37 -3
- package/src/http/mcp.ts +12 -0
- package/src/http/memory.ts +13 -2
- package/src/http/openapi.ts +1 -0
- package/src/http/poll.ts +70 -27
- package/src/http/route-def.ts +43 -9
- package/src/http/schedules.ts +2 -2
- package/src/http/scripts.ts +31 -4
- package/src/http/session-data.ts +44 -0
- package/src/http/tasks.ts +2 -2
- package/src/http/users.ts +74 -52
- package/src/http/webhooks.ts +5 -2
- package/src/http/workflows.ts +1 -4
- package/src/http/x.ts +14 -0
- package/src/integrations/kapso/inbound.ts +1 -1
- package/src/jira/outbound.ts +39 -8
- package/src/jira/webhook-lifecycle.ts +13 -2
- package/src/linear/outbound.ts +43 -10
- package/src/oauth/keepalive.ts +13 -2
- package/src/prompts/base-prompt.ts +1 -1
- package/src/prompts/session-templates.ts +45 -7
- package/src/providers/claude-adapter.ts +6 -0
- package/src/providers/claude-managed-models.ts +34 -10
- package/src/providers/codex-adapter.ts +6 -0
- package/src/providers/opencode-adapter.ts +6 -0
- package/src/providers/pi-mono-adapter.ts +8 -0
- package/src/providers/pricing-sources.md +24 -0
- package/src/queue-stall-alarm.ts +216 -0
- package/src/scheduler/scheduler.ts +7 -0
- package/src/script-workflows/executor.ts +2 -1
- package/src/scripts-runtime/executors/native.ts +2 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/loader.ts +5 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/tests/budget-claim-gate.test.ts +20 -0
- package/src/tests/claude-adapter.test.ts +32 -0
- package/src/tests/claude-managed-adapter.test.ts +38 -7
- package/src/tests/codex-adapter.test.ts +30 -0
- package/src/tests/create-task-validation.test.ts +128 -0
- package/src/tests/credential-wait.test.ts +67 -1
- package/src/tests/db-query.test.ts +711 -2
- package/src/tests/http-api-integration.test.ts +81 -0
- package/src/tests/http-users.test.ts +166 -1
- package/src/tests/identity-field-budget.test.ts +176 -0
- package/src/tests/mcp-tools-user.test.ts +34 -0
- package/src/tests/mcp-user-route.test.ts +17 -0
- package/src/tests/metrics-http.test.ts +44 -0
- package/src/tests/multi-runtime-registration.test.ts +2703 -0
- package/src/tests/openapi-response-contract.test.ts +24 -0
- package/src/tests/openapi-route-headers.test.ts +83 -0
- package/src/tests/opencode-adapter.test.ts +40 -0
- package/src/tests/outbound-listener-rejection.test.ts +138 -0
- package/src/tests/pi-mono-adapter.test.ts +98 -0
- package/src/tests/profile-sync-warning.test.ts +277 -0
- package/src/tests/profile-sync.test.ts +232 -6
- package/src/tests/prompt-template-session.test.ts +32 -3
- package/src/tests/queue-stall-alarm.test.ts +226 -0
- package/src/tests/requester-comms.test.ts +32 -0
- package/src/tests/runner-requester-profile.test.ts +38 -0
- package/src/tests/sandboxed-process.test.ts +45 -0
- package/src/tests/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +16 -3
- package/src/tests/scripts-external-api.test.ts +8 -3
- package/src/tests/scripts-http.test.ts +12 -0
- package/src/tests/scripts-retention.test.ts +384 -0
- package/src/tests/scripts-runtime-identity.test.ts +375 -0
- package/src/tests/scripts-runtime.test.ts +4 -3
- package/src/tests/sdk-allowlist.test.ts +47 -0
- package/src/tests/send-task-requested-by.test.ts +9 -0
- package/src/tests/session-costs.test.ts +506 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
- package/src/tests/steering-core.test.ts +28 -0
- package/src/tests/task-artifacts.test.ts +386 -0
- package/src/tests/task-attachments-schema.test.ts +17 -1
- package/src/tests/task-completion-idempotency.test.ts +57 -0
- package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
- package/src/tests/update-profile-api.test.ts +67 -39
- package/src/tests/update-profile-auth.test.ts +48 -4
- package/src/tests/user-attribution-migration.test.ts +54 -0
- package/src/tests/user-identity.test.ts +72 -0
- package/src/tests/workflow-async-v2.test.ts +39 -1
- package/src/tests/workflow-http-v2.test.ts +3 -2
- package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
- package/src/tests/workflow-retry-v2.test.ts +45 -1
- package/src/tests/workflow-swarm-script.test.ts +14 -5
- package/src/tools/db-query.ts +18 -8
- package/src/tools/get-task-details.ts +24 -3
- package/src/tools/manage-user.ts +36 -3
- package/src/tools/poll-task.ts +93 -14
- package/src/tools/script-common.ts +5 -0
- package/src/tools/send-task.ts +1 -4
- package/src/tools/store-progress.ts +13 -2
- package/src/tools/swarm-config/delete-config.ts +9 -0
- package/src/tools/swarm-config/set-config.ts +3 -2
- package/src/tools/task-action.ts +30 -0
- package/src/tools/task-tool-ctx.ts +8 -1
- package/src/tools/update-profile.ts +16 -21
- package/src/tools/utils.ts +5 -0
- package/src/tools/workflows/trigger-workflow.ts +7 -1
- package/src/types.ts +165 -1
- package/src/utils/github-pull-request.ts +76 -0
- package/src/utils/identity-field-budget.ts +73 -0
- package/src/utils/multi-runtime.ts +20 -0
- package/src/utils/requester-comms.ts +24 -0
- package/src/utils/sandboxed-process.ts +40 -14
- package/src/workflows/engine.ts +22 -2
- package/src/workflows/executors/swarm-script.ts +24 -1
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- package/templates/schedules/daily-compounding-reflection/content.md +12 -3
- package/templates/skills/db-query-guidance/config.json +14 -0
- package/templates/skills/db-query-guidance/content.md +57 -0
- package/dist/cli-3te4jdkm.js +0 -225
- package/dist/cli-bmze986d.js +0 -7
- package/dist/cli-kh3ba2zf.js +0 -607
- package/dist/index-hp1kdbjj.js +0 -36
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
package/src/types.ts
CHANGED
|
@@ -633,6 +633,102 @@ export const AgentTaskSchema = z
|
|
|
633
633
|
})
|
|
634
634
|
.openapi("AgentTask");
|
|
635
635
|
|
|
636
|
+
// ─── Create-task input contract ──────────────────────────────────────────────
|
|
637
|
+
//
|
|
638
|
+
// Runtime schema for `createTaskExtended` options (src/be/db.ts). Every task
|
|
639
|
+
// write — REST POST /api/tasks, MCP send-task, the scripts bridge, webhooks,
|
|
640
|
+
// the scheduler, and internal callers — funnels through that function, so
|
|
641
|
+
// this schema is the single enforcement point for task-input shapes. It
|
|
642
|
+
// rejects, never coerces: a type mismatch or out-of-range value throws, and
|
|
643
|
+
// only an ABSENT field falls back to the `??` defaults at the INSERT bind
|
|
644
|
+
// site. Deliberately NOT an `.openapi()` component: it is an internal write
|
|
645
|
+
// contract, not a response shape.
|
|
646
|
+
export const CreateTaskOptionsSchema = z.object({
|
|
647
|
+
key: z.string().min(1).optional(),
|
|
648
|
+
agentId: z.string().nullable().optional(),
|
|
649
|
+
creatorAgentId: z.string().optional(),
|
|
650
|
+
source: AgentTaskSourceSchema.optional(),
|
|
651
|
+
taskType: z.string().max(50).optional(),
|
|
652
|
+
tags: z.array(z.string()).optional(),
|
|
653
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
654
|
+
dependsOn: z.array(z.string()).optional(),
|
|
655
|
+
offeredTo: z.string().optional(),
|
|
656
|
+
/** Explicitly set initial status. */
|
|
657
|
+
status: z.enum(["backlog", "unassigned"]).optional(),
|
|
658
|
+
slackChannelId: z.string().optional(),
|
|
659
|
+
slackThreadTs: z.string().optional(),
|
|
660
|
+
/** Exact Slack message that directly triggered this task; never inherited. */
|
|
661
|
+
slackTriggerMessageTs: z.string().optional(),
|
|
662
|
+
slackUserId: z.string().optional(),
|
|
663
|
+
/**
|
|
664
|
+
* Opt out of the residual Slack/contextKey normalization (see the
|
|
665
|
+
* "Residual-mismatch guard" comment near the INSERT in src/be/db.ts): a
|
|
666
|
+
* deliberate cross-channel/thread dispatch (e.g. `send-task`'s
|
|
667
|
+
* `overrideSlackContext: true`) sets this so its explicit slackChannelId/
|
|
668
|
+
* slackThreadTs survive even when they disagree with a slack-family
|
|
669
|
+
* `contextKey`/parent. Trusted callers that don't set this get normalized —
|
|
670
|
+
* this boundary must not let a caller silently persist a mismatch.
|
|
671
|
+
*/
|
|
672
|
+
overrideSlackContext: z.boolean().optional(),
|
|
673
|
+
vcsProvider: z.enum(["github", "gitlab"]).optional(),
|
|
674
|
+
vcsRepo: z.string().optional(),
|
|
675
|
+
vcsEventType: z.string().optional(),
|
|
676
|
+
vcsNumber: z.number().int().optional(),
|
|
677
|
+
vcsCommentId: z.number().int().optional(),
|
|
678
|
+
vcsAuthor: z.string().optional(),
|
|
679
|
+
vcsUrl: z.string().optional(),
|
|
680
|
+
vcsInstallationId: z.number().int().optional(),
|
|
681
|
+
vcsNodeId: z.string().optional(),
|
|
682
|
+
agentmailInboxId: z.string().optional(),
|
|
683
|
+
agentmailMessageId: z.string().optional(),
|
|
684
|
+
agentmailThreadId: z.string().optional(),
|
|
685
|
+
mentionMessageId: z.string().optional(),
|
|
686
|
+
mentionChannelId: z.string().optional(),
|
|
687
|
+
dir: z.string().optional(),
|
|
688
|
+
parentTaskId: z.string().optional(),
|
|
689
|
+
model: z.string().optional(),
|
|
690
|
+
modelTier: ModelTierSchema.optional(),
|
|
691
|
+
effort: ReasoningEffortSchema.optional(),
|
|
692
|
+
scheduleId: z.string().optional(),
|
|
693
|
+
workflowRunId: z.string().optional(),
|
|
694
|
+
workflowRunStepId: z.string().optional(),
|
|
695
|
+
sourceTaskId: z.string().optional(),
|
|
696
|
+
/**
|
|
697
|
+
* Optional JSON Schema the agent's final output must conform to.
|
|
698
|
+
*
|
|
699
|
+
* Enforced via the MCP `store-progress` tool (validated in
|
|
700
|
+
* `src/tools/store-progress.ts`). NOT enforced when the task runs on
|
|
701
|
+
* default-mode Devin (no MCP) — see runbooks/harness-providers.md
|
|
702
|
+
* ("Per-task outputSchema support"). Callers reading `task.output` for
|
|
703
|
+
* a schema'd task should be defensive about JSON parsing.
|
|
704
|
+
*/
|
|
705
|
+
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
706
|
+
/**
|
|
707
|
+
* When a `parentTaskId` is set, the child inherits the parent's `outputSchema`
|
|
708
|
+
* by default. Set this to `false` to opt out — used by control-plane children
|
|
709
|
+
* (e.g. the Lead `reroute-decision` task) that must inherit Slack/VCS context
|
|
710
|
+
* from the parent but must NOT be forced to satisfy the original work's output
|
|
711
|
+
* contract on completion (which would block the control task — DES-523).
|
|
712
|
+
*/
|
|
713
|
+
inheritParentOutputSchema: z.boolean().optional(),
|
|
714
|
+
followUpConfig: FollowUpConfigSchema.optional(),
|
|
715
|
+
requestedByUserId: z.string().optional(),
|
|
716
|
+
contextKey: z.string().optional(),
|
|
717
|
+
/**
|
|
718
|
+
* Internal control-plane escape hatch for continuations that MUST coexist
|
|
719
|
+
* with their active Linear parent (for example promoted steering). General
|
|
720
|
+
* task creation keeps tracker-context dedup enabled.
|
|
721
|
+
*/
|
|
722
|
+
bypassTrackerContextDedup: z.boolean().optional(),
|
|
723
|
+
/**
|
|
724
|
+
* Routing-affinity snapshot gating pool eligibility (see
|
|
725
|
+
* `isAgentEligibleForTask`). Inherited from the parent (via `parentTaskId`)
|
|
726
|
+
* when not explicitly set — same treatment as `vcsRepo`/`contextKey`.
|
|
727
|
+
*/
|
|
728
|
+
routingAffinity: RoutingAffinitySchema.optional(),
|
|
729
|
+
});
|
|
730
|
+
export type CreateTaskOptions = z.infer<typeof CreateTaskOptionsSchema>;
|
|
731
|
+
|
|
636
732
|
// ============================================================================
|
|
637
733
|
// Task Attachments (Phase 1 — pointer-based artifacts)
|
|
638
734
|
// ============================================================================
|
|
@@ -645,11 +741,19 @@ export const AgentTaskSchema = z
|
|
|
645
741
|
export const TaskAttachmentKindSchema = z.enum(["agent-fs", "url", "shared-fs", "page"]);
|
|
646
742
|
export type TaskAttachmentKind = z.infer<typeof TaskAttachmentKindSchema>;
|
|
647
743
|
|
|
744
|
+
export const SERVER_GENERATED_ATTACHMENT_CAPABILITY = "_agentSwarmGeneratedBy";
|
|
745
|
+
|
|
746
|
+
const attachmentCapabilitiesSchema = z
|
|
747
|
+
.record(z.string(), z.unknown())
|
|
748
|
+
.refine((capabilities) => !(SERVER_GENERATED_ATTACHMENT_CAPABILITY in capabilities), {
|
|
749
|
+
message: `${SERVER_GENERATED_ATTACHMENT_CAPABILITY} is reserved for server-generated attachments`,
|
|
750
|
+
});
|
|
751
|
+
|
|
648
752
|
const attachmentCommonFields = {
|
|
649
753
|
name: z.string().min(1).describe("Display name for the attachment."),
|
|
650
754
|
providerId: z.string().min(1).optional(),
|
|
651
755
|
providerKey: z.string().min(1).optional(),
|
|
652
|
-
capabilities:
|
|
756
|
+
capabilities: attachmentCapabilitiesSchema.optional(),
|
|
653
757
|
mimeType: z.string().optional(),
|
|
654
758
|
sizeBytes: z.number().int().min(0).optional(),
|
|
655
759
|
sha256: z.string().optional(),
|
|
@@ -755,6 +859,22 @@ export const UserSchema = z
|
|
|
755
859
|
|
|
756
860
|
export type User = z.infer<typeof UserSchema>;
|
|
757
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Structured communication preferences read from `users.metadata.comms`.
|
|
864
|
+
* All fields are free-form strings; values render verbatim into the requester
|
|
865
|
+
* profile prompt. Written merge-safely via the `comms` field on
|
|
866
|
+
* PATCH /api/users/{id} and the manage-user tool.
|
|
867
|
+
*/
|
|
868
|
+
export const UserCommsPrefsSchema = z
|
|
869
|
+
.object({
|
|
870
|
+
tone: z.string().optional(),
|
|
871
|
+
language: z.string().optional(),
|
|
872
|
+
verbosity: z.string().optional(),
|
|
873
|
+
})
|
|
874
|
+
.openapi("UserCommsPrefs");
|
|
875
|
+
|
|
876
|
+
export type UserCommsPrefs = z.infer<typeof UserCommsPrefsSchema>;
|
|
877
|
+
|
|
758
878
|
/**
|
|
759
879
|
* Identity event types — mirrored in lockstep with the CHECK constraint on
|
|
760
880
|
* `user_identity_events.eventType` in migration 064. Drift breaks helper
|
|
@@ -1338,6 +1458,8 @@ export const EventNameSchema = z.enum([
|
|
|
1338
1458
|
"system.boot",
|
|
1339
1459
|
"system.migration",
|
|
1340
1460
|
"system.error",
|
|
1461
|
+
"system.profile_sync_rejected",
|
|
1462
|
+
"system.profile_sync_reconciled",
|
|
1341
1463
|
// Script catalog events
|
|
1342
1464
|
"script.global_upsert",
|
|
1343
1465
|
// Schedule events
|
|
@@ -1563,6 +1685,8 @@ export const ActiveSessionSchema = z
|
|
|
1563
1685
|
taskDescription: z.string().nullable(),
|
|
1564
1686
|
runnerSessionId: z.string().nullable(),
|
|
1565
1687
|
providerSessionId: z.string().nullable(),
|
|
1688
|
+
/** Runtime process that owns this session; null for single-runtime rows. */
|
|
1689
|
+
runtimeInstanceId: z.string().nullable().optional(),
|
|
1566
1690
|
startedAt: z.iso.datetime(),
|
|
1567
1691
|
lastHeartbeatAt: z.iso.datetime(),
|
|
1568
1692
|
})
|
|
@@ -1570,6 +1694,45 @@ export const ActiveSessionSchema = z
|
|
|
1570
1694
|
|
|
1571
1695
|
export type ActiveSession = z.infer<typeof ActiveSessionSchema>;
|
|
1572
1696
|
|
|
1697
|
+
// ============================================================================
|
|
1698
|
+
// Runtime Instance Types (multi-runtime worker tracking)
|
|
1699
|
+
// ============================================================================
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Lifecycle of a runtime instance. Zod is the source of truth for the allowed
|
|
1703
|
+
* values — the SQL column has no CHECK constraint (migration 132) so adding a
|
|
1704
|
+
* state doesn't require a table rebuild.
|
|
1705
|
+
*/
|
|
1706
|
+
export const RuntimeInstanceStatusSchema = z.enum(["active", "offline"]);
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* One worker process serving a logical agent. The `agents` row (AGENT_ID /
|
|
1710
|
+
* X-Agent-ID) keeps durable identity and the logical maxTasks policy; a
|
|
1711
|
+
* runtime instance carries only process-scoped state: liveness and its own
|
|
1712
|
+
* reported execution capacity. A logical agent may be served by 0..N runtime
|
|
1713
|
+
* instances; rows exist only for multi-runtime registrations
|
|
1714
|
+
* (MULTI_RUNTIME_ENABLED).
|
|
1715
|
+
*/
|
|
1716
|
+
export const RuntimeInstanceSchema = z
|
|
1717
|
+
.object({
|
|
1718
|
+
id: z.string(),
|
|
1719
|
+
agentId: z.string(),
|
|
1720
|
+
status: RuntimeInstanceStatusSchema,
|
|
1721
|
+
/** Runtime-local concurrent task capacity, self-reported at registration. */
|
|
1722
|
+
reportedSlots: z.number().int(),
|
|
1723
|
+
/** Process-local credential readiness; null when never reported. */
|
|
1724
|
+
credentialReady: z.boolean().nullable().optional(),
|
|
1725
|
+
/** Provider-neutral metadata; unused in the initial slice. */
|
|
1726
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
1727
|
+
lastSeenAt: z.iso.datetime(),
|
|
1728
|
+
createdAt: z.iso.datetime(),
|
|
1729
|
+
updatedAt: z.iso.datetime(),
|
|
1730
|
+
})
|
|
1731
|
+
.openapi("RuntimeInstance");
|
|
1732
|
+
|
|
1733
|
+
export type RuntimeInstanceStatus = z.infer<typeof RuntimeInstanceStatusSchema>;
|
|
1734
|
+
export type RuntimeInstance = z.infer<typeof RuntimeInstanceSchema>;
|
|
1735
|
+
|
|
1573
1736
|
// ============================================================================
|
|
1574
1737
|
// Workflow Engine Types
|
|
1575
1738
|
// ============================================================================
|
|
@@ -2238,6 +2401,7 @@ export const WorkflowRunSchema = z
|
|
|
2238
2401
|
triggerData: z.unknown().optional(),
|
|
2239
2402
|
context: z.record(z.string(), z.unknown()).optional(),
|
|
2240
2403
|
error: z.string().optional(),
|
|
2404
|
+
createdBy: z.string().optional(),
|
|
2241
2405
|
startedAt: z.string(),
|
|
2242
2406
|
lastUpdatedAt: z.string(),
|
|
2243
2407
|
finishedAt: z.string().optional(),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface GitHubPullRequestUrl {
|
|
2
|
+
url: string;
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
number: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const CANDIDATE_TOKEN_RE = /[^\s()[\]{}<>"'`]+/g;
|
|
9
|
+
const TRAILING_PROSE_PUNCTUATION_RE = /[)\]},.;:!]+$/;
|
|
10
|
+
const GITHUB_PATH_SEGMENT_RE = /^[\w.-]+$/;
|
|
11
|
+
|
|
12
|
+
function parseGitHubPullRequestToken(token: string): GitHubPullRequestUrl | null {
|
|
13
|
+
const candidate = token.replace(TRAILING_PROSE_PUNCTUATION_RE, "");
|
|
14
|
+
const lowerCandidate = candidate.toLowerCase();
|
|
15
|
+
if (
|
|
16
|
+
!lowerCandidate.startsWith("https://github.com/") &&
|
|
17
|
+
!lowerCandidate.startsWith("http://github.com/") &&
|
|
18
|
+
!lowerCandidate.startsWith("github.com/")
|
|
19
|
+
) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const withScheme = /^https?:\/\//i.test(candidate) ? candidate : `https://${candidate}`;
|
|
23
|
+
|
|
24
|
+
let parsed: URL;
|
|
25
|
+
try {
|
|
26
|
+
parsed = new URL(withScheme);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
parsed.host.toLowerCase() !== "github.com" ||
|
|
32
|
+
parsed.username.length > 0 ||
|
|
33
|
+
parsed.password.length > 0
|
|
34
|
+
) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [owner, repo, pull, numberText] = parsed.pathname.split("/").filter(Boolean);
|
|
39
|
+
if (
|
|
40
|
+
!owner ||
|
|
41
|
+
!repo ||
|
|
42
|
+
pull?.toLowerCase() !== "pull" ||
|
|
43
|
+
!numberText ||
|
|
44
|
+
!GITHUB_PATH_SEGMENT_RE.test(owner) ||
|
|
45
|
+
!GITHUB_PATH_SEGMENT_RE.test(repo) ||
|
|
46
|
+
!/^\d+$/.test(numberText)
|
|
47
|
+
) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
url: `https://github.com/${owner}/${repo}/pull/${numberText}`,
|
|
53
|
+
owner,
|
|
54
|
+
repo,
|
|
55
|
+
number: Number(numberText),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Extract distinct canonical GitHub pull-request URLs from free text. */
|
|
60
|
+
export function extractGitHubPullRequestUrls(
|
|
61
|
+
text: string | null | undefined,
|
|
62
|
+
): GitHubPullRequestUrl[] {
|
|
63
|
+
if (!text) return [];
|
|
64
|
+
|
|
65
|
+
const results: GitHubPullRequestUrl[] = [];
|
|
66
|
+
const seen = new Set<string>();
|
|
67
|
+
for (const match of text.matchAll(CANDIDATE_TOKEN_RE)) {
|
|
68
|
+
const pullRequest = parseGitHubPullRequestToken(match[0]);
|
|
69
|
+
if (!pullRequest) continue;
|
|
70
|
+
const dedupeKey = pullRequest.url.toLowerCase();
|
|
71
|
+
if (seen.has(dedupeKey)) continue;
|
|
72
|
+
seen.add(dedupeKey);
|
|
73
|
+
results.push(pullRequest);
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BOOTSTRAP_MAX_CHARS } from "../prompts/base-prompt";
|
|
2
|
+
|
|
3
|
+
export const SOUL_MD_MAX_CHARS = 10_000;
|
|
4
|
+
export const IDENTITY_MD_MAX_CHARS = 10_000;
|
|
5
|
+
|
|
6
|
+
export const IDENTITY_FIELD_BUDGETS = {
|
|
7
|
+
soulMd: SOUL_MD_MAX_CHARS,
|
|
8
|
+
identityMd: IDENTITY_MD_MAX_CHARS,
|
|
9
|
+
claudeMd: BOOTSTRAP_MAX_CHARS,
|
|
10
|
+
toolsMd: BOOTSTRAP_MAX_CHARS,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type BudgetedIdentityField = keyof typeof IDENTITY_FIELD_BUDGETS;
|
|
14
|
+
|
|
15
|
+
export interface IdentityFieldBudgetRejection {
|
|
16
|
+
field: BudgetedIdentityField;
|
|
17
|
+
dbSize: number;
|
|
18
|
+
diskSize: number;
|
|
19
|
+
budget: number;
|
|
20
|
+
delta: number;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type IdentityFieldBudgetResult =
|
|
25
|
+
| { ok: true }
|
|
26
|
+
| ({ ok: false } & IdentityFieldBudgetRejection);
|
|
27
|
+
|
|
28
|
+
export function checkIdentityFieldBudget({
|
|
29
|
+
field,
|
|
30
|
+
currentValue,
|
|
31
|
+
nextValue,
|
|
32
|
+
}: {
|
|
33
|
+
field: BudgetedIdentityField;
|
|
34
|
+
currentValue: string;
|
|
35
|
+
nextValue: string;
|
|
36
|
+
}): IdentityFieldBudgetResult {
|
|
37
|
+
const budget = IDENTITY_FIELD_BUDGETS[field];
|
|
38
|
+
if (nextValue.length <= budget || nextValue.length <= currentValue.length) {
|
|
39
|
+
return { ok: true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const delta = nextValue.length - currentValue.length;
|
|
43
|
+
const remediation =
|
|
44
|
+
field === "toolsMd"
|
|
45
|
+
? ` Content past the ${budget}-character cap is already silently dropped at read time, so shrinking that tail loses nothing sessions currently receive. Move durable content into memories and keep pointers to it in this field.`
|
|
46
|
+
: field === "claudeMd"
|
|
47
|
+
? ` The tail past the ${budget}-character cap is dropped from the base prompt and only reaches harnesses with a native CLAUDE.md loader. Move durable content into memories and keep pointers to it in this field.`
|
|
48
|
+
: " Move durable content into memories and keep pointers to it in this field.";
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
field,
|
|
53
|
+
dbSize: currentValue.length,
|
|
54
|
+
diskSize: nextValue.length,
|
|
55
|
+
budget,
|
|
56
|
+
delta,
|
|
57
|
+
reason:
|
|
58
|
+
`Update rejected for ${field}: current size ${currentValue.length} characters, ` +
|
|
59
|
+
`budget ${budget} characters, delta ${delta >= 0 ? "+" : ""}${delta} characters.` +
|
|
60
|
+
remediation,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class IdentityFieldBudgetError extends Error {
|
|
65
|
+
constructor(
|
|
66
|
+
readonly rejection: IdentityFieldBudgetRejection,
|
|
67
|
+
readonly dbHash: string,
|
|
68
|
+
readonly diskHash: string,
|
|
69
|
+
) {
|
|
70
|
+
super(rejection.reason);
|
|
71
|
+
this.name = "IdentityFieldBudgetError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseEnvFlag } from "./env-flag";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Opt-in, default off: allows one logical agent to be served by several
|
|
5
|
+
* worker processes. Lives outside `src/be` so worker-side code can import it
|
|
6
|
+
* without crossing the DB boundary.
|
|
7
|
+
*/
|
|
8
|
+
export function isMultiRuntimeEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
9
|
+
return parseEnvFlag(env.MULTI_RUNTIME_ENABLED, false);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Per-boot runtime identity the runner stamps into the process environment
|
|
14
|
+
* before any provider session exists. Adapters read it here to send the
|
|
15
|
+
* X-Runtime-Instance-ID header on the swarm MCP session — runtime identity is
|
|
16
|
+
* process/auth context, never a tool argument.
|
|
17
|
+
*/
|
|
18
|
+
export function swarmRuntimeInstanceId(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
19
|
+
return env.SWARM_RUNTIME_INSTANCE_ID || undefined;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { User, UserCommsPrefs } from "../types";
|
|
2
|
+
|
|
3
|
+
function pickString(value: unknown): string | undefined {
|
|
4
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract structured communication preferences from `users.metadata.comms`.
|
|
9
|
+
* `metadata` is a free-form JSON blob with no write-side validation, so only
|
|
10
|
+
* non-empty string fields survive; returns undefined when nothing usable is set.
|
|
11
|
+
*/
|
|
12
|
+
export function getUserCommsPrefs(
|
|
13
|
+
user: Pick<User, "metadata"> | null | undefined,
|
|
14
|
+
): UserCommsPrefs | undefined {
|
|
15
|
+
const comms = user?.metadata?.comms;
|
|
16
|
+
if (!comms || typeof comms !== "object" || Array.isArray(comms)) return undefined;
|
|
17
|
+
const record = comms as Record<string, unknown>;
|
|
18
|
+
const prefs: UserCommsPrefs = {
|
|
19
|
+
tone: pickString(record.tone),
|
|
20
|
+
language: pickString(record.language),
|
|
21
|
+
verbosity: pickString(record.verbosity),
|
|
22
|
+
};
|
|
23
|
+
return prefs.tone || prefs.language || prefs.verbosity ? prefs : undefined;
|
|
24
|
+
}
|
|
@@ -35,20 +35,36 @@ export const DEFAULT_SANDBOX_LIMITS: SandboxResourceLimits = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Bun
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* automatically — callers don't need to special-case it.
|
|
38
|
+
* Bun and Node reserve several GB of virtual address space and start runtime
|
|
39
|
+
* threads before user code runs. Give their process trees enough RLIMIT_AS
|
|
40
|
+
* and RLIMIT_NPROC headroom to boot while retaining the stricter defaults for
|
|
41
|
+
* direct non-interpreter commands.
|
|
43
42
|
*/
|
|
44
43
|
export const BUN_SANDBOX_VIRTUAL_MEMORY_MB = 4096;
|
|
44
|
+
export const JAVASCRIPT_RUNTIME_SANDBOX_MAX_PROCS = 512;
|
|
45
|
+
|
|
46
|
+
const JAVASCRIPT_RUNTIMES = new Set(["bun", "bunx", "node", "nodejs", "npx"]);
|
|
47
|
+
const SHELL_RUNTIMES = new Set(["bash", "dash", "sh"]);
|
|
48
|
+
|
|
49
|
+
function commandBasename(command: string): string {
|
|
50
|
+
return command.slice(command.lastIndexOf("/") + 1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function usesInterpreterSandboxProfile(innerCommand: readonly string[]): boolean {
|
|
54
|
+
const runtime = commandBasename(innerCommand[0] ?? "");
|
|
55
|
+
// A shell can invoke another interpreter through arbitrary valid shell
|
|
56
|
+
// grammar (substitutions, functions, variables, heredocs, and more). Treat
|
|
57
|
+
// the shell itself as the runtime boundary instead of letting untrusted
|
|
58
|
+
// source text select its own resource profile through brittle parsing.
|
|
59
|
+
return JAVASCRIPT_RUNTIMES.has(runtime) || SHELL_RUNTIMES.has(runtime);
|
|
60
|
+
}
|
|
45
61
|
|
|
46
62
|
function shellQuote(value: string): string {
|
|
47
63
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
/**
|
|
51
|
-
* Wrap `innerCommand` in a
|
|
67
|
+
* Wrap `innerCommand` in a shell prelude that applies the resource
|
|
52
68
|
* ulimits above, then `exec`s into a completely clean environment (`env -i`)
|
|
53
69
|
* containing ONLY the entries in `env`. This is the enforcement point for
|
|
54
70
|
* two invariants every spawn-user-code path must uphold:
|
|
@@ -60,7 +76,10 @@ function shellQuote(value: string): string {
|
|
|
60
76
|
* token / API key in `env`; pass secrets over stdin instead so they
|
|
61
77
|
* never appear in `/proc/<pid>/environ` or a child's `process.env`.
|
|
62
78
|
*
|
|
63
|
-
*
|
|
79
|
+
* JavaScript-capable runtime trees use Bash intentionally: Ubuntu's `/bin/sh` is
|
|
80
|
+
* dash, whose `ulimit` does not implement `-u` (RLIMIT_NPROC). Other commands
|
|
81
|
+
* retain the existing POSIX-sh sandbox behavior and its strict profile. No-ops
|
|
82
|
+
* (returns `innerCommand` unchanged) on win32, where `ulimit`/`env -i`
|
|
64
83
|
* don't exist — matches the existing native.ts behavior. Because this no-op
|
|
65
84
|
* means `env` is never applied on win32, callers MUST pass `env` itself
|
|
66
85
|
* (not just `{ PATH }`) to `Bun.spawn` on that platform — use
|
|
@@ -74,15 +93,18 @@ export function buildSandboxedCommand(
|
|
|
74
93
|
): string[] {
|
|
75
94
|
if (process.platform === "win32") return [...innerCommand];
|
|
76
95
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
const usesInterpreterProfile = usesInterpreterSandboxProfile(innerCommand);
|
|
97
|
+
const virtualMemoryMb = usesInterpreterProfile
|
|
98
|
+
? Math.max(limits.virtualMemoryMb, BUN_SANDBOX_VIRTUAL_MEMORY_MB)
|
|
99
|
+
: limits.virtualMemoryMb;
|
|
100
|
+
const maxProcs = usesInterpreterProfile
|
|
101
|
+
? Math.max(limits.maxProcs, JAVASCRIPT_RUNTIME_SANDBOX_MAX_PROCS)
|
|
102
|
+
: limits.maxProcs;
|
|
81
103
|
|
|
82
104
|
const ulimits = [
|
|
83
105
|
`ulimit -v ${Math.floor(virtualMemoryMb * 1024)} 2>/dev/null || true`,
|
|
84
106
|
`ulimit -t ${limits.cpuTimeSec} 2>/dev/null || true`,
|
|
85
|
-
`ulimit -u ${
|
|
107
|
+
`ulimit -u ${maxProcs} 2>/dev/null || true`,
|
|
86
108
|
`ulimit -f ${limits.maxFileKb} 2>/dev/null || true`,
|
|
87
109
|
`ulimit -n ${limits.maxFdCount} 2>/dev/null || true`,
|
|
88
110
|
].join("; ");
|
|
@@ -92,7 +114,11 @@ export function buildSandboxedCommand(
|
|
|
92
114
|
.join(" ");
|
|
93
115
|
const quotedInner = innerCommand.map(shellQuote).join(" ");
|
|
94
116
|
|
|
95
|
-
return [
|
|
117
|
+
return [
|
|
118
|
+
usesInterpreterProfile ? "bash" : "sh",
|
|
119
|
+
"-c",
|
|
120
|
+
`${ulimits}; exec env -i ${envAssignments} ${quotedInner}`,
|
|
121
|
+
];
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
/**
|
|
@@ -101,7 +127,7 @@ export function buildSandboxedCommand(
|
|
|
101
127
|
*
|
|
102
128
|
* On POSIX, `buildSandboxedCommand` wraps the command in an `env -i` prelude
|
|
103
129
|
* that injects `env` itself into the child — Bun.spawn only needs PATH to
|
|
104
|
-
* locate the
|
|
130
|
+
* locate the shell binary that runs the prelude.
|
|
105
131
|
*
|
|
106
132
|
* On win32, `buildSandboxedCommand` is a no-op (no `ulimit`/`env -i`), so
|
|
107
133
|
* there is no prelude to inject `env` — it must go through Bun.spawn's own
|
package/src/workflows/engine.ts
CHANGED
|
@@ -76,7 +76,13 @@ export async function startWorkflowExecution(
|
|
|
76
76
|
// Cooldown check
|
|
77
77
|
if (workflow.cooldown && shouldSkipCooldown(workflow.id, workflow.cooldown)) {
|
|
78
78
|
const runId = crypto.randomUUID();
|
|
79
|
-
createWorkflowRun({
|
|
79
|
+
createWorkflowRun({
|
|
80
|
+
id: runId,
|
|
81
|
+
workflowId: workflow.id,
|
|
82
|
+
triggerType: options.triggerType ?? "manual",
|
|
83
|
+
triggerData,
|
|
84
|
+
createdBy: options.requestedByUserId,
|
|
85
|
+
});
|
|
80
86
|
updateWorkflowRun(runId, {
|
|
81
87
|
status: "skipped",
|
|
82
88
|
error: "cooldown",
|
|
@@ -86,7 +92,13 @@ export async function startWorkflowExecution(
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
const runId = crypto.randomUUID();
|
|
89
|
-
createWorkflowRun({
|
|
95
|
+
createWorkflowRun({
|
|
96
|
+
id: runId,
|
|
97
|
+
workflowId: workflow.id,
|
|
98
|
+
triggerType: options.triggerType ?? "manual",
|
|
99
|
+
triggerData,
|
|
100
|
+
createdBy: options.requestedByUserId,
|
|
101
|
+
});
|
|
90
102
|
telemetry.workflow("started", {
|
|
91
103
|
workflowId: workflow.id,
|
|
92
104
|
nodeCount: workflow.definition.nodes.length,
|
|
@@ -160,6 +172,14 @@ export async function walkGraph(
|
|
|
160
172
|
secretKeys: Set<string> = new Set(),
|
|
161
173
|
options: WorkflowExecutionOptions = {},
|
|
162
174
|
): Promise<void> {
|
|
175
|
+
// Initial triggers carry the requester in options. Resumes, retries, and
|
|
176
|
+
// recovery start from persisted state, so rehydrate it from the run.
|
|
177
|
+
if (!options.requestedByUserId) {
|
|
178
|
+
options = {
|
|
179
|
+
...options,
|
|
180
|
+
requestedByUserId: getWorkflowRun(runId)?.createdBy,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
163
183
|
let nodeExecutionCount = 0;
|
|
164
184
|
// `run.id` is a context builtin (like `trigger` / `input`) so nodes can reference
|
|
165
185
|
// their own run — e.g. an audit/receipt node correlating its output with the run
|
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
getScriptMcpConnectionDescriptors,
|
|
5
5
|
} from "../../be/script-connections";
|
|
6
6
|
import { buildScriptCredentialBindingsWithFailures } from "../../be/script-credential-broker";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getScript,
|
|
9
|
+
getScriptVersion,
|
|
10
|
+
restoreScratchScriptLastUsedIfUnchanged,
|
|
11
|
+
touchScratchScriptLastUsed,
|
|
12
|
+
} from "../../be/scripts/db";
|
|
8
13
|
import {
|
|
9
14
|
DEFAULT_SCRIPT_RESOURCES,
|
|
10
15
|
MAX_SCRIPT_WALL_CLOCK_MS,
|
|
@@ -104,6 +109,12 @@ export class SwarmScriptExecutor extends BaseExecutor<
|
|
|
104
109
|
};
|
|
105
110
|
}
|
|
106
111
|
|
|
112
|
+
// Touch before executing so an already-stale scratch script isn't reaped
|
|
113
|
+
// by the retention sweep while this run is still in flight.
|
|
114
|
+
const runStartTouch = resolved.script.isScratch
|
|
115
|
+
? touchScratchScriptLastUsed(resolved.script.id)
|
|
116
|
+
: null;
|
|
117
|
+
|
|
107
118
|
const credentials = await buildScriptCredentialBindingsWithFailures({
|
|
108
119
|
agentId: agentId ?? undefined,
|
|
109
120
|
});
|
|
@@ -118,6 +129,18 @@ export class SwarmScriptExecutor extends BaseExecutor<
|
|
|
118
129
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agentId ?? undefined }),
|
|
119
130
|
timeoutMs: config.timeoutMs,
|
|
120
131
|
});
|
|
132
|
+
if (output.exitCode === 0 && !output.error && !output.runtimeError) {
|
|
133
|
+
touchScratchScriptLastUsed(resolved.script.id);
|
|
134
|
+
} else if (resolved.script.isScratch && runStartTouch) {
|
|
135
|
+
// Failed run — restore the pre-run timestamp so it doesn't buy the
|
|
136
|
+
// script another retention window, unless a concurrent run already
|
|
137
|
+
// touched it since.
|
|
138
|
+
restoreScratchScriptLastUsedIfUnchanged(
|
|
139
|
+
resolved.script.id,
|
|
140
|
+
resolved.script.updatedAt,
|
|
141
|
+
runStartTouch,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
121
144
|
|
|
122
145
|
const workflowOutput = {
|
|
123
146
|
result: output.result,
|
package/src/workflows/resume.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
updateWorkflowRun,
|
|
14
14
|
updateWorkflowRunStep,
|
|
15
15
|
} from "../be/db";
|
|
16
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
16
17
|
import { checkpointStep } from "./checkpoint";
|
|
17
18
|
import { FAILED_TASK_OUTPUT_PREFIX } from "./constants";
|
|
18
19
|
import { getSuccessors } from "./definition";
|
|
@@ -51,9 +52,9 @@ export function setupWorkflowResumeListener(
|
|
|
51
52
|
registry: ExecutorRegistry,
|
|
52
53
|
): void {
|
|
53
54
|
eventBus.on("task.completed", async (data: unknown) => {
|
|
54
|
-
const event = data as TaskEvent;
|
|
55
|
-
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
56
55
|
try {
|
|
56
|
+
const event = data as TaskEvent;
|
|
57
|
+
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
57
58
|
await resumeFromTaskCompletion(event, registry);
|
|
58
59
|
} catch (err) {
|
|
59
60
|
console.error("[workflows] Resume from task completion failed:", err);
|
|
@@ -61,9 +62,9 @@ export function setupWorkflowResumeListener(
|
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
eventBus.on("task.failed", async (data: unknown) => {
|
|
64
|
-
const event = data as TaskEvent;
|
|
65
|
-
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
66
65
|
try {
|
|
66
|
+
const event = data as TaskEvent;
|
|
67
|
+
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
67
68
|
await handleTaskFailure(event, event.failureReason ?? "Task failed", registry);
|
|
68
69
|
} catch (err) {
|
|
69
70
|
console.error("[workflows] Handle task failure error:", err);
|
|
@@ -71,9 +72,9 @@ export function setupWorkflowResumeListener(
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
eventBus.on("task.cancelled", async (data: unknown) => {
|
|
74
|
-
const event = data as TaskEvent;
|
|
75
|
-
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
76
75
|
try {
|
|
76
|
+
const event = data as TaskEvent;
|
|
77
|
+
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
77
78
|
await handleTaskFailure(event, "Task was cancelled", registry);
|
|
78
79
|
} catch (err) {
|
|
79
80
|
console.error("[workflows] Handle task cancellation error:", err);
|
|
@@ -81,9 +82,9 @@ export function setupWorkflowResumeListener(
|
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
eventBus.on("approval.resolved", async (data: unknown) => {
|
|
84
|
-
const event = data as ApprovalEvent;
|
|
85
|
-
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
86
85
|
try {
|
|
86
|
+
const event = data as ApprovalEvent;
|
|
87
|
+
if (!event.workflowRunId || !event.workflowRunStepId) return;
|
|
87
88
|
await resumeFromApprovalResolution(event, registry);
|
|
88
89
|
} catch (err) {
|
|
89
90
|
console.error("[workflows] Resume from approval resolution failed:", err);
|
|
@@ -589,9 +590,16 @@ function registerWait(waitId: string, eventName: string): void {
|
|
|
589
590
|
|
|
590
591
|
if (!listenersByEvent.has(eventName)) {
|
|
591
592
|
const listener = (data: unknown) => {
|
|
592
|
-
// Fire-and-forget: don't block the bus thread.
|
|
593
|
-
//
|
|
594
|
-
|
|
593
|
+
// Fire-and-forget: don't block the bus thread. Per-wait errors are
|
|
594
|
+
// logged inside processBusEvent's loop, but the code around that loop
|
|
595
|
+
// is not covered by it — and the emitter cannot observe this promise,
|
|
596
|
+
// since EventEmitter discards whatever a listener returns.
|
|
597
|
+
processBusEvent(eventName, data).catch((err) => {
|
|
598
|
+
console.error(
|
|
599
|
+
`[workflows] Wait bus listener failed for event=${eventName}:`,
|
|
600
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
601
|
+
);
|
|
602
|
+
});
|
|
595
603
|
};
|
|
596
604
|
listenersByEvent.set(eventName, listener);
|
|
597
605
|
workflowEventBus.on(eventName, listener);
|