#!/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', 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 = 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();