nixbot

builds

failed treefmt-check checks.aarch64-darwin.formatting · build #602 · raw

1treefmt v2.6.0traversed 1126 files2emitted 455 files for processing3formatted 455 files (4 changed) in 985ms4 M home/.pi/agent/extensions/inbox.ts5 M home/.pi/agent/extensions/python/index.ts6diff --git a/home/.pi/agent/extensions/inbox.ts b/home/.pi/agent/extensions/inbox.ts7index 0b5f038..f693974 1006448--- a/home/.pi/agent/extensions/inbox.ts9+++ b/home/.pi/agent/extensions/inbox.ts10@@ -30,15 +30,25 @@ function parse(raw: string): Event {11 }12 13 export default function (pi: ExtensionAPI) {14- const path = join(process.env.XDG_RUNTIME_DIR ?? "/tmp", "pi-inbox", `${process.pid}.sock`);15+ const path = join(16+ process.env.XDG_RUNTIME_DIR ?? "/tmp",17+ "pi-inbox",18+ `${process.pid}.sock`,19+ );20 21 pi.registerMessageRenderer("inbox", (message, _options, theme) => {22 const ev = message.details as Event;23 let lines = ev.text.split("\n");24 if (lines.length > TAIL_LINES) {25- lines = [`… ${lines.length - TAIL_LINES} lines`, ...lines.slice(-TAIL_LINES)];26+ lines = [27+ `… ${lines.length - TAIL_LINES} lines`,28+ ...lines.slice(-TAIL_LINES),29+ ];30 }31- const title = theme.fg("accent", theme.bold(`▌inbox ${ev.source ?? ""}`.trimEnd()));32+ const title = theme.fg(33+ "accent",34+ theme.bold(`▌inbox ${ev.source ?? ""}`.trimEnd()),35+ );36 return new Text(`${title}\n${theme.fg("muted", lines.join("\n"))}`, 0, 0);37 });38 39@@ -46,20 +56,27 @@ export default function (pi: ExtensionAPI) {40 mkdirSync(join(path, ".."), { recursive: true, mode: 0o700 });41 rmSync(path, { force: true });42 const server = createServer(async (conn) => {43- const raw = (await text(conn).catch(() => "")).slice(0, MAX_BYTES);44- const ev = parse(raw);45- if (!ev.text) return;46- const header = ev.source ? `[inbox: ${ev.source}]` : "[inbox]";47- pi.sendMessage(48- { customType: "inbox", content: `${header}\n${ev.text}`, display: true, details: ev },49- { deliverAs: "followUp", triggerTurn: true },50- );51+ const raw = (await text(conn).catch(() => "")).slice(0, MAX_BYTES);52+ const ev = parse(raw);53+ if (!ev.text) return;54+ const header = ev.source ? `[inbox: ${ev.source}]` : "[inbox]";55+ pi.sendMessage(56+ {57+ customType: "inbox",58+ content: `${header}\n${ev.text}`,59+ display: true,60+ details: ev,61+ },62+ { deliverAs: "followUp", triggerTurn: true },63+ );64 }).listen(path);65 process.env.PI_INBOX = path;66 67 pi.on("session_start", async (_event, ctx) => {68 const file = ctx.sessionManager.getSessionFile();69- process.env.PI_SESSION_ID = file ? basename(file, ".jsonl") : `pid-${process.pid}`;70+ process.env.PI_SESSION_ID = file71+ ? basename(file, ".jsonl")72+ : `pid-${process.pid}`;73 });74 75 pi.on("session_shutdown", async () => {76diff --git a/home/.pi/agent/extensions/python/index.ts b/home/.pi/agent/extensions/python/index.ts77index ca86ebf..a841c8a 10064478--- a/home/.pi/agent/extensions/python/index.ts79+++ b/home/.pi/agent/extensions/python/index.ts80@@ -58,121 +58,127 @@ export default function (pi: ExtensionAPI) {81 registerTool();82 });83 84- const registerTool = () => pi.registerTool({85- name: "python",86- label: "python",87- description:88- "Execute Python code in a persistent interpreter: variables, imports and open files survive between calls for the whole session. " +89- "The value of a trailing expression is echoed like in a REPL; use print() for anything else. " +90- "matplotlib figures are returned as images. Available: polars, matplotlib, requests, plumbum, pexpect, pyelftools + stdlib. " +91- `Output is truncated to the last ${DEFAULT_MAX_LINES} lines / ${92- formatSize(DEFAULT_MAX_BYTES)93- }.`,94- promptSnippet:95- "Run Python in a persistent interpreter (state kept across calls; polars, matplotlib, requests, pexpect, pyelftools)",96- promptGuidelines: [97- "Use python instead of bash for multi-step data work (JSON/CSV/logs/ELF), calculations, and anything where re-parsing input on every call would be wasteful; state persists, so load once and iterate.",98- "Drive interactive programs (ssh, REPLs, debuggers, installers) with pexpect inside the python tool: `child = pexpect.spawn(cmd, encoding='utf-8')` persists across calls; always pass `timeout=` to expect().",99- ...(process.env.PI_INBOX100- ? [101- "Don't block on long waits in python: run them in a `threading.Thread` that calls the predefined `notify(text, source=\"python\")` when done. It arrives later as an `[inbox: <source>]` message.",102- ]103- : []),104- ],105- parameters: Type.Object({106- code: Type.String({ description: "Python source to execute" }),107- timeout: Type.Optional(108- Type.Number({109- description: "Timeout in seconds (interrupts the cell, keeps state)",110- }),111- ),112- }),113+ const registerTool = () =>114+ pi.registerTool({115+ name: "python",116+ label: "python",117+ description:118+ "Execute Python code in a persistent interpreter: variables, imports and open files survive between calls for the whole session. " +119+ "The value of a trailing expression is echoed like in a REPL; use print() for anything else. " +120+ "matplotlib figures are returned as images. Available: polars, matplotlib, requests, plumbum, pexpect, pyelftools + stdlib. " +121+ `Output is truncated to the last ${DEFAULT_MAX_LINES} lines / ${122+ formatSize(DEFAULT_MAX_BYTES)123+ }.`,124+ promptSnippet:125+ "Run Python in a persistent interpreter (state kept across calls; polars, matplotlib, requests, pexpect, pyelftools)",126+ promptGuidelines: [127+ "Use python instead of bash for multi-step data work (JSON/CSV/logs/ELF), calculations, and anything where re-parsing input on every call would be wasteful; state persists, so load once and iterate.",128+ "Drive interactive programs (ssh, REPLs, debuggers, installers) with pexpect inside the python tool: `child = pexpect.spawn(cmd, encoding='utf-8')` persists across calls; always pass `timeout=` to expect().",129+ ...(process.env.PI_INBOX130+ ? [131+ 'Don\'t block on long waits in python: run them in a `threading.Thread` that calls the predefined `notify(text, source="python")` when done. It arrives later as an `[inbox: <source>]` message.',132+ ]133+ : []),134+ ],135+ parameters: Type.Object({136+ code: Type.String({ description: "Python source to execute" }),137+ timeout: Type.Optional(138+ Type.Number({139+ description:140+ "Timeout in seconds (interrupts the cell, keeps state)",141+ }),142+ ),143+ }),144 145- // Without this pi's fallback renders only the tool name, hiding the code.146- renderCall(args, theme, context) {147- const text = (context.lastComponent as Text | undefined) ??148- new Text("", 0, 0);149- let out = theme.fg("toolTitle", theme.bold("python"));150- if (args.timeout) out += theme.fg("muted", ` (timeout ${args.timeout}s)`);151- const code = (args.code ?? "").trimEnd();152- if (code) out += "\n" + highlightCode(code, "python").join("\n");153- text.setText(out);154- return text;155- },156+ // Without this pi's fallback renders only the tool name, hiding the code.157+ renderCall(args, theme, context) {158+ const text = (context.lastComponent as Text | undefined) ??159+ new Text("", 0, 0);160+ let out = theme.fg("toolTitle", theme.bold("python"));161+ if (args.timeout) {162+ out += theme.fg("muted", ` (timeout ${args.timeout}s)`);163+ }164+ const code = (args.code ?? "").trimEnd();165+ if (code) out += "\n" + highlightCode(code, "python").join("\n");166+ text.setText(out);167+ return text;168+ },169 170- renderResult(result, options, theme, context) {171- const text = (context.lastComponent as Text | undefined) ??172- new Text("", 0, 0);173- const output = result.content174- .filter((c) => c.type === "text")175- .map((c) => (c as { text: string }).text)176- .join("\n")177- .trimEnd();178- const images = result.content.filter((c) => c.type === "image").length;179- const lines = output ? output.split("\n") : [];180- // Tail, not head: tracebacks and REPL results come last.181- const shown = options.expanded ? lines : lines.slice(-10);182- const color = context.isError ? "error" : "toolOutput";183- let out = theme.fg("muted", "─── output ───");184- if (lines.length > shown.length) {185- out += theme.fg(186- "muted",187- `\n... (${lines.length - shown.length} earlier lines, `,188- ) + keyHint("app.tools.expand", "to expand") + theme.fg("muted", ")");189- }190- if (shown.length) {191- out += "\n" + shown.map((l) => theme.fg(color, l)).join("\n");192- }193- if (images) out += theme.fg("muted", `\n[${images} image(s)]`);194- text.setText(out);195- return text;196- },197+ renderResult(result, options, theme, context) {198+ const text = (context.lastComponent as Text | undefined) ??199+ new Text("", 0, 0);200+ const output = result.content201+ .filter((c) => c.type === "text")202+ .map((c) => (c as { text: string }).text)203+ .join("\n")204+ .trimEnd();205+ const images = result.content.filter((c) => c.type === "image").length;206+ const lines = output ? output.split("\n") : [];207+ // Tail, not head: tracebacks and REPL results come last.208+ const shown = options.expanded ? lines : lines.slice(-10);209+ const color = context.isError ? "error" : "toolOutput";210+ let out = theme.fg("muted", "─── output ───");211+ if (lines.length > shown.length) {212+ out += theme.fg(213+ "muted",214+ `\n... (${lines.length - shown.length} earlier lines, `,215+ ) + keyHint("app.tools.expand", "to expand") + theme.fg("muted", ")");216+ }217+ if (shown.length) {218+ out += "\n" + shown.map((l) => theme.fg(color, l)).join("\n");219+ }220+ if (images) out += theme.fg("muted", `\n[${images} image(s)]`);221+ text.setText(out);222+ return text;223+ },224 225- async execute(_id, params, signal, _onUpdate, ctx) {226- kernel ??= new Kernel(python, ctx.cwd);227- const ac = new AbortController();228- signal?.addEventListener("abort", () => ac.abort(), { once: true });229- let timedOut = false;230- const timer = params.timeout231- ? setTimeout(() => {232- timedOut = true;233- ac.abort();234- }, params.timeout * 1000)235- : undefined;236- const r = await kernel.exec(params.code, ac.signal).finally(() =>237- clearTimeout(timer)238- );239- // Throw like bash so pi ends the run as "aborted" and sends queued prompts.240- if (signal?.aborted) throw new Error("Command aborted");241- if (timedOut && r.error?.startsWith("KeyboardInterrupt")) {242- r.error =243- `Timed out after ${params.timeout}s (cell interrupted, interpreter state kept)`;244- }245+ async execute(_id, params, signal, _onUpdate, ctx) {246+ kernel ??= new Kernel(python, ctx.cwd);247+ const ac = new AbortController();248+ signal?.addEventListener("abort", () => ac.abort(), { once: true });249+ let timedOut = false;250+ const timer = params.timeout251+ ? setTimeout(() => {252+ timedOut = true;253+ ac.abort();254+ }, params.timeout * 1000)255+ : undefined;256+ const r = await kernel.exec(params.code, ac.signal).finally(() =>257+ clearTimeout(timer)258+ );259+ // Throw like bash so pi ends the run as "aborted" and sends queued prompts.260+ if (signal?.aborted) throw new Error("Command aborted");261+ if (timedOut && r.error?.startsWith("KeyboardInterrupt")) {262+ r.error =263+ `Timed out after ${params.timeout}s (cell interrupted, interpreter state kept)`;264+ }265 266- let text = r.stdout;267- if (r.stderr) {268- text += (text && !text.endsWith("\n") ? "\n" : "") + r.stderr;269- }270- if (r.result !== null) {271- text += (text && !text.endsWith("\n") ? "\n" : "") + r.result;272- }273- if (r.error) text += (text && !text.endsWith("\n") ? "\n" : "") + r.error;274- const t = truncateTail(text || (r.images.length ? "" : "(no output)"));275- if (t.truncated) {276- t.content =277- `[output truncated: showing last ${t.outputLines} of ${t.totalLines} lines]\n${t.content}`;278- }279+ let text = r.stdout;280+ if (r.stderr) {281+ text += (text && !text.endsWith("\n") ? "\n" : "") + r.stderr;282+ }283+ if (r.result !== null) {284+ text += (text && !text.endsWith("\n") ? "\n" : "") + r.result;285+ }286+ if (r.error) {287+ text += (text && !text.endsWith("\n") ? "\n" : "") + r.error;288+ }289+ const t = truncateTail(text || (r.images.length ? "" : "(no output)"));290+ if (t.truncated) {291+ t.content =292+ `[output truncated: showing last ${t.outputLines} of ${t.totalLines} lines]\n${t.content}`;293+ }294 295- const content: ({ type: "text"; text: string } | {296- type: "image";297- data: string;298- mimeType: string;299- })[] = [];300- if (t.content) content.push({ type: "text", text: t.content });301- for (const data of r.images) {302- content.push({ type: "image", data, mimeType: "image/png" });303- }304- return { content, details: undefined, isError: r.error !== null };305- },306- });307+ const content: ({ type: "text"; text: string } | {308+ type: "image";309+ data: string;310+ mimeType: string;311+ })[] = [];312+ if (t.content) content.push({ type: "text", text: t.content });313+ for (const data of r.images) {314+ content.push({ type: "image", data, mimeType: "image/png" });315+ }316+ return { content, details: undefined, isError: r.error !== null };317+ },318+ });319 }