diff --git a/packages/core/test/session-projector.test.ts b/packages/core/test/session-projector.test.ts index a7fbc6a3c42..214f779e67f 100644 --- a/packages/core/test/session-projector.test.ts +++ b/packages/core/test/session-projector.test.ts @@ -60,25 +60,32 @@ const assistantRow = ( return { id, session_id: sessionID, type, seq, time_created: DateTime.toEpochMillis(time.created), data } } +const seedSession = (overrides?: Partial) => + Effect.gen(function* () { + const db = (yield* Database.Service).db + yield* db + .insert(ProjectTable) + .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) + .run() + yield* db + .insert(SessionTable) + .values({ + id: sessionID, + project_id: Project.ID.global, + slug: "test", + directory: "/project", + title: "test", + version: "test", + ...overrides, + }) + .run() + return db + }) + describe("SessionProjector", () => { it.effect("does not settle a pending manual compaction on an auto failure", () => Effect.gen(function* () { - const db = (yield* Database.Service).db - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() + const db = yield* seedSession() const bus = yield* Bus.Service const inputID = SessionMessage.ID.make("msg_manual_compaction") yield* SessionInbox.admitCompaction(db, bus, { id: inputID, sessionID, delivery: "queue" }) @@ -95,22 +102,7 @@ describe("SessionProjector", () => { it.effect("loads legacy revert storage into canonical state", () => Effect.gen(function* () { - const db = (yield* Database.Service).db - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() + const db = yield* seedSession() const legacy = JSON.stringify({ messageID: "msg_boundary", snapshot: "tree", @@ -131,28 +123,14 @@ describe("SessionProjector", () => { it.effect("projects staged, cleared, and committed reverts", () => Effect.gen(function* () { - const db = (yield* Database.Service).db - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - cost: 1.25, - tokens_input: 10, - tokens_output: 4, - tokens_reasoning: 2, - tokens_cache_read: 3, - tokens_cache_write: 1, - }) - .run() + const db = yield* seedSession({ + cost: 1.25, + tokens_input: 10, + tokens_output: 4, + tokens_reasoning: 2, + tokens_cache_read: 3, + tokens_cache_write: 1, + }) const boundary = SessionMessage.ID.make("msg_boundary") const earlier = SessionMessage.ID.make("msg_earlier") yield* db @@ -227,24 +205,7 @@ describe("SessionProjector", () => { it.effect("orders projected messages and context by durable aggregate sequence", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + yield* seedSession() const bus = yield* Bus.Service yield* bus.publish(SessionEvent.InboxEnqueued, { @@ -300,24 +261,7 @@ describe("SessionProjector", () => { it.effect("maps malformed persisted rows consistently while single-message lookup defects", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const messageID = SessionMessage.ID.make("msg_malformed") yield* db .insert(SessionMessageTable) @@ -344,24 +288,7 @@ describe("SessionProjector", () => { it.effect("consumes the pending row and projects the message at promotion", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const bus = yield* Bus.Service const id = SessionMessage.ID.make("msg_admitted") const admitted = yield* SessionInbox.admit(db, bus, { @@ -387,26 +314,7 @@ describe("SessionProjector", () => { it.effect("projects durable context messages supported by the updater", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - agent: "plan", - model: previousModel, - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession({ agent: "plan", model: previousModel }) const bus = yield* Bus.Service yield* bus.publish(SessionEvent.AgentSelected, { @@ -532,24 +440,7 @@ describe("SessionProjector", () => { it.effect("rejects distinct creator events that reuse one projected message ID", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const bus = yield* Bus.Service const id = SessionMessage.ID.make("msg_creator_collision") const { id: _, type, ...data } = encodeMessage({ id, type: "synthetic", text: "existing", time: { created } }) @@ -576,24 +467,7 @@ describe("SessionProjector", () => { it.effect("projects retry state and clears it at the next step or execution terminal", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const bus = yield* Bus.Service const first = SessionMessage.ID.make("msg_retry_first") const second = SessionMessage.ID.make("msg_retry_second") @@ -643,24 +517,7 @@ describe("SessionProjector", () => { it.effect("does not infer restart continuation from lifecycle history", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const bus = yield* Bus.Service const suspended = () => db @@ -680,24 +537,7 @@ describe("SessionProjector", () => { it.effect("updates only the newest incomplete assistant projection", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() yield* db .insert(SessionMessageTable) .values([ @@ -757,24 +597,7 @@ describe("SessionProjector", () => { it.effect("projects ended and failed step terminal state", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() const endedID = SessionMessage.ID.make("msg_ended") const failedID = SessionMessage.ID.make("msg_failed") yield* db @@ -844,24 +667,7 @@ describe("SessionProjector", () => { it.effect("does not revive a stale incomplete assistant projection", () => Effect.gen(function* () { - const { db } = yield* Database.Service - yield* db - .insert(ProjectTable) - .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) - .run() - .pipe(Effect.orDie) - yield* db - .insert(SessionTable) - .values({ - id: sessionID, - project_id: Project.ID.global, - slug: "test", - directory: "/project", - title: "test", - version: "test", - }) - .run() - .pipe(Effect.orDie) + const db = yield* seedSession() yield* db .insert(SessionMessageTable) .values([ diff --git a/packages/core/test/session-title.test.ts b/packages/core/test/session-title.test.ts index 780dc5d41b9..5422eedef34 100644 --- a/packages/core/test/session-title.test.ts +++ b/packages/core/test/session-title.test.ts @@ -198,18 +198,20 @@ beforeEach(() => { titleStream = successfulTitle }) +const enableTitleAgent = Effect.gen(function* () { + const agents = yield* Agent.Service + yield* agents.transform((editor) => { + editor.update(Agent.ID.make("title"), (agent) => { + agent.mode = "primary" + agent.hidden = true + agent.system = "You are a title generator." + }) + }) +}) + it.effect("generates a title from the sole user message and renames the session", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_generate") yield* insertSession(sessionID) yield* prompt(sessionID, "Help me debug the failing build") @@ -240,17 +242,8 @@ it.effect("generates a title from the sole user message and renames the session" it.effect("uses a small model from the primary provider", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle selectedSmall = small - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_small_model") yield* insertSession(sessionID) yield* prompt(sessionID, "Use a small model for this title") @@ -267,20 +260,12 @@ it.effect("uses a small model from the primary provider", () => it.effect("falls back to the primary model when the small model fails", () => Effect.gen(function* () { - requests = [] titleStream = () => requests.length === 1 ? Stream.make(LLMEvent.providerError({ message: "Small model unavailable" })) : successfulTitle() selectedSmall = lowSmall - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_small_fallback") yield* insertSession( sessionID, @@ -314,16 +299,7 @@ it.effect("falls back to the primary model when the small model fails", () => it.effect("generates from the first user message after later messages exist", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_second_message") yield* insertSession(sessionID) yield* prompt(sessionID, "First message") @@ -342,16 +318,7 @@ it.effect("generates from the first user message after later messages exist", () it.effect("retries a legacy persisted fallback title", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_legacy") const created = Date.parse("2026-07-30T18:45:03.662Z") yield* insertSession(sessionID, "New session - 2026-07-30T18:45:03.662Z", created) @@ -368,16 +335,7 @@ it.effect("retries a legacy persisted fallback title", () => it.effect("generates a title for an explicitly requested child session", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_child") const { db } = yield* Database.Service yield* db @@ -411,8 +369,6 @@ it.effect("generates a title for an explicitly requested child session", () => it.effect("does not generate when the title agent is removed", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle const sessionID = Session.ID.make("ses_title_no_agent") yield* insertSession(sessionID) yield* prompt(sessionID, "Help me debug the failing build") @@ -429,14 +385,7 @@ it.effect("does not generate when the title agent is removed", () => it.effect("regenerates an existing title using the title agent", () => Effect.gen(function* () { - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_regenerate") yield* insertSession(sessionID, "Original title") yield* prompt(sessionID, "Investigate the login failure") @@ -480,14 +429,7 @@ it.effect("regenerates an existing title using the title agent", () => it.effect("bounds regeneration context while preserving the original request and recent conversation", () => Effect.gen(function* () { - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_regenerate_bounded") yield* insertSession(sessionID, "Original title") yield* prompt(sessionID, `ORIGINAL_GOAL ${"a".repeat(3_000)} OMITTED_ORIGINAL_END`) @@ -509,14 +451,7 @@ it.effect("bounds regeneration context while preserving the original request and it.effect("preserves the existing title when regeneration fails", () => Effect.gen(function* () { - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_regenerate_failure") yield* insertSession(sessionID, "Original title") yield* prompt(sessionID, "Fail to regenerate this title") @@ -533,15 +468,7 @@ it.effect("preserves the existing title when regeneration fails", () => it.effect("retries after a failed title request", () => Effect.gen(function* () { - requests = [] - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_retry") yield* insertSession(sessionID) yield* prompt(sessionID, "Retry this title") @@ -560,14 +487,7 @@ it.effect("retries after a failed title request", () => it.effect("does not rename after a failed title stream", () => Effect.gen(function* () { - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_stream_failure") yield* insertSession(sessionID) yield* prompt(sessionID, "Fail this title stream") @@ -589,16 +509,7 @@ it.effect("does not rename after a failed title stream", () => it.effect("keeps session context hooks away from title requests", () => Effect.gen(function* () { - requests = [] - titleStream = successfulTitle - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent // Context hooks shape the agent conversation; title generation is not part of // it, so it opts out and the transcript passes through unchanged. const hooks = yield* PluginHooks.Service @@ -621,15 +532,7 @@ it.effect("keeps session context hooks away from title requests", () => it.effect("preserves a manual rename completed while generation is in flight", () => Effect.gen(function* () { - requests = [] - const agentService = yield* Agent.Service - yield* agentService.transform((editor) => { - editor.update(Agent.ID.make("title"), (agent) => { - agent.mode = "primary" - agent.hidden = true - agent.system = "You are a title generator." - }) - }) + yield* enableTitleAgent const sessionID = Session.ID.make("ses_title_manual_rename") yield* insertSession(sessionID) yield* prompt(sessionID, "Generate this title")