diff --git a/packages/harness/src/runner.mjs b/packages/harness/src/runner.mjs index 00595542b..57f4b141a 100644 --- a/packages/harness/src/runner.mjs +++ b/packages/harness/src/runner.mjs @@ -62,7 +62,9 @@ export function founderCheck(policy, env) { // The request text one message becomes. export function turnRequest(m) { - const head = [`Message ${m.id} from ${m.from_role}`, `class ${m.class}`]; + const head = [`Message ${m.id} from ${m.from_role}`]; + // Row 109 (#1603): when the sender sent it, so a message that waited reads as old. + head.push(`class ${m.class}`); if (m.in_reply_to) head.push(`in reply to ${m.in_reply_to}`); if (m.decision) head.push(`citing decision ${m.decision}`); // Row 90 (#1584): the human input id task_create cites as its request. @@ -70,6 +72,22 @@ export function turnRequest(m) { return `${head.join(", ")}:\n\n${m.body}`; } +// Row 109 (#1603): the block at the top of a turn's request. It is what the broker said at `readAt`, +// the truth for launch state and task status over any older message. +export function stateBlock(state, readAt) { + const { launch, tasks } = state; + const since = launch.at ? ` since ${launch.at} (event ${launch.event})` : " (never switched)"; + const shown = tasks.open.length < tasks.total ? `, the first ${tasks.open.length} of cap ${tasks.cap}` : ""; + const open = tasks.open.map((t) => `- ${t.task_ref} ${t.title ?? ""}`.trimEnd()); + return [ + `Current state, read at ${readAt}:`, + `Launches: ${launch.state}${since}.`, + `Open tasks: ${tasks.total}${shown}.`, + ...open, + "", + ].join("\n"); +} + function clip(text) { return text.length > BODY_MAX ? text.slice(0, BODY_MAX) + "\n[answer cut at 32000 characters]" : text; } @@ -339,7 +357,23 @@ export async function main(runDir, { stdin = process.stdin, env = process.env, l } for (const m of messages) { if (stopping) break loop; - const turn = await runTurn({ session, request: turnRequest(m), n: ++n, env: turnEnv, onChild: (c) => (child = c) }); + // Row 109 (#1603): read the state now, not when the message was sent. A failed read fails the turn. + let block = null; + let unread = null; + try { + block = stateBlock(await client.call("state"), new Date().toISOString()); + } catch (e) { + if (SESSION_GONE.has(e.code)) { + log(`runner: session stopped working: ${e.code}`); + code = EXIT.session; + break loop; + } + unread = `could not read the current state: ${e.code ?? e.message}`; + } + ++n; + const turn = unread + ? { ok: false, reason: unread } + : await runTurn({ session, request: `${block}\n${turnRequest(m)}`, n, env: turnEnv, onChild: (c) => (child = c) }); if (stopping) break loop; if (!turn.ok) log(`runner: turn ${n} for message ${m.id} failed: ${turn.reason}`); try {