total-recall-brain 3.41.5 → 3.41.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/total-recall.mjs +7 -4
- package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
- package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +11 -0
- package/frontend/src/App.tsx +47 -122
- package/frontend/src/components/plugins/plugin-frame.ts +1 -0
- package/frontend/src/index.css +152 -86
- package/frontend/src/pages/HelpPage.tsx +13 -10
- package/frontend/src/pages/MemoryPage.tsx +28 -30
- package/frontend/src/pages/NetworkPage.tsx +3 -3
- package/frontend/src/pages/PluginsPage.tsx +6 -5
- package/frontend/src/pages/RulesPage.tsx +16 -12
- package/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +42 -190
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +8 -8
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -0
- package/src/cli/agent.mjs +12 -1
- package/src/cli/compact.mjs +2 -2
- package/src/cli/connect.mjs +56 -1
- package/src/cli/context.mjs +10 -7
- package/src/cli/doctor.mjs +5 -2
- package/src/cli/harness.mjs +62 -50
- package/src/cli/memory-entry.mjs +2 -2
- package/src/cli/preflight.mjs +152 -16
- package/src/cli/remember.mjs +19 -1
- package/src/cli/rules.mjs +54 -12
- package/src/cli/startup.mjs +1 -1
- package/src/cli/status.mjs +12 -2
- package/src/core/claude-hooks.mjs +41 -3
- package/src/core/cli-semantic-index.mjs +3 -2
- package/src/core/conflict-detector.mjs +23 -12
- package/src/core/context-compactor.mjs +2 -0
- package/src/core/context-policy.mjs +20 -0
- package/src/core/duplicate-guard.mjs +350 -0
- package/src/core/meta-harness.mjs +31 -11
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +28 -0
- package/src/core/scaffold-plugins.mjs +7 -0
- package/src/core/stdio-flush.mjs +23 -0
- package/src/core/surface.mjs +33 -10
- package/src/server/routes/plugins.mjs +2 -2
- package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
- package/frontend/dist/assets/index-WT3gs-x6.js +0 -5631
- package/frontend/src/components/DaemonLogsTab.tsx +0 -54
- package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
- package/frontend/src/components/TaskQueueTab.tsx +0 -193
- package/frontend/src/components/VoiceInput.tsx +0 -112
- package/frontend/src/pages/ChatPage.tsx +0 -1059
- package/frontend/src/pages/CollabPage.tsx +0 -470
- package/frontend/src/pages/DesignDocsPage.tsx +0 -621
- package/frontend/src/pages/FilesPage.tsx +0 -393
- package/frontend/src/pages/HealthPage.tsx +0 -359
- package/frontend/src/pages/InboxPage.tsx +0 -83
- package/frontend/src/pages/InstructionsPage.tsx +0 -583
- package/frontend/src/pages/IntegrationsPage.tsx +0 -761
- package/frontend/src/pages/MeshPage.css +0 -58
- package/frontend/src/pages/MeshPage.tsx +0 -1219
- package/frontend/src/pages/OkfPage.tsx +0 -535
- package/frontend/src/pages/SandboxPage.tsx +0 -113
- package/frontend/src/pages/SkillsPage.tsx +0 -1994
- package/frontend/src/pages/TasksPage.tsx +0 -427
- package/frontend/src/pages/UsagePage.tsx +0 -702
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
|
@@ -1,398 +1,215 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - Race: Speculative concurrent execution
|
|
7
|
-
*
|
|
8
|
-
* Works with any user's installed harness pool:
|
|
9
|
-
* - Dynamically discovers installed harnesses and assigns distinct model families
|
|
10
|
-
* for Architect, Implementer, and Verifier so confirmation bias is avoided.
|
|
11
|
-
* - Supports plan generation (default safe inspection) and live execution (`execute: true`)
|
|
12
|
-
* via `dispatchAgent` with real git worktree isolation for the Verifier stage.
|
|
2
|
+
* Multi-agent coordination.
|
|
3
|
+
* Pipeline: Explorer -> Architect -> Implementer -> Verifier. Writing happens only on a dedicated
|
|
4
|
+
* branch in its own worktree, and the Verifier inspects that exact commit from a different model
|
|
5
|
+
* family. Nothing merges into the operator's branch; the branch is the deliverable.
|
|
13
6
|
*/
|
|
14
|
-
|
|
15
|
-
import fs from 'node:fs';
|
|
16
|
-
import path from 'node:path';
|
|
17
|
-
import os from 'node:os';
|
|
18
|
-
import { spawnSync } from 'node:child_process';
|
|
19
|
-
import { ADAPTER_CATALOG, classifyTaskComplexity } from '../capability/index.mjs';
|
|
20
|
-
import { inspectAgents, dispatchAgent, getActiveAdapters } from './worker.mjs';
|
|
7
|
+
import { ADAPTER_CATALOG, classifyTaskComplexity, synthesizeOneWayValve } from '../capability/index.mjs';
|
|
21
8
|
import { notifyAlert } from './alerts.mjs';
|
|
22
|
-
import {
|
|
9
|
+
import { dispatchWithReceipt } from './dispatch.mjs';
|
|
10
|
+
import { resolvePool } from './pool.mjs';
|
|
11
|
+
import { createThread, appendThreadTurn, updateThread } from './threads.mjs';
|
|
12
|
+
import { executeCodeReview } from './review.mjs';
|
|
13
|
+
import { isGitRepo, isDirty, headCommit, createWorktree, removeWorktree, commitAll, diffBetween, linkDependencies, deleteBranch } from './worktree.mjs';
|
|
14
|
+
import { lastJsonWith } from './json-reply.mjs';
|
|
15
|
+
import { runQualityGate } from './gate.mjs';
|
|
23
16
|
|
|
24
|
-
/**
|
|
25
|
-
* Resolve installed or available harnesses with their model family metadata
|
|
26
|
-
*/
|
|
27
|
-
async function resolvePool(options = {}) {
|
|
28
|
-
const active = getActiveAdapters();
|
|
29
|
-
let installedIds = [];
|
|
30
|
-
if (Array.isArray(options.availableHarnesses) && options.availableHarnesses.length > 0) {
|
|
31
|
-
installedIds = options.availableHarnesses;
|
|
32
|
-
} else {
|
|
33
|
-
try {
|
|
34
|
-
const inspected = await inspectAgents({ env: process.env });
|
|
35
|
-
installedIds = (inspected.agents || []).filter(a => a.installed).map(a => a.id);
|
|
36
|
-
} catch {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const candidateIds = installedIds.length > 0 ? installedIds : active.map(a => a.id);
|
|
40
|
-
return candidateIds.map(id => {
|
|
41
|
-
const cap = ADAPTER_CATALOG[id] || {};
|
|
42
|
-
const workerSpec = active.find(a => a.id === id) || {};
|
|
43
|
-
return {
|
|
44
|
-
id,
|
|
45
|
-
name: cap.name || workerSpec.name || id,
|
|
46
|
-
family: cap.family || 'custom',
|
|
47
|
-
tier: cap.tier || 'budget',
|
|
48
|
-
qualityScore: cap.qualityScore ?? 7.5,
|
|
49
|
-
costScore: cap.costScore ?? 4
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Select three role-specialized harnesses preferring distinct model families
|
|
56
|
-
*/
|
|
57
17
|
export function assignPipelineRoles(pool = [], options = {}) {
|
|
58
18
|
const sorted = [...pool].sort((a, b) => b.qualityScore - a.qualityScore);
|
|
59
|
-
const findById =
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
? findById(options.verifier)
|
|
72
|
-
: (sorted.find(h => h.family !== implementer.family && h.id !== implementer.id)
|
|
73
|
-
|| sorted.find(h => h.id !== implementer.id)
|
|
74
|
-
|| implementer);
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
architect,
|
|
78
|
-
implementer,
|
|
79
|
-
verifier,
|
|
80
|
-
heterogeneousFamilies: verifier.family !== implementer.family || pool.length <= 1
|
|
81
|
-
};
|
|
19
|
+
const findById = id => sorted.find(h => h.id === id) || (ADAPTER_CATALOG[id] ? { ...ADAPTER_CATALOG[id], id } : { id, name: id, family: 'custom' });
|
|
20
|
+
// The session's own harness leads only when it can take work; TR_ACTIVE_HARNESS is advisory.
|
|
21
|
+
const active = process.env.TR_ACTIVE_HARNESS;
|
|
22
|
+
const orchestrator = options.orchestrator ? findById(options.orchestrator) : sorted.find(h => h.id === active) || sorted[0] || null;
|
|
23
|
+
if (!orchestrator) return { orchestrator: null, explorer: null, architect: null, implementer: null, verifier: null, heterogeneousFamilies: false };
|
|
24
|
+
|
|
25
|
+
const byCost = [...pool].sort((a, b) => a.costScore - b.costScore);
|
|
26
|
+
const explorer = options.explorer ? findById(options.explorer) : byCost.find(h => h.id !== orchestrator.id) || orchestrator;
|
|
27
|
+
const architect = options.architect ? findById(options.architect) : orchestrator;
|
|
28
|
+
const implementer = options.implementer ? findById(options.implementer) : sorted.find(h => h.id !== explorer.id && h.id !== architect.id) || sorted.find(h => h.id !== architect.id) || architect;
|
|
29
|
+
const verifier = options.verifier ? findById(options.verifier) : sorted.find(h => h.family !== implementer.family) || null;
|
|
30
|
+
return { orchestrator, explorer, architect, implementer, verifier, heterogeneousFamilies: Boolean(verifier) && verifier.family !== implementer.family };
|
|
82
31
|
}
|
|
83
32
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!task) throw new Error('Task description is required for coordination');
|
|
91
|
-
|
|
92
|
-
const pool = await resolvePool(options);
|
|
93
|
-
|
|
94
|
-
if (mode === 'pipeline') {
|
|
95
|
-
return await runSpecializedPipeline(task, pool, options);
|
|
96
|
-
} else if (mode === 'council') {
|
|
97
|
-
return await runCouncilCoordination(task, pool, options);
|
|
98
|
-
} else if (mode === 'race') {
|
|
99
|
-
return await runRaceCoordination(task, pool, options);
|
|
100
|
-
} else {
|
|
101
|
-
throw new Error(`Unsupported coordination mode: ${mode}`);
|
|
102
|
-
}
|
|
33
|
+
/** The agent must state a verdict. Silence or an unreadable answer is not a pass. */
|
|
34
|
+
export function parseVerification(response) {
|
|
35
|
+
const parsed = lastJsonWith(response, 'verdict');
|
|
36
|
+
const verdict = String(parsed?.verdict).toUpperCase();
|
|
37
|
+
if (['PASS', 'FAIL'].includes(verdict)) return { parsed: true, verdict, findings: Array.isArray(parsed.findings) ? parsed.findings : [] };
|
|
38
|
+
return { parsed: false, verdict: 'UNREADABLE', findings: [] };
|
|
103
39
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
function createVerifierWorktree(cwd) {
|
|
109
|
-
try {
|
|
110
|
-
const check = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd, encoding: 'utf8', timeout: 3000 });
|
|
111
|
-
if (check.status !== 0) return null;
|
|
112
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'meta-harness-verify-'));
|
|
113
|
-
const add = spawnSync('git', ['worktree', 'add', '--detach', tmpDir], { cwd, encoding: 'utf8', timeout: 10000 });
|
|
114
|
-
if (add.status !== 0) {
|
|
115
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
return tmpDir;
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
40
|
+
export function parseVote(response) {
|
|
41
|
+
const parsed = lastJsonWith(response, 'vote');
|
|
42
|
+
const vote = String(parsed?.vote).toUpperCase();
|
|
43
|
+
return ['APPROVE', 'REJECT'].includes(vote) ? { vote, reason: parsed.reason || null } : { vote: 'ABSTAIN', reason: null };
|
|
122
44
|
}
|
|
123
45
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
46
|
+
const stageOf = (role, harness, permissions, action, extra = {}) => ({ role, harness: harness?.id || null, harnessName: harness?.name || null, modelFamily: harness?.family || null, permissions, action, status: 'planned', output: null, ...extra });
|
|
47
|
+
const READ = { read: true, write: false };
|
|
48
|
+
const WRITE = { read: true, write: true };
|
|
49
|
+
|
|
50
|
+
function buildStages(roles, withSubagents) {
|
|
51
|
+
const stages = [];
|
|
52
|
+
if (withSubagents) stages.push(stageOf('Explorer', roles.explorer, READ, 'Find the relevant files, symbols and interfaces.', { contextDigest: null }));
|
|
53
|
+
stages.push(stageOf('Architect', roles.architect, READ, 'Produce the blueprint, file list and verification steps.'));
|
|
54
|
+
stages.push(stageOf('Implementer', roles.implementer, WRITE, 'Write the code and tests on a dedicated branch in its own worktree.'));
|
|
55
|
+
stages.push(stageOf('Verifier', roles.verifier, READ, 'Audit the implementation commit from a different model family.', { worktreeIsolated: true, passed: null }));
|
|
56
|
+
return stages;
|
|
132
57
|
}
|
|
133
58
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*/
|
|
138
|
-
async function runSpecializedPipeline(task, pool, options = {}) {
|
|
139
|
-
const startTime = Date.now();
|
|
59
|
+
async function runPipeline(task, poolInfo, options) {
|
|
60
|
+
const started = Date.now();
|
|
61
|
+
const pool = poolInfo.ready;
|
|
140
62
|
const roles = assignPipelineRoles(pool, options);
|
|
141
|
-
const executeLive = options.execute === true;
|
|
142
63
|
const cwd = options.cwd || process.cwd();
|
|
143
|
-
const
|
|
64
|
+
const execute = options.execute === true;
|
|
65
|
+
const withSubagents = options.subagents !== false;
|
|
66
|
+
const stages = roles.orchestrator ? buildStages(roles, withSubagents) : [];
|
|
67
|
+
const base = { mode: 'pipeline', task, orchestrator: roles.orchestrator?.id || null, executedLive: execute, excluded: poolInfo.excluded, heterogeneousFamiliesVerified: roles.heterogeneousFamilies, stages };
|
|
68
|
+
|
|
69
|
+
if (!execute) {
|
|
70
|
+
const blockers = [];
|
|
71
|
+
if (!roles.orchestrator) blockers.push('No harness can take work right now.');
|
|
72
|
+
else if (!roles.heterogeneousFamilies) blockers.push('Only one model family is available, so no independent Verifier exists.');
|
|
73
|
+
return { ...base, ok: blockers.length === 0, threadId: null, blockers };
|
|
74
|
+
}
|
|
75
|
+
if (!roles.orchestrator) throw new Error(`No harness can take work right now: ${poolInfo.excluded.map(e => `${e.harness} (${e.reason})`).join(', ') || 'none installed'}`);
|
|
76
|
+
if (!roles.heterogeneousFamilies && options.allowSameFamily !== true) throw new Error('Refusing to run: only one model family is available, so no independent Verifier exists. Pass --allow-same-family to accept self-review.');
|
|
77
|
+
if (!isGitRepo(cwd)) throw new Error('A pipeline writes on its own branch, so it must run inside a git repository');
|
|
78
|
+
|
|
79
|
+
const verifier = roles.verifier || roles.implementer;
|
|
144
80
|
const complexity = classifyTaskComplexity(task);
|
|
81
|
+
const thread = createThread({ title: task.slice(0, 80), task, platform: roles.orchestrator.id, model: roles.orchestrator.name, status: 'active' });
|
|
82
|
+
const baseCommit = headCommit(cwd);
|
|
83
|
+
const branch = options.branch || `meta-harness/${thread.id}`;
|
|
84
|
+
const warnings = isDirty(cwd) ? ['The working tree has uncommitted changes; the branch starts from the last commit and does not include them.'] : [];
|
|
85
|
+
const run = async (stage, harness, prompt, { workdir = cwd, permissions, role = stage.role } = {}) => {
|
|
86
|
+
stage.status = 'running';
|
|
87
|
+
const result = await dispatchWithReceipt({ agent: harness.id, cwd: workdir, contextCwd: cwd, contextTask: task, prompt, permissions: permissions || stage.permissions, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role, complexityScore: complexity.score });
|
|
88
|
+
stage.status = result.ok ? 'completed' : 'failed';
|
|
89
|
+
stage.output = result.response || result.error || null;
|
|
90
|
+
stage.failureKind = result.ok ? undefined : result.failureKind;
|
|
91
|
+
appendThreadTurn(thread.id, { role: role.toLowerCase(), agent: harness.id, content: stage.output || '' });
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
145
94
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
output: null
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
role: 'Implementer',
|
|
159
|
-
harness: roles.implementer.id,
|
|
160
|
-
harnessName: roles.implementer.name,
|
|
161
|
-
modelFamily: roles.implementer.family,
|
|
162
|
-
permissions: { read: true, write: true },
|
|
163
|
-
status: executeLive ? 'pending' : 'planned',
|
|
164
|
-
action: 'Author implementation files and unit tests following Architectural blueprint.',
|
|
165
|
-
output: null
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
role: 'Verifier',
|
|
169
|
-
harness: roles.verifier.id,
|
|
170
|
-
harnessName: roles.verifier.name,
|
|
171
|
-
modelFamily: roles.verifier.family,
|
|
172
|
-
permissions: { read: true, write: false },
|
|
173
|
-
worktreeIsolated: true,
|
|
174
|
-
status: executeLive ? 'pending' : 'planned',
|
|
175
|
-
action: 'Execute deterministic test gates, audit diffs, and verify invariant compliance in isolated worktree.',
|
|
176
|
-
passed: executeLive ? null : true,
|
|
177
|
-
output: null
|
|
95
|
+
let ok = true, commit = null, implementerDir = null, gate = null;
|
|
96
|
+
const stage = role => stages.find(item => item.role === role);
|
|
97
|
+
try {
|
|
98
|
+
let digest = '';
|
|
99
|
+
if (withSubagents) {
|
|
100
|
+
const explorer = await run(stage('Explorer'), roles.explorer, `[ROLE: EXPLORER]\nDo not change any file. Find the files, symbols and interfaces relevant to this task:\n${task}`);
|
|
101
|
+
if (!explorer.ok) { ok = false; return finish(); }
|
|
102
|
+
digest = synthesizeOneWayValve(explorer.response, { maxChars: 2000 });
|
|
103
|
+
stage('Explorer').contextDigest = digest;
|
|
178
104
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
agent:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
stages[0].status = archRes.ok ? 'completed' : 'failed';
|
|
194
|
-
stages[0].output = archRes.response || archRes.error || null;
|
|
195
|
-
recordRunReceipt({
|
|
196
|
-
harness: roles.architect.id,
|
|
197
|
-
task: `[Architect] ${task}`,
|
|
198
|
-
complexityScore: complexity.score,
|
|
199
|
-
success: Boolean(archRes.ok),
|
|
200
|
-
durationMs: Date.now() - archStart
|
|
201
|
-
});
|
|
105
|
+
const architect = await run(stage('Architect'), roles.architect, `[ROLE: ARCHITECT]\nDo not change any file. Task:\n${task}\n\nExplorer digest:\n${digest || 'None.'}\n\nProduce a concise blueprint, the files to change, and how to verify the result.`);
|
|
106
|
+
if (!architect.ok) { ok = false; return finish(); }
|
|
107
|
+
|
|
108
|
+
implementerDir = createWorktree(cwd, { branch });
|
|
109
|
+
const implementer = await run(stage('Implementer'), roles.implementer, `[ROLE: IMPLEMENTER]\nWork only in this checkout. Implement the task and its tests. Do not commit; the harness commits your changes.\nTask:\n${task}\n\nBlueprint:\n${architect.response}`, { workdir: implementerDir });
|
|
110
|
+
if (!implementer.ok) { ok = false; return finish(); }
|
|
111
|
+
commit = commitAll(implementerDir, `meta-harness: ${task.slice(0, 60)}\n\nImplementer: ${roles.implementer.id}`);
|
|
112
|
+
if (!commit) {
|
|
113
|
+
Object.assign(stage('Implementer'), { status: 'failed', failureKind: 'agent', output: 'The Implementer reported success but changed no files.' });
|
|
114
|
+
ok = false; return finish();
|
|
115
|
+
}
|
|
116
|
+
stage('Implementer').commit = commit;
|
|
117
|
+
updateThread(thread.id, { branch, worktreePath: null });
|
|
202
118
|
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const implRes = await dispatchFn({
|
|
209
|
-
agent: roles.implementer.id,
|
|
210
|
-
cwd,
|
|
211
|
-
prompt: `[ROLE: IMPLEMENTER]\nFollow this Architect blueprint:\n${archRes.response}\n\nImplement the requested task:\n${task}`,
|
|
212
|
-
permissions: { read: true, write: true },
|
|
213
|
-
allowUnknownAuth: options.allowUnknownAuth === true
|
|
214
|
-
});
|
|
215
|
-
stages[1].status = implRes.ok ? 'completed' : 'failed';
|
|
216
|
-
stages[1].output = implRes.response || implRes.error || null;
|
|
217
|
-
recordRunReceipt({
|
|
218
|
-
harness: roles.implementer.id,
|
|
219
|
-
task: `[Implementer] ${task}`,
|
|
220
|
-
complexityScore: complexity.score,
|
|
221
|
-
success: Boolean(implRes.ok),
|
|
222
|
-
durationMs: Date.now() - implStart
|
|
223
|
-
});
|
|
119
|
+
if (options.gate) {
|
|
120
|
+
linkDependencies(cwd, implementerDir);
|
|
121
|
+
gate = runQualityGate(implementerDir, options.gateOptions);
|
|
122
|
+
if (!gate.passed) { ok = false; stage('Verifier').status = 'skipped'; stage('Verifier').output = `Quality gate did not pass: ${gate.summary}`; return finish(); }
|
|
123
|
+
}
|
|
224
124
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
allowUnknownAuth: options.allowUnknownAuth === true
|
|
239
|
-
});
|
|
240
|
-
stages[2].status = verRes.ok ? 'completed' : 'failed';
|
|
241
|
-
stages[2].passed = Boolean(verRes.ok);
|
|
242
|
-
stages[2].output = verRes.response || verRes.error || null;
|
|
243
|
-
stages[2].worktreePath = worktreeDir || null;
|
|
244
|
-
recordRunReceipt({
|
|
245
|
-
harness: roles.verifier.id,
|
|
246
|
-
task: `[Verifier] ${task}`,
|
|
247
|
-
complexityScore: complexity.score,
|
|
248
|
-
success: Boolean(verRes.ok),
|
|
249
|
-
durationMs: Date.now() - verifyStart
|
|
250
|
-
});
|
|
251
|
-
if (!verRes.ok) allSucceeded = false;
|
|
252
|
-
} finally {
|
|
253
|
-
removeVerifierWorktree(cwd, worktreeDir);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
125
|
+
const diff = diffBetween(cwd, baseCommit, commit);
|
|
126
|
+
const verifyDir = createWorktree(cwd, { ref: commit, prefix: 'meta-harness-verify-' });
|
|
127
|
+
try {
|
|
128
|
+
const verification = await run(stage('Verifier'), verifier, `[ROLE: INDEPENDENT VERIFIER]\nYou did not write this code. Do not change any file. Run the checks you can and audit commit ${commit} for this task:\n${task}\n\nBlueprint:\n${architect.response}\n\nDiff${diff.length > 30000 ? ' (first 30000 characters; read the rest from this checkout)' : ''}:\n${diff.slice(0, 30000)}\n\nEnd your reply with exactly one JSON object: {"verdict":"PASS"|"FAIL","findings":["..."]}`, { workdir: verifyDir });
|
|
129
|
+
const parsed = verification.ok ? parseVerification(verification.response) : { parsed: false, verdict: 'NOT_RUN', findings: [] };
|
|
130
|
+
Object.assign(stage('Verifier'), { commit, verdict: parsed.verdict, findings: parsed.findings, passed: parsed.verdict === 'PASS' });
|
|
131
|
+
if (parsed.verdict !== 'PASS') ok = false;
|
|
132
|
+
} finally { removeWorktree(cwd, verifyDir); }
|
|
133
|
+
|
|
134
|
+
if (options.autoReview) {
|
|
135
|
+
const review = await executeCodeReview({ cwd, author: roles.implementer.id, threadId: thread.id, task, target: `${baseCommit}...${commit}`, dispatchFn: options.dispatchFn, reviewer: options.reviewer });
|
|
136
|
+
stages.push({ role: 'Reviewer', harness: review.review?.reviewer || null, status: review.ok ? 'completed' : 'failed', verdict: review.review?.verdict || null, riskScore: review.review?.riskScore ?? null, output: review.review?.summary || review.error || null });
|
|
137
|
+
if (!review.ok || review.review?.verdict !== 'APPROVE') ok = false;
|
|
256
138
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
139
|
+
return finish();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// An infrastructure failure (git, a refused commit) must still close the thread and report.
|
|
142
|
+
ok = false;
|
|
143
|
+
const current = stages.find(item => ['running', 'planned'].includes(item.status));
|
|
144
|
+
if (current) Object.assign(current, { status: 'failed', failureKind: 'process', output: String(error.message) });
|
|
145
|
+
return finish();
|
|
146
|
+
} finally {
|
|
147
|
+
removeWorktree(cwd, implementerDir);
|
|
148
|
+
if (implementerDir && !commit) deleteBranch(cwd, branch);
|
|
261
149
|
}
|
|
262
150
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
stages,
|
|
270
|
-
durationMs,
|
|
271
|
-
heterogeneousFamiliesVerified: roles.heterogeneousFamilies
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
if (executeLive) {
|
|
275
|
-
await notifyAlert({
|
|
276
|
-
title: allSucceeded ? 'Meta Harness: Pipeline Completed' : 'Meta Harness: Pipeline Failed',
|
|
277
|
-
message: `Multi-agent pipeline ${allSucceeded ? 'succeeded' : 'failed'} for task: ${task.slice(0, 60)}`,
|
|
278
|
-
severity: allSucceeded ? 'info' : 'warning'
|
|
279
|
-
});
|
|
151
|
+
function finish() {
|
|
152
|
+
return (async () => {
|
|
153
|
+
updateThread(thread.id, { status: ok ? 'completed' : 'failed', ...(commit ? { branch } : {}) });
|
|
154
|
+
const alert = await notifyAlert({ title: ok ? 'Meta Harness: pipeline passed' : 'Meta Harness: pipeline failed', message: `${task.slice(0, 80)}${commit ? ` (branch ${branch})` : ''}`, severity: ok ? 'info' : 'warning' });
|
|
155
|
+
return { ...base, ok, threadId: thread.id, branch: commit ? branch : null, commit, baseCommit, gate, warnings, durationMs: Date.now() - started, alert };
|
|
156
|
+
})();
|
|
280
157
|
}
|
|
281
|
-
|
|
282
|
-
return result;
|
|
283
158
|
}
|
|
284
159
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const executeLive = options.execute === true;
|
|
160
|
+
async function runCouncil(task, poolInfo, options) {
|
|
161
|
+
// One seat per model family, strongest first, so the seats can actually disagree.
|
|
162
|
+
const families = new Set();
|
|
163
|
+
const seats = options.harnesses || [...poolInfo.ready].sort((a, b) => b.qualityScore - a.qualityScore).filter(h => !families.has(h.family) && families.add(h.family)).slice(0, 3).map(h => h.id);
|
|
164
|
+
if (options.execute !== true) return { ok: seats.length > 0, mode: 'council', task, executedLive: false, consensus: 'planned', excluded: poolInfo.excluded, votes: seats.map(harness => ({ harness, status: 'planned' })) };
|
|
165
|
+
if (!seats.length) throw new Error('No harness can take work right now');
|
|
292
166
|
const cwd = options.cwd || process.cwd();
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const dispatches = await Promise.all(councilHarnesses.map(async (h) => {
|
|
297
|
-
try {
|
|
298
|
-
const res = await dispatchFn({
|
|
299
|
-
agent: h,
|
|
300
|
-
cwd,
|
|
301
|
-
prompt: `[COUNCIL DELIBERATION]\nProvide your independent engineering assessment and recommendation for:\n${task}`,
|
|
302
|
-
permissions: { read: true, write: false },
|
|
303
|
-
allowUnknownAuth: options.allowUnknownAuth === true
|
|
304
|
-
});
|
|
305
|
-
return {
|
|
306
|
-
harness: h,
|
|
307
|
-
ok: Boolean(res.ok),
|
|
308
|
-
recommendation: res.ok ? 'completed' : 'failed',
|
|
309
|
-
response: res.response || res.error || null
|
|
310
|
-
};
|
|
311
|
-
} catch (err) {
|
|
312
|
-
return {
|
|
313
|
-
harness: h,
|
|
314
|
-
ok: false,
|
|
315
|
-
recommendation: 'error',
|
|
316
|
-
response: err.message
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
}));
|
|
320
|
-
|
|
321
|
-
const passedCount = dispatches.filter(d => d.ok).length;
|
|
322
|
-
return {
|
|
323
|
-
ok: passedCount > 0,
|
|
324
|
-
mode: 'council',
|
|
325
|
-
executedLive: true,
|
|
326
|
-
task,
|
|
327
|
-
council: councilHarnesses,
|
|
328
|
-
consensus: passedCount === councilHarnesses.length ? 'unanimous' : (passedCount > 0 ? 'majority' : 'failed'),
|
|
329
|
-
votes: dispatches
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const votes = councilHarnesses.map(h => ({
|
|
334
|
-
harness: h,
|
|
335
|
-
status: 'planned',
|
|
336
|
-
recommendation: 'ready',
|
|
337
|
-
reasoning: `Scheduled independent read-only deliberation on ${h}.`
|
|
167
|
+
const votes = await Promise.all(seats.map(async harness => {
|
|
168
|
+
const result = await dispatchWithReceipt({ agent: harness, cwd, contextTask: task, prompt: `[ROLE: COUNCIL MEMBER]\nDo not change any file. Assess this proposal, with its trade-offs and risks:\n${task}\n\nEnd your reply with exactly one JSON object: {"vote":"APPROVE"|"REJECT","reason":"..."}`, permissions: READ, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role: 'Council' });
|
|
169
|
+
return { harness, ok: result.ok === true, ...(result.ok ? parseVote(result.response) : { vote: 'ABSTAIN', reason: null, error: result.error }), feedback: result.response || null };
|
|
338
170
|
}));
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
executedLive: false,
|
|
344
|
-
task,
|
|
345
|
-
council: councilHarnesses,
|
|
346
|
-
consensus: 'planned',
|
|
347
|
-
votes
|
|
348
|
-
};
|
|
171
|
+
const count = vote => votes.filter(item => item.vote === vote).length;
|
|
172
|
+
const majority = votes.length / 2;
|
|
173
|
+
const consensus = count('APPROVE') > majority ? 'approved' : count('REJECT') > majority ? 'rejected' : 'no-consensus';
|
|
174
|
+
return { ok: votes.some(item => item.ok), mode: 'council', task, executedLive: true, consensus, excluded: poolInfo.excluded, votes };
|
|
349
175
|
}
|
|
350
176
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const contenders = options.harnesses
|
|
356
|
-
|| pool.slice(0, 2).map(h => h.id);
|
|
357
|
-
const executeLive = options.execute === true;
|
|
177
|
+
async function runRace(task, poolInfo, options) {
|
|
178
|
+
const seats = options.candidates || [...poolInfo.ready].sort((a, b) => b.qualityScore - a.qualityScore).slice(0, 2).map(h => h.id);
|
|
179
|
+
if (options.execute !== true) return { ok: seats.length > 1, mode: 'race', task, executedLive: false, excluded: poolInfo.excluded, candidates: seats.map(harness => ({ harness, status: 'planned' })) };
|
|
180
|
+
if (seats.length < 2) throw new Error('A race needs at least two harnesses that can take work');
|
|
358
181
|
const cwd = options.cwd || process.cwd();
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
task,
|
|
383
|
-
contenders,
|
|
384
|
-
winner: winners[0]?.harness || null,
|
|
385
|
-
results
|
|
386
|
-
};
|
|
387
|
-
}
|
|
182
|
+
if (!isGitRepo(cwd)) throw new Error('A race writes on separate branches, so it must run inside a git repository');
|
|
183
|
+
const id = `race-${Date.now().toString(36)}`;
|
|
184
|
+
const entries = await Promise.all(seats.map(async harness => {
|
|
185
|
+
const branch = `meta-harness/${id}-${harness}`;
|
|
186
|
+
const directory = createWorktree(cwd, { branch });
|
|
187
|
+
let keep = false;
|
|
188
|
+
try {
|
|
189
|
+
const result = await dispatchWithReceipt({ agent: harness, cwd: directory, contextCwd: cwd, contextTask: task, prompt: `[ROLE: IMPLEMENTER]\nWork only in this checkout. Implement the task and its tests. Do not commit.\nTask:\n${task}`, permissions: WRITE, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role: 'Race' });
|
|
190
|
+
const commit = result.ok ? commitAll(directory, `meta-harness race: ${task.slice(0, 60)}\n\nAgent: ${harness}`) : null;
|
|
191
|
+
if (commit && options.gate) linkDependencies(cwd, directory);
|
|
192
|
+
const gate = commit && options.gate ? runQualityGate(directory, options.gateOptions) : null;
|
|
193
|
+
keep = Boolean(commit) && (!gate || gate.passed);
|
|
194
|
+
return { harness, branch: keep ? branch : null, commit: keep ? commit : null, ok: keep, gate, durationMs: result.durationMs, error: result.ok ? (commit ? (keep ? undefined : 'The quality gate failed') : 'No files changed') : result.error };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return { harness, branch: null, commit: null, ok: false, gate: null, durationMs: null, error: String(error.message) };
|
|
197
|
+
} finally {
|
|
198
|
+
removeWorktree(cwd, directory);
|
|
199
|
+
if (!keep) deleteBranch(cwd, branch);
|
|
200
|
+
}
|
|
201
|
+
}));
|
|
202
|
+
const finishers = entries.filter(entry => entry.ok).sort((a, b) => a.durationMs - b.durationMs);
|
|
203
|
+
return { ok: finishers.length > 0, mode: 'race', task, executedLive: true, gateApplied: Boolean(options.gate), winner: finishers[0]?.harness || null, excluded: poolInfo.excluded, candidates: entries };
|
|
204
|
+
}
|
|
388
205
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
206
|
+
export async function coordinateTask(taskDescription, options = {}) {
|
|
207
|
+
const task = String(taskDescription || '').trim();
|
|
208
|
+
if (!task) throw new Error('Task description is required for coordination');
|
|
209
|
+
const mode = options.mode || 'pipeline';
|
|
210
|
+
if (!['pipeline', 'orchestrate', 'council', 'race'].includes(mode)) throw new Error(`Unsupported coordination mode: ${mode}`);
|
|
211
|
+
const poolInfo = await resolvePool(options);
|
|
212
|
+
if (mode === 'council') return runCouncil(task, poolInfo, options);
|
|
213
|
+
if (mode === 'race') return runRace(task, poolInfo, options);
|
|
214
|
+
return runPipeline(task, poolInfo, options);
|
|
398
215
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** One path for every agent run: contain it if needed, dispatch it, classify the outcome, write its receipt. */
|
|
2
|
+
import { executeOnNode } from './executor.mjs';
|
|
3
|
+
import { classifyFailure, READ_ONLY_ADAPTERS } from './worker.mjs';
|
|
4
|
+
import { recordRunReceipt } from './learner.mjs';
|
|
5
|
+
import { isGitRepo, createWorktree, removeWorktree } from './worktree.mjs';
|
|
6
|
+
|
|
7
|
+
export async function dispatchWithReceipt(request, { dispatchFn, node = request.node || 'local', role, complexityScore, escalatedFrom } = {}) {
|
|
8
|
+
const started = Date.now();
|
|
9
|
+
// A harness that cannot be told not to write does read-only work in a checkout that is thrown away.
|
|
10
|
+
const contain = node === 'local' && request.permissions?.write !== true && !READ_ONLY_ADAPTERS.includes(request.agent) && request.isolated !== true && isGitRepo(request.cwd);
|
|
11
|
+
let scratch = null, result;
|
|
12
|
+
try {
|
|
13
|
+
let effective = request;
|
|
14
|
+
if (contain) {
|
|
15
|
+
scratch = createWorktree(request.cwd, { prefix: 'meta-harness-readonly-' });
|
|
16
|
+
effective = { ...request, cwd: scratch, contextCwd: request.contextCwd || request.cwd, isolated: true };
|
|
17
|
+
}
|
|
18
|
+
result = dispatchFn ? await dispatchFn(effective) : await executeOnNode(node, { ...effective, operation: 'dispatch' });
|
|
19
|
+
if (contain) result = { ...result, readOnly: 'disposable-checkout' };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const message = String(error?.message || error);
|
|
22
|
+
result = { ok: false, error: message, agent: request.agent, ...(error?.code === 'NOT_READY' ? { failureKind: 'not-ready' } : classifyFailure(message)) };
|
|
23
|
+
} finally {
|
|
24
|
+
if (scratch) removeWorktree(request.cwd, scratch);
|
|
25
|
+
}
|
|
26
|
+
const durationMs = Date.now() - started;
|
|
27
|
+
await recordRunReceipt({
|
|
28
|
+
harness: request.agent, role, node, task: request.contextTask || request.prompt, complexityScore,
|
|
29
|
+
success: result.ok === true, durationMs, failureKind: result.ok ? null : result.failureKind || 'agent',
|
|
30
|
+
error: result.ok ? null : result.error, resetsAt: result.resetsAt, escalatedFrom,
|
|
31
|
+
});
|
|
32
|
+
return { ...result, durationMs };
|
|
33
|
+
}
|