fix(poll-loop): apply pre-task scripts to follow-up injections too

Tasks arriving during an active query were pushed into the stream as
follow-ups without running their `script` gate — so a wakeAgent=false
pre-script that was supposed to suppress the tick silently leaked
through and woke the agent every time. Evidence: monitoring cron
firing every 10 min with [task-script] log lines never showing.

Run applyPreTaskScripts on the follow-up batch too: wakeAgent=false
tasks get marked completed and dropped; wakeAgent=true tasks have
scriptOutput enriched exactly like the initial-batch path. Added a
pollInFlight guard to serialize async runs and avoid overlapping
script executions when the interval fires while one is still going.

Wrapped in a MODULE-HOOK:scheduling-pre-task-followup marker block
to match the existing initial-batch hook convention.
This commit is contained in:
robbyczgw-cla
2026-04-29 14:55:47 +00:00
parent 9e45845000
commit ef8e3aa1b8

View File

@@ -260,9 +260,13 @@ async function processQuery(
// Stream liveness is decided host-side via the heartbeat file + processing // Stream liveness is decided host-side via the heartbeat file + processing
// claim age (see src/host-sweep.ts); if something is truly stuck, the host // claim age (see src/host-sweep.ts); if something is truly stuck, the host
// will kill the container and messages get reset to pending. // will kill the container and messages get reset to pending.
let pollInFlight = false;
const pollHandle = setInterval(() => { const pollHandle = setInterval(() => {
if (done) return; if (done || pollInFlight) return;
pollInFlight = true;
void (async () => {
try {
// Skip system messages (MCP tool responses) and /clear (needs fresh query). // Skip system messages (MCP tool responses) and /clear (needs fresh query).
// Thread routing is the router's concern — if a message landed in this // Thread routing is the router's concern — if a message landed in this
// session, the agent should see it. Per-thread sessions already isolate // session, the agent should see it. Per-thread sessions already isolate
@@ -275,16 +279,39 @@ async function processQuery(
if ((m.kind === 'chat' || m.kind === 'chat-sdk') && isClearCommand(m)) return false; if ((m.kind === 'chat' || m.kind === 'chat-sdk') && isClearCommand(m)) return false;
return true; return true;
}); });
if (newMessages.length > 0) { if (newMessages.length === 0) return;
const newIds = newMessages.map((m) => m.id); const newIds = newMessages.map((m) => m.id);
markProcessing(newIds); markProcessing(newIds);
const prompt = formatMessages(newMessages); // Run pre-task scripts on follow-ups too — without this, a task that
log(`Pushing ${newMessages.length} follow-up message(s) into active query`); // arrives during an active query (e.g. a */10 monitoring cron) bypasses
query.push(prompt); // its script gate and always wakes the agent, defeating the gate.
// Mirrors the initial-batch hook above.
markCompleted(newIds); let keep = newMessages;
let skipped: string[] = [];
// MODULE-HOOK:scheduling-pre-task-followup:start
const { applyPreTaskScripts } = await import('./scheduling/task-script.js');
const preTask = await applyPreTaskScripts(newMessages);
keep = preTask.keep;
skipped = preTask.skipped;
if (skipped.length > 0) {
markCompleted(skipped);
log(`Pre-task script skipped ${skipped.length} follow-up task(s): ${skipped.join(', ')}`);
} }
// MODULE-HOOK:scheduling-pre-task-followup:end
if (keep.length === 0) return;
const keptIds = keep.map((m) => m.id);
const prompt = formatMessages(keep);
log(`Pushing ${keep.length} follow-up message(s) into active query`);
query.push(prompt);
markCompleted(keptIds);
} finally {
pollInFlight = false;
}
})();
}, ACTIVE_POLL_INTERVAL_MS); }, ACTIVE_POLL_INTERVAL_MS);
try { try {