fix: task id passed to background job for continuation (#30485)
This commit is contained in:
@@ -78,6 +78,38 @@ describe("background.job", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("waits for extensions before completing a running job", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const first = yield* Deferred.make<void>()
|
||||
const second = yield* Deferred.make<void>()
|
||||
const job = yield* jobs.start({
|
||||
type: "test",
|
||||
run: Deferred.await(first).pipe(Effect.as("first")),
|
||||
})
|
||||
|
||||
expect(yield* jobs.extend({ id: job.id, run: Deferred.await(second).pipe(Effect.as("second")) })).toBe(true)
|
||||
yield* Deferred.succeed(first, undefined)
|
||||
expect((yield* jobs.get(job.id))?.status).toBe("running")
|
||||
|
||||
yield* Deferred.succeed(second, undefined)
|
||||
const done = yield* jobs.wait({ id: job.id })
|
||||
expect(done.info?.status).toBe("completed")
|
||||
expect(done.info?.output).toBe("second")
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("rejects extensions after a job completes", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const job = yield* jobs.start({ type: "test", run: Effect.succeed("done") })
|
||||
yield* jobs.wait({ id: job.id })
|
||||
|
||||
expect(yield* jobs.extend({ id: job.id, run: Effect.succeed("late") })).toBe(false)
|
||||
expect((yield* jobs.get(job.id))?.output).toBe("done")
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("records failed jobs", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
@@ -93,19 +125,56 @@ describe("background.job", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("ignores stale settlements after restarting a failed job", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const fail = yield* Deferred.make<void>()
|
||||
const interrupted = yield* Deferred.make<void>()
|
||||
const release = yield* Deferred.make<void>()
|
||||
const id = "job_test"
|
||||
yield* jobs.start({
|
||||
id,
|
||||
type: "test",
|
||||
run: Deferred.await(fail).pipe(Effect.andThen(Effect.fail(new Error("boom")))),
|
||||
})
|
||||
yield* jobs.extend({
|
||||
id,
|
||||
run: Effect.never.pipe(
|
||||
Effect.ensuring(Deferred.succeed(interrupted, undefined).pipe(Effect.andThen(Deferred.await(release)))),
|
||||
),
|
||||
})
|
||||
|
||||
yield* Deferred.succeed(fail, undefined)
|
||||
expect((yield* jobs.wait({ id })).info?.status).toBe("error")
|
||||
yield* Deferred.await(interrupted)
|
||||
yield* jobs.start({ id, type: "test", run: Effect.never })
|
||||
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
yield* Effect.yieldNow
|
||||
expect((yield* jobs.get(id))?.status).toBe("running")
|
||||
yield* jobs.cancel(id)
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("can cancel running jobs", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const interrupted = yield* Deferred.make<void>()
|
||||
const extendedInterrupted = yield* Deferred.make<void>()
|
||||
const job = yield* jobs.start({
|
||||
type: "test",
|
||||
run: Effect.never.pipe(Effect.ensuring(Deferred.succeed(interrupted, undefined))),
|
||||
})
|
||||
yield* jobs.extend({
|
||||
id: job.id,
|
||||
run: Effect.never.pipe(Effect.ensuring(Deferred.succeed(extendedInterrupted, undefined))),
|
||||
})
|
||||
|
||||
const cancelled = yield* jobs.cancel(job.id)
|
||||
|
||||
expect(cancelled?.status).toBe("cancelled")
|
||||
yield* Deferred.await(interrupted).pipe(Effect.timeout("1 second"))
|
||||
yield* Deferred.await(extendedInterrupted).pipe(Effect.timeout("1 second"))
|
||||
expect((yield* jobs.get(job.id))?.status).toBe("cancelled")
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -518,6 +518,79 @@ describe("tool.task", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
background.instance("background task completion waits for running updates", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const { chat, assistant } = yield* seed()
|
||||
const tool = yield* TaskTool
|
||||
const def = yield* tool.init()
|
||||
const first = defer<void>()
|
||||
const second = defer<void>()
|
||||
const updated = defer<SessionPrompt.PromptInput>()
|
||||
const injected = defer<SessionPrompt.PromptInput>()
|
||||
let prompts = 0
|
||||
const promptOps: TaskPromptOps = {
|
||||
...stubOps(),
|
||||
prompt: (input) => {
|
||||
if (input.sessionID === chat.id) {
|
||||
injected.resolve(input)
|
||||
return Effect.succeed(reply(input, "done"))
|
||||
}
|
||||
prompts++
|
||||
if (prompts === 1) return Effect.promise(() => first.promise).pipe(Effect.as(reply(input, "first done")))
|
||||
updated.resolve(input)
|
||||
return Effect.promise(() => second.promise).pipe(Effect.as(reply(input, "second done")))
|
||||
},
|
||||
}
|
||||
const context = {
|
||||
sessionID: chat.id,
|
||||
messageID: assistant.id,
|
||||
agent: "build",
|
||||
abort: new AbortController().signal,
|
||||
extra: { promptOps },
|
||||
messages: [],
|
||||
metadata: () => Effect.void,
|
||||
ask: () => Effect.void,
|
||||
}
|
||||
|
||||
const started = yield* def.execute(
|
||||
{
|
||||
description: "inspect bug",
|
||||
prompt: "look into the cache key path",
|
||||
subagent_type: "general",
|
||||
background: true,
|
||||
},
|
||||
context,
|
||||
)
|
||||
const result = yield* def.execute(
|
||||
{
|
||||
description: "add investigation scope",
|
||||
prompt: "also inspect cancellation",
|
||||
subagent_type: "general",
|
||||
task_id: started.metadata.sessionId,
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
expect((yield* Effect.promise(() => updated.promise)).parts).toEqual([
|
||||
{ type: "text", text: "also inspect cancellation" },
|
||||
])
|
||||
expect(result.metadata.sessionId).toBe(started.metadata.sessionId)
|
||||
expect(result.metadata.background).toBe(true)
|
||||
expect(result.output).toContain("Background task updated")
|
||||
first.resolve()
|
||||
expect((yield* jobs.get(started.metadata.sessionId))?.status).toBe("running")
|
||||
|
||||
second.resolve()
|
||||
const waited = yield* jobs.wait({ id: started.metadata.sessionId, timeout: 1_000 })
|
||||
expect(waited.info?.status).toBe("completed")
|
||||
expect(waited.info?.output).toBe("second done")
|
||||
const notification = yield* Effect.promise(() => injected.promise)
|
||||
expect(notification.parts[0]?.type).toBe("text")
|
||||
if (notification.parts[0]?.type === "text") expect(notification.parts[0].text).toContain("second done")
|
||||
}),
|
||||
)
|
||||
|
||||
background.instance("background tasks complete through the background job service", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
|
||||
Reference in New Issue
Block a user