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:
co-authored by
Claude Opus 5
parent
9d1544d799
commit
07e84e43b3
@@ -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;
|
||||
@@ -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";
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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.`;
|
||||
}
|
||||
Reference in New Issue
Block a user