fix(cli): make agent wait unlimited by default

This commit is contained in:
Mohamed Boudra
2026-02-23 20:51:42 +07:00
parent b167abfda5
commit 3f06063b4b
7 changed files with 98 additions and 25 deletions
+1 -1
View File
@@ -140,7 +140,7 @@ export function createCli(): Command {
.command('wait')
.description('Wait for an agent to become idle')
.argument('<id>', 'Agent ID (or prefix)')
.option('--timeout <seconds>', 'Maximum wait time (default: 600)')
.option('--timeout <seconds>', 'Maximum wait time (default: no limit)')
.option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runWaitCommand))
+1 -1
View File
@@ -99,7 +99,7 @@ export function createAgentCommand(): Command {
.command('wait')
.description('Wait for an agent to become idle')
.argument('<id>', 'Agent ID (or prefix)')
.option('--timeout <seconds>', 'Maximum wait time (default: 600)')
.option('--timeout <seconds>', 'Maximum wait time (default: no limit)')
.option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runWaitCommand))
+9 -10
View File
@@ -108,14 +108,17 @@ export async function runWaitCommand(
throw error
}
// Parse timeout (default 10 minutes)
let timeoutMs: number
// Parse timeout (no limit unless explicitly provided)
let timeoutMs = 0
let timeoutLabel: string | null = null
if (options.timeout) {
try {
timeoutMs = parseDuration(options.timeout)
if (timeoutMs <= 0) {
throw new Error('Timeout must be positive')
}
const timeoutSeconds = Math.floor(timeoutMs / 1000)
timeoutLabel = `${timeoutSeconds} second${timeoutSeconds === 1 ? '' : 's'}`
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
const error: CommandError = {
@@ -125,11 +128,7 @@ export async function runWaitCommand(
}
throw error
}
} else {
timeoutMs = 10 * 60 * 1000 // default 10 minutes
}
const timeoutSeconds = Math.floor(timeoutMs / 1000)
const timeoutLabel = `${timeoutSeconds} second${timeoutSeconds === 1 ? '' : 's'}`
let client
try {
@@ -156,15 +155,15 @@ export async function runWaitCommand(
await client.close()
if (state.status === 'timeout') {
const timeoutMessage = timeoutLabel
? `Agent did not finish within ${timeoutLabel}. Run \`paseo wait ${resolvedAgentId}\` again to keep waiting.`
: `Agent wait timed out. Run \`paseo wait ${resolvedAgentId}\` again to keep waiting.`
return {
type: 'single',
data: {
agentId: resolvedAgentId,
status: 'timeout',
message: appendRecentActivity(
`Agent did not finish within ${timeoutLabel}. Run \`paseo wait ${resolvedAgentId}\` again to keep waiting.`,
recentActivity
),
message: appendRecentActivity(timeoutMessage, recentActivity),
},
schema: agentWaitSchema,
}
+7 -6
View File
@@ -167,15 +167,16 @@ try {
console.log('wait command mentions ID\n')
}
// Test 12: timeout option has default value documented
// Test 12: timeout option documents no default limit
{
console.log('Test 12: timeout option has default value documented')
console.log('Test 12: timeout option documents no default limit')
const result = await $`npx paseo wait --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
const hasDefault =
result.stdout.includes('600') || result.stdout.toLowerCase().includes('default')
assert(hasDefault, 'help should mention timeout default value')
console.log('timeout option has default value documented\n')
assert(
result.stdout.toLowerCase().includes('default: no limit'),
'help should mention timeout default is no limit'
)
console.log('timeout option documents no default limit\n')
}
} finally {
// Clean up temp directory
@@ -1740,4 +1740,72 @@ describe('DaemonClient', () => {
},
])
})
test('waitForFinish with timeout=0 omits timeoutMs and has no client deadline', async () => {
vi.useFakeTimers()
try {
const logger = createMockLogger()
const mock = createMockTransport()
const client = new DaemonClient({
url: 'ws://test',
clientId: 'clsk_unit_test',
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
})
clients.push(client)
const connectPromise = client.connect()
mock.triggerOpen()
await connectPromise
const waitPromise = client.waitForFinish('agent-wait-zero-timeout', 0)
expect(mock.sent).toHaveLength(1)
const request = JSON.parse(String(mock.sent[0])) as {
type: 'session'
message: {
type: 'wait_for_finish_request'
requestId: string
agentId: string
timeoutMs?: number
}
}
expect(request.message.type).toBe('wait_for_finish_request')
expect(request.message.agentId).toBe('agent-wait-zero-timeout')
expect(request.message).not.toHaveProperty('timeoutMs')
const settled = vi.fn()
void waitPromise.then(
() => settled('resolved'),
() => settled('rejected')
)
await vi.advanceTimersByTimeAsync(5 * 60 * 1000)
expect(settled).not.toHaveBeenCalled()
mock.triggerMessage(
wrapSessionMessage({
type: 'wait_for_finish_response',
payload: {
requestId: request.message.requestId,
status: 'idle',
final: null,
error: null,
lastMessage: null,
},
})
)
await expect(waitPromise).resolves.toEqual({
status: 'idle',
final: null,
error: null,
lastMessage: null,
})
} finally {
vi.useRealTimers()
}
})
})
+3 -2
View File
@@ -2426,17 +2426,18 @@ export class DaemonClient {
async waitForFinish(agentId: string, timeout = 60000): Promise<WaitForFinishResult> {
const requestId = this.createRequestId()
const hasTimeout = Number.isFinite(timeout) && timeout > 0
const message = SessionInboundMessageSchema.parse({
type: 'wait_for_finish_request',
requestId,
agentId,
timeoutMs: timeout,
...(hasTimeout ? { timeoutMs: timeout } : {}),
})
const payload = await this.sendCorrelatedRequest({
requestId,
message,
responseType: 'wait_for_finish_response',
timeout: timeout + 5000,
timeout: hasTimeout ? timeout + 5000 : 0,
options: { skipQueue: true },
})
return {
+9 -5
View File
@@ -5497,10 +5497,12 @@ export class Session {
}
const abortController = new AbortController()
const effectiveTimeoutMs = timeoutMs ?? 600_000 // 10 minutes default
const timeoutHandle = setTimeout(() => {
abortController.abort('timeout')
}, effectiveTimeoutMs)
const hasTimeout = typeof timeoutMs === 'number' && timeoutMs > 0
const timeoutHandle = hasTimeout
? setTimeout(() => {
abortController.abort('timeout')
}, timeoutMs)
: null
try {
let result = await this.agentManager.waitForAgentEvent(agentId, {
@@ -5553,7 +5555,9 @@ export class Session {
payload: { requestId, status: 'timeout', final, error: null, lastMessage: null },
})
} finally {
clearTimeout(timeoutHandle)
if (timeoutHandle) {
clearTimeout(timeoutHandle)
}
}
}