kaizen-agent 1.34.3 → 1.34.5

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/cli/state.test.ts CHANGED
@@ -250,6 +250,10 @@ test("notes log: append to a run and to an idea; attachments saved and made abso
250
250
  const rel = saveAttachment(state, null, "../evil name.png", new Uint8Array([1]));
251
251
  expect(rel).toMatch(/^attachments\/\w+-_evil_name\.png$/);
252
252
  expect(requestOf("x", `![s](${rel})`, state)).toBe(`x\n\n![s](${join(state, rel)})`);
253
+ // The New idea form's path: idea first, then its files as a note; a plain file reaches the run by absolute path.
254
+ const doc = saveAttachment(state, null, "spec.pdf", new Uint8Array([3]));
255
+ expect(appendNote(state, { text: "chatty zq6" }, `[spec.pdf](${doc})`)).toBeNull();
256
+ expect(requestOf("/kaizen chatty zq6", readInbox(state)[0]!.notes, state)).toContain(`[spec.pdf](${join(state, doc)})`);
253
257
  expect(saveAttachment(state, "2026-09-01-waiting", "b.txt", new Uint8Array([2]))).toMatch(/^runs\/2026-09-01-waiting\/attachments\//);
254
258
  });
255
259
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.34.3",
3
+ "version": "1.34.5",
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": {
@@ -507,9 +507,10 @@ export function render() {
507
507
  if (ci === 0 && store.mode === "new") {
508
508
  const f = document.createElement("li");
509
509
  f.className = "inl";
510
- f.innerHTML = `${store.all ? `<div class="combo" id="ndc"><button class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<input id="newidea" placeholder="What would you like done?"><textarea id="newnotes" placeholder="Notes: links, paths, background (optional)"></textarea><div class="row"><button class="btn ghost" id="nc">Cancel</button><button class="btn pri" id="ns">Add idea</button></div>`;
510
+ f.innerHTML = `${store.all ? `<div class="combo" id="ndc"><button class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<input id="newidea" placeholder="What would you like done?"><textarea id="newnotes" placeholder="Notes: links, paths, background (optional)"></textarea><div class="att" id="newatt"></div><input type="file" id="newfile" multiple hidden><div class="row"><button class="btn ghost" id="newpick">Attach</button><button class="btn ghost" id="nc">Cancel</button><button class="btn pri" id="ns">Add idea</button></div>`;
511
511
  ul.appendChild(f);
512
- const inp = f.querySelector("#newidea");
512
+ const inp = f.querySelector("#newidea"),
513
+ files = [];
513
514
  draft(inp, "f:new:0");
514
515
  draft(f.querySelector("textarea"), "f:new:1");
515
516
  const save = async () => {
@@ -519,9 +520,44 @@ export function render() {
519
520
  store.mode = null;
520
521
  const dir = store.all ? newDir : null;
521
522
  const r = await post("/inbox", dir ? { op: "add", dir, text, notes } : { op: "add", text, notes });
522
- if (r.ok) toast("Idea added");
523
+ if (r.ok) {
524
+ toast("Idea added");
525
+ // The idea exists first: a failed upload leaves it as plain text.
526
+ if (files.length) {
527
+ const enc = await Promise.all(
528
+ files.map(
529
+ (x) =>
530
+ new Promise((res) => {
531
+ const rd = new FileReader();
532
+ rd.onload = () => res({ name: x.name, data: rd.result });
533
+ rd.readAsDataURL(x);
534
+ }),
535
+ ),
536
+ );
537
+ const n = await post("/note", { dir, for: text, text: "", files: enc });
538
+ if (!n.ok) toast("Idea added, files not attached");
539
+ }
540
+ }
523
541
  load();
524
542
  };
543
+ const add = (x) => {
544
+ files.push(x);
545
+ f.querySelector("#newatt").textContent = files.map((y) => y.name).join(" · ");
546
+ };
547
+ f.querySelector("#newpick").onclick = () => f.querySelector("#newfile").click();
548
+ f.querySelector("#newfile").onchange = (e) => {
549
+ [...e.target.files].forEach(add);
550
+ e.target.value = "";
551
+ };
552
+ for (const el of [inp, f.querySelector("textarea")])
553
+ el.onpaste = (e) => {
554
+ for (const it of e.clipboardData.items)
555
+ if (it.kind === "file") {
556
+ const x = it.getAsFile();
557
+ add(new File([x], x.name === "image.png" ? `paste-${Date.now()}.png` : x.name, { type: x.type }));
558
+ e.preventDefault();
559
+ }
560
+ };
525
561
  if (store.all) ideaCombo(f);
526
562
  f.querySelector("#ns").onclick = save;
527
563
  f.querySelector("#nc").onclick = () => {
@@ -236,7 +236,8 @@ export async function openSources() {
236
236
  const pick = (id, label, xs) =>
237
237
  `<select class="sel ice-sel" id="${id}" aria-label="${label}">${xs.join("")}</select>`;
238
238
  const chips =
239
- (byCat.size > 1
239
+ // Dismissed and Done are hidden until picked, so even a lone one is a choice.
240
+ (byCat.size > 1 || byCat.has("Dismissed") || byCat.has("Done")
240
241
  ? pick("ice-fc", "Category", [
241
242
  opt("", !iceCat, "All categories"),
242
243
  ...[...byCat.keys()].map((k) => opt(k, iceCat === k, k)),
package/web/board.html CHANGED
@@ -189,7 +189,7 @@ section h2 .clear{margin-left:auto;min-height:22px;padding:0 8px}
189
189
  .notes{display:flex;flex-direction:column;gap:8px}
190
190
  #pbody img{max-width:100%;border-radius:var(--r-s)}
191
191
  /* the in-card agent: a wider panel, and the terminal filling its body */
192
- aside.wide{width:100%;border-left:0}#pbody:has(.term){display:flex;flex-direction:column}#pbody .term{flex:1;min-height:0;min-height:320px;background:var(--bg);border-radius:var(--r-s)}#pbody .term .xterm{height:100%;padding:4px}#pbody .term .xterm-viewport{background:transparent!important}.termkeys{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;padding:10px 0 6px}.termkeys button{height:56px;padding:0;border:1px solid var(--line);border-bottom-width:4px;border-radius:10px;background:var(--bg-2);color:var(--ink-hi);font:600 18px var(--mono);touch-action:manipulation}.termkeys button:active{transform:translateY(2px);border-bottom-width:2px;background:var(--hover)}.termkeys button.on{color:var(--on-accent);background:var(--accent);border-color:var(--accent)}
192
+ aside.wide{width:100%;border-left:0}#pbody:has(.term){display:flex;flex-direction:column}#pbody .term{flex:1;min-height:0;min-height:320px;background:var(--bg);border-radius:var(--r-s)}#pbody .term .xterm{height:100%;padding:4px}#pbody .term .xterm-viewport{background:transparent!important}.termkeys{padding:6px 0}.termkeys button{height:52px;padding:0;border:1px solid var(--line);border-bottom-width:4px;border-radius:10px;background:var(--bg-2);color:var(--ink-hi);font:600 17px var(--mono);touch-action:manipulation}.termkeys button:active{transform:translateY(2px);border-bottom-width:2px;background:var(--hover)}.termkeys button.on{color:var(--on-accent);background:var(--accent);border-color:var(--accent)}.keygrid{display:grid;grid-template-columns:repeat(6,1fr);gap:8px}.ctrlpad{margin-bottom:8px}.ctrlpad div{display:flex;gap:4px;margin-bottom:5px}.ctrlpad div:nth-child(2){padding:0 5%}.ctrlpad div:nth-child(3){padding:0 10%}.ctrlpad button{flex:1;min-width:0;height:44px;font-size:15px}.termkeys [hidden]{display:none}.termkeys .keyhide{display:block;width:100%;height:30px;margin-bottom:6px;border-bottom-width:1px;font-size:14px}
193
193
  .desc{border-bottom:1px solid var(--line);padding-bottom:6px;margin-bottom:14px}.desc h3{margin-top:14px}
194
194
  .chat{display:flex;flex-direction:column;gap:10px;margin:0 0 14px}.msg{border:1px solid var(--line);border-radius:var(--r);padding:10px 12px;background:var(--bg)}.msg time{display:block;font-size:11px;color:var(--dim);margin-bottom:4px}.msg p:last-child{margin-bottom:0}.msg img{max-width:100%;max-height:260px;border-radius:var(--r-s);display:block;margin:4px 0}
195
195
  .notes .tools{display:flex;gap:6px;align-items:center}.notes .tools span{flex:1;font-size:11px;color:var(--dim)}.notes .att{font-size:11px;color:var(--dim)}
@@ -413,6 +413,8 @@ body:not(.paged) .src-sec,body:not(.paged) .src-scrim{display:none}
413
413
  .src-add .acts .btn{flex-shrink:0}
414
414
  @media(max-width:480px){.src-add .two,.src-add .two.plat{grid-template-columns:1fr}}
415
415
  @media(max-width:900px){main{grid-template-columns:1fr;padding-inline:16px;gap:8px}.hdr,.ftr,aside.docked>header,aside.docked>.body{padding-inline:16px}.more{display:grid}.card:hover .age{display:inline}.toast{left:16px;max-width:calc(100% - 32px)}}
416
+ @media(max-width:640px){.hdr{gap:8px 12px}.hdr h1{order:0;flex:1 0 50%}#newtop{order:1}.hdr .combo{order:2;flex:1 1 calc(50% - 6px);min-width:0}.hdr .combo>.sel{width:100%;max-width:none}.hdr .sp{display:none}.ftr{gap:6px 12px}}
417
+ @media(hover:none){#kbd{display:none}}
416
418
  @media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}
417
419
  </style>
418
420
  </head>