@desplega.ai/agent-swarm 2.4.0 → 2.4.2

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 (239) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-kedstxx6.js → acp-adapter-dv7hmx4r.js} +9 -9
  3. package/dist/{actions-vget6r1k.js → actions-51nc1gny.js} +21 -21
  4. package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-abp5tdx8.js} +4 -4
  5. package/dist/{app-mqap8t8m.js → app-9h14qvr6.js} +3 -3
  6. package/dist/{assistant-h0gv97mv.js → assistant-wv1gz3mt.js} +22 -22
  7. package/dist/{boot-reembed-qr8a894z.js → boot-reembed-6yn53ms9.js} +1 -1
  8. package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-7fcka4z5.js} +1 -1
  9. package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-pm1580ax.js} +4 -4
  10. package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-g5hfc1x8.js} +4 -4
  11. package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gf3kxhf0.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-s2k1qd8y.js} +13 -13
  15. package/dist/{cli-tj4j2ts4.js → cli-02qm7src.js} +8 -8
  16. package/dist/{cli-1cevz46c.js → cli-0qvtffe9.js} +6 -6
  17. package/dist/{cli-avmqhatn.js → cli-10hhg0km.js} +8 -8
  18. package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
  19. package/dist/{cli-kz116kvw.js → cli-239yjk27.js} +7 -7
  20. package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
  21. package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
  22. package/dist/{cli-x719j3vs.js → cli-5rk36sjf.js} +442 -205
  23. package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
  24. package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
  25. package/dist/{cli-5s5hx57y.js → cli-7awf1fp8.js} +1 -1
  26. package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
  27. package/dist/{cli-ywyrffff.js → cli-8sxqsbbq.js} +1 -1
  28. package/dist/{cli-rsd7ga7v.js → cli-8zwe9mpk.js} +11 -11
  29. package/dist/{cli-y83zk5sb.js → cli-a0kcty51.js} +4 -4
  30. package/dist/{cli-8b9wvjps.js → cli-a4gvwttf.js} +4 -4
  31. package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
  32. package/dist/{cli-mvg2hf8p.js → cli-ahtpy9kf.js} +14 -7
  33. package/dist/{cli-x1p5dhq2.js → cli-bcj753sq.js} +3 -3
  34. package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
  35. package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
  36. package/dist/{cli-rg7ntypm.js → cli-d3qqdy4c.js} +456 -94
  37. package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
  38. package/dist/{cli-02vhhssk.js → cli-efaqv1c7.js} +5 -5
  39. package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
  40. package/dist/{cli-kk2r80n5.js → cli-faypxz7q.js} +4 -4
  41. package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
  42. package/dist/{cli-wrv09v7q.js → cli-ftyypbg5.js} +1 -1
  43. package/dist/{cli-zvze9rbm.js → cli-fyzjmseb.js} +3 -3
  44. package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
  45. package/dist/{cli-nrrg0fvy.js → cli-h38rjv87.js} +133 -12
  46. package/dist/{cli-tg9ksbn8.js → cli-h93c9vb3.js} +2 -2
  47. package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
  48. package/dist/{cli-bhwa8rzc.js → cli-hyfw2j1e.js} +13 -12
  49. package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
  50. package/dist/{cli-6ftrrwm1.js → cli-n85dqg6d.js} +5 -5
  51. package/dist/{cli-8cxkw1f5.js → cli-nmqdc2t3.js} +2 -2
  52. package/dist/{cli-4fhyq711.js → cli-nn47d7e8.js} +13 -13
  53. package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
  54. package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
  55. package/dist/{cli-xfe63zx3.js → cli-pkht5hap.js} +2 -2
  56. package/dist/{cli-sppc0cmw.js → cli-pnp2j5zg.js} +2 -2
  57. package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
  58. package/dist/{cli-tgvpmnwz.js → cli-r99kc9yq.js} +5 -5
  59. package/dist/{cli-bfc3k1x5.js → cli-rdyw3mct.js} +9 -9
  60. package/dist/{cli-b80a1100.js → cli-s6vr7r2d.js} +2 -2
  61. package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
  62. package/dist/{cli-ttaz6c7m.js → cli-saw9a6f6.js} +6 -5
  63. package/dist/{cli-mz1ezzzh.js → cli-shvfedvm.js} +3 -3
  64. package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
  65. package/dist/{cli-2rwet3r7.js → cli-sv5gb0yb.js} +1 -1
  66. package/dist/{cli-5ff2sjyt.js → cli-t30yj95q.js} +2 -2
  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-nme7wery.js → cli-y2x52gs2.js} +46 -13
  73. package/dist/{cli-ffbrkh1r.js → cli-y8n5k515.js} +46 -38
  74. package/dist/{cli-ecshf504.js → cli-z4wd2ftv.js} +7 -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 +20 -20
  78. package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-ttygw382.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-60b58vv3.js} +7 -7
  82. package/dist/{commands-psqhkttb.js → commands-edmdwa4x.js} +2 -2
  83. package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
  84. package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-5fym8sw1.js} +23 -23
  85. package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
  86. package/dist/{db-nc1rd869.js → db-2y0mgstc.js} +3 -3
  87. package/dist/{db-2xbyenjr.js → db-nn0hcmx5.js} +6 -2
  88. package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
  89. package/dist/{dispatcher-ewceb8ts.js → dispatcher-0a9wwjcx.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-zhvt95n3.js} +2 -2
  98. package/dist/{grok-adapter-vj88xr23.js → grok-adapter-qnr1hp2y.js} +10 -10
  99. package/dist/handlers-dkcarkd0.js +39 -0
  100. package/dist/{heartbeat-t2m9r3wa.js → heartbeat-gz97r7ac.js} +23 -23
  101. package/dist/{hook-xa677f8r.js → hook-1mmdgjqs.js} +16 -9
  102. package/dist/{hook-8femx1bg.js → hook-x8rthca4.js} +6 -6
  103. package/dist/{http-xpmmq028.js → http-r8f85xr9.js} +274 -160
  104. package/dist/index-5jecgp3n.js +41 -0
  105. package/dist/index-aex0eba9.js +40 -0
  106. package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
  107. package/dist/{index-cdzyfrsp.js → index-smxyk04s.js} +1 -1
  108. package/dist/{index-dg2tmj99.js → index-vzvfqzfj.js} +20 -20
  109. package/dist/{index-s7ckdajq.js → index-y7rbcrc1.js} +10 -10
  110. package/dist/{keepalive-3nr4st3r.js → keepalive-64c0f34c.js} +4 -4
  111. package/dist/lead-b1da0f97.js +78 -0
  112. package/dist/{maintenance-verwpe2c.js → maintenance-mnxpar12.js} +1 -1
  113. package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-36hs2v4h.js} +3 -3
  114. package/dist/{onboard-aytqedmq.js → onboard-xes8de61.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-fzc57kcq.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-1nek2w7b.js} +3 -3
  119. package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-sqefb5hw.js} +2 -2
  120. package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-ttyfaep2.js} +1 -1
  121. package/dist/{render-v2-7nqgvk2q.js → render-v2-vb527jwn.js} +3 -3
  122. package/dist/{resume-b6djcj3c.js → resume-jcdzg1bn.js} +10 -8
  123. package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
  124. package/dist/{session-access-fc3f9y12.js → session-access-zsp4wsp0.js} +4 -4
  125. package/dist/{setup-n39szm6b.js → setup-1nb2zqd3.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-61w08fwr.js} +23 -23
  128. package/dist/worker-c1e68hpa.js +78 -0
  129. package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
  130. package/openapi.json +491 -52
  131. package/package.json +3 -3
  132. package/src/apps/action-runner.ts +146 -0
  133. package/src/apps/catalog.generated.json +3 -2
  134. package/src/apps/definition.ts +152 -4
  135. package/src/apps/row-store.ts +23 -9
  136. package/src/apps/schema-migrate.ts +14 -6
  137. package/src/apps/script-types.ts +2 -1
  138. package/src/apps/sync.ts +20 -19
  139. package/src/be/db/tasks/read.ts +112 -0
  140. package/src/be/db.ts +16 -4
  141. package/src/be/fleet-benchmark-stats.ts +10 -5
  142. package/src/be/models-catalog.ts +4 -0
  143. package/src/be/onboarding.ts +6 -0
  144. package/src/be/scripts/typecheck.ts +13 -0
  145. package/src/be/swarm-config-guard.ts +25 -1
  146. package/src/be/telemetry-enable.ts +1 -1
  147. package/src/commands/provider-credentials.ts +12 -2
  148. package/src/commands/runner.ts +3 -0
  149. package/src/heartbeat/heartbeat.ts +2 -2
  150. package/src/hooks/codex-hook.ts +6 -2
  151. package/src/hooks/hook.ts +7 -0
  152. package/src/hooks/pr-body-guard.ts +38 -11
  153. package/src/hooks/pr-room-guard.ts +88 -0
  154. package/src/http/agents.ts +1 -0
  155. package/src/http/apps.ts +99 -183
  156. package/src/http/fleet-benchmark.ts +79 -30
  157. package/src/http/global-config.ts +7 -0
  158. package/src/http/ranking.ts +24 -40
  159. package/src/http/status.ts +49 -43
  160. package/src/http/tasks.ts +49 -1
  161. package/src/http/telemetry-ui.ts +1 -1
  162. package/src/http/used-by.ts +55 -19
  163. package/src/http/users.ts +2 -2
  164. package/src/providers/acp-targets.ts +1 -1
  165. package/src/providers/amp-adapter.ts +1 -1
  166. package/src/providers/pi-mono-adapter.ts +15 -0
  167. package/src/providers/reasoning-effort.ts +2 -1
  168. package/src/providers/types.ts +2 -0
  169. package/src/ranking/telemetry-ranking.ts +5 -33
  170. package/src/scripts-runtime/sdk-allowlist.ts +7 -0
  171. package/src/scripts-runtime/types/stdlib.d.ts +31 -0
  172. package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
  173. package/src/server.ts +4 -0
  174. package/src/telemetry-context.ts +19 -1
  175. package/src/telemetry-contract/EVENTS.md +42 -8
  176. package/src/telemetry-contract/SOURCE.json +6 -5
  177. package/src/telemetry-contract/catalog.json +225 -5
  178. package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
  179. package/src/telemetry-contract/ranking/listing.v1.schema.json +2 -2
  180. package/src/telemetry-contract/types.gen.ts +225 -27
  181. package/src/telemetry-identity.ts +47 -9
  182. package/src/telemetry-snapshot.ts +9 -1
  183. package/src/telemetry-ui-events.ts +9 -2
  184. package/src/telemetry.ts +29 -6
  185. package/src/tests/app-action-run-tool.test.ts +178 -0
  186. package/src/tests/app-row-tools.test.ts +385 -0
  187. package/src/tests/apps-json-column.test.ts +611 -0
  188. package/src/tests/apps-script-types.test.ts +2 -0
  189. package/src/tests/apps-sync-engine.test.ts +42 -0
  190. package/src/tests/check-pr-body.test.ts +0 -23
  191. package/src/tests/credential-check.test.ts +38 -0
  192. package/src/tests/db-task-read-contract.test.ts +4 -0
  193. package/src/tests/db-task-write-contract.test.ts +2 -0
  194. package/src/tests/docs-index-footprint.test.js +2 -2
  195. package/src/tests/fleet-benchmark.test.ts +122 -2
  196. package/src/tests/http-users.test.ts +62 -1
  197. package/src/tests/model-catalog-table.test.ts +63 -0
  198. package/src/tests/model-profiles-api.test.ts +19 -7
  199. package/src/tests/models-catalog.test.ts +50 -0
  200. package/src/tests/onboarding.test.ts +21 -0
  201. package/src/tests/org-website.test.ts +62 -0
  202. package/src/tests/pi-mono-adapter.test.ts +23 -0
  203. package/src/tests/pr-body-leaks.test.ts +4 -0
  204. package/src/tests/pr-room-guard.test.ts +242 -0
  205. package/src/tests/ranking-listing.test.ts +8 -23
  206. package/src/tests/ranking-route.test.ts +49 -47
  207. package/src/tests/reasoning-effort.test.ts +39 -3
  208. package/src/tests/runner-harness-telemetry-env.test.ts +62 -1
  209. package/src/tests/status.test.ts +158 -9
  210. package/src/tests/swarm-tool-result-gate.test.ts +2 -4
  211. package/src/tests/task-lineage.test.ts +180 -0
  212. package/src/tests/telemetry-context.test.ts +128 -2
  213. package/src/tests/telemetry-init.test.ts +62 -5
  214. package/src/tests/telemetry-ui-route.test.ts +123 -0
  215. package/src/tests/tool-annotations.test.ts +3 -2
  216. package/src/tests/used-by-route.test.ts +144 -10
  217. package/src/tests/user-token-rest-auth.test.ts +38 -0
  218. package/src/tests/workflow-agent-task-retry.test.ts +130 -4
  219. package/src/tests/workflow-wait-recovery.test.ts +116 -4
  220. package/src/tools/app-action-run.ts +63 -0
  221. package/src/tools/app-rows.ts +239 -0
  222. package/src/tools/model-catalog/index.ts +1 -1
  223. package/src/tools/tool-config.ts +8 -0
  224. package/src/tools/utils.ts +1 -1
  225. package/src/tools/workflows/retry-workflow-run.ts +1 -1
  226. package/src/types.ts +17 -0
  227. package/src/utils/company-profile.ts +24 -0
  228. package/src/utils/org-website.ts +58 -21
  229. package/src/workflows/recovery.ts +10 -22
  230. package/src/workflows/resume.ts +16 -0
  231. package/src/workflows/task-step-routing.ts +47 -7
  232. package/templates/skills/apps/SKILL.md +44 -4
  233. package/templates/skills/apps/content.md +44 -4
  234. package/dist/handlers-zbs90bny.js +0 -39
  235. package/dist/index-8pwc1jc8.js +0 -40
  236. package/dist/index-nxa6me0q.js +0 -41
  237. package/dist/lead-td8h2sfn.js +0 -78
  238. package/dist/worker-wr6xgfr2.js +0 -78
  239. package/src/tests/check-ranking-entry.test.ts +0 -146
