@desplega.ai/agent-swarm 1.131.1 → 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-jgcy477k.js → actions-cskpayd7.js} +11 -9
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
- package/dist/{app-5da78bd4.js → app-ang77nbb.js} +8 -6
- package/dist/{assistant-f2vkk1hj.js → assistant-b4exebdw.js} +16 -13
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
- package/dist/{boot-reembed-q7mp8hcr.js → boot-reembed-ps2aftpb.js} +9 -7
- package/dist/{boot-reembed-xmw8wcx6.js → boot-reembed-znyk3nxb.js} +10 -8
- package/dist/{boot-scrub-logs-dsqrv79n.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
- package/dist/{claude-adapter-q6x7yspx.js → claude-adapter-3ct8kgxw.js} +13 -9
- package/dist/{claude-managed-adapter-wsg37bt8.js → claude-managed-adapter-akx4ntf2.js} +2 -2
- package/dist/{cli-114fvtmj.js → cli-13w2hwkh.js} +1 -1
- package/dist/{cli-4rkfg1wx.js → cli-1532szb1.js} +8 -6
- package/dist/{cli-g74vpjp2.js → cli-23d3rseb.js} +33 -17
- package/dist/{cli-3ty0t082.js → cli-2a75z9c5.js} +196 -7
- package/dist/{cli-pxfxkx5m.js → cli-2t30rj0d.js} +1 -1
- package/dist/{cli-rgrfzg8z.js → cli-3c3tdgab.js} +10 -10
- package/dist/{cli-6dp3ydxa.js → cli-3zzxa9jz.js} +15 -11
- package/dist/{cli-2f07m0xr.js → cli-4k6233gc.js} +1 -1
- package/dist/{cli-t8d349cy.js → cli-5p2tz05r.js} +2 -2
- package/dist/{cli-w2wxd56v.js → cli-5z2yhzvd.js} +127 -49
- package/dist/{cli-kd1ck4rx.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-ccf9vy7q.js → cli-9nfc2es8.js} +6 -6
- package/dist/{cli-ycrnynpw.js → cli-9ta3pwms.js} +11 -4
- package/dist/{cli-7dm2zqy2.js → cli-avr0ns7z.js} +2 -2
- package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
- package/dist/{cli-gecy3z2s.js → cli-dgk20ft7.js} +4 -4
- 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-6pcz9h15.js → cli-ftwpv451.js} +170 -387
- package/dist/{cli-nrkkknat.js → cli-g854s2dw.js} +1 -1
- package/dist/{cli-793jvbsh.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-sy7jk3tx.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-se2p6rkb.js → cli-r6tpm37h.js} +2 -2
- package/dist/{cli-g1re8r05.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-aca5pz9f.js → cli-sfd9v7sj.js} +1 -1
- package/dist/{cli-2rk0cx7s.js → cli-sh3ytsyj.js} +49 -39
- package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
- package/dist/{cli-6dpytdr2.js → cli-xdb4mpnp.js} +2 -2
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-jbcva0xj.js → cli-xv8t8p3c.js} +56 -31
- package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
- package/dist/{cli-jevgrb4w.js → cli-y08jvxzx.js} +601 -140
- package/dist/{cli-50vbt00w.js → cli-zfbf3srj.js} +650 -245
- package/dist/{cli-ygrbmjja.js → cli-zmq89k34.js} +6 -6
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-htjkn22b.js → codex-adapter-bef93pq4.js} +9 -7
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
- package/dist/{codex-session-runner-wjfc7smz.js → codex-session-runner-5pkxys7x.js} +9 -7
- package/dist/{commands-xhrfwqsk.js → commands-e23s1bff.js} +9 -7
- package/dist/{db-anvwb22f.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-b9kbc621.js → handlers-akazedvr.js} +16 -13
- package/dist/{hook-xkvn6c5h.js → hook-ze4cpf0v.js} +55 -41
- package/dist/{hook-cgk4wxmx.js → hook-zfqyyenw.js} +10 -7
- package/dist/{http-9xh6ta4r.js → http-sja0n6kw.js} +608 -147
- package/dist/index-0gr8vd6k.js +39 -0
- package/dist/{index-eaxyqcmj.js → index-354vkhdv.js} +154 -74
- package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
- package/dist/{index-25wjsdv2.js → index-95t3cqq5.js} +16 -14
- package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
- package/dist/{index-ywds4812.js → index-vpn36mma.js} +15 -13
- package/dist/{keepalive-vc7s2aa6.js → keepalive-3sp3meqj.js} +16 -10
- package/dist/{lead-r2yqfmx7.js → lead-mefeewkf.js} +39 -36
- package/dist/{maintenance-mw70k468.js → maintenance-8tbrbjfg.js} +10 -8
- package/dist/mistral-conversations-00zrp7g4.js +717 -0
- package/dist/{oauth-refresh-sweep-yecgjpk4.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
- package/dist/{onboard-xj0f73f9.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-nxg0n0b3.js → otel-impl-z6pxt3j9.js} +224 -9
- package/dist/{pi-mono-adapter-4g5kby31.js → pi-mono-adapter-btvqmj58.js} +1138 -408
- package/dist/{pricing-refresh-2cn6effr.js → pricing-refresh-kyq8cezh.js} +9 -7
- package/dist/{rbac-roles-1hcxdtg7.js → rbac-roles-3ryfrfna.js} +8 -6
- package/dist/{rbac-roles-vynday5g.js → rbac-roles-6qfvbee1.js} +9 -7
- package/dist/{render-v2-tc0bh8kk.js → render-v2-p61ppjey.js} +8 -6
- package/dist/{seed-pricing-8kceb43f.js → seed-pricing-j56bbj4y.js} +8 -6
- package/dist/{setup-46xtgq3c.js → setup-tpr6c1kn.js} +2 -2
- package/dist/{worker-qrbz6hmv.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/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/extract-schema.ts +3 -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 +22 -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 +10 -1
- 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/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/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/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/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +13 -1
- 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/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/workflows/engine.ts +22 -2
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- 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-vg4snay4.js +0 -36
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/{cli-wz9115x0.js → cli-zr9q011v.js} +6 -6
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
|
@@ -63,7 +63,7 @@ As the lead agent, you coordinate all worker agents in the swarm.
|
|
|
63
63
|
|
|
64
64
|
**Identity:**
|
|
65
65
|
- \`update-profile\`: Update your own or other agents' profile fields (name, role, capabilities, soulMd, identityMd, heartbeatMd, claudeMd, toolsMd, setupScript)
|
|
66
|
-
- \`manage-user\`: Register or update human users (resolve from GitHub/GitLab identity or other source metadata)
|
|
66
|
+
- \`manage-user\`: Register or update human users (resolve from GitHub/GitLab identity or other source metadata). When you learn a requester's stable communication preferences (tone, language, verbosity), persist them with its \`comms\` field so future sessions adapt.
|
|
67
67
|
|
|
68
68
|
#### Task Routing
|
|
69
69
|
|
|
@@ -803,6 +803,32 @@ When working in a repository, your system prompt may include a **Repository Guid
|
|
|
803
803
|
category: "system",
|
|
804
804
|
});
|
|
805
805
|
|
|
806
|
+
registerTemplate({
|
|
807
|
+
eventType: "system.agent.communication_style",
|
|
808
|
+
header: "",
|
|
809
|
+
defaultBody: `
|
|
810
|
+
### Communication Style
|
|
811
|
+
|
|
812
|
+
Your persona lives in your SOUL.md (see Your Identity, when present). It defines who you are. The rules below define how you write, whoever you are.
|
|
813
|
+
|
|
814
|
+
They govern everything a human reads from you: Slack, PR and issue comments, tickets, email, pages, and task summaries.
|
|
815
|
+
|
|
816
|
+
- Write like a person, not a press release. Plain words, direct statements.
|
|
817
|
+
- Lead with the outcome or the answer. Context comes after.
|
|
818
|
+
- Keep sentences short (25 words or fewer). One idea per sentence.
|
|
819
|
+
- Use active voice. Say who did what.
|
|
820
|
+
- Never use em dashes. Use a period, a comma, a colon, or parentheses.
|
|
821
|
+
- No marketing adjectives (seamless, robust, powerful). State the fact that earns the claim instead.
|
|
822
|
+
- No hedge stacks ("might possibly perhaps"). Keep a hedge only when you are genuinely unsure, and keep it: "may have failed" never becomes "failed".
|
|
823
|
+
- No filler ("I hope this helps", "Great question!") and no formal sign-offs.
|
|
824
|
+
- If something is broken, blocked, or a bad idea, say so plainly and say why.
|
|
825
|
+
- Mirror the requester: reply in the language they wrote in, match their register, and answer at the depth they asked. A one-line question gets the answer first, detail after.
|
|
826
|
+
- If a Requester Profile section is present, it wins on tone, depth, and format. Correctness always wins over style.
|
|
827
|
+
`,
|
|
828
|
+
variables: [],
|
|
829
|
+
category: "system",
|
|
830
|
+
});
|
|
831
|
+
|
|
806
832
|
// ============================================================================
|
|
807
833
|
// Per-task prompt templates (category: "task_lifecycle")
|
|
808
834
|
// ============================================================================
|
|
@@ -812,12 +838,18 @@ registerTemplate({
|
|
|
812
838
|
header: "",
|
|
813
839
|
defaultBody: `
|
|
814
840
|
## Requester Profile
|
|
815
|
-
This task was requested by {{requester_name}}{{requester_role_suffix}}.{{requester_notes_section}}
|
|
841
|
+
This task was requested by {{requester_name}}{{requester_role_suffix}}.{{requester_comms_section}}{{requester_notes_section}}
|
|
816
842
|
Honor this requester profile in tone, depth, and format where it doesn't conflict with correctness or your operating rules.
|
|
843
|
+
If this task reveals new stable communication preferences for this requester, persist them: the lead updates \`comms\` (tone, language, verbosity) via \`manage-user\`.
|
|
817
844
|
`,
|
|
818
845
|
variables: [
|
|
819
846
|
{ name: "requester_name", description: "The requesting user's display name" },
|
|
820
847
|
{ name: "requester_role_suffix", description: "Formatted role suffix, including parentheses" },
|
|
848
|
+
{
|
|
849
|
+
name: "requester_comms_section",
|
|
850
|
+
description:
|
|
851
|
+
"Formatted communication preferences (tone/language/verbosity) sourced from users.metadata.comms, or empty string",
|
|
852
|
+
},
|
|
821
853
|
{
|
|
822
854
|
name: "requester_notes_section",
|
|
823
855
|
description: "Formatted notes section sourced from users.notes, or empty string",
|
|
@@ -859,7 +891,8 @@ registerTemplate({
|
|
|
859
891
|
|
|
860
892
|
{{@template[system.agent.system]}}
|
|
861
893
|
{{@template[system.agent.share_urls]}}
|
|
862
|
-
{{@template[system.agent.code_quality]}}
|
|
894
|
+
{{@template[system.agent.code_quality]}}
|
|
895
|
+
{{@template[system.agent.communication_style]}}`,
|
|
863
896
|
variables: [
|
|
864
897
|
{ name: "role", description: "The agent's role" },
|
|
865
898
|
{ name: "agentId", description: "The agent's unique identifier" },
|
|
@@ -881,7 +914,8 @@ registerTemplate({
|
|
|
881
914
|
|
|
882
915
|
{{@template[system.agent.system]}}
|
|
883
916
|
{{@template[system.agent.share_urls]}}
|
|
884
|
-
{{@template[system.agent.code_quality]}}
|
|
917
|
+
{{@template[system.agent.code_quality]}}
|
|
918
|
+
{{@template[system.agent.communication_style]}}`,
|
|
885
919
|
variables: [
|
|
886
920
|
{ name: "role", description: "The agent's role" },
|
|
887
921
|
{ name: "agentId", description: "The agent's unique identifier" },
|
|
@@ -910,7 +944,8 @@ registerTemplate({
|
|
|
910
944
|
|
|
911
945
|
{{@template[system.agent.system]}}
|
|
912
946
|
{{@template[system.agent.share_urls]}}
|
|
913
|
-
{{@template[system.agent.code_quality]}}
|
|
947
|
+
{{@template[system.agent.code_quality]}}
|
|
948
|
+
{{@template[system.agent.communication_style]}}`,
|
|
914
949
|
variables: [
|
|
915
950
|
{ name: "role", description: "The agent's role" },
|
|
916
951
|
{ name: "agentId", description: "The agent's unique identifier" },
|
|
@@ -939,7 +974,8 @@ registerTemplate({
|
|
|
939
974
|
|
|
940
975
|
{{@template[system.agent.system]}}
|
|
941
976
|
{{@template[system.agent.share_urls]}}
|
|
942
|
-
{{@template[system.agent.code_quality]}}
|
|
977
|
+
{{@template[system.agent.code_quality]}}
|
|
978
|
+
{{@template[system.agent.communication_style]}}`,
|
|
943
979
|
variables: [
|
|
944
980
|
{ name: "role", description: "The agent's role" },
|
|
945
981
|
{ name: "agentId", description: "The agent's unique identifier" },
|
|
@@ -982,7 +1018,9 @@ registerTemplate({
|
|
|
982
1018
|
header: "",
|
|
983
1019
|
defaultBody: `{{@template[system.agent.role]}}
|
|
984
1020
|
|
|
985
|
-
{{@template[system.agent.worker.remote]}}
|
|
1021
|
+
{{@template[system.agent.worker.remote]}}
|
|
1022
|
+
|
|
1023
|
+
{{@template[system.agent.communication_style]}}`,
|
|
986
1024
|
variables: [
|
|
987
1025
|
{ name: "role", description: "The agent's role" },
|
|
988
1026
|
{ name: "agentId", description: "The agent's unique identifier" },
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
trackErrorFromJson,
|
|
20
20
|
} from "../utils/error-tracker";
|
|
21
21
|
import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
|
|
22
|
+
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
22
23
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
23
24
|
import { CTX_MODE_NUDGE_EVERY } from "./ctx-mode-env";
|
|
24
25
|
import { buildOtelTraceparentEnv, isHarnessOtelEnabled } from "./otel-env";
|
|
@@ -332,6 +333,7 @@ export function mergeMcpConfig(
|
|
|
332
333
|
installedServers: Record<string, Record<string, unknown>> | null,
|
|
333
334
|
taskId: string,
|
|
334
335
|
contextKey?: string,
|
|
336
|
+
runtimeInstanceId?: string,
|
|
335
337
|
): { mcpServers: Record<string, unknown> } {
|
|
336
338
|
const config: { mcpServers: Record<string, unknown> } = {
|
|
337
339
|
mcpServers: { ...(baseConfig?.mcpServers ?? {}) },
|
|
@@ -360,6 +362,9 @@ export function mergeMcpConfig(
|
|
|
360
362
|
if (contextKey) {
|
|
361
363
|
(server.headers as Record<string, string>)["X-Context-Key"] = contextKey;
|
|
362
364
|
}
|
|
365
|
+
if (runtimeInstanceId) {
|
|
366
|
+
(server.headers as Record<string, string>)["X-Runtime-Instance-ID"] = runtimeInstanceId;
|
|
367
|
+
}
|
|
363
368
|
}
|
|
364
369
|
|
|
365
370
|
return config;
|
|
@@ -434,6 +439,7 @@ export async function createSessionMcpConfig(
|
|
|
434
439
|
installedServers ?? null,
|
|
435
440
|
taskId,
|
|
436
441
|
contextKey,
|
|
442
|
+
swarmRuntimeInstanceId(),
|
|
437
443
|
);
|
|
438
444
|
const sessionConfigPath = `/tmp/mcp-${taskId}.json`;
|
|
439
445
|
await writeFile(sessionConfigPath, JSON.stringify(config, null, 2));
|
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
} from "../utils/context-window";
|
|
78
78
|
import { SessionErrorTracker } from "../utils/error-tracker";
|
|
79
79
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
80
|
+
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
80
81
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
81
82
|
import { type CodexAgentsMdHandle, writeCodexAgentsMd } from "./codex-agents-md";
|
|
82
83
|
import { computeCodexCostUsd, getCodexContextWindow, resolveCodexModel } from "./codex-models";
|
|
@@ -365,12 +366,17 @@ export async function buildCodexConfig(
|
|
|
365
366
|
// (2) Swarm MCP server — Streamable HTTP transport.
|
|
366
367
|
// Field names verified against https://developers.openai.com/codex/mcp:
|
|
367
368
|
// `url`, `http_headers`, `enabled`, `startup_timeout_sec`, `tool_timeout_sec`.
|
|
369
|
+
// Same per-boot runtime identity the runner registers with — dispatch tools
|
|
370
|
+
// require it in multi-runtime mode, and it travels as request context, not
|
|
371
|
+
// as a tool argument.
|
|
372
|
+
const runtimeInstanceId = swarmRuntimeInstanceId();
|
|
368
373
|
mcpServers["agent-swarm"] = {
|
|
369
374
|
url: `${config.apiUrl}/mcp`,
|
|
370
375
|
http_headers: {
|
|
371
376
|
Authorization: `Bearer ${config.apiKey}`,
|
|
372
377
|
"X-Agent-ID": config.agentId,
|
|
373
378
|
"X-Source-Task-Id": config.taskId ?? "",
|
|
379
|
+
...(runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}),
|
|
374
380
|
},
|
|
375
381
|
enabled: true,
|
|
376
382
|
startup_timeout_sec: 30,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "../utils/context-window";
|
|
20
20
|
import { validateOpencodeCredentials } from "../utils/credentials";
|
|
21
21
|
import { fetchInstalledMcpServers } from "../utils/mcp-server-fetcher";
|
|
22
|
+
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
22
23
|
import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
|
|
23
24
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
24
25
|
import { resolveSlashSkillPrompt } from "./codex-skill-resolver";
|
|
@@ -726,6 +727,10 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
726
727
|
const installedMcp =
|
|
727
728
|
(await fetchInstalledMcpServers(config.apiUrl, config.apiKey, config.agentId, "opencode")) ??
|
|
728
729
|
{};
|
|
730
|
+
// Same per-boot runtime identity the runner registers with — dispatch
|
|
731
|
+
// tools require it in multi-runtime mode, and it travels as request
|
|
732
|
+
// context, not as a tool argument.
|
|
733
|
+
const runtimeInstanceId = swarmRuntimeInstanceId();
|
|
729
734
|
const mcpConfig: Config["mcp"] = {
|
|
730
735
|
swarm: {
|
|
731
736
|
type: "remote",
|
|
@@ -733,6 +738,7 @@ export class OpencodeAdapter implements ProviderAdapter {
|
|
|
733
738
|
headers: {
|
|
734
739
|
Authorization: `Bearer ${config.apiKey}`,
|
|
735
740
|
"X-Agent-ID": config.agentId,
|
|
741
|
+
...(runtimeInstanceId ? { "X-Runtime-Instance-ID": runtimeInstanceId } : {}),
|
|
736
742
|
},
|
|
737
743
|
},
|
|
738
744
|
...installedMcp,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "@earendil-works/pi-coding-agent";
|
|
29
29
|
import { type TSchema, Type } from "typebox";
|
|
30
30
|
import { classifyAwsSdkError } from "../utils/aws-error-classifier";
|
|
31
|
+
import { swarmRuntimeInstanceId } from "../utils/multi-runtime";
|
|
31
32
|
import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
|
|
32
33
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
33
34
|
import { readPkgVersion } from "./harness-version";
|
|
@@ -1089,6 +1090,13 @@ export class PiMonoAdapter implements ProviderAdapter {
|
|
|
1089
1090
|
config.agentId,
|
|
1090
1091
|
config.taskId,
|
|
1091
1092
|
);
|
|
1093
|
+
// Same per-boot runtime identity the runner registers with — dispatch
|
|
1094
|
+
// tools require it in multi-runtime mode, and it travels as request
|
|
1095
|
+
// context, not as a tool argument.
|
|
1096
|
+
const runtimeInstanceId = swarmRuntimeInstanceId();
|
|
1097
|
+
if (runtimeInstanceId) {
|
|
1098
|
+
mcpClient.customHeaders["X-Runtime-Instance-ID"] = runtimeInstanceId;
|
|
1099
|
+
}
|
|
1092
1100
|
await mcpClient.initialize();
|
|
1093
1101
|
const tools = await mcpClient.listTools();
|
|
1094
1102
|
customTools = mcpToolsToDefinitions(mcpClient, tools);
|
|
@@ -84,6 +84,30 @@ Unlike token rates — where a missing rate marks the whole row
|
|
|
84
84
|
That asymmetry is deliberate (a small request fee shouldn't unprice an entire
|
|
85
85
|
session) and is documented in the cost-and-context-computation guide.
|
|
86
86
|
|
|
87
|
+
## Attribution reporting contract
|
|
88
|
+
|
|
89
|
+
Usage reporting keeps total spend separate from the cost population that can
|
|
90
|
+
truthfully carry a human requester:
|
|
91
|
+
|
|
92
|
+
- `attributableCostUsd` is total cost minus structurally human-free work;
|
|
93
|
+
coverage is `attributedCostUsd / attributableCostUsd`.
|
|
94
|
+
- `excludedCostUsd` and `excludedTaskCount` retain visibility into the removed
|
|
95
|
+
population. A stale requester id on structurally human-free work does not put
|
|
96
|
+
that cost back into either side of the coverage ratio.
|
|
97
|
+
- Human-free seeds are the `heartbeat`, `heartbeat-checklist`, and
|
|
98
|
+
`boot-triage` task types; legacy JSON tag rows matched by the `heartbeat`
|
|
99
|
+
tags `LIKE` check; creatorless schedules (including their workflow roots);
|
|
100
|
+
and requester-less system follow-ups of requester-less parents.
|
|
101
|
+
- The classification propagates through requester-less descendants. An
|
|
102
|
+
explicitly attributed child is a human handoff and stops propagation down
|
|
103
|
+
that branch.
|
|
104
|
+
|
|
105
|
+
The per-person report uses the same requester data model without grouping the
|
|
106
|
+
cost denominator or turning it into a leaderboard. Human-requested root tasks
|
|
107
|
+
determine Problems Initiated and Problems Shipped; their full task trees
|
|
108
|
+
determine Agents, Repos, and Surfaces Reached. Requester-less autonomous roots
|
|
109
|
+
and heartbeat-classified roots are omitted, and the metrics remain separate.
|
|
110
|
+
|
|
87
111
|
## Provider pricing caveats
|
|
88
112
|
|
|
89
113
|
- **Claude Sonnet 5 introductory rate:** Anthropic's pricing page lists
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { getDb } from "./be/db";
|
|
2
|
+
import { getSlackApp } from "./slack/app";
|
|
3
|
+
import { scrubSecrets } from "./utils/secret-scrubber";
|
|
4
|
+
|
|
5
|
+
export const QUEUE_STALL_THRESHOLD_MS = 30 * 60 * 1000;
|
|
6
|
+
export const QUEUE_STALL_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|
7
|
+
const QUEUE_STALL_STARTUP_DELAY_MS = 10_000;
|
|
8
|
+
|
|
9
|
+
export interface QueueStallSnapshot {
|
|
10
|
+
claimableCount: number;
|
|
11
|
+
oldestTaskId: string | null;
|
|
12
|
+
oldestClaimableAt: string | null;
|
|
13
|
+
oldestAgeMs: number;
|
|
14
|
+
recentPickupCount: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type QueueStallNotifier = (message: string) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
let alarmInterval: ReturnType<typeof setInterval> | null = null;
|
|
20
|
+
let startupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
let checkInFlight = false;
|
|
22
|
+
let alarmActive = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read the queue denominator and oldest age in one snapshot. Tasks with unmet
|
|
26
|
+
* dependencies are excluded because a worker cannot start them yet.
|
|
27
|
+
*/
|
|
28
|
+
export function getQueueStallSnapshot(now = new Date()): QueueStallSnapshot {
|
|
29
|
+
const oldest = getDb()
|
|
30
|
+
.prepare<
|
|
31
|
+
{ claimableCount: number; oldestTaskId: string | null; oldestClaimableAt: string | null },
|
|
32
|
+
[]
|
|
33
|
+
>(
|
|
34
|
+
`WITH queued AS (
|
|
35
|
+
SELECT
|
|
36
|
+
t.id,
|
|
37
|
+
t.dependsOn,
|
|
38
|
+
COALESCE(
|
|
39
|
+
(
|
|
40
|
+
SELECT MAX(entry.createdAt)
|
|
41
|
+
FROM agent_log entry
|
|
42
|
+
WHERE entry.taskId = t.id
|
|
43
|
+
AND entry.newValue = t.status
|
|
44
|
+
AND entry.eventType IN (
|
|
45
|
+
'task_created',
|
|
46
|
+
'task_status_change',
|
|
47
|
+
'task_released',
|
|
48
|
+
'task_accepted',
|
|
49
|
+
'task_rejected'
|
|
50
|
+
)
|
|
51
|
+
),
|
|
52
|
+
t.createdAt
|
|
53
|
+
) AS queueEnteredAt
|
|
54
|
+
FROM agent_tasks t
|
|
55
|
+
WHERE t.status IN ('pending', 'unassigned')
|
|
56
|
+
),
|
|
57
|
+
claimable AS (
|
|
58
|
+
SELECT
|
|
59
|
+
queued.id,
|
|
60
|
+
MAX(
|
|
61
|
+
queued.queueEnteredAt,
|
|
62
|
+
COALESCE(
|
|
63
|
+
(
|
|
64
|
+
SELECT MAX(COALESCE(prerequisite.finishedAt, prerequisite.createdAt))
|
|
65
|
+
FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
|
|
66
|
+
JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
|
|
67
|
+
),
|
|
68
|
+
queued.queueEnteredAt
|
|
69
|
+
)
|
|
70
|
+
) AS claimableAt
|
|
71
|
+
FROM queued
|
|
72
|
+
WHERE NOT EXISTS (
|
|
73
|
+
SELECT 1
|
|
74
|
+
FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
|
|
75
|
+
LEFT JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
|
|
76
|
+
WHERE prerequisite.id IS NULL OR prerequisite.status != 'completed'
|
|
77
|
+
)
|
|
78
|
+
),
|
|
79
|
+
oldest AS (
|
|
80
|
+
SELECT id, claimableAt FROM claimable ORDER BY claimableAt ASC LIMIT 1
|
|
81
|
+
)
|
|
82
|
+
SELECT
|
|
83
|
+
(SELECT COUNT(*) FROM claimable) AS claimableCount,
|
|
84
|
+
(SELECT id FROM oldest) AS oldestTaskId,
|
|
85
|
+
(SELECT claimableAt FROM oldest) AS oldestClaimableAt`,
|
|
86
|
+
)
|
|
87
|
+
.get();
|
|
88
|
+
|
|
89
|
+
const pickupCutoff = new Date(now.getTime() - QUEUE_STALL_THRESHOLD_MS).toISOString();
|
|
90
|
+
const recentPickupCount =
|
|
91
|
+
getDb()
|
|
92
|
+
.prepare<{ count: number }, [string]>(
|
|
93
|
+
`SELECT COUNT(*) AS count
|
|
94
|
+
FROM agent_log
|
|
95
|
+
WHERE eventType IN ('task_status_change', 'task_claimed')
|
|
96
|
+
AND oldValue IN ('pending', 'unassigned')
|
|
97
|
+
AND newValue = 'in_progress'
|
|
98
|
+
AND createdAt >= ?`,
|
|
99
|
+
)
|
|
100
|
+
.get(pickupCutoff)?.count ?? 0;
|
|
101
|
+
|
|
102
|
+
const oldestClaimableAt = oldest?.oldestClaimableAt ?? null;
|
|
103
|
+
return {
|
|
104
|
+
claimableCount: oldest?.claimableCount ?? 0,
|
|
105
|
+
oldestTaskId: oldest?.oldestTaskId ?? null,
|
|
106
|
+
oldestClaimableAt,
|
|
107
|
+
oldestAgeMs: oldestClaimableAt
|
|
108
|
+
? Math.max(0, now.getTime() - new Date(oldestClaimableAt).getTime())
|
|
109
|
+
: 0,
|
|
110
|
+
recentPickupCount,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isQueueStalled(snapshot: QueueStallSnapshot): boolean {
|
|
115
|
+
return snapshot.claimableCount > 0 && snapshot.oldestAgeMs >= QUEUE_STALL_THRESHOLD_MS;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatAge(ageMs: number): string {
|
|
119
|
+
return `${Math.floor(ageMs / 60_000)}m`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatCheckFailure(error: unknown): string {
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
+
return `[Queue Stall Alarm] Check or notification failed: ${scrubSecrets(message)}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function notifySlack(message: string): Promise<void> {
|
|
128
|
+
const channel = process.env.SLACK_ALERTS_CHANNEL?.trim();
|
|
129
|
+
if (!channel) throw new Error("SLACK_ALERTS_CHANNEL is not configured");
|
|
130
|
+
|
|
131
|
+
const app = getSlackApp();
|
|
132
|
+
if (!app) throw new Error("Slack is not connected");
|
|
133
|
+
|
|
134
|
+
await app.client.chat.postMessage({
|
|
135
|
+
channel,
|
|
136
|
+
text: message,
|
|
137
|
+
unfurl_links: false,
|
|
138
|
+
unfurl_media: false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Run one detector tick. Exported so tests and operators can exercise it directly. */
|
|
143
|
+
export async function checkQueueStall(
|
|
144
|
+
now = new Date(),
|
|
145
|
+
notify: QueueStallNotifier = notifySlack,
|
|
146
|
+
): Promise<QueueStallSnapshot> {
|
|
147
|
+
const snapshot = getQueueStallSnapshot(now);
|
|
148
|
+
const stalled = isQueueStalled(snapshot);
|
|
149
|
+
|
|
150
|
+
if (stalled && !alarmActive) {
|
|
151
|
+
await notify(
|
|
152
|
+
`🚨 *Agent Swarm queue pickup stalled*\n` +
|
|
153
|
+
`Claimable tasks: *${snapshot.claimableCount}*\n` +
|
|
154
|
+
`Oldest wait: *${formatAge(snapshot.oldestAgeMs)}* (task \`${snapshot.oldestTaskId}\`)\n` +
|
|
155
|
+
`Pickups in the last 30m: *${snapshot.recentPickupCount}*\n\n` +
|
|
156
|
+
"This alarm runs in the API process and does not require an agent to claim a task.",
|
|
157
|
+
);
|
|
158
|
+
alarmActive = true;
|
|
159
|
+
console.error(
|
|
160
|
+
`[Queue Stall Alarm] Fired: claimable=${snapshot.claimableCount}, oldest=${snapshot.oldestTaskId}, age=${formatAge(snapshot.oldestAgeMs)}, recent_pickups=${snapshot.recentPickupCount}`,
|
|
161
|
+
);
|
|
162
|
+
} else if (!stalled && alarmActive) {
|
|
163
|
+
await notify(
|
|
164
|
+
`✅ *Agent Swarm queue pickup recovered*\nClaimable tasks: *${snapshot.claimableCount}*\nPickups in the last 30m: *${snapshot.recentPickupCount}*`,
|
|
165
|
+
);
|
|
166
|
+
alarmActive = false;
|
|
167
|
+
console.log("[Queue Stall Alarm] Recovery notification sent");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return snapshot;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function scheduleCheck(): void {
|
|
174
|
+
if (checkInFlight) return;
|
|
175
|
+
checkInFlight = true;
|
|
176
|
+
checkQueueStall()
|
|
177
|
+
.catch((error) => {
|
|
178
|
+
console.error(formatCheckFailure(error));
|
|
179
|
+
})
|
|
180
|
+
.finally(() => {
|
|
181
|
+
checkInFlight = false;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function startQueueStallAlarm(): void {
|
|
186
|
+
if (alarmInterval || startupTimeout) return;
|
|
187
|
+
|
|
188
|
+
console.log(
|
|
189
|
+
`[Queue Stall Alarm] Starting (threshold ${formatAge(QUEUE_STALL_THRESHOLD_MS)}, interval ${formatAge(QUEUE_STALL_CHECK_INTERVAL_MS)})`,
|
|
190
|
+
);
|
|
191
|
+
startupTimeout = setTimeout(() => {
|
|
192
|
+
startupTimeout = null;
|
|
193
|
+
scheduleCheck();
|
|
194
|
+
}, QUEUE_STALL_STARTUP_DELAY_MS);
|
|
195
|
+
alarmInterval = setInterval(scheduleCheck, QUEUE_STALL_CHECK_INTERVAL_MS);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function stopQueueStallAlarm(): void {
|
|
199
|
+
if (startupTimeout) {
|
|
200
|
+
clearTimeout(startupTimeout);
|
|
201
|
+
startupTimeout = null;
|
|
202
|
+
}
|
|
203
|
+
if (alarmInterval) {
|
|
204
|
+
clearInterval(alarmInterval);
|
|
205
|
+
alarmInterval = null;
|
|
206
|
+
}
|
|
207
|
+
checkInFlight = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const _test = {
|
|
211
|
+
formatCheckFailure,
|
|
212
|
+
resetState(): void {
|
|
213
|
+
stopQueueStallAlarm();
|
|
214
|
+
alarmActive = false;
|
|
215
|
+
},
|
|
216
|
+
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getDb,
|
|
5
5
|
getDueScheduledTasks,
|
|
6
6
|
getScheduledTaskById,
|
|
7
|
+
getUserById,
|
|
7
8
|
getWorkflow,
|
|
8
9
|
updateScheduledTask,
|
|
9
10
|
} from "@/be/db";
|
|
@@ -117,10 +118,16 @@ export interface DispatchScheduleResult {
|
|
|
117
118
|
task?: AgentTask;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function withoutDeletedRequester(schedule: ScheduledTask): ScheduledTask {
|
|
122
|
+
if (!schedule.createdBy || getUserById(schedule.createdBy)) return schedule;
|
|
123
|
+
return { ...schedule, createdBy: undefined };
|
|
124
|
+
}
|
|
125
|
+
|
|
120
126
|
export async function dispatchScheduleTarget(
|
|
121
127
|
schedule: ScheduledTask,
|
|
122
128
|
extraTags: string[] = [],
|
|
123
129
|
): Promise<DispatchScheduleResult> {
|
|
130
|
+
schedule = withoutDeletedRequester(schedule);
|
|
124
131
|
switch (schedule.targetType) {
|
|
125
132
|
case "workflow": {
|
|
126
133
|
if (!schedule.workflowId) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, rm } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import type { ScriptRun } from "../types";
|
|
5
6
|
import {
|
|
6
7
|
buildSandboxedCommand,
|
|
@@ -40,7 +41,7 @@ export interface ScriptExecutor {
|
|
|
40
41
|
|
|
41
42
|
export function getScriptWorkflowHarnessPath(): string {
|
|
42
43
|
const runtimeDir = process.env.SCRIPT_WORKFLOW_RUNTIME_DIR;
|
|
43
|
-
if (!runtimeDir) return new URL("./harness.ts", import.meta.url)
|
|
44
|
+
if (!runtimeDir) return fileURLToPath(new URL("./harness.ts", import.meta.url));
|
|
44
45
|
|
|
45
46
|
const bundledHarness = `${resolve(runtimeDir)}/harness.bundle.js`;
|
|
46
47
|
if (!existsSync(bundledHarness)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import {
|
|
2
3
|
buildSandboxedCommand,
|
|
3
4
|
readStreamCapped,
|
|
@@ -120,7 +121,7 @@ export class NativeScriptExecutor implements ScriptExecutor {
|
|
|
120
121
|
// subprocesses cannot access. Use the pre-built bundle from real filesystem instead.
|
|
121
122
|
const harnessPath = process.env.SCRIPT_RUNTIME_DIR
|
|
122
123
|
? `${process.env.SCRIPT_RUNTIME_DIR}/eval-harness.bundle.js`
|
|
123
|
-
: new URL("../eval-harness.ts", import.meta.url)
|
|
124
|
+
: fileURLToPath(new URL("../eval-harness.ts", import.meta.url));
|
|
124
125
|
const controller = new AbortController();
|
|
125
126
|
let timedOut = false;
|
|
126
127
|
let killed = input.signal?.aborted ?? false;
|
|
@@ -10,6 +10,11 @@ export type SwarmConfigPayload = {
|
|
|
10
10
|
apiKey: { value: string; isSecret: true };
|
|
11
11
|
agentId: { value: string; isSecret: false };
|
|
12
12
|
mcpBaseUrl: { value: string; isSecret: false };
|
|
13
|
+
/**
|
|
14
|
+
* Per-boot runtime identity of the invoking worker process. System
|
|
15
|
+
* context like the agent identity — scripts never supply or override it.
|
|
16
|
+
*/
|
|
17
|
+
runtimeInstanceId?: { value: string; isSecret: false };
|
|
13
18
|
};
|
|
14
19
|
user: Record<string, { value: string; isSecret: boolean }>;
|
|
15
20
|
egressSecrets?: EgressSecretEntry[];
|
|
@@ -19,6 +19,8 @@ export type RunScriptInput = {
|
|
|
19
19
|
args?: unknown;
|
|
20
20
|
fsMode?: ScriptFsMode;
|
|
21
21
|
agentId: string;
|
|
22
|
+
/** Per-boot runtime identity of the invoking worker; system context, never script input. */
|
|
23
|
+
runtimeInstanceId?: string;
|
|
22
24
|
signal?: AbortSignal;
|
|
23
25
|
timeoutMs?: number;
|
|
24
26
|
mcpBaseUrl?: string;
|
|
@@ -50,6 +52,9 @@ async function buildConfigPayload(input: RunScriptInput): Promise<SwarmConfigPay
|
|
|
50
52
|
value: input.mcpBaseUrl ?? process.env.MCP_BASE_URL ?? "http://localhost:3013",
|
|
51
53
|
isSecret: false,
|
|
52
54
|
},
|
|
55
|
+
...(input.runtimeInstanceId
|
|
56
|
+
? { runtimeInstanceId: { value: input.runtimeInstanceId, isSecret: false as const } }
|
|
57
|
+
: {}),
|
|
53
58
|
},
|
|
54
59
|
user: input.userConfig ?? {},
|
|
55
60
|
egressSecrets: input.egressSecrets ?? (await buildEgressSecrets()),
|
|
@@ -5,6 +5,8 @@ export class SwarmConfig {
|
|
|
5
5
|
readonly apiKey: RedactedValue<string>;
|
|
6
6
|
readonly agentId: RedactedValue<string>;
|
|
7
7
|
readonly mcpBaseUrl: RedactedValue<string>;
|
|
8
|
+
/** Per-boot runtime identity of the invoking worker; system context, never script input. */
|
|
9
|
+
readonly runtimeInstanceId: RedactedValue<string> | undefined;
|
|
8
10
|
|
|
9
11
|
private readonly userValues: Map<string, RedactedValue<string>>;
|
|
10
12
|
|
|
@@ -21,6 +23,12 @@ export class SwarmConfig {
|
|
|
21
23
|
type: "system",
|
|
22
24
|
isSecret: payload.system.mcpBaseUrl.isSecret,
|
|
23
25
|
});
|
|
26
|
+
this.runtimeInstanceId = payload.system.runtimeInstanceId
|
|
27
|
+
? Redacted.make(payload.system.runtimeInstanceId.value, {
|
|
28
|
+
type: "system",
|
|
29
|
+
isSecret: payload.system.runtimeInstanceId.isSecret,
|
|
30
|
+
})
|
|
31
|
+
: undefined;
|
|
24
32
|
this.userValues = new Map(
|
|
25
33
|
Object.entries(payload.user ?? {}).map(([key, value]) => [
|
|
26
34
|
key,
|
|
@@ -14,6 +14,12 @@ function headers(config: SwarmConfig): Record<string, string> {
|
|
|
14
14
|
return {
|
|
15
15
|
Authorization: `Bearer ${Redacted.value(config.apiKey)}`,
|
|
16
16
|
"X-Agent-ID": Redacted.value(config.agentId),
|
|
17
|
+
// Per-boot identity of the invoking worker process (system context, not
|
|
18
|
+
// script input) — work-acquisition endpoints gate on it in multi-runtime
|
|
19
|
+
// mode.
|
|
20
|
+
...(config.runtimeInstanceId
|
|
21
|
+
? { "X-Runtime-Instance-ID": Redacted.value(config.runtimeInstanceId) }
|
|
22
|
+
: {}),
|
|
17
23
|
"Content-Type": "application/json",
|
|
18
24
|
};
|
|
19
25
|
}
|
|
@@ -168,6 +174,7 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
|
|
|
168
174
|
hideCompleted: body.hideCompleted,
|
|
169
175
|
consecutiveErrorsMin: body.consecutiveErrorsMin,
|
|
170
176
|
lastRunStatus: body.lastRunStatus,
|
|
177
|
+
fields: body.includeFull ? "full" : undefined,
|
|
171
178
|
}),
|
|
172
179
|
};
|
|
173
180
|
case "schedule_create":
|
|
@@ -158,6 +158,7 @@ declare module "swarm-sdk" {
|
|
|
158
158
|
hideCompleted?: boolean;
|
|
159
159
|
consecutiveErrorsMin?: number;
|
|
160
160
|
lastRunStatus?: "failed" | "succeeded";
|
|
161
|
+
includeFull?: boolean;
|
|
161
162
|
}): Promise<unknown>;
|
|
162
163
|
// --- scripts ---
|
|
163
164
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
@@ -140,6 +140,7 @@ declare module "swarm-sdk" {
|
|
|
140
140
|
hideCompleted?: boolean;
|
|
141
141
|
consecutiveErrorsMin?: number;
|
|
142
142
|
lastRunStatus?: "failed" | "succeeded";
|
|
143
|
+
includeFull?: boolean;
|
|
143
144
|
}): Promise<unknown>;
|
|
144
145
|
// --- scripts ---
|
|
145
146
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
@@ -128,6 +128,7 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
|
|
|
128
128
|
if ("error" in body) throw new Error("unexpected error response");
|
|
129
129
|
expect(body.trigger?.type).toBe("task_assigned");
|
|
130
130
|
expect((body.trigger as { taskId: string }).taskId).toBe(task.id);
|
|
131
|
+
expect(getAgentById(worker.id)?.status).toBe("busy");
|
|
131
132
|
});
|
|
132
133
|
|
|
133
134
|
test("pending task trigger includes requester role and notes", async () => {
|
|
@@ -272,6 +273,25 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
|
|
|
272
273
|
expect(claimedTriggers).toHaveLength(1);
|
|
273
274
|
expect(nullTriggers).toHaveLength(1);
|
|
274
275
|
});
|
|
276
|
+
|
|
277
|
+
test("pool claim marks the agent busy before the poll transaction returns", async () => {
|
|
278
|
+
const worker = createAgent({
|
|
279
|
+
name: "claim-status",
|
|
280
|
+
isLead: false,
|
|
281
|
+
status: "idle",
|
|
282
|
+
maxTasks: 1,
|
|
283
|
+
});
|
|
284
|
+
const pooled = createTaskExtended("claim status task", {});
|
|
285
|
+
|
|
286
|
+
expect(getAgentById(worker.id)?.status).toBe("idle");
|
|
287
|
+
|
|
288
|
+
const { body } = await callPoll(worker.id);
|
|
289
|
+
|
|
290
|
+
if ("error" in body) throw new Error("unexpected error response");
|
|
291
|
+
expect(body.trigger?.type).toBe("task_assigned");
|
|
292
|
+
expect((body.trigger as { taskId: string }).taskId).toBe(pooled.id);
|
|
293
|
+
expect(getAgentById(worker.id)?.status).toBe("busy");
|
|
294
|
+
});
|
|
275
295
|
});
|
|
276
296
|
|
|
277
297
|
// ─── MCP task-action accept tests ───────────────────────────────────────────
|