@alisio/plugin-swarm 0.0.0-stage → 0.1.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 (266) hide show
  1. package/.agents/agents/swarm-architect.md +20 -0
  2. package/.agents/agents/swarm-cleaner.md +20 -0
  3. package/.agents/agents/swarm-coder.md +20 -0
  4. package/.agents/agents/swarm-hardener.md +20 -0
  5. package/.agents/agents/swarm-lieutenant.md +20 -0
  6. package/.agents/agents/swarm-qa.md +20 -0
  7. package/.agents/agents/swarm-refactorer.md +20 -0
  8. package/.agents/agents/swarm-specifier.md +20 -0
  9. package/.agents/skills/swarm-architecture-rules/SKILL.md +43 -0
  10. package/.agents/skills/swarm-clarification/SKILL.md +42 -0
  11. package/.agents/skills/swarm-cleaner-metrics/SKILL.md +44 -0
  12. package/.agents/skills/swarm-engineering-constitution/SKILL.md +44 -0
  13. package/.agents/skills/swarm-gherkin-spec/SKILL.md +43 -0
  14. package/.agents/skills/swarm-handoff-protocol/SKILL.md +45 -0
  15. package/.agents/skills/swarm-lieutenant/SKILL.md +41 -0
  16. package/.agents/skills/swarm-mutation-hardening/SKILL.md +43 -0
  17. package/.agents/skills/swarm-qa-acceptance/SKILL.md +42 -0
  18. package/.agents/skills/swarm-tdd-slice/SKILL.md +44 -0
  19. package/.agents/skills/swarm-toolchain-profile/SKILL.md +41 -0
  20. package/LICENSE +21 -0
  21. package/README.es.md +337 -0
  22. package/README.md +322 -2
  23. package/assets/approval-flow.svg +1 -0
  24. package/assets/architecture.svg +1 -0
  25. package/assets/audit-states.svg +1 -0
  26. package/assets/dashboard.css +964 -0
  27. package/assets/dashboard.html +123 -0
  28. package/assets/dashboard.js +965 -0
  29. package/assets/dashboard.svg +1 -0
  30. package/assets/handoff-sequence.svg +1 -0
  31. package/assets/pack-pipelines.svg +1 -0
  32. package/assets/packs/four-pack/pack.json +18 -0
  33. package/assets/packs/six-pack/pack.json +22 -0
  34. package/assets/packs/two-pack/pack.json +14 -0
  35. package/assets/screenshots/dashboard-board-dark.png +0 -0
  36. package/assets/screenshots/dashboard-board-light.png +0 -0
  37. package/assets/screenshots/dashboard-documents-diff.png +0 -0
  38. package/assets/screenshots/dashboard-empty-state.png +0 -0
  39. package/assets/screenshots/dashboard-phone.png +0 -0
  40. package/assets/screenshots/dashboard-reject-dialog.png +0 -0
  41. package/assets/screenshots/dashboard-transcript-tail.png +0 -0
  42. package/assets/screenshots/dashboard-work-queue.png +0 -0
  43. package/assets/task-states.svg +1 -0
  44. package/assets/toolchains/node-ts.json +61 -0
  45. package/cover.svg +46 -0
  46. package/dist/adapters/child-session-runner.d.ts +36 -0
  47. package/dist/adapters/child-session-runner.d.ts.map +1 -0
  48. package/dist/adapters/child-session-runner.js +156 -0
  49. package/dist/adapters/child-session-runner.js.map +1 -0
  50. package/dist/adapters/fs-board-store.d.ts +11 -0
  51. package/dist/adapters/fs-board-store.d.ts.map +1 -0
  52. package/dist/adapters/fs-board-store.js +27 -0
  53. package/dist/adapters/fs-board-store.js.map +1 -0
  54. package/dist/adapters/fs-handoff-store.d.ts +36 -0
  55. package/dist/adapters/fs-handoff-store.d.ts.map +1 -0
  56. package/dist/adapters/fs-handoff-store.js +254 -0
  57. package/dist/adapters/fs-handoff-store.js.map +1 -0
  58. package/dist/adapters/fs-task-state-store.d.ts +16 -0
  59. package/dist/adapters/fs-task-state-store.d.ts.map +1 -0
  60. package/dist/adapters/fs-task-state-store.js +66 -0
  61. package/dist/adapters/fs-task-state-store.js.map +1 -0
  62. package/dist/adapters/git-worktree.d.ts +31 -0
  63. package/dist/adapters/git-worktree.d.ts.map +1 -0
  64. package/dist/adapters/git-worktree.js +209 -0
  65. package/dist/adapters/git-worktree.js.map +1 -0
  66. package/dist/adapters/process-exec.d.ts +36 -0
  67. package/dist/adapters/process-exec.d.ts.map +1 -0
  68. package/dist/adapters/process-exec.js +138 -0
  69. package/dist/adapters/process-exec.js.map +1 -0
  70. package/dist/adapters/process-gate-runner.d.ts +39 -0
  71. package/dist/adapters/process-gate-runner.d.ts.map +1 -0
  72. package/dist/adapters/process-gate-runner.js +242 -0
  73. package/dist/adapters/process-gate-runner.js.map +1 -0
  74. package/dist/adapters/unavailable-runner.d.ts +4 -0
  75. package/dist/adapters/unavailable-runner.d.ts.map +1 -0
  76. package/dist/adapters/unavailable-runner.js +12 -0
  77. package/dist/adapters/unavailable-runner.js.map +1 -0
  78. package/dist/app/audit.d.ts +23 -0
  79. package/dist/app/audit.d.ts.map +1 -0
  80. package/dist/app/audit.js +9 -0
  81. package/dist/app/audit.js.map +1 -0
  82. package/dist/app/budget.d.ts +29 -0
  83. package/dist/app/budget.d.ts.map +1 -0
  84. package/dist/app/budget.js +78 -0
  85. package/dist/app/budget.js.map +1 -0
  86. package/dist/app/chat-store.d.ts +18 -0
  87. package/dist/app/chat-store.d.ts.map +1 -0
  88. package/dist/app/chat-store.js +48 -0
  89. package/dist/app/chat-store.js.map +1 -0
  90. package/dist/app/documents.d.ts +33 -0
  91. package/dist/app/documents.d.ts.map +1 -0
  92. package/dist/app/documents.js +63 -0
  93. package/dist/app/documents.js.map +1 -0
  94. package/dist/app/forge.d.ts +106 -0
  95. package/dist/app/forge.d.ts.map +1 -0
  96. package/dist/app/forge.js +420 -0
  97. package/dist/app/forge.js.map +1 -0
  98. package/dist/app/operator.d.ts +46 -0
  99. package/dist/app/operator.d.ts.map +1 -0
  100. package/dist/app/operator.js +168 -0
  101. package/dist/app/operator.js.map +1 -0
  102. package/dist/app/pidfile.d.ts +18 -0
  103. package/dist/app/pidfile.d.ts.map +1 -0
  104. package/dist/app/pidfile.js +56 -0
  105. package/dist/app/pidfile.js.map +1 -0
  106. package/dist/app/project.d.ts +93 -0
  107. package/dist/app/project.d.ts.map +1 -0
  108. package/dist/app/project.js +291 -0
  109. package/dist/app/project.js.map +1 -0
  110. package/dist/app/prompts.d.ts +48 -0
  111. package/dist/app/prompts.d.ts.map +1 -0
  112. package/dist/app/prompts.js +88 -0
  113. package/dist/app/prompts.js.map +1 -0
  114. package/dist/app/pump.d.ts +144 -0
  115. package/dist/app/pump.d.ts.map +1 -0
  116. package/dist/app/pump.js +707 -0
  117. package/dist/app/pump.js.map +1 -0
  118. package/dist/app/services.d.ts +112 -0
  119. package/dist/app/services.d.ts.map +1 -0
  120. package/dist/app/services.js +323 -0
  121. package/dist/app/services.js.map +1 -0
  122. package/dist/app/state.d.ts +70 -0
  123. package/dist/app/state.d.ts.map +1 -0
  124. package/dist/app/state.js +96 -0
  125. package/dist/app/state.js.map +1 -0
  126. package/dist/args.d.ts +10 -0
  127. package/dist/args.d.ts.map +1 -0
  128. package/dist/args.js +33 -0
  129. package/dist/args.js.map +1 -0
  130. package/dist/coordinator.d.ts +80 -0
  131. package/dist/coordinator.d.ts.map +1 -0
  132. package/dist/coordinator.js +481 -0
  133. package/dist/coordinator.js.map +1 -0
  134. package/dist/dashboard/api.d.ts +28 -0
  135. package/dist/dashboard/api.d.ts.map +1 -0
  136. package/dist/dashboard/api.js +178 -0
  137. package/dist/dashboard/api.js.map +1 -0
  138. package/dist/dashboard/auth.d.ts +13 -0
  139. package/dist/dashboard/auth.d.ts.map +1 -0
  140. package/dist/dashboard/auth.js +36 -0
  141. package/dist/dashboard/auth.js.map +1 -0
  142. package/dist/dashboard/open.d.ts +7 -0
  143. package/dist/dashboard/open.d.ts.map +1 -0
  144. package/dist/dashboard/open.js +17 -0
  145. package/dist/dashboard/open.js.map +1 -0
  146. package/dist/dashboard/server.d.ts +28 -0
  147. package/dist/dashboard/server.d.ts.map +1 -0
  148. package/dist/dashboard/server.js +176 -0
  149. package/dist/dashboard/server.js.map +1 -0
  150. package/dist/doctor.d.ts +28 -0
  151. package/dist/doctor.d.ts.map +1 -0
  152. package/dist/doctor.js +108 -0
  153. package/dist/doctor.js.map +1 -0
  154. package/dist/domain/attention.d.ts +20 -0
  155. package/dist/domain/attention.d.ts.map +1 -0
  156. package/dist/domain/attention.js +41 -0
  157. package/dist/domain/attention.js.map +1 -0
  158. package/dist/domain/envelope.d.ts +37 -0
  159. package/dist/domain/envelope.d.ts.map +1 -0
  160. package/dist/domain/envelope.js +106 -0
  161. package/dist/domain/envelope.js.map +1 -0
  162. package/dist/domain/handoff.d.ts +57 -0
  163. package/dist/domain/handoff.d.ts.map +1 -0
  164. package/dist/domain/handoff.js +191 -0
  165. package/dist/domain/handoff.js.map +1 -0
  166. package/dist/domain/identifiers.d.ts +18 -0
  167. package/dist/domain/identifiers.d.ts.map +1 -0
  168. package/dist/domain/identifiers.js +70 -0
  169. package/dist/domain/identifiers.js.map +1 -0
  170. package/dist/domain/pack.d.ts +45 -0
  171. package/dist/domain/pack.d.ts.map +1 -0
  172. package/dist/domain/pack.js +207 -0
  173. package/dist/domain/pack.js.map +1 -0
  174. package/dist/domain/pipeline.d.ts +33 -0
  175. package/dist/domain/pipeline.d.ts.map +1 -0
  176. package/dist/domain/pipeline.js +50 -0
  177. package/dist/domain/pipeline.js.map +1 -0
  178. package/dist/domain/task.d.ts +26 -0
  179. package/dist/domain/task.d.ts.map +1 -0
  180. package/dist/domain/task.js +154 -0
  181. package/dist/domain/task.js.map +1 -0
  182. package/dist/domain/taskstate.d.ts +37 -0
  183. package/dist/domain/taskstate.d.ts.map +1 -0
  184. package/dist/domain/taskstate.js +98 -0
  185. package/dist/domain/taskstate.js.map +1 -0
  186. package/dist/gates/dry.d.ts +14 -0
  187. package/dist/gates/dry.d.ts.map +1 -0
  188. package/dist/gates/dry.js +71 -0
  189. package/dist/gates/dry.js.map +1 -0
  190. package/dist/gates/parsers.d.ts +34 -0
  191. package/dist/gates/parsers.d.ts.map +1 -0
  192. package/dist/gates/parsers.js +120 -0
  193. package/dist/gates/parsers.js.map +1 -0
  194. package/dist/index.d.ts +45 -0
  195. package/dist/index.d.ts.map +1 -0
  196. package/dist/index.js +207 -0
  197. package/dist/index.js.map +1 -0
  198. package/dist/options.d.ts +13 -0
  199. package/dist/options.d.ts.map +1 -0
  200. package/dist/options.js +27 -0
  201. package/dist/options.js.map +1 -0
  202. package/dist/panel.d.ts +5 -0
  203. package/dist/panel.d.ts.map +1 -0
  204. package/dist/panel.js +52 -0
  205. package/dist/panel.js.map +1 -0
  206. package/dist/ports/agent-runner.d.ts +51 -0
  207. package/dist/ports/agent-runner.d.ts.map +1 -0
  208. package/dist/ports/agent-runner.js +2 -0
  209. package/dist/ports/agent-runner.js.map +1 -0
  210. package/dist/ports/board-store.d.ts +7 -0
  211. package/dist/ports/board-store.d.ts.map +1 -0
  212. package/dist/ports/board-store.js +2 -0
  213. package/dist/ports/board-store.js.map +1 -0
  214. package/dist/ports/clock.d.ts +5 -0
  215. package/dist/ports/clock.d.ts.map +1 -0
  216. package/dist/ports/clock.js +2 -0
  217. package/dist/ports/clock.js.map +1 -0
  218. package/dist/ports/gate-runner.d.ts +34 -0
  219. package/dist/ports/gate-runner.d.ts.map +1 -0
  220. package/dist/ports/gate-runner.js +2 -0
  221. package/dist/ports/gate-runner.js.map +1 -0
  222. package/dist/ports/handoff-store.d.ts +39 -0
  223. package/dist/ports/handoff-store.d.ts.map +1 -0
  224. package/dist/ports/handoff-store.js +2 -0
  225. package/dist/ports/handoff-store.js.map +1 -0
  226. package/dist/ports/isolation.d.ts +43 -0
  227. package/dist/ports/isolation.d.ts.map +1 -0
  228. package/dist/ports/isolation.js +2 -0
  229. package/dist/ports/isolation.js.map +1 -0
  230. package/dist/ports/notifier.d.ts +28 -0
  231. package/dist/ports/notifier.d.ts.map +1 -0
  232. package/dist/ports/notifier.js +2 -0
  233. package/dist/ports/notifier.js.map +1 -0
  234. package/dist/ports/task-state-store.d.ts +10 -0
  235. package/dist/ports/task-state-store.d.ts.map +1 -0
  236. package/dist/ports/task-state-store.js +2 -0
  237. package/dist/ports/task-state-store.js.map +1 -0
  238. package/dist/refs.d.ts +10 -0
  239. package/dist/refs.d.ts.map +1 -0
  240. package/dist/refs.js +19 -0
  241. package/dist/refs.js.map +1 -0
  242. package/dist/resources.d.ts +55 -0
  243. package/dist/resources.d.ts.map +1 -0
  244. package/dist/resources.js +255 -0
  245. package/dist/resources.js.map +1 -0
  246. package/dist/status.d.ts +18 -0
  247. package/dist/status.d.ts.map +1 -0
  248. package/dist/status.js +60 -0
  249. package/dist/status.js.map +1 -0
  250. package/dist/storage.d.ts +18 -0
  251. package/dist/storage.d.ts.map +1 -0
  252. package/dist/storage.js +79 -0
  253. package/dist/storage.js.map +1 -0
  254. package/dist/toolchains/profile.d.ts +21 -0
  255. package/dist/toolchains/profile.d.ts.map +1 -0
  256. package/dist/toolchains/profile.js +60 -0
  257. package/dist/toolchains/profile.js.map +1 -0
  258. package/dist/toolchains/thresholds.d.ts +29 -0
  259. package/dist/toolchains/thresholds.d.ts.map +1 -0
  260. package/dist/toolchains/thresholds.js +45 -0
  261. package/dist/toolchains/thresholds.js.map +1 -0
  262. package/dist/version.d.ts +2 -0
  263. package/dist/version.d.ts.map +1 -0
  264. package/dist/version.js +3 -0
  265. package/dist/version.js.map +1 -0
  266. package/package.json +51 -3
