fix(cli): make agent wait unlimited by default
This commit is contained in:
@@ -140,7 +140,7 @@ export function createCli(): Command {
|
|||||||
.command('wait')
|
.command('wait')
|
||||||
.description('Wait for an agent to become idle')
|
.description('Wait for an agent to become idle')
|
||||||
.argument('<id>', 'Agent ID (or prefix)')
|
.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('--json', 'Output in JSON format')
|
||||||
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
.action(withOutput(runWaitCommand))
|
.action(withOutput(runWaitCommand))
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function createAgentCommand(): Command {
|
|||||||
.command('wait')
|
.command('wait')
|
||||||
.description('Wait for an agent to become idle')
|
.description('Wait for an agent to become idle')
|
||||||
.argument('<id>', 'Agent ID (or prefix)')
|
.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('--json', 'Output in JSON format')
|
||||||
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
.action(withOutput(runWaitCommand))
|
.action(withOutput(runWaitCommand))
|
||||||
|
|||||||
@@ -108,14 +108,17 @@ export async function runWaitCommand(
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse timeout (default 10 minutes)
|
// Parse timeout (no limit unless explicitly provided)
|
||||||
let timeoutMs: number
|
let timeoutMs = 0
|
||||||
|
let timeoutLabel: string | null = null
|
||||||
if (options.timeout) {
|
if (options.timeout) {
|
||||||
try {
|
try {
|
||||||
timeoutMs = parseDuration(options.timeout)
|
timeoutMs = parseDuration(options.timeout)
|
||||||
if (timeoutMs <= 0) {
|
if (timeoutMs <= 0) {
|
||||||
throw new Error('Timeout must be positive')
|
throw new Error('Timeout must be positive')
|
||||||
}
|
}
|
||||||
|
const timeoutSeconds = Math.floor(timeoutMs / 1000)
|
||||||
|
timeoutLabel = `${timeoutSeconds} second${timeoutSeconds === 1 ? '' : 's'}`
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
const error: CommandError = {
|
const error: CommandError = {
|
||||||
@@ -125,11 +128,7 @@ export async function runWaitCommand(
|
|||||||
}
|
}
|
||||||
throw error
|
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
|
let client
|
||||||
try {
|
try {
|
||||||
@@ -156,15 +155,15 @@ export async function runWaitCommand(
|
|||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
if (state.status === 'timeout') {
|
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 {
|
return {
|
||||||
type: 'single',
|
type: 'single',
|
||||||
data: {
|
data: {
|
||||||
agentId: resolvedAgentId,
|
agentId: resolvedAgentId,
|
||||||
status: 'timeout',
|
status: 'timeout',
|
||||||
message: appendRecentActivity(
|
message: appendRecentActivity(timeoutMessage, recentActivity),
|
||||||
`Agent did not finish within ${timeoutLabel}. Run \`paseo wait ${resolvedAgentId}\` again to keep waiting.`,
|
|
||||||
recentActivity
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
schema: agentWaitSchema,
|
schema: agentWaitSchema,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,15 +167,16 @@ try {
|
|||||||
console.log('wait command mentions ID\n')
|
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()
|
const result = await $`npx paseo wait --help`.nothrow()
|
||||||
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
|
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
|
||||||
const hasDefault =
|
assert(
|
||||||
result.stdout.includes('600') || result.stdout.toLowerCase().includes('default')
|
result.stdout.toLowerCase().includes('default: no limit'),
|
||||||
assert(hasDefault, 'help should mention timeout default value')
|
'help should mention timeout default is no limit'
|
||||||
console.log('timeout option has default value documented\n')
|
)
|
||||||
|
console.log('timeout option documents no default limit\n')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up temp directory
|
// 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> {
|
async waitForFinish(agentId: string, timeout = 60000): Promise<WaitForFinishResult> {
|
||||||
const requestId = this.createRequestId()
|
const requestId = this.createRequestId()
|
||||||
|
const hasTimeout = Number.isFinite(timeout) && timeout > 0
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: 'wait_for_finish_request',
|
type: 'wait_for_finish_request',
|
||||||
requestId,
|
requestId,
|
||||||
agentId,
|
agentId,
|
||||||
timeoutMs: timeout,
|
...(hasTimeout ? { timeoutMs: timeout } : {}),
|
||||||
})
|
})
|
||||||
const payload = await this.sendCorrelatedRequest({
|
const payload = await this.sendCorrelatedRequest({
|
||||||
requestId,
|
requestId,
|
||||||
message,
|
message,
|
||||||
responseType: 'wait_for_finish_response',
|
responseType: 'wait_for_finish_response',
|
||||||
timeout: timeout + 5000,
|
timeout: hasTimeout ? timeout + 5000 : 0,
|
||||||
options: { skipQueue: true },
|
options: { skipQueue: true },
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5497,10 +5497,12 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
const effectiveTimeoutMs = timeoutMs ?? 600_000 // 10 minutes default
|
const hasTimeout = typeof timeoutMs === 'number' && timeoutMs > 0
|
||||||
const timeoutHandle = setTimeout(() => {
|
const timeoutHandle = hasTimeout
|
||||||
abortController.abort('timeout')
|
? setTimeout(() => {
|
||||||
}, effectiveTimeoutMs)
|
abortController.abort('timeout')
|
||||||
|
}, timeoutMs)
|
||||||
|
: null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result = await this.agentManager.waitForAgentEvent(agentId, {
|
let result = await this.agentManager.waitForAgentEvent(agentId, {
|
||||||
@@ -5553,7 +5555,9 @@ export class Session {
|
|||||||
payload: { requestId, status: 'timeout', final, error: null, lastMessage: null },
|
payload: { requestId, status: 'timeout', final, error: null, lastMessage: null },
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutHandle)
|
if (timeoutHandle) {
|
||||||
|
clearTimeout(timeoutHandle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user