Skip to content
Merged
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
2 changes: 1 addition & 1 deletion packages/opencode/src/goal/goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export const layer = Layer.effect(

if (newParseFailures >= GoalPrompts.MAX_CONSECUTIVE_PARSE_FAILURES) {
const pauseReason =
"judge 模型未返回有效 JSON 判定。请配置 auxiliary.goalJudge 指向更可靠的模型,然后 /goal resume。"
"judge 模型未返回有效 JSON 判定。请检查模型配置或换用更可靠的模型,然后 /goal resume。"
const updated = new GoalState.Info({
...state,
status: "paused",
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/goal/judge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const run = Effect.fn("Goal.Judge.run")(function* (
user: userPrompt,
temperature: 0,
maxTokens: 200,
timeout: GoalPrompts.DEFAULT_JUDGE_TIMEOUT,
timeout: GoalPrompts.DEFAULT_JUDGE_TIMEOUT_SECONDS,
}).pipe(
Effect.map((text) => parseJudgeResponse(text)),
// Transport errors (timeout, network, non-JSON transport-level failure)
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/goal/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { GoalState } from "./state"
export * as GoalPrompts from "./prompts"

export const DEFAULT_MAX_TURNS = 20
export const DEFAULT_JUDGE_TIMEOUT = 30_000
// Seconds — used as `Effect.timeout(`${timeout} seconds`)` in loop.ts.
// Was 30_000 (ms) which produced "30000 seconds" = 8.3h (effectively no timeout).
export const DEFAULT_JUDGE_TIMEOUT_SECONDS = 30
export const MAX_CONSECUTIVE_PARSE_FAILURES = 3
export const JUDGE_RESPONSE_SNIPPET_CHARS = 4000
// Zombie-goal freshness guard threshold (D6). A goal that is still active with
Expand Down
24 changes: 15 additions & 9 deletions packages/opencode/src/hook/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,11 +1050,7 @@ const commandHandler: HookHandler = {

const mcpHandler: HookHandler = {
type: "mcp",
run: Effect.fn("SettingsHook.handler.mcp")(function* (entry, envelope, _cwd, inHook) {
if (inHook) {
log.warn("nested mcp hook skipped (re-entry guard)", { command: commandText(entry) })
return { json: undefined, exitBlock: undefined }
}
run: Effect.fn("SettingsHook.handler.mcp")(function* (entry, envelope, _cwd, _inHook) {
const mcpSvc = Option.getOrUndefined(yield* Effect.serviceOption(MCP.Service))
if (!mcpSvc) {
log.warn("mcp hook skipped: MCP service not in context", { command: commandText(entry) })
Expand Down Expand Up @@ -1245,7 +1241,7 @@ const agentHandler: HookHandler = {

const captured: { value: HookJSONOutput | null } = { value: null }
const ac = new AbortController()
const timeoutMs = entry.timeout ?? DEFAULT_AGENT_TIMEOUT_MS
const timeoutMs = entry.timeout ? entry.timeout * 1000 : DEFAULT_AGENT_TIMEOUT_MS
const timer = setTimeout(() => ac.abort(), timeoutMs)

const loopExit = yield* Effect.tryPromise({
Expand Down Expand Up @@ -1526,9 +1522,19 @@ export const layer = Layer.effect(
}
}
if (hso && "permissionDecision" in hso && hso.permissionDecision) {
result.permissionDecision = hso.permissionDecision
result.permissionDecisionReason =
"permissionDecisionReason" in hso ? hso.permissionDecisionReason : undefined
const incoming = hso.permissionDecision
const current = result.permissionDecision
// Most-restrictive-wins: deny > ask > allow. A later hook cannot
// relax an earlier hook's deny (Claude Code permission semantics).
const moreRestrictive =
current === undefined ||
incoming === "deny" ||
(incoming === "ask" && current === "allow")
if (moreRestrictive) {
result.permissionDecision = incoming
result.permissionDecisionReason =
"permissionDecisionReason" in hso ? hso.permissionDecisionReason : undefined
}
}
if (hso && "updatedInput" in hso && hso.updatedInput) {
result.updatedInput = hso.updatedInput
Expand Down