total-recall-brain 3.41.6 → 3.42.0

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.
Files changed (86) hide show
  1. package/package.json +1 -1
  2. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +3118 -17
  3. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +7 -2
  4. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +84 -40
  5. package/scaffold/.agent/skills/total-recall/plugins/auth/src/exit-after-flush.mjs +22 -0
  6. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-api-rotation.mjs +61 -6
  7. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -0
  8. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +8 -0
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +20 -1
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +3 -0
  11. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +15 -7
  12. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  13. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  14. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  15. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  16. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  17. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +51 -7
  18. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +67 -25
  19. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +4 -4
  20. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +52 -6
  21. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +1 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +21 -8
  23. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/compat-harness.mjs +110 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +48 -22
  25. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +9 -4
  26. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +21 -9
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +9 -5
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +10 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +30 -3
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +6 -1
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +19 -3
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +222 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-worker.mjs +18 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +94 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +70 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +60 -19
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +32 -7
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +24 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +88 -125
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +110 -29
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +35 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +95 -5
  43. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +13 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +5 -5
  45. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +78 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/context-provider.mjs +113 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
  48. package/src/cli/agent.mjs +11 -6
  49. package/src/cli/compact.mjs +3 -3
  50. package/src/cli/context.mjs +52 -19
  51. package/src/cli/harness.mjs +65 -147
  52. package/src/cli/init.mjs +4 -2
  53. package/src/cli/preflight.mjs +56 -13
  54. package/src/core/agent-manager.mjs +23 -329
  55. package/src/core/context-compactor.mjs +2 -1
  56. package/src/core/context-compiler.mjs +16 -25
  57. package/src/core/context-providers.mjs +69 -0
  58. package/src/core/meta-harness.mjs +27 -749
  59. package/src/core/plugin-runner.mjs +12 -5
  60. package/src/core/plugin-runtime.mjs +4 -2
  61. package/src/core/runtime.mjs +30 -604
  62. package/src/core/task-context.mjs +122 -0
  63. package/src/server/routes/context.mjs +4 -1
  64. package/scaffold/.agent/skills/decision/SKILL.md +0 -20
  65. package/scaffold/.agent/skills/decision/references/integration.md +0 -25
  66. package/scaffold/.agent/skills/decision/references/use-cases.md +0 -20
  67. package/scaffold/.agent/skills/meta-harness/SKILL.md +0 -15
  68. package/scaffold/.agent/skills/meta-harness/references/optimized/index.md +0 -20
  69. package/scaffold/.agent/skills/meta-harness/references/optimized/manifest.json +0 -79
  70. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +0 -135
  71. package/scaffold/.agent/skills/meta-harness/references/optimized/section-01.md +0 -5
  72. package/scaffold/.agent/skills/meta-harness/references/optimized/section-02.md +0 -17
  73. package/scaffold/.agent/skills/meta-harness/references/optimized/section-03.md +0 -8
  74. package/scaffold/.agent/skills/meta-harness/references/optimized/section-04.md +0 -2
  75. package/scaffold/.agent/skills/meta-harness/references/optimized/section-05.md +0 -6
  76. package/scaffold/.agent/skills/meta-harness/references/optimized/section-06.md +0 -14
  77. package/scaffold/.agent/skills/meta-harness/references/optimized/section-07.md +0 -8
  78. package/scaffold/.agent/skills/meta-harness/references/optimized/section-08.md +0 -4
  79. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +0 -20
  80. package/scaffold/.agent/skills/meta-harness/references/optimized/section-10.md +0 -6
  81. package/scaffold/.agent/skills/meta-harness/references/optimized/section-11.md +0 -5
  82. package/scaffold/.agent/skills/meta-harness/references/optimized/section-12.md +0 -7
  83. package/scaffold/.agent/skills/meta-harness/references/optimized/section-13.md +0 -4
  84. package/scaffold/.agent/skills/meta-harness/references/optimized/section-14.md +0 -7
  85. package/scaffold/.agent/skills/meta-harness/references/optimized/section-15.md +0 -9
  86. package/scaffold/.agent/skills/meta-harness/references/optimized/section-16.md +0 -5
@@ -6,20 +6,26 @@ import { evaluateGateCascade } from '../capability/index.mjs';
6
6
  import { dispatchWithReceipt } from './dispatch.mjs';
7
7
  import { notifyAlert } from './alerts.mjs';
8
8
  import { recordRunReceipt } from './learner.mjs';
9
- import { resolvePool } from './pool.mjs';
9
+ import { resolvePool, localExecutionNode } from './pool.mjs';
10
10
  import { routeTask } from './router.mjs';
11
11
  import { runQualityGate } from './gate.mjs';
12
12
  import { isGitRepo, createWorktree, removeWorktree, commitAll, linkDependencies, deleteBranch } from './worktree.mjs';
13
+ import { forwardRemoteOperation } from './remote-operation.mjs';
13
14
 
