refactor(core): simplify v2 prompt lifecycle and execution coordination (#35047)
This commit is contained in:
parent
57e9e9771d
commit
cd0b274856
11 changed files with 362 additions and 278 deletions
|
|
@ -81,11 +81,20 @@ describe("SessionV2.compact", () => {
|
|||
const events = yield* EventV2.Service
|
||||
const created = yield* session.create({ location })
|
||||
|
||||
const messageID = SessionMessage.ID.create()
|
||||
const prompt = Prompt.make({ text: "Please compact this session history." })
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID: created.id,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt,
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
sessionID: created.id,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt: Prompt.make({ text: "Please compact this session history." }),
|
||||
prompt,
|
||||
delivery: "steer",
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ describe("SessionV2.create", () => {
|
|||
prompt: Prompt.make({ text: "First" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||
yield* events.publish(SessionEvent.Synthetic, {
|
||||
sessionID: parent.id,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
|
|
@ -219,9 +219,9 @@ describe("SessionV2.create", () => {
|
|||
})
|
||||
|
||||
yield* session.prompt({ sessionID: parent.id, prompt: Prompt.make({ text: "Parent changed" }), resume: false })
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||
yield* session.prompt({ sessionID: forked.id, prompt: Prompt.make({ text: "Child continues" }), resume: false })
|
||||
yield* SessionInput.promoteSteers(db, events, forked.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, forked.id)
|
||||
|
||||
expect((yield* session.context(parent.id)).map((message) => message.type)).toEqual(["user", "synthetic", "user"])
|
||||
expect((yield* session.context(forked.id)).map((message) => message.type)).toEqual(["user", "synthetic", "user"])
|
||||
|
|
@ -246,13 +246,13 @@ describe("SessionV2.create", () => {
|
|||
prompt: Prompt.make({ text: "First" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||
const second = yield* session.prompt({
|
||||
sessionID: parent.id,
|
||||
prompt: Prompt.make({ text: "Second" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||
|
||||
const forked = yield* session.fork({ sessionID: parent.id, messageID: second.id })
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ describe("SessionV2.create", () => {
|
|||
const { db } = yield* Database.Service
|
||||
const created = yield* session.create({ location })
|
||||
yield* session.prompt({ sessionID: created.id, prompt: Prompt.make({ text: "Hello" }), resume: false })
|
||||
yield* SessionInput.promoteSteers(db, events, created.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, created.id)
|
||||
|
||||
expect(
|
||||
Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(2), Stream.runCollect)),
|
||||
|
|
@ -390,7 +390,7 @@ describe("SessionV2.create", () => {
|
|||
prompt: Prompt.make({ text: "Replay lifecycle" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers(sourceDb, sourceEvents, created.id, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(sourceDb, sourceEvents, created.id)
|
||||
const serialized = (yield* sourceDb
|
||||
.select()
|
||||
.from(EventTable)
|
||||
|
|
|
|||
|
|
@ -129,6 +129,13 @@ describe("SessionProjector", () => {
|
|||
.pipe(Effect.orDie)
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_first"),
|
||||
timestamp: created,
|
||||
prompt: Prompt.make({ text: "first" }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(
|
||||
SessionEvent.Prompted,
|
||||
{
|
||||
|
|
@ -140,6 +147,13 @@ describe("SessionProjector", () => {
|
|||
},
|
||||
{ id: EventV2.ID.make("evt_z") },
|
||||
)
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.make("msg_second"),
|
||||
timestamp: created,
|
||||
prompt: Prompt.make({ text: "second" }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(
|
||||
SessionEvent.Prompted,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ describe("SessionV2.prompt", () => {
|
|||
prompt: Prompt.make({ text: "boundary" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, sessionID)
|
||||
const stale = SessionMessage.ID.make("msg_stale_assistant")
|
||||
yield* db.insert(SessionMessageTable).values(assistantRow(stale, 100)).run().pipe(Effect.orDie)
|
||||
yield* events.publish(SessionEvent.RevertEvent.Staged, {
|
||||
|
|
@ -253,7 +253,7 @@ describe("SessionV2.prompt", () => {
|
|||
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
||||
yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers(db, events, sessionID)
|
||||
const streamed = Array.from(yield* Fiber.join(fiber))
|
||||
|
||||
expect(streamed.map((event): [number | undefined, string] => [event.durable?.seq, event.type])).toEqual([
|
||||
|
|
@ -425,10 +425,7 @@ describe("SessionV2.prompt", () => {
|
|||
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Promote once" }), resume: false })
|
||||
|
||||
yield* Effect.all(
|
||||
[
|
||||
SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER),
|
||||
SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER),
|
||||
],
|
||||
[SessionInput.promoteSteers(db, events, sessionID), SessionInput.promoteSteers(db, events, sessionID)],
|
||||
{ concurrency: "unbounded" },
|
||||
)
|
||||
|
||||
|
|
@ -440,23 +437,6 @@ describe("SessionV2.prompt", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("promotes steers only through the captured inbox cutoff", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const { db } = yield* Database.Service
|
||||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
const first = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Before cutoff" }), resume: false })
|
||||
const cutoff = first.admittedSeq
|
||||
const second = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "After cutoff" }), resume: false })
|
||||
|
||||
yield* SessionInput.promoteSteers(db, events, sessionID, cutoff)
|
||||
|
||||
expect(yield* admitted(first.id)).toHaveProperty("promotedSeq")
|
||||
expect(yield* admitted(second.id)).not.toHaveProperty("promotedSeq")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("reprojects pending inbox input without scheduling execution", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
|
|
@ -500,48 +480,6 @@ describe("SessionV2.prompt", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("returns an exact retry of a legacy projected prompt", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
const prompt = Prompt.make({ text: "Historical prompt" })
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
prompt,
|
||||
delivery: "steer",
|
||||
})
|
||||
|
||||
const retried = yield* session.prompt({ id: messageID, sessionID, prompt, resume: false })
|
||||
|
||||
expect(retried).toMatchObject({ id: messageID, prompt: { text: "Historical prompt" } })
|
||||
expect(yield* admitted(messageID)).toHaveProperty("promotedSeq")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("returns an exact retry of a legacy projected queued prompt", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
const prompt = Prompt.make({ text: "Historical queued prompt" })
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: yield* DateTime.now,
|
||||
prompt,
|
||||
delivery: "queue",
|
||||
})
|
||||
|
||||
const retried = yield* session.prompt({ id: messageID, sessionID, prompt, delivery: "queue", resume: false })
|
||||
|
||||
expect(retried).toMatchObject({ id: messageID, prompt: { text: "Historical queued prompt" } })
|
||||
expect(yield* admitted(messageID)).toMatchObject({ delivery: "queue" })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("rejects reuse of one globally unique message ID across sessions", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
|
|
|
|||
|
|
@ -392,6 +392,68 @@ describe("SessionRunCoordinator", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.effect("settles once per execution across coalesced drains", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const started = yield* Deferred.make<void>()
|
||||
const gate = yield* Deferred.make<void>()
|
||||
const idle = yield* Deferred.make<void>()
|
||||
let drains = 0
|
||||
const settled: Exit.Exit<void, never>[] = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never>({
|
||||
drain: () =>
|
||||
Effect.sync(() => ++drains).pipe(
|
||||
Effect.flatMap((run) =>
|
||||
run === 1
|
||||
? Deferred.succeed(started, undefined).pipe(Effect.andThen(Deferred.await(gate)))
|
||||
: Effect.void,
|
||||
),
|
||||
Effect.asVoid,
|
||||
),
|
||||
settled: (_key, exit) =>
|
||||
Effect.sync(() => void settled.push(exit)).pipe(
|
||||
Effect.andThen(Deferred.succeed(idle, undefined)),
|
||||
Effect.asVoid,
|
||||
),
|
||||
})
|
||||
|
||||
yield* coordinator.wake("session")
|
||||
yield* Deferred.await(started)
|
||||
yield* coordinator.wake("session")
|
||||
yield* Deferred.succeed(gate, undefined)
|
||||
yield* Deferred.await(idle)
|
||||
|
||||
expect(drains).toBe(2)
|
||||
expect(settled).toHaveLength(1)
|
||||
expect(Exit.isSuccess(settled[0]!)).toBe(true)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("settles interrupted executions before waiters resolve", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const started = yield* Deferred.make<void>()
|
||||
const gate = yield* Deferred.make<void>()
|
||||
const settled: Exit.Exit<void, never>[] = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never>({
|
||||
drain: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Deferred.await(gate))),
|
||||
settled: (_key, exit) => Effect.sync(() => void settled.push(exit)),
|
||||
})
|
||||
|
||||
yield* coordinator.wake("session")
|
||||
yield* Deferred.await(started)
|
||||
yield* coordinator.interrupt("session")
|
||||
|
||||
expect(settled).toHaveLength(1)
|
||||
expect(settled[0] !== undefined && Exit.isFailure(settled[0]) && Cause.hasInterrupts(settled[0].cause)).toBe(
|
||||
true,
|
||||
)
|
||||
expect(yield* coordinator.active).toEqual(new Set())
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("trampolines synchronous self-waking execution", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
|
|
@ -156,6 +156,14 @@ const echo = Layer.effectDiscard(
|
|||
output: Schema.Struct({}),
|
||||
execute: () => Effect.die("unexpected tool defect"),
|
||||
}),
|
||||
// BigInt output with no model content forces ToolOutputStore.bound onto its
|
||||
// JSON.stringify encode path, which fails with a typed StorageError.
|
||||
storefail: Tool.make({
|
||||
description: "Produce output that cannot be persisted",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Any,
|
||||
execute: () => Effect.succeed({ big: 1n }),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
@ -653,6 +661,7 @@ describe("SessionRunnerLLM", () => {
|
|||
response = []
|
||||
|
||||
const message = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run automatically" }) })
|
||||
yield* session.wait(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.messages({ sessionID })).toMatchObject([
|
||||
|
|
@ -677,7 +686,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.model).toBe(model)
|
||||
expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect"])
|
||||
expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect", "storefail"])
|
||||
expect(requests[0]?.messages.map((message) => ({ role: message.role, content: message.content }))).toEqual([
|
||||
{ role: "user", content: [{ type: "text", text: "First" }] },
|
||||
{ role: "user", content: [{ type: "text", text: "Second" }] },
|
||||
|
|
@ -712,6 +721,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
systemUnavailable = false
|
||||
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }) })
|
||||
yield* session.wait(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.messages.map((message) => message.role)).toEqual(["user"])
|
||||
|
|
@ -1553,7 +1563,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect"])
|
||||
expect(requests[0]?.tools.map((tool) => tool.name)).toEqual(["echo", "defect", "storefail"])
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Use tools" },
|
||||
{
|
||||
|
|
@ -2389,7 +2399,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* SessionV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover interrupted tool" }), resume: false })
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||
const assistantMessageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
|
|
@ -2453,7 +2463,7 @@ describe("SessionRunnerLLM", () => {
|
|||
prompt: Prompt.make({ text: "Recover interrupted hosted tool" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||
const assistantMessageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
|
|
@ -2513,7 +2523,7 @@ describe("SessionRunnerLLM", () => {
|
|||
prompt: Prompt.make({ text: "Recover interrupted tool input" }),
|
||||
resume: false,
|
||||
})
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID, Number.MAX_SAFE_INTEGER)
|
||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||
const assistantMessageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
|
|
@ -2626,8 +2636,9 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
streamStarted = yield* Deferred.make<void>()
|
||||
const second = yield* session.resume(otherSessionID).pipe(Effect.forkChild)
|
||||
yield* Effect.yieldNow
|
||||
yield* Deferred.await(streamStarted)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
expect(requests.map((request) => request.providerOptions?.openai?.promptCacheKey)).toEqual([
|
||||
|
|
@ -2797,6 +2808,49 @@ describe("SessionRunnerLLM", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("fails the drain when tool output persistence fails", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call storefail" }), resume: false })
|
||||
|
||||
requests.length = 0
|
||||
responses = [
|
||||
[
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-storefail", name: "storefail", input: {} }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||
LLMEvent.finish({ reason: "tool-calls" }),
|
||||
],
|
||||
[],
|
||||
]
|
||||
|
||||
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Call storefail" },
|
||||
{
|
||||
type: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-storefail",
|
||||
state: {
|
||||
status: "error",
|
||||
error: {
|
||||
type: "unknown",
|
||||
message: expect.stringContaining("Tool execution failed: Failed to encode tool output"),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("interrupts runner continuation when a question is dismissed", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
|
|
|
|||
|
|
@ -41,7 +41,14 @@ const models = Layer.mock(SessionRunnerModel.Service)({
|
|||
})
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, AgentV2.node, SessionTitle.node]),
|
||||
LayerNode.group([
|
||||
Database.node,
|
||||
EventV2.node,
|
||||
SessionProjector.node,
|
||||
SessionStore.node,
|
||||
AgentV2.node,
|
||||
SessionTitle.node,
|
||||
]),
|
||||
[
|
||||
[llmClient, client],
|
||||
[SessionRunnerModel.node, models],
|
||||
|
|
@ -76,9 +83,17 @@ const insertSession = (id: SessionV2.ID) =>
|
|||
const prompt = (sessionID: SessionV2.ID, text: string) =>
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
const messageID = SessionMessage.ID.create()
|
||||
yield* events.publish(SessionEvent.PromptAdmitted, {
|
||||
sessionID,
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt: Prompt.make({ text }),
|
||||
delivery: "steer",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Prompted, {
|
||||
sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
messageID,
|
||||
timestamp: DateTime.makeUnsafe(0),
|
||||
prompt: Prompt.make({ text }),
|
||||
delivery: "steer",
|
||||
|
|
@ -101,9 +116,9 @@ it.effect("generates a title from the sole user message and renames the session"
|
|||
yield* prompt(sessionID, "Help me debug the failing build")
|
||||
|
||||
const store = yield* SessionStore.Service
|
||||
const session = yield* store.get(sessionID).pipe(
|
||||
Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))),
|
||||
)
|
||||
const session = yield* store
|
||||
.get(sessionID)
|
||||
.pipe(Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))))
|
||||
const title = yield* SessionTitle.Service
|
||||
yield* title.generateForFirstPrompt(session)
|
||||
|
||||
|
|
@ -131,9 +146,9 @@ it.effect("does not generate once a second user message exists", () =>
|
|||
yield* prompt(sessionID, "Second message")
|
||||
|
||||
const store = yield* SessionStore.Service
|
||||
const session = yield* store.get(sessionID).pipe(
|
||||
Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))),
|
||||
)
|
||||
const session = yield* store
|
||||
.get(sessionID)
|
||||
.pipe(Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))))
|
||||
const title = yield* SessionTitle.Service
|
||||
yield* title.generateForFirstPrompt(session)
|
||||
|
||||
|
|
@ -179,9 +194,9 @@ it.effect("does not generate for a child session", () =>
|
|||
yield* prompt(sessionID, "Do this subtask")
|
||||
|
||||
const store = yield* SessionStore.Service
|
||||
const session = yield* store.get(sessionID).pipe(
|
||||
Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))),
|
||||
)
|
||||
const session = yield* store
|
||||
.get(sessionID)
|
||||
.pipe(Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))))
|
||||
const title = yield* SessionTitle.Service
|
||||
yield* title.generateForFirstPrompt(session)
|
||||
|
||||
|
|
@ -197,9 +212,9 @@ it.effect("does not generate when the title agent is removed", () =>
|
|||
yield* prompt(sessionID, "Help me debug the failing build")
|
||||
|
||||
const store = yield* SessionStore.Service
|
||||
const session = yield* store.get(sessionID).pipe(
|
||||
Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))),
|
||||
)
|
||||
const session = yield* store
|
||||
.get(sessionID)
|
||||
.pipe(Effect.flatMap((session) => (session ? Effect.succeed(session) : Effect.die("session missing"))))
|
||||
const title = yield* SessionTitle.Service
|
||||
yield* title.generateForFirstPrompt(session)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue