kaizen-agent 1.7.0 → 1.7.2
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/README.md +1 -1
- package/cli/install.ts +1 -13
- package/cli/state.ts +13 -0
- package/cli/web.ts +8 -1
- package/package.json +1 -1
- package/web/board.html +11 -4
package/README.md
CHANGED
|
@@ -305,7 +305,7 @@ the OS can find — Spotlight on macOS, the app menu on Linux, a Desktop shortcu
|
|
|
305
305
|
edit, a reject reason, a note on a run — has a mic. Press it, talk, press it again; the
|
|
306
306
|
words land at the caret next to whatever you had typed. Recognition is Whisper running
|
|
307
307
|
inside the page, so audio never leaves your machine. The first press downloads the model
|
|
308
|
-
from huggingface.co (about 165 MB on a WebGPU browser, 80 MB on the CPU fallback) and
|
|
308
|
+
from huggingface.co (about 165 MB on a WebGPU browser, up to 205 MB on a GPU without fp16, 80 MB on the CPU fallback) and
|
|
309
309
|
caches it in the browser. The transformers.js library itself is fetched from jsDelivr the
|
|
310
310
|
first time you press the mic in a session, so that first press needs the network; the
|
|
311
311
|
model does not download again. Chromium and Edge qualify (Firefox falls back to the
|
package/cli/install.ts
CHANGED
|
@@ -221,6 +221,7 @@ if (wantWeb) {
|
|
|
221
221
|
// The pid file only knows a --daemon; a board started from the shortcut or a
|
|
222
222
|
// terminal has none, so also ask the OS what listens on the port.
|
|
223
223
|
const recorded = existsSync(pidFile) ? Number(readFileSync(pidFile, "utf8")) : NaN;
|
|
224
|
+
const { pidOnPort } = await import("./state.ts");
|
|
224
225
|
const listening = await pidOnPort(port ?? 7420);
|
|
225
226
|
const pids = [...new Set([recorded, listening].filter((p): p is number => Number.isFinite(p) && p > 0))];
|
|
226
227
|
if (!pids.length) { console.log(" nothing running on the kaizen web port"); process.exit(0); }
|
|
@@ -797,19 +798,6 @@ if (blocked) process.exit(1);
|
|
|
797
798
|
|
|
798
799
|
// ---------------------------------------------------------------- helpers
|
|
799
800
|
|
|
800
|
-
// Whoever holds the board's port, by the OS's own accounting.
|
|
801
|
-
async function pidOnPort(port: number): Promise<number | null> {
|
|
802
|
-
try {
|
|
803
|
-
if (process.platform === "win32") {
|
|
804
|
-
const out = await Bun.$`netstat -ano -p tcp`.text();
|
|
805
|
-
const m = new RegExp(`127\\.0\\.0\\.1:${port}\\s+\\S+\\s+LISTENING\\s+(\\d+)`).exec(out);
|
|
806
|
-
return m ? Number(m[1]) : null;
|
|
807
|
-
}
|
|
808
|
-
const out = await Bun.$`lsof -t -iTCP:${port} -sTCP:LISTEN`.quiet().nothrow().text();
|
|
809
|
-
return Number(out.trim().split("\n")[0]) || null;
|
|
810
|
-
} catch { return null; }
|
|
811
|
-
}
|
|
812
|
-
|
|
813
801
|
function step(msg: string) { console.log(` ${c.green("+")} ${msg}`); }
|
|
814
802
|
|
|
815
803
|
// Runs the upgrade and reports what it actually did, version to version, so the
|
package/cli/state.ts
CHANGED
|
@@ -689,3 +689,16 @@ export function launchRun(it: Item, from: string): Launch {
|
|
|
689
689
|
return { ok: false, agent, prompt, projectDir, manual, why: err?.message ?? String(err) };
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
|
+
|
|
693
|
+
// Whoever holds the board's port, by the OS's own accounting.
|
|
694
|
+
export async function pidOnPort(port: number): Promise<number | null> {
|
|
695
|
+
try {
|
|
696
|
+
if (process.platform === "win32") {
|
|
697
|
+
const out = await Bun.$`netstat -ano -p tcp`.text();
|
|
698
|
+
const m = new RegExp(`127\\.0\\.0\\.1:${port}\\s+\\S+\\s+LISTENING\\s+(\\d+)`).exec(out);
|
|
699
|
+
return m ? Number(m[1]) : null;
|
|
700
|
+
}
|
|
701
|
+
const out = await Bun.$`lsof -t -iTCP:${port} -sTCP:LISTEN`.quiet().nothrow().text();
|
|
702
|
+
return Number(out.trim().split("\n")[0]) || null;
|
|
703
|
+
} catch { return null; }
|
|
704
|
+
}
|
package/cli/web.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join, dirname } from "node:path";
|
|
|
5
5
|
import {
|
|
6
6
|
home, type Item, boardCards, knownProjects, remember, locate, label, tilde,
|
|
7
7
|
readInbox, writeInbox, replaceIdea, abandonRun, launchRun, parseItem, short, setArchived, writeNotes,
|
|
8
|
-
appendNote, saveAttachment,
|
|
8
|
+
appendNote, saveAttachment, pidOnPort,
|
|
9
9
|
} from "./state.ts";
|
|
10
10
|
|
|
11
11
|
const PAGE = join(dirname(import.meta.dir), "web", "board.html");
|
|
@@ -108,6 +108,13 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
108
108
|
const url = `http://127.0.0.1:${port}/`;
|
|
109
109
|
const up = await fetch(url, { signal: AbortSignal.timeout(1500) }).then((r) => r.ok, () => false);
|
|
110
110
|
if (up) { console.log(`\n kaizen web already at ${url}\n`); if (opts.open !== false) await openApp(url); return; }
|
|
111
|
+
// A listener that does not answer is a board that hung; it is ours to stop.
|
|
112
|
+
const pid = await pidOnPort(port);
|
|
113
|
+
if (pid && pid !== process.pid) {
|
|
114
|
+
try { process.kill(pid, "SIGTERM"); console.log(` stopped a hung board on ${port} (pid ${pid})`); } catch { /* not ours to kill */ }
|
|
115
|
+
await Bun.sleep(300);
|
|
116
|
+
try { server = serve(port); continue; } catch { /* still held */ }
|
|
117
|
+
}
|
|
111
118
|
console.log(` port ${port} refused by the OS and nothing answers there; trying ${port + 1}`);
|
|
112
119
|
}
|
|
113
120
|
}
|
package/package.json
CHANGED
package/web/board.html
CHANGED
|
@@ -229,7 +229,12 @@ const TFJS="https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0",ASR_MO
|
|
|
229
229
|
// Inference lives in a module Worker so a slow chunk never freezes the board.
|
|
230
230
|
const WORKER_SRC=`import{pipeline}from"${TFJS}";let asr=null;
|
|
231
231
|
onmessage=async e=>{const m=e.data;try{
|
|
232
|
-
if(m.type==="load"){
|
|
232
|
+
if(m.type==="load"){const prog=p=>{if(p.status==="progress")postMessage({type:"progress",file:p.file,pct:p.progress||0})};
|
|
233
|
+
// WebGPU first, with the encoder in fp16 only where the adapter has shader-f16; any GPU failure falls back to the CPU.
|
|
234
|
+
const gpu=m.webgpu?{device:"webgpu",dtype:{encoder_model:m.f16?"fp16":"fp32",decoder_model_merged:"q4"}}:null,cpu={device:"wasm",dtype:"q8"};
|
|
235
|
+
try{if(!gpu)throw 0;asr=await pipeline("automatic-speech-recognition","${ASR_MODEL}",{...gpu,progress_callback:prog})}
|
|
236
|
+
catch(e){if(gpu)postMessage({type:"fallback",why:String(e&&e.message||e)});asr=await pipeline("automatic-speech-recognition","${ASR_MODEL}",{...cpu,progress_callback:prog})}
|
|
237
|
+
postMessage({type:"ready"})}
|
|
233
238
|
if(m.type==="run"){const r=await asr(m.audio,{chunk_length_s:30,return_timestamps:false});postMessage({type:"text",sid:m.sid,seq:m.seq,text:(r.text||"").trim()})}
|
|
234
239
|
}catch(err){postMessage({type:"error",sid:m.sid,seq:m.seq,error:String(err&&err.message||err)})}}`;
|
|
235
240
|
// The worklet only forwards 128-frame blocks; chunking and silence detection stay on the main thread.
|
|
@@ -238,11 +243,13 @@ const HAS_MIC=!!(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&wi
|
|
|
238
243
|
// `active` is the one session listening; `sessions` also holds stopped ones still waiting on a chunk.
|
|
239
244
|
let asrWorker=null,asrReady=null,asrProgress=()=>{},active=null,starting=false;const sessions=new Set();
|
|
240
245
|
// One worker for the page; loading is awaited by whoever pressed first, later presses join.
|
|
241
|
-
function loadAsr(){if(asrReady)return asrReady;asrReady=new Promise((res,rej)=>{
|
|
246
|
+
function loadAsr(){if(asrReady)return asrReady;asrReady=new Promise(async(res,rej)=>{
|
|
242
247
|
asrWorker=new Worker(URL.createObjectURL(new Blob([WORKER_SRC],{type:"text/javascript"})),{type:"module"});
|
|
243
|
-
const files={};asrWorker.onmessage=e=>{const m=e.data;if(m.type==="progress"){files[m.file]=m.pct;const v=Object.values(files);asrProgress(Math.round(v.reduce((a,b)=>a+b,0)/v.length))}else if(m.type==="ready")res();else if(m.type==="error"&&m.seq==null)rej(new Error(m.error));for(const s of sessions)s.onmsg(m)};
|
|
248
|
+
const files={};asrWorker.onmessage=e=>{const m=e.data;if(m.type==="progress"){files[m.file]=m.pct;const v=Object.values(files);asrProgress(Math.round(v.reduce((a,b)=>a+b,0)/v.length))}else if(m.type==="ready")res();else if(m.type==="fallback")toast("GPU could not run the model, using the CPU: "+m.why);else if(m.type==="error"&&m.seq==null)rej(new Error(m.error));for(const s of sessions)s.onmsg(m)};
|
|
244
249
|
asrWorker.onerror=e=>rej(new Error(e.message||"worker failed"));
|
|
245
|
-
|
|
250
|
+
// Ask the adapter what it can do: no adapter means the CPU path; no shader-f16 means an fp32 encoder.
|
|
251
|
+
let adapter=null;try{adapter=navigator.gpu&&await navigator.gpu.requestAdapter()}catch(e){}
|
|
252
|
+
if(!adapter)toast("No WebGPU: dictation will run on the CPU and be slower");asrWorker.postMessage({type:"load",webgpu:!!adapter,f16:!!(adapter&&adapter.features.has("shader-f16"))})});
|
|
246
253
|
asrReady.catch(()=>{asrReady=null;try{asrWorker.terminate()}catch(e){}asrWorker=null});return asrReady}
|
|
247
254
|
// After a re-render only a listener whose field is gone is stopped; closing the panel stops any.
|
|
248
255
|
function stopDictation(all){if(active&&(all||!active.field.isConnected))active.stop()}
|