@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
@@ -3,13 +3,9 @@ import { z } from "zod";
3
3
  import { getDbClient, upsertSwarmConfig } from "../be/db";
4
4
  import {
5
5
  buildListing,
6
- mintRankingClaim,
7
6
  PUBLIC_RANKING_ENABLED_KEY,
8
7
  RANKING_CATEGORIES,
9
- rankingEntryFile,
10
8
  rankingFormSchema,
11
- rankingLeaveUrl,
12
- rankingPrUrl,
13
9
  readTelemetryRanking,
14
10
  TELEMETRY_RANKING_KEY,
15
11
  type TelemetryRanking,
@@ -23,14 +19,14 @@ import {
23
19
  } from "../utils/org-identity";
24
20
  import { ensureConfigAdmin } from "./config";
25
21
  import { scheduleIntegrationsReload } from "./core";
22
+ import { globalConfig } from "./global-config";
26
23
  import { route } from "./route-def";
27
- import { globalConfig, telemetryProxyEndpoint } from "./used-by";
24
+ import { telemetryProxyEndpoint } from "./used-by";
28
25
 
29
26
  // ─── Schemas ─────────────────────────────────────────────────────────────────
30
27
 
31
28
  const ListingSchema = z.object({
32
29
  v: z.literal(1),
33
- claim: z.string(),
34
30
  name: z.string(),
35
31
  domain: z.string(),
36
32
  category: z.enum(RANKING_CATEGORIES),
@@ -49,26 +45,21 @@ const RankingStateSchema = z.object({
49
45
  name: z.string().nullable(),
50
46
  domain: z.string().nullable(),
51
47
  listing: ListingSchema.nullable(),
52
- /** False when the stored listing was built for another website: a new PR is needed. */
48
+ /** False when the stored listing was built for another website: Get ranked again. */
53
49
  listingMatchesDomain: z.boolean(),
54
- fileJson: z.string().nullable(),
55
- prUrl: z.string().nullable(),
50
+ /** The proxy page that checks whether the domain would be ranked. */
56
51
  verifyUrl: z.string().nullable(),
57
52
  });
58
53
  export type RankingState = z.infer<typeof RankingStateSchema>;
59
54
 
60
55
  const OptInResponseSchema = z.object({
61
56
  domain: z.string(),
62
- claim: z.string(),
63
57
  listing: ListingSchema,
64
- fileJson: z.string(),
65
- prUrl: z.string(),
66
58
  verifyUrl: z.string(),
67
59
  });
68
60
 
69
61
  const OptOutResponseSchema = z.object({
70
62
  enabled: z.literal(false),
71
- leaveUrl: z.string().nullable(),
72
63
  });
73
64
 
74
65
  // ─── Route Definitions ───────────────────────────────────────────────────────
@@ -79,7 +70,7 @@ const getRanking = route({
79
70
  pattern: ["api", "ranking"],
80
71
  summary: "Public ranking state",
81
72
  description:
82
- "Whether this install can join the public agent-swarm.dev ranking (telemetry on, organization name and website set), whether it is opted in, and its stored listing. The listing carries the private ranking claim, so only a config admin can read it.",
73
+ "Whether this install can join the public agent-swarm.dev ranking (telemetry on, organization name and website set), whether it is opted in, and its stored listing. Only a config admin can read it.",
83
74
  tags: ["Telemetry"],
84
75
  responses: {
85
76
  200: { description: "Ranking state", schema: RankingStateSchema },
@@ -95,7 +86,7 @@ const optIn = route({
95
86
  pattern: ["api", "ranking", "opt-in"],
96
87
  summary: "Opt this install into the public ranking",
97
88
  description:
98
- "Builds the telemetry_ranking listing from the organization name and website plus the chosen category, stores it, turns PUBLIC_RANKING_ENABLED on and sends ranking.opted_in. The install is ranked only after a ranking PR with the returned file is merged into desplega-ai/agent-swarm.",
89
+ "Builds the telemetry_ranking listing from the organization name and website plus the chosen category, stores it, turns PUBLIC_RANKING_ENABLED on and sends ranking.opted_in. That is the whole opt-in: the install shows on agent-swarm.dev/ranking at the next hourly refresh.",
99
90
  tags: ["Telemetry"],
100
91
  body: rankingFormSchema,
101
92
  responses: {
@@ -117,7 +108,7 @@ const optOut = route({
117
108
  pattern: ["api", "ranking", "opt-out"],
118
109
  summary: "Leave the public ranking",
119
110
  description:
120
- "Turns PUBLIC_RANKING_ENABLED off and sends ranking.opted_out, so workers stop sending ranking.usage. Returns the GitHub page that deletes the ranking entry.",
111
+ "Turns PUBLIC_RANKING_ENABLED off and sends ranking.opted_out, so workers stop sending ranking.usage. The install leaves agent-swarm.dev/ranking at the next hourly refresh.",
121
112
  tags: ["Telemetry"],
122
113
  responses: {
123
114
  200: { description: "Opted out", schema: OptOutResponseSchema },
@@ -163,11 +154,17 @@ async function telemetryReady(): Promise<boolean> {
163
154
  return isTelemetryEnabled() && Boolean(await globalConfig("telemetry_installation_id"));
164
155
  }
165
156
 
166
- async function verifyUrl(listing: Pick<TelemetryRanking, "domain" | "claim">): Promise<string> {
167
- const params = new URLSearchParams({ domain: listing.domain, claim: listing.claim });
157
+ async function verifyUrl(domain: string): Promise<string> {
158
+ const params = new URLSearchParams({ domain });
168
159
  return `${await telemetryProxyEndpoint("/v1/ranking/verify")}?${params.toString()}`;
169
160
  }
170
161
 
162
+ /** The listing without the claim a v2.4.0 opt-in stored. */
163
+ function withoutClaim(listing: TelemetryRanking): Omit<TelemetryRanking, "claim"> {
164
+ const { claim: _claim, ...rest } = listing;
165
+ return rest;
166
+ }
167
+
171
168
  async function setEnabled(enabled: boolean): Promise<void> {
172
169
  await upsertSwarmConfig({
173
170
  scope: "global",
@@ -179,7 +176,7 @@ async function setEnabled(enabled: boolean): Promise<void> {
179
176
  function emitOptedIn(listing: TelemetryRanking): void {
180
177
  telemetry.ranking(
181
178
  "opted_in",
182
- { listing: JSON.stringify(listing) },
179
+ { listing: JSON.stringify(withoutClaim(listing)) },
183
180
  { orgName: listing.name, orgWebsite: listing.domain },
184
181
  );
185
182
  }
@@ -204,17 +201,15 @@ export async function buildRankingState(): Promise<RankingState> {
204
201
  enabled: enabled && listingMatchesDomain,
205
202
  name: identity.name,
206
203
  domain: identity.website,
207
- listing,
204
+ listing: listing ? withoutClaim(listing) : null,
208
205
  listingMatchesDomain,
209
- fileJson: current ? rankingEntryFile(current) : null,
210
- prUrl: current ? rankingPrUrl(current) : null,
211
- verifyUrl: current ? await verifyUrl(current) : null,
206
+ verifyUrl: current ? await verifyUrl(current.domain) : null,
212
207
  };
213
208
  }
214
209
 
215
210
  /**
216
211
  * Keep the listing in step with the org identity after a global config write.
217
- * A renamed org rebuilds the listing with the same claim and re-sends it; a
212
+ * A renamed org rebuilds the listing and re-sends it; a
218
213
  * cleared or placeholder name, a changed or cleared website, or the setting
219
214
  * turned off by hand opts the install out.
220
215
  */
@@ -238,7 +233,7 @@ export async function syncRankingAfterConfigChange(
238
233
  if (identity.ok && identity.website === listing.domain) {
239
234
  if (identity.name === listing.name) return;
240
235
  try {
241
- rebuilt = buildListing(identity, listing, listing.claim);
236
+ rebuilt = buildListing(identity, listing);
242
237
  } catch {
243
238
  // A name the listing cannot carry (over 60 characters) leaves the ranking.
244
239
  }
@@ -272,7 +267,6 @@ export async function handleRanking(
272
267
  if (getRanking.match(req.method, pathSegments)) {
273
268
  const parsed = await getRanking.parse(req, res, pathSegments, queryParams);
274
269
  if (!parsed) return true;
275
- // The state carries the claim and the PR that proves ownership of the listing.
276
270
  if (!(await ensureConfigAdmin(req, res, "config.write.any"))) return true;
277
271
  getRanking.respond(res, 200, await buildRankingState());
278
272
  return true;
@@ -300,16 +294,12 @@ export async function handleRanking(
300
294
  return true;
301
295
  }
302
296
 
303
- const website = identity.website;
304
297
  const db = getDbClient();
305
- // Read the stored claim under the write lock: two concurrent first opt-ins
306
- // must return the same claim, or the one merged into community/ranking is lost.
298
+ // The listing and the flag land together, and the event fires only after both commit.
307
299
  const built = await db.transaction(async () => {
308
- const stored = await storedListing();
309
- const claim = stored?.domain === website ? stored.claim : mintRankingClaim();
310
300
  let listing: TelemetryRanking;
311
301
  try {
312
- listing = buildListing(identity, parsed.body, claim);
302
+ listing = buildListing(identity, parsed.body);
313
303
  } catch (error) {
314
304
  return { ok: false as const, error };
315
305
  }
@@ -339,11 +329,8 @@ export async function handleRanking(
339
329
 
340
330
  optIn.respond(res, 200, {
341
331
  domain: listing.domain,
342
- claim: listing.claim,
343
332
  listing,
344
- fileJson: rankingEntryFile(listing),
345
- prUrl: rankingPrUrl(listing),
346
- verifyUrl: await verifyUrl(listing),
333
+ verifyUrl: await verifyUrl(listing.domain),
347
334
  });
348
335
  return true;
349
336
  }
@@ -356,10 +343,7 @@ export async function handleRanking(
356
343
  await setEnabled(false);
357
344
  if (listing) emitOptedOut(listing.domain);
358
345
  scheduleIntegrationsReload();
359
- optOut.respond(res, 200, {
360
- enabled: false,
361
- leaveUrl: listing ? rankingLeaveUrl(listing.domain) : null,
362
- });
346
+ optOut.respond(res, 200, { enabled: false });
363
347
  return true;
364
348
  }
365
349
 
package/src/http/tasks.ts CHANGED
@@ -73,6 +73,7 @@ import {
73
73
  SteeringSourceSchema,
74
74
  SteerModeSchema,
75
75
  SteerResultSchema,
76
+ SUPERSEDED_WORKFLOW_TASK_REASON,
76
77
  splitLegacyModelAlias,
77
78
  TaskAttachmentSchema,
78
79
  } from "../types";
@@ -2014,7 +2015,7 @@ export async function handleTasks(
2014
2015
  // Check this BEFORE the supersede UPDATE so we don't leave a workflow
2015
2016
  // step in `superseded` if the engine expects `failed`.
2016
2017
  if (task.workflowRunStepId != null) {
2017
- const failed = await failTask(parsed.params.id, "superseded_workflow_task");
2018
+ const failed = await failTask(parsed.params.id, SUPERSEDED_WORKFLOW_TASK_REASON);
2018
2019
  ensure({
2019
2020
  id: "task.workflow_step_failed_on_supersede",
2020
2021
  flow: "task",
@@ -43,7 +43,7 @@ const enableTelemetry = route({
43
43
  description:
44
44
  "Stores `ANONYMIZED_TELEMETRY=true` and applies it to the running server: the install ID is minted if needed and `telemetry.enabled` is sent once, so no restart is needed.",
45
45
  tags: ["Telemetry"],
46
- body: z.object({ via: z.enum(["banner", "settings"]).default("banner") }),
46
+ body: z.object({ via: z.enum(["banner", "settings", "benchmark"]).default("banner") }),
47
47
  responses: {
48
48
  200: {
49
49
  description: "Telemetry is on",
@@ -1,12 +1,14 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
- import { getSwarmConfigs, upsertSwarmConfig } from "../be/db";
3
+ import { getDbClient, upsertSwarmConfig } from "../be/db";
4
4
  import { isTelemetryEnabled, trackRelayed } from "../telemetry";
5
+ import { USER_FUNCTIONS } from "../utils/company-profile";
5
6
  import { orgIdentityStatus } from "../utils/org-identity";
6
7
  import { normalizeOrgWebsite, TELEMETRY_PROXY_ORIGIN } from "../utils/org-website";
7
8
  import { ensureConfigAdmin } from "./config";
8
9
  import { scheduleIntegrationsReload } from "./core";
9
10
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
11
+ import { globalConfig } from "./global-config";
10
12
  import { route } from "./route-def";
11
13
  import { jsonError } from "./utils";
12
14
 
@@ -16,6 +18,9 @@ const CONSENT_AT_KEY = "SWARM_USED_BY_CONSENT_AT";
16
18
  // still reaches the proxy after the name or website is changed or cleared.
17
19
  const GRANTED_NAME_KEY = "SWARM_USED_BY_ORG_NAME";
18
20
  const GRANTED_WEBSITE_KEY = "SWARM_USED_BY_ORG_WEBSITE";
21
+ const CONTACT_EMAIL_KEY = "SWARM_USED_BY_CONTACT_EMAIL";
22
+ const CONTACT_FUNCTION_KEY = "SWARM_USED_BY_CONTACT_FUNCTION";
23
+ const QUOTE_KEY = "SWARM_USED_BY_QUOTE";
19
24
  const TIMEOUT_MS = 10_000;
20
25
 
21
26
  // ─── Route Definitions ───────────────────────────────────────────────────────
@@ -26,17 +31,30 @@ const usedBy = route({
26
31
  pattern: ["api", "used-by"],
27
32
  summary: "Grant or revoke the 'used by' logo consent",
28
33
  description:
29
- "Records whether agent-swarm.dev may show this organization's logo as a company that uses Agent Swarm. Sends the organization name and website to the telemetry proxy, where a person approves each logo by hand. An explicit admin action, so it is not gated by the telemetry opt-out.",
34
+ "Records consent to show the organization's logo on agent-swarm.dev. A grant requires a valid contact email. It sends organization identity, contact details, and an optional quote to the proxy for manual review. This explicit admin action works when telemetry is off. A revoke clears the stored contact details and quote.",
30
35
  tags: ["Telemetry"],
31
- body: z.object({ granted: z.boolean() }),
36
+ body: z
37
+ .object({
38
+ granted: z.boolean(),
39
+ contactEmail: z
40
+ .string()
41
+ .max(320, "Contact email must be at most 320 characters")
42
+ .email("Contact email must be a valid email address")
43
+ .optional(),
44
+ contactFunction: z.enum(USER_FUNCTIONS).optional(),
45
+ quote: z.string().max(280).optional(),
46
+ })
47
+ .refine((body) => !body.granted || body.contactEmail !== undefined, {
48
+ path: ["contactEmail"],
49
+ message: "Contact email is required when granting logo consent",
50
+ }),
32
51
  responses: {
33
52
  200: {
34
53
  description: "Consent recorded",
35
54
  schema: z.object({ granted: z.boolean(), changedAt: z.string() }),
36
55
  },
37
56
  400: {
38
- description:
39
- "Organization name or website is not set (a revoke falls back to the identity of the last grant)",
57
+ description: "Invalid contact details, or missing organization name or website",
40
58
  },
41
59
  401: { description: "Unauthorized" },
42
60
  403: { description: "Only a config admin can change the consent" },
@@ -47,12 +65,6 @@ const usedBy = route({
47
65
 
48
66
  // ─── Helpers ─────────────────────────────────────────────────────────────────
49
67
 
50
- /** A global config value, falling back to the process env. */
51
- export async function globalConfig(key: string): Promise<string | undefined> {
52
- const stored = (await getSwarmConfigs({ scope: "global", key }))?.[0]?.value?.trim();
53
- return stored || process.env[key]?.trim() || undefined;
54
- }
55
-
56
68
  /** A proxy path next to the configured `/v1/feedback` endpoint, so a stubbed proxy gets every route. */
57
69
  export async function telemetryProxyEndpoint(path: `/v1/${string}`): Promise<string> {
58
70
  const feedback = await globalConfig("feedback_endpoint");
@@ -80,7 +92,7 @@ export async function handleUsedBy(
80
92
  if (!parsed) return true;
81
93
  if (!(await ensureConfigAdmin(req, res, "config.write.any"))) return true;
82
94
 
83
- const { granted } = parsed.body;
95
+ const { granted, contactEmail, contactFunction, quote } = parsed.body;
84
96
  // The shared identity reader. The grant keeps its own rule (any non-empty
85
97
  // name), so it does not apply the ranking's placeholder-name check.
86
98
  const current = orgIdentityStatus({
@@ -116,6 +128,9 @@ export async function handleUsedBy(
116
128
  org_website: orgWebsite,
117
129
  ...(orgDomain ? { org_domain: orgDomain } : {}),
118
130
  granted,
131
+ ...(granted
132
+ ? { contact_email: contactEmail, contact_function: contactFunction, quote }
133
+ : {}),
119
134
  changed_at: changedAt,
120
135
  }),
121
136
  signal: AbortSignal.timeout(TIMEOUT_MS),
@@ -129,18 +144,39 @@ export async function handleUsedBy(
129
144
  return true;
130
145
  }
131
146
 
132
- await upsertSwarmConfig({ scope: "global", key: CONSENT_KEY, value: String(granted) });
133
- await upsertSwarmConfig({ scope: "global", key: CONSENT_AT_KEY, value: changedAt });
134
- if (granted) {
135
- await upsertSwarmConfig({ scope: "global", key: GRANTED_NAME_KEY, value: orgName });
136
- await upsertSwarmConfig({ scope: "global", key: GRANTED_WEBSITE_KEY, value: orgWebsite });
137
- }
147
+ await getDbClient().transaction(async () => {
148
+ await upsertSwarmConfig({ scope: "global", key: CONSENT_KEY, value: String(granted) });
149
+ await upsertSwarmConfig({ scope: "global", key: CONSENT_AT_KEY, value: changedAt });
150
+ if (granted) {
151
+ await upsertSwarmConfig({ scope: "global", key: GRANTED_NAME_KEY, value: orgName });
152
+ await upsertSwarmConfig({ scope: "global", key: GRANTED_WEBSITE_KEY, value: orgWebsite });
153
+ }
154
+ // Contact details stay out of agent env. The email is also a secret row,
155
+ // so `GET /api/config` masks it for every reader.
156
+ for (const [key, value, isSecret] of [
157
+ [CONTACT_EMAIL_KEY, contactEmail, true],
158
+ [CONTACT_FUNCTION_KEY, contactFunction, false],
159
+ [QUOTE_KEY, quote, false],
160
+ ] as const) {
161
+ await upsertSwarmConfig({
162
+ scope: "global",
163
+ key,
164
+ value: granted ? (value ?? "") : "",
165
+ isSecret,
166
+ exportToEnv: false,
167
+ });
168
+ }
169
+ });
138
170
  scheduleIntegrationsReload();
139
171
 
140
172
  const owner = await resolveHttpFavoriteOwner(req, myAgentId);
141
173
  trackRelayed(
142
174
  "used_by.consent_changed",
143
- { granted },
175
+ {
176
+ granted,
177
+ has_quote: granted && Boolean(quote),
178
+ has_function: granted && Boolean(contactFunction),
179
+ },
144
180
  { actor: owner?.userId ? { userId: owner.userId } : undefined, emitter: "ui" },
145
181
  );
146
182
  usedBy.respond(res, 200, { granted, changedAt });
package/src/http/users.ts CHANGED
@@ -205,7 +205,7 @@ const createUserRoute = route({
205
205
  tags: ["Users"],
206
206
  body: z.object({
207
207
  name: z.string().min(1),
208
- email: z.string().optional(),
208
+ email: z.string().email("Email must be a valid email address").optional(),
209
209
  role: z.string().optional(),
210
210
  notes: z.string().optional(),
211
211
  emailAliases: z.array(z.string()).optional(),
@@ -307,7 +307,7 @@ const updateUserRoute = route({
307
307
  body: z
308
308
  .object({
309
309
  name: z.string().min(1).optional(),
310
- email: z.string().optional(),
310
+ email: z.string().email("Email must be a valid email address").nullable().optional(),
311
311
  role: z.string().optional(),
312
312
  notes: z.string().optional(),
313
313
  emailAliases: z.array(z.string()).optional(),
@@ -44,7 +44,7 @@ import type {
44
44
  * default; the image installs exactly this version and the per-task settings
45
45
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
46
46
  */
47
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1792324855-g9c86a5";
47
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1792368068-g91990d";
48
48
 
49
49
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
50
50
  const AMP_MCP_SERVER_NAME = "agent-swarm";
@@ -115,6 +115,7 @@ function modelToCredKeys(modelStr: string | undefined): string[] | null {
115
115
  if (provider === "openrouter") return ["OPENROUTER_API_KEY"];
116
116
  if (provider === "openai") return ["OPENAI_API_KEY"];
117
117
  if (provider === "google") return ["GOOGLE_API_KEY", "GEMINI_API_KEY"];
118
+ if (provider === "opencode-go") return ["OPENCODE_API_KEY"];
118
119
  // models.json custom providers (`writeSessionModelsJson`). The
119
120
  // runner normalizes key aliases (HELMCODE_API_NAN → NAN_API_KEY), but a
120
121
  // worker without that step (or a boot-time check) may only see the alias.
@@ -145,6 +146,19 @@ function getHarnessDrivableBedrockModels(): Array<{ id: string; name: string }>
145
146
  }
146
147
  }
147
148
 
149
+ /**
150
+ * The `opencode-go` model ids this pi-ai build resolves. models.dev can list
151
+ * ids pi-ai does not know yet, and pi runs its default model for those, so the
152
+ * worker reports this list and the picker offers only these ids.
153
+ */
154
+ export function listPiOpencodeGoModelIds(): string[] {
155
+ try {
156
+ return getModels("opencode-go").map((m) => m.id);
157
+ } catch {
158
+ return [];
159
+ }
160
+ }
161
+
148
162
  /**
149
163
  * Enumerate the Bedrock models that are both invocable by this AWS account and
150
164
  * drivable by the pi-ai Converse harness, and verify the credential chain in
@@ -480,6 +494,7 @@ const PI_RUNTIME_API_KEYS: readonly (readonly [envKey: string, provider: string]
480
494
  ["OPENAI_API_KEY", "openai"],
481
495
  ["GEMINI_API_KEY", "google"],
482
496
  ["GOOGLE_API_KEY", "google"],
497
+ ["OPENCODE_API_KEY", "opencode-go"],
483
498
  // models.json custom providers (the registry). A runtime key beats the
484
499
  // file's `apiKey: "$NAN_API_KEY"`, which pi would otherwise read from the
485
500
  // shared process.env, where a credential pool is still the raw comma list.
@@ -168,7 +168,8 @@ function buildOpencodeReasoningOptions(
168
168
  // qualitative level — translate internally (see "What We're NOT Doing" in
169
169
  // the plan: no numeric budget surface for operators, only this
170
170
  // adapter-internal transport detail).
171
- if (level === "max") return { reasoningEffort: level };
171
+ // Its Anthropic transport reads `effort` into `output_config.effort`.
172
+ if (level === "max") return { effort: level };
172
173
  return { thinking: { type: "enabled", budgetTokens: ANTHROPIC_BUDGET_TOKENS_BY_LEVEL[level] } };
173
174
  }
174
175
  if (providerId === "openrouter") {
@@ -320,6 +320,8 @@ export interface CredStatus {
320
320
  * string signals Bedrock mode with `AWS_REGION` unset (no region fabricated).
321
321
  */
322
322
  bedrockRegion?: string;
323
+ /** Pi-mono only: the `opencode-go` model ids pi-ai resolves. Undefined for other harnesses. */
324
+ opencodeGoModels?: string[];
323
325
  }
324
326
 
325
327
  /**
@@ -23,8 +23,6 @@ export const RANKING_CATEGORIES = Object.keys(RANKING_TAXONOMY) as [
23
23
  keyof typeof RANKING_TAXONOMY,
24
24
  ...(keyof typeof RANKING_TAXONOMY)[],
25
25
  ];
26
- export const RANKING_CLAIM_PATTERN = /^rk_[0-9a-f]{32}$/;
27
-
28
26
  const hostname = z.string().refine((v) => normalizeOrgWebsite(v) === v, "bare hostname");
29
27
  const nullableText = (max: number) => z.string().trim().min(1).max(max).nullable();
30
28
 
@@ -37,7 +35,11 @@ function inCategory(category: string, subcategory: string | null): boolean {
37
35
  export const telemetryRankingV1 = z
38
36
  .strictObject({
39
37
  v: z.literal(1),
40
- claim: z.string().regex(RANKING_CLAIM_PATTERN),
38
+ /** Minted by v2.4.0 for the retired ranking PR. Still parsed, never written; the proxy ignores it. */
39
+ claim: z
40
+ .string()
41
+ .regex(/^rk_[0-9a-f]{32}$/)
42
+ .optional(),
41
43
  name: z.string().trim().min(1).max(60),
42
44
  domain: hostname,
43
45
  category: z.enum(RANKING_CATEGORIES),
@@ -88,12 +90,6 @@ export const rankingFormSchema = z.object({
88
90
  });
89
91
  export type RankingForm = z.infer<typeof rankingFormSchema>;
90
92
 
91
- /** `rk_` + 32 lowercase hex characters from the platform CSPRNG. */
92
- export function mintRankingClaim(): string {
93
- const bytes = crypto.getRandomValues(new Uint8Array(16));
94
- return `rk_${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
95
- }
96
-
97
93
  const blankToNull = (value: string | null | undefined) => value?.trim() || null;
98
94
 
99
95
  /**
@@ -104,7 +100,6 @@ const blankToNull = (value: string | null | undefined) => value?.trim() || null;
104
100
  export function buildListing(
105
101
  identity: OrgIdentityStatus,
106
102
  form: RankingForm,
107
- claim: string,
108
103
  now: Date = new Date(),
109
104
  ): TelemetryRanking {
110
105
  if (!identity.ok || !identity.name || !identity.website) {
@@ -112,7 +107,6 @@ export function buildListing(
112
107
  }
113
108
  const listing = {
114
109
  v: 1 as const,
115
- claim,
116
110
  name: identity.name,
117
111
  domain: identity.website,
118
112
  category: form.category,
@@ -123,25 +117,3 @@ export function buildListing(
123
117
  };
124
118
  return parseTelemetryRanking(JSON.stringify(listing));
125
119
  }
126
-
127
- /** The `community/ranking/<domain>.json` file that approves this install. */
128
- export function rankingEntryFile(listing: Pick<TelemetryRanking, "domain" | "claim">): string {
129
- return `${JSON.stringify({ $schema: "./schema.json", domain: listing.domain, claim: listing.claim }, null, 2)}\n`;
130
- }
131
-
132
- export const RANKING_REPO = "desplega-ai/agent-swarm";
133
-
134
- /** GitHub's "new file" page, prefilled with the entry, as a PR from the admin's fork. */
135
- export function rankingPrUrl(listing: Pick<TelemetryRanking, "domain" | "claim">): string {
136
- const params = new URLSearchParams({
137
- filename: `community/ranking/${listing.domain}.json`,
138
- value: rankingEntryFile(listing),
139
- message: `chore(ranking): add ${listing.domain}`,
140
- });
141
- return `https://github.com/${RANKING_REPO}/new/main?${params.toString()}`;
142
- }
143
-
144
- /** GitHub's delete page for an entry, to leave the ranking for good. */
145
- export function rankingLeaveUrl(domain: string): string {
146
- return `https://github.com/${RANKING_REPO}/delete/main/community/ranking/${domain}.json`;
147
- }
@@ -180,6 +180,13 @@ export const SDK_TOOL_NAME_MAP = {
180
180
  app_list: "app-list",
181
181
  app_patch: "app-patch",
182
182
  app_query: "app-query",
183
+ app_rows_list: "app-rows-list",
184
+ app_row_get: "app-row-get",
185
+ app_row_create: "app-row-create",
186
+ app_rows_create: "app-rows-create",
187
+ app_row_patch: "app-row-patch",
188
+ app_row_delete: "app-row-delete",
189
+ app_rows_delete: "app-rows-delete",
183
190
  app_rollback: "app-rollback",
184
191
  app_sync: "app-sync",
185
192
  app_upsert: "app-upsert",
@@ -658,6 +658,37 @@ declare module "swarm-sdk" {
658
658
  query: string;
659
659
  params?: Record<string, string | number | boolean>;
660
660
  }): Promise<unknown>;
661
+ app_rows_list(args: {
662
+ appId: string;
663
+ model: string;
664
+ equals?: Record<string, string | number | boolean>;
665
+ sort?: `${string}:asc` | `${string}:desc`;
666
+ limit?: number;
667
+ }): Promise<unknown>;
668
+ app_row_get(args: { appId: string; model: string; rowId: string }): Promise<unknown>;
669
+ app_row_create(args: {
670
+ appId: string;
671
+ model: string;
672
+ values: Record<string, unknown>;
673
+ }): Promise<unknown>;
674
+ app_rows_create(args: {
675
+ appId: string;
676
+ model: string;
677
+ rows: Record<string, unknown>[];
678
+ }): Promise<unknown>;
679
+ app_row_patch(args: {
680
+ appId: string;
681
+ model: string;
682
+ rowId: string;
683
+ values: Record<string, unknown>;
684
+ }): Promise<unknown>;
685
+ app_row_delete(args: { appId: string; model: string; rowId: string }): Promise<unknown>;
686
+ app_rows_delete(args: {
687
+ appId: string;
688
+ model: string;
689
+ equals: Record<string, string | number | boolean>;
690
+ limit?: number;
691
+ }): Promise<unknown>;
661
692
  app_rollback(args: {
662
693
  appId: string;
663
694
  version: number;
@@ -640,6 +640,37 @@ declare module "swarm-sdk" {
640
640
  query: string;
641
641
  params?: Record<string, string | number | boolean>;
642
642
  }): Promise<unknown>;
643
+ app_rows_list(args: {
644
+ appId: string;
645
+ model: string;
646
+ equals?: Record<string, string | number | boolean>;
647
+ sort?: `${string}:asc` | `${string}:desc`;
648
+ limit?: number;
649
+ }): Promise<unknown>;
650
+ app_row_get(args: { appId: string; model: string; rowId: string }): Promise<unknown>;
651
+ app_row_create(args: {
652
+ appId: string;
653
+ model: string;
654
+ values: Record<string, unknown>;
655
+ }): Promise<unknown>;
656
+ app_rows_create(args: {
657
+ appId: string;
658
+ model: string;
659
+ rows: Record<string, unknown>[];
660
+ }): Promise<unknown>;
661
+ app_row_patch(args: {
662
+ appId: string;
663
+ model: string;
664
+ rowId: string;
665
+ values: Record<string, unknown>;
666
+ }): Promise<unknown>;
667
+ app_row_delete(args: { appId: string; model: string; rowId: string }): Promise<unknown>;
668
+ app_rows_delete(args: {
669
+ appId: string;
670
+ model: string;
671
+ equals: Record<string, string | number | boolean>;
672
+ limit?: number;
673
+ }): Promise<unknown>;
643
674
  app_rollback(args: {
644
675
  appId: string;
645
676
  version: number;
package/src/server.ts CHANGED
@@ -11,6 +11,7 @@ import { resolveTemplate } from "./prompts/resolver";
11
11
  import { isRbacEnabled } from "./rbac";
12
12
  import { getSlackConfiguration } from "./slack/config";
13
13
  import { registerAcceptSteerTool } from "./tools/accept-steer";
14
+ import { registerAppActionRunTool } from "./tools/app-action-run";
14
15
  import { registerAppDiffTool } from "./tools/app-diff";
15
16
  import { registerAppGetTool } from "./tools/app-get";
16
17
  import { registerAppHistoryTool } from "./tools/app-history";
@@ -18,6 +19,7 @@ import { registerAppListTool } from "./tools/app-list";
18
19
  import { registerAppPatchTool } from "./tools/app-patch";
19
20
  import { registerAppQueryTool } from "./tools/app-query";
20
21
  import { registerAppRollbackTool } from "./tools/app-rollback";
22
+ import { registerAppRowTools } from "./tools/app-rows";
21
23
  import { registerAppSyncTool } from "./tools/app-sync";
22
24
  import { registerAppUpsertTool } from "./tools/app-upsert";
23
25
  import { registerCancelApprovalRequestTool } from "./tools/cancel-approval-request";
@@ -643,6 +645,8 @@ export async function createServer(
643
645
  // Pages capability - DB-backed lightweight artifacts (HTML / JSON specs).
644
646
  if (hasCapability("pages")) {
645
647
  registerAppGetTool(server);
648
+ registerAppRowTools(server);
649
+ registerAppActionRunTool(server);
646
650
  registerAppHistoryTool(server);
647
651
  registerAppDiffTool(server);
648
652
  registerAppListTool(server);