@desplega.ai/agent-swarm 1.167.0 → 1.169.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 (184) hide show
  1. package/dist/{acp-adapter-zpvy6321.js → acp-adapter-m47zfx4a.js} +2 -2
  2. package/dist/{actions-fq2zrwtf.js → actions-ws7k3tvq.js} +25 -19
  3. package/dist/{amp-adapter-5gbk7501.js → amp-adapter-nvv8k8j5.js} +1 -1
  4. package/dist/{app-nctetn1e.js → app-80w6qspx.js} +5 -5
  5. package/dist/{assistant-0aq8gjvj.js → assistant-jtq439kw.js} +29 -20
  6. package/dist/{boot-reembed-1ze5853n.js → boot-reembed-3nc0aaed.js} +4 -4
  7. package/dist/{boot-reembed-fmp5ww69.js → boot-reembed-feda8wn8.js} +4 -4
  8. package/dist/{boot-scrub-sweep-cbp8xd4e.js → boot-scrub-sweep-grsa2er0.js} +5 -5
  9. package/dist/{boot-seeds-22tx8a53.js → boot-seeds-gcfgq44f.js} +7 -7
  10. package/dist/{claude-adapter-0jhgeh1d.js → claude-adapter-pjv2q7t8.js} +7 -7
  11. package/dist/{claude-managed-adapter-tzwxy803.js → claude-managed-adapter-0wa8se82.js} +2 -2
  12. package/dist/{claude-managed-setup-nrhfx5b0.js → claude-managed-setup-zxa9p9tx.js} +1 -1
  13. package/dist/{claude-sdk-session-wh77rh83.js → claude-sdk-session-c9j6k9kx.js} +8772 -8663
  14. package/dist/{cli-c2tpw9ck.js → cli-3ry5gx4f.js} +1 -1
  15. package/dist/{cli-wydz3feg.js → cli-61tbnjpf.js} +214 -2472
  16. package/dist/{cli-cexctgn5.js → cli-7vz49g7h.js} +101 -34
  17. package/dist/{cli-6qxmjpem.js → cli-8xjst8q2.js} +34 -22
  18. package/dist/{cli-tx2zvn23.js → cli-91r17hb1.js} +3 -3
  19. package/dist/{cli-577bxkf6.js → cli-a8vps458.js} +182 -2208
  20. package/dist/{cli-tc5zec7v.js → cli-b7y8113e.js} +1 -1
  21. package/dist/{cli-69egj654.js → cli-bqrs5ycj.js} +1 -1
  22. package/dist/{cli-cf2vgn9d.js → cli-dv76vq5d.js} +30 -28
  23. package/dist/{cli-g5erf0zt.js → cli-e2prcx5m.js} +31 -0
  24. package/dist/{cli-c8r99akr.js → cli-e5qk3m2a.js} +209 -31
  25. package/dist/{cli-z1dqhc9y.js → cli-eqfbsxsn.js} +3 -3
  26. package/dist/{cli-kw0jcfje.js → cli-fyeft04q.js} +1 -1
  27. package/dist/{cli-5qkcdv8c.js → cli-gmdajfw5.js} +3 -3
  28. package/dist/{cli-ypgba1a8.js → cli-jkfxpg1b.js} +1 -1
  29. package/dist/{cli-mxvnnv5q.js → cli-jv36y8wz.js} +1 -1
  30. package/dist/{cli-m7vmeepg.js → cli-km8559dm.js} +6 -6
  31. package/dist/{cli-ykt8f7va.js → cli-kp3vfknv.js} +1 -1
  32. package/dist/{cli-ymmc1zxg.js → cli-m23r88re.js} +8 -5
  33. package/dist/{cli-v1pwxreq.js → cli-n4z21pt8.js} +1 -1
  34. package/dist/{cli-mxry91sa.js → cli-qg2zhjfc.js} +73 -4
  35. package/dist/{cli-1yrcfa5s.js → cli-rbr91whd.js} +4 -4
  36. package/dist/{cli-mkdsy1nq.js → cli-rg1c8ccw.js} +93 -7
  37. package/dist/cli-rh44xx46.js +2659 -0
  38. package/dist/cli-s0k2j7q9.js +2084 -0
  39. package/dist/{cli-ty4bzkqr.js → cli-sajr098b.js} +1 -1
  40. package/dist/{cli-6vfz44sr.js → cli-swrz3mj8.js} +1 -1
  41. package/dist/{cli-p8rt8spp.js → cli-vejds37t.js} +2 -2
  42. package/dist/{cli-jbzbr36n.js → cli-w6xa5nf5.js} +1 -1
  43. package/dist/{cli-9q3wt23f.js → cli-x6z90nq9.js} +1 -1
  44. package/dist/{cli-qzmzwegq.js → cli-ysnkvdx6.js} +102 -9
  45. package/dist/{cli-y0kjpfm9.js → cli-yvxfaem2.js} +2 -2
  46. package/dist/{cli-pkj1w46r.js → cli-z48ggfyq.js} +1 -1
  47. package/dist/{cli-6bpvv8h4.js → cli-zzev32jw.js} +1 -1
  48. package/dist/cli.js +20 -17
  49. package/dist/{codex-adapter-cgesnjkr.js → codex-adapter-0kpg09ca.js} +3 -3
  50. package/dist/{codex-hook-15dgc4cb.js → codex-hook-295jpbfa.js} +2 -2
  51. package/dist/{codex-session-runner-8kd7ddhr.js → codex-session-runner-8n672hxh.js} +3 -3
  52. package/dist/{commands-h6zaaxym.js → commands-mhmg5n0x.js} +4 -4
  53. package/dist/{cursor-adapter-v8mm6bp5.js → cursor-adapter-wn8n5zzk.js} +2 -2
  54. package/dist/{db-qg3myn53.js → db-wa6s1mwv.js} +4 -4
  55. package/dist/{dispatcher-7vnwj9qw.js → dispatcher-nhs2pkr8.js} +16 -14
  56. package/dist/{dsh-adapter-tz6hfp5r.js → dsh-adapter-5423p70m.js} +1 -1
  57. package/dist/{grok-adapter-jdt2dz2s.js → grok-adapter-28pkdt80.js} +3 -3
  58. package/dist/{handlers-2qtkpncr.js → handlers-5rtf9nng.js} +19 -17
  59. package/dist/{hook-51yfcgsv.js → hook-krjfkpzy.js} +4 -4
  60. package/dist/{hook-qnbdgwvd.js → hook-qkm73zk6.js} +5 -5
  61. package/dist/{http-jsmtag08.js → http-a397xcn4.js} +4845 -3484
  62. package/dist/{index-2t9kesn1.js → index-ac0v8v9d.js} +4 -4
  63. package/dist/{index-ekywyv2v.js → index-kqre45fr.js} +16 -14
  64. package/dist/{index-bt8wkv1w.js → index-rtxg15ah.js} +17 -15
  65. package/dist/{index-t4g06w93.js → index-vvsq7v45.js} +26 -23
  66. package/dist/{index-cx1sb9bq.js → index-ytdhtdry.js} +50 -11
  67. package/dist/{keepalive-4aezx4bj.js → keepalive-psfh8ysx.js} +6 -6
  68. package/dist/{lead-r44bkpm5.js → lead-m6rppjn7.js} +29 -27
  69. package/dist/{maintenance-grz5jrzh.js → maintenance-41f837ke.js} +4 -4
  70. package/dist/{node-qm9eb6z0.js → node-fkajaq96.js} +1 -1
  71. package/dist/{oauth-refresh-sweep-wm4ctjfj.js → oauth-refresh-sweep-6hr55hm8.js} +5 -5
  72. package/dist/{onboard-v60zgqf0.js → onboard-ktrtd7cz.js} +2 -2
  73. package/dist/{otel-impl-ghh4dyf8.js → otel-impl-dq4f2k3m.js} +1 -1
  74. package/dist/{pi-mono-adapter-0eg8kw2c.js → pi-mono-adapter-e5v9w0hv.js} +3 -2
  75. package/dist/{pricing-refresh-mjrqvnvw.js → pricing-refresh-kmk7j1ra.js} +6 -6
  76. package/dist/{rbac-roles-fd5tskj9.js → rbac-roles-9w1dkkxd.js} +4 -4
  77. package/dist/{rbac-roles-axv4d74x.js → rbac-roles-9ykx9tc5.js} +5 -5
  78. package/dist/{render-v2-k50j33qd.js → render-v2-ycqm53np.js} +5 -5
  79. package/dist/resume-cq2pkp8q.js +70 -0
  80. package/dist/{setup-4syh0pd2.js → setup-0dcmdvbn.js} +2 -2
  81. package/dist/{virtual-modules-ftr9kwe8.js → virtual-modules-1x9k4jcs.js} +1 -1
  82. package/dist/{work-objects-jcdbg8rr.js → work-objects-x01x2fh8.js} +19 -17
  83. package/dist/{worker-x8j8c9sx.js → worker-py1ssb1h.js} +29 -27
  84. package/openapi.json +15622 -15042
  85. package/package.json +8 -5
  86. package/src/apps/store.ts +1 -0
  87. package/src/artifact-sdk/feedback-overlay.ts +528 -0
  88. package/src/be/db/workflow-runs.ts +95 -0
  89. package/src/be/db-queries/slack-inbound.ts +489 -0
  90. package/src/be/db.ts +9 -2
  91. package/src/be/extensions/assets.ts +1 -1
  92. package/src/be/migrations/203_workflow_run_parent_step.sql +10 -0
  93. package/src/be/migrations/204_slack_inbound_receipts.sql +51 -0
  94. package/src/be/script-connections.ts +41 -7
  95. package/src/be/scripts/run-saved.ts +40 -20
  96. package/src/be/swarm-config-guard.ts +4 -0
  97. package/src/extensions/catalog.generated.json +17 -0
  98. package/src/extensions/catalog.ts +1 -1
  99. package/src/extensions/dispatcher.ts +3 -3
  100. package/src/heartbeat/heartbeat.ts +152 -2
  101. package/src/heartbeat/templates.ts +16 -0
  102. package/src/http/all-routes.ts +1 -0
  103. package/src/http/index.ts +2 -0
  104. package/src/http/integrations.ts +3 -1
  105. package/src/http/mcp-bridge.ts +1 -0
  106. package/src/http/pages-public.ts +7 -2
  107. package/src/http/pages.ts +178 -0
  108. package/src/http/script-connections.ts +35 -0
  109. package/src/http/slack-inbound.ts +107 -0
  110. package/src/http/tasks.ts +45 -27
  111. package/src/http/users.ts +1 -1
  112. package/src/http/webhooks.ts +20 -20
  113. package/src/prompts/session-templates.ts +32 -0
  114. package/src/providers/acp-targets.ts +1 -1
  115. package/src/providers/amp-adapter.ts +2 -2
  116. package/src/providers/claude-adapter.ts +6 -0
  117. package/src/providers/claude-background-keepalive.ts +100 -0
  118. package/src/providers/claude-managed-adapter.ts +2 -11
  119. package/src/providers/claude-sdk-session.ts +7 -1
  120. package/src/providers/dsh-adapter.ts +1 -1
  121. package/src/providers/opencode-adapter.ts +1 -2
  122. package/src/providers/pi-mono-adapter.ts +3 -3
  123. package/src/realtime/browser.generated.txt +8 -8
  124. package/src/realtime/browser.ts +3 -3
  125. package/src/scheduler/scheduler.ts +82 -13
  126. package/src/scripts-runtime/executors/quickjs-pool-config.ts +26 -0
  127. package/src/scripts-runtime/executors/quickjs.ts +9 -2
  128. package/src/scripts-runtime/executors/registry.ts +1 -1
  129. package/src/server.ts +1 -0
  130. package/src/slack/actions.ts +6 -2
  131. package/src/slack/app.ts +5 -0
  132. package/src/slack/assistant.ts +15 -5
  133. package/src/slack/egress-scrub.ts +3 -2
  134. package/src/slack/handlers.ts +36 -18
  135. package/src/slack/inbound-dispatch.ts +549 -0
  136. package/src/slack/inbound-files.ts +2 -0
  137. package/src/telemetry-context.ts +1 -0
  138. package/src/tests/claude-background-keepalive-wiring.test.ts +220 -0
  139. package/src/tests/claude-background-keepalive.fixture.ts +162 -0
  140. package/src/tests/claude-background-keepalive.test.ts +139 -0
  141. package/src/tests/codex-oauth-device.test.ts +1 -1
  142. package/src/tests/connection-embedded-auth.test.ts +51 -0
  143. package/src/tests/docs-index-footprint.test.js +3 -3
  144. package/src/tests/dsh-adapter.test.ts +1 -1
  145. package/src/tests/extensions-catalog.test.ts +1 -0
  146. package/src/tests/extensions-example-slack-reply-gate.test.ts +756 -0
  147. package/src/tests/heartbeat-checklist.test.ts +243 -0
  148. package/src/tests/pages-feedback.test.ts +218 -0
  149. package/src/tests/schedule-target-type.test.ts +136 -0
  150. package/src/tests/script-connections-http.test.ts +45 -0
  151. package/src/tests/script-connections-mcp.test.ts +41 -0
  152. package/src/tests/script-connections.test.ts +2 -0
  153. package/src/tests/script-executor-quickjs-pool.test.ts +106 -0
  154. package/src/tests/script-executor-registry.test.ts +56 -0
  155. package/src/tests/send-task-self-send.test.ts +107 -0
  156. package/src/tests/slack-inbound-diagnostics-auth.test.ts +93 -0
  157. package/src/tests/slack-inbound-receipts.test.ts +684 -0
  158. package/src/tests/slack-session-status.test.ts +4 -2
  159. package/src/tests/ui-e2e-ingest-payload.test.ts +5 -1
  160. package/src/tests/user-token-routes.test.ts +1 -1
  161. package/src/tests/workflow-executors.test.ts +3 -2
  162. package/src/tests/workflow-sub-workflow.test.ts +363 -0
  163. package/src/tools/script-connections/tool.ts +35 -3
  164. package/src/tools/send-task.ts +9 -1
  165. package/src/utils/internal-ai/complete-structured.ts +6 -3
  166. package/src/utils/runtime-model-catalog.ts +1 -1
  167. package/src/workflows/engine.ts +17 -1
  168. package/src/workflows/executors/agent-task.ts +2 -2
  169. package/src/workflows/executors/human-in-the-loop.ts +3 -3
  170. package/src/workflows/executors/registry.ts +2 -0
  171. package/src/workflows/executors/script.ts +1 -1
  172. package/src/workflows/executors/sub-workflow.ts +135 -0
  173. package/src/workflows/executors/wait.ts +3 -3
  174. package/src/workflows/recovery.ts +11 -1
  175. package/src/workflows/resume.ts +77 -0
  176. package/src/workflows/wait-filter.ts +5 -7
  177. package/templates/extensions/README.md +1 -0
  178. package/templates/extensions/slack-reply-gate/README.md +98 -0
  179. package/templates/extensions/slack-reply-gate/hooks.ts +582 -0
  180. package/templates/extensions/slack-reply-gate/manifest.json +8 -0
  181. package/templates/skills/heartbeat-runbook/content.md +3 -1
  182. package/templates/skills/pages/content.md +25 -0
  183. package/templates/skills/swarm-scripts/SKILL.md +10 -0
  184. package/templates/skills/swarm-scripts/content.md +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.167.0",
