@desplega.ai/agent-swarm 1.113.0 → 1.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
- package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
- package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
- package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
- package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
- package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
- package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
- package/dist/cli-52fg7s7x.js +269 -0
- package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
- package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
- package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
- package/dist/cli-86fdnerz.js +405 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
- package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
- package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
- package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
- package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
- package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
- package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
- package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
- package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
- package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
- package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
- package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
- package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/cli-t0vqevnm.js +393 -0
- package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
- package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
- package/dist/cli-wh91zby6.js +187222 -0
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
- package/dist/cli.js +32 -15
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
- package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
- package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
- package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
- package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
- package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
- package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
- package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
- package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
- package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
- package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
- package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
- package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
- package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
- package/dist/rbac-roles-kfesjy9y.js +28 -0
- package/dist/rbac-roles-mwcwhbxc.js +283 -0
- package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
- package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
- package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
- package/openapi.json +1327 -20
- package/package.json +3 -3
- package/src/agentmail/handlers.ts +16 -6
- package/src/agentmail/templates.ts +25 -5
- package/src/be/audit-user.ts +28 -4
- package/src/be/db-queries/oauth.ts +42 -0
- package/src/be/db.ts +314 -28
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/migrations/109_rbac_roles.sql +79 -0
- package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
- package/src/be/migrations/112_script_connections_graphql.sql +96 -0
- package/src/be/migrations/113_task_routing_affinity.sql +9 -0
- package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
- package/src/be/modelsdev-cache.json +135 -0
- package/src/be/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +52 -13
- package/src/be/rbac-roles.ts +424 -0
- package/src/be/script-connections.ts +459 -40
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +51 -7
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
- package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
- package/src/be/users.ts +24 -0
- package/src/cli.tsx +17 -0
- package/src/github/handlers.ts +10 -9
- package/src/github/templates.ts +45 -9
- package/src/gitlab/handlers.ts +20 -8
- package/src/gitlab/templates.ts +18 -6
- package/src/heartbeat/heartbeat.ts +143 -21
- package/src/http/all-routes.ts +3 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +44 -10
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +1 -5
- package/src/http/index.ts +25 -1
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +44 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +20 -3
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +27 -7
- package/src/http/skills.ts +11 -0
- package/src/http/tasks.ts +27 -13
- package/src/http/x.ts +6 -2
- package/src/integrations/kapso/inbound.ts +23 -6
- package/src/jira/sync.ts +111 -17
- package/src/jira/templates.ts +10 -2
- package/src/mcp-client/http-client.ts +194 -0
- package/src/oauth/app-validation.ts +16 -0
- package/src/oauth/ensure-token.ts +69 -14
- package/src/oauth/mcp-wrapper.ts +15 -0
- package/src/oauth/wrapper.ts +48 -20
- package/src/prompts/session-templates.ts +3 -3
- package/src/providers/codex-models.ts +100 -30
- package/src/providers/modelsdev-reasoning.json +51 -0
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/providers/reasoning-effort.ts +11 -5
- package/src/rbac/admission.ts +68 -0
- package/src/rbac/index.ts +2 -0
- package/src/rbac/legacy-policy.ts +12 -0
- package/src/rbac/permissions.ts +48 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +151 -19
- package/src/scripts-runtime/api-types.ts +38 -6
- package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
- package/src/scripts-runtime/credential-broker/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/eval-harness.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +2 -1
- package/src/scripts-runtime/loader.ts +3 -1
- package/src/scripts-runtime/mcp-client.ts +87 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +6 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
- package/src/server-user.ts +114 -59
- package/src/server.ts +12 -0
- package/src/slack/assistant.ts +14 -6
- package/src/slack/enrich.ts +17 -0
- package/src/slack/handlers.ts +8 -22
- package/src/slack/responses.ts +91 -1
- package/src/slack/thread-buffer.ts +6 -3
- package/src/slack/watcher.ts +9 -6
- package/src/tasks/worker-follow-up.ts +100 -3
- package/src/tests/agentmail-handlers.test.ts +32 -3
- package/src/tests/approval-requests.test.ts +61 -0
- package/src/tests/audit-user.test.ts +47 -1
- package/src/tests/codex-adapter.test.ts +46 -23
- package/src/tests/credential-broker.test.ts +5 -2
- package/src/tests/delete-page-tool.test.ts +197 -0
- package/src/tests/github-handlers.test.ts +28 -2
- package/src/tests/gitlab-handlers.test.ts +125 -0
- package/src/tests/heartbeat.test.ts +48 -3
- package/src/tests/identity.test.ts +117 -0
- package/src/tests/jira-sync.test.ts +99 -1
- package/src/tests/kapso-inbound.test.ts +7 -0
- package/src/tests/mcp-tools-user.test.ts +60 -6
- package/src/tests/model-control.test.ts +8 -2
- package/src/tests/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/pool-affinity.test.ts +401 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/rbac-admission-e2e.test.ts +386 -0
- package/src/tests/rbac-admission.test.ts +492 -0
- package/src/tests/rbac-e2e-helpers.ts +3 -0
- package/src/tests/rbac-engine.test.ts +50 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
- package/src/tests/rbac-roles.test.ts +328 -0
- package/src/tests/reasoning-effort.test.ts +15 -2
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +40 -0
- package/src/tests/script-apis-mcp.test.ts +37 -13
- package/src/tests/script-connections-http.test.ts +1158 -0
- package/src/tests/script-connections-mcp.test.ts +894 -0
- package/src/tests/script-connections.test.ts +754 -6
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +291 -0
- package/src/tests/seed-scripts.test.ts +36 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-inline-output.test.ts +65 -0
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/tool-annotations.test.ts +1 -0
- package/src/tests/user-token-rest-auth.test.ts +140 -1
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/credential-bindings/tool.ts +277 -18
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/script-apis.ts +62 -3
- package/src/tools/script-connections/tool.ts +275 -31
- package/src/tools/send-task.ts +15 -1
- package/src/tools/task-action.ts +34 -2
- package/src/tools/templates.ts +53 -1
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +2 -0
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +90 -11
- package/src/utils/scoped-resource.ts +25 -0
- package/src/workflows/executors/human-in-the-loop.ts +1 -0
- package/src/workflows/executors/swarm-script.ts +7 -1
- package/src/workflows/triggers.ts +146 -18
- package/templates/skills/swarm-scripts/content.md +33 -8
- package/dist/cli-bgef578c.js +0 -771
- package/dist/cli-c9mtm09b.js +0 -43
- package/dist/cli-ceewqajw.js +0 -1698
- package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
- package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
- package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
- package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
- package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
- package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
- package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
- package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
- package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
package/src/http/scripts.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { z } from "zod";
|
|
|
3
3
|
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
4
4
|
import { getAgentById, recordInlineScriptRun, upsertKv } from "../be/db";
|
|
5
5
|
import { createEvent } from "../be/events";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getScriptApiConnectionDescriptors,
|
|
8
|
+
getScriptApiTypes,
|
|
9
|
+
getScriptMcpConnectionDescriptors,
|
|
10
|
+
getScriptMcpTypes,
|
|
11
|
+
} from "../be/script-connections";
|
|
7
12
|
import { buildScriptCredentialBindings } from "../be/script-credential-broker";
|
|
8
13
|
import {
|
|
9
14
|
createScriptApi,
|
|
@@ -23,8 +28,8 @@ import {
|
|
|
23
28
|
import { searchScripts } from "../be/scripts/embeddings";
|
|
24
29
|
import { extractArgsJsonSchema } from "../be/scripts/extract-schema";
|
|
25
30
|
import {
|
|
26
|
-
SCRIPT_STDLIB_TYPES,
|
|
27
31
|
scriptSdkTypesWithGeneratedApis,
|
|
32
|
+
scriptStdlibTypesWithGeneratedApis,
|
|
28
33
|
typecheckScript,
|
|
29
34
|
} from "../be/scripts/typecheck";
|
|
30
35
|
import { can } from "../rbac";
|
|
@@ -131,6 +136,7 @@ const searchRoute = route({
|
|
|
131
136
|
200: { description: "Matching scripts" },
|
|
132
137
|
400: { description: "Validation error" },
|
|
133
138
|
},
|
|
139
|
+
rbac: { permission: "script.search" },
|
|
134
140
|
});
|
|
135
141
|
|
|
136
142
|
const deleteRoute = route({
|
|
@@ -268,6 +274,7 @@ const createScriptApiRoute = route({
|
|
|
268
274
|
400: { description: "Validation error or script has no owning agent" },
|
|
269
275
|
404: { description: "Script not found" },
|
|
270
276
|
},
|
|
277
|
+
rbac: { permission: "script.api.create" },
|
|
271
278
|
});
|
|
272
279
|
|
|
273
280
|
const listScriptApisRoute = route({
|
|
@@ -296,6 +303,7 @@ const revealScriptApiSecretRoute = route({
|
|
|
296
303
|
200: { description: "Decrypted token (null when authMode is 'none')" },
|
|
297
304
|
404: { description: "Endpoint not found" },
|
|
298
305
|
},
|
|
306
|
+
rbac: { permission: "script.api.read.secrets" },
|
|
299
307
|
});
|
|
300
308
|
|
|
301
309
|
const patchScriptApiRoute = route({
|
|
@@ -311,6 +319,7 @@ const patchScriptApiRoute = route({
|
|
|
311
319
|
200: { description: "Updated endpoint" },
|
|
312
320
|
404: { description: "Endpoint not found" },
|
|
313
321
|
},
|
|
322
|
+
rbac: { permission: "script.api.update" },
|
|
314
323
|
});
|
|
315
324
|
|
|
316
325
|
const rotateScriptApiRoute = route({
|
|
@@ -326,6 +335,7 @@ const rotateScriptApiRoute = route({
|
|
|
326
335
|
400: { description: "Endpoint uses 'none' auth — nothing to rotate" },
|
|
327
336
|
404: { description: "Endpoint not found" },
|
|
328
337
|
},
|
|
338
|
+
rbac: { permission: "script.api.rotate" },
|
|
329
339
|
});
|
|
330
340
|
|
|
331
341
|
const deleteScriptApiRoute = route({
|
|
@@ -340,6 +350,7 @@ const deleteScriptApiRoute = route({
|
|
|
340
350
|
200: { description: "Deleted" },
|
|
341
351
|
404: { description: "Endpoint not found" },
|
|
342
352
|
},
|
|
353
|
+
rbac: { permission: "script.api.delete" },
|
|
343
354
|
});
|
|
344
355
|
|
|
345
356
|
function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
@@ -511,8 +522,9 @@ export async function handleScripts(
|
|
|
511
522
|
args: parsed.body.args,
|
|
512
523
|
fsMode,
|
|
513
524
|
agentId: agent.id,
|
|
514
|
-
egressSecrets: buildScriptCredentialBindings({ agentId: agent.id }),
|
|
525
|
+
egressSecrets: await buildScriptCredentialBindings({ agentId: agent.id }),
|
|
515
526
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id }),
|
|
527
|
+
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agent.id }),
|
|
516
528
|
});
|
|
517
529
|
|
|
518
530
|
// Persist output to KV when idempotencyKey is provided and run succeeded
|
|
@@ -667,9 +679,11 @@ export async function handleScripts(
|
|
|
667
679
|
// Must be matched before getScriptByIdRoute — its ["api", "scripts", null]
|
|
668
680
|
// pattern would otherwise swallow the literal "type-defs" segment.
|
|
669
681
|
if (typeDefsRoute.match(req.method, pathSegments)) {
|
|
682
|
+
const apiTypes = getScriptApiTypes();
|
|
683
|
+
const mcpTypes = getScriptMcpTypes();
|
|
670
684
|
json(res, {
|
|
671
|
-
sdkTypes: scriptSdkTypesWithGeneratedApis(
|
|
672
|
-
stdlibTypes:
|
|
685
|
+
sdkTypes: scriptSdkTypesWithGeneratedApis(apiTypes, mcpTypes),
|
|
686
|
+
stdlibTypes: scriptStdlibTypesWithGeneratedApis(apiTypes, mcpTypes),
|
|
673
687
|
});
|
|
674
688
|
return true;
|
|
675
689
|
}
|
|
@@ -718,8 +732,14 @@ export async function handleScripts(
|
|
|
718
732
|
json(res, {
|
|
719
733
|
signature: JSON.parse(script.signatureJson),
|
|
720
734
|
argsJsonSchema: script.argsJsonSchema ? (JSON.parse(script.argsJsonSchema) as unknown) : null,
|
|
721
|
-
sdkTypes: scriptSdkTypesWithGeneratedApis(
|
|
722
|
-
|
|
735
|
+
sdkTypes: scriptSdkTypesWithGeneratedApis(
|
|
736
|
+
getScriptApiTypes({ agentId: agent.id }),
|
|
737
|
+
getScriptMcpTypes({ agentId: agent.id }),
|
|
738
|
+
),
|
|
739
|
+
stdlibTypes: scriptStdlibTypesWithGeneratedApis(
|
|
740
|
+
getScriptApiTypes({ agentId: agent.id }),
|
|
741
|
+
getScriptMcpTypes({ agentId: agent.id }),
|
|
742
|
+
),
|
|
723
743
|
});
|
|
724
744
|
return true;
|
|
725
745
|
}
|
package/src/http/skills.ts
CHANGED
|
@@ -103,6 +103,7 @@ const bulkUpsertSkillFilesRoute = route({
|
|
|
103
103
|
summary: "Bulk upsert bundled files for a skill",
|
|
104
104
|
tags: ["Skills"],
|
|
105
105
|
auth: { apiKey: true },
|
|
106
|
+
rbac: { permission: "skill.update.any" },
|
|
106
107
|
params: z.object({ id: z.string() }),
|
|
107
108
|
body: z.object({
|
|
108
109
|
files: z.array(skillFileWithPathSchema).max(100),
|
|
@@ -137,6 +138,7 @@ const upsertSkillFileRoute = route({
|
|
|
137
138
|
summary: "Upsert a bundled skill file",
|
|
138
139
|
tags: ["Skills"],
|
|
139
140
|
auth: { apiKey: true },
|
|
141
|
+
rbac: { permission: "skill.update.any" },
|
|
140
142
|
params: z.object({ id: z.string(), path: z.string() }),
|
|
141
143
|
body: skillFileBodySchema,
|
|
142
144
|
responses: {
|
|
@@ -154,6 +156,7 @@ const deleteSkillFileRoute = route({
|
|
|
154
156
|
summary: "Delete a bundled skill file",
|
|
155
157
|
tags: ["Skills"],
|
|
156
158
|
auth: { apiKey: true },
|
|
159
|
+
rbac: { permission: "skill.update.any" },
|
|
157
160
|
params: z.object({ id: z.string(), path: z.string() }),
|
|
158
161
|
responses: {
|
|
159
162
|
200: { description: "Skill file deleted" },
|
|
@@ -168,6 +171,7 @@ const createSkillRoute = route({
|
|
|
168
171
|
summary: "Create a new skill",
|
|
169
172
|
tags: ["Skills"],
|
|
170
173
|
auth: { apiKey: true },
|
|
174
|
+
rbac: { permission: "skill.create.swarm" },
|
|
171
175
|
body: z.object({
|
|
172
176
|
content: z.string().min(1),
|
|
173
177
|
type: z.string().optional(),
|
|
@@ -188,6 +192,7 @@ const updateSkillRoute = route({
|
|
|
188
192
|
summary: "Update a skill",
|
|
189
193
|
tags: ["Skills"],
|
|
190
194
|
auth: { apiKey: true },
|
|
195
|
+
rbac: { permission: "skill.update.any" },
|
|
191
196
|
params: z.object({ id: z.string() }),
|
|
192
197
|
body: z.record(z.string(), z.unknown()),
|
|
193
198
|
responses: {
|
|
@@ -204,6 +209,7 @@ const deleteSkillRoute = route({
|
|
|
204
209
|
summary: "Delete a skill",
|
|
205
210
|
tags: ["Skills"],
|
|
206
211
|
auth: { apiKey: true },
|
|
212
|
+
rbac: { permission: "skill.delete.any" },
|
|
207
213
|
params: z.object({ id: z.string() }),
|
|
208
214
|
responses: {
|
|
209
215
|
200: { description: "Skill deleted" },
|
|
@@ -219,6 +225,7 @@ const installSkillRoute = route({
|
|
|
219
225
|
summary: "Install skill for an agent",
|
|
220
226
|
tags: ["Skills"],
|
|
221
227
|
auth: { apiKey: true },
|
|
228
|
+
rbac: { permission: "skill.install.any" },
|
|
222
229
|
params: z.object({ id: z.string() }),
|
|
223
230
|
body: z.object({
|
|
224
231
|
agentId: z.string(),
|
|
@@ -236,6 +243,7 @@ const uninstallSkillRoute = route({
|
|
|
236
243
|
summary: "Uninstall skill for an agent",
|
|
237
244
|
tags: ["Skills"],
|
|
238
245
|
auth: { apiKey: true },
|
|
246
|
+
rbac: { permission: "skill.uninstall.any" },
|
|
239
247
|
params: z.object({ id: z.string(), agentId: z.string() }),
|
|
240
248
|
responses: {
|
|
241
249
|
200: { description: "Skill uninstalled" },
|
|
@@ -249,6 +257,7 @@ const installRemoteRoute = route({
|
|
|
249
257
|
summary: "Install a remote skill from GitHub",
|
|
250
258
|
tags: ["Skills"],
|
|
251
259
|
auth: { apiKey: true },
|
|
260
|
+
rbac: { permission: "skill.install.global" },
|
|
252
261
|
body: z.object({
|
|
253
262
|
sourceRepo: z.string(),
|
|
254
263
|
sourcePath: z.string().optional(),
|
|
@@ -268,6 +277,7 @@ const syncRemoteRoute = route({
|
|
|
268
277
|
summary: "Trigger remote skill sync",
|
|
269
278
|
tags: ["Skills"],
|
|
270
279
|
auth: { apiKey: true },
|
|
280
|
+
rbac: { permission: "skill.update.any" },
|
|
271
281
|
body: z.object({
|
|
272
282
|
skillId: z.string().optional(),
|
|
273
283
|
force: z.boolean().optional(),
|
|
@@ -284,6 +294,7 @@ const syncFilesystemRoute = route({
|
|
|
284
294
|
summary: "Sync installed skills to agent filesystem",
|
|
285
295
|
tags: ["Skills"],
|
|
286
296
|
auth: { apiKey: true, agentId: true },
|
|
297
|
+
rbac: { ungated: "self-scoped: syncs the caller's own agent FS" },
|
|
287
298
|
responses: {
|
|
288
299
|
200: { description: "Filesystem sync results" },
|
|
289
300
|
},
|
package/src/http/tasks.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { ensure } from "@desplega.ai/business-use";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
4
5
|
import {
|
|
5
6
|
backfillSupersedeTaskResumeTaskId,
|
|
6
7
|
cancelTask,
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
getTaskAttachments,
|
|
15
16
|
getTaskById,
|
|
16
17
|
getTasksCount,
|
|
17
|
-
getUserById,
|
|
18
18
|
pauseTask,
|
|
19
19
|
resumeTask,
|
|
20
20
|
supersedeTask,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
updateTaskProgress,
|
|
24
24
|
updateTaskVcs,
|
|
25
25
|
} from "../be/db";
|
|
26
|
+
import { findUserById } from "../be/users";
|
|
26
27
|
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
27
28
|
import { createResumeFollowUp, createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
|
|
28
29
|
import {
|
|
@@ -37,7 +38,6 @@ import {
|
|
|
37
38
|
ResumeReasonSchema,
|
|
38
39
|
splitLegacyModelAlias,
|
|
39
40
|
} from "../types";
|
|
40
|
-
import { getRequestAuth } from "../utils/request-auth-context";
|
|
41
41
|
import { route } from "./route-def";
|
|
42
42
|
import { json, jsonError } from "./utils";
|
|
43
43
|
|
|
@@ -60,8 +60,12 @@ const listTasks = route({
|
|
|
60
60
|
includeHeartbeat: z.enum(["true", "false"]).optional(),
|
|
61
61
|
/** ISO 8601 — return only tasks created on/after this timestamp. */
|
|
62
62
|
createdAfter: z.string().datetime().optional(),
|
|
63
|
+
/** ISO 8601 — return only tasks created before this timestamp. */
|
|
64
|
+
createdBefore: z.string().datetime().optional(),
|
|
63
65
|
/** Comma-separated source filter (e.g. `ui,slack`). Omit to include all. */
|
|
64
66
|
source: z.string().optional(),
|
|
67
|
+
/** `createdAt` enables stable time-axis paging; default preserves table freshness ordering. */
|
|
68
|
+
orderBy: z.enum(["lastUpdatedAt", "createdAt"]).optional(),
|
|
65
69
|
limit: z.coerce.number().int().optional(),
|
|
66
70
|
offset: z.coerce.number().int().optional(),
|
|
67
71
|
/** `full` restores the legacy shape (full `task` text + all fields); default is slim. */
|
|
@@ -343,7 +347,9 @@ export async function handleTasks(
|
|
|
343
347
|
search: parsed.query.search || undefined,
|
|
344
348
|
includeHeartbeat: parsed.query.includeHeartbeat === "true" || undefined,
|
|
345
349
|
createdAfter: parsed.query.createdAfter || undefined,
|
|
350
|
+
createdBefore: parsed.query.createdBefore || undefined,
|
|
346
351
|
source,
|
|
352
|
+
orderBy: parsed.query.orderBy,
|
|
347
353
|
limit: parsed.query.limit,
|
|
348
354
|
offset: parsed.query.offset,
|
|
349
355
|
};
|
|
@@ -360,17 +366,25 @@ export async function handleTasks(
|
|
|
360
366
|
const parsed = await createTask.parse(req, res, pathSegments, queryParams);
|
|
361
367
|
if (!parsed) return true;
|
|
362
368
|
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
// Prefer trusted server-side identity: an authenticated request user, or
|
|
370
|
+
// the caller's own ownership-gated task context (`X-Source-Task-Id` /
|
|
371
|
+
// ambient current task) — same as every other audited write site. This
|
|
372
|
+
// is the upstream #939 anti-spoofing behavior and stays the default.
|
|
373
|
+
//
|
|
374
|
+
// Fork-specific opt-in: in a single-tenant deployment where every caller
|
|
375
|
+
// shares ONE operator key (so `auth.kind` is never "user" and there's no
|
|
376
|
+
// way to bind a caller to a user), TRUST_BODY_REQUESTED_BY_USER_ID=true
|
|
377
|
+
// re-enables a body-supplied `requestedByUserId` as a last resort, after
|
|
378
|
+
// validating it names a real user. This is safe ONLY because every
|
|
379
|
+
// holder of that shared key is already trusted org-wide — it must stay
|
|
380
|
+
// off (default) anywhere callers of the shared/global key are not all
|
|
381
|
+
// equally trusted, since it lets any such caller attribute a task to any
|
|
382
|
+
// user. Must NOT be upstreamed as a default-on behavior.
|
|
383
|
+
let requestedByUserId = resolveHttpAuditUserId(req, myAgentId) ?? undefined;
|
|
384
|
+
const trustBodyRequestedByUserId = process.env.TRUST_BODY_REQUESTED_BY_USER_ID === "true";
|
|
385
|
+
if (trustBodyRequestedByUserId && !requestedByUserId && parsed.body.requestedByUserId) {
|
|
386
|
+
const candidate = findUserById(parsed.body.requestedByUserId);
|
|
387
|
+
if (candidate) requestedByUserId = candidate.id;
|
|
374
388
|
}
|
|
375
389
|
|
|
376
390
|
// Default agent for ingress-created tasks: when no explicit `agentId` is
|
package/src/http/x.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { recordInlineScriptRun } from "../be/db";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getScriptApiConnectionDescriptors,
|
|
7
|
+
getScriptMcpConnectionDescriptors,
|
|
8
|
+
} from "../be/script-connections";
|
|
6
9
|
import { buildScriptCredentialBindings } from "../be/script-credential-broker";
|
|
7
10
|
import {
|
|
8
11
|
getScriptApiById,
|
|
@@ -204,8 +207,9 @@ export async function handleX(
|
|
|
204
207
|
// timeout, so an external caller can't use a long X-Swarm-Timeout-Ms to
|
|
205
208
|
// burn proportionally more CPU per request.
|
|
206
209
|
timeoutMs,
|
|
207
|
-
egressSecrets: buildScriptCredentialBindings({ agentId: endpoint.agentId }),
|
|
210
|
+
egressSecrets: await buildScriptCredentialBindings({ agentId: endpoint.agentId }),
|
|
208
211
|
apiConnections: getScriptApiConnectionDescriptors({ agentId: endpoint.agentId }),
|
|
212
|
+
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: endpoint.agentId }),
|
|
209
213
|
});
|
|
210
214
|
|
|
211
215
|
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
@@ -2,8 +2,8 @@ import { resolveTemplate } from "@/prompts/resolver";
|
|
|
2
2
|
import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
|
|
3
3
|
import { workflowEventBus } from "@/workflows/event-bus";
|
|
4
4
|
import "@/tools/templates";
|
|
5
|
+
import { type IdentityResolution, renderIdentity, resolveIdentity } from "@/be/identity";
|
|
5
6
|
import { recordUnmappedIdentity } from "@/be/unmapped-identities";
|
|
6
|
-
import { findUserByExternalId } from "@/be/users";
|
|
7
7
|
import { getKapsoNumberMapping, markKapsoMessageSeen } from "./config";
|
|
8
8
|
|
|
9
9
|
const KAPSO_IDENTITY_KIND = "kapso";
|
|
@@ -44,11 +44,15 @@ function extractText(message: NonNullable<KapsoWebhookPayload["message"]>): stri
|
|
|
44
44
|
function buildTaskDescription(payload: KapsoWebhookPayload): string {
|
|
45
45
|
const message = payload.message ?? {};
|
|
46
46
|
const conversation = payload.conversation ?? {};
|
|
47
|
+
const externalId = normalizeKapsoSender(payload);
|
|
48
|
+
const contactDisplay = externalId
|
|
49
|
+
? renderIdentity(resolveKapsoIdentity(externalId))
|
|
50
|
+
: "(unknown user)";
|
|
47
51
|
return resolveTemplate("kapso.message.received", {
|
|
48
52
|
conversation_id: conversation.id ?? "unknown",
|
|
49
53
|
inbound_wamid: message.id ?? "unknown",
|
|
50
54
|
sender_phone: message.from ?? conversation.phone_number ?? "unknown",
|
|
51
|
-
contact_name:
|
|
55
|
+
contact_name: contactDisplay,
|
|
52
56
|
phone_number_id: payload.phone_number_id ?? "unknown",
|
|
53
57
|
test_note: payload.test ? "\n- test: true (do NOT send a real WhatsApp reply)" : "",
|
|
54
58
|
message_text: extractText(message),
|
|
@@ -61,14 +65,27 @@ function normalizeKapsoSender(payload: KapsoWebhookPayload): string | null {
|
|
|
61
65
|
return digits || null;
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Reverse-lookup a phone-number externalId across both identity kinds this
|
|
70
|
+
* integration writes (`kapso`, `whatsapp` — see the dual-kind note on
|
|
71
|
+
* `resolveKapsoRequestedByUserId`). Never a provider profile label.
|
|
72
|
+
*/
|
|
73
|
+
function resolveKapsoIdentity(externalId: string): IdentityResolution {
|
|
74
|
+
const kapsoResolution = resolveIdentity(KAPSO_IDENTITY_KIND, externalId);
|
|
75
|
+
if (kapsoResolution.status === "resolved") return kapsoResolution;
|
|
76
|
+
const whatsappResolution = resolveIdentity(WHATSAPP_IDENTITY_KIND, externalId);
|
|
77
|
+
if (whatsappResolution.status === "resolved") return whatsappResolution;
|
|
78
|
+
// Both unknown — report under the `kapso` kind for consistency with
|
|
79
|
+
// resolveKapsoRequestedByUserId's unmapped-tracker recording.
|
|
80
|
+
return kapsoResolution;
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
function resolveKapsoRequestedByUserId(payload: KapsoWebhookPayload): string | undefined {
|
|
65
84
|
const externalId = normalizeKapsoSender(payload);
|
|
66
85
|
if (!externalId) return undefined;
|
|
67
86
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
findUserByExternalId(WHATSAPP_IDENTITY_KIND, externalId);
|
|
71
|
-
if (mapped) return mapped.id;
|
|
87
|
+
const resolution = resolveKapsoIdentity(externalId);
|
|
88
|
+
if (resolution.status === "resolved") return resolution.userId;
|
|
72
89
|
|
|
73
90
|
recordUnmappedIdentity(KAPSO_IDENTITY_KIND, externalId, {
|
|
74
91
|
sampleEventType: "kapso.message.received",
|
package/src/jira/sync.ts
CHANGED
|
@@ -21,6 +21,14 @@ import {
|
|
|
21
21
|
getTrackerSyncByExternalId,
|
|
22
22
|
updateTrackerSyncSwarmId,
|
|
23
23
|
} from "../be/db-queries/tracker";
|
|
24
|
+
import { renderIdentity, resolveIdentity } from "../be/identity";
|
|
25
|
+
import { recordUnmappedIdentity } from "../be/unmapped-identities";
|
|
26
|
+
import {
|
|
27
|
+
findOrCreateUserByEmail,
|
|
28
|
+
findUserByExternalId,
|
|
29
|
+
type IdentityActor,
|
|
30
|
+
linkIdentity,
|
|
31
|
+
} from "../be/users";
|
|
24
32
|
import { ensureToken, ensureTokenOrThrow } from "../oauth/ensure-token";
|
|
25
33
|
import { resolveTemplate } from "../prompts/resolver";
|
|
26
34
|
import { buildJiraContextKey } from "../tasks/context-key";
|
|
@@ -31,6 +39,66 @@ import { getJiraMetadata } from "./metadata";
|
|
|
31
39
|
// Side-effect import: registers all Jira event templates in the prompt registry
|
|
32
40
|
import "./templates";
|
|
33
41
|
|
|
42
|
+
// ─── Actor resolution (mirrors resolveLinearActor / resolveSlackUserId) ────
|
|
43
|
+
|
|
44
|
+
/** Audit-trail actor for the Jira auto-link cascade. */
|
|
45
|
+
const JIRA_WEBHOOK_ACTOR: IdentityActor = { kind: "system", id: "webhook:jira" };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Map a Jira Atlassian `accountId` to a canonical `users.id`:
|
|
49
|
+
* 1. Existing `(jira, accountId)` mapping — fast path.
|
|
50
|
+
* 2. No mapping + an email is present on the payload (privacy settings
|
|
51
|
+
* permitting) → `findOrCreateUserByEmail` + `linkIdentity`.
|
|
52
|
+
* 3. No mapping + no email → record into the kv unmapped tracker; caller
|
|
53
|
+
* proceeds without a `requestedByUserId`.
|
|
54
|
+
*
|
|
55
|
+
* Returns `undefined` when no mapping could be established.
|
|
56
|
+
*/
|
|
57
|
+
function resolveJiraActor(
|
|
58
|
+
accountId: string | undefined,
|
|
59
|
+
email: string | undefined,
|
|
60
|
+
name: string | undefined,
|
|
61
|
+
sampleEventType: string,
|
|
62
|
+
sampleContext: string,
|
|
63
|
+
): string | undefined {
|
|
64
|
+
if (!accountId) return undefined;
|
|
65
|
+
|
|
66
|
+
const existing = findUserByExternalId("jira", accountId);
|
|
67
|
+
if (existing) return existing.id;
|
|
68
|
+
|
|
69
|
+
const trimmedEmail = typeof email === "string" ? email.trim() : "";
|
|
70
|
+
if (trimmedEmail !== "") {
|
|
71
|
+
const { user } = findOrCreateUserByEmail(
|
|
72
|
+
trimmedEmail,
|
|
73
|
+
{ name: name?.trim() || undefined },
|
|
74
|
+
JIRA_WEBHOOK_ACTOR,
|
|
75
|
+
);
|
|
76
|
+
try {
|
|
77
|
+
linkIdentity(user.id, "jira", accountId, JIRA_WEBHOOK_ACTOR);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.warn(
|
|
80
|
+
`[Jira Sync] linkIdentity('jira', ${accountId}) failed — likely a concurrent enroll`,
|
|
81
|
+
error,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return user.id;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
recordUnmappedIdentity("jira", accountId, { sampleEventType, sampleContext });
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Render a Jira actor for agent-visible text: the resolved canonical name or
|
|
93
|
+
* the explicit UNKNOWN sentinel — never the raw Jira `displayName`. Falls
|
|
94
|
+
* back to a bare "(unknown user)" when the payload carries no `accountId` at
|
|
95
|
+
* all (malformed/legacy webhook shape).
|
|
96
|
+
*/
|
|
97
|
+
function renderJiraIdentity(accountId: string | undefined): string {
|
|
98
|
+
if (!accountId) return "(unknown user)";
|
|
99
|
+
return renderIdentity(resolveIdentity("jira", accountId));
|
|
100
|
+
}
|
|
101
|
+
|
|
34
102
|
// ─── Bot identity (Atlassian accountId) ────────────────────────────────────
|
|
35
103
|
|
|
36
104
|
// Cache the bot's Atlassian accountId on globalThis (not a module-level `let`)
|
|
@@ -171,7 +239,7 @@ type IssueShape = {
|
|
|
171
239
|
fields?: {
|
|
172
240
|
summary?: string;
|
|
173
241
|
description?: unknown;
|
|
174
|
-
reporter?: { displayName?: string; accountId?: string } | null;
|
|
242
|
+
reporter?: { displayName?: string; accountId?: string; emailAddress?: string } | null;
|
|
175
243
|
};
|
|
176
244
|
};
|
|
177
245
|
|
|
@@ -218,7 +286,14 @@ export async function handleIssueEvent(event: Record<string, unknown>): Promise<
|
|
|
218
286
|
const issueId = issue.id;
|
|
219
287
|
const issueKey = issue.key;
|
|
220
288
|
const summary = issue.fields?.summary ?? "(no summary)";
|
|
221
|
-
const
|
|
289
|
+
const reporterAccountId = issue.fields?.reporter?.accountId;
|
|
290
|
+
const requestedByUserId = resolveJiraActor(
|
|
291
|
+
reporterAccountId,
|
|
292
|
+
issue.fields?.reporter?.emailAddress,
|
|
293
|
+
issue.fields?.reporter?.displayName,
|
|
294
|
+
"issue_updated",
|
|
295
|
+
`${issueKey}: ${summary}`,
|
|
296
|
+
);
|
|
222
297
|
const descriptionText = extractText(issue.fields?.description);
|
|
223
298
|
const issueUrl = buildIssueUrl(issueKey);
|
|
224
299
|
|
|
@@ -241,7 +316,8 @@ export async function handleIssueEvent(event: Record<string, unknown>): Promise<
|
|
|
241
316
|
await createInitialJiraTask({
|
|
242
317
|
issueKey,
|
|
243
318
|
summary,
|
|
244
|
-
|
|
319
|
+
reporterAccountId,
|
|
320
|
+
requestedByUserId,
|
|
245
321
|
descriptionText,
|
|
246
322
|
issueUrl,
|
|
247
323
|
syncRowId: claim.sync.id,
|
|
@@ -265,7 +341,8 @@ export async function handleIssueEvent(event: Record<string, unknown>): Promise<
|
|
|
265
341
|
await createInitialJiraTask({
|
|
266
342
|
issueKey,
|
|
267
343
|
summary,
|
|
268
|
-
|
|
344
|
+
reporterAccountId,
|
|
345
|
+
requestedByUserId,
|
|
269
346
|
descriptionText,
|
|
270
347
|
issueUrl,
|
|
271
348
|
syncRowId: claim.sync.id,
|
|
@@ -280,8 +357,8 @@ export async function handleIssueEvent(event: Record<string, unknown>): Promise<
|
|
|
280
357
|
type CommentShape = {
|
|
281
358
|
id?: string;
|
|
282
359
|
body?: unknown;
|
|
283
|
-
author?: { accountId?: string; displayName?: string } | null;
|
|
284
|
-
updateAuthor?: { accountId?: string; displayName?: string } | null;
|
|
360
|
+
author?: { accountId?: string; displayName?: string; emailAddress?: string } | null;
|
|
361
|
+
updateAuthor?: { accountId?: string; displayName?: string; emailAddress?: string } | null;
|
|
285
362
|
};
|
|
286
363
|
|
|
287
364
|
/**
|
|
@@ -345,7 +422,14 @@ export async function handleCommentEvent(event: Record<string, unknown>): Promis
|
|
|
345
422
|
const summary = issue.fields?.summary ?? "(no summary)";
|
|
346
423
|
const descriptionText = extractText(issue.fields?.description);
|
|
347
424
|
const commentText = extractText(comment.body);
|
|
348
|
-
const
|
|
425
|
+
const commentAuthorAccountId = comment.author?.accountId ?? comment.updateAuthor?.accountId;
|
|
426
|
+
const requestedByUserId = resolveJiraActor(
|
|
427
|
+
commentAuthorAccountId,
|
|
428
|
+
comment.author?.emailAddress ?? comment.updateAuthor?.emailAddress,
|
|
429
|
+
comment.author?.displayName ?? comment.updateAuthor?.displayName,
|
|
430
|
+
"comment_created",
|
|
431
|
+
`${issueKey}: ${commentText.slice(0, 80)}`,
|
|
432
|
+
);
|
|
349
433
|
const issueUrl = buildIssueUrl(issueKey);
|
|
350
434
|
|
|
351
435
|
if (!existing) {
|
|
@@ -372,7 +456,8 @@ export async function handleCommentEvent(event: Record<string, unknown>): Promis
|
|
|
372
456
|
summary,
|
|
373
457
|
descriptionText,
|
|
374
458
|
commentText,
|
|
375
|
-
|
|
459
|
+
commentAuthorAccountId,
|
|
460
|
+
requestedByUserId,
|
|
376
461
|
issueUrl,
|
|
377
462
|
syncRowId: claim.sync.id,
|
|
378
463
|
});
|
|
@@ -384,7 +469,8 @@ export async function handleCommentEvent(event: Record<string, unknown>): Promis
|
|
|
384
469
|
summary,
|
|
385
470
|
issueUrl,
|
|
386
471
|
commentText,
|
|
387
|
-
|
|
472
|
+
commentAuthorAccountId,
|
|
473
|
+
requestedByUserId,
|
|
388
474
|
syncRow: claim.sync,
|
|
389
475
|
});
|
|
390
476
|
}
|
|
@@ -394,7 +480,8 @@ export async function handleCommentEvent(event: Record<string, unknown>): Promis
|
|
|
394
480
|
summary,
|
|
395
481
|
issueUrl,
|
|
396
482
|
commentText,
|
|
397
|
-
|
|
483
|
+
commentAuthorAccountId,
|
|
484
|
+
requestedByUserId,
|
|
398
485
|
syncRow: existing,
|
|
399
486
|
});
|
|
400
487
|
}
|
|
@@ -404,7 +491,8 @@ async function routeCommentOnExistingSync(input: {
|
|
|
404
491
|
summary: string;
|
|
405
492
|
issueUrl: string;
|
|
406
493
|
commentText: string;
|
|
407
|
-
|
|
494
|
+
commentAuthorAccountId: string | undefined;
|
|
495
|
+
requestedByUserId: string | undefined;
|
|
408
496
|
syncRow: { id: string; swarmId: string };
|
|
409
497
|
}): Promise<void> {
|
|
410
498
|
const priorTask = input.syncRow.swarmId ? getTaskById(input.syncRow.swarmId) : null;
|
|
@@ -417,15 +505,17 @@ async function routeCommentOnExistingSync(input: {
|
|
|
417
505
|
}
|
|
418
506
|
|
|
419
507
|
// Terminal or orphan sync row → follow-up.
|
|
508
|
+
const commentAuthorDisplay = renderJiraIdentity(input.commentAuthorAccountId);
|
|
420
509
|
await createInitialJiraTask({
|
|
421
510
|
issueKey: input.issueKey,
|
|
422
511
|
summary: input.summary,
|
|
423
|
-
|
|
512
|
+
reporterAccountId: input.commentAuthorAccountId,
|
|
513
|
+
requestedByUserId: input.requestedByUserId,
|
|
424
514
|
descriptionText: "",
|
|
425
515
|
issueUrl: input.issueUrl,
|
|
426
516
|
syncRowId: input.syncRow.id,
|
|
427
517
|
followup: true,
|
|
428
|
-
followupTrigger: `New comment from ${
|
|
518
|
+
followupTrigger: `New comment from ${commentAuthorDisplay}`,
|
|
429
519
|
followupMessage: input.commentText,
|
|
430
520
|
});
|
|
431
521
|
}
|
|
@@ -453,7 +543,8 @@ export async function handleIssueDeleteEvent(event: Record<string, unknown>): Pr
|
|
|
453
543
|
async function createInitialJiraTask(input: {
|
|
454
544
|
issueKey: string;
|
|
455
545
|
summary: string;
|
|
456
|
-
|
|
546
|
+
reporterAccountId: string | undefined;
|
|
547
|
+
requestedByUserId: string | undefined;
|
|
457
548
|
descriptionText: string;
|
|
458
549
|
issueUrl: string;
|
|
459
550
|
syncRowId: string;
|
|
@@ -479,7 +570,7 @@ async function createInitialJiraTask(input: {
|
|
|
479
570
|
issue_key: input.issueKey,
|
|
480
571
|
issue_summary: input.summary,
|
|
481
572
|
issue_url: input.issueUrl,
|
|
482
|
-
reporter: input.
|
|
573
|
+
reporter: renderJiraIdentity(input.reporterAccountId),
|
|
483
574
|
description_section: descriptionSection,
|
|
484
575
|
};
|
|
485
576
|
|
|
@@ -493,6 +584,7 @@ async function createInitialJiraTask(input: {
|
|
|
493
584
|
agentId: lead?.id ?? "",
|
|
494
585
|
source: "jira",
|
|
495
586
|
taskType: "jira-issue",
|
|
587
|
+
requestedByUserId: input.requestedByUserId,
|
|
496
588
|
contextKey: buildJiraContextKey(input.issueKey),
|
|
497
589
|
});
|
|
498
590
|
|
|
@@ -509,7 +601,8 @@ async function createCommentMentionTask(input: {
|
|
|
509
601
|
summary: string;
|
|
510
602
|
descriptionText: string;
|
|
511
603
|
commentText: string;
|
|
512
|
-
|
|
604
|
+
commentAuthorAccountId: string | undefined;
|
|
605
|
+
requestedByUserId: string | undefined;
|
|
513
606
|
issueUrl: string;
|
|
514
607
|
syncRowId: string;
|
|
515
608
|
}): Promise<void> {
|
|
@@ -522,7 +615,7 @@ async function createCommentMentionTask(input: {
|
|
|
522
615
|
issue_key: input.issueKey,
|
|
523
616
|
issue_summary: input.summary,
|
|
524
617
|
issue_url: input.issueUrl,
|
|
525
|
-
comment_author: input.
|
|
618
|
+
comment_author: renderJiraIdentity(input.commentAuthorAccountId),
|
|
526
619
|
description_section: descriptionSection,
|
|
527
620
|
comment_text: input.commentText,
|
|
528
621
|
});
|
|
@@ -536,6 +629,7 @@ async function createCommentMentionTask(input: {
|
|
|
536
629
|
agentId: lead?.id ?? "",
|
|
537
630
|
source: "jira",
|
|
538
631
|
taskType: "jira-issue",
|
|
632
|
+
requestedByUserId: input.requestedByUserId,
|
|
539
633
|
contextKey: buildJiraContextKey(input.issueKey),
|
|
540
634
|
});
|
|
541
635
|
|
package/src/jira/templates.ts
CHANGED
|
@@ -23,7 +23,11 @@ Reporter: {{reporter}}
|
|
|
23
23
|
{ name: "issue_key", description: "Jira issue key (e.g. ENG-123)" },
|
|
24
24
|
{ name: "issue_summary", description: "Issue summary / title" },
|
|
25
25
|
{ name: "issue_url", description: "Issue URL on the Jira site" },
|
|
26
|
-
{
|
|
26
|
+
{
|
|
27
|
+
name: "reporter",
|
|
28
|
+
description:
|
|
29
|
+
"Reporter identity — resolved canonical name (e.g. 'Luis (jira:<accountId>)') or the UNKNOWN sentinel",
|
|
30
|
+
},
|
|
27
31
|
{
|
|
28
32
|
name: "description_section",
|
|
29
33
|
description: "Description section (extracted from ADF) or empty string",
|
|
@@ -46,7 +50,11 @@ Comment:
|
|
|
46
50
|
{ name: "issue_key", description: "Jira issue key (e.g. ENG-123)" },
|
|
47
51
|
{ name: "issue_summary", description: "Issue summary / title" },
|
|
48
52
|
{ name: "issue_url", description: "Issue URL on the Jira site" },
|
|
49
|
-
{
|
|
53
|
+
{
|
|
54
|
+
name: "comment_author",
|
|
55
|
+
description:
|
|
56
|
+
"Comment author identity — resolved canonical name (e.g. 'Luis (jira:<accountId>)') or the UNKNOWN sentinel",
|
|
57
|
+
},
|
|
50
58
|
{
|
|
51
59
|
name: "description_section",
|
|
52
60
|
description: "Description section (extracted from ADF) or empty string",
|