Add long-task governor wrapper MVP skeleton
This commit is contained in:
17
docs/_artifacts/long_task_governor_wrapper_example.json
Normal file
17
docs/_artifacts/long_task_governor_wrapper_example.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"requestText": "Research OpenClawGovernor and report back later.",
|
||||||
|
"hasFilesOrSystems": true,
|
||||||
|
"needsWaiting": true,
|
||||||
|
"needsSubagent": false,
|
||||||
|
"needsOwnerDecision": false,
|
||||||
|
"canReplyNow": false,
|
||||||
|
"taskName": "Research OpenClawGovernor concepts",
|
||||||
|
"currentStep": "Reading source material",
|
||||||
|
"nextStep": "Extract reusable governance ideas",
|
||||||
|
"nextReportCondition": "After the first source review is complete",
|
||||||
|
"waitingOn": "none",
|
||||||
|
"blocker": "none",
|
||||||
|
"checkpointTrigger": "after initial source review completes",
|
||||||
|
"externalizedTrigger": "cron reminder",
|
||||||
|
"triggerKind": "cron"
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"requestText": "Investigate the Telegram closure issue and report back later.",
|
||||||
|
"hasFilesOrSystems": true,
|
||||||
|
"needsWaiting": true,
|
||||||
|
"needsSubagent": false,
|
||||||
|
"needsOwnerDecision": true,
|
||||||
|
"canReplyNow": false,
|
||||||
|
"taskName": "Investigate Telegram closure issue",
|
||||||
|
"currentStep": "Reviewing recent failures",
|
||||||
|
"nextStep": "Narrow the root cause",
|
||||||
|
"nextReportCondition": "After next meaningful milestone",
|
||||||
|
"waitingOn": "none",
|
||||||
|
"blocker": "none",
|
||||||
|
"checkpointTrigger": "",
|
||||||
|
"externalizedTrigger": "",
|
||||||
|
"triggerKind": ""
|
||||||
|
}
|
||||||
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