14
15
  export async function executeRoutedTask(task, options = {}) {
16
+ const remote = await forwardRemoteOperation('route', task, { ...options, execute: true });
17
+ if (remote) return remote;
18
+ if (options.node) localExecutionNode(options);
15
19
  const cwd = options.cwd || process.cwd();
16
20
  if (!isGitRepo(cwd)) throw new Error('Routed execution writes on its own branch, so it must run inside a git repository');
17
21
  const route = options.route || await routeTask(task, options);
18
22
  if (!route.selected) return { ok: false, route, attempts: [], error: 'No harness can take this task right now.' };
23
+ const node = localExecutionNode(options, route.placement);
19
24
  const pool = options.pool || (await resolvePool(options)).ready.map(h => h.id);
20
25
  const maxAttempts = options.maxAttempts || 3;
21
26
  const id = `route-${Date.now().toString(36)}`;
22
27
  const attempts = [];
28
+ const alerts = [];
23
29
  const tried = new Set();
24
30
  let harness = route.selected, diagnostic = null, previous = null;
25
31
 
@@ -31,23 +37,30 @@ export async function executeRoutedTask(task, options = {}) {
31
37
  attempts.push(attempt);
32
38
  try {
33
39
  const prompt = `Work only in this checkout. Do not commit; the harness commits your changes.\nTask:\n${task}${diagnostic ? `\n\nA previous attempt by ${previous} failed. Diagnostic:\n${diagnostic}` : ''}`;
34
- const result = await dispatchWithReceipt({ agent: harness, cwd: directory, contextCwd: cwd, contextTask: task, prompt, permissions: { read: true, write: true }, allowUnknownAuth: true }, { dispatchFn: options.dispatchFn, role: 'Routed', complexityScore: route.complexity?.score, escalatedFrom: previous });
40
+ const result = await dispatchWithReceipt({ agent: harness, node, cwd: directory, contextCwd: cwd, contextTask: task, prompt, permissions: { read: true, write: true }, allowUnknownAuth: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role: 'Routed', complexityScore: route.complexity?.score, escalatedFrom: previous });
35
41
  attempt.ok = result.ok === true;
36
42
  attempt.durationMs = result.durationMs;
37
43
  attempt.output = result.response || null;
38
44
  if (result.ok) {
39
45
  attempt.commit = commitAll(directory, `meta-harness: ${task.slice(0, 60)}\n\nAgent: ${harness}`);
40
46
  if (attempt.commit) attempt.branch = branch;
41
- else { attempt.ok = false; attempt.error = 'The agent reported success but changed no files.'; }
47
+ else {
48
+ attempt.ok = false; attempt.error = 'The agent reported success but changed no files.';
49
+ await recordRunReceipt({ harness, role: 'Implementer', task, success: false, failureKind: 'no-change', error: attempt.error });
50
+ }
42
51
  } else {
43
52
  attempt.error = result.error;
44
53
  attempt.failureKind = result.failureKind;
45
54
  }
46
55
  if (attempt.ok && options.gate) {
47
56
  linkDependencies(cwd, directory);
48
- attempt.gate = (options.gateFn || runQualityGate)(directory, options.gateOptions);
57
+ attempt.gate = await (options.gateFn || runQualityGate)(directory, options.gateOptions);
49
58
  // A gate that could not run says nothing about this agent's work, so it does not escalate.
50
- if (['unavailable', 'unconfigured'].includes(attempt.gate.status)) return { ok: false, route, attempts, error: `The quality gate is ${attempt.gate.status}: ${attempt.gate.summary}` };
59
+ if (['unavailable', 'unconfigured'].includes(attempt.gate.status)) {
60
+ attempt.ok = false;
61
+ attempt.error = `The quality gate is ${attempt.gate.status}: ${attempt.gate.summary}`;
62
+ return { ok: false, route, attempts, error: attempt.error };
63
+ }
51
64
  await recordRunReceipt({ harness, role: 'Gate', task, success: attempt.gate.passed, failureKind: attempt.gate.passed ? null : 'gate', error: attempt.gate.passed ? null : attempt.gate.summary, gate: attempt.gate.status });
52
65
  attempt.ok = attempt.gate.passed;
53
66
  }
@@ -60,14 +73,14 @@ export async function executeRoutedTask(task, options = {}) {
60
73
  if (!attempt.ok) { deleteBranch(cwd, branch); attempt.branch = null; }
61
74
  }
62
75
 
63
- if (attempt.ok) return { ok: true, route, gateApplied: Boolean(options.gate), branch: attempt.branch, commit: attempt.commit, harness, attempts };
76
+ if (attempt.ok) return { ok: true, route, gateApplied: Boolean(options.gate), branch: attempt.branch, commit: attempt.commit, harness, attempts, alerts };
64
77
 
65
78
  const remaining = pool.filter(item => !tried.has(item));
66
79
  const next = remaining.length ? evaluateGateCascade(harness, { testsPassed: false }, remaining) : null;
67
80
  diagnostic = attempt.gate?.summary || attempt.error || 'The previous attempt failed.';
68
81
  previous = harness;
69
82
  harness = next?.recommendedHarness && !tried.has(next.recommendedHarness) ? next.recommendedHarness : null;
70
- if (harness) await notifyAlert({ title: 'Meta Harness: escalating', message: `${previous} failed on "${task.slice(0, 60)}"; escalating to ${harness}.`, severity: 'warning' });
83
+ if (harness) alerts.push(await notifyAlert({ title: 'Meta Harness: escalating', message: `${previous} failed on "${task.slice(0, 60)}"; escalating to ${harness}.`, severity: 'warning' }));
71
84
  }
72
- return { ok: false, route, gateApplied: Boolean(options.gate), attempts, error: 'No attempt passed.' };
85
+ return { ok: false, route, gateApplied: Boolean(options.gate), attempts, alerts, error: 'No attempt passed.' };
73
86
  }
@@ -0,0 +1,110 @@
1
+ /** Compatibility interfaces for native hosts. All execution uses the plugin's common boundary. */
2
+ import path from 'node:path';
3
+ import { getActiveAdapters, findBinary, inspectAgents, agentEnvironment } from './worker.mjs';
4
+ import { getInstalledVersion, getLatestVersion } from './tracker.mjs';
5
+
6
+ export const HARNESS_SPECS = Object.fromEntries(getActiveAdapters().map(adapter => [adapter.id, {
7
+ ...adapter, defaultFlags: adapter.args, execType: adapter.input === 'stdin' ? 'pipe_stdin' : 'flag_last',
8
+ enforcement: 'advisory', enforcementEvidence: 'Use the measured preflight adapter report for enforcement evidence',
9
+ }]));
10
+
11
+ export function normalizeToolSelection(tools) {
12
+ if (tools === undefined || tools === null) return null;
13
+ const values = (Array.isArray(tools) ? tools : String(tools).split(',')).map(value => String(value).trim()).filter(Boolean);
14
+ if (!values.length || (values.length === 1 && values[0] === 'none')) return '';
15
+ if (values.length === 1 && ['all', 'default'].includes(values[0])) return null;
16
+ return values.join(',');
17
+ }
18
+
19
+ export function validateHarnessOptions(id, options = {}) {
20
+ const adapter = getActiveAdapters().find(item => item.id === id);
21
+ if (!adapter) throw new Error(`Unknown harness ID "${id}"`);
22
+ if ((normalizeToolSelection(options.tools) !== null || options.settingSources !== undefined) && id !== 'claude') {
23
+ throw new Error(`Harness "${adapter.name}" does not support choosing its tool set or --setting-sources`);
24
+ }
25
+ if (options.settingSources !== undefined && (typeof options.settingSources !== 'string' || !/^(user|project|local)(,(user|project|local))*$/.test(options.settingSources))) throw new Error('Invalid --setting-sources');
26
+ if (options.allowUnknownAuth !== undefined && typeof options.allowUnknownAuth !== 'boolean') throw new Error('allowUnknownAuth must be an explicit boolean');
27
+ return adapter;
28
+ }
29
+
30
+ /** Legacy inspection helper only; execution flags are built by worker.mjs. */
31
+ export function buildHarnessArgs(spec, options = {}) {
32
+ const adapter = validateHarnessOptions(spec.id, options);
33
+ const args = [...adapter.args], extra = [];
34
+ const tools = normalizeToolSelection(options.tools);
35
+ if (tools !== null) extra.push('--tools', tools);
36
+ if (options.settingSources !== undefined) extra.push('--setting-sources', options.settingSources);
37
+ const promptFlag = args.at(-1) === '-p' ? args.pop() : null;
38
+ return [...args, ...extra, ...(promptFlag ? [promptFlag] : [])];
39
+ }
40
+
41
+ export const shellQuote = value => "'" + String(value).replaceAll("'", "'\\''") + "'";
42
+
43
+ export function detectHarnesses() {
44
+ return getActiveAdapters().map(adapter => {
45
+ const binaryPath = findBinary(adapter.binary);
46
+ return { id: adapter.id, name: adapter.name, available: Boolean(binaryPath), binaryPath, description: adapter.docs, execType: adapter.input };
47
+ });
48
+ }
49
+
50
+ export async function detectHarnessesWithAuth(options = {}) {
51
+ const result = await (options.inspect || inspectAgents)(options);
52
+ return result.agents.map(agent => ({ ...agent, available: agent.installed, authed: agent.authenticated === true && !agent.blockedBy, authRoute: agent.authenticated === 'unknown' ? 'unknown' : agent.authenticated ? 'measured-login' : 'needs-auth' }));
53
+ }
54
+
55
+ /** Kept for callers preparing environments; only an adapter's declared credentials may be resolved. */
56
+ export async function resolveHarnessEnv(spec, baseEnv = process.env, { readSecret } = {}) {
57
+ const adapter = validateHarnessOptions(spec.id);
58
+ const env = agentEnvironment(baseEnv);
59
+ if (!Object.keys(adapter.credentials || {}).length) return env;
60
+ if (!readSecret) {
61
+ const { runProcess } = await import('./worker.mjs');
62
+ const binary = findBinary(baseEnv.TOTAL_RECALL_BIN || 'total-recall', baseEnv);
63
+ if (!binary) throw new Error('Total Recall auth owner is unavailable');
64
+ readSecret = async key => {
65
+ const result = await runProcess(binary, ['auth', 'secret', 'get', key], { env: agentEnvironment(baseEnv) });
66
+ return result.exitCode === 0 ? result.stdout.trim() : null;
67
+ };
68
+ }
69
+ for (const [name, keys] of Object.entries(adapter.credentials)) {
70
+ for (const key of keys) { const value = await readSecret(key); if (value) { env[name] = value; break; } }
71
+ if (!env[name]) throw new Error(`Configure ${name} through auth before dispatch`);
72
+ }
73
+ return env;
74
+ }
75
+
76
+ export async function dispatchTask(id, prompt, options = {}, dependencies = {}) {
77
+ validateHarnessOptions(id, options);
78
+ const remote = options.node && options.node !== 'local';
79
+ if (remote && (typeof options.cwd !== 'string' || (!path.posix.isAbsolute(options.cwd) && !path.win32.isAbsolute(options.cwd)))) throw new Error('Choose an explicit absolute workspace on the remote node');
80
+ const { dispatchWithReceipt } = await import('./dispatch.mjs');
81
+ const result = await (dependencies.dispatch || dispatchWithReceipt)({
82
+ agent: id, cwd: remote ? options.cwd : path.resolve(options.cwd || process.cwd()), prompt,
83
+ node: options.node || 'local', permissions: { read: true, write: options.write === true },
84
+ allowUnknownAuth: options.allowUnknownAuth === true, timeoutMs: options.timeoutMs,
85
+ tools: options.tools, settingSources: options.settingSources, model: options.model,
86
+ }, { node: options.node || 'local', role: options.role || 'Dispatch' });
87
+ return { ...result, harnessId: id, harnessName: HARNESS_SPECS[id]?.name || id, success: result.ok === true };
88
+ }
89
+
90
+ export async function runCouncil(prompt, ids, options = {}) {
91
+ const { coordinateTask } = await import('./coordinator.mjs');
92
+ return coordinateTask(prompt, { ...options, mode: 'council', execute: true, ...(ids ? { availableHarnesses: ids } : {}) });
93
+ }
94
+
95
+ export const getInstalledHarnessVersion = id => getInstalledVersion(id) || 'unknown';
96
+ export const getLatestHarnessVersion = (id, installed) => getLatestVersion(id, installed);
97
+
98
+ export async function queryHarnessUsage(options = {}) {
99
+ const { inspectQuotas } = await import('./router.mjs');
100
+ const report = await (options.inspectQuotas || inspectQuotas)(options);
101
+ if (!report.ok) throw new Error(report.error || 'Harness usage is unavailable');
102
+ const remaining = value => typeof value === 'number' ? `${Math.max(0, 100 - value)}%` : '—';
103
+ return Object.entries(report.quotas || {}).filter(([id]) => !['system', 'metered'].includes(id)).map(([id, quota]) => ({
104
+ id, name: HARNESS_SPECS[id]?.name || id, available: quota.installed === true,
105
+ authed: quota.status === 'ready', authRoute: quota.status, status: quota.status,
106
+ version: quota.version || 'unknown', latestVersion: quota.latestVersion || null, isCurrent: quota.latestVersion ? quota.version === quota.latestVersion : null,
107
+ rolling5hRemainingPct: remaining(quota.rolling5hUsedPercent), weeklyRemainingPct: remaining(quota.weeklyUsedPercent),
108
+ quotaRemaining: quota.quotaNote || 'No usage source', enforcement: 'advisory', enforcementEvidence: null,
109
+ }));
110
+ }
@@ -4,19 +4,33 @@
4
4
  * branch in its own worktree, and the Verifier inspects that exact commit from a different model
5
5
  * family. Nothing merges into the operator's branch; the branch is the deliverable.
6
6
  */
7
- import { ADAPTER_CATALOG, classifyTaskComplexity, synthesizeOneWayValve } from '../capability/index.mjs';
7
+ import { recordRunReceipt } from './learner.mjs';
8
+ import { classifyTaskComplexity, synthesizeOneWayValve } from '../capability/index.mjs';
8
9
  import { notifyAlert } from './alerts.mjs';
9
10
  import { dispatchWithReceipt } from './dispatch.mjs';
10
- import { resolvePool } from './pool.mjs';
11
+ import { resolvePool, localExecutionNode } from './pool.mjs';
11
12
  import { createThread, appendThreadTurn, updateThread } from './threads.mjs';
12
13
  import { executeCodeReview } from './review.mjs';
13
14
  import { isGitRepo, isDirty, headCommit, createWorktree, removeWorktree, commitAll, diffBetween, linkDependencies, deleteBranch } from './worktree.mjs';
14
15
  import { lastJsonWith } from './json-reply.mjs';
15
16
  import { runQualityGate } from './gate.mjs';
17
+ import { forwardRemoteOperation } from './remote-operation.mjs';
18
+
19
+ const OPEN_THREADS = new Set();
20
+ const MAX_BLUEPRINT = 20000;
21
+ /** A stopped pipeline must not leave its thread reading as active. */
22
+ export function failOpenThreads(summary = 'The run was stopped before it finished.') {
23
+ for (const id of OPEN_THREADS) { try { updateThread(id, { status: 'failed', summary }); } catch { /* the thread store reports its own failure */ } }
24
+ OPEN_THREADS.clear();
25
+ }
16
26
 
17
27
  export function assignPipelineRoles(pool = [], options = {}) {
18
28
  const sorted = [...pool].sort((a, b) => b.qualityScore - a.qualityScore);
19
- const findById = id => sorted.find(h => h.id === id) || (ADAPTER_CATALOG[id] ? { ...ADAPTER_CATALOG[id], id } : { id, name: id, family: 'custom' });
29
+ const findById = id => {
30
+ const harness = sorted.find(h => h.id === id);
31
+ if (!harness) throw new Error(`Selected harness ${id} is not ready for this task`);
32
+ return harness;
33
+ };
20
34
  // The session's own harness leads only when it can take work; TR_ACTIVE_HARNESS is advisory.
21
35
  const active = process.env.TR_ACTIVE_HARNESS;
22
36
  const orchestrator = options.orchestrator ? findById(options.orchestrator) : sorted.find(h => h.id === active) || sorted[0] || null;
@@ -78,13 +92,15 @@ async function runPipeline(task, poolInfo, options) {
78
92
 
79
93
  const verifier = roles.verifier || roles.implementer;
80
94
  const complexity = classifyTaskComplexity(task);
81
- const thread = createThread({ title: task.slice(0, 80), task, platform: roles.orchestrator.id, model: roles.orchestrator.name, status: 'active' });
95
+ // Validate the repository before creating lifecycle state: an unborn repository has no usable base.
82
96
  const baseCommit = headCommit(cwd);
83
- const branch = options.branch || `meta-harness/${thread.id}`;
84
97
  const warnings = isDirty(cwd) ? ['The working tree has uncommitted changes; the branch starts from the last commit and does not include them.'] : [];
98
+ const thread = createThread({ title: task.slice(0, 80), task, platform: roles.orchestrator.id, model: roles.orchestrator.name, status: 'active' });
99
+ OPEN_THREADS.add(thread.id);
100
+ const branch = options.branch || `meta-harness/${thread.id}`;
85
101
  const run = async (stage, harness, prompt, { workdir = cwd, permissions, role = stage.role } = {}) => {
86
102
  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 });
103
+ const result = await dispatchWithReceipt({ agent: harness.id, node: 'local', cwd: workdir, contextCwd: cwd, contextTask: task, prompt, permissions: permissions || stage.permissions, allowUnknownAuth: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role, complexityScore: complexity.score });
88
104
  stage.status = result.ok ? 'completed' : 'failed';
89
105
  stage.output = result.response || result.error || null;
90
106
  stage.failureKind = result.ok ? undefined : result.failureKind;
@@ -104,13 +120,15 @@ async function runPipeline(task, poolInfo, options) {
104
120
  }
105
121
  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
122
  if (!architect.ok) { ok = false; return finish(); }
123
+ const blueprint = architect.response.length > MAX_BLUEPRINT ? `${architect.response.slice(0, MAX_BLUEPRINT)}\n[Blueprint shortened to ${MAX_BLUEPRINT} characters.]` : architect.response;
107
124
 
108
125
  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 });
