@desplega.ai/agent-swarm 1.164.1 → 1.166.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/acp-adapter-yzfxn8vj.js +23 -0
- package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
- package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
- package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
- package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
- package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
- package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
- package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
- package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
- package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
- package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
- package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
- package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
- package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
- package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
- package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
- package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
- package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
- package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
- package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
- package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
- package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
- package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
- package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
- package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
- package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
- package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
- package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
- package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
- package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
- package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
- package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
- package/dist/cli-fn697bnt.js +126 -0
- package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
- package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
- package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
- package/dist/cli-j2jbr8af.js +516 -0
- package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
- package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
- package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
- package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
- package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
- package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
- package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
- package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
- package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
- package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
- package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
- package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
- package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
- package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
- package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
- package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
- package/dist/cli-zp67qg5x.js +92 -0
- package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
- package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
- package/dist/cli.js +79 -26
- package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
- package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
- package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
- package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
- package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
- package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
- package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
- package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
- package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
- package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
- package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
- package/dist/grok-adapter-ar9cw9jg.js +29 -0
- package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
- package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
- package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
- package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
- package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
- package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
- package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
- package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
- package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
- package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
- package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
- package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
- package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
- package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
- package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
- package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
- package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
- package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
- package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
- package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
- package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
- package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
- package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
- package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
- package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +457 -31
- package/package.json +14 -6
- package/plugin/commands/create-pr.md +7 -1
- package/plugin/pi-skills/create-pr/SKILL.md +7 -1
- package/src/agentmail/app.ts +5 -4
- package/src/be/automation-preflight.ts +11 -9
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/connector-codes.ts +81 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/197_connector_codes.sql +23 -0
- package/src/be/migrations/198_permission_audit_guest.sql +33 -0
- package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/model-tier-resolution.ts +2 -1
- package/src/be/model-validation.ts +1 -1
- package/src/be/pricing-normalize.ts +3 -0
- package/src/be/rbac-audit.ts +2 -1
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scripts/typecheck.ts +23 -2
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +14 -0
- package/src/be/users.ts +7 -1
- package/src/be/workflow-replay.ts +77 -0
- package/src/cli.tsx +7 -0
- package/src/commands/provider-credentials.ts +46 -8
- package/src/commands/runner.ts +2 -10
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/github/app.ts +4 -4
- package/src/heartbeat/heartbeat.ts +20 -8
- package/src/hooks/pr-body-guard.ts +1 -1
- package/src/http/agents.ts +1 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +3 -0
- package/src/http/auth.ts +4 -1
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/codex-oauth-keep-warm.ts +2 -2
- package/src/http/config-values.ts +59 -0
- package/src/http/config.ts +5 -0
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/extensions.ts +1 -0
- package/src/http/index.ts +23 -6
- package/src/http/integrations.ts +1 -38
- package/src/http/ip-rate-limit.ts +82 -0
- package/src/http/kv.ts +38 -4
- package/src/http/mcp-servers.ts +1 -0
- package/src/http/memory.ts +9 -0
- package/src/http/models-catalog.ts +2 -0
- package/src/http/operator-actor.ts +4 -0
- package/src/http/page-proxy.ts +22 -0
- package/src/http/pages.ts +4 -2
- package/src/http/realtime.ts +1 -1
- package/src/http/request-principal.ts +14 -1
- package/src/http/script-connections.ts +8 -0
- package/src/http/script-runs.ts +12 -4
- package/src/http/scripts.ts +15 -0
- package/src/http/session-cost-recompute.ts +3 -2
- package/src/http/tasks.ts +9 -0
- package/src/http/users.ts +226 -0
- package/src/http/workflows.ts +3 -2
- package/src/http.ts +1 -0
- package/src/integrations/kapso/client.ts +3 -1
- package/src/jira/app.ts +2 -2
- package/src/kv-view.ts +324 -0
- package/src/linear/app.ts +2 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/prompts/memories.ts +42 -1
- package/src/providers/acp-adapter.ts +85 -24
- package/src/providers/acp-target-catalog.ts +54 -1
- package/src/providers/acp-targets.ts +358 -4
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/grok-adapter.ts +186 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/pricing-sources.md +6 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- package/src/rbac/can.ts +4 -3
- package/src/rbac/legacy-policy.ts +3 -3
- package/src/rbac/types.ts +6 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +27 -8
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
- package/src/server-user.ts +2 -20
- package/src/server.ts +3 -1
- package/src/slack/app.ts +4 -0
- package/src/slack/config.ts +3 -1
- package/src/slack/egress-scrub.ts +87 -0
- package/src/tests/acp-adapter.test.ts +191 -2
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/budget-user-scope.test.ts +7 -21
- package/src/tests/check-pr-body.test.ts +149 -6
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/claude-adapter-binary.test.ts +1 -1
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
- package/src/tests/console-scrub.test.ts +141 -0
- package/src/tests/credential-check.test.ts +90 -0
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/explicit-model-validation.test.ts +81 -0
- package/src/tests/extensions-pre-task.test.ts +33 -1
- package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
- package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +647 -0
- package/src/tests/heartbeat-checklist.test.ts +44 -0
- package/src/tests/integration-disable-flags.test.ts +156 -0
- package/src/tests/kv-http.test.ts +34 -0
- package/src/tests/kv-tool.test.ts +274 -1
- package/src/tests/kv-view.test.ts +147 -0
- package/src/tests/mcp-user-route.test.ts +257 -15
- package/src/tests/memory-injection-outcome.test.ts +143 -0
- package/src/tests/model-tier-resolution.test.ts +76 -3
- package/src/tests/page-guest-session.test.ts +424 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-viewer-auth.test.ts +22 -1
- package/src/tests/pr-body-leaks.test.ts +157 -2
- package/src/tests/preload.ts +10 -0
- package/src/tests/provider-registration.test.ts +3 -1
- package/src/tests/rbac-audit.test.ts +32 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +126 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scripts-mcp-e2e.test.ts +42 -6
- package/src/tests/scripts-runtime.test.ts +37 -0
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +317 -1
- package/src/tests/session-costs-golden.test.ts +17 -0
- package/src/tests/slack-egress-scrub.test.ts +216 -0
- package/src/tests/swarm-tool-result-gate.test.ts +143 -30
- package/src/tests/task-tools-ctx.test.ts +3 -0
- package/src/tests/user-token-routes.test.ts +342 -3
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/kv/kv-get.ts +136 -6
- package/src/tools/send-task.ts +61 -17
- package/src/tools/utils.ts +186 -43
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +16 -1
- package/src/utils/console-scrub-install.ts +5 -0
- package/src/utils/console-scrub.ts +57 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/page-session.ts +6 -2
- package/src/utils/pr-body-leaks.ts +149 -10
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +662 -36
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/notify.ts +6 -1
- package/src/workflows/executors/system-one-decision.ts +31 -12
- package/src/workflows/executors/system-one-openai-wire.ts +168 -0
- package/src/workflows/executors/system-one-providers.ts +112 -11
- package/src/workflows/executors/system-one-wire.ts +79 -0
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +5 -5
- package/templates/skills/agent-browser/config.json +1 -1
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/artifacts/content.md +17 -7
- package/templates/skills/jira-interaction/config.json +1 -1
- package/templates/skills/jira-interaction/content.md +56 -205
- package/templates/skills/linear-interaction/config.json +1 -1
- package/templates/skills/linear-interaction/content.md +31 -50
- package/templates/skills/pages/content.md +8 -4
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/workflow-iterate/content.md +4 -4
- package/tsconfig.json +3 -3
- package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
- package/dist/cli-0a7r5331.js +0 -168
- package/dist/cli-93rxw9yd.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-dgnj0zcc.js +0 -40
- package/dist/cli-qt5wzq6v.js +0 -256
- package/dist/cli-v7fqehqd.js +0 -9281
- package/dist/cli-vbr859rz.js +0 -572
- package/src/be/boot-scrub-logs.ts +0 -132
|
@@ -16,7 +16,12 @@ import type { WorkflowRunStep } from "../types";
|
|
|
16
16
|
import { shapeApprovalResolution } from "./approval-resolution";
|
|
17
17
|
import { loadCompletedStepRouting } from "./completed-step-routing";
|
|
18
18
|
import { FAILED_TASK_OUTPUT_PREFIX } from "./constants";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
failRunOnUnreadableReplay,
|
|
21
|
+
findReadyNodes,
|
|
22
|
+
isWorkflowRunActive,
|
|
23
|
+
walkGraph,
|
|
24
|
+
} from "./engine";
|
|
20
25
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
21
26
|
import { getSecretInputKeys } from "./input";
|
|
22
27
|
import { finalizeOrWait, resumeWaitState } from "./resume";
|
|
@@ -113,6 +118,7 @@ async function recoverRunningRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
113
118
|
}
|
|
114
119
|
recovered++;
|
|
115
120
|
} catch (err) {
|
|
121
|
+
if (await failRunOnUnreadableReplay(runId, err)) continue;
|
|
116
122
|
console.error(`[workflows] Failed to recover running run ${runId}:`, err);
|
|
117
123
|
}
|
|
118
124
|
}
|
|
@@ -248,6 +254,7 @@ async function recoverWaitingRuns(registry: ExecutorRegistry): Promise<number> {
|
|
|
248
254
|
}
|
|
249
255
|
recovered++;
|
|
250
256
|
} catch (err) {
|
|
257
|
+
if (await failRunOnUnreadableReplay(stuck.runId, err)) continue;
|
|
251
258
|
console.error(`[workflows] Failed to recover waiting run ${stuck.runId}:`, err);
|
|
252
259
|
}
|
|
253
260
|
}
|
|
@@ -349,6 +356,7 @@ async function recoverApprovalWaitingRuns(registry: ExecutorRegistry): Promise<n
|
|
|
349
356
|
}
|
|
350
357
|
recovered++;
|
|
351
358
|
} catch (err) {
|
|
359
|
+
if (await failRunOnUnreadableReplay(stuck.runId, err)) continue;
|
|
352
360
|
console.error(`[workflows] Failed to recover approval-waiting run ${stuck.runId}:`, err);
|
|
353
361
|
}
|
|
354
362
|
}
|
|
@@ -391,6 +399,7 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
|
|
|
391
399
|
await resumeWaitState(stuck.waitId, resumeStatus, payload, registry);
|
|
392
400
|
recovered++;
|
|
393
401
|
} catch (err) {
|
|
402
|
+
if (await failRunOnUnreadableReplay(stuck.runId, err)) continue;
|
|
394
403
|
console.error(`[workflows] Failed to recover wait-state ${stuck.waitId}:`, err);
|
|
395
404
|
}
|
|
396
405
|
}
|
package/src/workflows/resume.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
updateWorkflowRun,
|
|
18
18
|
updateWorkflowRunStep,
|
|
19
19
|
} from "../be/db";
|
|
20
|
+
import type { WaitStateRow } from "../types";
|
|
20
21
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
21
22
|
import {
|
|
22
23
|
type ApprovalSlackClient,
|
|
@@ -26,7 +27,7 @@ import { shapeApprovalResolution } from "./approval-resolution";
|
|
|
26
27
|
import { loadCompletedStepRouting } from "./completed-step-routing";
|
|
27
28
|
import { FAILED_TASK_OUTPUT_PREFIX } from "./constants";
|
|
28
29
|
import { getNextTargets } from "./definition";
|
|
29
|
-
import { findReadyNodes, hasRunningStep, walkGraph } from "./engine";
|
|
30
|
+
import { failRunOnUnreadableReplay, findReadyNodes, hasRunningStep, walkGraph } from "./engine";
|
|
30
31
|
import type { WorkflowEventBus } from "./event-bus";
|
|
31
32
|
import { workflowEventBus } from "./event-bus";
|
|
32
33
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
@@ -127,6 +128,23 @@ export function setupWorkflowResumeListener(
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Run one live resume of a waiting step. A sealed replay copy that cannot be
|
|
133
|
+
* opened fails the step and its run now, instead of logging and leaving the
|
|
134
|
+
* run `waiting` until a recovery sweep. Any other error rethrows.
|
|
135
|
+
*/
|
|
136
|
+
async function failClosedOnUnreadableReplay(
|
|
137
|
+
runId: string,
|
|
138
|
+
stepId: string,
|
|
139
|
+
resume: () => Promise<void>,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
try {
|
|
142
|
+
await resume();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (!(await failRunOnUnreadableReplay(runId, err, stepId))) throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
130
148
|
/**
|
|
131
149
|
* Resume a workflow after a linked task completes.
|
|
132
150
|
*
|
|
@@ -138,6 +156,15 @@ export function setupWorkflowResumeListener(
|
|
|
138
156
|
async function resumeFromTaskCompletion(
|
|
139
157
|
event: TaskEvent,
|
|
140
158
|
registry: ExecutorRegistry,
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
await failClosedOnUnreadableReplay(event.workflowRunId!, event.workflowRunStepId!, () =>
|
|
161
|
+
resumeFromTaskCompletionUnguarded(event, registry),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function resumeFromTaskCompletionUnguarded(
|
|
166
|
+
event: TaskEvent,
|
|
167
|
+
registry: ExecutorRegistry,
|
|
141
168
|
): Promise<void> {
|
|
142
169
|
const run = await getWorkflowRun(event.workflowRunId!);
|
|
143
170
|
if (!run || (run.status !== "waiting" && run.status !== "running")) return;
|
|
@@ -245,6 +272,17 @@ async function handleTaskFailure(
|
|
|
245
272
|
reason: string,
|
|
246
273
|
registry: ExecutorRegistry,
|
|
247
274
|
options: { retryable: boolean },
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
await failClosedOnUnreadableReplay(event.workflowRunId!, event.workflowRunStepId!, () =>
|
|
277
|
+
handleTaskFailureUnguarded(event, reason, registry, options),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function handleTaskFailureUnguarded(
|
|
282
|
+
event: TaskEvent,
|
|
283
|
+
reason: string,
|
|
284
|
+
registry: ExecutorRegistry,
|
|
285
|
+
options: { retryable: boolean },
|
|
248
286
|
): Promise<void> {
|
|
249
287
|
const run = await getWorkflowRun(event.workflowRunId!);
|
|
250
288
|
if (!run || (run.status !== "waiting" && run.status !== "running")) return;
|
|
@@ -541,6 +579,15 @@ export async function cancelWorkflowRunStep(
|
|
|
541
579
|
async function resumeFromApprovalResolution(
|
|
542
580
|
event: ApprovalEvent,
|
|
543
581
|
registry: ExecutorRegistry,
|
|
582
|
+
): Promise<void> {
|
|
583
|
+
await failClosedOnUnreadableReplay(event.workflowRunId!, event.workflowRunStepId!, () =>
|
|
584
|
+
resumeFromApprovalResolutionUnguarded(event, registry),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function resumeFromApprovalResolutionUnguarded(
|
|
589
|
+
event: ApprovalEvent,
|
|
590
|
+
registry: ExecutorRegistry,
|
|
544
591
|
): Promise<void> {
|
|
545
592
|
const run = await getWorkflowRun(event.workflowRunId!);
|
|
546
593
|
if (!run || (run.status !== "waiting" && run.status !== "running")) return;
|
|
@@ -551,8 +598,6 @@ async function resumeFromApprovalResolution(
|
|
|
551
598
|
const workflow = await getWorkflow(run.workflowId);
|
|
552
599
|
if (!workflow) return;
|
|
553
600
|
|
|
554
|
-
const ctx = (run.context ?? {}) as Record<string, unknown>;
|
|
555
|
-
|
|
556
601
|
if (event.status === "cancelled") {
|
|
557
602
|
console.warn(
|
|
558
603
|
`[workflows] approval ${event.requestId} is cancelled; step ${event.workflowRunStepId} stays waiting for the run cancel path`,
|
|
@@ -560,6 +605,10 @@ async function resumeFromApprovalResolution(
|
|
|
560
605
|
return;
|
|
561
606
|
}
|
|
562
607
|
|
|
608
|
+
// Opens the sealed replay context: after the cancelled bail-out, which leaves
|
|
609
|
+
// the step to the run cancel path.
|
|
610
|
+
const ctx = (run.context ?? {}) as Record<string, unknown>;
|
|
611
|
+
|
|
563
612
|
// Output and port for the approval status. A step parked by an executor other
|
|
564
613
|
// than human-in-the-loop shapes its own (see shapeApprovalResolution).
|
|
565
614
|
const { output: stepOutput, nextPort } = shapeApprovalResolution(
|
|
@@ -643,7 +692,17 @@ export async function resumeWaitState(
|
|
|
643
692
|
if (!result.updated || !result.row) return;
|
|
644
693
|
|
|
645
694
|
const waitRow = result.row;
|
|
695
|
+
await failClosedOnUnreadableReplay(waitRow.workflowRunId, waitRow.workflowRunStepId, () =>
|
|
696
|
+
resumeClaimedWait(waitRow, status, cappedPayload, registry),
|
|
697
|
+
);
|
|
698
|
+
}
|
|
646
699
|
|
|
700
|
+
async function resumeClaimedWait(
|
|
701
|
+
waitRow: WaitStateRow,
|
|
702
|
+
status: "fired" | "timeout",
|
|
703
|
+
cappedPayload: unknown,
|
|
704
|
+
registry: ExecutorRegistry,
|
|
705
|
+
): Promise<void> {
|
|
647
706
|
// 2. Load the surrounding run + step. If anything has moved on (cancelled,
|
|
648
707
|
// failed, retried, etc.), stay quiet.
|
|
649
708
|
const run = await getWorkflowRun(waitRow.workflowRunId);
|
|
@@ -11,6 +11,7 @@ import { checkpointStep, checkpointStepFailure, checkpointStepWaiting } from "./
|
|
|
11
11
|
import { getSuccessors, resolveValidationPort } from "./definition";
|
|
12
12
|
import {
|
|
13
13
|
buildNodeInterpolationCtx,
|
|
14
|
+
failRunOnUnreadableReplay,
|
|
14
15
|
holdWorkflowRun,
|
|
15
16
|
interpolateNodeConfig,
|
|
16
17
|
rehydrateCompletedStepOutputs,
|
|
@@ -200,11 +201,13 @@ export function startRetryPoller(registry: ExecutorRegistry, intervalMs = 5000):
|
|
|
200
201
|
}
|
|
201
202
|
} catch (err) {
|
|
202
203
|
// Execution threw — treat as failure
|
|
204
|
+
if (await failRunOnUnreadableReplay(run.id, err, step.id)) continue;
|
|
203
205
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
204
206
|
const retryPolicy = node.retry || executor.retryPolicy;
|
|
205
207
|
await checkpointStepFailure(run.id, step.id, errorMsg, step.retryCount, retryPolicy);
|
|
206
208
|
}
|
|
207
209
|
} catch (err) {
|
|
210
|
+
if (await failRunOnUnreadableReplay(step.runId, err, step.id)) continue;
|
|
208
211
|
console.error(`[workflows] Retry failed for step ${step.id}:`, err);
|
|
209
212
|
} finally {
|
|
210
213
|
release?.();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "77abe63716623f28177ebe29679c37b5859ef40e",
|
|
3
3
|
"excludedSkills": [
|
|
4
4
|
"feedback"
|
|
5
5
|
],
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"templates/skills/planning/content.md": "543476e4dbb5c352acd686c3929c45cc992d7d033d06f7e9a30f78009b50785c",
|
|
39
39
|
"templates/skills/planning/files/template.md": "c0d5ca63c07934546172322bbd1180e2901dc45e27b5d12ce3dc5c30f5e7a98b",
|
|
40
40
|
"templates/skills/qa/config.json": "c53b01558a691c0343b6270a548e6172af86600a429b789eedb518849f7ea741",
|
|
41
|
-
"templates/skills/qa/content.md": "
|
|
41
|
+
"templates/skills/qa/content.md": "c885095ea814b4c410dbc7aac1e2fde09b94b735d818b6f06153901311a232a0",
|
|
42
42
|
"templates/skills/qa/files/template.md": "1902e4bb65b19ab58c54ab5b63693ad09adecee69079c2c70f58d7d6c6597896",
|
|
43
43
|
"templates/skills/questioning/config.json": "da2d5fe6dfdac1915a0476a7063e62ebeb996b84caff07cc7527a55477656a80",
|
|
44
44
|
"templates/skills/questioning/content.md": "66f826a191e96d0041adca00a7c197a458de8e6fe164c1599b8708098d4109ab",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-browser
|
|
3
|
-
description: "Drive a real browser with the agent-browser CLI (headless Chromium over CDP). Use for any browser task: verifying a UI change, taking a screenshot, walking a web page, filling a form, extracting page text, reproducing a frontend bug, or attaching UI evidence to a PR, issue, Linear comment, or Slack thread. Starts with a `command -v agent-browser` check and falls back to manual QA on the slim worker image. Screenshots are
|
|
3
|
+
description: "Drive a real browser with the agent-browser CLI (headless Chromium over CDP). Use for any browser task: verifying a UI change, taking a screenshot, walking a web page, filling a form, extracting page text, reproducing a frontend bug, or attaching UI evidence to a PR, issue, Linear comment, or Slack thread. Starts with a `command -v agent-browser` check and falls back to manual QA on the slim worker image. Screenshots are kept on agent-fs and embedded in GitHub as user attachments. This is the only browser automation tool; do not use qa-use."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# agent-browser
|
|
@@ -64,21 +64,21 @@ ffmpeg -y -i in.mp4 -filter:v "setpts=PTS/1.5,fps=30" -an \
|
|
|
64
64
|
|
|
65
65
|
Speed-up is a post-process step: `record start` has no speed or human flag. Keep `fps=30` to preserve a steady 30 fps output rather than uneven frame drops at an inferred rate. `-an` disables audio because agent-browser recordings carry no audio track. The worker ships only `ffmpeg`, not `ffprobe`; inspect duration with `ffmpeg -i out-1.5x.mp4 2>&1 | grep Duration`.
|
|
66
66
|
|
|
67
|
-
Add `--contact-sheet` to the start command for a timestamped PNG summary of visual changes. Recording needs `ffmpeg` on PATH; check `agent-browser doctor` before starting. Stop the recording before closing the browser so the video is saved.
|
|
67
|
+
Add `--contact-sheet` to the start command for a timestamped PNG summary of visual changes. Recording needs `ffmpeg` on PATH; check `agent-browser doctor` before starting. Stop the recording before closing the browser so the video is saved. Keep the 1.5x file on the same agent-fs QA path as screenshots below. In a PR, upload it as a GitHub user attachment and put the URL on its own line, so GitHub plays it inline.
|
|
68
68
|
|
|
69
69
|
## Step 3: share the screenshot through agent-fs
|
|
70
70
|
|
|
71
|
-
Screenshots on the worker disk disappear with the task. Upload them to agent-fs under the qa path convention. Use `share-create` when a person needs to open the file
|
|
71
|
+
Screenshots on the worker disk disappear with the task. Upload them to agent-fs under the qa path convention: that copy is the durable original. Use `share-create` when a person needs to open the file. `--file` is binary-safe. `--content` is text-only and mangles PNGs.
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
agent-fs write thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png \
|
|
75
75
|
--file /tmp/<name>.png -m "<what it shows>"
|
|
76
76
|
agent-fs stat thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json # confirm size > 0
|
|
77
77
|
agent-fs share-create thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json
|
|
78
|
-
agent-fs signed-url thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json # raw URL for embeds; 24h default, --expires-in up to 7d
|
|
79
78
|
```
|
|
80
79
|
|
|
81
|
-
-
|
|
80
|
+
- **GitHub PR body or comment**: upload the PNG, GIF or mp4 as a GitHub user attachment with the `github-attach` skill and embed ``. Attachments never expire and mp4s play inline. A UI PR puts a before (main) and after pair in the `## Before / after` table. Fallback for images only, when the GitHub token is an App token (`ghu_`/`ghs_`): `agent-fs signed-url <path> --inline --json` (expires in 7 days). Never a presigned URL for an mp4 or a doc: it downloads.
|
|
81
|
+
- **Linear comment or Slack message**: paste the `share-create` link.
|
|
82
82
|
- In `store-progress`, list the upload in the `attachments` field with `kind: "agent-fs"` and the path, and paste the `share-create` link for people who need to open the file.
|
|
83
83
|
- If `agent-fs auth whoami` fails, report the local path, say the upload was skipped, and continue.
|
|
84
84
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"category": "skills",
|
|
3
|
-
"description": "Drive a real browser with the agent-browser CLI (headless Chromium over CDP). Use for any browser task: verifying a UI change, taking a screenshot, walking a web page, filling a form, extracting page text, reproducing a frontend bug, or attaching UI evidence to a PR, issue, Linear comment, or Slack thread. Starts with a `command -v agent-browser` check and falls back to manual QA on the slim worker image. Screenshots are
|
|
3
|
+
"description": "Drive a real browser with the agent-browser CLI (headless Chromium over CDP). Use for any browser task: verifying a UI change, taking a screenshot, walking a web page, filling a form, extracting page text, reproducing a frontend bug, or attaching UI evidence to a PR, issue, Linear comment, or Slack thread. Starts with a `command -v agent-browser` check and falls back to manual QA on the slim worker image. Screenshots are kept on agent-fs and embedded in GitHub as user attachments. This is the only browser automation tool; do not use qa-use.",
|
|
4
4
|
"displayName": "Agent Browser",
|
|
5
5
|
"kind": "skill",
|
|
6
6
|
"name": "agent-browser",
|
|
@@ -59,21 +59,21 @@ ffmpeg -y -i in.mp4 -filter:v "setpts=PTS/1.5,fps=30" -an \
|
|
|
59
59
|
|
|
60
60
|
Speed-up is a post-process step: `record start` has no speed or human flag. Keep `fps=30` to preserve a steady 30 fps output rather than uneven frame drops at an inferred rate. `-an` disables audio because agent-browser recordings carry no audio track. The worker ships only `ffmpeg`, not `ffprobe`; inspect duration with `ffmpeg -i out-1.5x.mp4 2>&1 | grep Duration`.
|
|
61
61
|
|
|
62
|
-
Add `--contact-sheet` to the start command for a timestamped PNG summary of visual changes. Recording needs `ffmpeg` on PATH; check `agent-browser doctor` before starting. Stop the recording before closing the browser so the video is saved.
|
|
62
|
+
Add `--contact-sheet` to the start command for a timestamped PNG summary of visual changes. Recording needs `ffmpeg` on PATH; check `agent-browser doctor` before starting. Stop the recording before closing the browser so the video is saved. Keep the 1.5x file on the same agent-fs QA path as screenshots below. In a PR, upload it as a GitHub user attachment and put the URL on its own line, so GitHub plays it inline.
|
|
63
63
|
|
|
64
64
|
## Step 3: share the screenshot through agent-fs
|
|
65
65
|
|
|
66
|
-
Screenshots on the worker disk disappear with the task. Upload them to agent-fs under the qa path convention. Use `share-create` when a person needs to open the file
|
|
66
|
+
Screenshots on the worker disk disappear with the task. Upload them to agent-fs under the qa path convention: that copy is the durable original. Use `share-create` when a person needs to open the file. `--file` is binary-safe. `--content` is text-only and mangles PNGs.
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
69
|
agent-fs write thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png \
|
|
70
70
|
--file /tmp/<name>.png -m "<what it shows>"
|
|
71
71
|
agent-fs stat thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json # confirm size > 0
|
|
72
72
|
agent-fs share-create thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json
|
|
73
|
-
agent-fs signed-url thoughts/<agent-id>/qa/<topic>-screenshots/<name>.png --json # raw URL for embeds; 24h default, --expires-in up to 7d
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
-
|
|
75
|
+
- **GitHub PR body or comment**: upload the PNG, GIF or mp4 as a GitHub user attachment with the `github-attach` skill and embed ``. Attachments never expire and mp4s play inline. A UI PR puts a before (main) and after pair in the `## Before / after` table. Fallback for images only, when the GitHub token is an App token (`ghu_`/`ghs_`): `agent-fs signed-url <path> --inline --json` (expires in 7 days). Never a presigned URL for an mp4 or a doc: it downloads.
|
|
76
|
+
- **Linear comment or Slack message**: paste the `share-create` link.
|
|
77
77
|
- In `store-progress`, list the upload in the `attachments` field with `kind: "agent-fs"` and the path, and paste the `share-create` link for people who need to open the file.
|
|
78
78
|
- If `agent-fs auth whoami` fails, report the local path, say the upload was skipped, and continue.
|
|
79
79
|
|
|
@@ -99,12 +99,19 @@ agent-fs stat thoughts/<agent-id>/qa/<topic>-screenshots/login.png --json | jq '
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
Capture with `agent-browser screenshot <path>` when it is installed, otherwise
|
|
102
|
-
Playwright, ffmpeg, or a system screenshot tool.
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
Playwright, ffmpeg, or a system screenshot tool. The agent-fs copy is the
|
|
103
|
+
durable original. Where each kind of content goes:
|
|
104
|
+
|
|
105
|
+
| Content | Use | Why |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| Image or GIF in GitHub | GitHub user attachment (`github-attach` skill) | Permanent, renders inline, short URL |
|
|
108
|
+
| Video (mp4) in GitHub | GitHub user attachment, else an `agent-fs share-create` link | GitHub plays it inline. A presigned link downloads it |
|
|
109
|
+
| Doc (plan, research, report) | `agent-fs share-create <path> --expires-in 604800` | Preview page, no download |
|
|
110
|
+
| Durable pointer | `live.agent-fs.dev` path, in a PR only under `## Swarm provenance` | Share links die after 7 days, this one does not (needs login) |
|
|
111
|
+
| Presigned `signed-url` | Image fallback only, with `--inline` | Downloads and expires after 7 days. Never for docs or mp4s |
|
|
105
112
|
|
|
106
113
|
```bash
|
|
107
|
-
agent-fs signed-url thoughts/<agent-id>/qa/<topic>-screenshots/login.png --json # 24h default, --expires-in up to 7d
|
|
114
|
+
agent-fs signed-url thoughts/<agent-id>/qa/<topic>-screenshots/login.png --inline --json # fallback only; 24h default, --expires-in up to 7d
|
|
108
115
|
```
|
|
109
116
|
|
|
110
117
|
## Naming conventions
|
|
@@ -120,9 +127,12 @@ misc/<agent-id>/<task-id>-<description>.ext
|
|
|
120
127
|
|
|
121
128
|
## Attaching artifacts
|
|
122
129
|
|
|
123
|
-
- **PR body** — embed
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
- **PR body** — embed images and mp4s as GitHub user attachments
|
|
131
|
+
(``), and link docs
|
|
132
|
+
with `share-create`. Never embed a `share-create` link as an image: it is an
|
|
133
|
+
HTML preview page. Swarm task links and durable agent-fs paths go only under
|
|
134
|
+
`## Swarm provenance`.
|
|
135
|
+
- **Slack** — link the agent-fs URL (team members only, the live viewer needs a login). Use a `share-create` link (up to 7 days) for anyone else.
|
|
126
136
|
- **`store-progress`** — use the `attachments` field with `kind: "agent-fs"` and the path.
|
|
127
137
|
- **Linear comments** — paste the live URL in the comment body.
|
|
128
138
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"slug": "jira-interaction",
|
|
6
6
|
"title": "Jira Interaction",
|
|
7
7
|
"description": "Generic Jira update discipline for issue comments, transitions, and triage.",
|
|
8
|
-
"version": "1.0.
|
|
8
|
+
"version": "1.0.1",
|
|
9
9
|
"category": "skills",
|
|
10
10
|
"placeholders": ["JIRA_PROJECT_KEY"],
|
|
11
11
|
"runAllSeedersCandidate": false,
|
|
@@ -1,183 +1,67 @@
|
|
|
1
1
|
# Jira Interaction (Read + Outbound Push)
|
|
2
2
|
|
|
3
|
-
The swarm has Jira OAuth
|
|
3
|
+
The swarm has Jira OAuth support but no inbound sync. Use Atlassian REST API v3 through script credential bindings.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Authentication: script credential binding
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. Hit `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3/...` — not your site hostname directly. 3LO bearer tokens only work via the `api.atlassian.com` proxy.
|
|
9
|
-
3. Bodies for descriptions/comments must be in **ADF** (Atlassian Document Format), not plain text or markdown.
|
|
7
|
+
Use the `swarm-scripts` skill and `script-run` (`args` first, `ctx` second) for every authenticated request. The API server resolves an OAuth authorization, refreshes it when needed, and substitutes `[REDACTED:JIRA_OAUTH_ACCESS_TOKEN]` in the request header only for the binding's allowed hosts. Never read credential tables, request a raw token from Lead, or copy a token into source, arguments, environment variables, logs, or task output.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
Try the conventional binding key `JIRA_OAUTH_ACCESS_TOKEN` first, using `Authorization: Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN]` for `api.atlassian.com`. Do not ask Lead before the first try. `credential-bindings` is lead-only; workers cannot list bindings.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
- Cloud ID: `<cloud-id>`
|
|
15
|
-
- Default project: `<PROJECT>`
|
|
16
|
-
- Scopes on the stored token: confirm from `oauth_tokens.scope` before write operations
|
|
11
|
+
On HTTP 401 with the conventional key, report the missing binding or authorization to Lead as a blocker. The fetch wrapper in `src/scripts-runtime/credential-broker/fetch-patch.ts` drops any header with an unresolved `[REDACTED:` placeholder, so a missing binding sends an unauthenticated request that yields 401, never a credential leak.
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
21
|
-
https://api.atlassian.com/oauth/token/accessible-resources | jq '.'
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Authentication
|
|
25
|
-
|
|
26
|
-
The OAuth token is in the swarm DB (`oauth_tokens`, provider = `jira`).
|
|
13
|
+
Lead uses `credential-bindings` action `list` to check the binding. If missing, Lead registers/authorizes the provider and creates a binding visible to the agent with `configKey: "JIRA_OAUTH_ACCESS_TOKEN"`, `authKind: "oauth"`, `oauthAuthorizationId`, `allowedHosts: ["api.atlassian.com"]`, and `headerTemplate: "Authorization: Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN]"`.
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
-- via the db-query MCP tool
|
|
30
|
-
SELECT accessToken, expiresAt, scope FROM oauth_tokens WHERE provider = 'jira';
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Always check `expiresAt` first.** Atlassian access tokens are short-lived (~1h). If expired, do NOT keep retrying — report it. Re-auth path:
|
|
15
|
+
An expiring authorization is refreshed server-side; a refresh failure, revoked/missing authorization, or persistent 401 needs Lead/user re-authorization through `credential-bindings` action `oauth-authorize-url`. Do not query expiry/token columns or loop on 401s.
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
<SWARM_API_BASE_URL>/api/trackers/jira/authorize
|
|
37
|
-
```
|
|
38
|
-
(User may need to remove the app and re-auth.)
|
|
17
|
+
Supported implementation: `src/be/script-credential-broker.ts` loads scoped relational bindings and calls `resolveOAuthBindingToken` from `src/be/oauth-credential-bindings.ts`. The scripts runtime substitutes placeholders at egress for allowed hosts.
|
|
39
18
|
|
|
40
19
|
## Calling pattern
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Standard header set:
|
|
48
|
-
```bash
|
|
49
|
-
-H "Authorization: Bearer $TOKEN"
|
|
50
|
-
-H "Accept: application/json"
|
|
51
|
-
-H "Content-Type: application/json" # only on POST/PUT
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Common operations
|
|
55
|
-
|
|
56
|
-
### 1. List projects
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
60
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project/search" \
|
|
61
|
-
| jq '.values[] | {key, name, id, projectTypeKey}'
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 2. Get a project (with issue types + lead)
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
68
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project/$PROJECT_KEY" \
|
|
69
|
-
| jq '{key, name, lead: .lead.displayName, issueTypes: [.issueTypes[] | {id, name, subtask}]}'
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 3. Search issues with JQL
|
|
73
|
-
|
|
74
|
-
Use the **`/search/jql`** endpoint (the older `/search` is deprecated for cloud).
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
curl -s -G \
|
|
78
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
79
|
-
--data-urlencode 'jql=project = <PROJECT> AND statusCategory != Done' \
|
|
80
|
-
--data-urlencode 'fields=summary,status,assignee,priority' \
|
|
81
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
82
|
-
| jq '[.issues[] | {key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName}]'
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### 4. Create an issue
|
|
86
|
-
|
|
87
|
-
Description must be ADF. Minimal valid ADF:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
curl -s -X POST \
|
|
91
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
92
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue" \
|
|
93
|
-
-d '{
|
|
94
|
-
"fields": {
|
|
95
|
-
"project": { "key": "<PROJECT>" },
|
|
96
|
-
"summary": "Short title",
|
|
97
|
-
"issuetype": { "name": "Task" },
|
|
98
|
-
"description": {
|
|
99
|
-
"type": "doc",
|
|
100
|
-
"version": 1,
|
|
101
|
-
"content": [
|
|
102
|
-
{ "type": "paragraph", "content": [ { "type": "text", "text": "Body goes here." } ] }
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}'
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Returns `{ id, key, self }` on success (HTTP 201). The `key` (e.g. `<PROJECT>-7`) is what humans use; URL is `https://<your-site>.atlassian.net/browse/<KEY>`.
|
|
110
|
-
|
|
111
|
-
Available issue types are project-specific; list the project before creating issues.
|
|
112
|
-
|
|
113
|
-
### 5. Transition issue status (e.g. → Done)
|
|
114
|
-
|
|
115
|
-
Transitions are project- and workflow-specific. Always discover them first:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
119
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/transitions" \
|
|
120
|
-
| jq '.transitions[] | {id, name, to: .to.name}'
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Transition IDs are project-specific. Do not copy IDs between Jira projects; discover them for the issue you are about to update.
|
|
124
|
-
|
|
125
|
-
Transition (returns HTTP 204 on success, no body):
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
curl -s -X POST \
|
|
129
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
130
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/transitions" \
|
|
131
|
-
-d '{"transition":{"id":"<TRANSITION_ID>"}}'
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 6. Comment on an issue
|
|
21
|
+
Allowed host: `api.atlassian.com`. Use the 3LO proxy `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3`, rather than the site hostname. Keep the site, cloud ID, and default project as deployment-specific values.
|
|
135
22
|
|
|
136
|
-
|
|
23
|
+
Run this source with `script-run`, supplying non-secret `args`: `cloudId`, `path`, optional `method`, `query`, and `body`.
|
|
137
24
|
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
25
|
+
```typescript
|
|
26
|
+
export default async function (args, ctx) {
|
|
27
|
+
const url = new URL(`https://api.atlassian.com/ex/jira/${encodeURIComponent(args.cloudId)}/rest/api/3/${args.path}`);
|
|
28
|
+
for (const [key, value] of Object.entries(args.query ?? {})) {
|
|
29
|
+
url.searchParams.set(key, String(value));
|
|
30
|
+
}
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method: args.method ?? "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: "Bearer [REDACTED:JIRA_OAUTH_ACCESS_TOKEN]",
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
...(args.body ? { "Content-Type": "application/json" } : {}),
|
|
37
|
+
},
|
|
38
|
+
...(args.body ? { body: JSON.stringify(args.body) } : {}),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new Error(`Jira HTTP ${response.status}: ${await response.text()}`);
|
|
41
|
+
return response.status === 204 ? { success: true } : await response.json();
|
|
42
|
+
}
|
|
148
43
|
```
|
|
149
44
|
|
|
150
|
-
|
|
45
|
+
Return only the issue/project fields needed for the task, never headers or credentials. To discover the cloud ID, use the same script header in a GET to `https://api.atlassian.com/oauth/token/accessible-resources`; select the authorized site's resource ID and confirm its scopes permit the intended operation.
|
|
151
46
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
156
|
-
--data-urlencode 'query=<name-or-email>' \
|
|
157
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/user/search" \
|
|
158
|
-
| jq '.[] | {accountId, displayName, emailAddress}'
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Assign:
|
|
162
|
-
```bash
|
|
163
|
-
curl -s -X PUT \
|
|
164
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
165
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/assignee" \
|
|
166
|
-
-d '{"accountId":"<ACCOUNT_ID>"}'
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
To unassign: `{"accountId": null}`.
|
|
170
|
-
|
|
171
|
-
### 8. Edit fields on an existing issue
|
|
47
|
+
## Common operations
|
|
172
48
|
|
|
173
|
-
|
|
174
|
-
curl -s -X PUT \
|
|
175
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
176
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>" \
|
|
177
|
-
-d '{ "fields": { "summary": "New summary", "labels": ["swarm","auto"] } }'
|
|
178
|
-
```
|
|
49
|
+
Pass these paths and payloads to the script above. GET is the default.
|
|
179
50
|
|
|
180
|
-
|
|
51
|
+
| Operation | Method and path | Query or body |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| List projects | GET `project/search` | Read `values` for keys, names, IDs |
|
|
54
|
+
| Get project and issue types | GET `project/<PROJECT_KEY>` | Read `issueTypes` before creating issues |
|
|
55
|
+
| Search issues | GET `search/jql` | `query: { jql: "project = <PROJECT> AND statusCategory != Done", fields: "summary,status,assignee,priority" }` |
|
|
56
|
+
| Create issue | POST `issue` | `body: { fields: { project: { key: "<PROJECT>" }, summary: "Short title", issuetype: { name: "Task" }, description: <ADF_DOC> } }` |
|
|
57
|
+
| Discover transitions | GET `issue/<KEY>/transitions` | Read `transitions` for the target status |
|
|
58
|
+
| Transition issue | POST `issue/<KEY>/transitions` | `body: { transition: { id: "<TRANSITION_ID>" } }` |
|
|
59
|
+
| Comment | POST `issue/<KEY>/comment` | `body: { body: <ADF_DOC> }` |
|
|
60
|
+
| Find account | GET `user/search` | `query: { query: "<name-or-email>" }` |
|
|
61
|
+
| Assign | PUT `issue/<KEY>/assignee` | `body: { accountId: "<ACCOUNT_ID>" }`; null unassigns |
|
|
62
|
+
| Edit fields | PUT `issue/<KEY>` | `body: { fields: { summary: "New summary", labels: ["swarm"] } }` |
|
|
63
|
+
|
|
64
|
+
Issue creation returns `{ id, key, self }` (201). The human URL is `https://<your-site>.atlassian.net/browse/<KEY>`. Transitions, assignment, and edits return 204 without a body.
|
|
181
65
|
|
|
182
66
|
## ADF cheat-sheet
|
|
183
67
|
|
|
@@ -195,50 +79,17 @@ If you need rich content, build it in a script — don't try to write deep ADF i
|
|
|
195
79
|
|
|
196
80
|
## Operational rules
|
|
197
81
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
- **Rate limits:** Atlassian rate-limits per app and per user. For bulk transitions/comments, sleep ~200–500 ms between calls.
|
|
205
|
-
- **Don't leak tokens.** Never echo the access token to logs or Slack. Read it into an env var only.
|
|
82
|
+
- Discover transitions per issue; IDs vary by project and workflow.
|
|
83
|
+
- Use `/search/jql` for cloud searches.
|
|
84
|
+
- Descriptions, comments, and rich text fields require ADF documents.
|
|
85
|
+
- Assignment uses account IDs rather than usernames.
|
|
86
|
+
- For bulk work use the `swarm-scripts` skill, handle pagination, and back off on 429 using `Retry-After`.
|
|
87
|
+
- Check every response before reporting success. On 400 inspect `errorMessages`/`errors`; on 403 confirm scopes and issue permissions with Lead; on 404 verify the cloud ID, project, and issue key. On refresh failure or persistent 401 request re-authorization.
|
|
206
88
|
|
|
207
|
-
##
|
|
208
|
-
|
|
209
|
-
| Status | Likely cause | Action |
|
|
210
|
-
|---|---|---|
|
|
211
|
-
| 401 | Token expired/invalid | Check `expiresAt`. Notify user to re-auth. Don't retry. |
|
|
212
|
-
| 403 | Missing scope, or restricted issue | Check the `scope` column. For `write:jira-work` operations, confirm scope is present. |
|
|
213
|
-
| 404 | Wrong key, wrong cloudId, wrong project | Re-verify with a project list call. |
|
|
214
|
-
| 400 | Body shape wrong (often ADF or required field) | Inspect `errorMessages` / `errors` in the response JSON. |
|
|
215
|
-
| 429 | Rate-limited | Back off, retry after `Retry-After` seconds. |
|
|
216
|
-
|
|
217
|
-
## Complete worked example: clean a project
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
TOKEN=$(db-query "SELECT accessToken FROM oauth_tokens WHERE provider='jira'")
|
|
221
|
-
CLOUD_ID="<cloud-id>"
|
|
222
|
-
PROJECT_KEY="<PROJECT>"
|
|
223
|
-
|
|
224
|
-
# 1. List open issues in KAN
|
|
225
|
-
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
226
|
-
--data-urlencode "jql=project = $PROJECT_KEY AND statusCategory != Done" \
|
|
227
|
-
--data-urlencode 'fields=summary,status' \
|
|
228
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
229
|
-
| jq -r '.issues[].key' > /tmp/keys.txt
|
|
230
|
-
|
|
231
|
-
# 2. Transition each to Done using the transition ID discovered for this workflow
|
|
232
|
-
for KEY in $(cat /tmp/keys.txt); do
|
|
233
|
-
curl -s -X POST \
|
|
234
|
-
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
235
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/$KEY/transitions" \
|
|
236
|
-
-d '{"transition":{"id":"<DONE_TRANSITION_ID>"}}'
|
|
237
|
-
sleep 0.3
|
|
238
|
-
done
|
|
239
|
-
```
|
|
89
|
+
## Worked workflow: close an issue
|
|
240
90
|
|
|
241
|
-
|
|
91
|
+
1. Run GET `issue/<KEY>/transitions` through the script and select the intended transition from the returned workflow.
|
|
92
|
+
2. Run POST `issue/<KEY>/transitions` with the discovered transition ID.
|
|
93
|
+
3. Confirm the 204 success before reporting the issue transitioned. For multiple issues, discover transitions for each issue and process pagination rather than assuming all workflows match.
|
|
242
94
|
|
|
243
|
-
|
|
244
|
-
- If/when inbound Jira webhooks land, this skill should add a "When to transition" section mirroring the Linear one.
|
|
95
|
+
The MCP tracker tools manage sync mappings; they do not replace this authenticated Jira request path.
|