Files
LWLT-AI/archive/handoff/2026-07-12/legacy-erp-handoff/scripts/run_erp_task.js
2026-07-13 19:57:46 +08:00

138 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const {
dispatchTask,
parseArgs,
readConfig,
summarizeCliResult,
} = require('../tools/erp_task_dispatcher');
const { validateTaskEnvelope } = require('../tools/erp_task_contract');
function resolvePath(filePath, cwd = process.cwd()) {
if (!filePath) return '';
return path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
}
function readTaskFile(taskPath) {
const resolved = resolvePath(taskPath);
const text = fs.readFileSync(resolved, 'utf8').replace(/^\uFEFF/, '');
return JSON.parse(text);
}
function validationResult(validation, expectedOperation = '') {
const task = validation.task || {};
const operation = task.operation || (validation.envelope && validation.envelope.operation) || null;
const route = task.route || (validation.envelope && validation.envelope.route) || null;
const violations = [...(validation.violations || [])];
if (expectedOperation && operation && operation !== expectedOperation) {
violations.push(`expected operation ${expectedOperation}, received ${operation}`);
}
const status = violations.length
? 'invalid_task'
: (validation.status || 'invalid_task');
return {
status,
reason: status === 'needs_clarification' ? 'adapter_not_ready' : 'invalid_task',
operation,
route,
violations,
customerMessage: status === 'needs_clarification'
? 'Task needs clarification before ERP execution.'
: 'The ERP task JSON is invalid and was not sent to an executor.',
};
}
async function runTaskFile(taskPath, options = {}) {
const input = readTaskFile(taskPath);
const validation = validateTaskEnvelope(input);
if (!validation.valid) return validationResult(validation, options.expectedOperation);
const expectedOperation = options.expectedOperation || '';
if (expectedOperation && validation.task.operation !== expectedOperation) {
return validationResult(validation, expectedOperation);
}
const result = await dispatchTask(validation.envelope, {
mode: options.mode || 'dry-run',
config: options.config || {},
auditDir: options.auditDir,
source: options.source,
sender: options.sender,
senderId: options.senderId,
senderName: options.senderName,
chatId: options.chatId,
channelId: options.channelId,
handlers: options.handlers,
registryPath: options.registryPath,
taskLock: options.taskLock,
});
return summarizeCliResult(result);
}
function helpText() {
return [
'Usage:',
' node scripts/run_erp_task.js --task task.json [--mode dry-run|execute] [--config config.json] [--audit-dir dir] [--json]',
'',
'Runs a validated ERP task JSON through the dispatcher. The default mode is dry-run.',
].join('\n');
}
function exitCodeForResult(result) {
if (!result || result.status === 'blocked' || result.status === 'invalid_task'
|| result.status === 'needs_clarification') return 2;
return 0;
}
async function main(argv = process.argv.slice(2)) {
const args = parseArgs(argv);
if (args.help || !args.task) {
console.log(helpText());
process.exitCode = args.help ? 0 : 1;
return;
}
const result = await runTaskFile(args.task, {
mode: args.mode,
config: readConfig(args.config),
auditDir: args.auditDir ? resolvePath(args.auditDir) : undefined,
source: {
sender: args.sender,
senderId: args.senderId,
senderName: args.senderName,
chatId: args.chatId,
channelId: args.channelId,
},
});
if (args.json) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(result.customerMessage || result.status);
if (result.auditPath) console.log(`audit: ${result.auditPath}`);
if (Array.isArray(result.violations) && result.violations.length) {
console.log(`violations: ${result.violations.join('; ')}`);
}
}
process.exitCode = exitCodeForResult(result);
}
if (require.main === module) {
main().catch((error) => {
console.error(error.stack || error.message);
process.exitCode = 1;
});
}
module.exports = {
resolvePath,
readTaskFile,
validationResult,
runTaskFile,
exitCodeForResult,
main,
};