diff --git a/src/cli/cli-program.ts b/src/cli/cli-program.ts index d9bc4dfc5..a7b1e210f 100644 --- a/src/cli/cli-program.ts +++ b/src/cli/cli-program.ts @@ -67,10 +67,9 @@ program .command("run ") .allowUnknownOption() .passThroughOptions() - .description("Run opencode with todo/background task completion enforcement") + .description("Run opencode with todo/background task completion enforcement") .option("-a, --agent ", "Agent to use (default: from CLI/env/config, fallback: Sisyphus)") .option("-d, --directory ", "Working directory") - .option("-t, --timeout ", "Timeout in milliseconds (default: 30 minutes)", parseInt) .option("-p, --port ", "Server port (attaches if port already in use)", parseInt) .option("--attach ", "Attach to existing opencode server URL") .option("--on-complete ", "Shell command to run after completion") @@ -81,7 +80,6 @@ program Examples: $ bunx oh-my-opencode run "Fix the bug in index.ts" $ bunx oh-my-opencode run --agent Sisyphus "Implement feature X" - $ bunx oh-my-opencode run --timeout 3600000 "Large refactoring task" $ bunx oh-my-opencode run --port 4321 "Fix the bug" $ bunx oh-my-opencode run --attach http://127.0.0.1:4321 "Fix the bug" $ bunx oh-my-opencode run --json "Fix the bug" | jq .sessionId @@ -110,7 +108,6 @@ Unlike 'opencode run', this command waits until: message, agent: options.agent, directory: options.directory, - timeout: options.timeout, port: options.port, attach: options.attach, onComplete: options.onComplete, diff --git a/src/cli/run/poll-for-completion.test.ts b/src/cli/run/poll-for-completion.test.ts index 47db01e8e..b07b5737a 100644 --- a/src/cli/run/poll-for-completion.test.ts +++ b/src/cli/run/poll-for-completion.test.ts @@ -336,68 +336,4 @@ describe("pollForCompletion", () => { expect(result).toBe(1) }) - it("returns 130 after graceful timeout window expires", async () => { - //#given - spyOn(console, "log").mockImplementation(() => {}) - spyOn(console, "error").mockImplementation(() => {}) - const ctx = createMockContext({ - statuses: { - "test-session": { type: "busy" }, - }, - }) - const eventState = createEventState() - eventState.mainSessionIdle = false - eventState.hasReceivedMeaningfulWork = true - const abortController = new AbortController() - - //#when - const result = await pollForCompletion(ctx, eventState, abortController, { - pollIntervalMs: 10, - requiredConsecutive: 1, - minStabilizationMs: 0, - timeoutMs: 30, - timeoutGraceMs: 40, - }) - - //#then - expect(result).toBe(130) - expect(abortController.signal.aborted).toBe(true) - }) - - it("allows completion during graceful timeout window", async () => { - //#given - spyOn(console, "log").mockImplementation(() => {}) - spyOn(console, "error").mockImplementation(() => {}) - const ctx = createMockContext() - const eventState = createEventState() - eventState.mainSessionIdle = true - eventState.hasReceivedMeaningfulWork = true - const abortController = new AbortController() - let todoCalls = 0 - - ;(ctx.client.session as unknown as { - todo: ReturnType - children: ReturnType - status: ReturnType - }).todo = mock(async () => { - todoCalls++ - if (todoCalls === 1) { - return { data: [{ id: "1", content: "wip", status: "in_progress", priority: "high" }] } - } - return { data: [] } - }) - - //#when - const result = await pollForCompletion(ctx, eventState, abortController, { - pollIntervalMs: 10, - requiredConsecutive: 1, - minStabilizationMs: 0, - timeoutMs: 20, - timeoutGraceMs: 80, - }) - - //#then - expect(result).toBe(0) - expect(abortController.signal.aborted).toBe(false) - }) }) diff --git a/src/cli/run/poll-for-completion.ts b/src/cli/run/poll-for-completion.ts index a1ea7415d..e307b44ce 100644 --- a/src/cli/run/poll-for-completion.ts +++ b/src/cli/run/poll-for-completion.ts @@ -8,14 +8,11 @@ const DEFAULT_POLL_INTERVAL_MS = 500 const DEFAULT_REQUIRED_CONSECUTIVE = 3 const ERROR_GRACE_CYCLES = 3 const MIN_STABILIZATION_MS = 10_000 -const DEFAULT_TIMEOUT_GRACE_MS = 15_000 export interface PollOptions { pollIntervalMs?: number requiredConsecutive?: number minStabilizationMs?: number - timeoutMs?: number - timeoutGraceMs?: number } export async function pollForCompletion( @@ -29,13 +26,10 @@ export async function pollForCompletion( options.requiredConsecutive ?? DEFAULT_REQUIRED_CONSECUTIVE const minStabilizationMs = options.minStabilizationMs ?? MIN_STABILIZATION_MS - const timeoutMs = options.timeoutMs ?? 0 - const timeoutGraceMs = options.timeoutGraceMs ?? DEFAULT_TIMEOUT_GRACE_MS let consecutiveCompleteChecks = 0 let errorCycleCount = 0 let firstWorkTimestamp: number | null = null const pollStartTimestamp = Date.now() - let timeoutNoticePrinted = false while (!abortController.signal.aborted) { await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)) @@ -44,20 +38,6 @@ export async function pollForCompletion( return 130 } - if (timeoutMs > 0) { - const elapsedMs = Date.now() - pollStartTimestamp - if (elapsedMs >= timeoutMs && !timeoutNoticePrinted) { - console.log(pc.yellow("\nTimeout reached. Entering graceful shutdown window...")) - timeoutNoticePrinted = true - } - - if (elapsedMs >= timeoutMs + timeoutGraceMs) { - console.log(pc.yellow("Grace period expired. Aborting...")) - abortController.abort() - return 130 - } - } - // ERROR CHECK FIRST — errors must not be masked by other gates if (eventState.mainSessionError) { errorCycleCount++ diff --git a/src/cli/run/runner.ts b/src/cli/run/runner.ts index 91e4cb6b2..df1303d98 100644 --- a/src/cli/run/runner.ts +++ b/src/cli/run/runner.ts @@ -11,7 +11,6 @@ import { pollForCompletion } from "./poll-for-completion" export { resolveRunAgent } -const DEFAULT_TIMEOUT_MS = 600_000 const EVENT_PROCESSOR_SHUTDOWN_TIMEOUT_MS = 2_000 export async function waitForEventProcessorShutdown( @@ -39,7 +38,6 @@ export async function run(options: RunOptions): Promise { const { message, directory = process.cwd(), - timeout = DEFAULT_TIMEOUT_MS, } = options const jsonManager = options.json ? createJsonOutputManager() : null @@ -99,9 +97,7 @@ export async function run(options: RunOptions): Promise { }) console.log(pc.dim("Waiting for completion...\n")) - const exitCode = await pollForCompletion(ctx, eventState, abortController, { - timeoutMs: timeout, - }) + const exitCode = await pollForCompletion(ctx, eventState, abortController) // Abort the event stream to stop the processor abortController.abort() diff --git a/src/cli/run/types.ts b/src/cli/run/types.ts index f310a2a3e..b804644c2 100644 --- a/src/cli/run/types.ts +++ b/src/cli/run/types.ts @@ -6,7 +6,6 @@ export interface RunOptions { agent?: string verbose?: boolean directory?: string - timeout?: number port?: number attach?: string onComplete?: string