@@ -0,0 +1,239 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { AppNameSchema, type ModelDef, SYSTEM_COLUMN_KINDS } from "../apps/definition";
4
+ import {
5
+ type AppRow,
6
+ AppRowAppNotFoundError,
7
+ AppRowValidationError,
8
+ createAppRow,
9
+ createAppRows,
10
+ deleteAppRow,
11
+ getAppRow,
12
+ patchAppRow,
13
+ validValue,
14
+ } from "../apps/row-store";
15
+ import { appDefinitionNeedsRepair, getApp } from "../apps/store";
16
+ import { getAgentById } from "../be/db";
17
+ import {
18
+ MAX_APP_BULK_ROWS_BODY_BYTES,
19
+ MAX_APP_ROW_BODY_BYTES,
20
+ rowMatchesFilters,
21
+ selectAppRows,
22
+ } from "../http/apps";
23
+ import { can } from "../rbac";
24
+ import {
25
+ createToolRegistrar,
26
+ type SwarmToolResult,
27
+ swarmToolOutputSchema,
28
+ toolErr,
29
+ toolOk,
30
+ } from "./utils";
31
+
32
+ const base = { appId: z.string().min(1), model: AppNameSchema };
33
+ const rowId = z.string().min(1);
34
+ const values = z.record(z.string(), z.unknown());
35
+ const equals = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]));
36
+ const limit = z.number().int().min(1).max(1000);
37
+ const rowOutput = { row: z.looseObject({}).optional() };
38
+ const rowsOutput = { rows: z.array(z.looseObject({})).optional() };
39
+
40
+ function checkSize(value: unknown, max: number): void {
41
+ if (Buffer.byteLength(JSON.stringify(value), "utf8") > max) {
42
+ throw new AppRowValidationError([
43
+ { path: "values", message: `Row input exceeds ${max} bytes.` },
44
+ ]);
45
+ }
46
+ }
47
+
48
+ function rowFilters(model: ModelDef, equals: Record<string, string | number | boolean> = {}) {
49
+ return Object.entries(equals).map(([column, value]) => {
50
+ const declared = Object.hasOwn(model.columns, column) ? model.columns[column] : undefined;
51
+ if ((!declared && !Object.hasOwn(SYSTEM_COLUMN_KINDS, column)) || declared?.hidden) {
52
+ throw new AppRowValidationError([
53
+ { path: `equals.${column}`, message: "Unknown or hidden column." },
54
+ ]);
55
+ }
56
+ if (declared?.kind === "json") {
57
+ throw new AppRowValidationError([
58
+ { path: `equals.${column}`, message: "json columns cannot be filtered" },
59
+ ]);
60
+ }
61
+ if (!validValue(declared ?? { kind: SYSTEM_COLUMN_KINDS[column]! }, value)) {
62
+ throw new AppRowValidationError([
63
+ { path: `equals.${column}`, message: "Filter value must match the column kind." },
64
+ ]);
65
+ }
66
+ return { column, value };
67
+ });
68
+ }
69
+
70
+ export function registerAppRowTools(server: McpServer) {
71
+ function register<Shape extends z.ZodRawShape>(
72
+ name: string,
73
+ description: string,
74
+ shape: Shape,
75
+ output: z.ZodRawShape,
76
+ operation: (
77
+ args: z.infer<z.ZodObject<typeof base & Shape>>,
78
+ model: ModelDef,
79
+ actor: string,
80
+ ) => Promise<SwarmToolResult>,
81
+ readOnly = false,
82
+ ) {
83
+ createToolRegistrar(server)(
84
+ name,
85
+ {
86
+ title: description,
87
+ description,
88
+ annotations: { readOnlyHint: readOnly },
89
+ rbac: { permission: "app.use" },
90
+ inputSchema: z.object({ ...base, ...shape }),
91
+ outputSchema: swarmToolOutputSchema(output),
92
+ },
93
+ async (args, info) => {
94
+ if (!info.agentId) return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
95
+ const { appId, model: modelName } = z.object(base).parse(args);
96
+ const agent = await getAgentById(info.agentId);
97
+ const decision = can({
98
+ principal: { kind: "agent", agentId: info.agentId, isLead: agent?.isLead ?? false },
99
+ verb: "app.use",
100
+ resource: { kind: "app", appId },
101
+ source: "mcp",
102
+ });
103
+ if (!decision.allow) return toolErr(decision.reason);
104
+ const app = await getApp(appId);
105
+ if (!app) return toolErr("App not found.");
106
+ if (appDefinitionNeedsRepair(app))
107
+ return toolErr("App definition needs repair.", { data: { issues: app.definitionError } });
108
+ if (!Object.hasOwn(app.definition.models, modelName)) return toolErr("Model not found.");
109
+ try {
110
+ return await operation(args, app.definition.models[modelName]!, `agent:${info.agentId}`);
111
+ } catch (error) {
112
+ if (error instanceof AppRowValidationError)
113
+ return toolErr(error.message, { data: { issues: error.issues } });
114
+ if (error instanceof AppRowAppNotFoundError) return toolErr(error.message);
115
+ throw error;
116
+ }
117
+ },
118
+ );
119
+ }
120
+
121
+ register(
122
+ "app-rows-list",
123
+ "List app rows with equality filters, sorting, and a limit.",
124
+ {
125
+ equals: equals.optional(),
126
+ sort: z.string().optional(),
127
+ limit: limit.optional(),
128
+ },
129
+ { ...rowsOutput, total: z.number().optional() },
130
+ async (args, model) => {
131
+ const data = await selectAppRows(
132
+ args.appId,
133
+ args.model,
134
+ model,
135
+ rowFilters(model, args.equals),
136
+ args.sort,
137
+ args.limit,
138
+ true,
139
+ );
140
+ return toolOk(`Found ${data.total} row(s).`, { data });
141
+ },
142
+ true,
143
+ );
144
+
145
+ register(
146
+ "app-row-get",
147
+ "Get an app row.",
148
+ { rowId },
149
+ rowOutput,
150
+ async (args) => {
151
+ const row = await getAppRow(args.appId, args.model, args.rowId);
152
+ return row ? toolOk("Row retrieved.", { data: { row } }) : toolErr("Row not found.");
153
+ },
154
+ true,
155
+ );
156
+
157
+ register(
158
+ "app-row-create",
159
+ "Create an app row.",
160
+ { values },
161
+ rowOutput,
162
+ async (args, model, actor) => {
163
+ checkSize({ values: args.values }, MAX_APP_ROW_BODY_BYTES);
164
+ const row = await createAppRow(args.appId, args.model, model, args.values, { actor });
165
+ return toolOk("Row created.", { data: { row } });
166
+ },
167
+ );
168
+
169
+ register(
170
+ "app-rows-create",
171
+ "Create up to 500 app rows within 10 MiB.",
172
+ {
173
+ rows: z.array(values).min(1).max(500),
174
+ },
175
+ rowsOutput,
176
+ async (args, model, actor) => {
177
+ checkSize({ rows: args.rows }, MAX_APP_BULK_ROWS_BODY_BYTES);
178
+ for (const values of args.rows) checkSize({ values }, MAX_APP_ROW_BODY_BYTES);
179
+ const rows = await createAppRows(args.appId, args.model, model, args.rows, { actor });
180
+ return toolOk(`Created ${rows.length} row(s).`, { data: { rows } });
181
+ },
182
+ );
183
+
184
+ register(
185
+ "app-row-patch",
186
+ "Patch an app row.",
187
+ { rowId, values },
188
+ rowOutput,
189
+ async (args, model, actor) => {
190
+ checkSize({ values: args.values }, MAX_APP_ROW_BODY_BYTES);
191
+ const row = await patchAppRow(args.appId, args.model, model, args.rowId, args.values, {
192
+ actor,
193
+ });
194
+ return row ? toolOk("Row updated.", { data: { row } }) : toolErr("Row not found.");
195
+ },
196
+ );
197
+
198
+ register(
199
+ "app-row-delete",
200
+ "Delete an app row.",
201
+ { rowId },
202
+ { ok: z.boolean().optional() },
203
+ async (args, model) => {
204
+ const deleted = await deleteAppRow(args.appId, args.model, model, args.rowId);
205
+ return deleted ? toolOk("Row deleted.", { data: { ok: true } }) : toolErr("Row not found.");
206
+ },
207
+ );
208
+
209
+ register(
210
+ "app-rows-delete",
211
+ "Delete matching app rows. Supply at least one equality filter.",
212
+ {
213
+ equals: equals.refine(
214
+ (value) => Object.keys(value).length > 0,
215
+ "Supply at least one equality filter.",
216
+ ),
217
+ limit: limit.optional(),
218
+ },
219
+ { deleted: z.number().optional() },
220
+ async (args, model) => {
221
+ const filters = rowFilters(model, args.equals);
222
+ const { rows } = await selectAppRows(
223
+ args.appId,
224
+ args.model,
225
+ model,
226
+ filters,
227
+ undefined,
228
+ args.limit ?? 1000,
229
+ true,
230
+ );
231
+ // Re-check under the row lock: a row patched out of the filter since the select is kept.
232
+ const matches = (row: AppRow) => rowMatchesFilters(row, filters);
233
+ let deleted = 0;
234
+ for (const row of rows)
235
+ if (await deleteAppRow(args.appId, args.model, model, row.id, matches)) deleted++;
236
+ return toolOk(`Deleted ${deleted} row(s).`, { data: { deleted } });
237
+ },
238
+ );
239
+ }
@@ -87,7 +87,7 @@ export const registerModelCatalogOverlayUpsertTool = (server: McpServer) => {
87
87
  .string()
88
88
  .min(1)
89
89
  .describe(
90
- `models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode, ${openAICompatibleProviderIds().join(", ")}.`,
90
+ `models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode, opencode-go, ${openAICompatibleProviderIds().join(", ")}.`,
91
91
  ),
92
92
  modelId: z
93
93
  .string()
@@ -189,6 +189,14 @@ export const DEFERRED_TOOLS = new Set([
189
189
  "app-list",
190
190
  "app-patch",
191
191
  "app-query",
192
+ "app-rows-list",
193
+ "app-row-get",
194
+ "app-row-create",
195
+ "app-rows-create",
196
+ "app-row-patch",
197
+ "app-row-delete",
198
+ "app-rows-delete",
199
+ "app-action-run",
192
200
  "app-sync",
193
201
  "app-upsert",
194
202
  "app-history",
@@ -347,7 +347,7 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
347
347
  const hasQueries = Object.keys(definition?.queries ?? {}).length > 0;
348
348
  const hasActions = Object.keys(definition?.actions ?? {}).length > 0;
349
349
  return hasQueries || hasActions
350
- ? "This app's callable surface is `definition.queries` (run with `app-query`, passing any `$param` values) and `definition.actions` (invoke with `POST /api/apps/<id>/actions/<name>`; `sync` actions refresh sources)."
350
+ ? "Run `definition.queries` with `app-query` and supply each `$param` value. Invoke `definition.actions` with `app-action-run` through MCP or `POST /api/apps/<id>/actions/<name>` through REST. Sync actions refresh sources."
351
351
  : undefined;
352
352
  },
353
353
  // kv-get is spill-exempt (retrieval path), so oversized values reach the
@@ -10,7 +10,7 @@ export const registerRetryWorkflowRunTool = (server: McpServer) => {
10
10
  title: "Retry Workflow Run",
11
11
  annotations: { destructiveHint: false },
12
12
  description:
13
- "Retry a failed workflow run from the beginning. The run must be in 'failed' status.",
13
+ "Resume the same failed workflow run from its failed step(s); completed steps are not re-run. Retrying re-creates the failed node's side effects (e.g. an agent task), so check the target state first if that work was already done manually.",
14
14
  inputSchema: z.object({
15
15
  runId: z.string().uuid().describe("Workflow run ID to retry"),
16
16
  }),
package/src/types.ts CHANGED
@@ -1362,6 +1362,14 @@ export const AgentBedrockStatusSchema = z
1362
1362
  .openapi("AgentBedrockStatus");
1363
1363
  export type AgentBedrockStatus = z.infer<typeof AgentBedrockStatusSchema>;
1364
1364
 
1365
+ export const AgentOpencodeGoStatusSchema = z
1366
+ .object({
1367
+ probedAt: z.number(), // unix ms
1368
+ models: z.array(z.string()).default([]),
1369
+ })
1370
+ .openapi("AgentOpencodeGoStatus");
1371
+ export type AgentOpencodeGoStatus = z.infer<typeof AgentOpencodeGoStatusSchema>;
1372
+
1365
1373
  const AcpSessionConfigSelectValueSchema = z.object({
1366
1374
  value: z.string(),
1367
1375
  name: z.string(),
@@ -1421,6 +1429,8 @@ export const AgentCredStatusSchema = z
1421
1429
  reportKind: z.enum(["boot", "post_task"]).default("boot"),
1422
1430
  /** Pi-mono Bedrock enumeration block — null when not in Bedrock mode. */
1423
1431
  bedrock: AgentBedrockStatusSchema.nullable().default(null),
1432
+ /** Pi-mono only: the `opencode-go` model ids the worker's pi-ai resolves. */
1433
+ opencodeGo: AgentOpencodeGoStatusSchema.nullable().default(null),
1424
1434
  /** ACP options advertised by the most recently created session. */
1425
1435
  acp: AgentAcpStatusSchema.nullable().default(null),
1426
1436
  })
@@ -2210,6 +2220,13 @@ export const RetryPolicySchema = z
2210
2220
  .openapi("RetryPolicy");
2211
2221
  export type RetryPolicy = z.infer<typeof RetryPolicySchema>;
2212
2222
 
2223
+ /**
2224
+ * `failureReason` of a workflow-step task cut by a supersede (deploy drain,
2225
+ * heartbeat remediation). The workflow engine re-dispatches it once even
2226
+ * when the node declares no `retry` (see `scheduleTaskStepRetry`).
2227
+ */
2228
+ export const SUPERSEDED_WORKFLOW_TASK_REASON = "superseded_workflow_task";
2229
+
2213
2230
  // --- Executor Metadata ---
2214
2231
 
2215
2232
  export const ExecutorMetaSchema = z
@@ -0,0 +1,24 @@
1
+ export const COMPANY_TEAM_SIZES = ["1", "2-10", "11-50", "51+"] as const;
2
+
3
+ export const COMPANY_USE_CASES = ["features", "bugs", "review", "ops", "docs", "data"] as const;
4
+
5
+ export const COMPANY_ACQUISITION_SOURCES = [
6
+ "search",
7
+ "github",
8
+ "social",
9
+ "colleague",
10
+ "newsletter",
11
+ "event",
12
+ "ai_assistant",
13
+ "hacker_news",
14
+ "other",
15
+ ] as const;
16
+
17
+ export const USER_FUNCTIONS = [
18
+ "engineer",
19
+ "eng_lead",
20
+ "founder",
21
+ "product",
22
+ "ops",
23
+ "other",
24
+ ] as const;
@@ -71,34 +71,71 @@ export function isFilledOrgLogoUrl(
71
71
  }
72
72
  }
73
73
 
74
+ /**
75
+ * Free-mail providers with regional domains, matched on the label before the
76
+ * public suffix: hotmail.es, yahoo.co.uk, gmx.net and yandex.com.tr all count.
77
+ */
78
+ const FREE_MAIL_FAMILIES = new Set([
79
+ "aol",
80
+ "fastmail",
81
+ "gmail",
82
+ "gmx",
83
+ "googlemail",
84
+ "hotmail",
85
+ "icloud",
86
+ "live",
87
+ "msn",
88
+ "outlook",
89
+ "protonmail",
90
+ "rocketmail",
91
+ "yahoo",
92
+ "yandex",
93
+ "ymail",
94
+ "zoho",
95
+ ]);
96
+
97
+ /** Free-mail providers on one domain only, where the label alone is too generic. */
74
98
  const FREE_MAIL_DOMAINS = new Set([
75
- "gmail.com",
76
- "googlemail.com",
77
- "outlook.com",
78
- "hotmail.com",
79
- "live.com",
80
- "msn.com",
81
- "yahoo.com",
82
- "icloud.com",
83
- "me.com",
99
+ "126.com",
100
+ "163.com",
101
+ "comcast.net",
102
+ "daum.net",
103
+ "free.fr",
104
+ "hanmail.net",
105
+ "hey.com",
106
+ "libero.it",
84
107
  "mac.com",
85
- "aol.com",
86
- "proton.me",
87
- "protonmail.com",
88
- "gmx.com",
89
- "gmx.de",
90
- "yandex.com",
91
- "yandex.ru",
92
108
  "mail.com",
93
- "zoho.com",
109
+ "mail.ru",
110
+ "me.com",
111
+ "naver.com",
112
+ "orange.fr",
113
+ "pm.me",
114
+ "proton.me",
94
115
  "qq.com",
95
- "163.com",
96
- "hey.com",
97
- "fastmail.com",
116
+ "rediffmail.com",
117
+ "sina.com",
118
+ "t-online.de",
119
+ "tutanota.com",
120
+ "web.de",
98
121
  ]);
99
122
 
123
+ /** Second-level labels of a country code suffix (acme.co.uk, acme.com.br). */
124
+ const SECOND_LEVEL_LABELS = new Set(["ac", "co", "com", "edu", "gov", "ne", "net", "or", "org"]);
125
+
126
+ /** Whether `host` (a normalized hostname) belongs to a public free-mail provider. */
127
+ function isFreeMailHost(host: string): boolean {
128
+ const labels = host.split(".");
129
+ const suffixLength = labels.length > 2 && SECOND_LEVEL_LABELS.has(labels.at(-2) ?? "") ? 2 : 1;
130
+ // mail.yahoo.co.uk becomes yahoo.co.uk.
131
+ const registrable = labels.slice(-(suffixLength + 1));
132
+ return (
133
+ FREE_MAIL_FAMILIES.has(registrable[0] ?? "") || FREE_MAIL_DOMAINS.has(registrable.join("."))
134
+ );
135
+ }
136
+
100
137
  /** The admin's email domain as a website suggestion, or null for free-mail domains. */
101
138
  export function websiteSuggestion(orgDomain: string | null | undefined): string | null {
102
139
  const host = normalizeOrgWebsite(orgDomain);
103
- return host && !FREE_MAIL_DOMAINS.has(host) ? host : null;
140
+ return host && !isFreeMailHost(host) ? host : null;
104
141
  }
@@ -25,7 +25,12 @@ import {
25
25
  } from "./engine";
26
26
  import type { ExecutorRegistry } from "./executors/registry";
27
27
  import { getSecretInputKeys } from "./input";
28
- import { finalizeOrWait, resumeFromChildRun, resumeWaitState } from "./resume";
28
+ import {
29
+ finalizeOrWait,
30
+ recoverResolvedWaitState,
31
+ resumeFromChildRun,
32
+ resumeWaitState,
33
+ } from "./resume";
29
34
  import {
30
35
  checkpointPortStepAndResolveSuccessors,
31
36
  completeTaskStepAndResolveSuccessors,
@@ -392,21 +397,12 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
392
397
 
393
398
  for (const stuck of stuckRuns) {
394
399
  try {
395
- // Decide what status to (re)apply.
396
- let resumeStatus: "fired" | "timeout";
397
- let payload: unknown;
398
-
399
- if (stuck.waitStatus === "fired") {
400
- resumeStatus = "fired";
401
- payload = stuck.firedPayload != null ? safeJsonParse(stuck.firedPayload) : undefined;
402
- } else if (stuck.waitStatus === "timeout") {
403
- resumeStatus = "timeout";
400
+ if (stuck.waitStatus === "pending") {
401
+ const status = stuck.waitMode === "time" ? "fired" : "timeout";
402
+ await resumeWaitState(stuck.waitId, status, undefined, registry);
404
403
  } else {
405
- // pending + overdue
406
- resumeStatus = stuck.waitMode === "time" ? "fired" : "timeout";
404
+ await recoverResolvedWaitState(stuck.waitId, registry);
407
405
  }
408
-
409
- await resumeWaitState(stuck.waitId, resumeStatus, payload, registry);
410
406
  recovered++;
411
407
  } catch (err) {
412
408
  if (await failRunOnUnreadableReplay(stuck.runId, err)) continue;
@@ -417,14 +413,6 @@ async function recoverWaitStates(registry: ExecutorRegistry): Promise<number> {
417
413
  return recovered;
418
414
  }
419
415
 
420
- function safeJsonParse(s: string): unknown {
421
- try {
422
- return JSON.parse(s);
423
- } catch {
424
- return s;
425
- }
426
- }
427
-
428
416
  /**
429
417
  * Get run IDs by status. Simple query since there's no dedicated function for this.
430
418
  */
@@ -711,6 +711,22 @@ export async function resumeWaitState(
711
711
  );
712
712
  }
713
713
 
714
+ /** Finish a wait resolution that committed before its step was checkpointed. */
715
+ export async function recoverResolvedWaitState(
716
+ waitId: string,
717
+ registry: ExecutorRegistry,
718
+ ): Promise<void> {
719
+ const waitRow = await getWaitStateById(waitId);
720
+ if (!waitRow) return;
721
+ const status = waitRow.status;
722
+ if (status !== "fired" && status !== "timeout") return;
723
+
724
+ // Replay the durable result; the step checkpoint arbitrates concurrent resumes.
725
+ await failClosedOnUnreadableReplay(waitRow.workflowRunId, waitRow.workflowRunStepId, () =>
726
+ resumeClaimedWait(waitRow, status, waitRow.firedPayload, registry),
727
+ );
728
+ }
729
+
714
730
  async function resumeClaimedWait(
715
731
  waitRow: WaitStateRow,
716
732
  status: "fired" | "timeout",
@@ -2,12 +2,15 @@ import {
2
2
  detachTaskFromWorkflowRunStep,
3
3
  getDbClient,
4
4
  getTaskById,
5
+ getWorkflowRun,
5
6
  getWorkflowRunStep,
6
7
  updateWorkflowRun,
7
8
  updateWorkflowRunStep,
8
9
  } from "../be/db";
9
10
  import {
11
+ type RetryPolicy,
10
12
  RetryPolicySchema,
13
+ SUPERSEDED_WORKFLOW_TASK_REASON,
11
14
  type WorkflowDefinition,
12
15
  type WorkflowNode,
13
16
  type WorkflowRunStep,
@@ -17,7 +20,7 @@ import { getSuccessors } from "./definition";
17
20
  import { joinForeach, resolveForeachParent } from "./foreach-join";
18
21
 
19
22
  export interface PortStepRoutingResult {
20
- /** False when another handler already moved the step out of `waiting`. */
23
+ /** False when the step was already claimed or its run is terminal. */
21
24
  claimed: boolean;
22
25
  successors: WorkflowNode[];
23
26
  }
@@ -85,6 +88,10 @@ export async function checkpointPortStepAndResolveSuccessors(
85
88
  return await getDbClient().transaction(async (): Promise<PortStepRoutingResult> => {
86
89
  const current = await getWorkflowRunStep(stepId);
87
90
  if (!current || current.status !== "waiting") return { claimed: false, successors: [] };
91
+ const run = await getWorkflowRun(runId);
92
+ if (!run || (run.status !== "waiting" && run.status !== "running")) {
93
+ return { claimed: false, successors: [] };
94
+ }
88
95
  await checkpointStep(runId, stepId, nodeId, { output, nextPort }, ctx);
89
96
  await updateWorkflowRun(runId, { status: "running" });
90
97
  return { claimed: true, successors: getSuccessors(def, nodeId, nextPort) };
@@ -97,15 +104,33 @@ export async function checkpointPortStepAndResolveSuccessors(
97
104
  * - `not-claimed`: another handler already moved the step out of `waiting`,
98
105
  * or the failed task is no longer the one bound to the step (it was
99
106
  * already retried and a fresh task now owns it); the caller stops.
100
- * - `not-eligible`: no retry applies (no `node.retry`, not an agent-task node,
101
- * or retries exhausted); the caller applies `onNodeFailure` as before.
107
+ * - `not-eligible`: no retry applies (no `node.retry` and not a supersede
108
+ * cut, not an agent-task node, or retries exhausted); the caller applies
109
+ * `onNodeFailure` as before.
102
110
  */
103
111
  export type TaskStepRetryOutcome = "scheduled" | "not-claimed" | "not-eligible";
104
112
 
113
+ /**
114
+ * Retry applied to an agent-task step whose task was cut by a supersede
115
+ * (deploy drain, heartbeat remediation) when the node declares no `retry`.
116
+ * The cut is not the task's fault, so the step gets one fresh attempt after
117
+ * the restart settles. An explicit `node.retry` always wins; `retry:
118
+ * { maxRetries: 0 }` opts a node with external side effects out.
119
+ */
120
+ export const SUPERSEDE_RETRY_POLICY: RetryPolicy = {
121
+ maxRetries: 1,
122
+ strategy: "static",
123
+ baseDelayMs: 30_000,
124
+ maxDelayMs: 30_000,
125
+ };
126
+
105
127
  /**
106
128
  * Apply a node's `retry` policy to a FAILED agent-task step task. The sync
107
129
  * executor path does this in `executeStep`; an async agent-task step only
108
130
  * learns about the failure here, from `task.failed` or the recovery sweep.
131
+ * A node without `retry` gets `SUPERSEDE_RETRY_POLICY` when the task was
132
+ * failed with `SUPERSEDED_WORKFLOW_TASK_REASON`. The task row is checked as
133
+ * well as `reason`, because the recovery sweep passes its own reason text.
109
134
  *
110
135
  * In one transaction: re-claim the step while it is still `waiting` AND still
111
136
  * bound to the failed task, detach that task (the executor reuses any task
@@ -114,6 +139,9 @@ export type TaskStepRetryOutcome = "scheduled" | "not-claimed" | "not-eligible";
114
139
  * task. Bounded by `maxRetries` via the step's persisted `retryCount`, read
115
140
  * only after ownership is confirmed: a stale caller's step snapshot may carry
116
141
  * the counter of a newer attempt.
142
+ *
143
+ * Foreach children (`<foreachId>#<key>`) are not matched here: the retry
144
+ * poller resolves nodes by exact id and cannot re-dispatch a child yet.
117
145
  */
118
146
  export async function scheduleTaskStepRetry(
119
147
  def: WorkflowDefinition,
@@ -123,10 +151,16 @@ export async function scheduleTaskStepRetry(
123
151
  reason: string,
124
152
  ): Promise<TaskStepRetryOutcome> {
125
153
  const node = def.nodes.find((n) => n.id === step.nodeId);
126
- if (!node || node.type !== "agent-task" || !node.retry) return "not-eligible";
127
- const parsed = RetryPolicySchema.safeParse(node.retry);
128
- if (!parsed.success) return "not-eligible";
129
- const policy = parsed.data;
154
+ if (!node || node.type !== "agent-task") return "not-eligible";
155
+ let policy: RetryPolicy;
156
+ if (node.retry) {
157
+ const parsed = RetryPolicySchema.safeParse(node.retry);
158
+ if (!parsed.success) return "not-eligible";
159
+ policy = parsed.data;
160
+ } else {
161
+ if (!(await isSupersedeCut(taskId, reason))) return "not-eligible";
162
+ policy = SUPERSEDE_RETRY_POLICY;
163
+ }
130
164
 
131
165
  return await getDbClient().transaction(async (): Promise<TaskStepRetryOutcome> => {
132
166
  const current = await getWorkflowRunStep(step.id);
@@ -139,6 +173,12 @@ export async function scheduleTaskStepRetry(
139
173
  });
140
174
  }
141
175
 
176
+ async function isSupersedeCut(taskId: string, reason: string): Promise<boolean> {
177
+ if (reason === SUPERSEDED_WORKFLOW_TASK_REASON) return true;
178
+ const task = await getTaskById(taskId);
179
+ return task?.failureReason === SUPERSEDED_WORKFLOW_TASK_REASON;
180
+ }
181
+
142
182
  export interface TaskStepRoutingResult {
143
183
  /**
144
184
  * False when another handler already moved the step out of `waiting` — the