kaizen-agent 1.45.0 → 1.46.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/package.json +3 -1
- package/web/board/api.ts +10 -0
- package/web/board/cards.ts +2 -2
- package/web/board/terms.ts +23 -1
- package/web/board/toast.ts +3 -2
- package/web/board.html +2 -1
- package/web/board.js +22 -21
- package/web/notebook.js +1 -1
- package/web/term.js +61 -35
- package/web/term.ts +59 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kaizen-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"description": "Staged workflow for AI agents — code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"@codemirror/view": "6.43.13",
|
|
56
56
|
"@lezer/highlight": "1.2.5",
|
|
57
57
|
"@xterm/addon-fit": "0.11.0",
|
|
58
|
+
"@xterm/addon-search": "0.16.0",
|
|
59
|
+
"@xterm/addon-web-links": "0.12.0",
|
|
58
60
|
"@xterm/addon-webgl": "0.19.0",
|
|
59
61
|
"@xterm/xterm": "6.0.0",
|
|
60
62
|
"prettier": "3.9.10"
|
package/web/board/api.ts
CHANGED
|
@@ -8,6 +8,10 @@ import { openTerms, termsShowing } from "./terms.ts";
|
|
|
8
8
|
// The server writes notifier() from state.ts over a marker in board.html: one rule for both UIs.
|
|
9
9
|
declare const due: (key: string, say: string | null, done: boolean, now: number) => boolean;
|
|
10
10
|
|
|
11
|
+
// In-card agents waiting on input, by session key. null until the first /state: what is
|
|
12
|
+
// already asking when the page loads is not news, only what starts asking after.
|
|
13
|
+
let asked: Set<string> | null = null;
|
|
14
|
+
|
|
11
15
|
// This page's project, sent with each request: the server keeps none, so another
|
|
12
16
|
// tab or a phone picking a project never moves this one.
|
|
13
17
|
export const dirHeader = () => (store.dir ? { "x-kz-dir": store.dir } : {});
|
|
@@ -35,6 +39,12 @@ export async function load(quiet) {
|
|
|
35
39
|
if (c.kind !== "run" || c.archived) continue;
|
|
36
40
|
if (due(key(c), c.notice, c.status === "done", store.S.now)) notifyUser(c.notice);
|
|
37
41
|
}
|
|
42
|
+
{
|
|
43
|
+
const now = new Map(store.S.cards.filter((c) => !c.archived && c.pty?.alive && c.pty.asking).map((c) => [c.pty.key, c]));
|
|
44
|
+
if (asked && document.hidden)
|
|
45
|
+
for (const [k, c] of now) if (!asked.has(k)) notifyUser(`${c.kind === "run" ? c.id : String(c.text).split("\n")[0].slice(0, 80)} needs you`, "ask:" + k);
|
|
46
|
+
asked = new Set(now.keys());
|
|
47
|
+
}
|
|
38
48
|
// A redraw under a form being typed in would reset its fields and its focus.
|
|
39
49
|
if (!(quiet && store.mode && $("board").querySelector(".inl input"))) render();
|
|
40
50
|
if (termsShowing()) openTerms();
|
package/web/board/cards.ts
CHANGED
|
@@ -863,8 +863,8 @@ export function render() {
|
|
|
863
863
|
s.appendChild(ul);
|
|
864
864
|
b.appendChild(s);
|
|
865
865
|
});
|
|
866
|
-
// The tab says how many runs wait on you, so a background tab still tells you.
|
|
867
|
-
const nw = store.S.cards.filter((c) => c.kind === "run" && c.status === "waiting" &&
|
|
866
|
+
// The tab says how many runs wait on you, or agents wait on input, so a background tab still tells you.
|
|
867
|
+
const nw = store.S.cards.filter((c) => !c.archived && ((c.kind === "run" && c.status === "waiting") || (c.pty?.alive && c.pty.asking))).length;
|
|
868
868
|
document.title = nw ? `(${nw}) kaizen` : "kaizen";
|
|
869
869
|
stopDictation(false); // forms were just rebuilt; a listener whose field is gone would write into nothing
|
|
870
870
|
if (fk && document.activeElement === document.body) cardEl(fk)?.focus({ preventScroll: true });
|
package/web/board/terms.ts
CHANGED
|
@@ -76,6 +76,27 @@ if (typeof window !== "undefined") addEventListener(
|
|
|
76
76
|
},
|
|
77
77
|
true,
|
|
78
78
|
);
|
|
79
|
+
// Alt+1…9 goes to the Nth tile; where one tile shows (maximised, or a phone's tabs) it switches
|
|
80
|
+
// which. Read by code, so the layout and Alt's own characters do not matter. Alt+arrows stay the shell's word jump.
|
|
81
|
+
if (typeof window !== "undefined") addEventListener(
|
|
82
|
+
"keydown",
|
|
83
|
+
(e) => {
|
|
84
|
+
const n = /^Digit([1-9])$/.exec(e.code)?.[1];
|
|
85
|
+
if (!showing || !n || !e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
|
|
86
|
+
const k = [...$("terms").querySelectorAll<HTMLElement>(".tile")][+n - 1]?.dataset.k;
|
|
87
|
+
if (!k) return;
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
if (phone?.matches) pick = k;
|
|
91
|
+
else if (lay.max && lay.max !== k) {
|
|
92
|
+
lay.max = k;
|
|
93
|
+
save();
|
|
94
|
+
}
|
|
95
|
+
openTerms();
|
|
96
|
+
focusTerm(k);
|
|
97
|
+
},
|
|
98
|
+
true,
|
|
99
|
+
);
|
|
79
100
|
|
|
80
101
|
export function openTerms() {
|
|
81
102
|
showing = true;
|
|
@@ -163,10 +184,11 @@ export function openTerms() {
|
|
|
163
184
|
const st = tst(c),
|
|
164
185
|
off = st === "ended";
|
|
165
186
|
const hd = t.el.querySelector("header")!;
|
|
166
|
-
hd.title = (c.kind === "run" ? c.id : c.text) + " · " + c.where;
|
|
187
|
+
hd.title = (c.kind === "run" ? c.id : c.text) + " · " + c.where + (i < 9 ? ` · Alt+${i + 1}` : "");
|
|
167
188
|
// An ended run can be resumed from here; an idea whose agent ended has no run to resume.
|
|
168
189
|
hd.innerHTML = html`<span class="tdot ${st}" role="img" title="${st}" aria-label="${st}"></span><b>${nameOf(c)} - ${projOf(c)}</b>${off ? html`<span class="tend">ended</span>` : ""}<span class="sp"></span>${off && c.kind === "run" ? html`<button class="btn sm" data-a="resume" title="Continue this run's agent">Resume</button>` : ""}<button class="ib sm" data-a="card" title="Open card" aria-label="Open card">${ico("open")}</button><button class="ib sm" data-a="max" title="${lay.max === k ? "Restore" : "Maximise"} (Ctrl+Shift+M)" aria-label="${lay.max === k ? "Restore" : "Maximise"}">${ico(lay.max === k ? "min" : "max")}</button>${off ? "" : html`<button class="ib sm" data-a="hide" title="Hide" aria-label="Hide">${ico("dash")}</button>`}<button class="ib sm" data-a="${off ? "hide" : "exit"}" title="${off ? "Close" : "Exit session"}" aria-label="${off ? "Close" : "Exit session"}">${ico("close")}</button>`.s;
|
|
169
190
|
t.el.classList.toggle("ended", off);
|
|
191
|
+
t.el.classList.toggle("asking", st === "asking");
|
|
170
192
|
t.el.classList.toggle("off", !!one && one !== k);
|
|
171
193
|
t.el.classList.toggle("max", !phone.matches && lay.max === k);
|
|
172
194
|
t.el.style.gridColumn = "";
|
package/web/board/toast.ts
CHANGED
|
@@ -24,9 +24,10 @@ export function askNotify() {
|
|
|
24
24
|
if (typeof Notification !== "undefined" && Notification.permission === "default") Notification.requestPermission();
|
|
25
25
|
} catch (e) {}
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
// tag: one notification per thing, so several open board tabs replace each other's.
|
|
28
|
+
export function notifyUser(body, tag?: string) {
|
|
28
29
|
try {
|
|
29
30
|
if (typeof Notification !== "undefined" && Notification.permission === "granted")
|
|
30
|
-
new Notification("kaizen", { body, icon: "/logo.png" });
|
|
31
|
+
new Notification("kaizen", { body, icon: "/logo.png", tag });
|
|
31
32
|
} catch (e) {}
|
|
32
33
|
}
|
package/web/board.html
CHANGED
|
@@ -278,8 +278,9 @@ body.termv #terms{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px
|
|
|
278
278
|
.tile header{display:flex;align-items:center;gap:8px;min-height:30px;padding:0 4px 0 10px;border-bottom:1px solid var(--line);background:var(--bg-2);cursor:grab;user-select:none}
|
|
279
279
|
.tile header b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:600}.tile header .sp{flex:1}
|
|
280
280
|
.tile.ended .term{opacity:.6}.tend{flex:none;padding:0 6px;border-radius:9px;font-size:11px;color:var(--dim);box-shadow:inset 0 0 0 1px var(--line)}.tile header .btn.sm{height:22px;padding:0 8px;font-size:11px}
|
|
281
|
+
.tile.asking{border-color:var(--warn);animation:task 1.6s ease-in-out infinite}@keyframes task{50%{box-shadow:0 0 0 3px color-mix(in srgb,var(--warn) 45%,transparent)}}@media (prefers-reduced-motion:reduce){.tile.asking{animation:none;box-shadow:0 0 0 2px var(--warn)}}
|
|
281
282
|
.tdot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--run)}.tdot.asking{background:var(--warn)}.tdot.ended{background:var(--dim);opacity:.6}
|
|
282
|
-
.tile .term{flex:1;min-height:0}.tile .term .xterm{height:100%;padding:4px}.tile .term .xterm-viewport{background:transparent!important}
|
|
283
|
+
.term{position:relative}.termtools{position:absolute;top:6px;right:14px;z-index:5;display:flex;gap:4px;opacity:.35;transition:opacity .15s}.term:hover .termtools,.termtools:focus-within{opacity:1}.termtools button,.termtools input{height:24px;padding:0 8px;border:1px solid var(--line);border-radius:6px;background:var(--bg-2);color:var(--ink-hi);font:12px var(--mono)}.termtools button:hover{background:var(--hover)}.tsearch{display:flex;gap:4px}.tsearch[hidden]{display:none}.termtools input{width:160px;padding:0 6px}.tile .term{flex:1;min-height:0}.tile .term .xterm{height:100%;padding:4px}.tile .term .xterm-viewport{background:transparent!important}
|
|
283
284
|
.tsplit{position:absolute;z-index:2;touch-action:none}.tsplit.c{top:0;width:10px;margin-left:-5px;cursor:col-resize}.tsplit.r{left:0;right:0;height:10px;margin-top:-5px;cursor:row-resize}.tsplit:hover,.tdrag .tsplit:active{background:color-mix(in srgb,var(--accent) 35%,transparent)}
|
|
284
285
|
.tile.max{position:fixed;inset:8px;z-index:10;box-shadow:var(--shadow-1)}body.tmax .tsplit{display:none}
|
|
285
286
|
body.tdrag{user-select:none}body.tdrag .tile{pointer-events:none}
|