mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-15 09:02:35 +00:00
chore: generate
This commit is contained in:
@@ -107,7 +107,10 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(BackgroundJob.defaultLayer), Layer.provide(SessionStatus.defaultLayer))
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(BackgroundJob.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
)
|
||||
|
||||
const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
|
||||
background: BackgroundJob.Interface,
|
||||
|
||||
@@ -71,7 +71,12 @@ function backgroundOutput(sessionID: SessionID) {
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
function backgroundMessage(input: { sessionID: SessionID; description: string; state: "completed" | "error"; text: string }) {
|
||||
function backgroundMessage(input: {
|
||||
sessionID: SessionID
|
||||
description: string
|
||||
state: "completed" | "error"
|
||||
text: string
|
||||
}) {
|
||||
const tag = input.state === "completed" ? "task_result" : "task_error"
|
||||
const title =
|
||||
input.state === "completed"
|
||||
@@ -106,7 +111,9 @@ export const TaskTool = Tool.define(
|
||||
const cfg = yield* config.get()
|
||||
const runInBackground = params.background === true
|
||||
if (runInBackground && !flags.experimentalBackgroundSubagents) {
|
||||
return yield* Effect.fail(new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"))
|
||||
return yield* Effect.fail(
|
||||
new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"),
|
||||
)
|
||||
}
|
||||
|
||||
if (!ctx.extra?.bypassAgentCheck) {
|
||||
@@ -196,27 +203,30 @@ export const TaskTool = Tool.define(
|
||||
return result.parts.findLast((item) => item.type === "text")?.text ?? ""
|
||||
})
|
||||
|
||||
const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> = Effect.fn(
|
||||
"TaskTool.resumeWhenIdle",
|
||||
)(function* (input: { userID: MessageID; state: "completed" | "error" }) {
|
||||
const latest = yield* sessions.findMessage(ctx.sessionID, (item) => item.info.role === "user").pipe(Effect.orDie)
|
||||
if (Option.isNone(latest)) return
|
||||
if (latest.value.info.id !== input.userID) return
|
||||
if ((yield* status.get(ctx.sessionID)).type !== "idle") {
|
||||
yield* Effect.sleep("300 millis")
|
||||
return yield* resumeWhenIdle(input)
|
||||
}
|
||||
yield* bus.publish(TuiEvent.ToastShow, {
|
||||
title: input.state === "completed" ? "Background task complete" : "Background task failed",
|
||||
message:
|
||||
input.state === "completed"
|
||||
? `Background task "${params.description}" finished. Resuming the main thread.`
|
||||
: `Background task "${params.description}" failed. Resuming the main thread.`,
|
||||
variant: input.state === "completed" ? "success" : "error",
|
||||
duration: 5000,
|
||||
const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> =
|
||||
Effect.fn("TaskTool.resumeWhenIdle")(function* (input: { userID: MessageID; state: "completed" | "error" }) {
|
||||
const latest = yield* sessions
|
||||
.findMessage(ctx.sessionID, (item) => item.info.role === "user")
|
||||
.pipe(Effect.orDie)
|
||||
if (Option.isNone(latest)) return
|
||||
if (latest.value.info.id !== input.userID) return
|
||||
if ((yield* status.get(ctx.sessionID)).type !== "idle") {
|
||||
yield* Effect.sleep("300 millis")
|
||||
return yield* resumeWhenIdle(input)
|
||||
}
|
||||
yield* bus.publish(TuiEvent.ToastShow, {
|
||||
title: input.state === "completed" ? "Background task complete" : "Background task failed",
|
||||
message:
|
||||
input.state === "completed"
|
||||
? `Background task "${params.description}" finished. Resuming the main thread.`
|
||||
: `Background task "${params.description}" failed. Resuming the main thread.`,
|
||||
variant: input.state === "completed" ? "success" : "error",
|
||||
duration: 5000,
|
||||
})
|
||||
yield* ops
|
||||
.loop({ sessionID: ctx.sessionID })
|
||||
.pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
|
||||
})
|
||||
yield* ops.loop({ sessionID: ctx.sessionID }).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
|
||||
})
|
||||
|
||||
const continueIfIdle = Effect.fn("TaskTool.continueIfIdle")(function* (input: {
|
||||
userID: MessageID
|
||||
@@ -225,7 +235,10 @@ export const TaskTool = Tool.define(
|
||||
yield* resumeWhenIdle(input).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
|
||||
})
|
||||
|
||||
const inject = Effect.fn("TaskTool.injectBackgroundResult")(function* (state: "completed" | "error", text: string) {
|
||||
const inject = Effect.fn("TaskTool.injectBackgroundResult")(function* (
|
||||
state: "completed" | "error",
|
||||
text: string,
|
||||
) {
|
||||
const currentParent = yield* sessions.get(ctx.sessionID)
|
||||
const message = yield* ops.prompt({
|
||||
sessionID: ctx.sessionID,
|
||||
@@ -249,7 +262,9 @@ export const TaskTool = Tool.define(
|
||||
|
||||
const existing = yield* background.get(nextSession.id)
|
||||
if (existing?.status === "running") {
|
||||
return yield* Effect.fail(new Error(`Task ${nextSession.id} is already running. Use task_status to check progress.`))
|
||||
return yield* Effect.fail(
|
||||
new Error(`Task ${nextSession.id} is already running. Use task_status to check progress.`),
|
||||
)
|
||||
}
|
||||
|
||||
if (runInBackground) {
|
||||
|
||||
@@ -14,7 +14,9 @@ const POLL_MS = 300
|
||||
|
||||
const Parameters = Schema.Struct({
|
||||
task_id: SessionID.annotate({ description: "The task_id returned by the task tool" }),
|
||||
wait: Schema.optional(Schema.Boolean).annotate({ description: "When true, wait until the task reaches a terminal state or timeout" }),
|
||||
wait: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "When true, wait until the task reaches a terminal state or timeout",
|
||||
}),
|
||||
timeout_ms: Schema.optional(PositiveInt).annotate({
|
||||
description: "Maximum milliseconds to wait when wait=true (default: 60000)",
|
||||
}),
|
||||
@@ -39,7 +41,8 @@ function inspectMessage(message: MessageV2.WithParts): InspectResult | undefined
|
||||
if (message.info.role !== "assistant") return
|
||||
const text = message.parts.findLast((part) => part.type === "text")?.text ?? ""
|
||||
if (message.info.error) return { state: "error", text: text || errorText(message.info.error) }
|
||||
if (message.info.finish && !["tool-calls", "unknown"].includes(message.info.finish)) return { state: "completed", text }
|
||||
if (message.info.finish && !["tool-calls", "unknown"].includes(message.info.finish))
|
||||
return { state: "completed", text }
|
||||
return { state: "running", text: text || "Task is still running." }
|
||||
}
|
||||
|
||||
@@ -51,7 +54,9 @@ export const TaskStatusTool = Tool.define(
|
||||
const status = yield* SessionStatus.Service
|
||||
const flags = yield* RuntimeFlags.Service
|
||||
|
||||
const inspect: (taskID: SessionID) => Effect.Effect<InspectResult> = Effect.fn("TaskStatusTool.inspect")(function* (taskID: SessionID) {
|
||||
const inspect: (taskID: SessionID) => Effect.Effect<InspectResult> = Effect.fn("TaskStatusTool.inspect")(function* (
|
||||
taskID: SessionID,
|
||||
) {
|
||||
const job = yield* jobs.get(taskID)
|
||||
if (job) {
|
||||
return {
|
||||
@@ -75,26 +80,32 @@ export const TaskStatusTool = Tool.define(
|
||||
}
|
||||
}
|
||||
|
||||
const latestAssistant = yield* sessions.findMessage(taskID, (item) => item.info.role === "assistant").pipe(Effect.orDie)
|
||||
const latestAssistant = yield* sessions
|
||||
.findMessage(taskID, (item) => item.info.role === "assistant")
|
||||
.pipe(Effect.orDie)
|
||||
if (Option.isSome(latestAssistant)) {
|
||||
const latest = inspectMessage(latestAssistant.value)
|
||||
if (!latest) return { state: "error", text: "Task is not running in this process." }
|
||||
if (latest.state === "running") return { state: "error", text: "Task is not running in this process and has no final output." }
|
||||
if (latest.state === "running")
|
||||
return { state: "error", text: "Task is not running in this process and has no final output." }
|
||||
return latest
|
||||
}
|
||||
return { state: "error", text: "Task is not running in this process and has not produced output." }
|
||||
})
|
||||
|
||||
const waitForTerminal: (taskID: SessionID, timeout: number) => Effect.Effect<{ result: InspectResult; timedOut: boolean }> = Effect.fn(
|
||||
"TaskStatusTool.waitForTerminal",
|
||||
)(function* (taskID: SessionID, timeout: number) {
|
||||
const result = yield* inspect(taskID)
|
||||
if (result.state !== "running") return { result, timedOut: false }
|
||||
if (timeout <= 0) return { result, timedOut: true }
|
||||
const sleep = Math.min(POLL_MS, timeout)
|
||||
yield* Effect.sleep(`${sleep} millis`)
|
||||
return yield* waitForTerminal(taskID, timeout - sleep)
|
||||
})
|
||||
const waitForTerminal: (
|
||||
taskID: SessionID,
|
||||
timeout: number,
|
||||
) => Effect.Effect<{ result: InspectResult; timedOut: boolean }> = Effect.fn("TaskStatusTool.waitForTerminal")(
|
||||
function* (taskID: SessionID, timeout: number) {
|
||||
const result = yield* inspect(taskID)
|
||||
if (result.state !== "running") return { result, timedOut: false }
|
||||
if (timeout <= 0) return { result, timedOut: true }
|
||||
const sleep = Math.min(POLL_MS, timeout)
|
||||
yield* Effect.sleep(`${sleep} millis`)
|
||||
return yield* waitForTerminal(taskID, timeout - sleep)
|
||||
},
|
||||
)
|
||||
|
||||
const run = Effect.fn("TaskStatusTool.execute")(function* (
|
||||
params: Schema.Schema.Type<typeof Parameters>,
|
||||
@@ -161,7 +172,8 @@ export const TaskStatusTool = Tool.define(
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters: Parameters,
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie),
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||
run(params, ctx).pipe(Effect.orDie),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -111,9 +111,11 @@ describe("tool.registry", () => {
|
||||
const agent = yield* Agent.Service
|
||||
const build = yield* agent.get("build")
|
||||
if (!build) throw new Error("build agent not found")
|
||||
const task = (
|
||||
yield* registry.tools({ providerID: ProviderID.opencode, modelID: ModelID.make("test"), agent: build })
|
||||
).find((tool) => tool.id === "task")
|
||||
const task = (yield* registry.tools({
|
||||
providerID: ProviderID.opencode,
|
||||
modelID: ModelID.make("test"),
|
||||
agent: build,
|
||||
})).find((tool) => tool.id === "task")
|
||||
|
||||
expect(task?.jsonSchema).toBeDefined()
|
||||
expect((task?.jsonSchema?.properties as Record<string, unknown> | undefined)?.background).toBeUndefined()
|
||||
|
||||
@@ -1738,7 +1738,10 @@ ToolRegistry.register({
|
||||
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
|
||||
const tone = createMemo(() => agent().color)
|
||||
const subtitle = createMemo(() => {
|
||||
const value = typeof props.input.description === "string" && props.input.description ? props.input.description : childSessionId()
|
||||
const value =
|
||||
typeof props.input.description === "string" && props.input.description
|
||||
? props.input.description
|
||||
: childSessionId()
|
||||
if (!value) return value
|
||||
if (props.metadata.background === true) return `${value} (background)`
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user