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:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { execFile } from 'child_process';
|
||||||
import { query, HookCallback, PreCompactHookInput } from '@anthropic-ai/claude-agent-sdk';
|
import { query, HookCallback, PreCompactHookInput } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
@@ -465,6 +466,55 @@ async function runQuery(
|
|||||||
return { newSessionId, lastAssistantUuid, closedDuringQuery };
|
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> {
|
async function main(): Promise<void> {
|
||||||
let containerInput: ContainerInput;
|
let containerInput: ContainerInput;
|
||||||
|
|
||||||
@@ -506,6 +556,26 @@ async function main(): Promise<void> {
|
|||||||
prompt += '\n' + pending.join('\n');
|
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
|
// Query loop: run query → wait for IPC message → run new query → repeat
|
||||||
let resumeAt: string | undefined;
|
let resumeAt: string | undefined;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user