From ed53346af06190b8ddbc0a5b1e3370d87c7c26d1 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 23 Apr 2026 08:45:54 +0800 Subject: [PATCH] fix: include long-task wrapper implementation for task 3 --- scripts/long_task_governor_wrapper.mjs | 158 +++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/scripts/long_task_governor_wrapper.mjs b/scripts/long_task_governor_wrapper.mjs index e69de29..45c897c 100644 --- a/scripts/long_task_governor_wrapper.mjs +++ b/scripts/long_task_governor_wrapper.mjs @@ -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();