total-recall-brain 3.44.0 → 3.45.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.
- package/bin/total-recall.mjs +6 -4
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +13 -8
- package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +2 -2
- package/frontend/src/pages/PluginsPage.tsx +1 -1
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/command.mjs +41 -2
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +6 -7
- package/src/cli/plugin/index.mjs +11 -7
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/startup.mjs +1 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +6 -2
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +31 -11
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compiler.mjs +20 -0
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +148 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-contracts/card.mjs +3 -0
- package/src/core/plugin-loader.mjs +12 -3
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-store.mjs +35 -23
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +26 -4
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +1 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +32 -4
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +65 -54
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +1 -0
- package/src/core/vector-field.mjs +134 -192
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/route-manifest.json +0 -56
- package/src/server/routes/brains.mjs +0 -18
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
|
|
5
|
+
// Protocol sources: code.claude.com/docs/en/{cli-reference,headless}.md and
|
|
6
|
+
// agent-sdk/{streaming-vs-single-mode,typescript}.md (read 2026-11-09).
|
|
7
|
+
const controls = ['reply', 'interrupt', 'answer', 'fork', 'goal', 'limits', 'schema', 'review'];
|
|
8
|
+
const ownedValues = new Set(['--input-format', '--output-format', '--session-id', '--name', '-n',
|
|
9
|
+
'--max-turns', '--max-budget-usd', '--json-schema', '--effort', '--fallback-model', '--mcp-config',
|
|
10
|
+
'--permission-prompt-tool']);
|
|
11
|
+
const ownedSwitches = new Set(['-p', '--print', '--verbose', '--replay-user-messages',
|
|
12
|
+
'--include-hook-events', '--forward-subagent-text']);
|
|
13
|
+
const textOf = content => typeof content === 'string' ? content : Array.isArray(content)
|
|
14
|
+
? content.filter(block => block.type === 'text').map(block => block.text).join('\n') : '';
|
|
15
|
+
const failureKind = (text, subtype) => /error_max_(turns|budget_usd)/.test(subtype || '') ? 'limit'
|
|
16
|
+
: /quota|rate.?limit|usage limit|credit|\b429\b/i.test(text) ? 'quota'
|
|
17
|
+
: /not (?:logged|signed) in|authenticat|invalid api key|\b401\b/i.test(text) ? 'auth' : 'agent';
|
|
18
|
+
|
|
19
|
+
/** Own one Claude stream-json process. Only Node built-ins are used on remote nodes. */
|
|
20
|
+
export function createClaudeDriver({ harness, binary, args = [], cwd, env, job,
|
|
21
|
+
options = {}, onEvent = () => {}, onFrame = () => {}, spawnFn = spawn } = {}) {
|
|
22
|
+
const capabilities = Object.fromEntries(controls.map(key => [key, 'unsupported']));
|
|
23
|
+
let sessionId = options.forkSession ? randomUUID() : options.sessionId || randomUUID();
|
|
24
|
+
let child, lines, started = false, closing = false, exited = false, initialized = false;
|
|
25
|
+
let settled = false, stderr = '', response = '', closePromise, continuation;
|
|
26
|
+
let usage = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
27
|
+
let resolveReady, rejectReady, resolveDone, resolveExit;
|
|
28
|
+
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
29
|
+
ready.catch(() => {}); // start() is the public reader of startup errors.
|
|
30
|
+
let done = new Promise(resolve => { resolveDone = resolve; });
|
|
31
|
+
const exit = new Promise(resolve => { resolveExit = resolve; });
|
|
32
|
+
const signal = name => {
|
|
33
|
+
try { if (process.platform !== 'win32' && child?.pid) process.kill(-child.pid, name); else child?.kill(name); return true; }
|
|
34
|
+
catch { return false; }
|
|
35
|
+
};
|
|
36
|
+
const pending = new Map();
|
|
37
|
+
const requests = new Map();
|
|
38
|
+
const emit = (kind, fields = {}) => { try { onEvent({ kind, text: '', ...fields }); } catch { /* observers do not own the process */ } };
|
|
39
|
+
const rejectPending = error => {
|
|
40
|
+
for (const entry of pending.values()) { clearTimeout(entry.timer); entry.reject(error); }
|
|
41
|
+
pending.clear();
|
|
42
|
+
requests.clear();
|
|
43
|
+
};
|
|
44
|
+
const complete = result => {
|
|
45
|
+
if (settled) return;
|
|
46
|
+
settled = true;
|
|
47
|
+
resolveDone({ ok: false, response, sessionId, usage: { ...usage }, structured: undefined,
|
|
48
|
+
error: null, failureKind: null, ...result });
|
|
49
|
+
};
|
|
50
|
+
const fail = error => {
|
|
51
|
+
const message = String(error?.message || error);
|
|
52
|
+
rejectReady(new Error(message));
|
|
53
|
+
rejectPending(new Error(message));
|
|
54
|
+
emit('error', { text: message });
|
|
55
|
+
complete({ error: message, failureKind: failureKind(message) });
|
|
56
|
+
};
|
|
57
|
+
const writable = () => {
|
|
58
|
+
if (!started || closing || exited || !child?.stdin?.writable) throw new Error('Claude session is not open.');
|
|
59
|
+
};
|
|
60
|
+
const write = frame => {
|
|
61
|
+
writable();
|
|
62
|
+
child.stdin.write(`${JSON.stringify(frame)}\n`, error => { if (error) fail(error); });
|
|
63
|
+
};
|
|
64
|
+
const userMessage = text => ({ type: 'user', uuid: randomUUID(), session_id: sessionId,
|
|
65
|
+
message: { role: 'user', content: text }, parent_tool_use_id: null });
|
|
66
|
+
const acknowledge = (id, value) => {
|
|
67
|
+
const entry = pending.get(id);
|
|
68
|
+
if (!entry) return;
|
|
69
|
+
pending.delete(id); clearTimeout(entry.timer);
|
|
70
|
+
emit('ack', { command: entry.command, requestId: id, via: 'native' });
|
|
71
|
+
entry.resolve(value);
|
|
72
|
+
};
|
|
73
|
+
const handle = frame => {
|
|
74
|
+
onFrame(frame);
|
|
75
|
+
if (!frame || typeof frame !== 'object') throw new Error('Invalid Claude stream message.');
|
|
76
|
+
const parent = frame.parent_tool_use_id;
|
|
77
|
+
const parentFields = parent ? { parent_tool_use_id: parent } : {};
|
|
78
|
+
if (frame.type === 'system' && frame.subtype === 'init') {
|
|
79
|
+
sessionId = frame.session_id || sessionId;
|
|
80
|
+
const reported = Array.isArray(frame.capabilities) ? frame.capabilities : [];
|
|
81
|
+
Object.assign(capabilities, { reply: 'native',
|
|
82
|
+
interrupt: reported.includes('interrupt_receipt_v1') || reported.includes('interrupt_cancel_queued_v1') ? 'native' : 'metaharness',
|
|
83
|
+
answer: 'metaharness', fork: 'native', goal: 'metaharness', limits: 'native', schema: 'native', review: 'metaharness' });
|
|
84
|
+
initialized = true;
|
|
85
|
+
emit('session', { text: `Claude session ${sessionId}`, sessionId, session: sessionId,
|
|
86
|
+
capabilities: { ...capabilities }, nativeCapabilities: [...reported], model: frame.model });
|
|
87
|
+
resolveReady();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (frame.type === 'control_response') {
|
|
91
|
+
const receipt = frame.response;
|
|
92
|
+
const entry = pending.get(receipt?.request_id);
|
|
93
|
+
if (entry?.command !== 'interrupt') return;
|
|
94
|
+
if (receipt.subtype === 'error') {
|
|
95
|
+
pending.delete(receipt.request_id); clearTimeout(entry.timer);
|
|
96
|
+
entry.reject(new Error(receipt.error || 'Claude rejected the interrupt.'));
|
|
97
|
+
} else acknowledge(receipt.request_id, { ack: true, via: 'native', ...receipt.response });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (frame.type === 'control_request') {
|
|
101
|
+
if (frame.request?.subtype === 'can_use_tool') {
|
|
102
|
+
requests.set(frame.request_id, frame.request);
|
|
103
|
+
emit('approval', { text: `Approval for ${frame.request.tool_name}`, requestId: frame.request_id, request: frame.request });
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (frame.type === 'system' && frame.subtype === 'api_retry') {
|
|
108
|
+
emit('error', { text: `API retry ${frame.attempt}/${frame.max_retries}: ${frame.error}`,
|
|
109
|
+
retry: true, attempt: frame.attempt, maxRetries: frame.max_retries,
|
|
110
|
+
retryDelayMs: frame.retry_delay_ms, errorStatus: frame.error_status });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (frame.type === 'system' && ['hook_started', 'hook_progress', 'hook_response'].includes(frame.subtype)) {
|
|
114
|
+
emit('hook', { text: frame.output || frame.hook_name || '', name: frame.hook_name,
|
|
115
|
+
phase: frame.subtype.slice(5), hookId: frame.hook_id, hookEvent: frame.hook_event,
|
|
116
|
+
outcome: frame.outcome, stdout: frame.stdout, stderr: frame.stderr, exitCode: frame.exit_code });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (frame.type === 'system' && frame.subtype === 'permission_denied') {
|
|
120
|
+
emit('error', { text: `Permission denied: ${frame.tool_name || frame.message || ''}` });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (frame.type === 'assistant' || frame.type === 'user') {
|
|
124
|
+
const content = frame.message?.content;
|
|
125
|
+
const blocks = Array.isArray(content) ? content : typeof content === 'string' ? [{ type: 'text', text: content }] : [];
|
|
126
|
+
if (frame.type === 'user' && !parent && !blocks.some(block => block.type === 'tool_result')) {
|
|
127
|
+
// Only a replay with our UUID acknowledges delivery. Tool results and subagent
|
|
128
|
+
// prompts must not acknowledge a user's pending message with the same text.
|
|
129
|
+
if (pending.get(frame.uuid)?.command === 'reply') acknowledge(frame.uuid, { ack: true, via: 'native' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const block of blocks) {
|
|
133
|
+
if (frame.type === 'assistant' && block.type === 'tool_use') {
|
|
134
|
+
emit('step-start', { text: `${block.name}: ${JSON.stringify(block.input || {})}`,
|
|
135
|
+
toolUseId: block.id, tool: block.name, input: block.input, ...parentFields });
|
|
136
|
+
} else if (block.type === 'tool_result') {
|
|
137
|
+
emit('step-end', { text: textOf(block.content) || (block.is_error ? 'Tool returned an error' : 'Tool finished'),
|
|
138
|
+
toolUseId: block.tool_use_id, isError: block.is_error === true, ...parentFields });
|
|
139
|
+
} else if (block.type === 'text' || (parent && block.type === 'thinking')) {
|
|
140
|
+
const text = block.text || block.thinking || '';
|
|
141
|
+
if (parent) emit('subagent', { text, ...parentFields });
|
|
142
|
+
else if (frame.type === 'assistant') {
|
|
143
|
+
response += text;
|
|
144
|
+
emit('message', { text });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (frame.error) emit('error', { text: String(frame.error), ...parentFields });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (frame.type === 'result') {
|
|
152
|
+
sessionId = frame.session_id || sessionId;
|
|
153
|
+
usage = { inputTokens: frame.usage?.input_tokens ?? 0,
|
|
154
|
+
outputTokens: frame.usage?.output_tokens ?? 0, costUsd: frame.total_cost_usd ?? 0 };
|
|
155
|
+
const ok = frame.subtype === 'success' && !frame.is_error;
|
|
156
|
+
const error = ok ? null : frame.errors?.join('\n') || frame.result || frame.subtype || 'Claude reported failure.';
|
|
157
|
+
const result = { ok, response: frame.result ?? response, sessionId, usage: { ...usage },
|
|
158
|
+
...(options.trackTurns ? { turns: frame.num_turns || 1 } : {}),
|
|
159
|
+
structured: frame.structured_output, error, failureKind: ok ? null : failureKind(error, frame.subtype) };
|
|
160
|
+
emit('usage', { ...usage });
|
|
161
|
+
emit('finish', { text: ok ? 'Finished' : error, ...result });
|
|
162
|
+
complete(result);
|
|
163
|
+
response = '';
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (frame.type === 'error') emit('error', { text: String(frame.error?.message || frame.message || frame.error) });
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function launchArgs() {
|
|
170
|
+
const result = [];
|
|
171
|
+
for (let i = 0; i < args.length; i++) {
|
|
172
|
+
const flag = args[i].split('=')[0];
|
|
173
|
+
if (ownedValues.has(flag)) { if (!args[i].includes('=')) i++; continue; }
|
|
174
|
+
if (ownedSwitches.has(flag)) continue;
|
|
175
|
+
// The MCP prompt tool must get approval requests under an operator/rules policy.
|
|
176
|
+
if (options.approvalPolicy && options.approvalPolicy !== 'auto') {
|
|
177
|
+
if (flag === '--dangerously-skip-permissions') continue;
|
|
178
|
+
if (flag === '--permission-mode') { if (!args[i].includes('=')) i++; continue; }
|
|
179
|
+
}
|
|
180
|
+
result.push(args[i]);
|
|
181
|
+
}
|
|
182
|
+
result.push('-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
|
|
183
|
+
'--replay-user-messages', '--include-hook-events', '--forward-subagent-text');
|
|
184
|
+
if (options.resumeSession) result.push('--resume', options.sessionId);
|
|
185
|
+
else result.push('--session-id', sessionId);
|
|
186
|
+
if (options.forkSession) {
|
|
187
|
+
if (options.atTurn !== undefined) throw new Error('Claude CLI cannot fork at a selected turn.');
|
|
188
|
+
result.push('--fork-session');
|
|
189
|
+
result.push('--session-id', sessionId);
|
|
190
|
+
}
|
|
191
|
+
const name = options.name ?? (typeof job === 'string' ? job : job?.id ?? job?.job);
|
|
192
|
+
for (const [flag, value] of [['--name', name], ['--max-turns', options.maxTurns],
|
|
193
|
+
['--max-budget-usd', options.maxBudgetUsd], ['--json-schema', options.resultSchema],
|
|
194
|
+
['--effort', options.effort], ['--fallback-model', options.fallbackModel], ['--mcp-config', options.mcpConfig]]) {
|
|
195
|
+
if (value !== undefined && value !== null) result.push(flag, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
196
|
+
}
|
|
197
|
+
result.push('--permission-prompt-tool', 'mcp__metaharness__approve');
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
capabilities, get done() { return done; },
|
|
203
|
+
async next(text, { resultSchema, maxTurns = options.maxTurns, maxBudgetUsd = options.maxBudgetUsd } = {}) {
|
|
204
|
+
if (continuation) return continuation.next(text, { resultSchema, maxTurns, maxBudgetUsd });
|
|
205
|
+
writable();
|
|
206
|
+
if (!settled) throw new Error('The current turn has not finished.');
|
|
207
|
+
// The schema is a process flag. Reopen the same native session when a
|
|
208
|
+
// goal-check turn needs a different schema from the requested result.
|
|
209
|
+
if (JSON.stringify(resultSchema) !== JSON.stringify(options.resultSchema) || maxTurns !== options.maxTurns || maxBudgetUsd !== options.maxBudgetUsd) {
|
|
210
|
+
await this.close();
|
|
211
|
+
continuation = createClaudeDriver({ harness, binary, args, cwd, env, job, spawnFn, onEvent, onFrame,
|
|
212
|
+
options: { ...options, sessionId, resumeSession: true, forkSession: false, resultSchema, maxTurns, maxBudgetUsd } });
|
|
213
|
+
await continuation.start(text);
|
|
214
|
+
done = continuation.done;
|
|
215
|
+
return done;
|
|
216
|
+
}
|
|
217
|
+
settled = false; response = '';
|
|
218
|
+
done = new Promise(resolve => { resolveDone = resolve; });
|
|
219
|
+
write(userMessage(text));
|
|
220
|
+
return done;
|
|
221
|
+
},
|
|
222
|
+
async start(brief) {
|
|
223
|
+
if (started) return ready;
|
|
224
|
+
if (closing) throw new Error('Claude session is closed.');
|
|
225
|
+
if (typeof brief !== 'string') throw new TypeError('The brief must be text.');
|
|
226
|
+
started = true;
|
|
227
|
+
try {
|
|
228
|
+
child = spawnFn(binary || harness?.binary || 'claude', launchArgs(), {
|
|
229
|
+
cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'],
|
|
230
|
+
});
|
|
231
|
+
child.once('error', error => { fail(error); if (!child.pid) { exited = true; resolveExit(); } });
|
|
232
|
+
child.stdin.on('error', error => { if (!closing && !exited) fail(error); });
|
|
233
|
+
child.stderr.setEncoding('utf8');
|
|
234
|
+
child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-65536); });
|
|
235
|
+
lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
236
|
+
lines.on('line', line => {
|
|
237
|
+
if (!line.trim()) return;
|
|
238
|
+
try { handle(JSON.parse(line)); }
|
|
239
|
+
catch (error) { fail(new Error(`Invalid Claude stream: ${error.message}`)); signal('SIGTERM'); }
|
|
240
|
+
});
|
|
241
|
+
child.once('close', (code, signal) => {
|
|
242
|
+
exited = true; lines.close(); resolveExit();
|
|
243
|
+
const error = new Error(stderr.trim() || `Claude closed without a result (${signal || code}).`);
|
|
244
|
+
rejectPending(error);
|
|
245
|
+
if (!initialized) rejectReady(error);
|
|
246
|
+
if (!settled) fail(error);
|
|
247
|
+
});
|
|
248
|
+
// The CLI reads its first user message before it emits system/init.
|
|
249
|
+
write(userMessage(brief));
|
|
250
|
+
} catch (error) { exited = true; resolveExit(); fail(error); }
|
|
251
|
+
return ready;
|
|
252
|
+
},
|
|
253
|
+
async send(text) {
|
|
254
|
+
if (continuation) return continuation.send(text);
|
|
255
|
+
if (typeof text !== 'string') throw new TypeError('The reply must be text.');
|
|
256
|
+
writable();
|
|
257
|
+
const frame = userMessage(text);
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
pending.set(frame.uuid, { command: 'reply', resolve, reject });
|
|
260
|
+
try { write(frame); } catch (error) { pending.delete(frame.uuid); reject(error); }
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
async interrupt() {
|
|
264
|
+
if (continuation) return continuation.interrupt();
|
|
265
|
+
writable();
|
|
266
|
+
if (!initialized) throw new Error('Claude session is not live.');
|
|
267
|
+
if (capabilities.interrupt !== 'native') {
|
|
268
|
+
if (!signal('SIGTERM')) throw new Error('Cannot interrupt Claude.');
|
|
269
|
+
emit('ack', { command: 'interrupt', via: 'metaharness' });
|
|
270
|
+
return { ack: true, via: 'metaharness' };
|
|
271
|
+
}
|
|
272
|
+
const requestId = randomUUID();
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
const timer = setTimeout(() => { pending.delete(requestId); reject(new Error('Claude interrupt receipt timed out.')); }, 30000);
|
|
275
|
+
pending.set(requestId, { command: 'interrupt', resolve, reject, timer });
|
|
276
|
+
try { write({ type: 'control_request', request_id: requestId, request: { subtype: 'interrupt' } }); }
|
|
277
|
+
catch (error) { clearTimeout(timer); pending.delete(requestId); reject(error); }
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
async answer(requestId, value) {
|
|
281
|
+
if (continuation) return continuation.answer(requestId, value);
|
|
282
|
+
if (!requests.has(requestId)) throw new Error('No native approval has this ID. Answer MCP requests through the Metaharness inbox.');
|
|
283
|
+
write({ type: 'control_response', response: { subtype: 'success', request_id: requestId, response: value } });
|
|
284
|
+
requests.delete(requestId);
|
|
285
|
+
emit('ack', { command: 'approve', requestId, via: 'native' });
|
|
286
|
+
return { ack: true, via: 'native' };
|
|
287
|
+
},
|
|
288
|
+
async fork({ atTurn } = {}) {
|
|
289
|
+
if (continuation) return continuation.fork({ atTurn });
|
|
290
|
+
if (!initialized) throw new Error('Claude session is not live.');
|
|
291
|
+
if (atTurn !== undefined) throw new Error('Claude CLI cannot fork at a selected turn.');
|
|
292
|
+
const fork = createClaudeDriver({ harness, binary, args,
|
|
293
|
+
cwd, env, job, options: { ...options, sessionId, resumeSession: true, forkSession: true }, spawnFn });
|
|
294
|
+
try {
|
|
295
|
+
// /rename is a documented local print-mode command. It materializes the fork
|
|
296
|
+
// without replaying the brief or asking a model to do more work.
|
|
297
|
+
await fork.start(`/rename ${options.name || 'Metaharness fork'}`);
|
|
298
|
+
const result = await fork.done;
|
|
299
|
+
if (!result.ok) throw new Error(result.error);
|
|
300
|
+
return { sessionId: result.sessionId };
|
|
301
|
+
} finally { await fork.close(); }
|
|
302
|
+
},
|
|
303
|
+
async close() {
|
|
304
|
+
if (continuation) return continuation.close();
|
|
305
|
+
if (closePromise) return closePromise;
|
|
306
|
+
closing = true;
|
|
307
|
+
rejectPending(new Error('Claude session is closed.'));
|
|
308
|
+
if (!started) { exited = true; rejectReady(new Error('Claude session is closed.')); complete({ error: 'Claude session was not started.', failureKind: 'agent' }); resolveExit(); }
|
|
309
|
+
closePromise = (async () => {
|
|
310
|
+
if (exited) return;
|
|
311
|
+
child.stdin.end();
|
|
312
|
+
const term = setTimeout(() => signal('SIGTERM'), 1000);
|
|
313
|
+
const kill = setTimeout(() => signal('SIGKILL'), 2000);
|
|
314
|
+
try { await exit; } finally { signal('SIGKILL'); clearTimeout(term); clearTimeout(kill); }
|
|
315
|
+
})();
|
|
316
|
+
return closePromise;
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|