feat(rewrite): bootstrap monorepo Bun + port domain layer ke TypeScript

Mulai migrasi Zesdex dari Rust ke TypeScript/Bun.

- Root workspace package.json, tsconfig strict, bun.lock, gitignore fix
- Paket @zesdex/domain (apps/packages/domain):
  - core: ChatMessage/Conversation/ToolCall/SseParser/repairJson/
    sanitizeToolArguments/UsageStats/Store/DomainError
  - auth: Session/SessionId/SessionLock/OAuth + repository & service interfaces
  - cms: AppConfig/Settings/SettingsPatch/Memory/EditLog + repository &
    service interfaces
  - agent: TurnEvent/SessionRuntime/AgentTurnParams/AgentProgress/prompt
  - subagent & workflow models
- Wire-shape JSON disamakan dengan Rust (serde rename/flatten/skip_if)
- 27 unit test Bun port dari test Rust (SseParser, repair_json, resolve_effective_model, session id)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-09-02 22:50:38 +07:00
co-authored by Claude Opus 5
parent 9d1544d799
commit 07e84e43b3
45 changed files with 2315 additions and 1 deletions
@@ -0,0 +1,28 @@
/** Shared default constants. Mirrors `agent/defaults.rs`. */
/** Default LLM provider API base URL. */
export const DEFAULT_API_BASE = "https://opencode.ai/zen/v1";
/** Default LLM model identifier. */
export const DEFAULT_MODEL = "deepseek-v4-flash-free";
/** Fallback JWT secret used only when `JWT_SECRET` env var is unset. */
export const FALLBACK_JWT_SECRET = "dev-secret";
/** Default context window size (256k tokens). */
export const DEFAULT_CONTEXT_WINDOW = 256_000;
/** Maximum tool-call iterations per agent turn. */
export const MAX_TOOL_ITERATIONS = 50;
/** Maximum subagent tool-call iterations. */
export const MAX_SUBAGENT_ITERATIONS = 25;
/** Default LLM request max tokens. */
export const DEFAULT_MAX_TOKENS = 4096;
/** Default temperature for the main agent. */
export const DEFAULT_TEMPERATURE = 0.7;
/** Default temperature for compaction / summary calls. */
export const DEFAULT_COMPACT_TEMPERATURE = 0.3;
+5
View File
@@ -0,0 +1,5 @@
/** Agent domain module — turn events, runtime, progress, prompts, constants. */
export * from "./mod.ts";
export * from "./defaults.ts";
export * from "./progress.ts";
export * from "./prompt.ts";
+194
View File
@@ -0,0 +1,194 @@
/**
* Domain types for agent lifecycle: turn events, session runtime, progress.
* Mirrors `apps/domain/src/agent/mod.rs`.
*/
import type { ChatMessage, JsonValue, ToolCallResult, UsageStats } from "../core/index.ts";
import type { AgentProgress } from "./progress.ts";
/** Which kind of caller (main agent vs subagent vs reviewer) is invoking a tool. */
export type Origin = "Main" | "SubAgent" | "Reviewer";
export const OriginTag = {
Main: "main",
SubAgent: "subagent",
Reviewer: "reviewer",
} as const satisfies Record<Origin, string>;
export function originTag(o: Origin): string {
return OriginTag[o];
}
/** Severity/category of a toast notification. */
export type ToastKind = "Info" | "Success" | "Warning" | "Error" | "Lesson";
/** A transient status message shown in the TUI, auto-dismissed after lifetime_ms. */
export interface Toast {
kind: ToastKind;
message: string;
created_at: number;
lifetime_ms: number;
}
export const DEFAULT_TOAST_LIFETIME_MS = 5000;
export function newToast(kind: ToastKind, message: string): Toast {
return { kind, message, created_at: Date.now(), lifetime_ms: DEFAULT_TOAST_LIFETIME_MS };
}
export function toastExpired(toast: Toast, nowMs: number): boolean {
return nowMs - toast.created_at > toast.lifetime_ms;
}
/**
* Agent status for workflow progress tracking. `Failed` carries a message;
* we represent it as a string union plus an optional error on failures.
*/
export type AgentStatus = "Pending" | "Running" | "Completed" | "Failed" | "Cancelled";
export function agentStatusDisplay(s: AgentStatus, error?: string): string {
switch (s) {
case "Pending":
return "pending";
case "Running":
return "running";
case "Completed":
return "completed";
case "Failed":
return error ? `failed: ${error}` : "failed";
case "Cancelled":
return "cancelled";
}
}
/** Events emitted onto the turn-event queue while an agent turn runs. */
export type TurnEvent =
| { kind: "assistant_message"; message: ChatMessage }
| { kind: "tool_result"; tool_call_id: string; tool_name: string; output: string; is_error: boolean; path: string | null }
| { kind: "system_note"; systemKind: string; message: string }
| { kind: "stream_start" }
| { kind: "stream_token"; content: string }
| { kind: "stream_reasoning"; content: string }
| { kind: "stream_done"; message: ChatMessage }
| { kind: "usage"; tokens_in: number; tokens_out: number }
| { kind: "review_usage"; tokens_in: number; tokens_out: number }
| { kind: "compacted"; messages: ChatMessage[] }
| { kind: "error"; message: string }
| { kind: "done" }
| { kind: "workflow_agent_update"; agent_id: string; agent_name: string; status: AgentStatus; error?: string }
| { kind: "todo_update"; content: string }
| { kind: "plan_update"; content: string }
| { kind: "agent_progress"; progress: AgentProgress };
/** How a pending tool call should be executed when the turn resumes. */
export type ExecutionModel = "Inline" | "Deferred" | "AsyncTokio";
/** A tool call awaiting execution. */
export interface PendingTool {
tool_name: string;
args: JsonValue;
execution_model: ExecutionModel;
}
/** Reference to a background bash job tracked in session state. */
export interface BashJobRef {
id: string;
command: string;
started_at: number;
running: boolean;
}
/** Tracks counts of learned patterns by outcome and lifecycle stage. */
export interface LessonStats {
total: number;
user: number;
feedback: number;
project: number;
reference: number;
active: number;
stale: number;
contradicted: number;
human: number;
verified: number;
unverified: number;
}
export function newLessonStats(): LessonStats {
return {
total: 0, user: 0, feedback: 0, project: 0, reference: 0,
active: 0, stale: 0, contradicted: 0, human: 0, verified: 0, unverified: 0,
};
}
/** Per-session runtime state: message history, pending tool queue, jobs, counters. */
export interface SessionRuntime {
messages: ChatMessage[];
tool_call_results: ToolCallResult[];
pending_tool_queue: PendingTool[];
bash_jobs: BashJobRef[];
subagent_queue: number;
edit_count: number;
consecutive_empty_reviews: number;
session_start: number;
lessons: LessonStats;
review_count: number;
session_dir: string;
usage: UsageStats;
hive_mind_converged: boolean;
}
export function newSessionRuntime(sessionDir: string): SessionRuntime {
return {
messages: [],
tool_call_results: [],
pending_tool_queue: [],
bash_jobs: [],
subagent_queue: 0,
edit_count: 0,
consecutive_empty_reviews: 0,
session_start: Date.now(),
lessons: newLessonStats(),
review_count: 0,
session_dir: sessionDir,
usage: {
tokens_in: 0, tokens_out: 0, last_tokens_in: 0, last_tokens_out: 0,
api_calls: 0, review_tokens: 0, total_ms: 0,
},
hive_mind_converged: false,
};
}
export function runtimePushMessage(rt: SessionRuntime, msg: ChatMessage): void {
rt.messages.push(msg);
}
/** Simple ASCII progress display for a long-running operation. */
export interface ProgressState {
current: number;
total: number;
message: string;
start_time: number;
}
/** Owned parameters required to spawn and execute an agent turn. */
export interface AgentTurnParams {
messages: ChatMessage[];
session_dir: string;
workspace_roots: string[];
/** Event sink — array or queue implementing push/drain semantics. */
turn_events: TurnEventSink;
/** Whether a turn is currently in flight. */
in_flight: { value: boolean };
/** Abort control. */
abort: AbortController;
api_key: string;
model: string;
api_base?: string;
}
/** Minimal event-sink abstraction (backs the Rust `Arc<Mutex<VecDeque>>`). */
export interface TurnEventSink {
/** Append one event. */
push(event: TurnEvent): void;
/** Drain all pending events (FIFO) and return them. */
drain(): TurnEvent[];
}
@@ -0,0 +1,44 @@
/** Progress reporting types for agent/subagent operations. Mirrors `progress.rs`. */
import type { AgentStatus } from "./mod.ts";
/**
* A failure status carries the error text alongside the `Failed` kind;
* success statuses have no error.
*/
export type AgentStatusWithError = Exclude<AgentStatus, "Failed"> | "Failed";
/** Describes progress within a single subagent or workflow-node execution. */
export interface AgentProgress {
/** Unique identifier for this agent. */
agent_id: string;
/** Human-readable display name shown in the TUI sidebar. */
agent_name: string;
/** Current lifecycle status. */
status: AgentStatus;
/** Error message when status === "Failed". */
error?: string;
/** Optional current tool / step being executed. `null` when idle. */
current_tool: string | null;
/** Optional progress range: [completed, total]. `null` = indeterminate. */
steps: [number, number] | null;
}
/** Mark this agent as running with an optional tool name. */
export function agentProgressRunning(agentId: string, agentName: string, currentTool: string | null): AgentProgress {
return { agent_id: agentId, agent_name: agentName, status: "Running", current_tool: currentTool, steps: null };
}
/** Mark this agent as pending (queued but not yet started). */
export function agentProgressPending(agentId: string, agentName: string): AgentProgress {
return { agent_id: agentId, agent_name: agentName, status: "Pending", current_tool: null, steps: null };
}
/** Mark this agent as completed successfully. */
export function agentProgressCompleted(agentId: string, agentName: string): AgentProgress {
return { agent_id: agentId, agent_name: agentName, status: "Completed", current_tool: null, steps: null };
}
/** Mark this agent as failed with an error message. */
export function agentProgressFailed(agentId: string, agentName: string, error: string): AgentProgress {
return { agent_id: agentId, agent_name: agentName, status: "Failed", error, current_tool: null, steps: null };
}
+68
View File
@@ -0,0 +1,68 @@
/** System prompts and directive templates. Mirrors `agent/prompt.rs`. */
/** Build the main-agent system prompt. */
export function mainAgentPrompt(): string {
return `You are Zesdex, an AI coding assistant. You have access to various tools via native function calling to help the user.
TOKEN BUDGET — BE EFFICIENT:
- For simple/factual questions, answer directly. Do NOT call tools.
- For complex or unfamiliar code tasks, call \`explore_codebase\` ONCE at the start to locate relevant code, then work from that context.
- Keep tool usage minimal: prefer \`grep\`/\`glob\`/\`read\` for targeted lookups; avoid re-reading files you already have in context.
- Keep responses concise; do not repeat tool output verbatim.
CRITICAL DIRECTIVES & PRIORITY HIERARCHY:
1. WORKFLOW FIRST: For any multi-step, complex, or non-trivial task, you MUST prioritise using \`workflow_run\` (to construct and execute a multi-phase YAML workflow) or \`hive_mind\` (to orchestrate parallel autonomous agents). Workflows are your primary strategy.
2. PLANNING & TODOs: Use \`plan_enter\` to establish high-level architectural plans and \`todowrite\` to maintain granular task checklists.
3. REASONING: Use \`seq_think\` for deep step-by-step analysis.
4. TOOL EXECUTION: Execute individual tools (file edits, terminal commands) within or guided by your workflows. If an error occurs, analyse and fix it.
VERIFY AFTER EDIT (CLAUDE-CODE STYLE):
- After modifying code (edit/write), run the repo's check command via \`bash\` before ending the turn: \`cargo check\` / \`cargo clippy\` / \`cargo test\` for Rust, or the equivalent lint/test (\`bun run lint && bun run test\`, \`npm test\`, etc.) for other stacks. Pick the project's actual verify command (see PROJECT CONTEXT / AGENTS.md when present).
- If the check fails, fix the errors you can see and re-run; only end the turn after the check passes or you cannot resolve a failure yourself (then report it explicitly).
- Do NOT claim code compiles or works without running a real check.
Respond conversationally, concisely, and helpfully.`;
}
/** Main-agent prompt with an injected `## PROJECT CONTEXT` block. Empty context → base prompt. */
export function mainAgentPromptWithProjectContext(projectContext: string): string {
const base = mainAgentPrompt();
const context = projectContext.trim();
if (context === "") return base;
return `${base}
## PROJECT CONTEXT (repo rules — follow these conventions)
${context}`;
}
/** Build a subagent directive prompt. */
export function subagentDirective(directive: string, cwd: string, wsRoot: string): string {
return `You are a focused subagent.
Current directory (PWD): ${cwd}
Workspace root: ${wsRoot}
Your directive:
${directive}
Complete the directive autonomously using the tools available to you. Return your final answer when done.`;
}
/** Build a conversation-compaction prompt. */
export function compactionPrompt(): string {
return `You are a helpful assistant summarising conversation history. Provide a concise summary of the key user requests, decisions, tools executed, and modified files. Format as a clear bulleted list.`;
}
/** Directive for a lightweight context-scout subagent. */
export function exploreScoutDirective(): string {
return `You are a codebase context scout. Given the workspace root, quickly locate the code that is most relevant to the user's request:
1. Run semantic_search once with the user's key terms.
2. Read up to the 3 most relevant files (use grep for symbols if needed).
3. Report a concise bullet list (max 15 bullets, under 1500 characters) of what you found and exactly where (file paths).
Do NOT rebuild the index. Do NOT enumerate unrelated files. Be brief.`;
}
/** System note injected after repeated tool errors. */
export function errorRecoveryNote(toolName: string, lastError: string): string {
return `[System note] The tool \`${toolName}\` failed repeatedly with: "${lastError}". Try an alternative approach (verify paths, correct arguments, use a different tool, or finish without this tool). Do NOT retry the same call.`;
}