125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
import { afterEach, describe, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { Agent } from "@/agent/agent"
|
|
import { BackgroundJob } from "@/background/job"
|
|
import { Bus } from "@/bus"
|
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|
import { Session } from "@/session/session"
|
|
import { MessageID } from "@/session/schema"
|
|
import { SessionStatus } from "@/session/status"
|
|
import { TaskStatusTool } from "@/tool/task_status"
|
|
import { Truncate } from "@/tool/truncate"
|
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
|
import { disposeAllInstances } from "../fixture/fixture"
|
|
import { testEffect } from "../lib/effect"
|
|
|
|
afterEach(async () => {
|
|
await disposeAllInstances()
|
|
})
|
|
|
|
const layer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|
Layer.mergeAll(
|
|
Agent.defaultLayer,
|
|
BackgroundJob.defaultLayer,
|
|
Bus.defaultLayer,
|
|
CrossSpawnSpawner.defaultLayer,
|
|
Session.defaultLayer,
|
|
SessionStatus.defaultLayer,
|
|
Truncate.defaultLayer,
|
|
RuntimeFlags.layer(flags),
|
|
)
|
|
|
|
const it = testEffect(layer({ experimentalBackgroundSubagents: true }))
|
|
|
|
describe("tool.task_status", () => {
|
|
it.instance("returns completed background job output", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* BackgroundJob.Service
|
|
const sessions = yield* Session.Service
|
|
const tool = yield* TaskStatusTool
|
|
const def = yield* tool.init()
|
|
const chat = yield* sessions.create({})
|
|
const task = yield* sessions.create({ parentID: chat.id })
|
|
|
|
yield* jobs.start({ id: task.id, type: "task", run: Effect.succeed("all done") })
|
|
|
|
const result = yield* def.execute(
|
|
{ task_id: task.id, wait: true, timeout_ms: 1_000 },
|
|
{
|
|
sessionID: chat.id,
|
|
messageID: MessageID.ascending(),
|
|
agent: "build",
|
|
abort: new AbortController().signal,
|
|
messages: [],
|
|
metadata: () => Effect.void,
|
|
ask: () => Effect.void,
|
|
},
|
|
)
|
|
|
|
expect(result.output).toContain("state: completed")
|
|
expect(result.output).toContain("all done")
|
|
expect(result.metadata.timed_out).toBe(false)
|
|
}),
|
|
)
|
|
|
|
it.instance("wait=true times out while the background job is running", () =>
|
|
Effect.gen(function* () {
|
|
const jobs = yield* BackgroundJob.Service
|
|
const sessions = yield* Session.Service
|
|
const tool = yield* TaskStatusTool
|
|
const def = yield* tool.init()
|
|
const chat = yield* sessions.create({})
|
|
const task = yield* sessions.create({ parentID: chat.id })
|
|
|
|
yield* jobs.start({ id: task.id, type: "task", run: Effect.never })
|
|
|
|
const result = yield* def.execute(
|
|
{ task_id: task.id, wait: true, timeout_ms: 50 },
|
|
{
|
|
sessionID: chat.id,
|
|
messageID: MessageID.ascending(),
|
|
agent: "build",
|
|
abort: new AbortController().signal,
|
|
messages: [],
|
|
metadata: () => Effect.void,
|
|
ask: () => Effect.void,
|
|
},
|
|
)
|
|
|
|
expect(result.output).toContain("state: running")
|
|
expect(result.output).toContain("Timed out after 50ms")
|
|
expect(result.metadata.timed_out).toBe(true)
|
|
}),
|
|
)
|
|
|
|
// kilocode_change start - task status cannot inspect another session's child
|
|
it.instance("rejects cross-session task inspection", () =>
|
|
Effect.gen(function* () {
|
|
const sessions = yield* Session.Service
|
|
const tool = yield* TaskStatusTool
|
|
const def = yield* tool.init()
|
|
const owner = yield* sessions.create({})
|
|
const other = yield* sessions.create({})
|
|
const task = yield* sessions.create({ parentID: owner.id })
|
|
|
|
const exit = yield* def
|
|
.execute(
|
|
{ task_id: task.id },
|
|
{
|
|
sessionID: other.id,
|
|
messageID: MessageID.ascending(),
|
|
agent: "build",
|
|
abort: new AbortController().signal,
|
|
messages: [],
|
|
metadata: () => Effect.void,
|
|
ask: () => Effect.void,
|
|
},
|
|
)
|
|
.pipe(Effect.exit)
|
|
|
|
expect(exit._tag).toBe("Failure")
|
|
}),
|
|
)
|
|
// kilocode_change end
|
|
})
|