fix: include long-task wrapper implementation for task 3
This commit is contained in:
@@ -0,0 +1,158 @@
|
|||||||
|
#!/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 inferFromRequestText(input) {
|
||||||
|
const text = (input.requestText || '').toLowerCase();
|
||||||
|
const inferred = { ...input };
|
||||||
|
|
||||||
|
if (!input.canReplyNow && /\b(can( not|'t)? use|check|inspect|investigate|review|verify|fix|debug|analyze|analyse|compare|deploy|run)\b/.test(text)) {
|
||||||
|
inferred.hasFilesOrSystems = true;
|
||||||
|
}
|
||||||
|
if (!input.needsOwnerDecision && /\b(accept|reject|approve|decision|choose|pick|verdict)\b/.test(text)) {
|
||||||
|
inferred.needsOwnerDecision = true;
|
||||||
|
}
|
||||||
|
if (!input.needsWaiting && /\b(wait|later|after|async|background|follow up|follow-up)\b/.test(text)) {
|
||||||
|
inferred.needsWaiting = true;
|
||||||
|
}
|
||||||
|
if (!input.needsSubagent && /\bsubagent\b/.test(text)) {
|
||||||
|
inferred.needsSubagent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
recommendedFallback: 'none',
|
||||||
|
requiredNextAction: 'proceed_with_normal_long_task_flow',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input.checkpointTrigger) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: 'missing first forced checkpoint trigger',
|
||||||
|
recommendedFallback: 'non_silent_follow_up',
|
||||||
|
requiredNextAction: 'define_first_checkpoint_trigger_before_silent_launch',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input.externalizedTrigger) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: 'missing externalized checkpoint path',
|
||||||
|
recommendedFallback: 'non_silent_follow_up',
|
||||||
|
requiredNextAction: 'bind_externalized_checkpoint_path_or_abort_silent_launch',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
reason: `${input.triggerKind || 'externalized'} trigger is defined`,
|
||||||
|
recommendedFallback: 'none',
|
||||||
|
requiredNextAction: 'proceed_with_silent_launch',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = inferFromRequestText(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,
|
||||||
|
recommendedFallback: silentLaunch.recommendedFallback,
|
||||||
|
requiredNextAction: silentLaunch.requiredNextAction,
|
||||||
|
handoff,
|
||||||
|
};
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify(output, null, args.pretty ? 2 : 0) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|||||||
Reference in New Issue
Block a user