@@ -0,0 +1,965 @@
1
+ "use strict";
2
+ /* Swarm dashboard: a follow-up view of the work running in this workspace. Work is started and
3
+ managed from the Alisio chat with /swarm:* commands; this page shows it live and offers the
4
+ decisions that are easier with visual evidence (approve, reject, answer, retry, delete).
5
+ Vanilla JavaScript, no framework. Agent and task text is always written with textContent. */
6
+ (function () {
7
+ var TOKEN = (document.querySelector('meta[name="swarm-token"]') || {}).content || "";
8
+ var POLL_MS = 2000;
9
+ var STATUSES = ["queued", "working", "merging", "waiting_approval", "clarifying", "blocked", "rejected", "done"];
10
+ var state = null;
11
+ var lastStateJson = "";
12
+ var seenAttention = null;
13
+ var paused = false;
14
+ var filters = { project: "", role: "", status: "" };
15
+ var docsContext = null;
16
+ var docsData = null;
17
+ var sideBySide = true;
18
+ var tailContext = null;
19
+ var tailSession = "";
20
+ var tailTimer = null;
21
+ var appliedHash = null;
22
+
23
+ function $(id) {
24
+ return document.getElementById(id);
25
+ }
26
+
27
+ function el(tag, props, children) {
28
+ var node = document.createElement(tag);
29
+ var key;
30
+ props = props || {};
31
+ for (key in props) {
32
+ if (!Object.prototype.hasOwnProperty.call(props, key)) continue;
33
+ var value = props[key];
34
+ if (value === undefined || value === null || value === false) continue;
35
+ if (key === "text") node.textContent = value;
36
+ else if (key === "class") node.className = value;
37
+ else if (key.slice(0, 2) === "on") node.addEventListener(key.slice(2), value);
38
+ else node.setAttribute(key, value === true ? "" : String(value));
39
+ }
40
+ (children || []).forEach(function (child) {
41
+ if (child) node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
42
+ });
43
+ return node;
44
+ }
45
+
46
+ function clear(node) {
47
+ while (node.firstChild) node.removeChild(node.firstChild);
48
+ }
49
+
50
+ function store(key, value) {
51
+ try {
52
+ if (value === undefined) return window.localStorage.getItem(key);
53
+ window.localStorage.setItem(key, value);
54
+ } catch (e) {
55
+ /* storage can be blocked: the dashboard works without it */
56
+ }
57
+ return null;
58
+ }
59
+
60
+ /* ---- API ---- */
61
+
62
+ function api(method, path, body) {
63
+ var options = { method: method, headers: { "x-swarm-token": TOKEN }, credentials: "same-origin" };
64
+ if (body !== undefined) {
65
+ options.headers["content-type"] = "application/json";
66
+ options.body = JSON.stringify(body);
67
+ }
68
+ return fetch(path, options).then(function (response) {
69
+ return response.text().then(function (text) {
70
+ var json = null;
71
+ try {
72
+ json = text ? JSON.parse(text) : null;
73
+ } catch (e) {
74
+ json = null;
75
+ }
76
+ if (!response.ok) {
77
+ throw new Error((json && json.error) || "Request failed (" + response.status + ")");
78
+ }
79
+ return json;
80
+ });
81
+ });
82
+ }
83
+
84
+ var noticeTimer = null;
85
+ function notify(message) {
86
+ var node = $("notice");
87
+ node.textContent = message;
88
+ node.hidden = false;
89
+ clearTimeout(noticeTimer);
90
+ noticeTimer = setTimeout(function () {
91
+ node.hidden = true;
92
+ }, 6000);
93
+ }
94
+
95
+ function act(promise, onError) {
96
+ return promise.then(function () { return refresh(true); }, function (error) {
97
+ if (onError) onError(error);
98
+ else notify(error.message);
99
+ return refresh(true);
100
+ });
101
+ }
102
+
103
+ function copy(text, label) {
104
+ var done = function () { notify("Copied " + label + ": " + text); };
105
+ var fallback = function () {
106
+ var area = el("textarea", { "aria-hidden": "true", style: "position:fixed;left:-999px" });
107
+ area.value = text;
108
+ document.body.appendChild(area);
109
+ area.select();
110
+ try {
111
+ document.execCommand("copy");
112
+ done();
113
+ } catch (e) {
114
+ notify("Copy is not available here. Select this: " + text);
115
+ }
116
+ area.remove();
117
+ };
118
+ if (navigator.clipboard && navigator.clipboard.writeText) {
119
+ navigator.clipboard.writeText(text).then(done, fallback);
120
+ } else {
121
+ fallback();
122
+ }
123
+ }
124
+
125
+ /* ---- formatting and commands ---- */
126
+
127
+ function age(seconds) {
128
+ if (seconds === undefined || seconds === null) return "";
129
+ if (seconds < 60) return seconds + "s";
130
+ if (seconds < 3600) return Math.round(seconds / 60) + "m";
131
+ if (seconds < 86400) return Math.round(seconds / 3600) + "h";
132
+ return Math.round(seconds / 86400) + "d";
133
+ }
134
+
135
+ function clock(iso) {
136
+ var d = new Date(iso);
137
+ if (isNaN(d.getTime())) return "";
138
+ return d.toTimeString().slice(0, 8);
139
+ }
140
+
141
+ var KIND_LABEL = {
142
+ approval: "Approval",
143
+ clarification: "Clarification",
144
+ blocked: "Blocked",
145
+ "gate-failed": "Gate failed",
146
+ decision: "Decision",
147
+ };
148
+
149
+ /* The chat command that does the same as the page, so the user can continue in Alisio. */
150
+ function commandFor(kind, project, task, status) {
151
+ var ref = project + "/" + task;
152
+ if (kind === "approval" || status === "waiting_approval") return "/swarm:approve " + ref;
153
+ if (kind === "clarification" || status === "clarifying") return "/swarm:answer " + ref + " -- ";
154
+ if (kind === "blocked" || kind === "gate-failed" || status === "blocked" || status === "rejected") {
155
+ return "/swarm:task " + project + " retry " + task;
156
+ }
157
+ if (kind === "decision") return "/swarm:budget raise ";
158
+ return "/swarm:status " + project;
159
+ }
160
+
161
+ function copyButton(command, label) {
162
+ return el("button", {
163
+ type: "button",
164
+ class: "btn small",
165
+ text: label || "Copy command",
166
+ title: "Copy: " + command,
167
+ "aria-label": "Copy the command " + command,
168
+ onclick: function () { copy(command, "command"); },
169
+ });
170
+ }
171
+
172
+ /* ---- dialogs ---- */
173
+
174
+ function openDialog(id) {
175
+ var dialog = $(id);
176
+ if (typeof dialog.showModal === "function") dialog.showModal();
177
+ else dialog.setAttribute("open", "");
178
+ return dialog;
179
+ }
180
+
181
+ function closeDialog(dialog) {
182
+ if (typeof dialog.close === "function") dialog.close();
183
+ else dialog.removeAttribute("open");
184
+ }
185
+
186
+ function wireDialogs() {
187
+ Array.prototype.forEach.call(document.querySelectorAll("dialog"), function (dialog) {
188
+ dialog.addEventListener("click", function (event) {
189
+ var target = event.target;
190
+ if (target === dialog) closeDialog(dialog);
191
+ if (target && target.hasAttribute && target.hasAttribute("data-close")) closeDialog(dialog);
192
+ });
193
+ dialog.addEventListener("close", function () {
194
+ if (dialog.id === "dlg-tail") stopTail();
195
+ if (dialog.id === "dlg-docs") { docsContext = null; docsData = null; }
196
+ });
197
+ });
198
+ }
199
+
200
+ /* ---- theme: system by default, explicit light or dark on request ---- */
201
+
202
+ var THEMES = ["system", "light", "dark"];
203
+ function applyTheme(choice) {
204
+ var root = document.documentElement;
205
+ if (choice === "light" || choice === "dark") root.setAttribute("data-theme", choice);
206
+ else root.removeAttribute("data-theme");
207
+ var button = $("theme-btn");
208
+ if (button) button.textContent = "Theme: " + choice;
209
+ }
210
+
211
+ /* ---- title, favicon, top bar ---- */
212
+
213
+ function setBadge(count) {
214
+ document.title = count > 0 ? "(" + count + ") Swarm" : "Swarm";
215
+ var svg =
216
+ count > 0
217
+ ? '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="15" fill="#c0392b"/><text x="16" y="22" font-size="18" font-family="sans-serif" font-weight="700" text-anchor="middle" fill="#fff">' +
218
+ (count > 9 ? "9+" : String(count)) +
219
+ "</text></svg>"
220
+ : '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="12" fill="#2d5a3d"/></svg>';
221
+ $("favicon").setAttribute("href", "data:image/svg+xml," + encodeURIComponent(svg));
222
+ }
223
+
224
+ function renderTopbar() {
225
+ var running = ((state && state.projects) || []).filter(function (p) { return p.running; }).length;
226
+ $("live-chip").querySelector(".dot").className = "dot " + (paused ? "idle" : running > 0 ? "live" : "idle");
227
+ $("live-text").textContent = paused
228
+ ? "paused"
229
+ : "live · " + running + (running === 1 ? " project running" : " projects running");
230
+ var needs = ((state && state.attention) || []).length;
231
+ var chip = $("decision-chip");
232
+ chip.hidden = needs === 0;
233
+ chip.textContent = needs + (needs === 1 ? " needs your decision" : " need your decision");
234
+ setBadge(needs);
235
+ var budget = $("budget-chip");
236
+ if (state && state.budget && state.budget.limit !== undefined && state.budget.limit !== null) {
237
+ budget.hidden = false;
238
+ budget.textContent =
239
+ "tokens " + state.budget.total + " / " + state.budget.limit + (state.budget.exceeded ? " (cap reached)" : "");
240
+ } else {
241
+ budget.hidden = true;
242
+ }
243
+ }
244
+
245
+ /* ---- filters ---- */
246
+
247
+ function fillSelect(id, values, current) {
248
+ var select = $(id);
249
+ var signature = values.join("|");
250
+ if (select.getAttribute("data-sig") !== signature) {
251
+ select.setAttribute("data-sig", signature);
252
+ clear(select);
253
+ select.appendChild(el("option", { value: "", text: "All" }));
254
+ values.forEach(function (value) { select.appendChild(el("option", { value: value, text: value.replace(/_/g, " ") })); });
255
+ }
256
+ select.value = values.indexOf(current) === -1 ? "" : current;
257
+ }
258
+
259
+ function renderFilters() {
260
+ var projects = ((state && state.projects) || []).filter(function (p) { return p.running; });
261
+ var roles = [];
262
+ projects.forEach(function (p) { p.roles.forEach(function (r) { if (roles.indexOf(r) === -1) roles.push(r); }); });
263
+ fillSelect("filter-project", projects.map(function (p) { return p.name; }), filters.project);
264
+ fillSelect("filter-role", roles, filters.role);
265
+ fillSelect("filter-status", STATUSES, filters.status);
266
+ filters.project = $("filter-project").value;
267
+ filters.role = $("filter-role").value;
268
+ filters.status = $("filter-status").value;
269
+ }
270
+
271
+ function projectShown(name) {
272
+ return !filters.project || filters.project === name;
273
+ }
274
+
275
+ function cardShown(card) {
276
+ if (filters.role && card.lane !== filters.role) return false;
277
+ if (filters.status && card.status !== filters.status) return false;
278
+ return true;
279
+ }
280
+
281
+ /* ---- attention ---- */
282
+
283
+ function attentionButtons(item) {
284
+ var buttons = [];
285
+ var ref = { project: item.project, task: item.task };
286
+ var has = function (name) { return item.actions.indexOf(name) !== -1; };
287
+ var button = function (label, handler, options) {
288
+ options = options || {};
289
+ return el("button", {
290
+ type: "button",
291
+ class: "btn small" + (options.primary ? " primary" : ""),
292
+ text: label,
293
+ disabled: options.disabled,
294
+ title: options.title,
295
+ onclick: handler,
296
+ });
297
+ };
298
+ var budget = item.id.indexOf(":budget") !== -1;
299
+ if (item.kind === "approval") {
300
+ buttons.push(button("Documents", function () { openDocuments(ref); }));
301
+ buttons.push(
302
+ button("Approve", function () { act(api("POST", "/api/approvals/" + encodeURIComponent(item.id) + "/approve", {})); }, {
303
+ primary: true,
304
+ disabled: !has("approve"),
305
+ title: has("approve") ? "" : "Resolve or clear the document comments first",
306
+ }),
307
+ );
308
+ buttons.push(button("Reject", function () { openReject(item); }));
309
+ } else if (item.kind === "clarification") {
310
+ buttons.push(button("Answer", function () { openAnswer(item); }, { primary: true }));
311
+ } else if (!budget) {
312
+ if (has("retry")) buttons.push(button("Retry", function () { act(api("POST", "/api/tasks/retry", ref)); }, { primary: true }));
313
+ if (has("delete")) {
314
+ buttons.push(
315
+ button("Delete", function () {
316
+ if (window.confirm("Archive and delete " + item.project + "/" + item.task + "?")) {
317
+ act(api("POST", "/api/tasks/delete", ref));
318
+ }
319
+ }),
320
+ );
321
+ }
322
+ }
323
+ buttons.push(copyButton(commandFor(item.kind, item.project, item.task, "")));
324
+ return buttons;
325
+ }
326
+
327
+ function renderAttention() {
328
+ var all = (state && state.attention) || [];
329
+ var items = all.filter(function (item) { return projectShown(item.project); });
330
+ var section = $("attention");
331
+ var list = $("attention-list");
332
+ clear(list);
333
+ section.hidden = items.length === 0;
334
+ items.forEach(function (item) {
335
+ var budget = item.id.indexOf(":budget") !== -1;
336
+ var children = [
337
+ el("span", { class: "tag", "data-kind": item.kind, text: KIND_LABEL[item.kind] || item.kind }),
338
+ budget
339
+ ? el("strong", { text: item.project + " token budget" })
340
+ : el("a", { class: "linkish", href: "#" + item.project + "/" + item.task, text: item.project + "/" + item.task }),
341
+ ].concat(attentionButtons(item));
342
+ if (item.detail) children.push(el("p", { class: "detail", text: item.detail }));
343
+ if (budget) children.push(el("p", { class: "detail", text: "Raise the cap from the Alisio chat with /swarm:budget raise <tokens>." }));
344
+ list.appendChild(el("li", {}, children));
345
+ });
346
+ var ids = all.map(function (item) { return item.id; });
347
+ if (seenAttention !== null && $("chime").checked) {
348
+ var fresh = ids.filter(function (id) { return seenAttention.indexOf(id) === -1; });
349
+ if (fresh.length > 0) chime();
350
+ }
351
+ seenAttention = ids;
352
+ }
353
+
354
+ function chime() {
355
+ try {
356
+ var Context = window.AudioContext || window.webkitAudioContext;
357
+ if (!Context) return;
358
+ var context = new Context();
359
+ var osc = context.createOscillator();
360
+ var gain = context.createGain();
361
+ osc.frequency.value = 880;
362
+ gain.gain.value = 0.05;
363
+ osc.connect(gain);
364
+ gain.connect(context.destination);
365
+ osc.start();
366
+ osc.stop(context.currentTime + 0.18);
367
+ osc.onended = function () { context.close(); };
368
+ } catch (e) {
369
+ /* a missing audio device must never break the dashboard */
370
+ }
371
+ }
372
+
373
+ /* ---- board ---- */
374
+
375
+ function renderCard(project, card) {
376
+ var label = card.merging ? "Merging" : card.status.replace(/_/g, " ");
377
+ var node = el(
378
+ "article",
379
+ { class: "card" + (card.merging ? " merging" : ""), "data-card": project.name + "/" + card.name, "data-status": card.status },
380
+ [
381
+ el("div", { class: "card-head" }, [
382
+ el("a", { class: "card-name", href: "#" + project.name + "/" + card.name, text: card.name }),
383
+ el("span", {
384
+ class: "audits",
385
+ text: "✓ " + card.auditCount,
386
+ title: card.auditCount + " audit round(s)",
387
+ "aria-label": card.auditCount + " audit rounds",
388
+ }),
389
+ ]),
390
+ el("p", { class: "snippet", text: card.snippet }),
391
+ el("div", { class: "card-foot" }, [
392
+ el("span", { class: "badge", text: label }),
393
+ copyButton(commandFor("", project.name, card.name, card.status), "Copy"),
394
+ ]),
395
+ ],
396
+ );
397
+ return node;
398
+ }
399
+
400
+ function emptyBoard(board) {
401
+ board.appendChild(
402
+ el("div", { class: "empty-state" }, [
403
+ el("p", { text: "No project is running in this workspace yet." }),
404
+ el("p", { text: "This page follows work live. Start it from the Alisio chat:" }),
405
+ el("pre", { class: "plain", text: "/swarm:project new <name> -- <mission>\n/swarm:task <project> new -- <task text>" }),
406
+ el("p", { class: "hint", text: "Closed projects are listed by /swarm:project list; open one with /swarm:project open <name>." }),
407
+ ]),
408
+ );
409
+ }
410
+
411
+ function renderBoard() {
412
+ var board = $("board");
413
+ var scroll = board.scrollTop;
414
+ clear(board);
415
+ if (!state) return;
416
+ if (!state.initialised) {
417
+ board.appendChild(el("p", { class: "empty-state", text: "The swarm forge is not initialised. Run /swarm:init in your Alisio session." }));
418
+ return;
419
+ }
420
+ var running = state.projects.filter(function (p) { return p.running; });
421
+ if (running.length === 0) return emptyBoard(board);
422
+ var shown = running.filter(function (p) { return projectShown(p.name); });
423
+ if (shown.length === 0) {
424
+ board.appendChild(el("p", { class: "empty-state", text: "No running project matches the filter." }));
425
+ return;
426
+ }
427
+ shown.forEach(function (project) {
428
+ var columns = project.columns
429
+ .filter(function (column) { return !filters.role || column === filters.role; })
430
+ .map(function (column) {
431
+ var cards = project.tasks.filter(function (task) {
432
+ if (!cardShown(task)) return false;
433
+ return task.status === "done" ? column === "done" : task.lane === column;
434
+ });
435
+ return el("section", { class: "column", "aria-label": column + " column" }, [
436
+ el("h4", { text: column.toUpperCase() }),
437
+ el("div", { class: "dropzone" }, cards.map(function (card) { return renderCard(project, card); })),
438
+ ]);
439
+ });
440
+ board.appendChild(
441
+ el("section", { class: "band", "aria-label": project.name, "data-band": project.name }, [
442
+ el("div", { class: "band-head" }, [
443
+ el("h3", {}, [
444
+ el("a", { class: "linkish", href: "#" + project.name, text: project.name }),
445
+ " ",
446
+ el("span", { class: "hint", text: project.pack }),
447
+ ]),
448
+ el("button", { type: "button", class: "btn small", text: "Mission", onclick: function () { showMission(project.name); } }),
449
+ ]),
450
+ el("div", { class: "columns" }, columns),
451
+ ]),
452
+ );
453
+ });
454
+ board.scrollTop = scroll;
455
+ }
456
+
457
+ /* ---- work queue and activity ---- */
458
+
459
+ function meter(value) {
460
+ var bars = [];
461
+ for (var i = 0; i < 6; i += 1) bars.push(el("i", { class: i < value ? "on" : "" }));
462
+ return el("span", { class: "meter", role: "img", "aria-label": "activity " + value + " of 6" }, bars);
463
+ }
464
+
465
+ function renderQueue() {
466
+ var queue = $("queue");
467
+ var scroll = queue.scrollTop;
468
+ clear(queue);
469
+ var running = ((state && state.projects) || []).filter(function (p) { return p.running && projectShown(p.name); });
470
+ if (running.length === 0) {
471
+ queue.appendChild(el("p", { class: "empty-state", text: "Nothing is running." }));
472
+ return;
473
+ }
474
+ running.forEach(function (project) {
475
+ var rows = project.queue
476
+ .filter(function (row) { return !filters.role || row.role === filters.role; })
477
+ .map(function (row) {
478
+ var side = [
479
+ el("button", {
480
+ type: "button",
481
+ class: "linkish",
482
+ text: row.role,
483
+ title: "Show the " + row.role + " activity",
484
+ onclick: function () { openTail(project.name, row.role); },
485
+ }),
486
+ el("div", {}, [meter(row.activity)]),
487
+ ];
488
+ if (row.sessionId) {
489
+ side.push(
490
+ el("div", { class: "session" }, [
491
+ el("code", { text: row.sessionId, title: "Alisio session id" }),
492
+ el("button", {
493
+ type: "button",
494
+ class: "btn small",
495
+ text: "Copy",
496
+ "aria-label": "Copy the Alisio session id of " + row.role,
497
+ onclick: function () { copy(row.sessionId, "session id"); },
498
+ }),
499
+ ]),
500
+ );
501
+ }
502
+ return el("tr", {}, [
503
+ el("td", {}, row.task ? [el("a", { href: "#" + project.name + "/" + row.task, text: row.task })] : []),
504
+ el("td", {}, [
505
+ el("div", { class: "role-cell" }, [
506
+ el("span", { class: "dot " + row.state, title: row.state, "aria-hidden": "true" }),
507
+ el("span", { class: "sr-only", text: row.state }),
508
+ el("div", {}, side),
509
+ ]),
510
+ ]),
511
+ el("td", { class: "age", text: age(row.ageSeconds) }),
512
+ ]);
513
+ });
514
+ queue.appendChild(el("h3", { text: project.name }));
515
+ queue.appendChild(
516
+ el("table", {}, [
517
+ el("thead", {}, [el("tr", {}, [el("th", { scope: "col", text: "Task" }), el("th", { scope: "col", text: "Role" }), el("th", { scope: "col", text: "Age" })])]),
518
+ el("tbody", {}, rows),
519
+ ]),
520
+ );
521
+ });
522
+ queue.scrollTop = scroll;
523
+ }
524
+
525
+ function atBottom(node) {
526
+ return node.scrollHeight - node.scrollTop - node.clientHeight < 24;
527
+ }
528
+
529
+ function renderActivity() {
530
+ var log = $("activity-log");
531
+ var stick = atBottom(log);
532
+ clear(log);
533
+ var entries = ((state && state.activity) || []).filter(function (entry) {
534
+ return projectShown(entry.project) && (!filters.role || entry.role === filters.role);
535
+ });
536
+ if (entries.length === 0) log.appendChild(el("li", { class: "hint", text: "No activity yet." }));
537
+ entries.forEach(function (entry) {
538
+ log.appendChild(
539
+ el("li", {}, [
540
+ el("span", { class: "time", text: clock(entry.at) }),
541
+ el("span", { class: "kind", text: entry.kind }),
542
+ el("a", { href: "#" + entry.project + "/" + entry.task, text: entry.project + "/" + entry.task }),
543
+ " ",
544
+ el("span", { class: "hint", text: entry.role }),
545
+ el("div", { text: entry.message }),
546
+ ]),
547
+ );
548
+ });
549
+ if (stick) log.scrollTop = log.scrollHeight;
550
+ }
551
+
552
+ /* ---- deep links ---- */
553
+
554
+ function applyHash(force) {
555
+ var hash = decodeURIComponent((window.location.hash || "").slice(1));
556
+ if (!hash || (hash === appliedHash && !force)) return;
557
+ var node = document.querySelector('[data-card="' + hash + '"]') || document.querySelector('[data-band="' + hash + '"]');
558
+ if (!node) return;
559
+ appliedHash = hash;
560
+ Array.prototype.forEach.call(document.querySelectorAll(".linked"), function (old) { old.classList.remove("linked"); });
561
+ node.classList.add("linked");
562
+ node.scrollIntoView({ block: "center", inline: "nearest" });
563
+ node.setAttribute("tabindex", "-1");
564
+ node.focus({ preventScroll: true });
565
+ }
566
+
567
+ /* ---- decision dialogs ---- */
568
+
569
+ var rejectItem = null;
570
+ function openReject(item) {
571
+ rejectItem = item;
572
+ $("form-reject").reset();
573
+ $("reject-error").textContent = "";
574
+ $("reject-target").textContent = item.project + "/" + item.task;
575
+ openDialog("dlg-reject");
576
+ }
577
+
578
+ function submitReject(event) {
579
+ event.preventDefault();
580
+ var action = (document.querySelector('input[name="reject-action"]:checked') || {}).value;
581
+ var body = { action: action };
582
+ var comments = $("reject-comments").value.trim();
583
+ if (comments) body.comments = comments;
584
+ api("POST", "/api/approvals/" + encodeURIComponent(rejectItem.id || "approval:" + rejectItem.project + ":" + rejectItem.task) + "/reject", body).then(
585
+ function () {
586
+ closeDialog($("dlg-reject"));
587
+ closeDialog($("dlg-docs"));
588
+ return refresh(true);
589
+ },
590
+ function (failure) { $("reject-error").textContent = failure.message; },
591
+ );
592
+ }
593
+
594
+ var promptHandler = null;
595
+ function openAnswer(item) {
596
+ $("dlg-prompt-title").textContent = "Answer";
597
+ $("prompt-detail").textContent = item.detail || "The role asked for clarification.";
598
+ $("prompt-label").textContent = "Your answer";
599
+ $("prompt-ok").textContent = "Send answer";
600
+ $("prompt-text").value = "";
601
+ $("prompt-error").textContent = "";
602
+ promptHandler = function (text) {
603
+ return api("POST", "/api/clarifications/" + encodeURIComponent(item.id) + "/answer", { text: text });
604
+ };
605
+ openDialog("dlg-prompt");
606
+ $("prompt-text").focus();
607
+ }
608
+
609
+ function submitPrompt(event) {
610
+ event.preventDefault();
611
+ var text = $("prompt-text").value.trim();
612
+ if (!text) {
613
+ $("prompt-error").textContent = "Enter an answer.";
614
+ return;
615
+ }
616
+ promptHandler(text).then(
617
+ function () {
618
+ closeDialog($("dlg-prompt"));
619
+ return refresh(true);
620
+ },
621
+ function (failure) { $("prompt-error").textContent = failure.message; },
622
+ );
623
+ }
624
+
625
+ function showMission(project) {
626
+ $("dlg-info-title").textContent = project + ": mission";
627
+ $("info-text").textContent = "Loading...";
628
+ openDialog("dlg-info");
629
+ api("GET", "/api/mission?project=" + encodeURIComponent(project)).then(
630
+ function (data) { $("info-text").textContent = data.mission || "(no mission recorded)"; },
631
+ function (error) { $("info-text").textContent = error.message; },
632
+ );
633
+ }
634
+
635
+ /* ---- documents and diff ---- */
636
+
637
+ function diffRows(diff) {
638
+ var rows = [];
639
+ var left = [];
640
+ var right = [];
641
+ var flush = function () {
642
+ var n = Math.max(left.length, right.length);
643
+ for (var i = 0; i < n; i += 1) rows.push({ l: left[i], r: right[i] });
644
+ left = [];
645
+ right = [];
646
+ };
647
+ diff.split("\n").forEach(function (line) {
648
+ if (/^(diff |index |--- |\+\+\+ |@@|new file|deleted file|similarity|rename)/.test(line)) {
649
+ flush();
650
+ rows.push({ head: line });
651
+ } else if (line.charAt(0) === "+") right.push(line.slice(1));
652
+ else if (line.charAt(0) === "-") left.push(line.slice(1));
653
+ else {
654
+ flush();
655
+ var text = line.charAt(0) === " " ? line.slice(1) : line;
656
+ rows.push({ c: text });
657
+ }
658
+ });
659
+ flush();
660
+ return rows;
661
+ }
662
+
663
+ function renderDiff(diff) {
664
+ var host = $("docs-diff");
665
+ clear(host);
666
+ if (!diff) {
667
+ host.appendChild(el("p", { class: "hint", text: "No changes between the base and the held commit." }));
668
+ return;
669
+ }
670
+ if (!sideBySide) {
671
+ var pre = el("pre", { class: "plain" });
672
+ diff.split("\n").forEach(function (line) {
673
+ var kind = line.charAt(0) === "+" && line.slice(0, 3) !== "+++" ? "add" : line.charAt(0) === "-" && line.slice(0, 3) !== "---" ? "del" : /^(diff|@@|\+\+\+|---)/.test(line) ? "hunk" : "";
674
+ pre.appendChild(el("span", { class: kind, text: line + "\n" }));
675
+ });
676
+ host.appendChild(pre);
677
+ return;
678
+ }
679
+ var rows = diffRows(diff).map(function (row) {
680
+ if (row.head !== undefined) return el("tr", { class: "hunk-row" }, [el("td", { colspan: "2", text: row.head })]);
681
+ if (row.c !== undefined) return el("tr", {}, [el("td", { text: row.c }), el("td", { text: row.c })]);
682
+ return el("tr", {}, [
683
+ el("td", { class: row.l !== undefined ? "del" : "blank", text: row.l !== undefined ? "- " + row.l : "" }),
684
+ el("td", { class: row.r !== undefined ? "add" : "blank", text: row.r !== undefined ? "+ " + row.r : "" }),
685
+ ]);
686
+ });
687
+ host.appendChild(
688
+ el("table", { class: "sbs" }, [
689
+ el("thead", {}, [el("tr", {}, [el("th", { scope: "col", text: "Before (base)" }), el("th", { scope: "col", text: "After (held commit)" })])]),
690
+ el("tbody", {}, rows),
691
+ ]),
692
+ );
693
+ }
694
+
695
+ function approvalPath(ref, action) {
696
+ return "/api/approvals/" + encodeURIComponent("approval:" + ref.project + ":" + ref.task) + "/" + action;
697
+ }
698
+
699
+ function renderDocs(data) {
700
+ docsData = data;
701
+ var ref = docsContext;
702
+ $("docs-meta").textContent =
703
+ data.project + "/" + data.task + " · held commit " + data.commit + " · gate role " + data.role + (data.base ? " · base " + data.base : "");
704
+ var list = $("docs-list");
705
+ clear(list);
706
+ if (data.docs.length === 0) list.appendChild(el("p", { class: "hint", text: "No task documents were found in this change." }));
707
+ data.docs.forEach(function (doc) {
708
+ var comments = doc.comments.map(function (comment) { return el("li", { text: comment.text }); });
709
+ var input = el("textarea", { rows: "2", maxlength: "4000", "aria-label": "Comment on " + doc.path });
710
+ var add = el("button", {
711
+ type: "button",
712
+ class: "btn small",
713
+ text: "Add comment",
714
+ onclick: function () {
715
+ var text = input.value.trim();
716
+ if (!text) return;
717
+ api("POST", approvalPath(ref, "comments"), { doc: doc.path, text: text }).then(
718
+ function () { return reloadDocs(); },
719
+ function (error) { $("docs-error").textContent = error.message; },
720
+ );
721
+ },
722
+ });
723
+ list.appendChild(
724
+ el("section", { class: "doc" }, [
725
+ el("h3", { text: doc.path + (doc.truncated ? " (truncated)" : "") }),
726
+ el("pre", { class: "doc-text", tabindex: "0", text: doc.text }),
727
+ comments.length ? el("ul", { class: "comments", "aria-label": "Comments on " + doc.path }, comments) : null,
728
+ el("div", { class: "comment-form" }, [input, add]),
729
+ ]),
730
+ );
731
+ });
732
+ renderDiff(data.diff + (data.diffTruncated ? "\n(diff truncated)" : ""));
733
+ $("docs-approve").disabled = !data.approvable;
734
+ $("docs-approve").title = data.approvable ? "" : "Resolve or clear the comments first";
735
+ $("docs-clear").disabled = data.comments.length === 0;
736
+ }
737
+
738
+ function reloadDocs() {
739
+ var ref = docsContext;
740
+ if (!ref) return Promise.resolve();
741
+ return api("GET", "/api/doc?project=" + encodeURIComponent(ref.project) + "&task=" + encodeURIComponent(ref.task)).then(
742
+ renderDocs,
743
+ function (error) { $("docs-error").textContent = error.message; },
744
+ );
745
+ }
746
+
747
+ function openDocuments(ref) {
748
+ docsContext = ref;
749
+ $("docs-error").textContent = "";
750
+ $("docs-meta").textContent = "Loading...";
751
+ clear($("docs-list"));
752
+ clear($("docs-diff"));
753
+ openDialog("dlg-docs");
754
+ reloadDocs();
755
+ }
756
+
757
+ /* ---- agent tail ---- */
758
+
759
+ function stopTail() {
760
+ clearInterval(tailTimer);
761
+ tailTimer = null;
762
+ tailContext = null;
763
+ }
764
+
765
+ function loadTail() {
766
+ var ctx = tailContext;
767
+ if (!ctx || paused) return;
768
+ api("GET", "/api/agents/" + encodeURIComponent(ctx.role) + "/tail?project=" + encodeURIComponent(ctx.project)).then(
769
+ function (data) {
770
+ tailSession = data.sessionId || "";
771
+ $("tail-meta").textContent = "state: " + data.state + (data.sessionId ? " · session " + data.sessionId : " · no session yet");
772
+ $("tail-copy").hidden = !data.sessionId;
773
+ var log = $("tail-log");
774
+ var stick = atBottom(log);
775
+ clear(log);
776
+ if (data.tail.length === 0) log.appendChild(el("li", { text: "No recorded activity yet." }));
777
+ data.tail.forEach(function (entry) {
778
+ log.appendChild(
779
+ el("li", {}, [el("span", { class: "kind", text: entry.kind === "prompt" ? "Prompt" : "Reply" }), el("span", { class: "hint", text: entry.at }), el("div", { text: entry.text })]),
780
+ );
781
+ });
782
+ if (stick) log.scrollTop = log.scrollHeight;
783
+ },
784
+ function (error) { $("tail-meta").textContent = error.message; },
785
+ );
786
+ }
787
+
788
+ function openTail(project, role) {
789
+ tailContext = { project: project, role: role };
790
+ tailSession = "";
791
+ $("dlg-tail-title").textContent = project + " / " + role + " (Alisio session)";
792
+ $("tail-meta").textContent = "Loading...";
793
+ $("tail-copy").hidden = true;
794
+ clear($("tail-log"));
795
+ openDialog("dlg-tail");
796
+ loadTail();
797
+ clearInterval(tailTimer);
798
+ tailTimer = setInterval(loadTail, POLL_MS);
799
+ }
800
+
801
+ /* ---- splitters ---- */
802
+
803
+ function drag(handle, onMove) {
804
+ handle.addEventListener("pointerdown", function (event) {
805
+ handle.setPointerCapture(event.pointerId);
806
+ handle.classList.add("dragging");
807
+ var move = function (e) { onMove(e); };
808
+ var up = function () {
809
+ handle.classList.remove("dragging");
810
+ handle.removeEventListener("pointermove", move);
811
+ handle.removeEventListener("pointerup", up);
812
+ };
813
+ handle.addEventListener("pointermove", move);
814
+ handle.addEventListener("pointerup", up);
815
+ });
816
+ }
817
+
818
+ function clamp(value, min, max) {
819
+ return Math.min(max, Math.max(min, value));
820
+ }
821
+
822
+ function setRail(width) {
823
+ var value = clamp(width, 240, Math.max(260, window.innerWidth - 320));
824
+ document.documentElement.style.setProperty("--swarm-rail-width", value + "px");
825
+ store("swarm.rail", String(value));
826
+ }
827
+
828
+ function setQueue(percent) {
829
+ var value = clamp(percent, 15, 85);
830
+ document.documentElement.style.setProperty("--swarm-queue-height", value + "%");
831
+ store("swarm.queue", String(value));
832
+ }
833
+
834
+ function wireSplitters() {
835
+ var savedRail = Number(store("swarm.rail"));
836
+ var savedQueue = Number(store("swarm.queue"));
837
+ if (savedRail) setRail(savedRail);
838
+ if (savedQueue) setQueue(savedQueue);
839
+ drag($("split-v"), function (event) { setRail(window.innerWidth - event.clientX); });
840
+ drag($("split-h"), function (event) {
841
+ var rect = $("rail").getBoundingClientRect();
842
+ setQueue(((event.clientY - rect.top) / rect.height) * 100);
843
+ });
844
+ $("split-v").addEventListener("keydown", function (event) {
845
+ var current = $("rail").getBoundingClientRect().width;
846
+ if (event.key === "ArrowLeft") { setRail(current + 20); event.preventDefault(); }
847
+ if (event.key === "ArrowRight") { setRail(current - 20); event.preventDefault(); }
848
+ });
849
+ $("split-h").addEventListener("keydown", function (event) {
850
+ var rail = $("rail").getBoundingClientRect();
851
+ var current = ($("queue-pane").getBoundingClientRect().height / rail.height) * 100;
852
+ if (event.key === "ArrowUp") { setQueue(current - 4); event.preventDefault(); }
853
+ if (event.key === "ArrowDown") { setQueue(current + 4); event.preventDefault(); }
854
+ });
855
+ }
856
+
857
+ /* ---- polling ---- */
858
+
859
+ var timer = null;
860
+ var failures = 0;
861
+
862
+ function render() {
863
+ renderFilters();
864
+ renderTopbar();
865
+ renderAttention();
866
+ renderBoard();
867
+ renderQueue();
868
+ renderActivity();
869
+ applyHash(false);
870
+ }
871
+
872
+ function refresh(force) {
873
+ if (paused && force !== true) return Promise.resolve();
874
+ return api("GET", "/api/state").then(
875
+ function (next) {
876
+ failures = 0;
877
+ var json = JSON.stringify(next);
878
+ if (json === lastStateJson) return;
879
+ lastStateJson = json;
880
+ state = next;
881
+ render();
882
+ },
883
+ function (error) {
884
+ failures += 1;
885
+ if (failures >= 2) {
886
+ $("live-text").textContent = "offline";
887
+ $("live-chip").querySelector(".dot").className = "dot none";
888
+ if (failures === 2) notify("Lost contact with the dashboard server: " + error.message);
889
+ }
890
+ },
891
+ );
892
+ }
893
+
894
+ function setPaused(value) {
895
+ paused = value;
896
+ $("pause-btn").setAttribute("aria-pressed", String(paused));
897
+ $("pause-btn").textContent = paused ? "Resume refresh" : "Pause refresh";
898
+ if (state) renderTopbar();
899
+ if (!paused) refresh(true);
900
+ }
901
+
902
+ function init() {
903
+ var theme = store("swarm.theme");
904
+ applyTheme(THEMES.indexOf(theme) === -1 ? "system" : theme);
905
+ $("theme-btn").addEventListener("click", function () {
906
+ var current = store("swarm.theme");
907
+ var next = THEMES[(Math.max(0, THEMES.indexOf(current)) + 1) % THEMES.length];
908
+ store("swarm.theme", next);
909
+ applyTheme(next);
910
+ });
911
+ wireDialogs();
912
+ wireSplitters();
913
+ $("chime").checked = store("swarm.chime") === "1";
914
+ $("chime").addEventListener("change", function () { store("swarm.chime", $("chime").checked ? "1" : "0"); });
915
+ $("pause-btn").addEventListener("click", function () { setPaused(!paused); });
916
+ ["project", "role", "status"].forEach(function (name) {
917
+ $("filter-" + name).addEventListener("change", function () {
918
+ filters[name] = $("filter-" + name).value;
919
+ if (state) render();
920
+ });
921
+ });
922
+ $("filter-clear").addEventListener("click", function () {
923
+ filters = { project: "", role: "", status: "" };
924
+ if (state) render();
925
+ });
926
+ $("diff-mode").addEventListener("click", function () {
927
+ sideBySide = !sideBySide;
928
+ $("diff-mode").setAttribute("aria-pressed", String(sideBySide));
929
+ $("diff-mode").textContent = sideBySide ? "Side by side" : "Unified";
930
+ if (docsData) renderDiff(docsData.diff + (docsData.diffTruncated ? "\n(diff truncated)" : ""));
931
+ });
932
+ window.addEventListener("hashchange", function () { applyHash(true); });
933
+ $("form-reject").addEventListener("submit", submitReject);
934
+ $("form-prompt").addEventListener("submit", submitPrompt);
935
+ $("tail-copy").addEventListener("click", function () { if (tailSession) copy(tailSession, "session id"); });
936
+ $("docs-copy").addEventListener("click", function () {
937
+ var ref = docsContext;
938
+ if (ref) copy("/swarm:approve " + ref.project + "/" + ref.task, "command");
939
+ });
940
+ $("docs-approve").addEventListener("click", function () {
941
+ var ref = docsContext;
942
+ if (!ref) return;
943
+ api("POST", approvalPath(ref, "approve"), {}).then(
944
+ function () { closeDialog($("dlg-docs")); return refresh(true); },
945
+ function (error) { $("docs-error").textContent = error.message; },
946
+ );
947
+ });
948
+ $("docs-reject").addEventListener("click", function () {
949
+ var ref = docsContext;
950
+ if (ref) openReject({ project: ref.project, task: ref.task });
951
+ });
952
+ $("docs-clear").addEventListener("click", function () {
953
+ var ref = docsContext;
954
+ if (!ref) return;
955
+ api("POST", approvalPath(ref, "comments"), { clear: true }).then(
956
+ function () { return reloadDocs().then(function () { return refresh(true); }); },
957
+ function (error) { $("docs-error").textContent = error.message; },
958
+ );
959
+ });
960
+ refresh(true);
961
+ timer = setInterval(refresh, POLL_MS);
962
+ }
963
+
964
+ init();
965
+ })();