#!/usr/bin/env node import fs from 'fs'; function fail(code, message) { process.stderr.write(`${code}: ${message}\n`); process.exit(1); } function parseArgs(argv) { const args = { input: '', pretty: true }; for (let i = 2; i < argv.length; i += 1) { const arg = argv[i]; if (arg === '--input') { const value = argv[i + 1]; if (!value || value.startsWith('--')) fail('CLI_ERROR', '--input requires a value'); args.input = value; i += 1; } else if (arg === '--compact') { args.pretty = false; } else { fail('CLI_ERROR', `unknown argument: ${arg}`); } } return args; } function readInput(path) { if (!path || path === '-') return fs.readFileSync(0, 'utf8'); return fs.readFileSync(path, 'utf8'); } function normalizeRequest(raw) { let data; try { data = JSON.parse(raw); } catch { fail('INVALID_JSON', 'input must be valid JSON'); } 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; } if (!input.checkpointTrigger && inferred.needsSubagent) { inferred.checkpointTrigger = 'when delegated work returns or the next checkpoint fires'; } if (!input.externalizedTrigger && inferred.needsSubagent) { inferred.externalizedTrigger = 'wrapper-derived checkpoint artifact'; } if (!input.triggerKind && inferred.needsSubagent) { inferred.triggerKind = 'artifact'; } 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 toSlug(value) { return String(value || '') .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 48); } function buildExternalizedCheckpointPath(input, classificationResult) { if (classificationResult.classification !== 'long_task') return ''; if (!classificationResult.silentCandidate) return ''; if (!input.externalizedTrigger) return ''; const taskSeed = [input.currentStep, input.nextStep, input.waitingOn, input.blocker] .map((value) => toSlug(value)) .filter(Boolean) .join('-'); const stableSeed = taskSeed || 'long-task'; return `checkpoints/${stableSeed}.json`; } function buildProgressEvidence(input, classificationResult, externalizedCheckpointPath) { if (classificationResult.classification !== 'long_task') return null; if (!classificationResult.silentCandidate) return null; if (!externalizedCheckpointPath) return null; return { sessionKey: toSlug([input.currentStep, input.waitingOn, input.nextStep].filter(Boolean).join('-')) || 'long-task-session', checkpointPath: externalizedCheckpointPath, }; } 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 externalizedCheckpointPath = buildExternalizedCheckpointPath(input, classificationResult); const progressEvidence = buildProgressEvidence(input, classificationResult, externalizedCheckpointPath); 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, progressEvidence, externalizedCheckpointPath, 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'); } try { main(); } catch (error) { fail('CLI_ERROR', error && error.message ? error.message : 'unexpected error'); }