refactor: rombak arsitektur file ke modular clean-arch (vertikal per-feature)
Organisasi ulang layer-first (domain/application/infrastructure) menjadi vertikal per-feature, behavior tidak berubah: - src/shared/domain/ -> fondasi bersama (ex-domain/core): message, provider, tool_call, tool_result, store, error, usage, conversation - src/features/agent/ -> LLM turn loop: domain (TurnEvent, AgentTurnParams) + application (turn_service, ports) + infrastructure (llm LlmClient + tools) - src/features/cms/ -> settings/conversation/memory persistence: domain + application (services) + infrastructure (persistence repos) - src/features/subagent/ -> orchestration: domain (AccessTier) + infrastructure (engine runner, context, provider, delegate, spawn_tools) - src/features/workflow/ -> multi-phase + hive-mind: domain + infrastructure - src/interfaces/ -> cli + tui (outer ring) tetap Subagent-runner (engine.ts runAgent, division.ts toolsFor) dipindah ke features/subagent (bukan agent tools) utk memutus coupling agent<->subagent. Alias tsconfig: @zesdex/domain -> shared/domain, + @zesdex/agent, @zesdex/agent-infra, @zesdex/cms, @zesdex/subagent, @zesdex/workflow, @zesdex/shared. Bersihkan dead: application/ports PasswordService/TokenService/AuthService. compose.ts (composition root) & bootstrap repoint ke barrel feature. Gates: tsc --noEmit 0 error, bun test 70 pass/0 fail, bun build 147 modules, ./dist/zesdex --headless -> real turn OK.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
adaptiveMaxTokens,
|
||||
conversationChars,
|
||||
ErrorTracker,
|
||||
truncateToolOutput,
|
||||
} from "./turn_service.ts";
|
||||
import { type ChatMessage, newConversation, systemMessage, userMessage, toolResultMessage } from "@zesdex/domain";
|
||||
|
||||
describe("truncateToolOutput", () => {
|
||||
it("short output is unchanged", () => {
|
||||
expect(truncateToolOutput("short")).toBe("short");
|
||||
});
|
||||
|
||||
it("long output preserves head and marks cut", () => {
|
||||
const long = "x".repeat(12_000 + 500);
|
||||
const truncated = truncateToolOutput(long);
|
||||
expect(truncated.length).toBeLessThan(long.length);
|
||||
expect(truncated).toContain("...[truncated");
|
||||
expect(truncated.startsWith("xxx")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("adaptiveMaxTokens", () => {
|
||||
it("scales with request length", () => {
|
||||
expect(adaptiveMaxTokens(10)).toBe(800);
|
||||
expect(adaptiveMaxTokens(200)).toBe(1600);
|
||||
expect(adaptiveMaxTokens(5000)).toBe(4096);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ErrorTracker", () => {
|
||||
it("injects recovery note after repeated errors", () => {
|
||||
const tracker = new ErrorTracker();
|
||||
const messages: ChatMessage[] = [];
|
||||
tracker.record("read", "Error: File not found", messages);
|
||||
tracker.record("read", "Error: File not found", messages);
|
||||
expect(tracker.shouldStop()).toBe(false);
|
||||
tracker.record("read", "Error: File not found", messages);
|
||||
expect(messages.some((m) => m.content?.includes("[System note]"))).toBe(true);
|
||||
});
|
||||
|
||||
it("stops after too many errors", () => {
|
||||
const tracker = new ErrorTracker();
|
||||
const messages: ChatMessage[] = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
tracker.record("bash", `Error: boom ${i}`, messages);
|
||||
}
|
||||
expect(tracker.shouldStop()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("conversationChars", () => {
|
||||
it("sums content only", () => {
|
||||
const conv = newConversation("sys", "s1");
|
||||
conv.messages.push(systemMessage("sys"), userMessage("hello world"), toolResultMessage("id", "output"));
|
||||
expect(conversationChars(conv.messages)).toBe(3 + 11 + 6);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user