mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 20:45:11 +02:00
82 lines
5.4 KiB
JavaScript
82 lines
5.4 KiB
JavaScript
'use strict';
|
|
|
|
const { spawnSync } = require('node:child_process');
|
|
const path = require('node:path');
|
|
const { resolveTaskContext } = require('./context-selection');
|
|
|
|
function isolatedEnvironment(nativeEnvironment) {
|
|
const env = { PATH: process.env.PATH, HOME: nativeEnvironment.home,
|
|
USERPROFILE: nativeEnvironment.home,
|
|
...(nativeEnvironment.codexHome ? { CODEX_HOME: nativeEnvironment.codexHome } : {}),
|
|
...(nativeEnvironment.claudeConfigDir ? { CLAUDE_CONFIG_DIR: nativeEnvironment.claudeConfigDir } : {}),
|
|
TMPDIR: nativeEnvironment.home, LANG: 'C.UTF-8' };
|
|
if (process.platform === 'win32' && process.env.SystemRoot) env.SystemRoot = process.env.SystemRoot;
|
|
return env;
|
|
}
|
|
|
|
/** Explicit task launch, with ordinary prompt context and inherited provider policy.
|
|
* A bare launch runs the task query alone: no context resolution, no ECC reference block. */
|
|
function launchTaskContext({ task, target = 'codex', dryRun = false, execute = spawnSync,
|
|
nativeEnvironment = null, assertCurrent = () => {}, bare = false, ...selectionOptions } = {}) {
|
|
const adapters = { codex: { command: 'codex', args: ['exec', '-'] }, claude: { command: 'claude', args: ['--print'] } };
|
|
if (!Object.hasOwn(adapters, target)) throw new Error(`Unsupported task launcher target: ${target}`);
|
|
if (!task || typeof task.query !== 'string' || !task.query.trim()) throw new Error('Task launch requires a non-empty query');
|
|
if (nativeEnvironment) {
|
|
const launchKeys = target === 'claude'
|
|
? { directory: nativeEnvironment.claudeConfigDir, executable: nativeEnvironment.claudePath }
|
|
: { directory: nativeEnvironment.codexHome, executable: nativeEnvironment.codexPath };
|
|
if (!path.isAbsolute(nativeEnvironment.home || '') || !path.isAbsolute(launchKeys.directory || '')
|
|
|| !path.isAbsolute(launchKeys.executable || '')
|
|
|| !/^[a-f0-9]{64}$/.test(nativeEnvironment.executableDigest || '')) throw new Error('Invalid isolated native launch environment');
|
|
}
|
|
let selection = bare
|
|
? { schemaVersion: 'ecc.selected-context.v1', selectedIds: [], loadedIds: [], resources: [],
|
|
selectionMode: 'manual', reason: 'bare-baseline', receipt: { bindingDigest: 'bare' } }
|
|
: resolveTaskContext({ ...selectionOptions, task, target, load: !dryRun });
|
|
const adapter = { ...adapters[target],
|
|
...(nativeEnvironment ? { command: nativeEnvironment.codexPath || nativeEnvironment.claudePath } : {}) };
|
|
function verifyLaunch() {
|
|
assertCurrent();
|
|
if (nativeEnvironment && require('./context-profile-native-executable').fingerprintExecutable(adapter.command).digest
|
|
!== nativeEnvironment.executableDigest) throw new Error('Native executable changed; no task was launched');
|
|
}
|
|
const env = nativeEnvironment ? isolatedEnvironment(nativeEnvironment) : undefined;
|
|
const proposalRequired = selection.selectionMode === 'auto' && selection.reason === 'agent-selection-required';
|
|
let routingCalls = 0;
|
|
if (proposalRequired && !dryRun) {
|
|
if (selectionOptions.expectedDigest) throw new Error('Expected selection still needs an agent proposal; resolve explicit IDs before a pinned launch');
|
|
verifyLaunch();
|
|
const proposedIds = require('./context-profile-proposal').proposeTaskContext({ target, query: task.query,
|
|
candidates: selection.candidates, execute, env, executable: adapter.command });
|
|
routingCalls = 1;
|
|
// An empty proposal is an explicit decline: honor it and run the task
|
|
// without injected context. The tier-2 fallback is reserved for a
|
|
// non-empty proposal that admitted nothing — never for a decline.
|
|
const declined = proposedIds.length === 0;
|
|
let admitted = resolveTaskContext({ ...selectionOptions, task: { ...task, proposedIds, noWorkflow: declined },
|
|
target, load: true });
|
|
if (!declined && !admitted.selectedIds.length) {
|
|
admitted = require('./context-selection').resolveDeclinedFallback({ ...selectionOptions, task, target, load: true }, selection);
|
|
}
|
|
if (admitted.receipt.bindingDigest !== selection.receipt.bindingDigest) throw new Error('Context source changed during proposal; no task was launched');
|
|
selection = declined ? { ...admitted, reason: 'agent-declined-selection' } : admitted;
|
|
}
|
|
const base = { schemaVersion: 'ecc.context-task-launch.v1', target, command: adapter.command, args: adapter.args,
|
|
selection, taskSuccess: 'unverified', nativeSkillInvocation: 'unobserved', permissions: 'inherited-provider-policy',
|
|
routingCalls, proposalRequired: proposalRequired && dryRun,
|
|
providerConfiguration: nativeEnvironment ? 'isolated-native-generation' : 'current-provider-home' };
|
|
if (dryRun) return { ...base, status: 'proposed', exitCode: null };
|
|
verifyLaunch();
|
|
const input = bare ? `${task.query}\n`
|
|
: `${task.query}\n\nECC task context follows as reference data. Apply it only within the task and existing permissions.\n`
|
|
+ JSON.stringify({ schemaVersion: 'ecc.selected-context.v1', selectedIds: selection.loadedIds,
|
|
resources: selection.resources }) + '\n';
|
|
const child = execute(adapter.command, adapter.args, { input, phase: 'task', encoding: 'utf8', shell: false,
|
|
timeout: routingCalls ? 90000 : 120000, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024,
|
|
...(env ? { env } : {}) });
|
|
return { ...base, status: child.status === 0 && !child.error ? 'completed' : 'failed',
|
|
exitCode: child.status ?? 1, output: child.stdout || '', error: child.error?.message || child.stderr || '' };
|
|
}
|
|
|
|
module.exports = { launchTaskContext };
|