@felixdotgo/aidlc-kit 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEVELOPMENT.md +13 -26
- package/README.md +35 -39
- package/dist/assets/.agents/aidlc/orchestrator.md +1 -1
- package/dist/assets/.agents/aidlc/schemas/state.schema.json +2 -0
- package/dist/assets/.agents/aidlc/scripts/lib/assignment-store.mjs +94 -48
- package/dist/assets/.agents/aidlc/scripts/lib/context-runtime.mjs +5 -4
- package/dist/assets/.agents/aidlc/scripts/lib/loop-authority.mjs +21 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-daemon.mjs +52 -78
- package/dist/assets/.agents/aidlc/scripts/lib/loop-drivers.mjs +88 -14
- package/dist/assets/.agents/aidlc/scripts/lib/loop-env.mjs +279 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-cli.mjs +20 -5
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-remote.mjs +10 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet-supervisor.mjs +2 -1
- package/dist/assets/.agents/aidlc/scripts/lib/loop-fleet.mjs +3 -2
- package/dist/assets/.agents/aidlc/scripts/lib/loop-git.mjs +9 -3
- package/dist/assets/.agents/aidlc/scripts/lib/loop-holder.mjs +376 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-intake.mjs +11 -3
- package/dist/assets/.agents/aidlc/scripts/lib/loop-lease.mjs +67 -61
- package/dist/assets/.agents/aidlc/scripts/lib/loop-leases.mjs +268 -0
- package/dist/assets/.agents/aidlc/scripts/lib/loop-local-workers.mjs +78 -17
- package/dist/assets/.agents/aidlc/scripts/lib/loop-runtime.mjs +42 -10
- package/dist/assets/.agents/aidlc/scripts/lib/loop-stream.mjs +66 -9
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-local.mjs +16 -56
- package/dist/assets/.agents/aidlc/scripts/lib/loop-supervisor-run-local.mjs +12 -6
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-docker.mjs +14 -2
- package/dist/assets/.agents/aidlc/scripts/lib/loop-worker-log.mjs +158 -0
- package/dist/assets/.agents/aidlc/scripts/lib/repo-head.mjs +22 -0
- package/dist/assets/.agents/aidlc/scripts/lib/store.mjs +11 -0
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime-migrate.mjs +75 -0
- package/dist/assets/.agents/aidlc/scripts/lib/task-runtime.mjs +97 -0
- package/dist/assets/.agents/aidlc/scripts/loop-entry.mjs +87 -4
- package/dist/assets/.agents/aidlc/scripts/loop.cmd +101 -0
- package/dist/assets/.agents/aidlc/scripts/loop.sh +71 -0
- package/dist/assets/.agents/aidlc/scripts/remote-state.mjs +10 -4
- package/dist/assets/.agents/aidlc/scripts/state.mjs +16 -8
- package/dist/assets/ui/changes.js +83 -0
- package/dist/assets/ui/common.js +664 -0
- package/dist/assets/ui/dashboard.css +281 -59
- package/dist/assets/ui/dashboard.js +77 -0
- package/dist/assets/ui/detail.js +159 -0
- package/dist/assets/ui/live-log.js +332 -0
- package/dist/services/aidlc-coordination/package.json +1 -1
- package/dist/services/aidlc-coordination/src/lifecycle-core.mjs +11 -0
- package/dist/src/cli/argv.js +10 -4
- package/dist/src/cli/commands.js +52 -22
- package/dist/src/cli/loop-launcher.d.ts +47 -0
- package/dist/src/cli/loop-launcher.js +322 -0
- package/dist/src/cli.js +3 -0
- package/dist/src/installer.js +4 -2
- package/dist/src/model/records.d.ts +4 -0
- package/dist/src/state/validate.js +5 -0
- package/dist/src/task-ui.d.ts +31 -52
- package/dist/src/task-ui.js +25 -1562
- package/dist/src/ui/assets.d.ts +14 -0
- package/dist/src/ui/assets.js +45 -0
- package/dist/src/ui/changes.d.ts +83 -0
- package/dist/src/ui/changes.js +498 -0
- package/dist/src/ui/containers.d.ts +71 -0
- package/dist/src/ui/containers.js +214 -0
- package/dist/src/ui/daemon.d.ts +75 -0
- package/dist/src/ui/daemon.js +232 -0
- package/dist/src/ui/data.d.ts +42 -0
- package/dist/src/ui/data.js +89 -0
- package/dist/src/ui/docs.d.ts +13 -0
- package/dist/src/ui/docs.js +39 -0
- package/dist/src/ui/files.d.ts +3 -0
- package/dist/src/ui/files.js +20 -0
- package/dist/src/ui/http.d.ts +14 -0
- package/dist/src/ui/http.js +80 -0
- package/dist/src/ui/live.d.ts +5 -0
- package/dist/src/ui/live.js +43 -0
- package/dist/src/ui/markdown.d.ts +2 -0
- package/dist/src/ui/markdown.js +127 -0
- package/dist/src/ui/options.d.ts +48 -0
- package/dist/src/ui/options.js +81 -0
- package/dist/src/ui/security.d.ts +34 -0
- package/dist/src/ui/security.js +38 -0
- package/dist/src/ui/server.d.ts +39 -0
- package/dist/src/ui/server.js +347 -0
- package/dist/src/ui/sse.d.ts +14 -0
- package/dist/src/ui/sse.js +281 -0
- package/dist/src/ui/views/board.d.ts +7 -0
- package/dist/src/ui/views/board.js +131 -0
- package/dist/src/ui/views/changes.d.ts +19 -0
- package/dist/src/ui/views/changes.js +154 -0
- package/dist/src/ui/views/components.d.ts +56 -0
- package/dist/src/ui/views/components.js +113 -0
- package/dist/src/ui/views/docs.d.ts +15 -0
- package/dist/src/ui/views/docs.js +64 -0
- package/dist/src/ui/views/layout.d.ts +44 -0
- package/dist/src/ui/views/layout.js +233 -0
- package/dist/src/ui/views/task.d.ts +25 -0
- package/dist/src/ui/views/task.js +320 -0
- package/dist/src/upgrade.js +5 -2
- package/dist/src/workflow.d.ts +4 -0
- package/dist/src/workflow.js +33 -1
- package/docs/README.md +48 -33
- package/docs/architecture.md +101 -38
- package/docs/command-reference.md +269 -93
- package/docs/configuration.md +62 -42
- package/docs/coordination-service.md +85 -36
- package/docs/design-decisions.md +35 -31
- package/docs/development.md +111 -61
- package/docs/loop-local-run.md +135 -402
- package/docs/loop-runbook.md +84 -105
- package/docs/operating-workflow.md +55 -42
- package/docs/testing-and-release.md +70 -51
- package/docs/troubleshooting.md +165 -69
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Task detail page: gate feedback and approval. Handlers are delegated (the
|
|
3
|
+
* controls are re-rendered by live updates) and read the task and gate from the
|
|
4
|
+
* current DOM, never from values baked in at page load. Loaded after common.js.
|
|
5
|
+
*/
|
|
6
|
+
const detailTask = () => document.body.getAttribute("data-task") || "";
|
|
7
|
+
// Feedback: keeps the task at this gate; the lead revises and re-presents it.
|
|
8
|
+
const onFeedbackSend = (button) => {
|
|
9
|
+
const panel = document.getElementById("feedback-panel");
|
|
10
|
+
const field = document.getElementById("feedback");
|
|
11
|
+
const result = document.getElementById("feedback-result");
|
|
12
|
+
if (!panel || !field) return;
|
|
13
|
+
if (result) result.textContent = "";
|
|
14
|
+
if (!field.value.trim()) { toast("Write the feedback first", "warning", "It is sent to the loop lead as guidance; the task stays at this gate."); field.focus(); return; }
|
|
15
|
+
const label = panel.getAttribute("data-gate-label") || "";
|
|
16
|
+
withLoading(button, async () => {
|
|
17
|
+
const res = await post("/api/gate/feedback", { taskId: detailTask(), feedback: field.value });
|
|
18
|
+
if (res.status >= 200 && res.status < 300) {
|
|
19
|
+
field.value = "";
|
|
20
|
+
toast("Feedback sent — task stays at " + label, "success", "The lead will revise the review artifact and re-present this gate.");
|
|
21
|
+
await liveRefresh();
|
|
22
|
+
} else failureToast(res, "Feedback failed");
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
// Approve: the button only opens the confirmation dialog; "Confirm approve" sends.
|
|
26
|
+
const onApproveOpen = () => {
|
|
27
|
+
const note = document.getElementById("source");
|
|
28
|
+
const result = document.getElementById("approve-result");
|
|
29
|
+
if (!note) return;
|
|
30
|
+
if (result) result.textContent = "";
|
|
31
|
+
if (!note.value.trim()) { toast("Add an approval note first", "warning", "It is recorded as the approval source. To request changes, use Feedback instead."); note.focus(); return; }
|
|
32
|
+
const quote = document.getElementById("approve-modal-note");
|
|
33
|
+
if (quote) quote.textContent = note.value.trim();
|
|
34
|
+
openModal("approve-modal");
|
|
35
|
+
};
|
|
36
|
+
const onApproveConfirm = (button) => {
|
|
37
|
+
const panel = document.getElementById("approve-panel");
|
|
38
|
+
const note = document.getElementById("source");
|
|
39
|
+
if (!panel || !note) return;
|
|
40
|
+
const gate = panel.getAttribute("data-gate") || "";
|
|
41
|
+
const label = panel.getAttribute("data-gate-label") || gate;
|
|
42
|
+
withLoading(button, async () => {
|
|
43
|
+
// The gate shown is sent along; the server refuses if the task has moved on since.
|
|
44
|
+
const res = await post("/api/gate/approve", { taskId: detailTask(), gate, source: note.value });
|
|
45
|
+
closeAllModals();
|
|
46
|
+
if (res.status >= 200 && res.status < 300) {
|
|
47
|
+
const next = res.body && res.body.result && res.body.result.nextAction;
|
|
48
|
+
note.value = "";
|
|
49
|
+
toast("Approved " + label, "success", next && next.classification ? "Next: " + next.classification + (next.phase ? " · " + next.phase : "") : "");
|
|
50
|
+
await liveRefresh();
|
|
51
|
+
} else failureToast(res, "Approval failed");
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
/* ---- Container log viewer (Containers panel) -------------------------------
|
|
55
|
+
* The viewer (#container-log, [data-live-ignore]) is client-owned: a Log button
|
|
56
|
+
* fetches the raw tail of that assignment's log and, while Follow is on and the row
|
|
57
|
+
* still reports the container as running, re-fetches at the Settings live interval
|
|
58
|
+
* (fallback 1.5 s; never while polling is paused). The running state is read from
|
|
59
|
+
* the row's button on every tick, which live updates keep current. */
|
|
60
|
+
const containerLog = { assignment: "", timer: 0, inflight: null, follow: true };
|
|
61
|
+
const containerViewer = () => document.getElementById("container-log");
|
|
62
|
+
const containerSettings = () => (window.aidlcSettings && window.aidlcSettings.get()) || {};
|
|
63
|
+
const containerInterval = () => { const value = Number(containerSettings().livePoll); return Number.isFinite(value) ? value : 1500; };
|
|
64
|
+
const containerButton = () => {
|
|
65
|
+
if (!containerLog.assignment) return null;
|
|
66
|
+
for (const button of document.querySelectorAll("[data-container-log]")) if (button.getAttribute("data-container-log") === containerLog.assignment) return button;
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
const containerRunning = () => { const button = containerButton(); return !!button && button.getAttribute("data-container-running") === "1"; };
|
|
70
|
+
const fetchContainerLog = async () => {
|
|
71
|
+
const el = containerViewer();
|
|
72
|
+
if (!el || !containerLog.assignment) return;
|
|
73
|
+
if (containerLog.inflight) return containerLog.inflight;
|
|
74
|
+
const assignment = containerLog.assignment;
|
|
75
|
+
containerLog.inflight = (async () => {
|
|
76
|
+
const body = el.querySelector("[data-container-log-body]");
|
|
77
|
+
const state = el.querySelector("[data-container-log-state]");
|
|
78
|
+
try {
|
|
79
|
+
const kb = Number(containerSettings().logTailKb) || 64;
|
|
80
|
+
const url = "/api/tasks/" + encodeURIComponent(detailTask()) + "/containers/" + encodeURIComponent(assignment) + "/log?tail=" + (kb * 1024);
|
|
81
|
+
const response = await fetch(url, { cache: "no-store" });
|
|
82
|
+
const text = await response.text();
|
|
83
|
+
if (assignment !== containerLog.assignment) return;
|
|
84
|
+
if (!response.ok) { if (body) body.textContent = text || ("HTTP " + response.status); if (state) state.textContent = "unavailable"; return; }
|
|
85
|
+
const atBottom = body ? body.scrollHeight - body.scrollTop - body.clientHeight < 8 : false;
|
|
86
|
+
if (body) body.textContent = text || "(no output yet)";
|
|
87
|
+
const size = Number(response.headers.get("x-aidlc-log-size")) || 0;
|
|
88
|
+
if (state) state.textContent = (size >= 1024 ? Math.round(size / 1024) + " KiB" : size + " B") + (response.headers.get("x-aidlc-log-truncated") === "1" ? " · tail" : "") + (containerRunning() ? " · running" : " · ended");
|
|
89
|
+
if (body && (containerLog.follow || atBottom)) body.scrollTop = body.scrollHeight;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (state) state.textContent = "unreachable";
|
|
92
|
+
} finally { containerLog.inflight = null; }
|
|
93
|
+
})();
|
|
94
|
+
return containerLog.inflight;
|
|
95
|
+
};
|
|
96
|
+
const scheduleContainerLog = () => {
|
|
97
|
+
clearTimeout(containerLog.timer);
|
|
98
|
+
containerLog.timer = 0;
|
|
99
|
+
const el = containerViewer();
|
|
100
|
+
if (!el || el.hidden || !containerLog.assignment || !containerLog.follow) return;
|
|
101
|
+
const interval = containerInterval();
|
|
102
|
+
if (!interval) return; // live updates paused in Settings
|
|
103
|
+
if (!containerRunning()) return; // the container ended: the last fetch was final
|
|
104
|
+
containerLog.timer = setTimeout(async () => { await fetchContainerLog(); scheduleContainerLog(); }, interval);
|
|
105
|
+
};
|
|
106
|
+
const openContainerLog = (button) => {
|
|
107
|
+
const el = containerViewer();
|
|
108
|
+
if (!el) return;
|
|
109
|
+
containerLog.assignment = button.getAttribute("data-container-log") || "";
|
|
110
|
+
containerLog.follow = true;
|
|
111
|
+
const name = el.querySelector("[data-container-log-name]");
|
|
112
|
+
if (name) name.textContent = (button.getAttribute("data-container-name") || containerLog.assignment) + " · " + containerLog.assignment;
|
|
113
|
+
const follow = el.querySelector("[data-container-follow]");
|
|
114
|
+
if (follow) follow.setAttribute("aria-pressed", "true");
|
|
115
|
+
const body = el.querySelector("[data-container-log-body]");
|
|
116
|
+
if (body) body.textContent = "loading…";
|
|
117
|
+
el.hidden = false;
|
|
118
|
+
el.scrollIntoView({ block: "nearest" });
|
|
119
|
+
fetchContainerLog().then(scheduleContainerLog);
|
|
120
|
+
};
|
|
121
|
+
const closeContainerLog = () => {
|
|
122
|
+
const el = containerViewer();
|
|
123
|
+
clearTimeout(containerLog.timer);
|
|
124
|
+
containerLog.timer = 0;
|
|
125
|
+
containerLog.assignment = "";
|
|
126
|
+
if (el) el.hidden = true;
|
|
127
|
+
};
|
|
128
|
+
const toggleContainerFollow = (button) => {
|
|
129
|
+
containerLog.follow = button.getAttribute("aria-pressed") !== "true";
|
|
130
|
+
button.setAttribute("aria-pressed", containerLog.follow ? "true" : "false");
|
|
131
|
+
if (containerLog.follow) fetchContainerLog().then(scheduleContainerLog); else scheduleContainerLog();
|
|
132
|
+
};
|
|
133
|
+
if (window.aidlcSettings) window.aidlcSettings.onChange((settings, changed) => { if (changed.includes("livePoll")) scheduleContainerLog(); });
|
|
134
|
+
// A live update that re-renders the row (container ended) stops the follow loop on the next tick.
|
|
135
|
+
document.addEventListener("keydown", (event) => {
|
|
136
|
+
if (event.key !== "Escape") return;
|
|
137
|
+
const el = containerViewer();
|
|
138
|
+
if (!el || el.hidden) return;
|
|
139
|
+
// common.js answers Escape for the notifications dropdown, modals, and the side panel first.
|
|
140
|
+
if (typeof notifyOpen === "function" && notifyOpen()) return;
|
|
141
|
+
if (typeof modalOpen === "function" && modalOpen()) return;
|
|
142
|
+
if (typeof drawerOpen === "function" && drawerOpen()) return;
|
|
143
|
+
closeContainerLog();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
document.addEventListener("click", (event) => {
|
|
147
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
148
|
+
if (!target) return;
|
|
149
|
+
const feedback = target.closest("#feedback-button");
|
|
150
|
+
if (feedback) { onFeedbackSend(feedback); return; }
|
|
151
|
+
if (target.closest("#approve-button")) { onApproveOpen(); return; }
|
|
152
|
+
const confirm = target.closest("#approve-confirm");
|
|
153
|
+
if (confirm) { onApproveConfirm(confirm); return; }
|
|
154
|
+
const log = target.closest("[data-container-log]");
|
|
155
|
+
if (log && !log.disabled) { openContainerLog(log); return; }
|
|
156
|
+
const follow = target.closest("[data-container-follow]");
|
|
157
|
+
if (follow) { toggleContainerFollow(follow); return; }
|
|
158
|
+
if (target.closest("[data-container-close]")) closeContainerLog();
|
|
159
|
+
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Live log: streams /api/events?task-id=<task> (SSE) into every [data-live-log]
|
|
3
|
+
* element. The task page shows the full log (data-log-mode="full": lines grouped
|
|
4
|
+
* under turn headers, an Important/All filter, a Raw JSON switch); the task side
|
|
5
|
+
* panel shows a compact one (data-log-mode="compact": the last data-log-limit
|
|
6
|
+
* lines, Important only). Log elements are client-owned ([data-live-ignore]):
|
|
7
|
+
* live morphs never touch them. A MutationObserver starts logs that appear later
|
|
8
|
+
* (the side panel is injected and re-rendered), stops the stream of a log that is
|
|
9
|
+
* removed or hidden (closed side panel), and restarts it when shown again.
|
|
10
|
+
* Event data only ever reaches the DOM through textContent.
|
|
11
|
+
* Loaded after common.js (uses its `liveRefresh` when present).
|
|
12
|
+
*/
|
|
13
|
+
(() => {
|
|
14
|
+
/* ---- Formatting: event → human sentence (pure; exposed for tests) ---- */
|
|
15
|
+
const DEFAULT_GATES = { G0_confirm: "Confirm requirements", G1_review: "Approve plan", G2_codereview: "Review implementation" };
|
|
16
|
+
const str = (value) => typeof value === "string" ? value : "";
|
|
17
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
18
|
+
const clock = (iso) => { const d = new Date(iso); return isNaN(d) ? "--:--:--" : two(d.getHours()) + ":" + two(d.getMinutes()) + ":" + two(d.getSeconds()); };
|
|
19
|
+
const firstLine = (text) => (text.split("\n").find((line) => line.trim()) || "").trim();
|
|
20
|
+
const shortSha = (sha) => str(sha).slice(0, 7);
|
|
21
|
+
const duration = (ms) => { const s = Math.max(0, Math.round(Number(ms || 0) / 1000)); return s < 60 ? s + "s" : Math.floor(s / 60) + "m" + (s % 60 ? " " + (s % 60) + "s" : ""); };
|
|
22
|
+
const code = (value) => ({ code: value });
|
|
23
|
+
// Driver events without an actor (older logs) come from the lead's turn.
|
|
24
|
+
const LEAD_TYPES = /^(agent_|turn_|lifecycle_command$)/;
|
|
25
|
+
const actorOf = (e) => str(e.actor) || (LEAD_TYPES.test(e.type) ? "lead" : "loop");
|
|
26
|
+
// Hidden in Important mode: bookkeeping that does not tell the reader what happened.
|
|
27
|
+
const NOISE = new Set(["turn_completed", "daemon_poll", "agent_event", "resume", "context_changed", "jobs_active", "daemon_start", "resync"]);
|
|
28
|
+
// Outcome and loop events stand on their own instead of nesting under a turn.
|
|
29
|
+
const SYSTEM = new Set(["gate", "waiting", "daemon_turn", "daemon_turn_error", "loop_stopped", "job_integrated", "job_scheduled", "build_deferred", "diagnostic", "daemon_start", "daemon_poll", "daemon_poll_error", "worker_image_build", "resume", "context_changed", "jobs_active", "release_failed", "resync"]);
|
|
30
|
+
const TOOL_VERBS = [
|
|
31
|
+
[/^(bash|shell|command|command_execution|exec|run|local_shell)/i, "ran"],
|
|
32
|
+
[/^(edit|multiedit|write|apply_patch|patch|notebookedit|file_change|str_replace)/i, "edited"],
|
|
33
|
+
[/^(read|view|cat)/i, "read"],
|
|
34
|
+
[/^(grep|glob|search|find|ls|list|websearch|webfetch|fetch)/i, "searched"],
|
|
35
|
+
];
|
|
36
|
+
const toolSentence = (e) => {
|
|
37
|
+
const tool = str(e.tool) || "tool";
|
|
38
|
+
const summary = str(e.summary);
|
|
39
|
+
if (!summary) return ["used tool ", code(tool)];
|
|
40
|
+
const verb = (TOOL_VERBS.find(([pattern]) => pattern.test(tool)) || [])[1];
|
|
41
|
+
return verb ? [verb + " ", code(summary)] : ["used " + tool + " ", code(summary)];
|
|
42
|
+
};
|
|
43
|
+
const toneOfStatus = (status) => /fail|error/.test(status) ? "error" : /pause|block|stop|stall|gate|handoff/.test(status) ? "warn" : /done|complete|success/.test(status) ? "ok" : "muted";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Describes one event: `parts` is the sentence (strings, `{code}` spans, `{link, href}`),
|
|
47
|
+
* `tone` one of error|warn|ok|muted|"", `full` the untruncated text when `parts` shows
|
|
48
|
+
* only its first line, `turn` the header label of a turn start, `noise` hides it in
|
|
49
|
+
* Important mode, `system` keeps it out of turn groups.
|
|
50
|
+
*/
|
|
51
|
+
const describe = (e, ctx = {}) => {
|
|
52
|
+
const gates = ctx.gates || DEFAULT_GATES;
|
|
53
|
+
const gateName = (gate) => gates[gate] || str(gate) || "a gate";
|
|
54
|
+
const taskId = str(e.taskId) || str(ctx.taskId);
|
|
55
|
+
const actor = actorOf(e);
|
|
56
|
+
const info = { actor, parts: [], tone: "", noise: NOISE.has(e.type), system: SYSTEM.has(e.type) };
|
|
57
|
+
const turnLabel = (agent) => ["Turn " + (Number(e.iteration || 0) + 1), str(e.phase), actor + (agent ? "/" + agent : ""), e.itemId ? "item " + e.itemId : ""].filter(Boolean).join(" · ");
|
|
58
|
+
switch (e.type) {
|
|
59
|
+
case "turn_start":
|
|
60
|
+
info.turn = turnLabel(str(e.agent));
|
|
61
|
+
info.parts = ["started turn " + (Number(e.iteration || 0) + 1) + (e.phase ? " · " + e.phase : "") + (e.agent ? " · " + e.agent : "")];
|
|
62
|
+
break;
|
|
63
|
+
case "worker_turn_start":
|
|
64
|
+
info.turn = turnLabel(str(e.agent));
|
|
65
|
+
info.parts = [e.summary ? "worker " + e.summary : "worker started turn " + (Number(e.iteration || 0) + 1)];
|
|
66
|
+
break;
|
|
67
|
+
case "turn_end": info.parts = ["turn " + (Number(e.iteration || 0) + 1) + " finished"]; info.tone = "ok"; break;
|
|
68
|
+
case "turn_completed": info.parts = ["agent finished its reply"]; info.tone = "muted"; break;
|
|
69
|
+
case "agent_text": {
|
|
70
|
+
const text = str(e.text).trim();
|
|
71
|
+
info.parts = [firstLine(text)];
|
|
72
|
+
if (text !== firstLine(text)) info.full = text;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "agent_tool": info.parts = toolSentence(e); break;
|
|
76
|
+
case "lifecycle_command": {
|
|
77
|
+
const exit = e.exitCode;
|
|
78
|
+
const failed = (Number.isInteger(exit) && exit !== 0) || e.status === "failed";
|
|
79
|
+
const outcome = Number.isInteger(exit) ? " → exit " + exit : e.status === "in_progress" ? " → running" : " → " + (str(e.status) || "unknown");
|
|
80
|
+
info.parts = e.summary ? ["command ", code(str(e.summary)), outcome] : ["command" + outcome];
|
|
81
|
+
info.tone = failed ? "error" : Number.isInteger(exit) ? "ok" : "";
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "gate":
|
|
85
|
+
case "waiting":
|
|
86
|
+
info.parts = ["waiting for your review at " + gateName(e.gate) + " · ", { link: "open review", href: "/tasks/" + encodeURIComponent(taskId) + "#approve-panel" }];
|
|
87
|
+
info.tone = "warn";
|
|
88
|
+
break;
|
|
89
|
+
case "daemon_turn": {
|
|
90
|
+
const status = str(e.status) || "unknown";
|
|
91
|
+
info.parts = status === "gate" && e.gate ? ["loop paused at " + gateName(e.gate)] : ["loop run ended: " + status + (e.reason ? " · " + e.reason : "")];
|
|
92
|
+
info.tone = toneOfStatus(status);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "daemon_turn_error":
|
|
96
|
+
info.parts = ["loop turn failed: " + (str(e.message) || "error") + " — retry" + (e.failures ? " #" + e.failures : "") + " in " + duration(e.retryInMs)];
|
|
97
|
+
info.tone = "error";
|
|
98
|
+
break;
|
|
99
|
+
case "daemon_poll_error": info.parts = ["daemon poll failed: " + (str(e.message) || "error")]; info.tone = "error"; break;
|
|
100
|
+
case "loop_stopped": info.parts = ["loop stopped" + (e.reason ? ": " + e.reason : "")]; info.tone = "warn"; break;
|
|
101
|
+
case "build_deferred": info.parts = ["deferred item ", code(str(e.itemId) || "?"), e.reason ? ": " + e.reason : ""]; info.tone = "warn"; break;
|
|
102
|
+
case "worker_image_build": info.parts = ["building worker image ", code(str(e.image) || "?"), e.provider ? " (" + e.provider + ")" : ""]; info.tone = "muted"; break;
|
|
103
|
+
case "worker_reported": {
|
|
104
|
+
const count = Array.isArray(e.changedPaths) ? e.changedPaths.length : 0;
|
|
105
|
+
info.parts = ["worker reported " + count + " file" + (count === 1 ? "" : "s") + (e.commit ? " (" + shortSha(e.commit) + ")" : "")];
|
|
106
|
+
if (count) info.title = e.changedPaths.filter((path) => typeof path === "string").join("\n");
|
|
107
|
+
info.tone = "ok";
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "job_integrated": {
|
|
111
|
+
const sha = shortSha(e.commit || e.commitSha);
|
|
112
|
+
info.parts = ["integrated item ", code(str(e.itemId) || "?"), sha ? " (" + sha + ")" : ""];
|
|
113
|
+
info.tone = "ok";
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "job_scheduled": info.parts = ["scheduled item ", code(str(e.itemId) || "?")]; info.tone = "muted"; break;
|
|
117
|
+
case "diagnostic": info.parts = [str(e.message) || str(e.summary) || "diagnostic"]; info.tone = "warn"; break;
|
|
118
|
+
case "daemon_start": info.parts = ["daemon started" + (e.pid ? " (pid " + e.pid + ")" : "")]; info.tone = "muted"; break;
|
|
119
|
+
case "daemon_poll": info.parts = ["daemon poll · " + (Array.isArray(e.actionable) ? e.actionable.length : 0) + " actionable"]; info.tone = "muted"; break;
|
|
120
|
+
case "resume": info.parts = ["resumed the loop"]; info.tone = "muted"; break;
|
|
121
|
+
case "context_changed": info.parts = [str(e.message) || "task context changed"]; info.tone = "muted"; break;
|
|
122
|
+
case "agent_event": info.parts = ["provider event"]; info.tone = "muted"; break;
|
|
123
|
+
default:
|
|
124
|
+
info.parts = [str(e.message) || str(e.summary) || String(e.type)];
|
|
125
|
+
info.tone = /error|fail/.test(e.type) ? "error" : "";
|
|
126
|
+
}
|
|
127
|
+
return info;
|
|
128
|
+
};
|
|
129
|
+
window.aidlcLiveLog = { describe, clock };
|
|
130
|
+
|
|
131
|
+
/* ---- Rendering ---- */
|
|
132
|
+
// Page-level filter/raw choice; until the user picks one here, the Settings defaults
|
|
133
|
+
// (`logFilter`, `logRaw` in window.aidlcSettings) apply.
|
|
134
|
+
const PREFS_KEY = "aidlc-live-log";
|
|
135
|
+
const settingsDefaults = () => {
|
|
136
|
+
const settings = window.aidlcSettings && typeof window.aidlcSettings.get === "function" ? window.aidlcSettings.get() : {};
|
|
137
|
+
return { filter: settings.logFilter === "all" ? "all" : "important", raw: settings.logRaw === true };
|
|
138
|
+
};
|
|
139
|
+
const readPrefs = () => {
|
|
140
|
+
const fallback = settingsDefaults();
|
|
141
|
+
try {
|
|
142
|
+
const saved = JSON.parse(localStorage.getItem(PREFS_KEY) || "{}");
|
|
143
|
+
return {
|
|
144
|
+
filter: saved.filter === "all" || saved.filter === "important" ? saved.filter : fallback.filter,
|
|
145
|
+
raw: typeof saved.raw === "boolean" ? saved.raw : fallback.raw,
|
|
146
|
+
};
|
|
147
|
+
} catch (error) { return fallback; }
|
|
148
|
+
};
|
|
149
|
+
const prefs = readPrefs();
|
|
150
|
+
const savePrefs = () => { try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); } catch (error) { /* storage disabled */ } };
|
|
151
|
+
const node = (tag, className, text) => { const el = document.createElement(tag); if (className) el.className = className; if (text !== undefined) el.textContent = text; return el; };
|
|
152
|
+
const appendParts = (target, parts) => {
|
|
153
|
+
for (const part of parts) {
|
|
154
|
+
if (typeof part === "string") { if (part) target.append(part); }
|
|
155
|
+
else if (part && part.code !== undefined) target.append(node("code", "ad-log-code", part.code));
|
|
156
|
+
else if (part && part.link) { const a = node("a", "ad-log-link", part.link); a.href = part.href; target.append(a); }
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const actorClass = (actor) => {
|
|
160
|
+
if (actor === "lead") return "is-lead";
|
|
161
|
+
if (actor === "loop") return "is-loop";
|
|
162
|
+
let hash = 0;
|
|
163
|
+
for (const char of actor) hash = (hash * 31 + char.charCodeAt(0)) >>> 0;
|
|
164
|
+
return "is-worker-" + (hash % 4);
|
|
165
|
+
};
|
|
166
|
+
// Events that usually mean the canonical task changed: refresh the page regions
|
|
167
|
+
// now (debounced) instead of waiting for the next poll.
|
|
168
|
+
const STATE_EVENTS = new Set(["turn_end", "lifecycle_command", "gate", "waiting", "daemon_turn", "daemon_turn_error", "loop_stopped", "job_integrated"]);
|
|
169
|
+
let refreshTimer = 0;
|
|
170
|
+
const refreshSoon = () => {
|
|
171
|
+
if (typeof liveRefresh !== "function") return;
|
|
172
|
+
clearTimeout(refreshTimer);
|
|
173
|
+
refreshTimer = setTimeout(() => liveRefresh(), 150);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const instances = new Map();
|
|
177
|
+
const createLog = (root) => {
|
|
178
|
+
const compact = root.getAttribute("data-log-mode") === "compact";
|
|
179
|
+
const limit = Math.max(1, Math.min(2000, Number(root.getAttribute("data-log-limit")) || (compact ? 20 : 2000)));
|
|
180
|
+
const taskId = root.getAttribute("data-task-id") || document.body.getAttribute("data-task") || "";
|
|
181
|
+
let gates = DEFAULT_GATES;
|
|
182
|
+
try { gates = Object.assign({}, DEFAULT_GATES, JSON.parse(root.getAttribute("data-gate-labels") || "{}")); } catch (error) { /* defaults */ }
|
|
183
|
+
const body = root.querySelector("[data-log-body]");
|
|
184
|
+
const stateEl = root.querySelector("[data-log-state]");
|
|
185
|
+
const followBtn = root.querySelector("[data-log-follow]");
|
|
186
|
+
let source = null;
|
|
187
|
+
let follow = true;
|
|
188
|
+
let entries = [];
|
|
189
|
+
let block = null; // current turn group: { actor, body }
|
|
190
|
+
const turns = new Map(); // actor -> label of its latest turn
|
|
191
|
+
const view = () => compact ? { filter: "important", raw: false } : prefs;
|
|
192
|
+
const setState = (text) => { if (stateEl) stateEl.textContent = text; };
|
|
193
|
+
const placeholder = (text) => { body.textContent = ""; body.append(node("span", "ad-log-muted", text)); };
|
|
194
|
+
const scroll = () => { if (follow) body.scrollTop = body.scrollHeight; };
|
|
195
|
+
|
|
196
|
+
const lineFor = (entry, current) => {
|
|
197
|
+
const line = node("div", "ad-log-line");
|
|
198
|
+
if (!entry.event) { line.classList.add("is-unparsed"); line.textContent = entry.raw; return line; }
|
|
199
|
+
const info = entry.info;
|
|
200
|
+
if (info.tone) line.classList.add("is-" + info.tone);
|
|
201
|
+
line.setAttribute("data-type", entry.event.type);
|
|
202
|
+
const actor = node("span", "ad-log-actor " + actorClass(info.actor), info.actor);
|
|
203
|
+
actor.title = "actor: " + info.actor;
|
|
204
|
+
const text = node("span", "ad-log-text");
|
|
205
|
+
if (current.raw) { text.classList.add("is-json"); text.textContent = entry.raw; }
|
|
206
|
+
else if (info.full && current.filter === "all") text.textContent = info.full;
|
|
207
|
+
else if (info.full && !compact) {
|
|
208
|
+
const more = node("details", "ad-log-more");
|
|
209
|
+
const summary = node("summary");
|
|
210
|
+
appendParts(summary, info.parts);
|
|
211
|
+
more.append(summary, node("div", "ad-log-full", info.full));
|
|
212
|
+
text.append(more);
|
|
213
|
+
} else appendParts(text, info.parts);
|
|
214
|
+
if (info.title) text.title = info.title;
|
|
215
|
+
line.append(node("span", "ad-log-time", clock(entry.event.at)), actor, text);
|
|
216
|
+
return line;
|
|
217
|
+
};
|
|
218
|
+
const openGroup = (actor, label, entry) => {
|
|
219
|
+
const group = node("div", "ad-log-turn");
|
|
220
|
+
const head = node("div", "ad-log-turn-head");
|
|
221
|
+
head.append(node("span", "ad-log-time", entry ? clock(entry.event.at) : ""), node("span", "ad-log-actor " + actorClass(actor), actor), node("span", "ad-log-turn-label", label));
|
|
222
|
+
const groupBody = node("div", "ad-log-turn-body");
|
|
223
|
+
group.append(head, groupBody);
|
|
224
|
+
body.append(group);
|
|
225
|
+
block = { actor, body: groupBody };
|
|
226
|
+
return head;
|
|
227
|
+
};
|
|
228
|
+
// Appends one entry to the DOM (when the current view shows it); returns its node.
|
|
229
|
+
const render = (entry) => {
|
|
230
|
+
const current = view();
|
|
231
|
+
const info = entry.info;
|
|
232
|
+
if (info && info.noise && current.filter === "important") return null;
|
|
233
|
+
if (compact || current.raw || !info) { const line = lineFor(entry, current); body.append(line); return line; }
|
|
234
|
+
if (info.turn) { turns.set(info.actor, info.turn); return openGroup(info.actor, info.turn, entry); }
|
|
235
|
+
if (info.system) { block = null; const line = lineFor(entry, current); body.append(line); return line; }
|
|
236
|
+
if (!block || block.actor !== info.actor) openGroup(info.actor, (turns.get(info.actor) || info.actor) + " · continued", null);
|
|
237
|
+
const line = lineFor(entry, current);
|
|
238
|
+
block.body.append(line);
|
|
239
|
+
if (entry.event.type === "turn_end") block = null;
|
|
240
|
+
return line;
|
|
241
|
+
};
|
|
242
|
+
const drop = (entry) => {
|
|
243
|
+
const el = entry.node;
|
|
244
|
+
if (!el) return;
|
|
245
|
+
const group = el.closest(".ad-log-turn");
|
|
246
|
+
el.remove();
|
|
247
|
+
if (group && !group.querySelector(".ad-log-line, .ad-log-turn-head")) group.remove();
|
|
248
|
+
};
|
|
249
|
+
const rerender = () => {
|
|
250
|
+
body.textContent = "";
|
|
251
|
+
block = null;
|
|
252
|
+
turns.clear();
|
|
253
|
+
for (const entry of entries) entry.node = render(entry);
|
|
254
|
+
if (!entries.length) placeholder("No events for this task yet.");
|
|
255
|
+
if (compact) trimCompact();
|
|
256
|
+
scroll();
|
|
257
|
+
};
|
|
258
|
+
// Compact: only the last `limit` shown lines stay in the DOM.
|
|
259
|
+
const trimCompact = () => { while (body.querySelectorAll(".ad-log-line").length > limit) body.querySelector(".ad-log-line").remove(); };
|
|
260
|
+
const append = (raw) => {
|
|
261
|
+
if (!entries.length) body.textContent = "";
|
|
262
|
+
setState("streaming");
|
|
263
|
+
let event = null;
|
|
264
|
+
try { event = JSON.parse(raw); } catch (error) { event = null; }
|
|
265
|
+
if (!event || typeof event !== "object" || typeof event.type !== "string") event = null;
|
|
266
|
+
const entry = { raw, event, info: event ? describe(event, { taskId, gates }) : null, node: null };
|
|
267
|
+
entries.push(entry);
|
|
268
|
+
entry.node = render(entry);
|
|
269
|
+
const cap = compact ? Math.max(limit * 10, 200) : limit;
|
|
270
|
+
while (entries.length > cap) { const old = entries.shift(); if (!compact) drop(old); }
|
|
271
|
+
if (compact) trimCompact();
|
|
272
|
+
if (event && STATE_EVENTS.has(event.type)) refreshSoon();
|
|
273
|
+
scroll();
|
|
274
|
+
};
|
|
275
|
+
const clear = () => { entries = []; block = null; turns.clear(); placeholder("No events for this task yet."); };
|
|
276
|
+
|
|
277
|
+
const syncControls = () => {
|
|
278
|
+
root.querySelectorAll("[data-log-filter]").forEach((button) => button.setAttribute("aria-pressed", String(button.getAttribute("data-log-filter") === prefs.filter)));
|
|
279
|
+
root.querySelectorAll("[data-log-raw]").forEach((button) => button.setAttribute("aria-pressed", String(prefs.raw)));
|
|
280
|
+
};
|
|
281
|
+
root.addEventListener("click", (event) => {
|
|
282
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
283
|
+
if (!target) return;
|
|
284
|
+
const filter = target.closest("[data-log-filter]");
|
|
285
|
+
if (filter) { prefs.filter = filter.getAttribute("data-log-filter") === "all" ? "all" : "important"; savePrefs(); instances.forEach((log) => log.refreshView()); return; }
|
|
286
|
+
if (target.closest("[data-log-raw]")) { prefs.raw = !prefs.raw; savePrefs(); instances.forEach((log) => log.refreshView()); return; }
|
|
287
|
+
if (target.closest("[data-log-follow]")) { follow = !follow; followBtn?.setAttribute("aria-pressed", String(follow)); scroll(); return; }
|
|
288
|
+
if (target.closest("[data-log-clear]")) { clear(); }
|
|
289
|
+
});
|
|
290
|
+
syncControls();
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
refreshView: () => { syncControls(); if (!compact) rerender(); },
|
|
294
|
+
running: () => source !== null,
|
|
295
|
+
start: () => {
|
|
296
|
+
if (source || !body) return;
|
|
297
|
+
clear();
|
|
298
|
+
placeholder("connecting…");
|
|
299
|
+
setState("connecting…");
|
|
300
|
+
source = new EventSource("/api/events?task-id=" + encodeURIComponent(taskId));
|
|
301
|
+
source.onopen = () => { if (!entries.length) { setState("connected · waiting for events"); placeholder("No events for this task yet."); } };
|
|
302
|
+
source.onmessage = (message) => append(message.data);
|
|
303
|
+
// The server replays the log from the start of its window: drop what is shown.
|
|
304
|
+
source.addEventListener("reset", () => clear());
|
|
305
|
+
source.onerror = () => {
|
|
306
|
+
const closed = source && source.readyState === 2;
|
|
307
|
+
setState(closed ? "unavailable" : entries.length ? "reconnecting…" : "unavailable");
|
|
308
|
+
if (!entries.length) placeholder("Log stream unavailable (loop not running or log missing).");
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
stop: () => { if (source) { source.close(); source = null; setState("paused"); } },
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Starts logs that are attached and visible; stops removed or hidden ones.
|
|
316
|
+
const scan = () => {
|
|
317
|
+
document.querySelectorAll("[data-live-log]").forEach((root) => { if (!instances.has(root)) instances.set(root, createLog(root)); });
|
|
318
|
+
for (const [root, log] of instances) {
|
|
319
|
+
if (!root.isConnected) { log.stop(); instances.delete(root); continue; }
|
|
320
|
+
if (root.closest("[hidden]")) log.stop(); else log.start();
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
let scheduled = false;
|
|
324
|
+
const scheduleScan = () => { if (scheduled) return; scheduled = true; queueMicrotask(() => { scheduled = false; scan(); }); };
|
|
325
|
+
// Mutations inside a log (its lines, its state text) never add or remove logs.
|
|
326
|
+
const insideLog = (record) => { const el = record.target.nodeType === 1 ? record.target : record.target.parentElement; return !!el && !!el.closest("[data-live-log]"); };
|
|
327
|
+
new MutationObserver((records) => { if (records.some((record) => !insideLog(record))) scheduleScan(); })
|
|
328
|
+
.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["hidden"] });
|
|
329
|
+
window.addEventListener("pagehide", () => instances.forEach((log) => log.stop()));
|
|
330
|
+
window.addEventListener("pageshow", (event) => { if (event.persisted) scan(); });
|
|
331
|
+
scan();
|
|
332
|
+
})();
|
|
@@ -12,6 +12,8 @@ const phaseGate = (phase) => phase === "clarify" ? "G0_confirm" : phase === "pla
|
|
|
12
12
|
const iso = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
|
|
13
13
|
const text = (value) => typeof value === "string" && Boolean(value.trim());
|
|
14
14
|
const idPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
15
|
+
const commitPattern = /^[0-9a-f]{7,64}$/;
|
|
16
|
+
const commitFields = ["baseCommit", "finalCommit"];
|
|
15
17
|
const digest = (content) => createHash("sha256").update(content).digest("hex");
|
|
16
18
|
const archiveRecordPath = (id) => `.agents/data/state/archive/${id}.json`;
|
|
17
19
|
const taskRecordContent = (task) => `${JSON.stringify(task, null, 2)}\n`;
|
|
@@ -74,6 +76,7 @@ const validateTask = (id, task) => {
|
|
|
74
76
|
if (task.closure && (!text(task.closure.reason) || !text(task.closure.source) || !iso(task.closure.recordedAt))) throw new Error(`Invalid task closure: ${id}`);
|
|
75
77
|
if ((task.status === "superseded") !== Boolean(task.successorTaskId) || (task.status === "closed" && task.successorTaskId)) throw new Error(`Invalid task successor: ${id}`);
|
|
76
78
|
if (task.predecessorTaskId !== undefined && !text(task.predecessorTaskId)) throw new Error(`Invalid task predecessor: ${id}`);
|
|
79
|
+
for (const field of commitFields) if (task[field] !== undefined && !(typeof task[field] === "string" && commitPattern.test(task[field]))) throw new Error(`Invalid task ${field}: ${id}`);
|
|
77
80
|
for (const path of Object.values(task.artifacts ?? {})) if (path) {
|
|
78
81
|
const cleaned = normalize(path);
|
|
79
82
|
if (isAbsolute(cleaned) || cleaned === ".." || cleaned.startsWith(`..${separator}`) || !cleaned.startsWith(`.agents${separator}data${separator}tasks${separator}`)) throw new Error(`Invalid artifact path for ${id}: ${path}`);
|
|
@@ -278,6 +281,14 @@ export const transitionTask = (state, id, target) => {
|
|
|
278
281
|
task.phase = target; task.gate = phaseGate(target); task.status = target === "done" ? "done" : "active"; delete task.legacyG2Wait; task.updatedAt = new Date().toISOString(); return task;
|
|
279
282
|
};
|
|
280
283
|
|
|
284
|
+
/** Record a git SHA on a task. Wrappers resolve the SHA (lib/repo-head.mjs); the core never runs git. */
|
|
285
|
+
export const recordTaskCommit = (task, field, sha) => {
|
|
286
|
+
if (!task || typeof task !== "object") throw new Error("recordTaskCommit requires a task");
|
|
287
|
+
if (!commitFields.includes(field)) throw new Error(`Unknown task commit field: ${field}`);
|
|
288
|
+
if (typeof sha !== "string" || !commitPattern.test(sha)) throw new Error(`Invalid ${field}: expected a lowercase hex git SHA (7-64 chars)`);
|
|
289
|
+
task[field] = sha; task.updatedAt = new Date().toISOString(); return task;
|
|
290
|
+
};
|
|
291
|
+
|
|
281
292
|
export const readyTaskItems = (task) => {
|
|
282
293
|
const byId = new Map(task.tasks.map((item) => [item.id, item]));
|
|
283
294
|
if (byId.size !== task.tasks.length) throw new Error(`Duplicate execution item: ${task.id}`);
|
package/dist/src/cli/argv.js
CHANGED
|
@@ -10,9 +10,15 @@ export const usage = `Usage:
|
|
|
10
10
|
npx @felixdotgo/aidlc-kit status [path] [--json]
|
|
11
11
|
npx @felixdotgo/aidlc-kit doctor [path] [--strict] [--json]
|
|
12
12
|
npx @felixdotgo/aidlc-kit profile validate [path]
|
|
13
|
-
npx @felixdotgo/aidlc-kit loop
|
|
13
|
+
npx @felixdotgo/aidlc-kit loop up [--root PATH] [--loop-env PATH] [--port 4173] [--host 127.0.0.1] [--open] [--no-ui]
|
|
14
|
+
npx @felixdotgo/aidlc-kit loop down|stop [--root PATH] [--now]
|
|
15
|
+
npx @felixdotgo/aidlc-kit loop env [--root PATH] [--loop-env PATH] [--json]
|
|
16
|
+
npx @felixdotgo/aidlc-kit loop status [--root PATH] [--json] (daemon, dashboard, environment)
|
|
17
|
+
npx @felixdotgo/aidlc-kit loop start|status|intake [--task-id ID] [--title TITLE] [--root PATH] [--mode local|fleet] [--loop-env PATH]
|
|
14
18
|
npx @felixdotgo/aidlc-kit loop worker-image [--provider ${LOOP_PROVIDERS.join("|")}] [--tag TAG] [--root PATH]
|
|
15
|
-
npx @felixdotgo/aidlc-kit
|
|
19
|
+
npx @felixdotgo/aidlc-kit loop leases [--task-id ID] [--root PATH] [--json] [--clear-stale]
|
|
20
|
+
npx @felixdotgo/aidlc-kit loop migrate-data [--root PATH] [--json] [--dry-run] (move pre-runtime per-task loop files under .agents/data/tasks/<id>/runtime/)
|
|
21
|
+
npx @felixdotgo/aidlc-kit ui [path] [--port 4173] [--host 127.0.0.1] [--open] [--reviewer-token-file PATH]
|
|
16
22
|
npx @felixdotgo/aidlc-kit mcp setup [path] [--dry-run] [--yes] [--storage sqlite|postgres] [--deployment docker|remote] [--bind localhost|network] [--workspace <id>] [--poll-ms <n>] [--token-env <name>] [--providers <p,...>] [--enable]
|
|
17
23
|
npx @felixdotgo/aidlc-kit version
|
|
18
24
|
Global options: --help, --version, -v`;
|
|
@@ -26,7 +32,7 @@ export const value = (args, name) => {
|
|
|
26
32
|
return next;
|
|
27
33
|
};
|
|
28
34
|
export const flags = (args) => new Set(args.filter((item) => item.startsWith("--")));
|
|
29
|
-
const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--loop-mode", "--loop-lead", "--loop-workers", "--port", "--host", "--
|
|
35
|
+
const valueFlags = new Set(["--agent", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--loop-mode", "--loop-lead", "--loop-workers", "--port", "--host", "--reviewer-token-file"]);
|
|
30
36
|
const COMMAND_KEYWORDS = ["init", "upgrade", "uninstall", "status", "doctor", "validate", "run", "list"];
|
|
31
37
|
const positional = (args) => {
|
|
32
38
|
const result = [];
|
|
@@ -48,7 +54,7 @@ export const UNINSTALL_FLAGS = new Set(["--yes", "--dry-run"]);
|
|
|
48
54
|
export const STATUS_FLAGS = new Set(["--json"]);
|
|
49
55
|
export const DOCTOR_FLAGS = new Set(["--strict", "--json"]);
|
|
50
56
|
export const MCP_FLAGS = new Set(["--dry-run", "--yes", "--storage", "--deployment", "--bind", "--workspace", "--poll-ms", "--token-env", "--providers", "--enable"]);
|
|
51
|
-
export const UI_FLAGS = new Set(["--port", "--host", "--open", "--
|
|
57
|
+
export const UI_FLAGS = new Set(["--port", "--host", "--open", "--reviewer-token-file"]);
|
|
52
58
|
export const assertKnownOptions = (args, allowed) => {
|
|
53
59
|
for (let index = 0; index < args.length; index += 1) {
|
|
54
60
|
const arg = args[index];
|