refactor: rombak jadi full CLI/TUI only — hapus semua interface server (api/ws/grpc/web/daemon) + wire TUI ke agent beneran

- Hapus total interfaces: api (REST), ws (WebSocket), grpc, web, daemon (IPC)
- Hapus infra server-only yang jadi dead code: infrastructure/ipc + seluruh
  stack auth (domain/auth, application/auth, persistence/iam, infrastructure/auth jwt/password/oauth_loopback)
- TUI (OpenTUI) jadi interface utama: wire ke runSingleProcess() real runtime,
  ganti mock '(no LLM configured)' dengan turnService.runTurn asli + streaming
  stream_token/reasoning/tool_result/system_note/error ke transcript
- Tampilkan model asli di header (mis. claude-opus-5), abort turn via Ctrl+C
- CLI jadi mode headless tambahan: zesdex --headless '<prompt>' one-shot turn
- Bersihkan package.json (script tui/headless), tsconfig paths, docker-compose,
  AGENTS.md; hapus StartOAuth/login stub
- verified: tsc 0 err, 70 test pass, build dist/zesdex; headless 'pong' real; TUI
  interactive render + real turn 'say pong in one word' -> assistant 'pong'
This commit is contained in:
asepharyana
2026-09-03 01:19:27 +07:00
parent 34322e492a
commit 7e6ed34f0d
68 changed files with 250 additions and 3661 deletions
+79 -15
View File
@@ -119,27 +119,78 @@ export function ZesdexApp(props: { state: AppStateRest; onQuit: () => void }): R
const dimensions = useTerminalDimensionsHook();
// Local-only "turn drain": when a turn is submitted without a live LLM,
// simulate a short thinking delay then append a note. Real wiring happens
// in the CLI. Replaced the old raw-loop poll.
// Turn driver: when a turn is submitted, run it against the real agent and
// stream every event into the transcript as it happens.
useEffect(() => {
const timer = setInterval(() => {
if (state.pendingSubmit != null) {
const text = state.pendingSubmit;
let turnRunning = false;
const poll = async () => {
if (turnRunning) return;
const text = state.pendingSubmit;
if (text == null) return;
if (state.turnRunner == null) {
appendToLastTranscript(
state,
"\n(no agent runtime — run via `zesdex` on the CLI / ensure settings)",
false,
);
state.pendingSubmit = null;
state.turnInFlightFlag.value = true;
setTimeout(() => {
state.turnInFlightFlag.value = false;
appendToLastTranscript(state, `(no LLM configured — processed: "${text}")`, false);
setTick((x: number) => x + 1);
}, 50);
return;
}
setTick((x: number) => x + 1);
}, 120);
turnRunning = true;
const captured = state.pendingSubmit;
state.pendingSubmit = null;
state.turnInFlightFlag.value = true;
const abort = new AbortController();
state.turnAbort = abort;
// Open an assistant message we stream into.
pushTranscript(state, makeChatMessage(Roles.Assistant, ""));
try {
await state.turnRunner(captured ?? text, (ev) => {
switch (ev.kind) {
case "stream_token":
appendToLastTranscript(state, ev.content, false);
break;
case "stream_reasoning":
appendToLastTranscript(state, ev.content, true);
break;
case "system_note":
pushTranscript(
state,
makeChatMessage(Roles.System, `[${ev.systemKind}] ${ev.message}`),
);
break;
case "error":
pushTranscript(state, makeChatMessage(Roles.System, `error: ${ev.message}`));
break;
case "tool_result":
pushTranscript(
state,
makeChatMessage(Roles.Tool, `⇄ ${ev.tool_name} · ${ev.is_error ? "err" : "ok"}`),
);
break;
default:
break;
}
});
} finally {
state.turnInFlightFlag.value = false;
state.turnAbort = null;
state.abortFlag.value = false;
// Re-prime the next poll so queued turns run.
setTimeout(() => {
turnRunning = false;
setTick((x: number) => x + 1);
}, 0);
}
};
const timer = setInterval(() => poll(), 120);
return () => clearInterval(timer);
}, [state, setTick]);
const header = ` zesdex · ${state.settings.provider ?? "?"} `;
const model = String(state.settings.provider ?? "?");
const header = ` zesdex · ${model} `;
const messages = state.transcriptCache.messages;
const scrollFromEnd = messages.length - state.scroll.offset;
const visible = messages.slice(Math.max(0, scrollFromEnd - 40), scrollFromEnd);
@@ -159,6 +210,19 @@ export function ZesdexApp(props: { state: AppStateRest; onQuit: () => void }): R
{oneLine(m.content)}
</text>
))}
{/* Reasoning block for the last assistant message (dimmed). */}
{messages.length > 0 &&
(() => {
const last = messages[messages.length - 1];
if (last && last.role === Roles.Assistant && last.reasoning.length > 0) {
return (
<text fg="gray">
{`reasoning ${oneLine(last.reasoning)}`}
</text>
);
}
return null;
})()}
</scrollbox>
{/* Overlay or input area */}