2026-09-02 22:18:35 +07:00
|
|
|
#!/usr/bin/env bun
|
|
|
|
|
/**
|
2026-09-03 01:19:27 +07:00
|
|
|
* Zesdex TUI — bootstrapper / composition root.
|
2026-09-02 22:18:35 +07:00
|
|
|
*
|
2026-09-03 01:19:27 +07:00
|
|
|
* Wires the real agent runtime (via `runSingleProcess` from the CLI compose
|
|
|
|
|
* root) into the OpenTUI app, and installs a `turnRunner` that executes real
|
|
|
|
|
* turns against the LLM + tool executor, streaming events into the transcript.
|
2026-09-02 22:18:35 +07:00
|
|
|
*/
|
|
|
|
|
import * as fs from "node:fs";
|
2026-09-03 02:30:12 +07:00
|
|
|
import { newStore, userMessage } from "@zesdex/domain";
|
|
|
|
|
import type { TurnEventSink } from "@zesdex/agent";
|
2026-09-03 01:19:27 +07:00
|
|
|
import { runSingleProcess, type WiredRuntime } from "../cli/compose.ts";
|
|
|
|
|
import { createTuiState, type AppStateRest, type TurnRunner } from "./state.ts";
|
2026-09-03 00:10:01 +07:00
|
|
|
import { bootTui } from "./ui.tsx";
|
2026-09-02 22:18:35 +07:00
|
|
|
|
2026-09-03 01:19:27 +07:00
|
|
|
/** Build the real turn runner from the wired runtime. */
|
|
|
|
|
function makeTurnRunner(runtime: WiredRuntime): TurnRunner {
|
|
|
|
|
return async (text, onEvent) => {
|
|
|
|
|
const abort = new AbortController();
|
|
|
|
|
// Live sink: forward every event to the UI immediately as it happens.
|
|
|
|
|
const sink: TurnEventSink = {
|
|
|
|
|
push(ev) {
|
|
|
|
|
onEvent(ev);
|
|
|
|
|
},
|
|
|
|
|
drain() {
|
|
|
|
|
return [];
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const messages = [userMessage(text)];
|
|
|
|
|
await runtime.turnService.runTurn({
|
|
|
|
|
messages,
|
|
|
|
|
session_dir: runtime.store.base_dir,
|
|
|
|
|
workspace_roots: [process.cwd()],
|
|
|
|
|
turn_events: sink,
|
|
|
|
|
in_flight: { value: false },
|
|
|
|
|
abort,
|
|
|
|
|
api_key: runtime.apiKey,
|
|
|
|
|
model: runtime.model,
|
|
|
|
|
api_base: runtime.apiBase,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Run the interactive OpenTUI with the real agent wired in. */
|
2026-09-02 22:18:35 +07:00
|
|
|
export async function runTui(): Promise<void> {
|
|
|
|
|
const cwd = process.cwd();
|
|
|
|
|
|
2026-09-03 01:19:27 +07:00
|
|
|
// Build the real runtime once (LLM client + tool executor + turn service).
|
|
|
|
|
const runtime = await runSingleProcess();
|
|
|
|
|
const store = newStore();
|
|
|
|
|
void store;
|
|
|
|
|
const dataDir = `${process.env.HOME ?? ""}/.local/share/zesdex`;
|
|
|
|
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
|
|
|
const sessionLabel = new Date().toISOString().replace(/[:T]/g, "-").slice(0, 19);
|
|
|
|
|
const sessionDir = `${dataDir}/sessions/${sessionLabel}`;
|
|
|
|
|
fs.mkdirSync(sessionDir, { recursive: true });
|
|
|
|
|
const memoryDir = `${dataDir}/memory`;
|
|
|
|
|
|
|
|
|
|
const state: AppStateRest = createTuiState([cwd], sessionDir, memoryDir);
|
|
|
|
|
state.turnRunner = makeTurnRunner(runtime);
|
|
|
|
|
state.misc.apiConnected = true;
|
|
|
|
|
// Surface the real effective model/provider in the header.
|
|
|
|
|
state.settings = { provider: runtime.model } as Record<string, unknown>;
|
|
|
|
|
|
2026-09-03 00:10:01 +07:00
|
|
|
await bootTui(state);
|
2026-09-02 22:18:35 +07:00
|
|
|
}
|