126
+ 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${blueprint}`, { workdir: implementerDir });
110
127
  if (!implementer.ok) { ok = false; return finish(); }
111
128
  commit = commitAll(implementerDir, `meta-harness: ${task.slice(0, 60)}\n\nImplementer: ${roles.implementer.id}`);
112
129
  if (!commit) {
113
130
  Object.assign(stage('Implementer'), { status: 'failed', failureKind: 'agent', output: 'The Implementer reported success but changed no files.' });
131
+ await recordRunReceipt({ harness: roles.implementer.id, role: 'Implementer', task, success: false, failureKind: 'no-change', error: 'The Implementer reported success but changed no files.' });
114
132
  ok = false; return finish();
115
133
  }
116
134
  stage('Implementer').commit = commit;
@@ -118,21 +136,21 @@ async function runPipeline(task, poolInfo, options) {
118
136
 
119
137
  if (options.gate) {
120
138
  linkDependencies(cwd, implementerDir);
121
- gate = runQualityGate(implementerDir, options.gateOptions);
139
+ gate = await (options.gateFn || runQualityGate)(implementerDir, options.gateOptions);
122
140
  if (!gate.passed) { ok = false; stage('Verifier').status = 'skipped'; stage('Verifier').output = `Quality gate did not pass: ${gate.summary}`; return finish(); }
123
141
  }
124
142
 
125
143
  const diff = diffBetween(cwd, baseCommit, commit);
126
144
  const verifyDir = createWorktree(cwd, { ref: commit, prefix: 'meta-harness-verify-' });
127
145
  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 });
146
+ 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${blueprint}\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
147
  const parsed = verification.ok ? parseVerification(verification.response) : { parsed: false, verdict: 'NOT_RUN', findings: [] };
130
148
  Object.assign(stage('Verifier'), { commit, verdict: parsed.verdict, findings: parsed.findings, passed: parsed.verdict === 'PASS' });
131
- if (parsed.verdict !== 'PASS') ok = false;
149
+ if (parsed.verdict !== 'PASS') { ok = false; return finish(); }
132
150
  } finally { removeWorktree(cwd, verifyDir); }
133
151
 
134
152
  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 });
153
+ const review = await executeCodeReview({ cwd, author: roles.implementer.id, threadId: thread.id, task, target: `${baseCommit}...${commit}`, dispatchFn: options.dispatchFn, reviewer: options.reviewer, allowUnknownAuth: options.allowUnknownAuth === true, availableHarnesses: options.availableHarnesses });
136
154
  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
155
  if (!review.ok || review.review?.verdict !== 'APPROVE') ok = false;
138
156
  }
@@ -145,14 +163,16 @@ async function runPipeline(task, poolInfo, options) {
145
163
  return finish();
146
164
  } finally {
147
165
  removeWorktree(cwd, implementerDir);
148
- if (implementerDir && !commit) deleteBranch(cwd, branch);
166
+ if (implementerDir && (!commit || !ok)) deleteBranch(cwd, branch);
149
167
  }
150
168
 
151
169
  function finish() {
152
170
  return (async () => {
153
- updateThread(thread.id, { status: ok ? 'completed' : 'failed', ...(commit ? { branch } : {}) });
171
+ for (const item of stages) if (item.status === 'planned') item.status = 'skipped';
172
+ OPEN_THREADS.delete(thread.id);
173
+ updateThread(thread.id, { status: ok ? 'completed' : 'failed', branch: ok && commit ? branch : null, commit, baseCommit });
154
174
  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 };
175
+ return { ...base, ok, threadId: thread.id, branch: ok && commit ? branch : null, commit, baseCommit, gate, warnings, durationMs: Date.now() - started, alert };
156
176
  })();
157
177
  }
158
178
  }
@@ -165,7 +185,7 @@ async function runCouncil(task, poolInfo, options) {
165
185
  if (!seats.length) throw new Error('No harness can take work right now');
166
186
  const cwd = options.cwd || process.cwd();
167
187
  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' });
188
+ const result = await dispatchWithReceipt({ agent: harness, node: 'local', 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: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role: 'Council' });
169
189
  return { harness, ok: result.ok === true, ...(result.ok ? parseVote(result.response) : { vote: 'ABSTAIN', reason: null, error: result.error }), feedback: result.response || null };
170
190
  }));
171
191
  const count = vote => votes.filter(item => item.vote === vote).length;
@@ -178,20 +198,23 @@ async function runRace(task, poolInfo, options) {
178
198
  const seats = options.candidates || [...poolInfo.ready].sort((a, b) => b.qualityScore - a.qualityScore).slice(0, 2).map(h => h.id);
179
199
  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
200
  if (seats.length < 2) throw new Error('A race needs at least two harnesses that can take work');
201
+ if (!options.gate) return { ok: false, mode: 'race', task, executedLive: false, verification: 'unverified', gateApplied: false, error: 'A race requires a deterministic quality gate before any candidate can win.' };
181
202
  const cwd = options.cwd || process.cwd();
182
203
  if (!isGitRepo(cwd)) throw new Error('A race writes on separate branches, so it must run inside a git repository');
183
204
  const id = `race-${Date.now().toString(36)}`;
184
205
  const entries = await Promise.all(seats.map(async harness => {
185
206
  const branch = `meta-harness/${id}-${harness}`;
186
- const directory = createWorktree(cwd, { branch });
207
+ let directory;
187
208
  let keep = false;
188
209
  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' });
210
+ const started = Date.now();
211
+ directory = createWorktree(cwd, { branch });
212
+ const result = await dispatchWithReceipt({ agent: harness, node: 'local', 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: options.allowUnknownAuth === true }, { dispatchFn: options.dispatchFn, role: 'Race' });
190
213
  const commit = result.ok ? commitAll(directory, `meta-harness race: ${task.slice(0, 60)}\n\nAgent: ${harness}`) : null;
191
214
  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 };
215
+ const gate = commit ? await (options.gateFn || runQualityGate)(directory, options.gateOptions) : null;
216
+ keep = Boolean(commit) && gate?.passed === true;
217
+ return { harness, branch: keep ? branch : null, commit: keep ? commit : null, ok: keep, gate, completedAt: Date.now(), durationMs: Date.now() - started, error: result.ok ? (commit ? (keep ? undefined : 'The quality gate failed') : 'No files changed') : result.error };
195
218
  } catch (error) {
196
219
  return { harness, branch: null, commit: null, ok: false, gate: null, durationMs: null, error: String(error.message) };
197
220
  } finally {
@@ -199,15 +222,18 @@ async function runRace(task, poolInfo, options) {
199
222
  if (!keep) deleteBranch(cwd, branch);
200
223
  }
201
224
  }));
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 };
225
+ const finishers = entries.filter(entry => entry.ok).sort((a, b) => a.completedAt - b.completedAt);
226
+ return { ok: finishers.length > 0, mode: 'race', task, executedLive: true, gateApplied: true, verification: finishers.length ? 'passed' : 'failed', winner: finishers[0]?.harness || null, excluded: poolInfo.excluded, candidates: entries };
204
227
  }
205
228
 
206
229
  export async function coordinateTask(taskDescription, options = {}) {
207
230
  const task = String(taskDescription || '').trim();
208
231
  if (!task) throw new Error('Task description is required for coordination');
232
+ const remote = await forwardRemoteOperation('coordinate', task, options);
233
+ if (remote) return remote;
209
234
  const mode = options.mode || 'pipeline';
210
235
  if (!['pipeline', 'orchestrate', 'council', 'race'].includes(mode)) throw new Error(`Unsupported coordination mode: ${mode}`);
236
+ localExecutionNode(options);
211
237
  const poolInfo = await resolvePool(options);
212
238
  if (mode === 'council') return runCouncil(task, poolInfo, options);
213
239
  if (mode === 'race') return runRace(task, poolInfo, options);
@@ -4,7 +4,7 @@ import { classifyFailure, READ_ONLY_ADAPTERS } from './worker.mjs';
4
4
  import { recordRunReceipt } from './learner.mjs';
5
5
  import { isGitRepo, createWorktree, removeWorktree } from './worktree.mjs';
6
6
 
7
- export async function dispatchWithReceipt(request, { dispatchFn, node = request.node || 'local', role, complexityScore, escalatedFrom } = {}) {
7
+ export async function dispatchWithReceipt(request, { dispatchFn, node = request.node || 'local', role, complexityScore, escalatedFrom, record = recordRunReceipt } = {}) {
8
8
  const started = Date.now();
9
9
  // A harness that cannot be told not to write does read-only work in a checkout that is thrown away.
10
10
  const contain = node === 'local' && request.permissions?.write !== true && !READ_ONLY_ADAPTERS.includes(request.agent) && request.isolated !== true && isGitRepo(request.cwd);
@@ -24,10 +24,15 @@ export async function dispatchWithReceipt(request, { dispatchFn, node = request.
24
24
  if (scratch) removeWorktree(request.cwd, scratch);
25
25
  }
26
26
  const durationMs = Date.now() - started;
27
- await recordRunReceipt({
27
+ // A finished run keeps its result: the worktree and any cost are real even when bookkeeping fails.
28
+ let receiptError;
29
+ try {
30
+ await record({
28
31
  harness: request.agent, role, node, task: request.contextTask || request.prompt, complexityScore,
32
+ model: result.model ?? null, exitCode: result.exitCode ?? null, finishedReason: result.finishedReason ?? null,
29
33
  success: result.ok === true, durationMs, failureKind: result.ok ? null : result.failureKind || 'agent',
30
34
  error: result.ok ? null : result.error, resetsAt: result.resetsAt, escalatedFrom,
31
- });
32
- return { ...result, durationMs };
35
+ });
36
+ } catch (error) { receiptError = String(error?.message || error); }
37
+ return { ...result, durationMs, ...(receiptError ? { receiptError } : {}) };
33
38
  }
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
+ import { loadCustomAdapters } from './worker.mjs';
6
7
 
7
8
  export const DOC_SOURCES = [
8
9
  { id: 'claude', index: 'https://code.claude.com/docs/llms.txt', prefix: 'https://code.claude.com/docs/', catalog:true },
@@ -19,12 +20,22 @@ export const DOC_SOURCES = [
19
20
  { id: 'cline', index: 'https://docs.cline.bot/llms.txt', prefix: 'https://docs.cline.bot/', catalog:true },
20
21
  { id: 'openclaw', index: 'https://docs.openclaw.ai/llms.txt', prefix: 'https://docs.openclaw.ai/', catalog:true },
21
22
  { id: 'aider', index: 'https://aider.chat/docs/', prefix: 'https://aider.chat/docs/' },
22
- { id: 'goose', index: 'https://block.github.io/goose/docs/quickstart', prefix: 'https://block.github.io/goose/docs/' },
23
+ { id: 'goose', index: 'https://goose-docs.ai/llms.txt', prefix: 'https://goose-docs.ai/', catalog:true },
23
24
  { id: 'openhands', index: 'https://docs.openhands.dev/llms.txt', prefix: 'https://docs.openhands.dev/', catalog:true },
24
25
  { id: 'replit', index: 'https://docs.replit.com/llms.txt', prefix: 'https://docs.replit.com/', catalog:true },
25
26
  { id: 'lovable', index: 'https://docs.lovable.dev/llms.txt', prefix: 'https://docs.lovable.dev/', catalog:true },
26
27
  { id: 'ollama', index: 'https://docs.ollama.com/llms.txt', prefix: 'https://docs.ollama.com/', catalog:true },
27
28
  ];
29
+ function sourcesFor(agent) {
30
+ const sources = new Map(DOC_SOURCES.map(source => [source.id, source]));
31
+ for (const adapter of loadCustomAdapters()) if (adapter.docs) {
32
+ const url = new URL(adapter.docs);
33
+ sources.set(adapter.id, { id: adapter.id, index: url.href, prefix: new URL('.', url).href, origin: url.hostname });
34
+ }
35
+ const selected = agent ? [sources.get(agent)].filter(Boolean) : [...sources.values()];
36
+ if (!selected.length) throw new Error('Unknown documentation adapter or missing declared documentation URL');
37
+ return selected;
38
+ }
28
39
  const hash = value => crypto.createHash('sha256').update(value).digest('hex');
29
40
  const TTL = 24 * 60 * 60 * 1000;
30
41
  function pageFile(directory,id,file){if(!/^[a-f0-9]{64}-[a-f0-9]{64}\.txt$/.test(file || ''))throw new Error('Invalid cached documentation filename');return path.join(directory,id,file);}
@@ -39,11 +50,11 @@ export function documentationDirectory() {
39
50
  }
40
51
  function metadata(directory, id) { try { return JSON.parse(fs.readFileSync(path.join(directory,id,'index.json'),'utf8')); } catch { return null; } }
41
52
  export function documentationStatus({ directory = documentationDirectory(), agent, now = Date.now() } = {}) {
42
- const sources = agent ? DOC_SOURCES.filter(source => source.id === agent) : DOC_SOURCES;
53
+ const sources = sourcesFor(agent);
43
54
  if (!sources.length) throw new Error('Unknown documentation adapter');
44
55
  const agents = sources.map(source => {
45
56
  const saved = metadata(directory,source.id);
46
- const complete = saved?.complete === true && saved.pages?.length > 0 && saved.pages.every(page => {
57
+ const complete = saved?.complete === true && saved.pages?.some(page => page.url === source.index) && saved.pages.every(page => {
47
58
  try { return hash(fs.readFileSync(pageFile(directory,source.id,page.file))) === page.sha256; } catch { return false; }
48
59
  });
49
60
  const age=now-Date.parse(saved?.checkedAt);
@@ -52,9 +63,10 @@ export function documentationStatus({ directory = documentationDirectory(), agen
52
63
  });
53
64
  return { ok:agents.every(row=>row.fresh), directory, agents };
54
65
  }
55
- async function download(url, fetchImpl) {
56
- const allowed = new Set(['code.claude.com','claude.com','platform.claude.com','docs.anthropic.com','anthropic.com','developers.openai.com','openai.com','help.openai.com','learn.chatgpt.com','chatgpt.com','antigravity.google','cursor.com','docs.x.ai','dev.meta.ai','docs.devin.ai','docs.windsurf.com','windsurf.com','docs.cline.bot','docs.openclaw.ai','aider.chat','docs.replit.com','docs.lovable.dev','docs.ollama.com','block.github.io','docs.openhands.dev','raw.githubusercontent.com','api.github.com']);
66
+ async function download(url, fetchImpl, declaredOrigin) {
67
+ const allowed = new Set(['code.claude.com','claude.com','platform.claude.com','docs.anthropic.com','anthropic.com','developers.openai.com','openai.com','help.openai.com','learn.chatgpt.com','chatgpt.com','antigravity.google','cursor.com','docs.x.ai','dev.meta.ai','docs.devin.ai','docs.windsurf.com','windsurf.com','docs.cline.bot','docs.openclaw.ai','aider.chat','docs.replit.com','docs.lovable.dev','docs.ollama.com','goose-docs.ai','block.github.io','docs.openhands.dev','raw.githubusercontent.com','api.github.com']);
57
68
  let target = new URL(url);
69
+ if (declaredOrigin) allowed.add(declaredOrigin);
58
70
  for(let redirects=0;redirects<5;redirects++) {
59
71
  if(target.protocol!=='https:' || target.username || target.password || !allowed.has(target.hostname)) throw new Error(`Unapproved official documentation origin: ${target.hostname}`);
60
72
  const response=await fetchImpl(target,{redirect:'manual',signal:AbortSignal.timeout(20000),headers:{'User-Agent':'Total-Recall-Meta-Harness-Docs','Accept':'*/*'}});
@@ -80,7 +92,7 @@ function links(text,base,prefix) {
80
92
  .filter(url=>url.startsWith(prefix) && !/%3[CE]|[<>"'`\s]/i.test(url) && !/\.(png|jpg|jpeg|svg|gif|css|js|woff2?|ico|pdf|json|ya?ml|xml|zip|gz|tgz|lock)$/i.test(url));
