Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,17 +286,20 @@ export const layer: Layer.Layer<
const failToolCall = Effect.fn("SessionProcessor.failToolCall")(function* (toolCallID: string, error: unknown) {
const match = yield* readToolCall(toolCallID)
if (!match || match.part.state.status !== "running") return false
// Agent-recoverable failures (bad args, malformed call, unknown task/actor
// id) carry a marker the TUI reads to render them muted instead of as a red
// error block. The full actionable message still flows to the model.
const recoverable = isRecoverableError(error)
const metadata = "metadata" in match.part.state && isRecord(match.part.state.metadata) ? match.part.state.metadata : {}
const errorMetadata = recoverable
? { ...metadata, recoverable: true }
: Object.keys(metadata).length > 0
? metadata
: undefined
yield* session.updatePart({
...match.part,
state: {
status: "error",
input: match.part.state.input,
error: errorMessage(error),
...(recoverable ? { metadata: { ...match.part.state.metadata, recoverable: true } } : {}),
...(errorMetadata ? { metadata: errorMetadata } : {}),
time: { start: match.part.state.time.start, end: Date.now() },
},
})
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
start: part.state.status === "running" ? part.state.time.start : Date.now(),
end: Date.now(),
},
metadata: part.state.status === "pending" ? undefined : part.state.metadata,
metadata: "metadata" in part.state ? part.state.metadata : undefined,
input: part.state.input,
},
} satisfies MessageV2.ToolPart)
Expand Down
55 changes: 31 additions & 24 deletions packages/opencode/test/session/prompt-effect.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NodeFileSystem } from "@effect/platform-node"
import { FetchHttpClient } from "effect/unstable/http"
import { afterEach, expect } from "bun:test"

Check warning on line 3 in packages/opencode/test/session/prompt-effect.test.ts

View workflow job for this annotation

GitHub Actions / lint

eslint(no-unused-vars)

Identifier 'afterEach' is imported but never used.
import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"

Check warning on line 4 in packages/opencode/test/session/prompt-effect.test.ts

View workflow job for this annotation

GitHub Actions / lint

eslint(no-unused-vars)

Identifier 'Deferred' is imported but never used.
import path from "path"
import { Agent as AgentSvc } from "../../src/agent/agent"
Expand Down Expand Up @@ -38,6 +38,7 @@
import { Snapshot } from "../../src/snapshot"
import { ToolRegistry } from "../../src/tool"
import { Truncate } from "../../src/tool"
import { Actor } from "../../src/actor/spawn"
import { ActorRegistry } from "../../src/actor/registry"
import { ActorWaiter } from "../../src/actor/waiter"
import { Memory } from "../../src/memory"
Expand Down Expand Up @@ -162,7 +163,7 @@
const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer))
const run = SessionRunState.layer.pipe(Layer.provide(status))
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
function makeHttp() {
function makeHttp(input?: { actor?: boolean }) {
const taskRegistry = ActorRegistry.defaultLayer
const deps = Layer.mergeAll(
Session.defaultLayer,
Expand Down Expand Up @@ -225,32 +226,38 @@
Layer.provideMerge(deps),
)
const trunc = Truncate.layer.pipe(Layer.provideMerge(deps))
return Layer.mergeAll(
TestLLMServer.layer,
SessionPrompt.layer.pipe(
const prompt = SessionPrompt.layer.pipe(
Layer.provide(Goal.defaultLayer),
Layer.provide(TaskGateState.defaultLayer),
Layer.provide(TaskRegistry.defaultLayer),
Layer.provide(SessionRevert.defaultLayer),
Layer.provide(summary),
Layer.provide(checkpoint),
Layer.provide(team),
Layer.provide(taskRegistry),
Layer.provideMerge(run),
Layer.provideMerge(prune),
Layer.provideMerge(compaction),
Layer.provideMerge(proc),
Layer.provideMerge(registry),
Layer.provideMerge(trunc),
Layer.provide(Instruction.defaultLayer),
Layer.provide(SystemPrompt.defaultLayer),
Layer.provide(Inbox.defaultLayer),
Layer.provideMerge(deps),
),
).pipe(Layer.provide(summary))
Layer.provide(TaskGateState.defaultLayer),
Layer.provide(TaskRegistry.defaultLayer),
Layer.provide(SessionRevert.defaultLayer),
Layer.provide(summary),
Layer.provide(checkpoint),
Layer.provide(team),
Layer.provide(taskRegistry),
Layer.provideMerge(run),
Layer.provideMerge(prune),
Layer.provideMerge(compaction),
Layer.provideMerge(proc),
Layer.provideMerge(registry),
Layer.provideMerge(trunc),
Layer.provide(Instruction.defaultLayer),
Layer.provide(SystemPrompt.defaultLayer),
Layer.provide(Inbox.defaultLayer),
Layer.provideMerge(deps),
)
const actor = Actor.layer.pipe(
Layer.provideMerge(prompt),
Layer.provideMerge(taskRegistry),
Layer.provide(TaskRegistry.defaultLayer),
Layer.provideMerge(Inbox.defaultLayer),
)
if (input?.actor) return Layer.mergeAll(TestLLMServer.layer, prompt, actor).pipe(Layer.provide(summary))
return Layer.mergeAll(TestLLMServer.layer, prompt).pipe(Layer.provide(summary))
}

const it = testEffect(makeHttp())
const itActor = testEffect(makeHttp({ actor: true }))
const unix = process.platform !== "win32" ? it.live : it.live.skip

// Config that registers a custom "test" provider with a "test-model" model
Expand Down Expand Up @@ -593,7 +600,7 @@
),
)

it.live("failed subtask preserves metadata on error tool state", () =>
itActor.live("failed subtask preserves metadata on error tool state", () =>
provideTmpdirServer(
Effect.fnUntraced(function* ({ llm }) {
const prompt = yield* SessionPrompt.Service
Expand Down
Loading