@desplega.ai/agent-swarm 1.131.0 → 1.133.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
- package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
- package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
- package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
- package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
- package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
- package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
- package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
- package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
- package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
- package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
- package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
- package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
- package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
- package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
- package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
- package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
- package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
- package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
- package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
- package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
- package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
- package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
- package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
- package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
- package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
- package/dist/cli-et1bbwqf.js +13 -0
- package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
- package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
- package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
- package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
- package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
- package/dist/cli-hkwfe3qj.js +530 -0
- package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
- package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
- package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
- package/dist/cli-pzq5rjym.js +15 -0
- package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
- package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
- package/dist/cli-qys3rhhz.js +27 -0
- package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
- package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
- package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
- package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
- package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
- package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
- package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
- package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
- package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
- package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
- package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
- package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
- package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
- package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
- package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
- package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
- package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
- package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
- package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
- package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
- package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
- package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
- package/dist/index-0gr8vd6k.js +39 -0
- package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
- package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
- package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
- package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
- package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
- package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
- package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
- package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
- package/dist/mistral-conversations-00zrp7g4.js +717 -0
- package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
- package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
- package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
- package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
- package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
- package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
- package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
- package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
- package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
- package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
- package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
- package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
- package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
- package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
- package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
- package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
- package/openapi.json +542 -16
- package/package.json +6 -6
- package/src/be/budget-refusal-notify.ts +14 -7
- package/src/be/db-queries/task-artifacts.ts +182 -0
- package/src/be/db.ts +783 -169
- package/src/be/events.ts +5 -0
- package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
- package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
- package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
- package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
- package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
- package/src/be/migrations/136_task_requester_provenance.sql +37 -0
- package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
- package/src/be/multi-runtime.ts +430 -0
- package/src/be/scripts/db.ts +43 -1
- package/src/be/scripts/extract-schema.ts +3 -1
- package/src/be/scripts/retention.ts +164 -0
- package/src/be/scripts/run-saved.ts +16 -1
- package/src/be/scripts/typecheck.ts +3 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -1
- package/src/be/swarm-config-guard.ts +18 -0
- package/src/commands/credential-wait.ts +54 -0
- package/src/commands/onboard/steps/core-credentials.tsx +1 -1
- package/src/commands/onboard/steps/custom-templates.tsx +1 -1
- package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
- package/src/commands/onboard/steps/health-check.tsx +1 -1
- package/src/commands/onboard/steps/start.tsx +1 -1
- package/src/commands/profile-sync-audit.ts +56 -0
- package/src/commands/profile-sync.ts +287 -15
- package/src/commands/provider-credentials.ts +46 -19
- package/src/commands/runner.ts +196 -33
- package/src/commands/templates.ts +22 -0
- package/src/github/handlers.ts +55 -9
- package/src/heartbeat/heartbeat.ts +71 -12
- package/src/hooks/hook.ts +49 -33
- package/src/http/active-sessions.ts +11 -1
- package/src/http/agents.ts +261 -30
- package/src/http/all-routes.ts +1 -0
- package/src/http/config.ts +13 -2
- package/src/http/core.ts +102 -4
- package/src/http/db-query-bounded.ts +313 -0
- package/src/http/db-query-shared.ts +249 -0
- package/src/http/db-query.ts +54 -42
- package/src/http/events.ts +2 -0
- package/src/http/index.ts +27 -2
- package/src/http/mcp-bridge.ts +37 -3
- package/src/http/mcp.ts +12 -0
- package/src/http/memory.ts +13 -2
- package/src/http/openapi.ts +1 -0
- package/src/http/poll.ts +70 -27
- package/src/http/route-def.ts +43 -9
- package/src/http/schedules.ts +2 -2
- package/src/http/scripts.ts +31 -4
- package/src/http/session-data.ts +44 -0
- package/src/http/tasks.ts +2 -2
- package/src/http/users.ts +74 -52
- package/src/http/webhooks.ts +5 -2
- package/src/http/workflows.ts +1 -4
- package/src/http/x.ts +14 -0
- package/src/integrations/kapso/inbound.ts +1 -1
- package/src/jira/outbound.ts +39 -8
- package/src/jira/webhook-lifecycle.ts +13 -2
- package/src/linear/outbound.ts +43 -10
- package/src/oauth/keepalive.ts +13 -2
- package/src/prompts/base-prompt.ts +1 -1
- package/src/prompts/session-templates.ts +45 -7
- package/src/providers/claude-adapter.ts +6 -0
- package/src/providers/claude-managed-models.ts +34 -10
- package/src/providers/codex-adapter.ts +6 -0
- package/src/providers/opencode-adapter.ts +6 -0
- package/src/providers/pi-mono-adapter.ts +8 -0
- package/src/providers/pricing-sources.md +24 -0
- package/src/queue-stall-alarm.ts +216 -0
- package/src/scheduler/scheduler.ts +7 -0
- package/src/script-workflows/executor.ts +2 -1
- package/src/scripts-runtime/executors/native.ts +2 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/loader.ts +5 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/tests/budget-claim-gate.test.ts +20 -0
- package/src/tests/claude-adapter.test.ts +32 -0
- package/src/tests/claude-managed-adapter.test.ts +38 -7
- package/src/tests/codex-adapter.test.ts +30 -0
- package/src/tests/create-task-validation.test.ts +128 -0
- package/src/tests/credential-wait.test.ts +67 -1
- package/src/tests/db-query.test.ts +711 -2
- package/src/tests/http-api-integration.test.ts +81 -0
- package/src/tests/http-users.test.ts +166 -1
- package/src/tests/identity-field-budget.test.ts +176 -0
- package/src/tests/mcp-tools-user.test.ts +34 -0
- package/src/tests/mcp-user-route.test.ts +17 -0
- package/src/tests/metrics-http.test.ts +44 -0
- package/src/tests/multi-runtime-registration.test.ts +2703 -0
- package/src/tests/openapi-response-contract.test.ts +24 -0
- package/src/tests/openapi-route-headers.test.ts +83 -0
- package/src/tests/opencode-adapter.test.ts +40 -0
- package/src/tests/outbound-listener-rejection.test.ts +138 -0
- package/src/tests/pi-mono-adapter.test.ts +98 -0
- package/src/tests/profile-sync-warning.test.ts +277 -0
- package/src/tests/profile-sync.test.ts +232 -6
- package/src/tests/prompt-template-session.test.ts +32 -3
- package/src/tests/queue-stall-alarm.test.ts +226 -0
- package/src/tests/requester-comms.test.ts +32 -0
- package/src/tests/runner-requester-profile.test.ts +38 -0
- package/src/tests/sandboxed-process.test.ts +45 -0
- package/src/tests/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +16 -3
- package/src/tests/scripts-external-api.test.ts +8 -3
- package/src/tests/scripts-http.test.ts +12 -0
- package/src/tests/scripts-retention.test.ts +384 -0
- package/src/tests/scripts-runtime-identity.test.ts +375 -0
- package/src/tests/scripts-runtime.test.ts +4 -3
- package/src/tests/sdk-allowlist.test.ts +47 -0
- package/src/tests/send-task-requested-by.test.ts +9 -0
- package/src/tests/session-costs.test.ts +506 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
- package/src/tests/steering-core.test.ts +28 -0
- package/src/tests/task-artifacts.test.ts +386 -0
- package/src/tests/task-attachments-schema.test.ts +17 -1
- package/src/tests/task-completion-idempotency.test.ts +57 -0
- package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
- package/src/tests/update-profile-api.test.ts +67 -39
- package/src/tests/update-profile-auth.test.ts +48 -4
- package/src/tests/user-attribution-migration.test.ts +54 -0
- package/src/tests/user-identity.test.ts +72 -0
- package/src/tests/workflow-async-v2.test.ts +39 -1
- package/src/tests/workflow-http-v2.test.ts +3 -2
- package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
- package/src/tests/workflow-retry-v2.test.ts +45 -1
- package/src/tests/workflow-swarm-script.test.ts +14 -5
- package/src/tools/db-query.ts +18 -8
- package/src/tools/get-task-details.ts +24 -3
- package/src/tools/manage-user.ts +36 -3
- package/src/tools/poll-task.ts +93 -14
- package/src/tools/script-common.ts +5 -0
- package/src/tools/send-task.ts +1 -4
- package/src/tools/store-progress.ts +13 -2
- package/src/tools/swarm-config/delete-config.ts +9 -0
- package/src/tools/swarm-config/set-config.ts +3 -2
- package/src/tools/task-action.ts +30 -0
- package/src/tools/task-tool-ctx.ts +8 -1
- package/src/tools/update-profile.ts +16 -21
- package/src/tools/utils.ts +5 -0
- package/src/tools/workflows/trigger-workflow.ts +7 -1
- package/src/types.ts +165 -1
- package/src/utils/github-pull-request.ts +76 -0
- package/src/utils/identity-field-budget.ts +73 -0
- package/src/utils/multi-runtime.ts +20 -0
- package/src/utils/requester-comms.ts +24 -0
- package/src/utils/sandboxed-process.ts +40 -14
- package/src/workflows/engine.ts +22 -2
- package/src/workflows/executors/swarm-script.ts +24 -1
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- package/templates/schedules/daily-compounding-reflection/content.md +12 -3
- package/templates/skills/db-query-guidance/config.json +14 -0
- package/templates/skills/db-query-guidance/content.md +57 -0
- package/dist/cli-3te4jdkm.js +0 -225
- package/dist/cli-bmze986d.js +0 -7
- package/dist/cli-kh3ba2zf.js +0 -607
- package/dist/index-hp1kdbjj.js +0 -36
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
package/src/http/schedules.ts
CHANGED
|
@@ -40,7 +40,7 @@ const scheduleUpdateBodySchema = z.object({
|
|
|
40
40
|
taskTemplate: z.string().optional(),
|
|
41
41
|
taskType: z.string().optional(),
|
|
42
42
|
tags: z.array(z.string()).optional(),
|
|
43
|
-
priority: z.number().int().optional(),
|
|
43
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
44
44
|
targetAgentId: z.string().nullable().optional(),
|
|
45
45
|
enabled: z.boolean().optional(),
|
|
46
46
|
timezone: z.string().optional(),
|
|
@@ -96,7 +96,7 @@ const createSchedule = route({
|
|
|
96
96
|
taskTemplate: z.string().min(1).optional(),
|
|
97
97
|
taskType: z.string().optional(),
|
|
98
98
|
tags: z.array(z.string()).optional(),
|
|
99
|
-
priority: z.number().int().optional(),
|
|
99
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
100
100
|
targetAgentId: z.string().optional(),
|
|
101
101
|
enabled: z.boolean().optional(),
|
|
102
102
|
timezone: z.string().optional(),
|
package/src/http/scripts.ts
CHANGED
|
@@ -24,7 +24,9 @@ import {
|
|
|
24
24
|
listScriptApisForScript,
|
|
25
25
|
listScripts,
|
|
26
26
|
listScriptVersions,
|
|
27
|
+
restoreScratchScriptLastUsedIfUnchanged,
|
|
27
28
|
rotateScriptApiSecret,
|
|
29
|
+
touchScratchScriptLastUsed,
|
|
28
30
|
updateScriptApi,
|
|
29
31
|
upsertScriptByName,
|
|
30
32
|
} from "../be/scripts/db";
|
|
@@ -51,7 +53,7 @@ import {
|
|
|
51
53
|
ScriptVersionRecordSchema,
|
|
52
54
|
} from "../types";
|
|
53
55
|
import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
54
|
-
import { route } from "./route-def";
|
|
56
|
+
import { route, runtimeInstanceHeader } from "./route-def";
|
|
55
57
|
import { json, jsonError } from "./utils";
|
|
56
58
|
|
|
57
59
|
const scriptNameSchema = z.string().min(1).max(200);
|
|
@@ -242,6 +244,7 @@ const runRoute = route({
|
|
|
242
244
|
description:
|
|
243
245
|
"Inline source skips typecheck and is auto-saved as a scratch script only on success.",
|
|
244
246
|
tags: ["Scripts"],
|
|
247
|
+
headers: runtimeInstanceHeader("acquire work through the script SDK"),
|
|
245
248
|
body: runBodySchema,
|
|
246
249
|
responses: {
|
|
247
250
|
200: { description: "Script run completed", schema: scriptRunResponseSchema },
|
|
@@ -693,14 +696,23 @@ export async function handleScripts(
|
|
|
693
696
|
const agent = requireAgent(res, agentId);
|
|
694
697
|
if (!agent) return true;
|
|
695
698
|
|
|
699
|
+
// Per-boot identity of the invoking worker process. Carried into the
|
|
700
|
+
// script runtime as system context so SDK work-acquisition calls present
|
|
701
|
+
// the same runtime the worker itself would — never script input.
|
|
702
|
+
const runtimeInstanceId = ((h) => (Array.isArray(h) ? h[0] : h))(
|
|
703
|
+
req.headers["x-runtime-instance-id"],
|
|
704
|
+
);
|
|
705
|
+
|
|
696
706
|
let source = parsed.body.source;
|
|
697
707
|
let fsMode = parsed.body.fsMode;
|
|
708
|
+
let namedScript: ScriptRecord | null = null;
|
|
698
709
|
if (parsed.body.name) {
|
|
699
710
|
const script = resolveScript(parsed.body.name, agent.id, parsed.body.scope);
|
|
700
711
|
if (!script) {
|
|
701
712
|
jsonError(res, "Script not found", 404);
|
|
702
713
|
return true;
|
|
703
714
|
}
|
|
715
|
+
namedScript = script;
|
|
704
716
|
source = script.source;
|
|
705
717
|
fsMode = script.fsMode;
|
|
706
718
|
}
|
|
@@ -711,21 +723,37 @@ export async function handleScripts(
|
|
|
711
723
|
}
|
|
712
724
|
|
|
713
725
|
const startedAt = new Date().toISOString();
|
|
726
|
+
// Touch before executing so an already-stale scratch script isn't reaped
|
|
727
|
+
// by the retention sweep while this run is still in flight.
|
|
728
|
+
const runStartTouch = namedScript?.isScratch
|
|
729
|
+
? touchScratchScriptLastUsed(namedScript.id)
|
|
730
|
+
: null;
|
|
714
731
|
const credentials = await buildScriptCredentialBindingsWithFailures({ agentId: agent.id });
|
|
715
732
|
const output = await runScript({
|
|
716
733
|
source: source as string,
|
|
717
734
|
args: parsed.body.args,
|
|
718
735
|
fsMode,
|
|
719
736
|
agentId: agent.id,
|
|
737
|
+
runtimeInstanceId,
|
|
720
738
|
egressSecrets: credentials.egressSecrets,
|
|
721
739
|
failedBindings: credentials.failedBindings,
|
|
722
740
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id }),
|
|
723
741
|
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agent.id }),
|
|
724
742
|
});
|
|
743
|
+
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
744
|
+
|
|
745
|
+
if (namedScript?.isScratch && ok) {
|
|
746
|
+
touchScratchScriptLastUsed(namedScript.id);
|
|
747
|
+
} else if (namedScript?.isScratch && runStartTouch) {
|
|
748
|
+
// Failed run — restore the pre-run timestamp so it doesn't buy the
|
|
749
|
+
// script another retention window, unless a concurrent run already
|
|
750
|
+
// touched it since.
|
|
751
|
+
restoreScratchScriptLastUsedIfUnchanged(namedScript.id, namedScript.updatedAt, runStartTouch);
|
|
752
|
+
}
|
|
725
753
|
|
|
726
754
|
// Persist output to KV when idempotencyKey is provided and run succeeded
|
|
727
755
|
let kvSaved: { namespace: string; key: string } | undefined;
|
|
728
|
-
if (parsed.body.idempotencyKey &&
|
|
756
|
+
if (parsed.body.idempotencyKey && ok) {
|
|
729
757
|
const kvNamespace = `script:executions`;
|
|
730
758
|
const kvKey = parsed.body.idempotencyKey;
|
|
731
759
|
const kvValue = {
|
|
@@ -745,7 +773,7 @@ export async function handleScripts(
|
|
|
745
773
|
}
|
|
746
774
|
|
|
747
775
|
let autoSaved: { slug: string; reason: string } | undefined;
|
|
748
|
-
if (parsed.body.source &&
|
|
776
|
+
if (parsed.body.source && ok) {
|
|
749
777
|
const slug = scratchSlug(parsed.body.intent, parsed.body.source);
|
|
750
778
|
await upsertScriptByName({
|
|
751
779
|
name: slug,
|
|
@@ -767,7 +795,6 @@ export async function handleScripts(
|
|
|
767
795
|
// Persist the inline run (no journal) so one-off executions show up alongside
|
|
768
796
|
// durable workflow runs in the Script Runs dashboard. Best-effort: recording
|
|
769
797
|
// must never fail the actual execution.
|
|
770
|
-
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
771
798
|
const runError = ok
|
|
772
799
|
? undefined
|
|
773
800
|
: scrubSecrets(
|
package/src/http/session-data.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createSessionLogs,
|
|
6
6
|
getActivePricingRow,
|
|
7
7
|
getAllSessionCosts,
|
|
8
|
+
getAttributionByPerson,
|
|
8
9
|
getDashboardCostSummary,
|
|
9
10
|
getSessionCostSummary,
|
|
10
11
|
getSessionCostsByAgentId,
|
|
@@ -34,6 +35,9 @@ const SessionCostSummaryTotalsSchema = z.object({
|
|
|
34
35
|
totalSessions: z.number().int(),
|
|
35
36
|
avgCostPerSession: z.number(),
|
|
36
37
|
attributedCostUsd: z.number(),
|
|
38
|
+
attributableCostUsd: z.number(),
|
|
39
|
+
excludedCostUsd: z.number(),
|
|
40
|
+
excludedTaskCount: z.number().int(),
|
|
37
41
|
});
|
|
38
42
|
|
|
39
43
|
/** Mirrors `SessionCostDailyRow` in src/be/db.ts. */
|
|
@@ -79,6 +83,17 @@ const DashboardCostSummarySchema = z.object({
|
|
|
79
83
|
costMtd: z.number(),
|
|
80
84
|
});
|
|
81
85
|
|
|
86
|
+
/** Mirrors `AttributionByPersonRow` in src/be/db.ts. */
|
|
87
|
+
const AttributionByPersonRowSchema = z.object({
|
|
88
|
+
userId: z.string(),
|
|
89
|
+
problemsInitiated: z.number().int(),
|
|
90
|
+
problemsShipped: z.number().int(),
|
|
91
|
+
agentsReached: z.number().int(),
|
|
92
|
+
reposReached: z.number().int(),
|
|
93
|
+
surfacesReached: z.number().int(),
|
|
94
|
+
firstPassYield: z.null(),
|
|
95
|
+
});
|
|
96
|
+
|
|
82
97
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
83
98
|
|
|
84
99
|
const createSessionLogsRoute = route({
|
|
@@ -203,6 +218,24 @@ const getSessionCostSummaryRoute = route({
|
|
|
203
218
|
},
|
|
204
219
|
});
|
|
205
220
|
|
|
221
|
+
const getAttributionByPersonRoute = route({
|
|
222
|
+
method: "get",
|
|
223
|
+
path: "/api/attribution/by-person",
|
|
224
|
+
pattern: ["api", "attribution", "by-person"],
|
|
225
|
+
summary: "Four-metric per-person attribution (problems initiated/shipped, reach)",
|
|
226
|
+
tags: ["Session Data"],
|
|
227
|
+
query: z.object({
|
|
228
|
+
startDate: z.string().optional(),
|
|
229
|
+
endDate: z.string().optional(),
|
|
230
|
+
}),
|
|
231
|
+
responses: {
|
|
232
|
+
200: {
|
|
233
|
+
description: "Per-person attribution rows",
|
|
234
|
+
schema: z.object({ rows: z.array(AttributionByPersonRowSchema) }),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
206
239
|
const getDashboardCosts = route({
|
|
207
240
|
method: "get",
|
|
208
241
|
path: "/api/session-costs/dashboard",
|
|
@@ -403,6 +436,17 @@ export async function handleSessionData(
|
|
|
403
436
|
return true;
|
|
404
437
|
}
|
|
405
438
|
|
|
439
|
+
if (getAttributionByPersonRoute.match(req.method, pathSegments)) {
|
|
440
|
+
const parsed = await getAttributionByPersonRoute.parse(req, res, pathSegments, queryParams);
|
|
441
|
+
if (!parsed) return true;
|
|
442
|
+
const rows = getAttributionByPerson({
|
|
443
|
+
startDate: parsed.query.startDate || undefined,
|
|
444
|
+
endDate: parsed.query.endDate || undefined,
|
|
445
|
+
});
|
|
446
|
+
getAttributionByPersonRoute.respond(res, 200, { rows });
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
406
450
|
if (listSessionCosts.match(req.method, pathSegments)) {
|
|
407
451
|
const parsed = await listSessionCosts.parse(req, res, pathSegments, queryParams);
|
|
408
452
|
if (!parsed) return true;
|
package/src/http/tasks.ts
CHANGED
|
@@ -220,7 +220,7 @@ const createTask = route({
|
|
|
220
220
|
agentId: z.string().optional(),
|
|
221
221
|
taskType: z.string().optional(),
|
|
222
222
|
tags: z.array(z.string()).optional(),
|
|
223
|
-
priority: z.number().int().optional(),
|
|
223
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
224
224
|
dependsOn: z.array(z.string()).optional(),
|
|
225
225
|
offeredTo: z.string().optional(),
|
|
226
226
|
dir: z.string().optional(),
|
|
@@ -760,7 +760,7 @@ export async function handleTasks(
|
|
|
760
760
|
creatorAgentId: myAgentId || undefined,
|
|
761
761
|
taskType: parsed.body.taskType || undefined,
|
|
762
762
|
tags: parsed.body.tags || undefined,
|
|
763
|
-
priority: parsed.body.priority
|
|
763
|
+
priority: parsed.body.priority,
|
|
764
764
|
dependsOn: parsed.body.dependsOn || undefined,
|
|
765
765
|
offeredTo: parsed.body.offeredTo || undefined,
|
|
766
766
|
dir: parsed.body.dir || undefined,
|
package/src/http/users.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
deleteKv,
|
|
33
33
|
deleteUser,
|
|
34
34
|
getAllUsers,
|
|
35
|
+
getDb,
|
|
35
36
|
getUserById,
|
|
36
37
|
listKv,
|
|
37
38
|
updateUser,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
revokeToken,
|
|
49
50
|
unlinkIdentity,
|
|
50
51
|
} from "../be/users";
|
|
51
|
-
import { UserSchema } from "../types";
|
|
52
|
+
import { UserCommsPrefsSchema, UserSchema } from "../types";
|
|
52
53
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
53
54
|
import { getOperatorActor } from "./operator-actor";
|
|
54
55
|
import { route } from "./route-def";
|
|
@@ -296,6 +297,11 @@ const updateUserRoute = route({
|
|
|
296
297
|
preferredChannel: z.string().optional(),
|
|
297
298
|
timezone: z.string().optional(),
|
|
298
299
|
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
300
|
+
comms: UserCommsPrefsSchema.nullable()
|
|
301
|
+
.optional()
|
|
302
|
+
.describe(
|
|
303
|
+
"Merges into metadata.comms without touching sibling metadata keys; null removes only the comms key. When metadata is also provided, it is applied first and replaces the whole blob.",
|
|
304
|
+
),
|
|
299
305
|
dailyBudgetUsd: z.number().nullable().optional(),
|
|
300
306
|
status: z.enum(["invited", "active", "suspended"]).optional(),
|
|
301
307
|
// Complete-list diff: passing this replaces the user's identity set,
|
|
@@ -776,60 +782,63 @@ export async function handleUsers(
|
|
|
776
782
|
}
|
|
777
783
|
|
|
778
784
|
try {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
785
|
+
const mergedUser = getDb().transaction(() => {
|
|
786
|
+
// Move every identity from source → target.
|
|
787
|
+
for (const ident of sourceBefore.identities) {
|
|
788
|
+
unlinkIdentity(sourceId, ident.kind, ident.externalId, actor);
|
|
789
|
+
linkIdentity(targetId, ident.kind, ident.externalId, actor);
|
|
790
|
+
}
|
|
784
791
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
792
|
+
// Merge email aliases — append source.email + source.emailAliases into
|
|
793
|
+
// target.emailAliases (de-duped). Emit `email_added` per added alias.
|
|
794
|
+
const targetAliases = new Set(targetBefore.emailAliases ?? []);
|
|
795
|
+
const targetPrimary = (targetBefore.email ?? "").toLowerCase();
|
|
796
|
+
const newAliases: string[] = [];
|
|
797
|
+
const candidates = [
|
|
798
|
+
...(sourceBefore.email ? [sourceBefore.email] : []),
|
|
799
|
+
...(sourceBefore.emailAliases ?? []),
|
|
800
|
+
];
|
|
801
|
+
for (const candidate of candidates) {
|
|
802
|
+
const lower = candidate.toLowerCase();
|
|
803
|
+
if (!lower || lower === targetPrimary) continue;
|
|
804
|
+
if (
|
|
805
|
+
![...targetAliases].some((a) => a.toLowerCase() === lower) &&
|
|
806
|
+
!newAliases.some((a) => a.toLowerCase() === lower)
|
|
807
|
+
) {
|
|
808
|
+
newAliases.push(candidate);
|
|
809
|
+
}
|
|
802
810
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
811
|
+
if (newAliases.length > 0) {
|
|
812
|
+
const merged = [...(targetBefore.emailAliases ?? []), ...newAliases];
|
|
813
|
+
updateUser(targetId, { emailAliases: merged });
|
|
814
|
+
for (const alias of newAliases) {
|
|
815
|
+
recordIdentityEvent(targetId, "email_added", actor, null, { email: alias });
|
|
816
|
+
}
|
|
809
817
|
}
|
|
810
|
-
}
|
|
811
818
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
819
|
+
// Delete source — CASCADE cleans up any leftover external_ids row that
|
|
820
|
+
// we may have missed. Retained user references move to the target.
|
|
821
|
+
deleteUser(sourceId, targetId);
|
|
822
|
+
|
|
823
|
+
// Single manual_merge event on target capturing the before/after rows.
|
|
824
|
+
// The source row is deleted above, so carry a minimal snapshot of the
|
|
825
|
+
// source user ({id, name, email}) inside the `after` payload under
|
|
826
|
+
// `source` — this lets the UI render "Merged manually from X → Y".
|
|
827
|
+
const targetAfter = composeUser(targetId);
|
|
828
|
+
recordIdentityEvent(targetId, "manual_merge", actor, targetBefore, {
|
|
829
|
+
...targetAfter,
|
|
830
|
+
source: {
|
|
831
|
+
id: sourceBefore.id,
|
|
832
|
+
name: sourceBefore.name,
|
|
833
|
+
email: sourceBefore.email,
|
|
834
|
+
},
|
|
835
|
+
});
|
|
829
836
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
837
|
+
// Re-compose AFTER the event so the response surfaces the merge event in
|
|
838
|
+
// recentEvents (otherwise the timeline is missing the event we just wrote).
|
|
839
|
+
return composeUser(targetId);
|
|
840
|
+
})();
|
|
841
|
+
mergeUsersRoute.respond(res, 200, { user: mergedUser });
|
|
833
842
|
} catch (err) {
|
|
834
843
|
jsonError(res, err instanceof Error ? err.message : "Failed to merge users", 500);
|
|
835
844
|
}
|
|
@@ -863,10 +872,23 @@ export async function handleUsers(
|
|
|
863
872
|
}
|
|
864
873
|
|
|
865
874
|
try {
|
|
866
|
-
const { identities, metadata, ...rest } = parsed.body;
|
|
875
|
+
const { identities, metadata, comms, ...rest } = parsed.body;
|
|
867
876
|
// updateUser doesn't accept `metadata: null` directly — passthrough via cast.
|
|
868
877
|
const update: Parameters<typeof updateUser>[1] = { ...rest };
|
|
869
|
-
if (
|
|
878
|
+
if (comms !== undefined) {
|
|
879
|
+
// Merge-safe write: metadata (if provided) is applied first and
|
|
880
|
+
// replaces the whole blob, then comms is merged into it so sibling
|
|
881
|
+
// metadata keys survive.
|
|
882
|
+
const base: Record<string, unknown> = {
|
|
883
|
+
...(metadata !== undefined ? (metadata ?? {}) : (before.metadata ?? {})),
|
|
884
|
+
};
|
|
885
|
+
if (comms === null) {
|
|
886
|
+
delete base.comms;
|
|
887
|
+
} else {
|
|
888
|
+
base.comms = comms;
|
|
889
|
+
}
|
|
890
|
+
update.metadata = Object.keys(base).length > 0 ? base : null;
|
|
891
|
+
} else if (metadata !== undefined) {
|
|
870
892
|
update.metadata = metadata as Record<string, unknown> | null;
|
|
871
893
|
}
|
|
872
894
|
const updated = updateUser(parsed.params.id, update);
|
package/src/http/webhooks.ts
CHANGED
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
import type { KapsoConfig } from "../integrations/kapso/config";
|
|
51
51
|
import { getKapsoConfig } from "../integrations/kapso/config";
|
|
52
52
|
import type { KapsoWebhookPayload } from "../integrations/kapso/inbound";
|
|
53
|
-
import { routeKapsoInbound } from "../integrations/kapso/inbound";
|
|
53
|
+
import { resolveKapsoRequestedByUserId, routeKapsoInbound } from "../integrations/kapso/inbound";
|
|
54
54
|
import { getExecutorRegistry } from "../workflows";
|
|
55
55
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
56
56
|
import { handleWebhookTrigger, verifyHmacSignature, WebhookError } from "../workflows/triggers";
|
|
@@ -600,7 +600,10 @@ export async function handleWebhooks(
|
|
|
600
600
|
rawBody,
|
|
601
601
|
req.headers,
|
|
602
602
|
getExecutorRegistry(),
|
|
603
|
-
{
|
|
603
|
+
{
|
|
604
|
+
alreadyAuthenticated: true,
|
|
605
|
+
requestedByUserId: resolveKapsoRequestedByUserId(payload),
|
|
606
|
+
},
|
|
604
607
|
);
|
|
605
608
|
break;
|
|
606
609
|
case "no_mapping":
|
package/src/http/workflows.ts
CHANGED
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
WorkflowSchema,
|
|
32
32
|
WorkflowVersionSchema,
|
|
33
33
|
} from "../types";
|
|
34
|
-
import { getRequestAuth } from "../utils/request-auth-context";
|
|
35
34
|
import { getExecutorRegistry, startWorkflowExecution } from "../workflows";
|
|
36
35
|
import {
|
|
37
36
|
applyDefinitionPatch,
|
|
@@ -827,13 +826,11 @@ export async function handleWorkflows(
|
|
|
827
826
|
return true;
|
|
828
827
|
}
|
|
829
828
|
const body = await parseBody<Record<string, unknown>>(req);
|
|
830
|
-
const auth = getRequestAuth(req);
|
|
831
|
-
|
|
832
829
|
let runId: string;
|
|
833
830
|
try {
|
|
834
831
|
runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
|
|
835
832
|
triggerType: "api",
|
|
836
|
-
requestedByUserId:
|
|
833
|
+
requestedByUserId: resolveHttpAuditUserId(req, myAgentId) ?? undefined,
|
|
837
834
|
});
|
|
838
835
|
} catch (err) {
|
|
839
836
|
if (err instanceof TriggerSchemaError) {
|
package/src/http/x.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
getScriptApiSecret,
|
|
13
13
|
getScriptById,
|
|
14
14
|
recordScriptApiUsage,
|
|
15
|
+
restoreScratchScriptLastUsedIfUnchanged,
|
|
16
|
+
touchScratchScriptLastUsed,
|
|
15
17
|
} from "../be/scripts/db";
|
|
16
18
|
import {
|
|
17
19
|
MAX_SCRIPT_WALL_CLOCK_MS,
|
|
@@ -228,6 +230,10 @@ export async function handleX(
|
|
|
228
230
|
const timeoutMs = resolveTimeoutMs(req);
|
|
229
231
|
const startedAt = new Date().toISOString();
|
|
230
232
|
|
|
233
|
+
// Touch before executing so an already-stale scratch script isn't reaped by
|
|
234
|
+
// the retention sweep while this run is still in flight.
|
|
235
|
+
const runStartTouch = script.isScratch ? touchScratchScriptLastUsed(script.id) : null;
|
|
236
|
+
|
|
231
237
|
const credentials = await buildScriptCredentialBindingsWithFailures({
|
|
232
238
|
agentId: endpoint.agentId,
|
|
233
239
|
});
|
|
@@ -250,6 +256,14 @@ export async function handleX(
|
|
|
250
256
|
|
|
251
257
|
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
252
258
|
const error = ok ? null : buildExecutionError(output);
|
|
259
|
+
if (ok) {
|
|
260
|
+
touchScratchScriptLastUsed(script.id);
|
|
261
|
+
} else if (script.isScratch && runStartTouch) {
|
|
262
|
+
// Failed run — restore the pre-run timestamp so it doesn't buy the
|
|
263
|
+
// script another retention window, unless a concurrent run already
|
|
264
|
+
// touched it since.
|
|
265
|
+
restoreScratchScriptLastUsedIfUnchanged(script.id, script.updatedAt, runStartTouch);
|
|
266
|
+
}
|
|
253
267
|
|
|
254
268
|
// Usage + observability — best-effort, must never fail the response.
|
|
255
269
|
try {
|
|
@@ -80,7 +80,7 @@ function resolveKapsoIdentity(externalId: string): IdentityResolution {
|
|
|
80
80
|
return kapsoResolution;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function resolveKapsoRequestedByUserId(payload: KapsoWebhookPayload): string | undefined {
|
|
83
|
+
export function resolveKapsoRequestedByUserId(payload: KapsoWebhookPayload): string | undefined {
|
|
84
84
|
const externalId = normalizeKapsoSender(payload);
|
|
85
85
|
if (!externalId) return undefined;
|
|
86
86
|
|
package/src/jira/outbound.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getTrackerSync, updateTrackerSync } from "../be/db-queries/tracker";
|
|
2
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
2
3
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
3
4
|
import { jiraFetch } from "./client";
|
|
4
5
|
|
|
@@ -28,14 +29,44 @@ const OUTPUT_TRUNCATE_CHARS = 4000;
|
|
|
28
29
|
* as a known v1 limitation; a per-issue debounce / bounded outbound queue
|
|
29
30
|
* could be added in v2 if it becomes a real problem.
|
|
30
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* `EventEmitter.emit()` calls listeners synchronously and discards whatever
|
|
34
|
+
* they return, so a rejected async handler is never seen by the emitting side
|
|
35
|
+
* — it escapes to the process-level `unhandledRejection` handler with no
|
|
36
|
+
* subsystem context. These wrappers observe the *complete* handler body,
|
|
37
|
+
* including work that runs before any internal `try` (e.g. `getTrackerSync`).
|
|
38
|
+
*
|
|
39
|
+
* Each wrapper is created once at module scope so `off()` is passed the exact
|
|
40
|
+
* reference `on()` registered; building them inside `init` would leave the
|
|
41
|
+
* listeners attached after teardown.
|
|
42
|
+
*/
|
|
43
|
+
function observed(
|
|
44
|
+
eventName: string,
|
|
45
|
+
handler: (data: unknown) => Promise<void>,
|
|
46
|
+
): (data: unknown) => void {
|
|
47
|
+
return (data: unknown): void => {
|
|
48
|
+
handler(data).catch((error) => {
|
|
49
|
+
console.error(
|
|
50
|
+
`[Jira Outbound] ${eventName} handler failed:`,
|
|
51
|
+
scrubSecrets(error instanceof Error ? error.message : String(error)),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const onTaskCreated = observed("task.created", handleTaskCreated);
|
|
58
|
+
const onTaskCompleted = observed("task.completed", handleTaskCompleted);
|
|
59
|
+
const onTaskFailed = observed("task.failed", handleTaskFailed);
|
|
60
|
+
const onTaskCancelled = observed("task.cancelled", handleTaskCancelled);
|
|
61
|
+
|
|
31
62
|
export function initJiraOutboundSync(): void {
|
|
32
63
|
if (subscribed) return;
|
|
33
64
|
subscribed = true;
|
|
34
65
|
|
|
35
|
-
workflowEventBus.on("task.created",
|
|
36
|
-
workflowEventBus.on("task.completed",
|
|
37
|
-
workflowEventBus.on("task.failed",
|
|
38
|
-
workflowEventBus.on("task.cancelled",
|
|
66
|
+
workflowEventBus.on("task.created", onTaskCreated);
|
|
67
|
+
workflowEventBus.on("task.completed", onTaskCompleted);
|
|
68
|
+
workflowEventBus.on("task.failed", onTaskFailed);
|
|
69
|
+
workflowEventBus.on("task.cancelled", onTaskCancelled);
|
|
39
70
|
console.log("[Jira] Outbound sync subscribed to event bus");
|
|
40
71
|
}
|
|
41
72
|
|
|
@@ -43,10 +74,10 @@ export function teardownJiraOutboundSync(): void {
|
|
|
43
74
|
if (!subscribed) return;
|
|
44
75
|
subscribed = false;
|
|
45
76
|
|
|
46
|
-
workflowEventBus.off("task.created",
|
|
47
|
-
workflowEventBus.off("task.completed",
|
|
48
|
-
workflowEventBus.off("task.failed",
|
|
49
|
-
workflowEventBus.off("task.cancelled",
|
|
77
|
+
workflowEventBus.off("task.created", onTaskCreated);
|
|
78
|
+
workflowEventBus.off("task.completed", onTaskCompleted);
|
|
79
|
+
workflowEventBus.off("task.failed", onTaskFailed);
|
|
80
|
+
workflowEventBus.off("task.cancelled", onTaskCancelled);
|
|
50
81
|
console.log("[Jira] Outbound sync unsubscribed from event bus");
|
|
51
82
|
}
|
|
52
83
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getPublicMcpBaseUrl } from "../utils/constants";
|
|
2
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
2
3
|
import { jiraFetch } from "./client";
|
|
3
4
|
import { getJiraMetadata, updateJiraMetadata } from "./metadata";
|
|
4
5
|
|
|
@@ -359,11 +360,21 @@ export function startJiraWebhookKeepalive(): void {
|
|
|
359
360
|
// Immediate-on-boot check (after a short delay so server finishes startup
|
|
360
361
|
// and OAuth tokens are loaded). Mirrors src/oauth/keepalive.ts pattern.
|
|
361
362
|
setTimeout(() => {
|
|
362
|
-
runKeepalive()
|
|
363
|
+
runKeepalive().catch((err) =>
|
|
364
|
+
console.error(
|
|
365
|
+
"[Jira webhook keepalive] run failed:",
|
|
366
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
367
|
+
),
|
|
368
|
+
);
|
|
363
369
|
}, 10_000);
|
|
364
370
|
|
|
365
371
|
keepaliveInterval = setInterval(() => {
|
|
366
|
-
runKeepalive()
|
|
372
|
+
runKeepalive().catch((err) =>
|
|
373
|
+
console.error(
|
|
374
|
+
"[Jira webhook keepalive] run failed:",
|
|
375
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
376
|
+
),
|
|
377
|
+
);
|
|
367
378
|
}, TWELVE_HOURS_MS);
|
|
368
379
|
}
|
|
369
380
|
|
package/src/linear/outbound.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getTrackerSync, updateTrackerSync } from "../be/db-queries/tracker";
|
|
2
2
|
import { ensureToken } from "../oauth/ensure-token";
|
|
3
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
3
4
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
4
5
|
import { getLinearClient, resetLinearClient } from "./client";
|
|
5
6
|
import { endAgentSession, postAgentSessionAction, taskSessionMap } from "./sync";
|
|
@@ -8,15 +9,47 @@ let subscribed = false;
|
|
|
8
9
|
|
|
9
10
|
const LOOP_PREVENTION_WINDOW_MS = 5_000;
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* `EventEmitter.emit()` calls listeners synchronously and discards whatever
|
|
14
|
+
* they return, so a rejected async handler is never seen by the emitting side
|
|
15
|
+
* — it escapes to the process-level `unhandledRejection` handler with no
|
|
16
|
+
* subsystem context. These wrappers observe the *complete* handler body,
|
|
17
|
+
* including work that runs before any internal `try` (e.g. `getTrackerSync`)
|
|
18
|
+
* and the `updateTrackerSync` write that runs after it.
|
|
19
|
+
*
|
|
20
|
+
* Each wrapper is created once at module scope so `off()` is passed the exact
|
|
21
|
+
* reference `on()` registered; building them inside `init` would leave the
|
|
22
|
+
* listeners attached after teardown.
|
|
23
|
+
*/
|
|
24
|
+
function observed(
|
|
25
|
+
eventName: string,
|
|
26
|
+
handler: (data: unknown) => Promise<void>,
|
|
27
|
+
): (data: unknown) => void {
|
|
28
|
+
return (data: unknown): void => {
|
|
29
|
+
handler(data).catch((error) => {
|
|
30
|
+
console.error(
|
|
31
|
+
`[Linear Outbound] ${eventName} handler failed:`,
|
|
32
|
+
scrubSecrets(error instanceof Error ? error.message : String(error)),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const onTaskCreated = observed("task.created", handleTaskCreated);
|
|
39
|
+
const onTaskCompleted = observed("task.completed", handleTaskCompleted);
|
|
40
|
+
const onTaskFailed = observed("task.failed", handleTaskFailed);
|
|
41
|
+
const onTaskCancelled = observed("task.cancelled", handleTaskCancelled);
|
|
42
|
+
const onTaskProgress = observed("task.progress", handleTaskProgress);
|
|
43
|
+
|
|
11
44
|
export function initLinearOutboundSync(): void {
|
|
12
45
|
if (subscribed) return;
|
|
13
46
|
subscribed = true;
|
|
14
47
|
|
|
15
|
-
workflowEventBus.on("task.created",
|
|
16
|
-
workflowEventBus.on("task.completed",
|
|
17
|
-
workflowEventBus.on("task.failed",
|
|
18
|
-
workflowEventBus.on("task.cancelled",
|
|
19
|
-
workflowEventBus.on("task.progress",
|
|
48
|
+
workflowEventBus.on("task.created", onTaskCreated);
|
|
49
|
+
workflowEventBus.on("task.completed", onTaskCompleted);
|
|
50
|
+
workflowEventBus.on("task.failed", onTaskFailed);
|
|
51
|
+
workflowEventBus.on("task.cancelled", onTaskCancelled);
|
|
52
|
+
workflowEventBus.on("task.progress", onTaskProgress);
|
|
20
53
|
console.log("[Linear] Outbound sync subscribed to event bus");
|
|
21
54
|
}
|
|
22
55
|
|
|
@@ -24,11 +57,11 @@ export function teardownLinearOutboundSync(): void {
|
|
|
24
57
|
if (!subscribed) return;
|
|
25
58
|
subscribed = false;
|
|
26
59
|
|
|
27
|
-
workflowEventBus.off("task.created",
|
|
28
|
-
workflowEventBus.off("task.completed",
|
|
29
|
-
workflowEventBus.off("task.failed",
|
|
30
|
-
workflowEventBus.off("task.cancelled",
|
|
31
|
-
workflowEventBus.off("task.progress",
|
|
60
|
+
workflowEventBus.off("task.created", onTaskCreated);
|
|
61
|
+
workflowEventBus.off("task.completed", onTaskCompleted);
|
|
62
|
+
workflowEventBus.off("task.failed", onTaskFailed);
|
|
63
|
+
workflowEventBus.off("task.cancelled", onTaskCancelled);
|
|
64
|
+
workflowEventBus.off("task.progress", onTaskProgress);
|
|
32
65
|
console.log("[Linear] Outbound sync unsubscribed from event bus");
|
|
33
66
|
}
|
|
34
67
|
|