@desplega.ai/agent-swarm 1.157.0 → 1.158.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
  2. package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
  3. package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
  4. package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
  5. package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
  6. package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
  7. package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
  8. package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
  9. package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
  10. package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
  11. package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
  12. package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
  13. package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
  14. package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
  15. package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
  16. package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
  17. package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
  18. package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
  19. package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
  20. package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
  21. package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
  22. package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
  23. package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
  24. package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
  25. package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
  26. package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
  27. package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
  28. package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
  29. package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
  30. package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
  31. package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
  32. package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
  33. package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
  34. package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
  35. package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
  36. package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
  37. package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
  38. package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
  39. package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
  40. package/dist/cli-t1ze64h3.js +811 -0
  41. package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
  42. package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
  43. package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
  44. package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
  45. package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
  46. package/dist/cli.js +29 -15
  47. package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
  48. package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
  49. package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
  50. package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
  51. package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
  52. package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
  53. package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
  54. package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
  55. package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
  56. package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
  57. package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
  58. package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
  59. package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
  60. package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
  61. package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
  62. package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
  63. package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
  64. package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
  65. package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
  66. package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
  67. package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
  68. package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
  69. package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
  70. package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
  71. package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
  72. package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
  73. package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
  74. package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
  75. package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
  76. package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
  77. package/openapi.json +1626 -629
  78. package/package.json +2 -1
  79. package/src/apps/catalog.generated.json +229 -0
  80. package/src/apps/page-validator.ts +71 -16
  81. package/src/apps/row-store.ts +66 -11
  82. package/src/apps/sync.ts +59 -17
  83. package/src/artifact-sdk/browser-sdk.ts +436 -2
  84. package/src/be/approval-sweeps.ts +109 -0
  85. package/src/be/boot-seeds.ts +40 -0
  86. package/src/be/chunking.ts +1 -1
  87. package/src/be/db/agents.ts +20 -0
  88. package/src/be/db/approvals.ts +78 -0
  89. package/src/be/db/tasks/read.ts +197 -39
  90. package/src/be/db/tasks/write.ts +41 -9
  91. package/src/be/db-client.ts +107 -48
  92. package/src/be/db.ts +219 -76
  93. package/src/be/events.ts +33 -1
  94. package/src/be/memory/graph-expansion.ts +10 -3
  95. package/src/be/memory/providers/sqlite-store.ts +55 -13
  96. package/src/be/memory/raters/retrieval.ts +5 -2
  97. package/src/be/memory/reranker.ts +26 -2
  98. package/src/be/memory/types.ts +2 -0
  99. package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
  100. package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
  101. package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
  102. package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
  103. package/src/be/models-catalog.ts +2 -0
  104. package/src/be/modelsdev-cache.json +1857 -0
  105. package/src/be/modelsdev-cache.ts +14 -1
  106. package/src/be/pricing-normalize.ts +2 -0
  107. package/src/be/scripts/typecheck.ts +1 -0
  108. package/src/be/seed-pricing.ts +3 -1
  109. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  110. package/src/be/steering.ts +53 -3
  111. package/src/be/swarm-config-guard.ts +3 -0
  112. package/src/be/task-citations.ts +29 -0
  113. package/src/cli.tsx +14 -1
  114. package/src/commands/onboard/compose-generator.ts +8 -3
  115. package/src/commands/onboard/container-engine.ts +220 -0
  116. package/src/commands/onboard/manifest.ts +1 -0
  117. package/src/commands/onboard/steps/health-check.tsx +4 -1
  118. package/src/commands/onboard/steps/prereq-check.tsx +50 -54
  119. package/src/commands/onboard/steps/start.tsx +31 -16
  120. package/src/commands/onboard/types.ts +7 -2
  121. package/src/commands/onboard.tsx +39 -4
  122. package/src/commands/profile-sync.ts +79 -26
  123. package/src/commands/runner.ts +8 -1
  124. package/src/extensions/catalog.generated.json +17 -0
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/heartbeat/heartbeat.ts +263 -103
  127. package/src/hooks/hook.ts +21 -12
  128. package/src/http/agents.ts +35 -0
  129. package/src/http/approval-requests.ts +136 -64
  130. package/src/http/apps.ts +14 -3
  131. package/src/http/config.ts +15 -1
  132. package/src/http/events.ts +62 -2
  133. package/src/http/index.ts +6 -4
  134. package/src/http/memory.ts +21 -7
  135. package/src/http/schedules.ts +1 -1
  136. package/src/http/session-data.ts +9 -4
  137. package/src/http/sessions.ts +25 -18
  138. package/src/http/stats.ts +19 -7
  139. package/src/http/status.ts +11 -1
  140. package/src/http/tasks.ts +40 -8
  141. package/src/http/webhooks.ts +67 -46
  142. package/src/http/workflows.ts +15 -20
  143. package/src/linear/outbound.ts +37 -15
  144. package/src/otel-impl.ts +151 -1
  145. package/src/otel.ts +14 -3
  146. package/src/prompts/memories.ts +19 -2
  147. package/src/providers/claude-managed-models.ts +8 -0
  148. package/src/providers/modelsdev-reasoning.json +551 -0
  149. package/src/rbac/legacy-policy.ts +16 -0
  150. package/src/rbac/permissions.ts +4 -0
  151. package/src/scheduler/schedule-task.ts +3 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/swarm-sdk.ts +9 -0
  154. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  155. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  156. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  157. package/src/server.ts +12 -7
  158. package/src/slack/app.ts +35 -4
  159. package/src/slack/connection-state.ts +17 -0
  160. package/src/tasks/context-key.ts +1 -0
  161. package/src/tasks/worker-follow-up.ts +6 -2
  162. package/src/tests/agent-task-activity.test.ts +74 -0
  163. package/src/tests/agents-list-model-display.test.ts +6 -1
  164. package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
  165. package/src/tests/approval-requests.test.ts +395 -0
  166. package/src/tests/approval-sweeps-config.test.ts +47 -0
  167. package/src/tests/approval-sweeps.test.ts +508 -0
  168. package/src/tests/apps-kibo-blocks.test.ts +77 -0
  169. package/src/tests/apps-spike.test.ts +56 -1
  170. package/src/tests/apps-sync-engine.test.ts +20 -9
  171. package/src/tests/boot-seeds.test.ts +170 -0
  172. package/src/tests/cancel-approval-request-tool.test.ts +115 -0
  173. package/src/tests/claude-managed-adapter.test.ts +6 -0
  174. package/src/tests/config-key-filter.test.ts +80 -0
  175. package/src/tests/context-window.test.ts +1 -0
  176. package/src/tests/db-client-busy-retry.test.ts +96 -0
  177. package/src/tests/db-client.test.ts +60 -1
  178. package/src/tests/db-task-read-contract.test.ts +10 -0
  179. package/src/tests/db-task-write-contract.test.ts +9 -0
  180. package/src/tests/docs-index-footprint.test.js +3 -3
  181. package/src/tests/events-http.test.ts +103 -0
  182. package/src/tests/extensions-catalog.test.ts +22 -17
  183. package/src/tests/extensions-pre-task.test.ts +107 -0
  184. package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
  185. package/src/tests/follow-up-config-inheritance.test.ts +260 -0
  186. package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
  187. package/src/tests/heartbeat-reboot-live.test.ts +95 -0
  188. package/src/tests/heartbeat-stall-cas.test.ts +80 -0
  189. package/src/tests/heartbeat.test.ts +99 -3
  190. package/src/tests/hook-mcp-config.test.ts +29 -0
  191. package/src/tests/http-api-integration.test.ts +16 -2
  192. package/src/tests/kv-storage.test.ts +62 -0
  193. package/src/tests/linear-outbound-sync.test.ts +120 -5
  194. package/src/tests/list-endpoint-slimming.test.ts +91 -0
  195. package/src/tests/memory-graph-organic-route.test.ts +155 -0
  196. package/src/tests/memory-http-recall-gating.test.ts +14 -4
  197. package/src/tests/memory-hybrid.test.ts +161 -1
  198. package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
  199. package/src/tests/memory.test.ts +22 -0
  200. package/src/tests/onboard-container-engine.test.ts +240 -0
  201. package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
  202. package/src/tests/pricing-routes.test.ts +14 -0
  203. package/src/tests/profile-sync-warning.test.ts +8 -4
  204. package/src/tests/promote-draft-task-route.test.ts +19 -0
  205. package/src/tests/provider-registration.test.ts +296 -42
  206. package/src/tests/rbac-engine.test.ts +30 -0
  207. package/src/tests/reasoning-effort.test.ts +8 -0
  208. package/src/tests/script-connections.test.ts +7 -0
  209. package/src/tests/sdk-allowlist.test.ts +39 -0
  210. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  211. package/src/tests/sessions-detail-batch.test.ts +250 -0
  212. package/src/tests/sessions.test.ts +118 -0
  213. package/src/tests/slack-config.test.ts +22 -0
  214. package/src/tests/sonnet-5-5-registry.test.ts +44 -0
  215. package/src/tests/status.test.ts +36 -1
  216. package/src/tests/swarm-diff.test.ts +316 -10
  217. package/src/tests/task-completion-idempotency.test.ts +2 -1
  218. package/src/tests/tasks-requested-by-filter.test.ts +3 -1
  219. package/src/tests/tool-annotations.test.ts +1 -1
  220. package/src/tests/ui-list-slim-projections.test.ts +158 -0
  221. package/src/tests/webhooks-task-blocked.test.ts +127 -0
  222. package/src/tests/workflow-approval-recovery.test.ts +203 -0
  223. package/src/tests/workflow-hitl-routing.test.ts +22 -1
  224. package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
  225. package/src/tests/workflow-http-v2.test.ts +99 -8
  226. package/src/tests/workflow-retry-validation.test.ts +56 -0
  227. package/src/tests/workflow-tla-races.test.ts +389 -0
  228. package/src/tests/workflow-validation-port-routing.test.ts +113 -1
  229. package/src/tools/cancel-approval-request.ts +57 -0
  230. package/src/tools/memory-search.ts +1 -0
  231. package/src/tools/request-human-input.ts +4 -2
  232. package/src/tools/send-task.ts +4 -0
  233. package/src/tools/templates.ts +19 -0
  234. package/src/tools/tool-config.ts +2 -1
  235. package/src/tools/workflows/list-workflow-runs.ts +1 -0
  236. package/src/types.ts +38 -0
  237. package/src/utils/context-window.ts +1 -0
  238. package/src/workflows/approval-cancel.ts +152 -0
  239. package/src/workflows/approval-notifications.ts +84 -0
  240. package/src/workflows/checkpoint.ts +10 -5
  241. package/src/workflows/definition.ts +21 -0
  242. package/src/workflows/engine.ts +152 -21
  243. package/src/workflows/executors/human-in-the-loop.ts +1 -1
  244. package/src/workflows/recovery.ts +100 -45
  245. package/src/workflows/resume.ts +49 -18
  246. package/src/workflows/retry-poller.ts +71 -21
  247. package/templates/extensions/README.md +1 -0
  248. package/templates/extensions/github-sender-allowlist/README.md +18 -0
  249. package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
  250. package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
  251. package/templates/skills/apps/content.md +20 -1
  252. package/templates/skills/code-quality/content.md +26 -1
  253. package/templates/skills/pages/content.md +16 -4
  254. package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
  255. package/dist/cli-2daksqps.js +0 -378
  256. package/dist/rbac-roles-3gxmnb20.js +0 -37
