SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
3.1 KB · 91 lines tsx
Raw Blame History
1/**2 * KHAELOR3 * File: prototypes/tui-spike/driver/debug-ink-input.tsx4 * Description: Diagnostic harness — traces stdin arrival vs Ink useInput delivery vs echo-frame5 *              write time, to attribute candidate B's measured input latency (skew vs real stall).6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 */1011import fs from "node:fs";12import React, { useEffect, useReducer, useState } from "react";13import { Box, Static, Text, render, useApp, useInput } from "ink";14import { DemoModel } from "../shared/demo";1516const log: string[] = [];17const t0 = process.hrtime.bigint();18const ms = () => Number(process.hrtime.bigint() - t0) / 1e6;1920const model = new DemoModel();2122process.stdin.on("data", (b: Buffer) => {23  log.push(`${ms().toFixed(1)} DATA ${JSON.stringify(b.toString("utf8"))}`);24});2526const origWrite = process.stdout.write.bind(process.stdout);27let lastComposer = "";28(process.stdout as unknown as { write: unknown }).write = (chunk: unknown, ...rest: unknown[]) => {29  const s = typeof chunk === "string" ? chunk : Buffer.from(chunk as Uint8Array).toString("utf8");30  if (lastComposer.length > 0 && s.includes("❯ " + lastComposer)) {31    log.push(`${ms().toFixed(1)} ECHO ${JSON.stringify(lastComposer.slice(-8))}`);32    lastComposer = "";33  }34  return (origWrite as (c: unknown, ...r: unknown[]) => boolean)(chunk, ...rest);35};3637function App(): React.JSX.Element {38  const { exit } = useApp();39  const [settled, setSettled] = useState<{ id: number; text: string }[]>([]);40  const [, force] = useReducer((x: number) => x + 1, 0);4142  useEffect(() => {43    model.on("settled", (lines: string[]) =>44      setSettled((s) => [...s, { id: s.length, text: lines.join("\n") }]),45    );46    model.on("dirty", () => force());47    const ticker = setInterval(() => {48      if (model.status !== null) force();49    }, 100);50    model.start();51    return () => clearInterval(ticker);52  }, []);5354  useInput((input, key) => {55    log.push(56      `${ms().toFixed(1)} USEINPUT ${JSON.stringify(input)} esc=${key.escape} ret=${key.return}`,57    );58    if (key.escape) {59      exit();60      return;61    }62    if (key.return) {63      model.handleKey({ type: "enter" });64      log.push(`${ms().toFixed(1)} ENTER composer_now=${JSON.stringify(model.composer)}`);65      return;66    }67    for (const ch of input) model.handleKey({ type: "char", ch });68    lastComposer = model.composer;69    log.push(`${ms().toFixed(1)} COMPOSER ${JSON.stringify(model.composer.slice(-12))}`);70  });7172  const snap = model.snapshot();73  const tail = snap.tail.length > 0 ? snap.tail.split("\n").slice(-12) : [];74  return (75    <>76      <Static items={settled}>{(i) => <Text key={i.id}>{i.text}</Text>}</Static>77      <Box flexDirection="column">78        {tail.length > 0 && <Text>{tail.join("\n")}</Text>}79        <Text>{"❯ " + snap.composer}</Text>80      </Box>81    </>82  );83}8485const inst = render(<App />, { exitOnCtrlC: true, patchConsole: false });86setTimeout(() => inst.unmount(), Number(process.env.DURATION_MS ?? 6000)).unref();87await inst.waitUntilExit();88model.stop();89fs.writeFileSync(process.env.DEBUG_OUT ?? "out/debug-ink.log", log.join("\n") + "\n");90process.exit(0);91