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:
+79
-15
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user