@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.
- package/.agents/agents/swarm-architect.md +20 -0
- package/.agents/agents/swarm-cleaner.md +20 -0
- package/.agents/agents/swarm-coder.md +20 -0
- package/.agents/agents/swarm-hardener.md +20 -0
- package/.agents/agents/swarm-lieutenant.md +20 -0
- package/.agents/agents/swarm-qa.md +20 -0
- package/.agents/agents/swarm-refactorer.md +20 -0
- package/.agents/agents/swarm-specifier.md +20 -0
- package/.agents/skills/swarm-architecture-rules/SKILL.md +43 -0
- package/.agents/skills/swarm-clarification/SKILL.md +42 -0
- package/.agents/skills/swarm-cleaner-metrics/SKILL.md +44 -0
- package/.agents/skills/swarm-engineering-constitution/SKILL.md +44 -0
- package/.agents/skills/swarm-gherkin-spec/SKILL.md +43 -0
- package/.agents/skills/swarm-handoff-protocol/SKILL.md +45 -0
- package/.agents/skills/swarm-lieutenant/SKILL.md +41 -0
- package/.agents/skills/swarm-mutation-hardening/SKILL.md +43 -0
- package/.agents/skills/swarm-qa-acceptance/SKILL.md +42 -0
- package/.agents/skills/swarm-tdd-slice/SKILL.md +44 -0
- package/.agents/skills/swarm-toolchain-profile/SKILL.md +41 -0
- package/LICENSE +21 -0
- package/README.es.md +337 -0
- package/README.md +322 -2
- package/assets/approval-flow.svg +1 -0
- package/assets/architecture.svg +1 -0
- package/assets/audit-states.svg +1 -0
- package/assets/dashboard.css +964 -0
- package/assets/dashboard.html +123 -0
- package/assets/dashboard.js +965 -0
- package/assets/dashboard.svg +1 -0
- package/assets/handoff-sequence.svg +1 -0
- package/assets/pack-pipelines.svg +1 -0
- package/assets/packs/four-pack/pack.json +18 -0
- package/assets/packs/six-pack/pack.json +22 -0
- package/assets/packs/two-pack/pack.json +14 -0
- package/assets/screenshots/dashboard-board-dark.png +0 -0
- package/assets/screenshots/dashboard-board-light.png +0 -0
- package/assets/screenshots/dashboard-documents-diff.png +0 -0
- package/assets/screenshots/dashboard-empty-state.png +0 -0
- package/assets/screenshots/dashboard-phone.png +0 -0
- package/assets/screenshots/dashboard-reject-dialog.png +0 -0
- package/assets/screenshots/dashboard-transcript-tail.png +0 -0
- package/assets/screenshots/dashboard-work-queue.png +0 -0
- package/assets/task-states.svg +1 -0
- package/assets/toolchains/node-ts.json +61 -0
- package/cover.svg +46 -0
- package/dist/adapters/child-session-runner.d.ts +36 -0
- package/dist/adapters/child-session-runner.d.ts.map +1 -0
- package/dist/adapters/child-session-runner.js +156 -0
- package/dist/adapters/child-session-runner.js.map +1 -0
- package/dist/adapters/fs-board-store.d.ts +11 -0
- package/dist/adapters/fs-board-store.d.ts.map +1 -0
- package/dist/adapters/fs-board-store.js +27 -0
- package/dist/adapters/fs-board-store.js.map +1 -0
- package/dist/adapters/fs-handoff-store.d.ts +36 -0
- package/dist/adapters/fs-handoff-store.d.ts.map +1 -0
- package/dist/adapters/fs-handoff-store.js +254 -0
- package/dist/adapters/fs-handoff-store.js.map +1 -0
- package/dist/adapters/fs-task-state-store.d.ts +16 -0
- package/dist/adapters/fs-task-state-store.d.ts.map +1 -0
- package/dist/adapters/fs-task-state-store.js +66 -0
- package/dist/adapters/fs-task-state-store.js.map +1 -0
- package/dist/adapters/git-worktree.d.ts +31 -0
- package/dist/adapters/git-worktree.d.ts.map +1 -0
- package/dist/adapters/git-worktree.js +209 -0
- package/dist/adapters/git-worktree.js.map +1 -0
- package/dist/adapters/process-exec.d.ts +36 -0
- package/dist/adapters/process-exec.d.ts.map +1 -0
- package/dist/adapters/process-exec.js +138 -0
- package/dist/adapters/process-exec.js.map +1 -0
- package/dist/adapters/process-gate-runner.d.ts +39 -0
- package/dist/adapters/process-gate-runner.d.ts.map +1 -0
- package/dist/adapters/process-gate-runner.js +242 -0
- package/dist/adapters/process-gate-runner.js.map +1 -0
- package/dist/adapters/unavailable-runner.d.ts +4 -0
- package/dist/adapters/unavailable-runner.d.ts.map +1 -0
- package/dist/adapters/unavailable-runner.js +12 -0
- package/dist/adapters/unavailable-runner.js.map +1 -0
- package/dist/app/audit.d.ts +23 -0
- package/dist/app/audit.d.ts.map +1 -0
- package/dist/app/audit.js +9 -0
- package/dist/app/audit.js.map +1 -0
- package/dist/app/budget.d.ts +29 -0
- package/dist/app/budget.d.ts.map +1 -0
- package/dist/app/budget.js +78 -0
- package/dist/app/budget.js.map +1 -0
- package/dist/app/chat-store.d.ts +18 -0
- package/dist/app/chat-store.d.ts.map +1 -0
- package/dist/app/chat-store.js +48 -0
- package/dist/app/chat-store.js.map +1 -0
- package/dist/app/documents.d.ts +33 -0
- package/dist/app/documents.d.ts.map +1 -0
- package/dist/app/documents.js +63 -0
- package/dist/app/documents.js.map +1 -0
- package/dist/app/forge.d.ts +106 -0
- package/dist/app/forge.d.ts.map +1 -0
- package/dist/app/forge.js +420 -0
- package/dist/app/forge.js.map +1 -0
- package/dist/app/operator.d.ts +46 -0
- package/dist/app/operator.d.ts.map +1 -0
- package/dist/app/operator.js +168 -0
- package/dist/app/operator.js.map +1 -0
- package/dist/app/pidfile.d.ts +18 -0
- package/dist/app/pidfile.d.ts.map +1 -0
- package/dist/app/pidfile.js +56 -0
- package/dist/app/pidfile.js.map +1 -0
- package/dist/app/project.d.ts +93 -0
- package/dist/app/project.d.ts.map +1 -0
- package/dist/app/project.js +291 -0
- package/dist/app/project.js.map +1 -0
- package/dist/app/prompts.d.ts +48 -0
- package/dist/app/prompts.d.ts.map +1 -0
- package/dist/app/prompts.js +88 -0
- package/dist/app/prompts.js.map +1 -0
- package/dist/app/pump.d.ts +144 -0
- package/dist/app/pump.d.ts.map +1 -0
- package/dist/app/pump.js +707 -0
- package/dist/app/pump.js.map +1 -0
- package/dist/app/services.d.ts +112 -0
- package/dist/app/services.d.ts.map +1 -0
- package/dist/app/services.js +323 -0
- package/dist/app/services.js.map +1 -0
- package/dist/app/state.d.ts +70 -0
- package/dist/app/state.d.ts.map +1 -0
- package/dist/app/state.js +96 -0
- package/dist/app/state.js.map +1 -0
- package/dist/args.d.ts +10 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +33 -0
- package/dist/args.js.map +1 -0
- package/dist/coordinator.d.ts +80 -0
- package/dist/coordinator.d.ts.map +1 -0
- package/dist/coordinator.js +481 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/dashboard/api.d.ts +28 -0
- package/dist/dashboard/api.d.ts.map +1 -0
- package/dist/dashboard/api.js +178 -0
- package/dist/dashboard/api.js.map +1 -0
- package/dist/dashboard/auth.d.ts +13 -0
- package/dist/dashboard/auth.d.ts.map +1 -0
- package/dist/dashboard/auth.js +36 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/open.d.ts +7 -0
- package/dist/dashboard/open.d.ts.map +1 -0
- package/dist/dashboard/open.js +17 -0
- package/dist/dashboard/open.js.map +1 -0
- package/dist/dashboard/server.d.ts +28 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +176 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +108 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain/attention.d.ts +20 -0
- package/dist/domain/attention.d.ts.map +1 -0
- package/dist/domain/attention.js +41 -0
- package/dist/domain/attention.js.map +1 -0
- package/dist/domain/envelope.d.ts +37 -0
- package/dist/domain/envelope.d.ts.map +1 -0
- package/dist/domain/envelope.js +106 -0
- package/dist/domain/envelope.js.map +1 -0
- package/dist/domain/handoff.d.ts +57 -0
- package/dist/domain/handoff.d.ts.map +1 -0
- package/dist/domain/handoff.js +191 -0
- package/dist/domain/handoff.js.map +1 -0
- package/dist/domain/identifiers.d.ts +18 -0
- package/dist/domain/identifiers.d.ts.map +1 -0
- package/dist/domain/identifiers.js +70 -0
- package/dist/domain/identifiers.js.map +1 -0
- package/dist/domain/pack.d.ts +45 -0
- package/dist/domain/pack.d.ts.map +1 -0
- package/dist/domain/pack.js +207 -0
- package/dist/domain/pack.js.map +1 -0
- package/dist/domain/pipeline.d.ts +33 -0
- package/dist/domain/pipeline.d.ts.map +1 -0
- package/dist/domain/pipeline.js +50 -0
- package/dist/domain/pipeline.js.map +1 -0
- package/dist/domain/task.d.ts +26 -0
- package/dist/domain/task.d.ts.map +1 -0
- package/dist/domain/task.js +154 -0
- package/dist/domain/task.js.map +1 -0
- package/dist/domain/taskstate.d.ts +37 -0
- package/dist/domain/taskstate.d.ts.map +1 -0
- package/dist/domain/taskstate.js +98 -0
- package/dist/domain/taskstate.js.map +1 -0
- package/dist/gates/dry.d.ts +14 -0
- package/dist/gates/dry.d.ts.map +1 -0
- package/dist/gates/dry.js +71 -0
- package/dist/gates/dry.js.map +1 -0
- package/dist/gates/parsers.d.ts +34 -0
- package/dist/gates/parsers.d.ts.map +1 -0
- package/dist/gates/parsers.js +120 -0
- package/dist/gates/parsers.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +207 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +13 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +27 -0
- package/dist/options.js.map +1 -0
- package/dist/panel.d.ts +5 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +52 -0
- package/dist/panel.js.map +1 -0
- package/dist/ports/agent-runner.d.ts +51 -0
- package/dist/ports/agent-runner.d.ts.map +1 -0
- package/dist/ports/agent-runner.js +2 -0
- package/dist/ports/agent-runner.js.map +1 -0
- package/dist/ports/board-store.d.ts +7 -0
- package/dist/ports/board-store.d.ts.map +1 -0
- package/dist/ports/board-store.js +2 -0
- package/dist/ports/board-store.js.map +1 -0
- package/dist/ports/clock.d.ts +5 -0
- package/dist/ports/clock.d.ts.map +1 -0
- package/dist/ports/clock.js +2 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/gate-runner.d.ts +34 -0
- package/dist/ports/gate-runner.d.ts.map +1 -0
- package/dist/ports/gate-runner.js +2 -0
- package/dist/ports/gate-runner.js.map +1 -0
- package/dist/ports/handoff-store.d.ts +39 -0
- package/dist/ports/handoff-store.d.ts.map +1 -0
- package/dist/ports/handoff-store.js +2 -0
- package/dist/ports/handoff-store.js.map +1 -0
- package/dist/ports/isolation.d.ts +43 -0
- package/dist/ports/isolation.d.ts.map +1 -0
- package/dist/ports/isolation.js +2 -0
- package/dist/ports/isolation.js.map +1 -0
- package/dist/ports/notifier.d.ts +28 -0
- package/dist/ports/notifier.d.ts.map +1 -0
- package/dist/ports/notifier.js +2 -0
- package/dist/ports/notifier.js.map +1 -0
- package/dist/ports/task-state-store.d.ts +10 -0
- package/dist/ports/task-state-store.d.ts.map +1 -0
- package/dist/ports/task-state-store.js +2 -0
- package/dist/ports/task-state-store.js.map +1 -0
- package/dist/refs.d.ts +10 -0
- package/dist/refs.d.ts.map +1 -0
- package/dist/refs.js +19 -0
- package/dist/refs.js.map +1 -0
- package/dist/resources.d.ts +55 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +255 -0
- package/dist/resources.js.map +1 -0
- package/dist/status.d.ts +18 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +60 -0
- package/dist/status.js.map +1 -0
- package/dist/storage.d.ts +18 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +79 -0
- package/dist/storage.js.map +1 -0
- package/dist/toolchains/profile.d.ts +21 -0
- package/dist/toolchains/profile.d.ts.map +1 -0
- package/dist/toolchains/profile.js +60 -0
- package/dist/toolchains/profile.js.map +1 -0
- package/dist/toolchains/thresholds.d.ts +29 -0
- package/dist/toolchains/thresholds.d.ts.map +1 -0
- package/dist/toolchains/thresholds.js +45 -0
- package/dist/toolchains/thresholds.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- 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
|
+
})();
|