fix(cli): make agent wait unlimited by default
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user