81
93
  }
82
94
  export async function refreshDocumentation({ directory = documentationDirectory(), agent, fetchImpl = fetch, now = () => new Date(), maxPages = 1500 } = {}) {
83
- const sources=agent?DOC_SOURCES.filter(source=>source.id===agent):DOC_SOURCES;
95
+ const sources=sourcesFor(agent);
84
96
  if(!sources.length)throw new Error('Unknown documentation adapter');
85
97
  const reports=[];
86
98
  for(const source of sources) {
@@ -98,7 +110,7 @@ export async function refreshDocumentation({ directory = documentationDirectory(
98
110
  const batch=[];
99
111
  while(queue.length && batch.length<4){const url=queue.shift();if(!seen.has(url)){seen.add(url);batch.push(url);}}
100
112
  if(seen.size>maxPages)throw new Error('Official documentation exceeds configured page budget');
101
- const downloads=await Promise.allSettled(batch.map(async url=>({url,text:await download(url,fetchImpl)})));
113
+ const downloads=await Promise.allSettled(batch.map(async url=>({url,text:await download(url,fetchImpl,source.origin)})));
102
114
  for(let index=0;index<downloads.length;index++) {
103
115
  const result=downloads[index];
104
116
  if(result.status==='rejected'){
@@ -152,7 +164,7 @@ export async function maintainDocumentation(options={}) {
152
164
  }
153
165
 
154
166
  export function readDocumentation({agent,url,directory=documentationDirectory()}) {
155
- if(!DOC_SOURCES.some(source=>source.id===agent))throw new Error('Unknown documentation adapter');
167
+ sourcesFor(agent);
156
168
  const index=metadata(directory,agent);
157
169
  if(!url)return {agent,pages:index?.pages?.map(page=>({url:page.url,sha256:page.sha256,bytes:page.bytes})) || []};
158
170
  const page=index?.pages?.find(page=>page.url===url);
@@ -164,7 +176,7 @@ export function readDocumentation({agent,url,directory=documentationDirectory()}
164
176
 
165
177
  export function searchDocumentation({agent,query,directory=documentationDirectory()}) {
166
178
  if(typeof query!=='string' || !query.trim())throw new Error('Documentation query required');
167
- const sources=agent?DOC_SOURCES.filter(source=>source.id===agent):DOC_SOURCES;
179
+ const sources=sourcesFor(agent);
168
180
  const matches=[],phrase=query.trim().toLowerCase(),terms=phrase.split(/\s+/).filter(Boolean);
169
181
  for(const source of sources)for(const page of metadata(directory,source.id)?.pages || []) {
170
182
  const doc=readDocumentation({agent:source.id,url:page.url,directory});const text=doc.content.toLowerCase();
@@ -1,7 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
- import { runWorker, runProcess, getActiveAdapters, notReady } from './worker.mjs';
4
+ import { runWorker, runProcess, getActiveAdapters, notReady, agentEnvironment } from './worker.mjs';
5
+ import { dispatchWithReceipt } from './dispatch.mjs';
5
6
 
6
7
  async function hostModule(name) {
7
8
  const selected = process.env.TR_PACKAGE_ROOT;
@@ -49,7 +50,7 @@ export async function executeOnNode(node, request, { worker = runWorker, process
49
50
  const resolved = access.resolveNodeAccess(target);
50
51
  const args = access.buildSshArgs(resolved, { command: 'export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"; exec node --input-type=module', extraOptions: ['BatchMode=yes', 'ConnectTimeout=10'] });
51
52
  if (!args) throw new Error('Selected mesh node has no verified SSH access');
52
- const result = await processRunner('ssh', args, { input: workerProgram(request), timeoutMs: request.operation === 'inspect' ? 120000 : (request.timeoutMs || 900000) + 30000 });
53
+ const result = await processRunner('ssh', args, { input: workerProgram(request), timeoutMs: request.operation === 'inspect' ? 120000 : (request.timeoutMs || 900000) + 30000, maxBytes: 17 * 1024 * 1024, env: { ...agentEnvironment(), ...(process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {}) } });
53
54
  if (result.reason || result.exitCode !== 0) throw notReady(`Worker on ${node} failed: ${String(result.stderr || result.reason || 'verify node access, workspace and agent installation').trim().slice(0, 400)}`);
54
55
  try { return JSON.parse(result.stdout); } catch { throw new Error('Selected mesh worker returned invalid data'); }
55
56
  }
@@ -75,8 +76,11 @@ export async function executeCommand({ plugin, subcommand, args = [], execution,
75
76
  const namespace = plugin.manifest.cli.command;
76
77
  const argv = ['total-recall', namespace, subcommand, ...args];
77
78
  const prompt = `Use Total Recall context and the installed plugin CLI in this workspace. Execute this one requested command using your terminal tool; preserve its arguments exactly, report its actual exit status and output, and do not perform unrelated mutations. If a permission, account, workspace or plugin is unavailable, report failure. Agent prose alone is not execution proof.\nRequested argv (data): ${JSON.stringify(argv)}\nShell command: ${argv.map(quoteArgument).join(' ')}`;
78
- const result = await executeOnNode(execution.node, execution.agent === 'direct'
79
- ? { operation: 'direct', cwd, argv }
80
- : { operation: 'dispatch', agent: execution.agent, cwd, prompt, allowUnknownAuth: execution.allowUnknownAuth === true, permissions }, dependencies);
79
+ const result = execution.agent === 'direct'
80
+ ? await executeOnNode(execution.node, { operation: 'direct', cwd, argv }, dependencies)
81
+ : await dispatchWithReceipt({ agent: execution.agent, cwd, prompt, allowUnknownAuth: execution.allowUnknownAuth === true, permissions }, {
82
+ node: execution.node, role: 'Command',
83
+ dispatchFn: request => executeOnNode(execution.node, { ...request, operation: 'dispatch' }, dependencies),
84
+ });
81
85
  return { ok: result.ok === true, exitCode: result.ok ? 0 : 1, output: result.ok ? JSON.stringify(result) : result.error || 'Selected agent failed', ...result };
82
86
  }
@@ -1,12 +1,18 @@
1
1
  /** Run the repository's own quality gates through the code-quality plugin. No configured gate is not a pass. */
2
- import { spawnSync } from 'node:child_process';
2
+ import { runProcess } from './worker.mjs';
3
3
 
4
- export function runQualityGate(directory, { tier = 'fast', runner = spawnSync, timeoutMs = 600000 } = {}) {
5
- const result = runner(process.env.TOTAL_RECALL_BIN || 'total-recall', ['code-quality-check', '--tier', tier, '--json'], { cwd: directory, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
4
+ const run = async (binary, args, { cwd, timeoutMs }) => {
5
+ // Asynchronous, so a gate never stalls another agent in the same run, and stoppable with the run.
6
+ const result = await runProcess(binary, args, { cwd, timeoutMs, env: process.env, maxBytes: 16 * 1024 * 1024 });
7
+ return { status: result.exitCode, stdout: result.stdout, stderr: result.stderr, error: result.reason ? new Error(result.reason) : undefined };
8
+ };
9
+
10
+ export async function runQualityGate(directory, { tier = 'fast', runner = run, timeoutMs = 600000 } = {}) {
11
+ const result = await runner(process.env.TOTAL_RECALL_BIN || 'total-recall', ['code-quality-check', '--tier', tier, '--json'], { cwd: directory, timeoutMs });
6
12
  const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim();
7
13
  if (result.error || result.status === null) return { status: 'unavailable', passed: false, tier, summary: `The gate did not run: ${result.error?.message || 'terminated'}` };
8
14
  let report = null;
9
15
  try { report = JSON.parse(result.stdout); } catch { /* a text report */ }
10
- if (/No code-quality state/.test(report?.error || output)) return { status: 'unconfigured', passed: false, tier, summary: String(report?.error || output).slice(0, 500) };
16
+ if (/No code-quality state|No checks at tier/.test(output)) return { status: 'unconfigured', passed: false, tier, summary: String(report?.error || output).slice(0, 500) };
11
17
  return { status: result.status === 0 ? 'passed' : 'failed', passed: result.status === 0, tier, summary: output.slice(-2000), report };
12
18
  }