demobites 1.22.0 → 1.24.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/launcher/index.mjs +17 -1
- package/package.json +1 -1
- package/scripts/record.mjs +29 -1
package/launcher/index.mjs
CHANGED
|
@@ -36,7 +36,8 @@ const warn = (m) => console.log(` ! ${m}`);
|
|
|
36
36
|
|
|
37
37
|
// The skill's home and the project's key. The key lives in <cwd>/.recorder/config.json
|
|
38
38
|
// (login.mjs writes it there), so everything here is per-project.
|
|
39
|
-
|
|
39
|
+
// A test home (DEMOBITE_HOME) keeps its own skill copy too: two recorders on one Mac must never refresh each other's camera.
|
|
40
|
+
const skillsDir = process.env.DEMOBITE_HOME ? path.join(process.env.DEMOBITE_HOME, "skills") : path.join(os.homedir(), ".claude", "skills");
|
|
40
41
|
const dest = path.join(skillsDir, "agentic-recorder");
|
|
41
42
|
const readCfg = () => {
|
|
42
43
|
try { return JSON.parse(fs.readFileSync(path.join(process.cwd(), ".recorder", "config.json"), "utf8")); }
|
|
@@ -669,9 +670,24 @@ if (arg === "listen") {
|
|
|
669
670
|
if (SERVICE) removeService(); // bootout ends this process; the handlers below clear the lock
|
|
670
671
|
bye("OFF");
|
|
671
672
|
};
|
|
673
|
+
// FORGET FROM THE APP (1.24.0, founder 2026-10-17: "we can and should forget from here as well"): the
|
|
674
|
+
// app's order drops one site's folder from this machine's bindings; the planner works from the live app there.
|
|
675
|
+
const runForget = async (job) => {
|
|
676
|
+
noteState({ busy: true, job: job.id });
|
|
677
|
+
await event(job.id, "started", { machine: name });
|
|
678
|
+
let site = "";
|
|
679
|
+
try { site = siteOf(new URL(job.url).hostname); } catch { /* below */ }
|
|
680
|
+
const file = reposFile(process.cwd());
|
|
681
|
+
const repos = readJson(file) ?? {};
|
|
682
|
+
if (site && repos[site]) { delete repos[site]; writeJson(file, repos); }
|
|
683
|
+
console.log(` ○ ${site || "that site"} no longer has code on this machine, on your word from the app.`);
|
|
684
|
+
await event(job.id, "ready", { forgot: site, machine: name });
|
|
685
|
+
noteState({ busy: false, job: null, last_error: null });
|
|
686
|
+
};
|
|
672
687
|
const runJob = async (job) => {
|
|
673
688
|
waitOff();
|
|
674
689
|
if (job.kind === "off") return runOff(job);
|
|
690
|
+
if (job.kind === "forget") return runForget(job);
|
|
675
691
|
if (job.kind === "look" || job.kind === "signin" || job.kind === "signout") return runLookOrSignin(job);
|
|
676
692
|
const model = typeof job.model === "string" && job.model ? job.model : JOB_MODEL;
|
|
677
693
|
// Alive while filming (1.13.1): a heartbeat every 5 s so the rail never shows this machine
|
package/package.json
CHANGED
package/scripts/record.mjs
CHANGED
|
@@ -367,6 +367,33 @@ const pushMouse = (type, x, y, button = null, extra = null) => {
|
|
|
367
367
|
// has nothing to sit on between moves.
|
|
368
368
|
const sampler = setInterval(() => pushMouse("move", cx, cy), CURSOR_SAMPLE_MS);
|
|
369
369
|
|
|
370
|
+
// THE LIVE FRAME (founder 2026-10-16, "a thumbnail of what we're recording"): a small still of what the
|
|
371
|
+
// camera sees, posted at every step and every few seconds, so the Recordings row in the app shows the
|
|
372
|
+
// take as it happens. Never on the take's path: a frame that fails is dropped, never waited on. One CDP
|
|
373
|
+
// capture at ~480 px wide, JPEG, well under 40 KB; the server keeps only the latest and drops it when
|
|
374
|
+
// the take ends.
|
|
375
|
+
const FRAME_EVERY_MS = 5000;
|
|
376
|
+
let frameCdp = null;
|
|
377
|
+
let framing = false;
|
|
378
|
+
let frameStep = { n: 0, N: 0 };
|
|
379
|
+
let frameWarned = false;
|
|
380
|
+
const postFrame = async () => {
|
|
381
|
+
if (framing || !frameStep.N) return;
|
|
382
|
+
framing = true;
|
|
383
|
+
// A capture can stall while the page navigates (the first take of 2026-10-16 posted nothing: one hung
|
|
384
|
+
// capture held the flag for the whole take). Four seconds, then the frame is dropped and the next one tries.
|
|
385
|
+
const within = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error(`timeout ${ms} ms`)), ms))]);
|
|
386
|
+
try {
|
|
387
|
+
frameCdp ??= await within(ctx.newCDPSession(page), 4000);
|
|
388
|
+
const scale = Math.min(1, 480 / VIEW.width);
|
|
389
|
+
const { data } = await within(frameCdp.send("Page.captureScreenshot", { format: "jpeg", quality: 55, clip: { x: 0, y: 0, width: VIEW.width, height: VIEW.height, scale } }), 4000);
|
|
390
|
+
await within(postEvent("frame", { ...frameStep, jpeg: data }), 8000);
|
|
391
|
+
} catch (e) {
|
|
392
|
+
if (!frameWarned) { frameWarned = true; console.error(`frame: dropped (${String(e?.message ?? e).split("\n")[0]})`); }
|
|
393
|
+
} finally { framing = false; }
|
|
394
|
+
};
|
|
395
|
+
const framer = setInterval(() => void postFrame(), FRAME_EVERY_MS);
|
|
396
|
+
|
|
370
397
|
/** Ask the element what cursor it shows. Takes the locator visibleBox already
|
|
371
398
|
* resolved — re-scanning cost ~1s of dead video per step, which inflated a
|
|
372
399
|
* 29s take to 36s purely from instrumentation. Playwright locators pierce
|
|
@@ -779,7 +806,7 @@ try {
|
|
|
779
806
|
process.stdout.write(`step ${rec.n} ${step.action} ${step.label ?? ""}\n`);
|
|
780
807
|
// The customer's line (founder 2026-10-14): one bar, step n of N. The
|
|
781
808
|
// agent relays this, never the step/action lines above.
|
|
782
|
-
{ const N = STORYBOARD.steps.length, filled = Math.round((rec.n / N) * 20); process.stdout.write(`Filming ${"█".repeat(filled)}${"░".repeat(20 - filled)} step ${rec.n} of ${N}\n`); void postEvent("filming", { n: rec.n, N }); }
|
|
809
|
+
{ const N = STORYBOARD.steps.length, filled = Math.round((rec.n / N) * 20); process.stdout.write(`Filming ${"█".repeat(filled)}${"░".repeat(20 - filled)} step ${rec.n} of ${N}\n`); void postEvent("filming", { n: rec.n, N }); frameStep = { n: rec.n, N }; void postFrame(); }
|
|
783
810
|
if (step.action === "goto") {
|
|
784
811
|
const navIssuedAt = t();
|
|
785
812
|
// domcontentloaded, not load: a heavy site behind a proxy (the cloud
|
|
@@ -1210,6 +1237,7 @@ try {
|
|
|
1210
1237
|
}
|
|
1211
1238
|
|
|
1212
1239
|
clearInterval(sampler);
|
|
1240
|
+
clearInterval(framer);
|
|
1213
1241
|
manifest.duration = t();
|
|
1214
1242
|
void postEvent("finishing", { text: "Camera off. Saving the take…" });
|
|
1215
1243
|
const video = page.video();
|