@@ -1,13 +1,19 @@
1
1
  import { Select, Spinner } from "@inkjs/ui";
2
2
  import { Box, Text } from "ink";
3
3
  import { useEffect, useRef, useState } from "react";
4
+ import {
5
+ CONTAINER_ENGINE_LABELS,
6
+ ENGINE_INSTALL_HINTS,
7
+ type EngineCheck,
8
+ type EngineResolution,
9
+ resolveContainerEngine,
10
+ } from "../container-engine.ts";
4
11
  import type { StepProps } from "../types.ts";
5
12
 
6
13
  type CheckStatus = "checking" | "passed" | "failed";
7
14
 
8
15
  interface CheckResult {
9
- docker: { ok: boolean; version?: string; error?: string };
10
- compose: { ok: boolean; version?: string; error?: string };
16
+ engine: EngineResolution;
11
17
  ports: { ok: boolean; conflicts?: string[] };
12
18
  }
13
19
 
@@ -22,35 +28,10 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
22
28
 
23
29
  const run = async () => {
24
30
  const res: CheckResult = {
25
- docker: { ok: false },
26
- compose: { ok: false },
31
+ engine: await resolveContainerEngine(state.containerEngine),
27
32
  ports: { ok: true },
28
33
  };
29
34
 
30
- // Check Docker
31
- try {
32
- const out = await Bun.$`docker --version`.quiet();
33
- if (out.exitCode === 0) {
34
- res.docker = { ok: true, version: out.text().trim() };
35
- } else {
36
- res.docker = { ok: false, error: "Docker exited with non-zero status" };
37
- }
38
- } catch {
39
- res.docker = { ok: false, error: "Docker not found" };
40
- }
41
-
42
- // Check Docker Compose
43
- try {
44
- const out = await Bun.$`docker compose version`.quiet();
45
- if (out.exitCode === 0) {
46
- res.compose = { ok: true, version: out.text().trim() };
47
- } else {
48
- res.compose = { ok: false, error: "Docker Compose not found" };
49
- }
50
- } catch {
51
- res.compose = { ok: false, error: "Docker Compose v2 not found" };
52
- }
53
-
54
35
  // Check ports
55
36
  const portsToCheck = [3013];
56
37
  const agentCount = state.services.reduce((sum, s) => sum + s.count, 0);
@@ -75,7 +56,8 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
75
56
 
76
57
  setResult(res);
77
58
 
78
- if (res.docker.ok && res.compose.ok && res.ports.ok) {
59
+ if (res.engine.ok && res.ports.ok) {
60
+ addLog(`Container engine: ${CONTAINER_ENGINE_LABELS[res.engine.engine]}`);
79
61
  addLog("All prerequisites met");
80
62
  setStatus("passed");
81
63
  } else {
@@ -84,15 +66,16 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
84
66
  };
85
67
 
86
68
  run().catch((err) => goToError(err.message));
87
- }, [state.services, addLog, goToError]);
69
+ }, [state.services, state.containerEngine, addLog, goToError]);
88
70
 