3
+ "version": "1.169.0",
4
4
  "description": "agent-swarm.dev — an open-source operating system for AI work: a lead agent breaks goals into tasks, routes them to specialized workers such as Claude Code or Codex, runs each worker in an isolated container, and preserves shared memory, tools, schedules, and review gates so delegated work compounds across sessions.",
5
5
  "keywords": [
6
6
  "agent-swarm.dev",
@@ -148,6 +148,7 @@
148
148
  "inspector": "bunx @modelcontextprotocol/inspector --transport stdio bun src/stdio.ts",
149
149
  "inspector:http": "bunx @modelcontextprotocol/inspector --transport http https://api.swarm.localhost:1355/mcp",
150
150
  "lint": "biome check src apps/evals packages/ui-e2e packages/model-routing packages/model-catalog",
151
+ "lint:slop": "bun --bun oxlint src apps/evals packages/ui-e2e packages/model-routing packages/model-catalog",
151
152
  "lint:fix": "biome check --write src apps/evals packages/ui-e2e packages/model-routing packages/model-catalog",
152
153
  "format": "biome format --write src",
153
154
  "build:binary": "bun build ./src/cli.tsx ./src/providers/pi-binary-root/src/extensions/codemode/worker.ts --root ./src/providers/pi-binary-root --compile --bytecode --format=esm --compile-exec-argv='--expose-gc' --target=bun-linux-x64 --outfile ./dist/agent-swarm",
@@ -185,11 +186,13 @@
185
186
  "@desplega/model-routing": "workspace:*",
186
187
  "@faker-js/faker": "^10.4.0",
187
188
  "@opencode-ai/plugin": "1.18.35",
189
+ "@oxlint/plugins": "1.78.0",
188
190
  "@types/bun": "latest",
189
191
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
190
192
  "dependency-cruiser": "^18.0.0",
191
193
  "konsistent": "1.0.0-beta.1",
192
194
  "openapi-typescript": "^7",
195
+ "oxlint": "1.78.0",
193
196
  "ts-morph": "^28.0.0",
194
197
  "turbo": "^2.10.0"
195
198
  },
