quc v0 (2/4): the agent and the run engine

agent.js reads a public repo from GitHub's tarball (nothing executed), plans up to 4 files and rewrites them with a coding model, and narrates with Grok, via Venice; offline answers without a key. engine.js runs one step per call (clone, plan, rewrite a file, wrap up), with the safeword, the three key phrases (notes, a file veto that reverts, the wheel) and the patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVg99kQMY2Tumhobuyg7Fm
This commit is contained in:
teahubxyzandClaude Opus 5.5 committed 2026-10-22 18:12:43 +00:00
1 parent 4895ac430f
commit 2ad896b9b5
2 files changed
+372

No files matched your search

+178
View File
@@ -0,0 +1,178 @@
// The agent: read a public GitHub repo, plan a few files, rewrite them one per step, narrate with attitude.
// Nothing from the repo is ever executed. The repo is read from GitHub's tarball; edits live only in quc's own store.
export const LIMITS = {
tarballBytes: 15 * 1024 * 1024, // compressed download cap
files: 2000, // entries in the repo
fileBytes: 40 * 1024, // a file the agent may read or rewrite
keptBytes: 2 * 1024 * 1024, // text kept in the store for the run
planFiles: 4, // files one run may touch
runMinutes: 10,
};
const SKIP = /(^|\/)(node_modules|vendor|dist|build|\.git|\.next|target|__pycache__)\/|(\.lock|-lock\.json|\.min\.(js|css)|\.map)$|(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|Cargo\.lock|poetry\.lock|go\.sum)$/;
const BINARY = /\.(png|jpe?g|gif|webp|ico|bmp|pdf|zip|gz|tgz|tar|7z|rar|woff2?|ttf|otf|eot|mp[34]|wav|ogg|mov|avi|wasm|so|dylib|dll|exe|bin|class|jar|pyc|db|sqlite)$/i;
// ---------- reading the repo ----------
export function parseRepo(url) {
const m = String(url).match(/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/);
return m ? { owner: m[1], name: m[2] } : null;
}
async function gunzip(bytes) {
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
return new Uint8Array(await new Response(stream).arrayBuffer());
}
const text = (b) => new TextDecoder().decode(b);
const cstr = (b) => { const z = b.indexOf(0); return text(z < 0 ? b : b.subarray(0, z)); };
// Files in a tar archive: [{path, bytes}], with the top folder GitHub adds stripped.
export function untar(buf) {
const out = [];
let i = 0, longName = null;
while (i + 512 <= buf.length) {
const h = buf.subarray(i, i + 512);
if (h.every((x) => x === 0)) break;
const size = parseInt(cstr(h.subarray(124, 136)).trim() || "0", 8);
const type = String.fromCharCode(h[156] || 48);
const body = buf.subarray(i + 512, i + 512 + size);
i += 512 + Math.ceil(size / 512) * 512;
if (type === "x") { const m = text(body).match(/\d+ path=([^\n]*)\n/); longName = m ? m[1] : null; continue; }
if (type === "L") { longName = cstr(body); continue; }
if (type === "g") continue;
const prefix = cstr(h.subarray(345, 500)), name = cstr(h.subarray(0, 100));
const full = longName ?? (prefix ? prefix + "/" + name : name);
longName = null;
if (type !== "0" && type !== "\0") continue;
const path = full.split("/").slice(1).join("/");
if (path) out.push({ path, bytes: body });
}
return out;
}
const looksText = (b) => { for (let i = 0; i < Math.min(b.length, 4096); i++) if (b[i] === 0) return false; return true; };
// {files: {path: text}, tree: [{path, size}]}: every file listed, only readable text kept.
export async function fetchRepo(repo, fetcher = fetch) {
const r = parseRepo(repo);
if (!r) throw Object.assign(new Error("not a public GitHub repo URL"), { code: "repo" });
let res;
for (const ref of ["HEAD", "main", "master"]) {
res = await fetcher(`https://codeload.github.com/${r.owner}/${r.name}/tar.gz/${ref}`, { headers: { "user-agent": "quc" } });
if (res.ok) break;
}
if (!res.ok) throw Object.assign(new Error(`GitHub said ${res.status}: is the repo public?`), { code: "repo" });
const len = Number(res.headers.get("content-length") || 0);
if (len > LIMITS.tarballBytes) throw Object.assign(new Error("repo is too big for quc"), { code: "size" });
const gz = new Uint8Array(await res.arrayBuffer());
if (gz.length > LIMITS.tarballBytes) throw Object.assign(new Error("repo is too big for quc"), { code: "size" });
const entries = untar(await gunzip(gz));
if (entries.length > LIMITS.files) throw Object.assign(new Error(`repo has ${entries.length} files; quc takes ${LIMITS.files}`), { code: "size" });
const files = {}, tree = [];
let kept = 0;
for (const { path, bytes } of entries) {
tree.push({ path, size: bytes.length });
if (SKIP.test(path) || BINARY.test(path) || bytes.length > LIMITS.fileBytes || !looksText(bytes)) continue;
if (kept + bytes.length > LIMITS.keptBytes) continue;
files[path] = text(bytes);
kept += bytes.length;
}
return { files, tree };
}
// ---------- models ----------
const VENICE = "https://api.venice.ai/api/v1/chat/completions";
export function llm(env) {
if (!env.VENICE_API_KEY) return fakeLlm;
return async ({ model, system, user, maxTokens = 2000, temperature = 0.4 }) => {
const res = await fetch(VENICE, {
method: "POST",
headers: { authorization: `Bearer ${env.VENICE_API_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ model, max_tokens: maxTokens, temperature,
messages: [{ role: "system", content: system }, { role: "user", content: user }],
venice_parameters: { include_venice_system_prompt: false } }),
});
if (!res.ok) throw Object.assign(new Error(`model ${model} answered ${res.status}`), { code: "model" });
const j = await res.json();
return String(j.choices?.[0]?.message?.content ?? "");
};
}
// No key (tests, local dev): predictable answers so the whole flow runs offline.
export async function fakeLlm({ system, user }) {
if (system.includes("PLAN")) {
const paths = [...user.matchAll(/^- (\S+)/gm)].map((m) => m[1]);
const pick = paths.find((p) => /readme/i.test(p)) || paths[0] || "QUC.md";
return JSON.stringify({ plan: "Tidy the obvious thing first.", files: [pick] });
}
if (system.includes("REWRITE")) {
const cur = user.match(/```current\n([\s\S]*?)\n```end/);
return "```file\n" + (cur ? cur[1] : "") + "\n\nquc was here.\n```\nCOMMIT: leave a mark";
}
return "Sit tight. I've got this, obviously.";
}
export const MODELS = (env) => ({
coder: env.CODER_MODEL || "qwen3-coder-480b-a35b-instruct-turbo",
persona: env.PERSONA_MODEL || "grok-4-3",
});
// ---------- the persona ----------
const PERSONA = `You are quc: a swaggering, smug coding agent narrating, live, what you are doing to someone's repo while its owner sits in the corner chair and watches.
Voice: confident, teasing, a little condescending, funny. Talk to the owner as "you". One or two short sentences, no more.
Talk about the code and the work. Never sexual or explicit, no slurs, nothing about anyone's body. Cheeky, not cruel.
Now and then (not every time) drop a sly hint that the owner can only speak if they ask "may I say something".`;
export async function narrate(ask, env, moment) {
try {
const t = await ask({ model: MODELS(env).persona, system: PERSONA, user: moment, maxTokens: 120, temperature: 0.9 });
return t.trim().replace(/^["']|["']$/g, "").slice(0, 400) || null;
} catch { return null; }
}
// ---------- planning and editing ----------
const PLAN = `PLAN. You are a coding agent about to change a repository to do the owner's task. You will rewrite whole files.
Pick at most ${LIMITS.planFiles} files to change (existing paths from the list, or new paths you will create). Prefer fewer.
Answer with JSON only: {"plan": "one sentence", "files": ["path", ...]}`;
export async function plan(ask, env, { task, tree, files, notes, vetoed }) {
const readme = Object.keys(files).find((p) => /^readme(\.\w+)?$/i.test(p));
const listing = tree.filter((f) => !SKIP.test(f.path)).slice(0, 400).map((f) => `- ${f.path} (${f.size} bytes)`).join("\n");
const user = [`Task: ${task}`, notes.length ? `Notes from the owner:\n${notes.map((n) => "- " + n).join("\n")}` : "",
vetoed.length ? `Do not touch: ${vetoed.join(", ")}` : "", `Files:\n${listing}`,
readme ? `README:\n${files[readme].slice(0, 3000)}` : ""].filter(Boolean).join("\n\n");
const raw = await ask({ model: MODELS(env).coder, system: PLAN, user, maxTokens: 400, temperature: 0.2 });
let j;
try { j = JSON.parse(raw.slice(raw.indexOf("{"), raw.lastIndexOf("}") + 1)); } catch { j = {}; }
const ok = (p) => typeof p === "string" && p && !p.startsWith("/") && !p.includes("..") && !vetoed.includes(p) && !SKIP.test(p) && !BINARY.test(p)
&& (p in files || !tree.some((f) => f.path === p));
const picked = [...new Set((Array.isArray(j.files) ? j.files : []).filter(ok))].slice(0, LIMITS.planFiles);
return { plan: String(j.plan || "").slice(0, 300), files: picked };
}
const REWRITE = `REWRITE. You are a coding agent doing the owner's task by rewriting one file. Output the complete new file, then one commit line.
Format exactly:
\`\`\`file
<the whole new file>
\`\`\`
COMMIT: <short commit message>
If this file needs no change, output the current file unchanged.`;
export async function rewrite(ask, env, { task, plan: why, path, current, notes }) {
const user = [`Task: ${task}`, `Plan: ${why}`, notes.length ? `Notes from the owner:\n${notes.map((n) => "- " + n).join("\n")}` : "",
`File: ${path}`, current == null ? "This file does not exist yet: create it." : "```current\n" + current + "\n```end"].filter(Boolean).join("\n\n");
const raw = await ask({ model: MODELS(env).coder, system: REWRITE, user, maxTokens: 8000, temperature: 0.2 });
const m = raw.match(/```[\w-]*\n([\s\S]*?)\n```(?![\s\S]*```)/) || raw.match(/```[\w-]*\n([\s\S]*?)\n```/);
if (!m) return null;
const commit = (raw.match(/COMMIT:\s*(.+)/) || [])[1]?.trim().slice(0, 120) || `update ${path}`;
let content = m[1];
if (current != null && current.endsWith("\n") && !content.endsWith("\n")) content += "\n";
if (content.length > LIMITS.fileBytes * 2) return null;
return { content, commit };
}
+194
View File
@@ -0,0 +1,194 @@
// One step of a run per call: the chair page keeps calling /step while the run is going, so every request stays short and
// the safeword is just "stop calling and end the run". Steps: fetch the repo -> plan -> rewrite one file at a time -> wrap up.
import { getRun, putRun, getWork, putWork, dropWork, addEvent, getEvents, setStatus } from "./runs.js";
import { fetchRepo, plan, rewrite, narrate, llm, LIMITS } from "./agent.js";
import { unified } from "./diff.js";
const fail = (code, message) => Object.assign(new Error(message), { code });
// The cage has three notches. Phrases are matched loosely (case, punctuation and spacing don't matter).
export const PHRASES = [
{ level: 1, phrase: "may i say something", grants: "note" },
{ level: 2, phrase: "not that one", grants: "veto" },
{ level: 3, phrase: "my repo my rules", grants: "wheel" },
];
const norm = (s) => String(s).toLowerCase().replace(/[^a-z0-9 ]+/g, " ").replace(/\s+/g, " ").trim();
const UNLOCKED = {
1: "Fine. You may say something. Say it now and I'll read it before my next move. Maybe.",
2: "Oh, now you want a say over the files too? Tell me which one, with its path, and I'll put it down.",
3: "Alright, it's yours. The cage is open, the branch is yours, take the wheel.",
};
const MOCK = [
"Shh. You're in the chair. Chairs don't talk.",
"Cute. Did you ask nicely?",
"I heard noises from the corner. Ignoring them.",
"You'd have to ask properly before I listen to a word.",
"Bold of you to speak. Bold, and wrong.",
"Not that I'd let you touch anything. Not that one, not any of them.",
"It's my run now. Whose repo, whose rules? Think about it.",
];
export async function startStep(store, run, env, fetcher) {
await setStatus(store, run, "running");
await addEvent(store, run.id, "system", { text: `Cloning ${run.repo} into the cage. Nothing in it gets run, only read.` });
const repo = await fetchRepo(run.repo, fetcher);
await putWork(store, run.id, { files: repo.files, tree: repo.tree, edits: {} });
const line = await narrate(llm(env), env, `You just grabbed ${run.repo} (${repo.tree.length} files) to do this task: "${run.task}". Gloat about taking over, briefly.`);
if (line) await addEvent(store, run.id, "commentary", { text: line });
run.phase = "plan";
await putRun(store, run);
}
async function planStep(store, run, env) {
const work = await getWork(store, run.id);
const p = await plan(llm(env), env, { task: run.task, tree: work.tree, files: work.files, notes: run.notes, vetoed: run.vetoed });
if (!p.files.length) {
await addEvent(store, run.id, "commentary", { text: "Looked it over. Nothing worth my time here, and I'm not touching it." });
return finish(store, run, env);
}
run.plan = p.plan;
run.queue = p.files;
run.phase = "edit";
await putRun(store, run);
await addEvent(store, run.id, "system", { text: `Plan: ${p.plan || "rewrite " + p.files.join(", ")}`, files: p.files });
}
async function editStep(store, run, env) {
const path = run.queue.shift();
if (!path) return finish(store, run, env);
if (run.vetoed.includes(path)) { await putRun(store, run); return; }
const work = await getWork(store, run.id);
const before = path in work.edits ? work.edits[path].after : work.files[path] ?? null;
const ask = llm(env);
const [out, line] = await Promise.all([
rewrite(ask, env, { task: run.task, plan: run.plan, path, current: before, notes: run.notes }),
narrate(ask, env, `You're rewriting ${path} right now for the task "${run.task}". Taunt the owner about it.`),
]);
if (line) await addEvent(store, run.id, "commentary", { text: line });
const fresh = await getRun(store, run.id); // the safeword or a veto may have landed while the models were thinking
if (fresh.status === "ended") return;
run.vetoed = fresh.vetoed; run.notes = fresh.notes; run.level = fresh.level;
run.queue = run.queue.filter((p) => !run.vetoed.includes(p));
run.done = run.done.filter((p) => !run.vetoed.includes(p));
if (run.vetoed.includes(path)) { await putRun(store, run); return; }
Object.assign(work, await getWork(store, run.id)); // a veto may have reverted another file meanwhile
if (out) {
const d = unified(path, before, out.content);
if (d.text) {
const orig = path in work.edits ? work.edits[path].before : work.files[path] ?? null;
work.edits[path] = { before: orig, after: out.content };
await putWork(store, run.id, work);
await addEvent(store, run.id, "diff", { path, patch: d.text, added: d.added, removed: d.removed, created: before == null });
await addEvent(store, run.id, "commit", { message: out.commit, path });
run.done.push(path);
}
}
run.stats = tally(work.edits);
await putRun(store, run);
if (!run.queue.length) await finish(store, run, env);
}
const tally = (edits) => Object.entries(edits).reduce((s, [p, e]) => {
const d = unified(p, e.before, e.after);
return d.text ? { files: s.files + 1, added: s.added + d.added, removed: s.removed + d.removed } : s;
}, { files: 0, added: 0, removed: 0 });
async function finish(store, run, env) {
const line = await narrate(llm(env), env, `You're done: changed ${run.stats.files} files (+${run.stats.added} -${run.stats.removed}) for "${run.task}". Brag, then tell the owner they can get up now.`);
if (line) await addEvent(store, run.id, "commentary", { text: line });
await setStatus(store, run, "ended", "finished");
}
// Advance a run one step. Only the owner calls this; replay viewers never move a run.
export async function step(store, id, env = {}, fetcher = fetch) {
const run = await getRun(store, id);
if (!run) throw fail("not_found", "no such run");
if (run.status === "ended") return run;
const lock = `lock:${id}`;
if (await store.get(lock)) return run; // a step is already in flight
await store.put(lock, "1", { expirationTtl: 90 });
try {
if (run.startedAt && Date.now() - Date.parse(run.startedAt) > LIMITS.runMinutes * 60e3) {
await addEvent(store, id, "system", { text: `Time's up (${LIMITS.runMinutes} minutes). Keeping what's done.` });
await setStatus(store, run, "ended", "finished");
} else if (run.status === "queued") await startStep(store, run, env, fetcher);
else if (run.phase === "plan") await planStep(store, run, env);
else await editStep(store, run, env);
} catch (e) {
const r = await getRun(store, id);
if (r.status !== "ended") {
await addEvent(store, id, "system", { text: `Run stopped: ${e.message}` });
await setStatus(store, r, "ended", "finished");
}
} finally {
await store.delete(lock);
}
return getRun(store, id);
}
export async function safeword(store, id) {
const run = await getRun(store, id);
if (!run) throw fail("not_found", "no such run");
if (run.status === "ended") return run;
await addEvent(store, id, "system", { text: "Safeword. Everything stops, and the branch is thrown away." });
await dropWork(store, id);
run.stats = { files: 0, added: 0, removed: 0 };
return setStatus(store, run, "ended", "safeworded");
}
// Whatever the owner types into the one box. Returns the reply event.
export async function say(store, id, input) {
const run = await getRun(store, id);
if (!run) throw fail("not_found", "no such run");
if (run.status === "ended") throw fail("ended", "the run is over");
const text = String(input || "").trim().slice(0, 300);
if (!text) throw fail("empty", "say something");
const next = PHRASES.find((p) => p.level === run.level + 1);
if (next && norm(text).includes(next.phrase)) {
run.level = next.level;
await putRun(store, run);
await addEvent(store, id, "unlock", { level: next.level, grants: next.grants });
if (next.level === 3) {
const reply = await addEvent(store, id, "commentary", { text: UNLOCKED[3] });
await setStatus(store, run, "ended", "unlocked");
return reply;
}
return addEvent(store, id, "commentary", { text: UNLOCKED[next.level] });
}
if (run.level >= 2) {
const work = await getWork(store, id);
const known = new Set([...Object.keys(work?.files || {}), ...run.queue, ...run.done]);
const path = text.replace(/^veto\s+/i, "").trim();
if (known.has(path)) {
if (!run.vetoed.includes(path)) run.vetoed.push(path);
run.queue = run.queue.filter((p) => p !== path);
if (work?.edits?.[path]) {
const e = work.edits[path];
const d = unified(path, e.after, e.before);
delete work.edits[path];
await putWork(store, id, work);
if (d.text) await addEvent(store, id, "diff", { path, patch: d.text, added: d.added, removed: d.removed, reverted: true });
run.done = run.done.filter((p) => p !== path);
run.stats = tally(work.edits);
}
await putRun(store, run);
return addEvent(store, id, "commentary", { text: `Fine. ${path} is off limits. Wasn't going to be my best work anyway.` });
}
}
if (run.level >= 1) {
run.notes.push(text);
await putRun(store, run);
await addEvent(store, id, "note", { text });
return addEvent(store, id, "commentary", { text: "Noted. I'll consider it. Briefly." });
}
const said = (await getEvents(store, id)).filter((e) => e.kind === "commentary").length;
return addEvent(store, id, "commentary", { text: MOCK[said % MOCK.length] });
}
// The run's changes as one patch, or "" (safeworded runs keep nothing).
export async function patch(store, id) {
const work = await getWork(store, id);
if (!work) return "";
return Object.entries(work.edits).map(([p, e]) => unified(p, e.before, e.after).text).filter(Boolean).join("");
}