feat: add script execution phase to agent-runner

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gabi Simons
2026-03-18 13:43:56 +02:00
committed by Gabi Simons
parent 42d098c3c1
commit 9f5aff99b6

View File

@@ -16,6 +16,7 @@
import fs from 'fs';
import path from 'path';
import { execFile } from 'child_process';
import { query, HookCallback, PreCompactHookInput } from '@anthropic-ai/claude-agent-sdk';
import { fileURLToPath } from 'url';
@@ -465,6 +466,55 @@ async function runQuery(
return { newSessionId, lastAssistantUuid, closedDuringQuery };
}
interface ScriptResult {
wakeAgent: boolean;
data?: unknown;
}
const SCRIPT_TIMEOUT_MS = 30_000;
async function runScript(script: string): Promise<ScriptResult | null> {
const scriptPath = '/tmp/task-script.sh';
fs.writeFileSync(scriptPath, script, { mode: 0o755 });
return new Promise((resolve) => {
execFile('bash', [scriptPath], {
timeout: SCRIPT_TIMEOUT_MS,
maxBuffer: 1024 * 1024,
env: process.env,
}, (error, stdout, stderr) => {
if (stderr) {
log(`Script stderr: ${stderr.slice(0, 500)}`);
}
if (error) {
log(`Script error: ${error.message}`);
return resolve(null);
}
// Parse last non-empty line of stdout as JSON
const lines = stdout.trim().split('\n');
const lastLine = lines[lines.length - 1];
if (!lastLine) {
log('Script produced no output');
return resolve(null);
}
try {
const result = JSON.parse(lastLine);
if (typeof result.wakeAgent !== 'boolean') {
log(`Script output missing wakeAgent boolean: ${lastLine.slice(0, 200)}`);
return resolve(null);
}
resolve(result as ScriptResult);
} catch {
log(`Script output is not valid JSON: ${lastLine.slice(0, 200)}`);
resolve(null);
}
});
});
}
async function main(): Promise<void> {
let containerInput: ContainerInput;
@@ -506,6 +556,26 @@ async function main(): Promise<void> {
prompt += '\n' + pending.join('\n');
}
// Script phase: run script before waking agent
if (containerInput.script && containerInput.isScheduledTask) {
log('Running task script...');
const scriptResult = await runScript(containerInput.script);
if (!scriptResult || !scriptResult.wakeAgent) {
const reason = scriptResult ? 'wakeAgent=false' : 'script error/no output';
log(`Script decided not to wake agent: ${reason}`);
writeOutput({
status: 'success',
result: `Script: ${reason}`,
});
return;
}
// Script says wake agent — enrich prompt with script data
log(`Script wakeAgent=true, enriching prompt with data`);
prompt = `[SCHEDULED TASK]\n\nScript output:\n${JSON.stringify(scriptResult.data, null, 2)}\n\nInstructions:\n${containerInput.prompt}`;
}
// Query loop: run query → wait for IPC message → run new query → repeat
let resumeAt: string | undefined;
try {