89
71
  // Auto-advance on pass
90
72
  useEffect(() => {
91
- if (status === "passed") {
92
- const timer = setTimeout(() => goToNext(), 500);
73
+ if (status === "passed" && result?.engine.ok) {
74
+ const engine = result.engine.engine;
75
+ const timer = setTimeout(() => goToNext({ containerEngine: engine }), 500);
93
76
  return () => clearTimeout(timer);
94
77
  }
95
- }, [status, goToNext]);
78
+ }, [status, result, goToNext]);
96
79
 
97
80
  if (status === "checking") {
98
81
  return (
@@ -102,46 +85,41 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
102
85
  );
103
86
  }
104
87
 
105
- if (status === "passed" && result) {
88
+ if (status === "passed" && result?.engine.ok) {
106
89
  return (
107
90
  <Box flexDirection="column" padding={1}>
108
91
  <Text color="green">
109
- {"✓"} {result.docker.version}
92
+ {"✓"} {result.engine.probe.binary.version}
110
93
  </Text>
111
94
  <Text color="green">
112
- {"✓"} {result.compose.version}
95
+ {"✓"} {result.engine.probe.compose.version}
113
96
  </Text>
114
97
  <Text color="green">{"✓"} All ports available</Text>
115
98
  </Box>
116
99
  );
117
100
  }
118
101
 
102
+ const probe = result?.engine.probe ?? null;
103
+ const engineLabel = probe ? CONTAINER_ENGINE_LABELS[probe.engine] : "Container engine";
104
+
119
105
  // Failed
120
106
  return (
121
107
  <Box flexDirection="column" padding={1}>
122
- {result?.docker.ok ? (
123
- <Text color="green">
124
- {"✓"} {result.docker.version}
125
- </Text>
126
- ) : (
108
+ {result && !result.engine.ok && !result.engine.engine ? (
127
109
  <Box flexDirection="column">
128
110
  <Text color="red">
129
- {"✗"} Docker: {result?.docker.error}
130
- </Text>
131
- <Text dimColor>
132
- {" "}
133
- Install: brew install --cask docker (macOS) or https://docs.docker.com/get-docker/
111
+ {"✗"} {result.engine.error}
134
112
  </Text>
113
+ <Text dimColor> {ENGINE_INSTALL_HINTS.docker}</Text>
114
+ <Text dimColor> or {ENGINE_INSTALL_HINTS.podman}</Text>
135
115
  </Box>
136
- )}
137
- {result?.compose.ok ? (
138
- <Text color="green">
139
- {"✓"} {result.compose.version}
140
- </Text>
141
116
  ) : (
142
- <Text color="red">
143
- {"✗"} Docker Compose v2: {result?.compose.error}
144
- </Text>
117
+ <>
118
+ <CheckLine label={engineLabel} check={probe?.binary} />
119
+ {probe?.binary.ok ? (
120
+ <CheckLine label={`${engineLabel} Compose`} check={probe.compose} />
121
+ ) : null}
122
+ </>
145
123
  )}
146
124
  {result?.ports.ok ? (
147
125
  <Text color="green">{"✓"} All ports available</Text>
@@ -176,3 +154,21 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
176
154
  </Box>
177
155
  );
178
156
  }
157
+
158
+ function CheckLine({ label, check }: { label: string; check: EngineCheck | undefined }) {
159
+ if (check?.ok) {
160
+ return (
161
+ <Text color="green">
162
+ {"✓"} {check.version}
163
+ </Text>
164
+ );
165
+ }
166
+ return (
167
+ <Box flexDirection="column">
168
+ <Text color="red">
169
+ {"✗"} {label}: {check?.error}
170
+ </Text>
171
+ {check?.hint ? <Text dimColor> {check.hint}</Text> : null}
172
+ </Box>
173
+ );
174
+ }
@@ -1,6 +1,13 @@
1
1
  import { Select, Spinner } from "@inkjs/ui";
2
2
  import { Box, Text } from "ink";
3
3
  import { useEffect, useRef, useState } from "react";
4
+ import {
5
+ CONTAINER_ENGINE_LABELS,
6
+ composeArgv,
7
+ composeCommandText,
8
+ defaultCommandRunner,
9
+ engineForCommands,
10
+ } from "../container-engine.ts";
4
11
  import type { StepProps } from "../types.ts";
5
12
 
6
13
  type StartStatus = "starting" | "success" | "failed";
@@ -9,24 +16,27 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
9
16
  const [status, setStatus] = useState<StartStatus>("starting");
10
17
  const [errorMsg, setErrorMsg] = useState("");
11
18
  const executed = useRef(false);
19
+ const engine = engineForCommands(state.containerEngine);
20
+ const engineLabel = CONTAINER_ENGINE_LABELS[engine];
12
21
 
13
22
  useEffect(() => {
14
23
  if (executed.current) return;
15
24
  executed.current = true;
16
25
 
17
26
  const run = async () => {
18
- addLog("Running docker compose up -d...");
27
+ addLog(`Running ${composeCommandText(engine, "--env-file .env up -d")}...`);
19
28
  try {
20
- const result = await Bun.$`docker compose --env-file .env up -d`
21
- .cwd(state.outputDir)
22
- .quiet();
29
+ const result = await defaultCommandRunner(
30
+ composeArgv(engine, ["--env-file", ".env", "up", "-d"]),
31
+ { cwd: state.outputDir },
32
+ );
23
33
 
24
34
  if (result.exitCode === 0) {
25
- addLog("Docker stack started successfully");
35
+ addLog(`${engineLabel} stack started successfully`);
26
36
  setStatus("success");
27
37
  } else {
28
- const stderr = result.stderr.toString().trim();
29
- setErrorMsg(stderr || "docker compose up failed");
38
+ const stderr = result.stderr.trim();
39
+ setErrorMsg(stderr || `${composeCommandText(engine, "up")} failed`);
30
40
  setStatus("failed");
31
41
  }
32
42
  } catch (err) {
@@ -36,7 +46,7 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
36
46
  };
37
47
 
38
48
  run().catch((err) => addLog(`Start failed: ${err}`));
39
- }, [state.outputDir, addLog]);
49
+ }, [state.outputDir, engine, engineLabel, addLog]);
40
50
 
41
51
  useEffect(() => {
42
52
  if (status === "success") {
@@ -48,7 +58,7 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
48
58
  if (status === "starting") {
49
59
  return (
50
60
  <Box padding={1}>
51
- <Spinner label="Starting Docker stack..." />
61
+ <Spinner label={`Starting ${engineLabel} stack...`} />
52
62
  </Box>
53
63
  );
54
64
  }
@@ -56,20 +66,24 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
56
66
  if (status === "success") {
57
67
  return (
58
68
  <Box padding={1}>
59
- <Text color="green">{"✓"} Docker stack started</Text>
69
+ <Text color="green">
70
+ {"✓"} {engineLabel} stack started
71
+ </Text>
60
72
  </Box>
61
73
  );
62
74
  }
63
75
 
64
76
  return (
65
77
  <Box flexDirection="column" padding={1}>
66
- <Text color="red">{"✗"} Failed to start Docker stack</Text>
78
+ <Text color="red">
79
+ {"✗"} Failed to start {engineLabel} stack
80
+ </Text>
67
81
  <Text dimColor>{errorMsg}</Text>
68
82
  <Box marginTop={1}>
69
83
  <Select
70
84
  options={[
71
85
  { label: "Retry", value: "retry" },
72
- { label: "View logs (docker compose logs)", value: "logs" },
86
+ { label: `View logs (${composeCommandText(engine, "logs")})`, value: "logs" },
73
87
  { label: "Skip", value: "skip" },
74
88
  ]}
75
89
  onChange={async (value) => {
@@ -79,10 +93,11 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
79
93
  setErrorMsg("");
80
94
  } else if (value === "logs") {
81
95
  try {
82
- const logs = await Bun.$`docker compose logs --tail 30`
83
- .cwd(state.outputDir)
84
- .quiet();
85
- addLog(logs.text());
96
+ const logs = await defaultCommandRunner(
97
+ composeArgv(engine, ["logs", "--tail", "30"]),
98
+ { cwd: state.outputDir },
99
+ );
100
+ addLog(logs.stdout || logs.stderr);
86
101
  } catch {
87
102
  addLog("Failed to fetch logs");
88
103
  }
@@ -1,4 +1,5 @@
1
1
  import type { SlackMode } from "../../slack/config";
2
+ import type { ContainerEnginePreference } from "./container-engine.ts";
2
3
 
3
4
  export type OnboardStep =
4
5
  | "welcome"
@@ -90,6 +91,8 @@ export interface OnboardState {
90
91
  apiPort: number;
91
92
  maxConcurrentTasks: number | null;
92
93
  pullPolicy: PullPolicy;
94
+ /** `auto` until the prereq check resolves it to the engine that passed. */
95
+ containerEngine: ContainerEnginePreference;
93
96
  outputDir: string;
94
97
  nonInteractive: boolean;
95
98
  error: string | null;
@@ -110,6 +113,7 @@ export interface OnboardProps {
110
113
  preset?: string;
111
114
  maxConcurrentTasks?: string;
112
115
  pullPolicy?: string;
116
+ containerEngine?: string;
113
117
  }
114
118
 
115
119
  export type PullPolicy = "always" | "missing" | "never";
@@ -137,7 +141,7 @@ export const STEP_LABELS: { step: OnboardStep; label: string }[] = [
137
141
  { step: "integration_menu", label: "Integrations" },
138
142
  { step: "review", label: "Review" },
139
143
  { step: "generate", label: "Generate" },
140
- { step: "prereq_check", label: "Docker" },
144
+ { step: "prereq_check", label: "Engine" },
141
145
  { step: "start", label: "Start" },
142
146
  { step: "health_check", label: "Health" },
143
147
  { step: "post_connect", label: "Connect" },
@@ -205,6 +209,7 @@ export const INITIAL_STATE: OnboardState = {
205
209
  apiPort: 0,
206
210
  maxConcurrentTasks: null,
207
211
  pullPolicy: "always",
212
+ containerEngine: "auto",
208
213
  outputDir: process.cwd(),
209
214
  nonInteractive: false,
210
215
  error: null,
@@ -293,7 +298,7 @@ export function nextStep(current: OnboardStep, state: OnboardState): OnboardStep
293
298
  return "generate";
294
299
 
295
300
  case "prereq_check":
296
- // If Docker is missing and user chose "files only", skip to done
301
+ // If the container engine is missing and user chose "files only", skip to done
297
302
  // Default: proceed to start
298
303
  return "start";
299
304
 
@@ -5,6 +5,13 @@ import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import pkg from "../../package.json";
6
6
  import { getSlackConfiguration, isSlackConfigured } from "../slack/config.ts";
7
7
  import { getApiKey } from "../utils/api-key.ts";
8
+ import {
9
+ CONTAINER_ENGINE_LABELS,
10
+ composeCommandText,
11
+ engineForCommands,
12
+ isContainerEnginePreference,
13
+ resolveContainerEnginePreference,
14
+ } from "./onboard/container-engine.ts";
8
15
  import { buildOnboardDashboardUrl } from "./onboard/dashboard-url.ts";
9
16
  import {
10
17
  parseMaxConcurrentTasks,
@@ -53,6 +60,7 @@ export function Onboard({
53
60
  preset,
54
61
  maxConcurrentTasks,
55
62
  pullPolicy,
63
+ containerEngine,
56
64
  }: OnboardProps) {
57
65
  const { exit } = useApp();
58
66
  const [state, setState] = useState<OnboardState>(() => {
@@ -75,6 +83,13 @@ export function Onboard({
75
83
  }
76
84
  initial.pullPolicy = pullPolicy;
77
85
  }
86
+ const engine = resolveContainerEnginePreference(containerEngine, process.env);
87
+ if (!engine.ok) {
88
+ initial.step = "error";
89
+ initial.error = engine.error;
90
+ return initial;
91
+ }
92
+ initial.containerEngine = engine.value;
78
93
  return initial;
79
94
  });
80
95
 
@@ -311,8 +326,14 @@ export function Onboard({
311
326
  </Box>
312
327
  <Box marginTop={1} flexDirection="column">
313
328
  <Text bold>Useful commands:</Text>
314
- <Text dimColor> docker compose logs -f</Text>
315
- <Text dimColor> docker compose down</Text>
329
+ <Text dimColor>
330
+ {" "}
331
+ {composeCommandText(engineForCommands(state.containerEngine), "logs -f")}
332
+ </Text>
333
+ <Text dimColor>
334
+ {" "}
335
+ {composeCommandText(engineForCommands(state.containerEngine), "down")}
336
+ </Text>
316
337
  <Text dimColor> agent-swarm setup</Text>
317
338
  </Box>
318
339
  </Box>
@@ -341,8 +362,22 @@ function renderStep(step: OnboardStep, props: StepProps) {
341
362
  <Text bold>How would you like to deploy your swarm?</Text>
342
363
  <Box marginTop={1}>
343
364
  <Select
344
- options={[{ label: "Local (Docker Compose)", value: "local" }]}
345
- onChange={() => props.goToNext({ deployType: "local" })}
365
+ defaultValue={props.state.containerEngine}
366
+ options={[
367
+ {
368
+ label: "Local, auto-detect engine (Docker if installed, else Podman)",
369
+ value: "auto",
370
+ },
371
+ { label: `Local (${CONTAINER_ENGINE_LABELS.docker} Compose)`, value: "docker" },
372
+ {
373
+ label: `Local (${CONTAINER_ENGINE_LABELS.podman} Compose, rootless)`,
374
+ value: "podman",
375
+ },
376
+ ]}
377
+ onChange={(value) => {
378
+ if (!isContainerEnginePreference(value)) return;
379
+ props.goToNext({ deployType: "local", containerEngine: value });
380
+ }}
346
381
  />
347
382
  </Box>
348
383
  <Text dimColor>Remote (SSH) — Coming soon</Text>
@@ -189,6 +189,82 @@ export async function renderProfileSyncRejectionBanner(event: SwarmEvent): Promi
189
189
  return result.skipped ? null : result.text;
190
190
  }
191
191
 
192
+ type LatestProfileSyncEvents = {
193
+ rejection: SwarmEvent | null;
194
+ reconciliation: SwarmEvent | null;
195
+ };
196
+
197
+ const PROFILE_SYNC_REJECTED = "system.profile_sync_rejected";
198
+ const PROFILE_SYNC_RECONCILED = "system.profile_sync_reconciled";
199
+
200
+ function profileSyncHeaders(config: Pick<ProfileSyncOptions, "agentId" | "apiKey">) {
201
+ return { Authorization: `Bearer ${config.apiKey}`, "X-Agent-ID": config.agentId };
202
+ }
203
+
204
+ /**
205
+ * One request for every field's latest rejection and reconciliation. Returns
206
+ * null when the API does not confirm `latestPerDataField` (an API that predates
207
+ * it ignores the parameters), so the caller falls back to per-field lookups.
208
+ */
209
+ async function fetchLatestProfileSyncEventsBatched(
210
+ config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey">,
211
+ fetchImpl: typeof fetch,
212
+ ): Promise<LatestProfileSyncEvents[] | null> {
213
+ const query = new URLSearchParams({
214
+ events: `${PROFILE_SYNC_REJECTED},${PROFILE_SYNC_RECONCILED}`,
215
+ dataFields: Object.keys(IDENTITY_FIELD_BUDGETS).join(","),
216
+ agentId: config.agentId,
217
+ latestPerDataField: "true",
218
+ });
219
+ const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
220
+ headers: profileSyncHeaders(config),
221
+ });
222
+ if (!response.ok) return null;
223
+ const payload = (await response.json()) as {
224
+ events?: SwarmEvent[];
225
+ latestPerDataField?: boolean;
226
+ };
227
+ if (payload.latestPerDataField !== true || !Array.isArray(payload.events)) return null;
228
+ const events = payload.events;
229
+ const latest = (event: string, field: string) =>
230
+ events.find((candidate) => candidate.event === event && candidate.data?.field === field) ??
231
+ null;
232
+ return (Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map((field) => ({
233
+ rejection: latest(PROFILE_SYNC_REJECTED, field),
234
+ reconciliation: latest(PROFILE_SYNC_RECONCILED, field),
235
+ }));
236
+ }
237
+
238
+ /** Legacy path: one `limit=1` lookup per (field, event), for APIs without batching. */
239
+ async function fetchLatestProfileSyncEventsPerField(
240
+ config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey">,
241
+ fetchImpl: typeof fetch,
242
+ ): Promise<LatestProfileSyncEvents[]> {
243
+ return await Promise.all(
244
+ (Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map(async (field) => {
245
+ const fetchLatestEvent = async (event: SwarmEvent["event"]): Promise<SwarmEvent | null> => {
246
+ const query = new URLSearchParams({
247
+ event,
248
+ agentId: config.agentId,
249
+ dataField: field,
250
+ limit: "1",
251
+ });
252
+ const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
253
+ headers: profileSyncHeaders(config),
254
+ });
255
+ if (!response.ok) return null;
256
+ const payload = (await response.json()) as { events?: SwarmEvent[] };
257
+ return payload.events?.[0] ?? null;
258
+ };
259
+ const [rejection, reconciliation] = await Promise.all([
260
+ fetchLatestEvent(PROFILE_SYNC_REJECTED),
261
+ fetchLatestEvent(PROFILE_SYNC_RECONCILED),
262
+ ]);
263
+ return { rejection, reconciliation };
264
+ }),
265
+ );
266
+ }
267
+
192
268
  /** Fetch every field's latest unresolved rejection before a provider session. */
193
269
  export async function fetchProfileSyncRejectionBanner(
194
270
  config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
@@ -196,32 +272,9 @@ export async function fetchProfileSyncRejectionBanner(
196
272
  readFile: FileReader = readFileIfExists,
197
273
  ): Promise<string> {
198
274
  try {
199
- const responses = await Promise.all(
200
- (Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map(async (field) => {
201
- const fetchLatestEvent = async (event: SwarmEvent["event"]): Promise<SwarmEvent | null> => {
202
- const query = new URLSearchParams({
203
- event,
204
- agentId: config.agentId,
205
- dataField: field,
206
- limit: "1",
207
- });
208
- const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
209
- headers: {
210
- Authorization: `Bearer ${config.apiKey}`,
211
- "X-Agent-ID": config.agentId,
212
- },
213
- });
214
- if (!response.ok) return null;
215
- const payload = (await response.json()) as { events?: SwarmEvent[] };
216
- return payload.events?.[0] ?? null;
217
- };
218
- const [rejection, reconciliation] = await Promise.all([
219
- fetchLatestEvent("system.profile_sync_rejected"),
220
- fetchLatestEvent("system.profile_sync_reconciled"),
221
- ]);
222
- return { rejection, reconciliation };
223
- }),
224
- );
275
+ const responses =
276
+ (await fetchLatestProfileSyncEventsBatched(config, fetchImpl)) ??
277
+ (await fetchLatestProfileSyncEventsPerField(config, fetchImpl));
225
278
  const latestEvents = responses.flatMap(({ rejection, reconciliation }) =>
226
279
  rejection ? [{ rejection, reconciliation }] : [],
227
280
  );
@@ -1156,6 +1156,7 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
1156
1156
  "post-message": "💬 Sending message",
1157
1157
  "read-messages": "💬 Reading messages",
1158
1158
  "request-human-input": "🙋 Requesting human input",
1159
+ "cancel-approval-request": "🚫 Cancelling approval request",
1159
1160
  "cancel-task": "🚫 Cancelling task",
1160
1161
  "db-query": "🗃️ Querying database",
1161
1162
  "inject-learning": "🧠 Storing learning",
@@ -3253,7 +3254,13 @@ async function fetchRelevantMemories(
3253
3254
  if (!response.ok) return null;
3254
3255
 
3255
3256
  const data = (await response.json()) as {
3256
- results: Array<{ id: string; name: string; content: string; similarity: number }>;
3257
+ results: Array<{
3258
+ id: string;
3259
+ name: string;
3260
+ content: string;
3261
+ similarity: number;
3262
+ rawSimilarity?: number;
3263
+ }>;
3257
3264
  };
3258
3265
 
3259
3266
  return renderMemoriesPrompt(data.results || []);
@@ -1,4 +1,21 @@
1
1
  {
2
+ "github-sender-allowlist": {
3
+ "manifestFile": "manifest.json",
4
+ "manifest": {
5
+ "$schema": "../manifest.schema.json",
6
+ "name": "github-sender-allowlist",
7
+ "description": "Blocks GitHub webhook tasks from senders outside configured allowlists",
8
+ "version": "1.0.0",
9
+ "runtime": "api",
10
+ "assets": {
11
+ "hooks": "hooks.ts"
12
+ }
13
+ },
14
+ "files": {
15
+ "hooks.ts": "import { block, type SwarmExtension } from \"swarm-extension\";\nimport { z } from \"zod\";\n\nexport const config = z.object({\n internal: z.array(z.string()).default([]),\n external: z.array(z.string()).default([]),\n reviewBots: z.array(z.string()).default([]),\n exemptTaskTypes: z.array(z.string()).default([]),\n});\n\nconst manifest = {\n name: \"github-sender-allowlist\",\n description: \"Blocks GitHub webhook tasks from senders outside configured allowlists\",\n version: \"1.0.0\",\n runtime: \"api\",\n assets: { hooks: \"hooks.ts\" },\n config,\n} as const;\n\nfunction includesLogin(logins: string[], login: string): boolean {\n return logins.some((allowed) => allowed.toLowerCase() === login.toLowerCase());\n}\n\nconst extension: SwarmExtension<typeof manifest> = (api) => {\n api.on(\"pre.task.create\", (event, ctx) => {\n const { options } = event;\n if (options.source !== \"github\") return;\n\n const taskType = options.taskType ?? \"unknown\";\n const login = options.vcsAuthor ?? \"\";\n if (includesLogin(ctx.config.internal, login)) return;\n\n const isReviewBot = includesLogin(ctx.config.reviewBots, login);\n if (isReviewBot && taskType === \"github-review\") return;\n if (!isReviewBot && ctx.config.exemptTaskTypes.includes(taskType)) return;\n\n const isPullRequest = options.vcsUrl?.includes(\"/pull/\") ?? false;\n const isExternalPullRequestTask =\n (taskType === \"github-comment\" || taskType === \"github-pr\" || taskType === \"github-review\") &&\n isPullRequest;\n const isExternalSender = includesLogin(ctx.config.external, login);\n if (!isReviewBot && isExternalSender && isExternalPullRequestTask) return;\n\n const rule = isReviewBot\n ? \"review bots are allowed only for github-review tasks\"\n : isExternalSender\n ? \"external senders are allowed only for PR comments, reviews, and github-pr tasks on pull requests\"\n : \"sender is not on the internal, external, or reviewBots allowlist\";\n const reason = `GitHub sender \"${login || \"unknown\"}\" is blocked: ${rule}.`;\n ctx.log.warn(reason, { login: login || null, taskType, vcsUrl: options.vcsUrl ?? null });\n return block(reason);\n });\n};\n\nexport default extension;\n"
16
+ },
17
+ "readme": "# github-sender-allowlist\n\nBlocks GitHub webhook tasks from senders outside the configured allowlists. Login matching is case-insensitive. Tasks from other sources pass through. No task type is exempt by default.\n\nConfig:\n\n```json\n{\n \"internal\": [\"tarasyarema\", \"harlequinetcie\"],\n \"external\": [\"fuvidani\", \"capchase-bot\"],\n \"reviewBots\": [],\n \"exemptTaskTypes\": []\n}\n```\n\nInternal senders can create any GitHub task, including reviews. Logins in `reviewBots` can create `github-review` tasks only. External senders can create `github-comment` or `github-pr` tasks on pull requests, and `github-review` tasks when `vcsUrl` contains `/pull/`. Choose any review-bot logins from the sender census in the [security audit](https://live.agent-fs.dev/file/~/648a5f3c-35c8-4f11-8673-b89de52cd6bd/2faf73ba-4eee-4472-8b3b-359c4ed6bfbb/thoughts/a09d19a4-bd35-4593-9b6f-c2ccafccead8/reviews/2026-09-28-github-sender-authorization-audit.md) when installing the extension; the template intentionally defaults to an empty list.\n\n`exemptTaskTypes` is an optional operator override for explicitly exempting task types. It defaults to an empty list. The GitHub handler does not pass the action for `github-pr` tasks, so external `github-pr` access also permits assignment and label events on pull requests.\n"
18
+ },
2
19
  "notify-on-complete": {
3
20
  "manifestFile": "manifest.json",
4
21
  "manifest": {