Add long-task governor wrapper MVP skeleton
This commit is contained in:
123
scripts/long_task_governor_wrapper.mjs
Normal file
123
scripts/long_task_governor_wrapper.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'fs';
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { input: '', pretty: true };
|
||||
for (let i = 2; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--input') args.input = argv[++i] || '';
|
||||
else if (arg === '--compact') args.pretty = false;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function readInput(path) {
|
||||
if (!path || path === '-') {
|
||||
return fs.readFileSync(0, 'utf8');
|
||||
}
|
||||
return fs.readFileSync(path, 'utf8');
|
||||
}
|
||||
|
||||
function normalizeRequest(raw) {
|
||||
const data = JSON.parse(raw);
|
||||
return {
|
||||
requestText: data.requestText || '',
|
||||
hasFilesOrSystems: Boolean(data.hasFilesOrSystems),
|
||||
needsWaiting: Boolean(data.needsWaiting),
|
||||
needsSubagent: Boolean(data.needsSubagent),
|
||||
needsOwnerDecision: Boolean(data.needsOwnerDecision),
|
||||
canReplyNow: Boolean(data.canReplyNow),
|
||||
taskName: data.taskName || 'Untitled long-task',
|
||||
currentStep: data.currentStep || 'Classifying request',
|
||||
nextStep: data.nextStep || 'Define next actionable step',
|
||||
nextReportCondition: data.nextReportCondition || 'After next meaningful milestone',
|
||||
waitingOn: data.waitingOn || 'none',
|
||||
blocker: data.blocker || 'none',
|
||||
checkpointTrigger: data.checkpointTrigger || '',
|
||||
externalizedTrigger: data.externalizedTrigger || '',
|
||||
triggerKind: data.triggerKind || '',
|
||||
};
|
||||
}
|
||||
|
||||
function classify(input) {
|
||||
const classification = input.canReplyNow && !input.hasFilesOrSystems && !input.needsWaiting && !input.needsSubagent && !input.needsOwnerDecision
|
||||
? 'general_chat'
|
||||
: 'long_task';
|
||||
|
||||
const silentCandidate = classification === 'long_task' && (input.needsWaiting || input.needsSubagent || Boolean(input.checkpointTrigger));
|
||||
const needsCheckpoint = classification === 'long_task';
|
||||
|
||||
return {
|
||||
classification,
|
||||
silentCandidate,
|
||||
needsOwnerDecision: input.needsOwnerDecision,
|
||||
needsCheckpoint,
|
||||
needsSubagent: input.needsSubagent,
|
||||
};
|
||||
}
|
||||
|
||||
function bootstrapTaskState(input, classificationResult) {
|
||||
if (classificationResult.classification !== 'long_task') return null;
|
||||
return {
|
||||
task_name: input.taskName,
|
||||
status: input.blocker !== 'none' ? 'blocked' : (input.waitingOn !== 'none' ? 'waiting_user' : 'active'),
|
||||
current_step: input.currentStep,
|
||||
next_step: input.nextStep,
|
||||
next_report_condition: input.nextReportCondition,
|
||||
waiting_on: input.waitingOn,
|
||||
blocker: input.blocker,
|
||||
silent: classificationResult.silentCandidate,
|
||||
};
|
||||
}
|
||||
|
||||
function validateSilentLaunch(input, classificationResult) {
|
||||
if (!classificationResult.silentCandidate) {
|
||||
return { ok: true, reason: 'not a silent long-task' };
|
||||
}
|
||||
|
||||
if (!input.checkpointTrigger) {
|
||||
return { ok: false, reason: 'missing first forced checkpoint trigger' };
|
||||
}
|
||||
|
||||
if (!input.externalizedTrigger) {
|
||||
return { ok: false, reason: 'missing externalized checkpoint path' };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
reason: `${input.triggerKind || 'externalized'} trigger is defined`,
|
||||
};
|
||||
}
|
||||
|
||||
function planHandoff(classificationResult) {
|
||||
if (classificationResult.needsOwnerDecision) {
|
||||
return { mode: 'button_path' };
|
||||
}
|
||||
return { mode: 'direct_reply' };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv);
|
||||
const raw = readInput(args.input);
|
||||
const input = normalizeRequest(raw);
|
||||
const classificationResult = classify(input);
|
||||
const taskRecord = bootstrapTaskState(input, classificationResult);
|
||||
const silentLaunch = validateSilentLaunch(input, classificationResult);
|
||||
const handoff = planHandoff(classificationResult);
|
||||
|
||||
const output = {
|
||||
classification: classificationResult.classification,
|
||||
silentCandidate: classificationResult.silentCandidate,
|
||||
needsOwnerDecision: classificationResult.needsOwnerDecision,
|
||||
needsCheckpoint: classificationResult.needsCheckpoint,
|
||||
needsSubagent: classificationResult.needsSubagent,
|
||||
taskRecord,
|
||||
silentLaunchOk: silentLaunch.ok,
|
||||
silentLaunchReason: silentLaunch.reason,
|
||||
handoff,
|
||||
};
|
||||
|
||||
process.stdout.write(JSON.stringify(output, null, args.pretty ? 2 : 0) + '\n');
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user