kaizen-agent 1.32.0 → 1.33.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.
@@ -0,0 +1,100 @@
1
+ import { store } from "./store.ts";
2
+ import { where } from "./agent-term.ts";
3
+ import { post } from "./api.ts";
4
+ import { key, via } from "./cards.ts";
5
+ import { $, esc } from "./dom.ts";
6
+ import { openRun } from "./panel.ts";
7
+ import { askNotify, toast } from "./toast.ts";
8
+
9
+ // Resume: a run whose in-card agent ended, or one stalled with no progress, gets its agent
10
+ // back on the run (its conversation, where the tool can resume one), in its card.
11
+ export const resumable = (c) => c.status === "stalled" || !!(c.pty && !c.pty.alive);
12
+ export async function resume(c, b) {
13
+ b.disabled = true;
14
+ askNotify();
15
+ const r = await post("/resume", { dir: c.state, id: c.id });
16
+ if (!r.ok) {
17
+ b.disabled = false;
18
+ return;
19
+ }
20
+ toast("Resumed, " + where(r) + ": " + via(r.agent));
21
+ if (r.pty) store.wantTerm = key(c);
22
+ if (store.openKey === key(c)) {
23
+ $("pfoot").innerHTML = "";
24
+ openRun(c, true);
25
+ }
26
+ }
27
+ export const APPROVAL = {
28
+ "approvals.plan": "The plan is ready. Read it in the Plan tab, then approve or send it back.",
29
+ "approvals.review": "The work is ready to check. Approve to finish it, or revise with what to change.",
30
+ findings: "The review has open findings. Approve as-is to accept them and finish, or fix selected findings first.",
31
+ },
32
+ decided = new Set(),
33
+ answer = (c) => key(c) + ":" + c.awaiting + ":" + c.moved;
34
+ export async function decide(c, b, why, answers) {
35
+ b.disabled = true;
36
+ askNotify();
37
+ const r = await post("/decide", {
38
+ dir: c.state,
39
+ id: c.id,
40
+ awaiting: c.awaiting,
41
+ ...(why === undefined ? {} : { why }),
42
+ ...(answers ? { answers } : {}),
43
+ });
44
+ // A failed send keeps the footer, and with it a typed comment.
45
+ if (!r.ok) {
46
+ b.disabled = false;
47
+ return;
48
+ }
49
+ decided.add(answer(c));
50
+ toast((why === undefined ? "Approved" : "Sent back") + ", " + where(r) + ": " + via(r.agent));
51
+ if (r.pty) store.wantTerm = key(c);
52
+ if (store.openKey === key(c)) {
53
+ $("pfoot").innerHTML = "";
54
+ openRun(c, true);
55
+ }
56
+ }
57
+ export function confirmRevise(c) {
58
+ const foot = $("pfoot"),
59
+ plan = c.awaiting === "approvals.plan";
60
+ foot.innerHTML = `<div class="inl" style="flex:1"><div class="q">${plan ? "What should the plan do differently?" : "What should change in the work?"}</div><div class="q dim">Opens your agent on this run with your comment.${plan ? " It revises the plan and comes back here for approval." : " It changes the work, rechecks it and comes back here."}</div><textarea aria-label="Revise comment"></textarea><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Send back</button></div></div>`;
61
+ const inp = foot.querySelector("textarea");
62
+ inp.focus();
63
+ foot.querySelector("[data-a=c]").onclick = () => {
64
+ foot.innerHTML = "";
65
+ openRun(c, true);
66
+ };
67
+ foot.querySelector("[data-a=s]").onclick = (e) => {
68
+ const why = inp.value.trim();
69
+ if (!why) {
70
+ toast("Say what should change");
71
+ inp.focus();
72
+ return;
73
+ }
74
+ decide(c, e.target, why);
75
+ };
76
+ }
77
+ // The plan's questions, answered with the approval: the recommended option preselected,
78
+ // a note for what the options miss. Each answer reaches the agent as one line.
79
+ export function confirmAnswers(c, qs) {
80
+ const foot = $("pfoot");
81
+ foot.innerHTML = `<div class="inl qa" style="flex:1"><div class="q">The plan asks ${qs.length === 1 ? "one question" : qs.length + " questions"}.</div><div class="q dim">Your answers go to your agent with the approval. It records them and builds the plan.</div>${qs.map((x, i) => `<fieldset><legend>${esc(x.q)}</legend>${x.options.map((o, j) => `<label><input type="${x.multi ? "checkbox" : "radio"}" name="q${i}" value="${j}"${o.rec || (!x.multi && !j && !x.options.some((p) => p.rec)) ? " checked" : ""}> ${esc(o.text)}</label>`).join("")}<input type="text" data-n="${i}" placeholder="Note (optional)" aria-label="Note: ${esc(x.q)}"></fieldset>`).join("")}<div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Approve with these answers</button></div></div>`;
82
+ foot.querySelector("[data-a=c]").onclick = () => {
83
+ foot.innerHTML = "";
84
+ openRun(c, true);
85
+ };
86
+ foot.querySelector("[data-a=s]").onclick = (e) =>
87
+ decide(
88
+ c,
89
+ e.target,
90
+ undefined,
91
+ qs.map((x, i) => {
92
+ const picked =
93
+ [...foot.querySelectorAll(`[name=q${i}]:checked`)]
94
+ .map((n) => `"${x.options[+n.value].label}"`)
95
+ .join(", ") || "none of the options",
96
+ note = foot.querySelector(`[data-n="${i}"]`).value.trim();
97
+ return `${x.q} — ${picked}${note ? "; " + note : ""}`;
98
+ }),
99
+ );
100
+ }