diff options
| author | Adam Malczewski <[email protected]> | 2026-06-24 00:08:47 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-24 00:08:47 +0900 |
| commit | d225ea4bd5f95d39a910704fe45acdf847c953fa (patch) | |
| tree | ec0a33665087bb34844b19955ab6fa74c39e3656 /packages/transport-http/src/app.test.ts | |
| parent | 674853d87d54dba1cd83c4e51fce5411602f4d5d (diff) | |
| download | dispatch-d225ea4bd5f95d39a910704fe45acdf847c953fa.tar.gz dispatch-d225ea4bd5f95d39a910704fe45acdf847c953fa.zip | |
feat(system-prompt): wire into turn flow + compaction + API routes
session-orchestrator:
- Wire systemPromptService as optional dep (lazy via host.getService)
- Regular turn: construct on first turn (new conversation), get on subsequent
turns, set on providerOpts.systemPrompt (cache-safe)
- Compaction: construct (fresh resolve) + append COMPACTION_SYSTEM_PROMPT
- 12 new tests (construct/get/service-unavailable/compaction)
transport-http:
- GET /system-prompt (returns template or DEFAULT_TEMPLATE)
- PUT /system-prompt (validate + setTemplate, 503 when unavailable)
- GET /system-prompt/variables (static catalog, always available)
- 6 new tests
system-prompt service: added getTemplate/setTemplate to interface + impl.
1396 vitest pass. typecheck + biome clean.
Diffstat (limited to 'packages/transport-http/src/app.test.ts')
| -rw-r--r-- | packages/transport-http/src/app.test.ts | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/packages/transport-http/src/app.test.ts b/packages/transport-http/src/app.test.ts index 0b840db..2a4b451 100644 --- a/packages/transport-http/src/app.test.ts +++ b/packages/transport-http/src/app.test.ts @@ -10,11 +10,13 @@ import type { StoredChunk, TurnMetrics, } from "@dispatch/kernel"; +import { DEFAULT_TEMPLATE } from "@dispatch/system-prompt"; import { createThroughputStore, dayKeyOf } from "@dispatch/throughput-store"; import type { DeleteWorkspaceResponse, QueuedMessage, QueueResponse, + SystemPromptVariable, ThroughputResponse, WorkspaceListResponse, WorkspaceResponse, @@ -28,6 +30,7 @@ import type { CredentialStore, LspService, SessionOrchestrator, + SystemPromptService, WarmService, } from "./seam.js"; import { conversationOpened } from "./seam.js"; @@ -381,6 +384,39 @@ function createCapturingLspService( }; } +function createFakeSystemPromptService( + template: string = "custom template", +): SystemPromptService & { + readonly setTemplateCalls: readonly string[]; + readonly getTemplateCalls: number; +} { + const setCalls: string[] = []; + let getTemplateCount = 0; + let currentTemplate = template; + return { + get setTemplateCalls() { + return setCalls; + }, + get getTemplateCalls() { + return getTemplateCount; + }, + async construct() { + return currentTemplate; + }, + async get() { + return currentTemplate; + }, + async getTemplate() { + getTemplateCount++; + return currentTemplate; + }, + async setTemplate(t) { + setCalls.push(t); + currentTemplate = t; + }, + }; +} + const noopLogger = createFakeLogger(); describe("GET /health", () => { @@ -3249,3 +3285,114 @@ it("GET /conversations/:id/lsp uses effective cwd", async () => { }; expect(body.cwd).toBe("/effective"); }); + +describe("GET /system-prompt", () => { + it("returns stored template", async () => { + const service = createFakeSystemPromptService("custom template"); + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + systemPromptService: service, + logger: noopLogger, + }); + const res = await app.request("/system-prompt"); + expect(res.status).toBe(200); + const body = (await res.json()) as { template: string }; + expect(body.template).toBe("custom template"); + expect(service.getTemplateCalls).toBe(1); + }); + + it("returns default when service unavailable", async () => { + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + logger: noopLogger, + }); + const res = await app.request("/system-prompt"); + expect(res.status).toBe(200); + const body = (await res.json()) as { template: string }; + expect(body.template).toBe(DEFAULT_TEMPLATE); + }); +}); + +describe("PUT /system-prompt", () => { + it("sets template", async () => { + const service = createFakeSystemPromptService(); + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + systemPromptService: service, + logger: noopLogger, + }); + const res = await app.request("/system-prompt", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ template: "new" }), + }); + expect(res.status).toBe(200); + const body = (await res.json()) as { template: string }; + expect(body.template).toBe("new"); + expect(service.setTemplateCalls).toEqual(["new"]); + }); + + it("missing template → 400", async () => { + const service = createFakeSystemPromptService(); + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + systemPromptService: service, + logger: noopLogger, + }); + const res = await app.request("/system-prompt", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({}), + }); + expect(res.status).toBe(400); + expect(service.setTemplateCalls).toEqual([]); + }); + + it("service unavailable → 503", async () => { + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + logger: noopLogger, + }); + const res = await app.request("/system-prompt", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ template: "new" }), + }); + expect(res.status).toBe(503); + const body = (await res.json()) as { error: string }; + expect(body.error).toBe("System prompt service not available"); + }); +}); + +describe("GET /system-prompt/variables", () => { + it("returns catalog", async () => { + const app = createApp({ + conversationStore: createFakeConversationStore(), + orchestrator: createFakeOrchestrator([]), + credentialStore: createFakeCredentialStore([]), + logger: noopLogger, + }); + const res = await app.request("/system-prompt/variables"); + expect(res.status).toBe(200); + const body = (await res.json()) as { variables: readonly SystemPromptVariable[] }; + expect(Array.isArray(body.variables)).toBe(true); + // Contains at least system:time, prompt:cwd, and a dynamic file:<path>. + const hasSystemTime = body.variables.some((v) => v.type === "system" && v.name === "time"); + const hasPromptCwd = body.variables.some((v) => v.type === "prompt" && v.name === "cwd"); + const fileEntry = body.variables.find((v) => v.type === "file"); + expect(hasSystemTime).toBe(true); + expect(hasPromptCwd).toBe(true); + expect(fileEntry).toBeDefined(); + expect(fileEntry?.dynamic).toBe(true); + }); +}); |
