@desplega.ai/agent-swarm 2.4.0 → 2.4.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.
Files changed (222) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-kedstxx6.js → acp-adapter-zvzr538d.js} +9 -9
  3. package/dist/{actions-vget6r1k.js → actions-jhtqdw69.js} +21 -21
  4. package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-bd84rhs2.js} +4 -4
  5. package/dist/{app-mqap8t8m.js → app-kreq1gwg.js} +3 -3
  6. package/dist/{assistant-h0gv97mv.js → assistant-3k22sbsq.js} +22 -22
  7. package/dist/{boot-reembed-qr8a894z.js → boot-reembed-e53x6b9v.js} +1 -1
  8. package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-f6z923ny.js} +1 -1
  9. package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-0538vsb7.js} +4 -4
  10. package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-6wxqkfk2.js} +4 -4
  11. package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gfts5044.js} +9 -9
  12. package/dist/{claude-managed-adapter-1mwrsm9d.js → claude-managed-adapter-h2y889ge.js} +2 -2
  13. package/dist/{claude-managed-setup-gkfgks4j.js → claude-managed-setup-h00hsgtz.js} +2 -2
  14. package/dist/{claude-sdk-session-apb4ypz3.js → claude-sdk-session-56besmfz.js} +13 -13
  15. package/dist/{cli-nrrg0fvy.js → cli-0bh477zn.js} +18 -5
  16. package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
  17. package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
  18. package/dist/{cli-1cevz46c.js → cli-3kh4kpqy.js} +6 -6
  19. package/dist/{cli-tg9ksbn8.js → cli-3p8fqcnh.js} +2 -2
  20. package/dist/{cli-x1p5dhq2.js → cli-3r448dqn.js} +3 -3
  21. package/dist/{cli-ecshf504.js → cli-3tnpj2bh.js} +7 -7
  22. package/dist/{cli-ffbrkh1r.js → cli-471qk6s2.js} +46 -38
  23. package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
  24. package/dist/{cli-zvze9rbm.js → cli-6ncjvx55.js} +3 -3
  25. package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
  26. package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
  27. package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
  28. package/dist/{cli-y83zk5sb.js → cli-a0kcty51.js} +4 -4
  29. package/dist/{cli-8b9wvjps.js → cli-a4gvwttf.js} +4 -4
  30. package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
  31. package/dist/{cli-mz1ezzzh.js → cli-ash6xxk7.js} +2 -2
  32. package/dist/{cli-rsd7ga7v.js → cli-axh29tyr.js} +11 -11
  33. package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
  34. package/dist/{cli-6ftrrwm1.js → cli-bn5kevnf.js} +5 -5
  35. package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
  36. package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
  37. package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
  38. package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
  39. package/dist/{cli-8cxkw1f5.js → cli-fz9kewbh.js} +2 -2
  40. package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
  41. package/dist/{cli-kz116kvw.js → cli-gf0kfg81.js} +7 -7
  42. package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
  43. package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
  44. package/dist/{cli-ywyrffff.js → cli-jh0qcrga.js} +1 -1
  45. package/dist/{cli-ttaz6c7m.js → cli-k9z4zd4p.js} +6 -5
  46. package/dist/{cli-nme7wery.js → cli-kmqgmcnb.js} +46 -13
  47. package/dist/{cli-x719j3vs.js → cli-kr2pmfqx.js} +392 -168
  48. package/dist/{cli-wrv09v7q.js → cli-m2qgh5sv.js} +1 -1
  49. package/dist/{cli-xfe63zx3.js → cli-mfe7f7b5.js} +2 -2
  50. package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
  51. package/dist/{cli-5s5hx57y.js → cli-nx462e4v.js} +1 -1
  52. package/dist/{cli-bfc3k1x5.js → cli-p0rakbrk.js} +9 -9
  53. package/dist/{cli-avmqhatn.js → cli-p6argxh9.js} +8 -8
  54. package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
  55. package/dist/{cli-sppc0cmw.js → cli-pnp2j5zg.js} +2 -2
  56. package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
  57. package/dist/{cli-bhwa8rzc.js → cli-rdq7tzwm.js} +13 -12
  58. package/dist/{cli-rg7ntypm.js → cli-rqqsfz30.js} +379 -87
  59. package/dist/{cli-kk2r80n5.js → cli-s48yvzb8.js} +4 -4
  60. package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
  61. package/dist/{cli-b80a1100.js → cli-sjyetj21.js} +2 -2
  62. package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
  63. package/dist/{cli-2rwet3r7.js → cli-sv5gb0yb.js} +1 -1
  64. package/dist/{cli-5ff2sjyt.js → cli-syhq378v.js} +2 -2
  65. package/dist/{cli-tj4j2ts4.js → cli-tbhwrxpt.js} +8 -8
  66. package/dist/{cli-4fhyq711.js → cli-vy0fay6k.js} +13 -13
  67. package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
  68. package/dist/{cli-f3nx1h8z.js → cli-wxbn3yfx.js} +2 -2
  69. package/dist/{cli-wyrr38xm.js → cli-x5mq1fyq.js} +1 -1
  70. package/dist/{cli-ccn0eybf.js → cli-xbzvet96.js} +6 -6
  71. package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
  72. package/dist/{cli-tgvpmnwz.js → cli-xftrvxag.js} +5 -5
  73. package/dist/{cli-02vhhssk.js → cli-z071fb95.js} +5 -5
  74. package/dist/{cli-mvg2hf8p.js → cli-z1ew8vbq.js} +14 -7
  75. package/dist/{cli-dsgj3e7a.js → cli-zfg9enh9.js} +2 -2
  76. package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
  77. package/dist/cli.js +19 -19
  78. package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-p6rmm7vv.js} +6 -6
  79. package/dist/{codex-hook-qwzccd64.js → codex-hook-x7nejyrr.js} +7 -4
  80. package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
  81. package/dist/{codex-session-runner-0vzn09t8.js → codex-session-runner-8vd5t9t0.js} +7 -7
  82. package/dist/{commands-psqhkttb.js → commands-7ems2p13.js} +2 -2
  83. package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
  84. package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-bg33ae53.js} +23 -23
  85. package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
  86. package/dist/{db-2xbyenjr.js → db-w505dwa8.js} +2 -2
  87. package/dist/{db-nc1rd869.js → db-ys2rbdft.js} +3 -3
  88. package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
  89. package/dist/{dispatcher-ewceb8ts.js → dispatcher-0s0mckqr.js} +20 -20
  90. package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
  91. package/dist/{dsh-adapter-75xwh78t.js → dsh-adapter-3n3net33.js} +3 -3
  92. package/dist/{e2b-5r6a5jeb.js → e2b-7am6tmq6.js} +2 -2
  93. package/dist/{e2b-pw1nvtmh.js → e2b-thdcxtjd.js} +2 -2
  94. package/dist/{easter-egg-3d-xsc47jwf.js → easter-egg-3d-5cgynaja.js} +1 -1
  95. package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
  96. package/dist/{execute-gzmw1mnw.js → execute-t4e6m3rw.js} +1 -1
  97. package/dist/{external-channel-a5dsmxn4.js → external-channel-5b23fv8z.js} +2 -2
  98. package/dist/{grok-adapter-vj88xr23.js → grok-adapter-q9fghe5q.js} +10 -10
  99. package/dist/handlers-mqrym347.js +39 -0
  100. package/dist/{heartbeat-t2m9r3wa.js → heartbeat-tww6yx1w.js} +23 -23
  101. package/dist/{hook-xa677f8r.js → hook-dx0pkvar.js} +16 -9
  102. package/dist/{hook-8femx1bg.js → hook-pvmh9b6m.js} +6 -6
  103. package/dist/{http-xpmmq028.js → http-k2ejbt05.js} +199 -131
  104. package/dist/{index-s7ckdajq.js → index-2z710v4x.js} +10 -10
  105. package/dist/{index-dg2tmj99.js → index-dgz61nff.js} +20 -20
  106. package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
  107. package/dist/index-h2ek08d6.js +40 -0
  108. package/dist/index-pqfwh9nn.js +41 -0
  109. package/dist/{index-cdzyfrsp.js → index-x5aktmp8.js} +1 -1
  110. package/dist/{keepalive-3nr4st3r.js → keepalive-nhvpw10z.js} +4 -4
  111. package/dist/lead-jb6sf69z.js +78 -0
  112. package/dist/{maintenance-verwpe2c.js → maintenance-agakc519.js} +1 -1
  113. package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-4dcbhtn0.js} +3 -3
  114. package/dist/{onboard-aytqedmq.js → onboard-4n2kwa4v.js} +3 -3
  115. package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
  116. package/dist/{otel-impl-hckw4wt1.js → otel-impl-tfa12080.js} +2 -2
  117. package/dist/{pi-mono-adapter-hxby0ce1.js → pi-mono-adapter-ytp7gz8b.js} +22 -11
  118. package/dist/{pricing-refresh-5nvme3sm.js → pricing-refresh-3cqx3f3x.js} +3 -3
  119. package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-9fae5rnv.js} +1 -1
  120. package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-y6zbfzve.js} +2 -2
  121. package/dist/{render-v2-7nqgvk2q.js → render-v2-emawjvc4.js} +3 -3
  122. package/dist/{resume-b6djcj3c.js → resume-zhgts7h7.js} +10 -8
  123. package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
  124. package/dist/{session-access-fc3f9y12.js → session-access-cm823e06.js} +4 -4
  125. package/dist/{setup-n39szm6b.js → setup-tkp99w1e.js} +1 -1
  126. package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
  127. package/dist/{work-objects-jem2fre0.js → work-objects-f722h263.js} +23 -23
  128. package/dist/worker-p7f336rq.js +78 -0
  129. package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
  130. package/openapi.json +99 -52
  131. package/package.json +2 -2
  132. package/src/apps/action-runner.ts +146 -0
  133. package/src/apps/row-store.ts +4 -2
  134. package/src/apps/script-types.ts +1 -1
  135. package/src/be/db.ts +14 -4
  136. package/src/be/fleet-benchmark-stats.ts +10 -5
  137. package/src/be/models-catalog.ts +4 -0
  138. package/src/be/onboarding.ts +6 -0
  139. package/src/be/scripts/typecheck.ts +13 -0
  140. package/src/be/swarm-config-guard.ts +10 -1
  141. package/src/be/telemetry-enable.ts +1 -1
  142. package/src/commands/provider-credentials.ts +12 -2
  143. package/src/commands/runner.ts +3 -0
  144. package/src/heartbeat/heartbeat.ts +2 -2
  145. package/src/hooks/codex-hook.ts +6 -2
  146. package/src/hooks/hook.ts +7 -0
  147. package/src/hooks/pr-body-guard.ts +38 -11
  148. package/src/hooks/pr-room-guard.ts +88 -0
  149. package/src/http/agents.ts +1 -0
  150. package/src/http/apps.ts +84 -181
  151. package/src/http/fleet-benchmark.ts +79 -30
  152. package/src/http/global-config.ts +7 -0
  153. package/src/http/ranking.ts +24 -40
  154. package/src/http/tasks.ts +2 -1
  155. package/src/http/telemetry-ui.ts +1 -1
  156. package/src/http/used-by.ts +55 -19
  157. package/src/http/users.ts +2 -2
  158. package/src/providers/amp-adapter.ts +1 -1
  159. package/src/providers/pi-mono-adapter.ts +15 -0
  160. package/src/providers/reasoning-effort.ts +2 -1
  161. package/src/providers/types.ts +2 -0
  162. package/src/ranking/telemetry-ranking.ts +5 -33
  163. package/src/scripts-runtime/sdk-allowlist.ts +7 -0
  164. package/src/scripts-runtime/types/stdlib.d.ts +31 -0
  165. package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
  166. package/src/server.ts +4 -0
  167. package/src/telemetry-context.ts +19 -1
  168. package/src/telemetry-contract/EVENTS.md +42 -8
  169. package/src/telemetry-contract/SOURCE.json +6 -5
  170. package/src/telemetry-contract/catalog.json +225 -5
  171. package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
  172. package/src/telemetry-contract/ranking/listing.v1.schema.json +2 -2
  173. package/src/telemetry-contract/types.gen.ts +225 -27
  174. package/src/telemetry-identity.ts +47 -9
  175. package/src/telemetry-snapshot.ts +9 -1
  176. package/src/telemetry-ui-events.ts +9 -2
  177. package/src/telemetry.ts +14 -3
  178. package/src/tests/app-action-run-tool.test.ts +178 -0
  179. package/src/tests/app-row-tools.test.ts +360 -0
  180. package/src/tests/check-pr-body.test.ts +0 -23
  181. package/src/tests/credential-check.test.ts +38 -0
  182. package/src/tests/fleet-benchmark.test.ts +122 -2
  183. package/src/tests/http-users.test.ts +62 -1
  184. package/src/tests/model-catalog-table.test.ts +63 -0
  185. package/src/tests/model-profiles-api.test.ts +19 -7
  186. package/src/tests/models-catalog.test.ts +50 -0
  187. package/src/tests/onboarding.test.ts +21 -0
  188. package/src/tests/org-website.test.ts +62 -0
  189. package/src/tests/pi-mono-adapter.test.ts +23 -0
  190. package/src/tests/pr-body-leaks.test.ts +4 -0
  191. package/src/tests/pr-room-guard.test.ts +242 -0
  192. package/src/tests/ranking-listing.test.ts +8 -23
  193. package/src/tests/ranking-route.test.ts +49 -47
  194. package/src/tests/reasoning-effort.test.ts +39 -3
  195. package/src/tests/runner-harness-telemetry-env.test.ts +56 -1
  196. package/src/tests/swarm-tool-result-gate.test.ts +2 -4
  197. package/src/tests/telemetry-context.test.ts +128 -2
  198. package/src/tests/telemetry-ui-route.test.ts +123 -0
  199. package/src/tests/tool-annotations.test.ts +3 -2
  200. package/src/tests/used-by-route.test.ts +144 -10
  201. package/src/tests/workflow-agent-task-retry.test.ts +130 -4
  202. package/src/tests/workflow-wait-recovery.test.ts +116 -4
  203. package/src/tools/app-action-run.ts +63 -0
  204. package/src/tools/app-rows.ts +234 -0
  205. package/src/tools/model-catalog/index.ts +1 -1
  206. package/src/tools/tool-config.ts +8 -0
  207. package/src/tools/utils.ts +1 -1
  208. package/src/tools/workflows/retry-workflow-run.ts +1 -1
  209. package/src/types.ts +17 -0
  210. package/src/utils/company-profile.ts +24 -0
  211. package/src/utils/org-website.ts +58 -21
  212. package/src/workflows/recovery.ts +10 -22
  213. package/src/workflows/resume.ts +16 -0
  214. package/src/workflows/task-step-routing.ts +47 -7
  215. package/templates/skills/apps/SKILL.md +41 -2
  216. package/templates/skills/apps/content.md +41 -2
  217. package/dist/handlers-zbs90bny.js +0 -39
  218. package/dist/index-8pwc1jc8.js +0 -40
  219. package/dist/index-nxa6me0q.js +0 -41
  220. package/dist/lead-td8h2sfn.js +0 -78
  221. package/dist/worker-wr6xgfr2.js +0 -78
  222. package/src/tests/check-ranking-entry.test.ts +0 -146
