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* (
|
const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
|
||||||
background: BackgroundJob.Interface,
|
background: BackgroundJob.Interface,
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ function backgroundOutput(sessionID: SessionID) {
|
|||||||
].join("\n")
|
].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 tag = input.state === "completed" ? "task_result" : "task_error"
|
||||||
const title =
|
const title =
|
||||||
input.state === "completed"
|
input.state === "completed"
|
||||||
@@ -106,7 +111,9 @@ export const TaskTool = Tool.define(
|
|||||||
const cfg = yield* config.get()
|
const cfg = yield* config.get()
|
||||||
const runInBackground = params.background === true
|
const runInBackground = params.background === true
|
||||||
if (runInBackground && !flags.experimentalBackgroundSubagents) {
|
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) {
|
if (!ctx.extra?.bypassAgentCheck) {
|
||||||
@@ -196,27 +203,30 @@ export const TaskTool = Tool.define(
|
|||||||
return result.parts.findLast((item) => item.type === "text")?.text ?? ""
|
return result.parts.findLast((item) => item.type === "text")?.text ?? ""
|
||||||
})
|
})
|
||||||
|
|
||||||
const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> = Effect.fn(
|
const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> =
|
||||||
"TaskTool.resumeWhenIdle",
|
Effect.fn("TaskTool.resumeWhenIdle")(function* (input: { userID: MessageID; state: "completed" | "error" }) {
|
||||||
)(function* (input: { userID: MessageID; state: "completed" | "error" }) {
|
const latest = yield* sessions
|
||||||
const latest = yield* sessions.findMessage(ctx.sessionID, (item) => item.info.role === "user").pipe(Effect.orDie)
|
.findMessage(ctx.sessionID, (item) => item.info.role === "user")
|
||||||
if (Option.isNone(latest)) return
|
.pipe(Effect.orDie)
|
||||||
if (latest.value.info.id !== input.userID) return
|
if (Option.isNone(latest)) return
|
||||||
if ((yield* status.get(ctx.sessionID)).type !== "idle") {
|
if (latest.value.info.id !== input.userID) return
|
||||||
yield* Effect.sleep("300 millis")
|
if ((yield* status.get(ctx.sessionID)).type !== "idle") {
|
||||||
return yield* resumeWhenIdle(input)
|
yield* Effect.sleep("300 millis")
|
||||||
}
|
return yield* resumeWhenIdle(input)
|
||||||
yield* bus.publish(TuiEvent.ToastShow, {
|
}
|
||||||
title: input.state === "completed" ? "Background task complete" : "Background task failed",
|
yield* bus.publish(TuiEvent.ToastShow, {
|
||||||
message:
|
title: input.state === "completed" ? "Background task complete" : "Background task failed",
|
||||||
input.state === "completed"
|
message:
|
||||||
? `Background task "${params.description}" finished. Resuming the main thread.`
|
input.state === "completed"
|
||||||
: `Background task "${params.description}" failed. Resuming the main thread.`,
|
? `Background task "${params.description}" finished. Resuming the main thread.`
|
||||||
variant: input.state === "completed" ? "success" : "error",
|
: `Background task "${params.description}" failed. Resuming the main thread.`,
|
||||||
duration: 5000,
|
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: {
|
const continueIfIdle = Effect.fn("TaskTool.continueIfIdle")(function* (input: {
|
||||||
userID: MessageID
|
userID: MessageID
|
||||||
@@ -225,7 +235,10 @@ export const TaskTool = Tool.define(
|
|||||||
yield* resumeWhenIdle(input).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
|
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 currentParent = yield* sessions.get(ctx.sessionID)
|
||||||
const message = yield* ops.prompt({
|
const message = yield* ops.prompt({
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
@@ -249,7 +262,9 @@ export const TaskTool = Tool.define(
|
|||||||
|
|
||||||
const existing = yield* background.get(nextSession.id)
|
const existing = yield* background.get(nextSession.id)
|
||||||
if (existing?.status === "running") {
|
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) {
|
if (runInBackground) {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ const POLL_MS = 300
|
|||||||
|
|
||||||
const Parameters = Schema.Struct({
|
const Parameters = Schema.Struct({
|
||||||
task_id: SessionID.annotate({ description: "The task_id returned by the task tool" }),
|
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({
|
timeout_ms: Schema.optional(PositiveInt).annotate({
|
||||||
description: "Maximum milliseconds to wait when wait=true (default: 60000)",
|
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
|
if (message.info.role !== "assistant") return
|
||||||
const text = message.parts.findLast((part) => part.type === "text")?.text ?? ""
|
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.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." }
|
return { state: "running", text: text || "Task is still running." }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +54,9 @@ export const TaskStatusTool = Tool.define(
|
|||||||
const status = yield* SessionStatus.Service
|
const status = yield* SessionStatus.Service
|
||||||
const flags = yield* RuntimeFlags.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)
|
const job = yield* jobs.get(taskID)
|
||||||
if (job) {
|
if (job) {
|
||||||
return {
|
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)) {
|
if (Option.isSome(latestAssistant)) {
|
||||||
const latest = inspectMessage(latestAssistant.value)
|
const latest = inspectMessage(latestAssistant.value)
|
||||||
if (!latest) return { state: "error", text: "Task is not running in this process." }
|
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 latest
|
||||||
}
|
}
|
||||||
return { state: "error", text: "Task is not running in this process and has not produced output." }
|
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(
|
const waitForTerminal: (
|
||||||
"TaskStatusTool.waitForTerminal",
|
taskID: SessionID,
|
||||||
)(function* (taskID: SessionID, timeout: number) {
|
timeout: number,
|
||||||
const result = yield* inspect(taskID)
|
) => Effect.Effect<{ result: InspectResult; timedOut: boolean }> = Effect.fn("TaskStatusTool.waitForTerminal")(
|
||||||
if (result.state !== "running") return { result, timedOut: false }
|
function* (taskID: SessionID, timeout: number) {
|
||||||
if (timeout <= 0) return { result, timedOut: true }
|
const result = yield* inspect(taskID)
|
||||||
const sleep = Math.min(POLL_MS, timeout)
|
if (result.state !== "running") return { result, timedOut: false }
|
||||||
yield* Effect.sleep(`${sleep} millis`)
|
if (timeout <= 0) return { result, timedOut: true }
|
||||||
return yield* waitForTerminal(taskID, timeout - sleep)
|
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* (
|
const run = Effect.fn("TaskStatusTool.execute")(function* (
|
||||||
params: Schema.Schema.Type<typeof Parameters>,
|
params: Schema.Schema.Type<typeof Parameters>,
|
||||||
@@ -161,7 +172,8 @@ export const TaskStatusTool = Tool.define(
|
|||||||
return {
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: Parameters,
|
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 agent = yield* Agent.Service
|
||||||
const build = yield* agent.get("build")
|
const build = yield* agent.get("build")
|
||||||
if (!build) throw new Error("build agent not found")
|
if (!build) throw new Error("build agent not found")
|
||||||
const task = (
|
const task = (yield* registry.tools({
|
||||||
yield* registry.tools({ providerID: ProviderID.opencode, modelID: ModelID.make("test"), agent: build })
|
providerID: ProviderID.opencode,
|
||||||
).find((tool) => tool.id === "task")
|
modelID: ModelID.make("test"),
|
||||||
|
agent: build,
|
||||||
|
})).find((tool) => tool.id === "task")
|
||||||
|
|
||||||
expect(task?.jsonSchema).toBeDefined()
|
expect(task?.jsonSchema).toBeDefined()
|
||||||
expect((task?.jsonSchema?.properties as Record<string, unknown> | undefined)?.background).toBeUndefined()
|
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 title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
|
||||||
const tone = createMemo(() => agent().color)
|
const tone = createMemo(() => agent().color)
|
||||||
const subtitle = createMemo(() => {
|
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 (!value) return value
|
||||||
if (props.metadata.background === true) return `${value} (background)`
|
if (props.metadata.background === true) return `${value} (background)`
|
||||||
return value
|
return value
|
||||||
|
|||||||
Reference in New Issue
Block a user