fix(tasks): use --expect-final for agent dispatch and Aegis review

The two-step agent → agent.wait pattern used in dispatchAssignedTasks and
runAegisReviews only returns lifecycle metadata (runId, status, timestamps).
The agent's actual response text is only available via --expect-final on the
initial agent call, which blocks until completion and returns the full payload
including result.payloads[0].text.

Without this fix:
- Task resolution is stored as the raw wait JSON instead of the agent's response
- Aegis cannot parse a VERDICT from the resolution, so it always defaults to rejected
- Tasks are permanently stuck in a reject/retry loop and never complete

Fix: replace the two-call pattern with a single --expect-final call in both
dispatchAssignedTasks and runAegisReviews. Also improve sessionId extraction
to use the agentMeta path from the final payload.

Co-authored-by: Tom Watts <tom@techteamup.com>
This commit is contained in:
Tom Watts 2026-03-14 09:38:55 +00:00 committed by GitHub
parent 4fa1cbd3a5
commit 5d7b05b4f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 19 additions and 30 deletions

View File

@ -215,22 +215,18 @@ export async function runAegisReviews(): Promise<{ ok: boolean; message: string
idempotencyKey: `aegis-review-${task.id}-${Date.now()}`,
deliver: false,
}
const invokeResult = await runOpenClaw(
['gateway', 'call', 'agent', '--timeout', '10000', '--params', JSON.stringify(invokeParams), '--json'],
{ timeoutMs: 12_000 }
)
const acceptedPayload = parseGatewayJson(invokeResult.stdout)
?? parseGatewayJson(String((invokeResult as any)?.stderr || ''))
const runId = acceptedPayload?.runId
if (!runId) throw new Error('Gateway did not return a runId for Aegis review')
const waitResult = await runOpenClaw(
['gateway', 'call', 'agent.wait', '--timeout', '120000', '--params', JSON.stringify({ runId, timeoutMs: 115_000 }), '--json'],
// Use --expect-final to block until the agent completes and returns the full
// response payload (payloads[0].text). The two-step agent → agent.wait pattern
// only returns lifecycle metadata (runId/status/timestamps) and never includes
// the agent's actual text, so Aegis could never parse a verdict.
const finalResult = await runOpenClaw(
['gateway', 'call', 'agent', '--expect-final', '--timeout', '120000', '--params', JSON.stringify(invokeParams), '--json'],
{ timeoutMs: 125_000 }
)
const waitPayload = parseGatewayJson(waitResult.stdout)
const finalPayload = parseGatewayJson(finalResult.stdout)
?? parseGatewayJson(String((finalResult as any)?.stderr || ''))
const agentResponse = parseAgentResponse(
waitPayload?.result ? JSON.stringify(waitPayload.result) : waitResult.stdout
finalPayload?.result ? JSON.stringify(finalPayload.result) : finalResult.stdout
)
if (!agentResponse.text) {
throw new Error('Aegis review returned empty response')
@ -382,28 +378,21 @@ export async function dispatchAssignedTasks(): Promise<{ ok: boolean; message: s
idempotencyKey: `task-dispatch-${task.id}-${Date.now()}`,
deliver: false,
}
const invokeResult = await runOpenClaw(
['gateway', 'call', 'agent', '--timeout', '10000', '--params', JSON.stringify(invokeParams), '--json'],
{ timeoutMs: 12_000 }
)
const acceptedPayload = parseGatewayJson(invokeResult.stdout)
?? parseGatewayJson(String((invokeResult as any)?.stderr || ''))
const runId = acceptedPayload?.runId
if (!runId) throw new Error('Gateway did not return a runId for task dispatch')
// Step 2: Wait for completion
const waitResult = await runOpenClaw(
['gateway', 'call', 'agent.wait', '--timeout', '120000', '--params', JSON.stringify({ runId, timeoutMs: 115_000 }), '--json'],
// Use --expect-final to block until the agent completes and returns the full
// response payload (result.payloads[0].text). The two-step agent → agent.wait
// pattern only returns lifecycle metadata and never includes the agent's text.
const finalResult = await runOpenClaw(
['gateway', 'call', 'agent', '--expect-final', '--timeout', '120000', '--params', JSON.stringify(invokeParams), '--json'],
{ timeoutMs: 125_000 }
)
const waitPayload = parseGatewayJson(waitResult.stdout)
const finalPayload = parseGatewayJson(finalResult.stdout)
?? parseGatewayJson(String((finalResult as any)?.stderr || ''))
const agentResponse = parseAgentResponse(
waitPayload?.result ? JSON.stringify(waitPayload.result) : waitResult.stdout
finalPayload?.result ? JSON.stringify(finalPayload.result) : finalResult.stdout
)
// Capture sessionId from the wait payload if not in the parsed response
if (!agentResponse.sessionId && waitPayload?.sessionId) {
agentResponse.sessionId = waitPayload.sessionId
if (!agentResponse.sessionId && finalPayload?.result?.meta?.agentMeta?.sessionId) {
agentResponse.sessionId = finalPayload.result.meta.agentMeta.sessionId
}
if (!agentResponse.text) {