@@ -4,6 +4,11 @@ import { validateQuickJSPoolSize } from "../scripts-runtime/executors/quickjs-po
4
4
  import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
5
5
  import { ProviderNameSchema } from "../types";
6
6
  import { API_DRAIN_MAX_MS_LIMIT } from "../utils/api-drain";
7
+ import {
8
+ COMPANY_ACQUISITION_SOURCES,
9
+ COMPANY_TEAM_SIZES,
10
+ COMPANY_USE_CASES,
11
+ } from "../utils/company-profile";
7
12
  import { envExportRestrictedError, isEnvExportRestrictedKey } from "../utils/config-env-export";
8
13
  import { validateEgressAllowlist } from "../utils/egress-guard";
9
14
  import { parseEnabledTools } from "../utils/enabled-tools";
@@ -151,7 +156,8 @@ function booleanValidators(keys: string[]): Record<string, ConfigValidator> {
151
156
 
152
157
  /** Build a single `{ KEY: validator }` entry restricted to `options`. */
153
158
  function enumValidator(key: string, options: string[]): Record<string, ConfigValidator> {
154
- const message = `Invalid ${key} value (must be one of: ${options.join(", ")})`;
159
+ const listed = options.filter(Boolean).join(", ");
160
+ const message = `Invalid ${key} value (must be one of: ${listed}${options.includes("") ? ", or empty" : ""})`;
155
161
  return {
156
162
  [key]: (value: unknown) => {
157
163
  if (typeof value !== "string") return message;
@@ -423,6 +429,9 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
423
429
  ? null
424
430
  : "Invalid SWARM_ORG_WEBSITE (must be a public hostname such as example.com, without scheme, www, path or port)";
425
431
  },
432
+ ...enumValidator("SWARM_ORG_TEAM_SIZE", ["", ...COMPANY_TEAM_SIZES]),
433
+ ...enumValidator("SWARM_ORG_USE_CASE", ["", ...COMPANY_USE_CASES]),
434
+ ...enumValidator("SWARM_ORG_ACQUISITION_SOURCE", ["", ...COMPANY_ACQUISITION_SOURCES]),
426
435
  ...booleanValidators([
427
436
  "SWARM_USED_BY_CONSENT",
428
437
  "PUBLIC_RANKING_ENABLED",
@@ -13,7 +13,7 @@ import { telemetryIngestRecorder } from "./telemetry-ingest-anchor";
13
13
 
14
14
  type GetConfig = (key: string) => Promise<string | undefined>;
15
15
  type SetConfig = (key: string, value: string) => Promise<void>;
16
- export type TelemetryEnabledVia = "banner" | "settings" | "env";
16
+ export type TelemetryEnabledVia = "banner" | "settings" | "benchmark" | "env";
17
17
 
18
18
  let accessors: { getConfig: GetConfig; setConfig: SetConfig } | null = null;
19
19
  let lastEnabled = false;
@@ -155,8 +155,13 @@ export const CREDENTIAL_PROVIDER_CHECKERS: Record<SupportedProvider, CredentialC
155
155
  devin: (env) => checkDevinCredentials(env),
156
156
  opencode: (env, opts) => checkOpencodeCredentials(env, opts),
157
157
  pi: async (env, opts) => {
158
- const { checkPiMonoCredentials } = await import("../providers/pi-mono-adapter");
159
- return checkPiMonoCredentials(env, opts);
158
+ const { checkPiMonoCredentials, listPiOpencodeGoModelIds } = await import(
159
+ "../providers/pi-mono-adapter"
160
+ );
161
+ return {
162
+ ...(await checkPiMonoCredentials(env, opts)),
163
+ opencodeGoModels: listPiOpencodeGoModelIds(),
164
+ };
160
165
  },
161
166
  acp: (env) => {
162
167
  const target = env.ACP_TARGET ?? "custom";
@@ -662,6 +667,10 @@ export async function buildCredStatusReport(
662
667
  error: presence.ready ? undefined : (presence.hint ?? undefined),
663
668
  }
664
669
  : null;
670
+ const opencodeGo: AgentCredStatus["opencodeGo"] =
671
+ presence.opencodeGoModels !== undefined
672
+ ? { probedAt: Date.now(), models: presence.opencodeGoModels }
673
+ : null;
665
674
  return {
666
675
  ready: presence.ready,
667
676
  missing: presence.missing ?? [],
@@ -672,6 +681,7 @@ export async function buildCredStatusReport(
672
681
  reportedAt: Date.now(),
673
682
  reportKind: kind,
674
683
  bedrock,
684
+ opencodeGo,
675
685
  acp: null,
676
686
  };
677
687
  }
@@ -1233,6 +1233,9 @@ export const RELOADABLE_ENV_KEYS: ReadonlySet<string> = new Set([
1233
1233
  // turning the setting off stops it on running workers.
1234
1234
  "SWARM_ORG_WEBSITE",
1235
1235
  "PUBLIC_RANKING_ENABLED",
1236
+ "SWARM_ORG_TEAM_SIZE",
1237
+ "SWARM_ORG_USE_CASE",
1238
+ "SWARM_ORG_ACQUISITION_SOURCE",
1236
1239
  // pi reads these from process.env for its traits and its session.
1237
1240
  "PI_TOOL_DEFERRAL",
1238
1241
  "PI_CODEMODE",
@@ -76,7 +76,7 @@ import {
76
76
  REBOOT_RETRY_PIN_TAG,
77
77
  resolveLeadOnlyRecoveryAssignment,
78
78
  } from "../tasks/worker-follow-up";
79
- import { type AgentTask, TERMINAL_TASK_STATUSES } from "../types";
79
+ import { type AgentTask, SUPERSEDED_WORKFLOW_TASK_REASON, TERMINAL_TASK_STATUSES } from "../types";
80
80
  import { isEnvFlagEnabled } from "../utils/env-flag";
81
81
  import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
82
82
  import { DEFAULT_OPENROUTER_BASE_URL, getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
@@ -679,7 +679,7 @@ function isRemediationAction(value: unknown): value is RemediationAction {
679
679
 
680
680
  function decideRemediation(task: AgentTask, facts: RemediationFacts): RemediationDecision {
681
681
  if (task.workflowRunStepId != null) {
682
- return { action: "fail", reason: "superseded_workflow_task" };
682
+ return { action: "fail", reason: SUPERSEDED_WORKFLOW_TASK_REASON };
683
683
  }
684
684
  if (facts.skipAutoResume || facts.alreadyResumed) {
685
685
  return { action: "fail", reason: facts.legacyFailReason };
@@ -35,6 +35,7 @@ import { getApiKey } from "../utils/api-key";
35
35
  import { getMcpBaseUrl } from "../utils/constants";
36
36
  import { isSteeringEnabled } from "../utils/steering-enabled";
37
37
  import { guardGhPrBody, type PrBodyGuardDeps } from "./pr-body-guard";
38
+ import { guardGhPrRoom, type PrRoomGuardDeps } from "./pr-room-guard";
38
39
 
39
40
  const FETCH_TIMEOUT_MS = 5_000;
40
41
 
@@ -169,13 +170,16 @@ export async function handleCodexHookEvent(
169
170
  };
170
171
  }
171
172
 
172
- /** PreToolUse: the PR body leak guard's block reason, or null to allow the call. */
173
+ /** PreToolUse: return the first PR guard block reason, or null to allow the call. */
173
174
  export async function codexPreToolUseBlock(
174
175
  msg: CodexHookMessage,
175
176
  deps?: PrBodyGuardDeps,
177
+ roomDeps?: PrRoomGuardDeps,
176
178
  ): Promise<string | null> {
177
179
  if (msg.hook_event_name !== "PreToolUse") return null;
178
- return guardGhPrBody(msg.tool_input, msg.cwd ?? process.cwd(), deps);
180
+ const cwd = msg.cwd ?? process.cwd();
181
+ const block = await guardGhPrBody(msg.tool_input, cwd, deps);
182
+ return block ?? (await guardGhPrRoom(msg.tool_input, cwd, roomDeps));
179
183
  }
180
184
 
181
185
  /** stdin/stdout entry point used by the `codex-hook` CLI command. */
package/src/hooks/hook.ts CHANGED
@@ -24,6 +24,7 @@ import { getMcpBaseUrl, MAX_PROFILE_FILE_LENGTH } from "../utils/constants";
24
24
  import { summarizeSession as runSummarize } from "../utils/internal-ai";
25
25
  import { scrubSecrets } from "../utils/secret-scrubber";
26
26
  import { guardGhPrBody } from "./pr-body-guard";
27
+ import { guardGhPrRoom } from "./pr-room-guard";
27
28
  import { checkToolLoop, clearToolHistory } from "./tool-loop-detection";
28
29
 
29
30
  const SERVER_NAME = pkg.config?.name ?? "agent-swarm";
@@ -1189,6 +1190,12 @@ export async function handleHook(): Promise<void> {
1189
1190
  process.exitCode = 2;
1190
1191
  return;
1191
1192
  }
1193
+ const prRoomBlock = await guardGhPrRoom(msg.tool_input, msg.cwd ?? process.cwd());
1194
+ if (prRoomBlock) {
1195
+ console.error(prRoomBlock);
1196
+ process.exitCode = 2;
1197
+ return;
1198
+ }
1192
1199
  }
1193
1200
 
1194
1201
  // Emitted last, so a later block stays the only stdout of the event.
@@ -40,22 +40,29 @@ import { isAbsolute, resolve } from "node:path";
40
40
  import { findPrBodyLeaks, type LeakCategory } from "../utils/pr-body-leaks";
41
41
 
42
42
  export type GhPrInvocation = {
43
+ action: "create" | "edit";
43
44
  /** Working directory of the `gh` call after any `cd`; null when a `cd` target could not be resolved. */
44
45
  cwd: string | null;
45
46
  /** Explicit target repos: `--repo` / `-R`, a PR URL, or `GH_REPO`. Empty means the checkout at `cwd`. */
46
47
  repos: string[];
48
+ /** Expanded targets for workflow guards. Null means an unknown shell value. */
49
+ resolvedRepos: Array<string | null>;
47
50
  /** Also check the checkout: `GH_REPO` was assigned without `export`, so gh may not see it. */
48
51
  alsoCheckout: boolean;
49
52
  /** The `gh pr edit` target could not be classified (unset variable, `$(...)`, non-PR URL), so the PR's repo is unknown. */
50
53
  unknownTarget: boolean;
51
54
  /** Literal public text: `--title` and `--body` values, heredoc or here-string stdin. */
52
55
  texts: string[];
56
+ /** Expanded body values and stdin, excluding the title. Null means an unknown shell value. */
57
+ bodyTexts: Array<string | null>;
53
58
  /** Files that become the body, expanded: `--body-file PATH`, or `--body-file -` with `< PATH`. */
54
59
  bodyFiles: string[];
55
60
  /** Body file paths whose variables could not be resolved, as written. They count as unreadable. */
56
61
  unresolvedBodyFiles: string[];
57
62
  /** The body text could not be isolated (piped stdin, unquoted `$(...)`); scan the whole command. */
58
63
  opaqueBody: boolean;
64
+ /** gh may build or change the body from a source the parser cannot read: `--fill*`, `--template`, `--editor`, `--web`, `--recover`. */
65
+ externalBody: boolean;
59
66
  };
60
67
 
61
68
  export type PrBodyGuardDeps = {
@@ -282,6 +289,9 @@ const SHORT_FLAGS: Record<string, string> = {
282
289
  p: "project",
283
290
  r: "reviewer",
284
291
  T: "template",
292
+ e: "editor",
293
+ f: "fill",
294
+ w: "web",
285
295
  };
286
296
 
287
297
  type ParsedArgs = {
@@ -826,11 +836,14 @@ function parseWithState(
826
836
  else bodyFiles.push(expanded);
827
837
  }
828
838
  found.push({
839
+ action,
829
840
  cwd: wrapperFlags || dirUnknown ? null : state.dir,
830
841
  repos: [...new Set(repos)],
842
+ resolvedRepos: repos.map(expand),
831
843
  alsoCheckout,
832
844
  unknownTarget,
833
845
  texts: [...inline, ...(fromStdin ? stdin : [])],
846
+ bodyTexts: [...values("body"), ...(fromStdin ? stdin : [])].map(expand),
834
847
  bodyFiles,
835
848
  unresolvedBodyFiles,
836
849
  // An unquoted `$(` splits at the paren, leaving a value that ends in `$`.
@@ -839,6 +852,9 @@ function parseWithState(
839
852
  inline.some((value) => value.endsWith("$")) ||
840
853
  (action === "edit" && target?.endsWith("$") === true) ||
841
854
  (fromStdin && stdin.length === 0 && args.stdinFile === undefined),
855
+ externalBody:
856
+ values("fill", "fill-first", "fill-verbose", "template", "editor", "web", "recover")
857
+ .length > 0,
842
858
  });
843
859
  };
844
860
 
@@ -861,6 +877,25 @@ function parseWithState(
861
877
  const BLOCK_ADVICE =
862
878
  "Paraphrase the motivation ('a maintainer asked for X') and link only public sources (Fixes #N, a public PR or issue). Task, session, agent-fs and Slack permalinks go only under '## Swarm provenance'. Bare Slack ids, ts values and private-chat quotes are blocked everywhere.";
863
879
 
880
+ /** Read body files relative to the parsed checkout and retain the first unreadable path. */
881
+ export async function readGhPrBodyFiles(
882
+ call: GhPrInvocation,
883
+ readFile: PrBodyGuardDeps["readFile"],
884
+ ): Promise<{ texts: string[]; unreadable: string | undefined }> {
885
+ const texts: string[] = [];
886
+ let unreadable = call.unresolvedBodyFiles[0];
887
+ for (const bodyFile of call.bodyFiles) {
888
+ try {
889
+ const base = isAbsolute(bodyFile) ? "/" : call.cwd;
890
+ if (base === null) throw new Error("unknown working directory");
891
+ texts.push(await readFile(resolve(base, bodyFile)));
892
+ } catch {
893
+ unreadable ??= bodyFile;
894
+ }
895
+ }
896
+ return { texts, unreadable };
897
+ }
898
+
864
899
  /** Check a shell command. Returns a block reason, or null to allow. */
865
900
  export async function checkGhPrCommand(
866
901
  command: string,
@@ -873,17 +908,9 @@ export async function checkGhPrCommand(
873
908
  for (const text of call.opaqueBody ? [...call.texts, command] : call.texts) {
874
909
  for (const leak of findPrBodyLeaks(text)) leaks.add(leak);
875
910
  }
876
- let unreadable: string | undefined = call.unresolvedBodyFiles[0];
877
- for (const bodyFile of call.bodyFiles) {
878
- try {
879
- const base = isAbsolute(bodyFile) ? "/" : call.cwd;
880
- if (base === null) throw new Error("unknown working directory");
881
- for (const leak of findPrBodyLeaks(await deps.readFile(resolve(base, bodyFile)))) {
882
- leaks.add(leak);
883
- }
884
- } catch {
885
- unreadable ??= bodyFile;
886
- }
911
+ const { texts, unreadable } = await readGhPrBodyFiles(call, deps.readFile);
912
+ for (const text of texts) {
913
+ for (const leak of findPrBodyLeaks(text)) leaks.add(leak);
887
914
  }
888
915
  if (leaks.size === 0 && unreadable === undefined) continue;
889
916
 
@@ -0,0 +1,88 @@
1
+ import { parseGhPrCommands, readGhPrBodyFiles, shellCommandOf } from "./pr-body-guard";
2
+
3
+ export type PrRoomGuardDeps = {
4
+ env: Record<string, string | undefined>;
5
+ readFile: (path: string) => Promise<string>;
6
+ remoteOrigin: (cwd: string) => Promise<string>;
7
+ };
8
+
9
+ const BLOCK_REASON =
10
+ 'This repo uses PR rooms. Create the room first with the pr-room skill (app action createRoom). Then pass the returned prBody with --body-file. The body must contain the line "PR room: prr-...".';
11
+ const ROOM_LINE = /^PR room: prr-[a-z2-7]{10}\b/m;
12
+ const REMOTE_LOOKUP_TIMEOUT_MS = 5_000;
13
+
14
+ /** Read the `remote.origin.url` of a checkout. A lookup that does not finish in `timeoutMs` is killed and rejects. */
15
+ export async function gitRemoteOrigin(
16
+ cwd: string,
17
+ timeoutMs = REMOTE_LOOKUP_TIMEOUT_MS,
18
+ ): Promise<string> {
19
+ const proc = Bun.spawn(["git", "-C", cwd, "config", "--get", "remote.origin.url"], {
20
+ stdout: "pipe",
21
+ stderr: "ignore",
22
+ stdin: "ignore",
23
+ timeout: timeoutMs,
24
+ killSignal: "SIGKILL",
25
+ });
26
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
27
+ if (code !== 0) throw new Error("Cannot resolve checkout remote");
28
+ return out.trim();
29
+ }
30
+
31
+ const defaultDeps = (): PrRoomGuardDeps => ({
32
+ env: process.env,
33
+ readFile: (path) => Bun.file(path).text(),
34
+ remoteOrigin: (cwd) => gitRemoteOrigin(cwd),
35
+ });
36
+
37
+ /** Extract owner/repo from an HTTPS, SSH URL, or SCP-style SSH remote. */
38
+ function repoFromRemote(remote: string): string | undefined {
39
+ const match =
40
+ /^(?:https?:\/\/[^/]+\/|ssh:\/\/[^/]+\/|[^\s/@:]+@[^\s/:]+:)([^/\s]+)\/([^/\s]+?)\/?$/.exec(
41
+ remote.trim(),
42
+ );
43
+ return match ? `${match[1]}/${match[2]?.replace(/\.git$/, "")}`.toLowerCase() : undefined;
44
+ }
45
+
46
+ /** Require a room marker for configured repositories. Unresolved or ambiguous inputs allow the command. */
47
+ export async function guardGhPrRoom(
48
+ toolInput: unknown,
49
+ cwd: string,
50
+ deps: PrRoomGuardDeps = defaultDeps(),
51
+ ): Promise<string | null> {
52
+ try {
53
+ const repos = new Set(
54
+ (deps.env.PR_ROOM_REPOS ?? "")
55
+ .split(",")
56
+ .map((repo) => repo.trim().toLowerCase())
57
+ .filter(Boolean),
58
+ );
59
+ if (repos.size === 0) return null;
60
+ const command = shellCommandOf(toolInput);
61
+ if (command === null) return null;
62
+ for (const call of parseGhPrCommands(command, cwd, deps.env)) {
63
+ if (call.action !== "create" || call.unknownTarget || call.cwd === null) continue;
64
+ // The body comes from a pipe, a command substitution, gh itself, or an unknown variable.
65
+ if (call.opaqueBody || call.externalBody || call.bodyTexts.includes(null)) continue;
66
+ const targets = [...call.resolvedRepos];
67
+ if (targets.includes(null)) continue;
68
+ if (targets.length === 0 || call.alsoCheckout) {
69
+ try {
70
+ const repo = repoFromRemote(await deps.remoteOrigin(call.cwd));
71
+ if (!repo) continue;
72
+ targets.push(repo);
73
+ } catch {
74
+ continue;
75
+ }
76
+ }
77
+ // An unexported `GH_REPO=x` may not reach gh: block only when every candidate is listed.
78
+ if (!targets.every((repo) => repo !== null && repos.has(repo.trim().toLowerCase()))) continue;
79
+ const body = await readGhPrBodyFiles(call, deps.readFile);
80
+ if (body.unreadable !== undefined) continue;
81
+ const texts = [...call.bodyTexts, ...body.texts];
82
+ if (!texts.some((text) => text !== null && ROOM_LINE.test(text))) return BLOCK_REASON;
83
+ }
84
+ } catch {
85
+ // A workflow reminder must not prevent commands when resolution fails.
86
+ }
87
+ return null;
88
+ }
@@ -1502,6 +1502,7 @@ export async function handleAgentsRest(
1502
1502
  reportedAt: parsed.body.latest_model?.reportedAt ?? parsed.body.acp!.reportedAt,
1503
1503
  reportKind: "post_task" as const,
1504
1504
  bedrock: null,
1505
+ opencodeGo: null,
1505
1506
  acp: null,
1506
1507
  };
1507
1508
  finalAgent =