@@ -200,10 +203,10 @@
200
203
  "bun": ">=1.3.12"
201
204
  },
202
205
  "dependencies": {
203
- "@agentclientprotocol/sdk": "1.7.0",
206
+ "@agentclientprotocol/sdk": "1.8.0",
204
207
  "@ai-sdk/openai": "^3.0.41",
205
- "@anthropic-ai/claude-agent-sdk": "0.3.293",
206
- "@anthropic-ai/sdk": "^0.132.1",
208
+ "@anthropic-ai/claude-agent-sdk": "0.3.296",
209
+ "@anthropic-ai/sdk": "^0.133.0",
207
210
  "@asteasolutions/zod-to-openapi": "^8.0.0",
208
211
  "@aws-sdk/client-bedrock": "3.1048.0",
209
212
  "@cursor/sdk": "1.0.37",
@@ -216,7 +219,7 @@
216
219
  "@jitl/quickjs-singlefile-mjs-release-sync": "0.32.0",
217
220
  "@linear/sdk": "^77.0.0",
218
221
  "@modelcontextprotocol/sdk": "1.31.0",
219
- "@openai/codex": "0.161.0",
222
+ "@openai/codex": "0.162.1",
220
223
  "@opencode-ai/sdk": "^1.18.35",
221
224
  "@openfort/openfort-node": "^0.9.1",
222
225
  "@opentelemetry/api": "^1.9.1",
package/src/apps/store.ts CHANGED
@@ -63,6 +63,7 @@ export function decodeApp(row: AppDbRow): AppRecord {
63
63
  id: row.id,
64
64
  name: row.name,
65
65
  ...(row.description === null ? {} : { description: row.description }),
66
+ // oxlint-disable-next-line anti-slop/no-chained-type-assertions -- invalid JSON keeps the raw string; definitionError flags it.
66
67
  definition: row.definition as unknown as AppRecord["definition"],
67
68
  definitionError: [invalidJsonIssue(error)],
68
69
  createdAt: row.created_at,
@@ -0,0 +1,528 @@
1
+ // In-page feedback overlay for DB-backed pages (`/p/:id`).
2
+ //
3
+ // Served only when the request carries `?__swarm-feedback` (the dashboard's
4
+ // "Feedback" toggle sets it, see apps/ui/src/pages/pages/[id]/page.tsx). The
5
+ // viewer picks elements on the page, leaves a comment on each, and sends the
6
+ // batch to `POST /api/pages/:id/feedback`, which creates one task for the lead
7
+ // (src/http/pages.ts). Inside the dashboard the parent SPA sends it with its
8
+ // bearer (postMessage bridge), so the page never gets a viewer session for
9
+ // this. Opened directly, the overlay falls back to the `/@swarm/api` proxy,
10
+ // which needs an existing page session.
11
+ //
12
+ // Pure DOM, zero deps, rendered inside a shadow root so page CSS (and the
13
+ // Tailwind Play CDN) cannot restyle it. Draft comments persist in
14
+ // sessionStorage per page, so a reload does not lose them. User text is only
15
+ // ever written through `textContent`.
16
+ //
17
+ // The script body is a plain string: keep it free of backticks and `${`.
18
+
19
+ /** Query param that turns the overlay on. `0` / `false` keep it off. */
20
+ export const PAGE_FEEDBACK_PARAM = "__swarm-feedback";
21
+
22
+ export function isPageFeedbackRequested(queryParams: URLSearchParams): boolean {
23
+ if (!queryParams.has(PAGE_FEEDBACK_PARAM)) return false;
24
+ const value = (queryParams.get(PAGE_FEEDBACK_PARAM) ?? "").toLowerCase();
25
+ return value !== "0" && value !== "false";
26
+ }
27
+
28
+ const FEEDBACK_OVERLAY_JS = `
29
+ (function () {
30
+ var cfg = window.__swarmFeedback;
31
+ if (!cfg || !cfg.pageId || window.__swarmFeedbackMounted) return;
32
+ window.__swarmFeedbackMounted = true;
33
+
34
+ var pageId = cfg.pageId;
35
+ var storageKey = 'swarm-feedback:' + pageId;
36
+ var state = { selecting: false, comments: load(), draft: null, sending: false, collapsed: false, status: null };
37
+
38
+ function load() {
39
+ try { return JSON.parse(sessionStorage.getItem(storageKey) || '[]') || []; } catch (e) { return []; }
40
+ }
41
+ function save() {
42
+ try { sessionStorage.setItem(storageKey, JSON.stringify(state.comments)); } catch (e) {}
43
+ }
44
+
45
+ // ─── Element description ───────────────────────────────────────────────
46
+ function esc(value) {
47
+ return window.CSS && CSS.escape ? CSS.escape(value) : String(value).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&');
48
+ }
49
+ function uniqueId(el) {
50
+ if (!el.id) return false;
51
+ try { return document.querySelectorAll('#' + esc(el.id)).length === 1; } catch (e) { return false; }
52
+ }
53
+ function cssPath(el) {
54
+ var parts = [];
55
+ while (el && el.nodeType === 1 && el !== document.documentElement) {
56
+ if (uniqueId(el)) { parts.unshift('#' + esc(el.id)); break; }
57
+ var part = el.tagName.toLowerCase();
58
+ var parent = el.parentElement;
59
+ if (parent) {
60
+ var same = [];
61
+ for (var i = 0; i < parent.children.length; i++) {
62
+ if (parent.children[i].tagName === el.tagName) same.push(parent.children[i]);
63
+ }
64
+ if (same.length > 1) part += ':nth-of-type(' + (same.indexOf(el) + 1) + ')';
65
+ }
66
+ parts.unshift(part);
67
+ el = parent;
68
+ }
69
+ return parts.join(' > ');
70
+ }
71
+ function excerpt(el) {
72
+ return (el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 200);
73
+ }
74
+ function htmlSnippet(el) {
75
+ var html = el.outerHTML || '';
76
+ return html.length > 400 ? html.slice(0, 400) + '…' : html;
77
+ }
78
+ function findEl(selector) {
79
+ try { return document.querySelector(selector); } catch (e) { return null; }
80
+ }
81
+
82
+ // ─── Shadow root + styles ──────────────────────────────────────────────
83
+ var host = document.createElement('swarm-feedback-root');
84
+ host.setAttribute('data-swarm-feedback', '');
85
+ var root = host.attachShadow({ mode: 'open' });
86
+ var style = document.createElement('style');
87
+ style.textContent = [
88
+ ':host { all: initial; }',
89
+ '@media print { :host { display: none !important; } }',
90
+ '* { box-sizing: border-box; font-family: "Space Grotesk", system-ui, sans-serif; }',
91
+ '.hl { position: fixed; pointer-events: none; z-index: 2147483645; border: 2px solid #f59e0b; background: rgba(245, 158, 11, 0.12); border-radius: 3px; display: none; }',
92
+ '.hl-label { position: absolute; left: -2px; top: -22px; background: #f59e0b; color: #111827; font: 600 11px/1 "Space Mono", ui-monospace, monospace; padding: 4px 6px; border-radius: 3px; white-space: nowrap; max-width: 360px; overflow: hidden; text-overflow: ellipsis; }',
93
+ '.marker { position: fixed; z-index: 2147483646; width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 999px; background: #f59e0b; color: #111827; font: 700 11px/22px "Space Mono", ui-monospace, monospace; text-align: center; cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.3); border: 2px solid #fff; }',
94
+ '.panel { position: fixed; right: 16px; bottom: 16px; z-index: 2147483647; width: 320px; max-height: min(70vh, 560px); display: flex; flex-direction: column; background: #fff; color: #111827; border: 1px solid #e5e7eb; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.18); font-size: 13px; line-height: 1.4; }',
95
+ '.panel.collapsed { width: auto; }',
96
+ '.head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid #e5e7eb; }',
97
+ '.panel.collapsed .head { border-bottom: 0; }',
98
+ '.title { font-weight: 600; flex: 1; }',
99
+ '.count { font: 600 11px/1 "Space Mono", ui-monospace, monospace; background: #f3f4f6; border-radius: 999px; padding: 3px 7px; }',
100
+ '.body { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; overflow: auto; }',
101
+ '.panel.collapsed .body { display: none; }',
102
+ 'button { font: inherit; font-size: 12px; border: 1px solid #d1d5db; background: #fff; color: #111827; border-radius: 6px; padding: 6px 10px; cursor: pointer; }',
103
+ 'button:hover:not(:disabled) { background: #f9fafb; }',
104
+ 'button:disabled { opacity: 0.5; cursor: default; }',
105
+ 'button.primary { background: #111827; border-color: #111827; color: #fff; }',
106
+ 'button.primary:hover:not(:disabled) { background: #1f2937; }',
107
+ 'button.active { background: #f59e0b; border-color: #f59e0b; color: #111827; }',
108
+ 'button.icon { padding: 2px 7px; border: 0; color: #6b7280; }',
109
+ '.hint { color: #6b7280; font-size: 12px; }',
110
+ 'ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }',
111
+ 'li { display: flex; gap: 8px; align-items: flex-start; border: 1px solid #e5e7eb; border-radius: 8px; padding: 8px; }',
112
+ 'li:hover { border-color: #f59e0b; }',
113
+ '.num { flex: none; width: 20px; height: 20px; border-radius: 999px; background: #f59e0b; color: #111827; font: 700 11px/20px "Space Mono", ui-monospace, monospace; text-align: center; }',
114
+ '.li-main { flex: 1; min-width: 0; }',
115
+ '.sel { font: 11px/1.3 "Space Mono", ui-monospace, monospace; color: #6b7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
116
+ '.missing { color: #b45309; }',
117
+ '.text { white-space: pre-wrap; word-break: break-word; }',
118
+ 'textarea { font: inherit; font-size: 13px; width: 100%; min-height: 64px; resize: vertical; border: 1px solid #d1d5db; border-radius: 6px; padding: 6px 8px; color: #111827; background: #fff; }',
119
+ 'textarea:focus { outline: 2px solid #f59e0b; outline-offset: 0; border-color: #f59e0b; }',
120
+ '.row { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }',
121
+ '.composer { position: fixed; z-index: 2147483647; width: 300px; background: #fff; color: #111827; border: 1px solid #e5e7eb; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.18); padding: 10px; display: flex; flex-direction: column; gap: 8px; font-size: 13px; }',
122
+ '.status { font-size: 12px; border-radius: 6px; padding: 6px 8px; }',
123
+ '.status.ok { background: #ecfdf5; color: #065f46; }',
124
+ '.status.err { background: #fef2f2; color: #991b1b; }',
125
+ '.status a { color: inherit; font-weight: 600; }',
126
+ ].join('\\n');
127
+ root.appendChild(style);
128
+
129
+ var hl = document.createElement('div');
130
+ hl.className = 'hl';
131
+ var hlLabel = document.createElement('div');
132
+ hlLabel.className = 'hl-label';
133
+ hl.appendChild(hlLabel);
134
+ var markers = document.createElement('div');
135
+ var panel = document.createElement('div');
136
+ panel.className = 'panel';
137
+ var composer = null;
138
+ root.appendChild(hl);
139
+ root.appendChild(markers);
140
+ root.appendChild(panel);
141
+
142
+ function el(tag, cls, text) {
143
+ var node = document.createElement(tag);
144
+ if (cls) node.className = cls;
145
+ if (text != null) node.textContent = text;
146
+ return node;
147
+ }
148
+ function describe(target) {
149
+ var label = target.tagName.toLowerCase();
150
+ if (target.id) label += '#' + target.id;
151
+ else if (typeof target.className === 'string' && target.className.trim()) {
152
+ label += '.' + target.className.trim().split(/\\s+/).slice(0, 2).join('.');
153
+ }
154
+ return label;
155
+ }
156
+
157
+ // ─── Highlight ─────────────────────────────────────────────────────────
158
+ function showHighlight(target, label) {
159
+ var r = target.getBoundingClientRect();
160
+ hl.style.display = 'block';
161
+ hl.style.left = r.left + 'px';
162
+ hl.style.top = r.top + 'px';
163
+ hl.style.width = r.width + 'px';
164
+ hl.style.height = r.height + 'px';
165
+ hlLabel.textContent = label || describe(target);
166
+ hlLabel.style.top = r.top < 24 ? '100%' : '-22px';
167
+ }
168
+ function hideHighlight() { hl.style.display = 'none'; }
169
+
170
+ // ─── Select mode ───────────────────────────────────────────────────────
171
+ var prevCursor = '';
172
+ function isOwn(e) { return e.target === host; }
173
+ function pickable(target) {
174
+ return target && target.nodeType === 1 && target !== document.documentElement && target !== document.body;
175
+ }
176
+ function onMove(e) {
177
+ if (state.draft) return;
178
+ if (isOwn(e) || !pickable(e.target)) { hideHighlight(); return; }
179
+ showHighlight(e.target);
180
+ }
181
+ function swallow(e) {
182
+ if (isOwn(e)) return;
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
186
+ }
187
+ function onClick(e) {
188
+ if (isOwn(e)) return;
189
+ swallow(e);
190
+ if (state.draft || !pickable(e.target)) return;
191
+ openComposer(e.target);
192
+ }
193
+ function setSelecting(on) {
194
+ state.selecting = on;
195
+ var method = on ? 'addEventListener' : 'removeEventListener';
196
+ document[method]('mousemove', onMove, true);
197
+ document[method]('click', onClick, true);
198
+ document[method]('mousedown', swallow, true);
199
+ document[method]('mouseup', swallow, true);
200
+ document[method]('submit', swallow, true);
201
+ if (on) {
202
+ prevCursor = document.documentElement.style.cursor;
203
+ document.documentElement.style.cursor = 'crosshair';
204
+ } else {
205
+ document.documentElement.style.cursor = prevCursor;
206
+ hideHighlight();
207
+ }
208
+ render();
209
+ }
210
+ document.addEventListener('keydown', function (e) {
211
+ if (e.key !== 'Escape') return;
212
+ if (state.draft) { closeComposer(); return; }
213
+ if (state.selecting) setSelecting(false);
214
+ }, true);
215
+
216
+ // ─── Composer ──────────────────────────────────────────────────────────
217
+ function openComposer(target) {
218
+ var selector = cssPath(target);
219
+ state.draft = { target: target, selector: selector };
220
+ showHighlight(target, describe(target));
221
+ composer = el('div', 'composer');
222
+ composer.appendChild(el('div', 'sel', selector));
223
+ var input = el('textarea');
224
+ input.placeholder = 'What should change here?';
225
+ composer.appendChild(input);
226
+ var row = el('div', 'row');
227
+ var cancel = el('button', '', 'Cancel');
228
+ var add = el('button', 'primary', 'Add comment');
229
+ cancel.type = 'button';
230
+ add.type = 'button';
231
+ cancel.addEventListener('click', closeComposer);
232
+ add.addEventListener('click', function () { commitDraft(input.value); });
233
+ input.addEventListener('keydown', function (e) {
234
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); commitDraft(input.value); }
235
+ });
236
+ row.appendChild(cancel);
237
+ row.appendChild(add);
238
+ composer.appendChild(row);
239
+ root.appendChild(composer);
240
+ placeComposer(target);
241
+ input.focus();
242
+ }
243
+ function placeComposer(target) {
244
+ var r = target.getBoundingClientRect();
245
+ var width = 300;
246
+ var height = composer.offsetHeight || 150;
247
+ var left = Math.min(Math.max(8, r.left), window.innerWidth - width - 8);
248
+ var top = r.bottom + 8;
249
+ if (top + height > window.innerHeight - 8) top = Math.max(8, r.top - height - 8);
250
+ composer.style.left = left + 'px';
251
+ composer.style.top = top + 'px';
252
+ }
253
+ function closeComposer() {
254
+ if (composer) composer.remove();
255
+ composer = null;
256
+ state.draft = null;
257
+ hideHighlight();
258
+ }
259
+ function commitDraft(value) {
260
+ var text = (value || '').trim();
261
+ if (!text || !state.draft) return;
262
+ var target = state.draft.target;
263
+ state.comments.push({
264
+ id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
265
+ selector: state.draft.selector,
266
+ tagName: target.tagName.toLowerCase(),
267
+ text: excerpt(target),
268
+ html: htmlSnippet(target),
269
+ comment: text,
270
+ });
271
+ state.status = null;
272
+ save();
273
+ closeComposer();
274
+ render();
275
+ }
276
+
277
+ // ─── Markers ───────────────────────────────────────────────────────────
278
+ function renderMarkers() {
279
+ markers.textContent = '';
280
+ state.comments.forEach(function (c, i) {
281
+ var target = findEl(c.selector);
282
+ if (!target) return;
283
+ var r = target.getBoundingClientRect();
284
+ if (r.width === 0 && r.height === 0) return;
285
+ if (r.bottom < 0 || r.top > window.innerHeight) return;
286
+ var m = el('div', 'marker', String(i + 1));
287
+ m.style.left = Math.max(11, r.left) + 'px';
288
+ m.style.top = Math.max(11, r.top) + 'px';
289
+ m.title = c.comment;
290
+ m.addEventListener('mouseenter', function () { showHighlight(target, '#' + (i + 1)); });
291
+ m.addEventListener('mouseleave', hideHighlight);
292
+ m.addEventListener('click', function () {
293
+ state.collapsed = false;
294
+ render();
295
+ });
296
+ markers.appendChild(m);
297
+ });
298
+ }
299
+ var rafPending = false;
300
+ function scheduleMarkers() {
301
+ if (rafPending) return;
302
+ rafPending = true;
303
+ requestAnimationFrame(function () {
304
+ rafPending = false;
305
+ renderMarkers();
306
+ if (state.draft && composer) placeComposer(state.draft.target);
307
+ });
308
+ }
309
+ window.addEventListener('scroll', scheduleMarkers, { capture: true, passive: true });
310
+ window.addEventListener('resize', scheduleMarkers);
311
+
312
+ // ─── Send ──────────────────────────────────────────────────────────────
313
+ function pageUrl() {
314
+ try {
315
+ var url = new URL(location.href);
316
+ url.searchParams.delete('__swarm-feedback');
317
+ url.searchParams.delete('key');
318
+ return url.toString();
319
+ } catch (e) { return location.href; }
320
+ }
321
+ function errorMessage(status, body) {
322
+ if (status === 401) return 'Your page session expired or is missing. Open this page from the swarm dashboard and try again.';
323
+ if (status === 403) return 'This session cannot send feedback. Sign in to the swarm dashboard and open the page from there.';
324
+ var detail = body && body.error ? ': ' + body.error : '';
325
+ return 'Sending failed (' + status + detail + ').';
326
+ }
327
+ // Inside the dashboard, the parent SPA sends the comments with its own
328
+ // bearer (apps/ui/src/pages/pages/[id]/feedback-bridge.ts), so the page
329
+ // needs no viewer session. Elsewhere, fall back to the cookie-gated proxy.
330
+ var bridge = null;
331
+ var pending = {};
332
+ var requestSeq = 0;
333
+ window.addEventListener('message', function (e) {
334
+ if (e.source !== window.parent || window.parent === window) return;
335
+ var data = e.data;
336
+ if (!data || typeof data !== 'object') return;
337
+ if (data.type === 'swarm-feedback:host') {
338
+ bridge = { target: e.source, origin: e.origin };
339
+ return;
340
+ }
341
+ if (data.type === 'swarm-feedback:result' && pending[data.requestId]) {
342
+ var done = pending[data.requestId];
343
+ delete pending[data.requestId];
344
+ done(data);
345
+ }
346
+ });
347
+ function sendViaBridge(payload) {
348
+ return new Promise(function (resolve, reject) {
349
+ var requestId = 'fb' + (++requestSeq);
350
+ // The dashboard asks the viewer to confirm, so allow time for that.
351
+ var timer = setTimeout(function () {
352
+ delete pending[requestId];
353
+ reject({ message: 'The dashboard did not answer. Try again.' });
354
+ }, 600000);
355
+ pending[requestId] = function (data) {
356
+ clearTimeout(timer);
357
+ if (data.ok) resolve({ url: data.taskUrl });
358
+ else reject({ message: 'Sending failed: ' + (data.error || 'unknown error') });
359
+ };
360
+ bridge.target.postMessage({ type: 'swarm-feedback:send', requestId: requestId, payload: payload }, bridge.origin);
361
+ });
362
+ }
363
+ function sendViaProxy(payload) {
364
+ return fetch('/@swarm/api/pages/' + encodeURIComponent(pageId) + '/feedback', {
365
+ method: 'POST',
366
+ credentials: 'same-origin',
367
+ headers: { 'Content-Type': 'application/json' },
368
+ body: JSON.stringify(payload),
369
+ }).then(function (res) {
370
+ return res.text().then(function (text) {
371
+ var body = null;
372
+ try { body = text ? JSON.parse(text) : null; } catch (e) {}
373
+ if (!res.ok) throw { message: errorMessage(res.status, body) };
374
+ return { url: body && body.task_url };
375
+ });
376
+ });
377
+ }
378
+ function send(note) {
379
+ if (state.sending || state.comments.length === 0) return;
380
+ state.sending = true;
381
+ state.status = null;
382
+ render();
383
+ var payload = {
384
+ pageUrl: pageUrl(),
385
+ comments: state.comments.map(function (c) {
386
+ return { selector: c.selector, tagName: c.tagName, text: c.text, html: c.html, comment: c.comment };
387
+ }),
388
+ };
389
+ if (note && note.trim()) payload.note = note.trim();
390
+ (bridge ? sendViaBridge(payload) : sendViaProxy(payload)).then(function (result) {
391
+ state.comments = [];
392
+ save();
393
+ state.noteDraft = '';
394
+ state.status = { ok: true, url: result && result.url };
395
+ }).catch(function (err) {
396
+ state.status = { ok: false, message: (err && err.message) || 'Sending failed.' };
397
+ }).then(function () {
398
+ state.sending = false;
399
+ render();
400
+ });
401
+ }
402
+
403
+ // ─── Panel ─────────────────────────────────────────────────────────────
404
+ state.noteDraft = '';
405
+ function render() {
406
+ panel.textContent = '';
407
+ panel.className = 'panel' + (state.collapsed ? ' collapsed' : '');
408
+
409
+ var head = el('div', 'head');
410
+ head.appendChild(el('span', 'title', 'Feedback'));
411
+ head.appendChild(el('span', 'count', String(state.comments.length)));
412
+ var toggle = el('button', 'icon', state.collapsed ? '▴' : '▾');
413
+ toggle.type = 'button';
414
+ toggle.title = state.collapsed ? 'Expand' : 'Collapse';
415
+ toggle.addEventListener('click', function () { state.collapsed = !state.collapsed; render(); });
416
+ head.appendChild(toggle);
417
+ panel.appendChild(head);
418
+
419
+ var body = el('div', 'body');
420
+ var pick = el('button', state.selecting ? 'active' : '', state.selecting ? 'Selecting… (Esc to stop)' : 'Select an element');
421
+ pick.type = 'button';
422
+ pick.addEventListener('click', function () { setSelecting(!state.selecting); });
423
+ body.appendChild(pick);
424
+
425
+ if (state.comments.length === 0) {
426
+ body.appendChild(el('div', 'hint', state.selecting
427
+ ? 'Click any element on the page to comment on it.'
428
+ : 'Pick elements on the page and leave a comment on each. Then send them to the swarm as one task.'));
429
+ } else {
430
+ var list = el('ol');
431
+ state.comments.forEach(function (c, i) {
432
+ var item = el('li');
433
+ item.appendChild(el('span', 'num', String(i + 1)));
434
+ var main = el('div', 'li-main');
435
+ var target = findEl(c.selector);
436
+ main.appendChild(el('div', 'sel' + (target ? '' : ' missing'), (target ? '' : '(not found) ') + c.selector));
437
+ main.appendChild(el('div', 'text', c.comment));
438
+ item.appendChild(main);
439
+ var remove = el('button', 'icon', '×');
440
+ remove.type = 'button';
441
+ remove.title = 'Remove comment';
442
+ remove.addEventListener('click', function () {
443
+ state.comments.splice(i, 1);
444
+ save();
445
+ hideHighlight();
446
+ render();
447
+ });
448
+ item.appendChild(remove);
449
+ if (target) {
450
+ item.addEventListener('mouseenter', function () { showHighlight(target, '#' + (i + 1)); });
451
+ item.addEventListener('mouseleave', function () { if (!state.draft) hideHighlight(); });
452
+ item.addEventListener('click', function (e) {
453
+ if (e.target === remove) return;
454
+ target.scrollIntoView({ block: 'center', behavior: 'smooth' });
455
+ });
456
+ }
457
+ list.appendChild(item);
458
+ });
459
+ body.appendChild(list);
460
+
461
+ var note = el('textarea');
462
+ note.placeholder = 'Overall note (optional)';
463
+ note.value = state.noteDraft;
464
+ note.addEventListener('input', function () { state.noteDraft = note.value; });
465
+ body.appendChild(note);
466
+ }
467
+
468
+ if (state.status) {
469
+ var status = el('div', 'status ' + (state.status.ok ? 'ok' : 'err'));
470
+ if (state.status.ok) {
471
+ status.appendChild(document.createTextNode('Sent to the swarm. '));
472
+ if (state.status.url) {
473
+ var link = el('a', '', 'Open task');
474
+ link.href = state.status.url;
475
+ link.target = '_blank';
476
+ link.rel = 'noreferrer';
477
+ status.appendChild(link);
478
+ }
479
+ } else {
480
+ status.textContent = state.status.message;
481
+ }
482
+ body.appendChild(status);
483
+ }
484
+
485
+ var row = el('div', 'row');
486
+ if (state.comments.length > 0) {
487
+ var clear = el('button', '', 'Clear');
488
+ clear.type = 'button';
489
+ clear.disabled = state.sending;
490
+ clear.addEventListener('click', function () {
491
+ state.comments = [];
492
+ save();
493
+ render();
494
+ });
495
+ row.appendChild(clear);
496
+ }
497
+ var sendBtn = el('button', 'primary', state.sending ? (bridge ? 'Confirm in the dashboard…' : 'Sending…') : 'Send to swarm');
498
+ sendBtn.type = 'button';
499
+ sendBtn.disabled = state.sending || state.comments.length === 0;
500
+ sendBtn.addEventListener('click', function () { send(state.noteDraft); });
501
+ row.appendChild(sendBtn);
502
+ body.appendChild(row);
503
+
504
+ panel.appendChild(body);
505
+ renderMarkers();
506
+ }
507
+
508
+ function mount() {
509
+ document.documentElement.appendChild(host);
510
+ render();
511
+ if (window.parent !== window) {
512
+ try { window.parent.postMessage({ type: 'swarm-feedback:hello' }, '*'); } catch (e) {}
513
+ }
514
+ }
515
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
516
+ else mount();
517
+ })();
518
+ `;
519
+
520
+ /**
521
+ * Script tags appended to a served HTML page when feedback mode is on. The
522
+ * page id is a 32-char hex id, but it is JSON-encoded anyway so the bootstrap
523
+ * stays safe if the id format ever changes.
524
+ */
525
+ export function feedbackOverlayScripts(pageId: string): string {
526
+ const config = JSON.stringify({ pageId }).replace(/</g, "\\u003c");
527
+ return `<script>window.__swarmFeedback = ${config};</script><script>${FEEDBACK_OVERLAY_JS}</script>`;
528
+ }