total-recall-brain 3.43.3 → 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 +16 -9
- 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 +3 -3
- package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
- package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
- package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
- package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
- package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
- package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
- 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 +17 -16
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
- 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 +160 -29
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
- 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 +101 -16
- 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 +54 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
- 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 +64 -15
- 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 +249 -0
- 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 +6 -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/pool.mjs +16 -8
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
- 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 +28 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
- 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 +110 -0
- 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 +597 -70
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/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/connect.mjs +2 -29
- package/src/cli/context.mjs +1 -0
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/ingest-okf.mjs +4 -4
- 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/release.mjs +4 -1
- 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 +10 -3
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/automatic-brain-updates.mjs +11 -6
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +42 -12
- 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-compactor.mjs +7 -2
- package/src/core/context-compiler.mjs +26 -1
- package/src/core/context-policy.mjs +7 -3
- package/src/core/daemon-control.mjs +22 -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/global-brain-updates.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/mesh-brain-updates.mjs +2 -1
- 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-restart.mjs +128 -7
- 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 +33 -6
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +68 -57
- package/src/core/task-context.mjs +9 -8
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +5 -0
- package/src/core/vault-files.mjs +8 -2
- package/src/core/vault.mjs +1 -1
- package/src/core/vector-field.mjs +134 -192
- package/src/server/index.mjs +3 -1
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Metaharness Smart Router
|
|
3
3
|
* Manages honest quota/readiness tracking, benchmark matrices, RouteLLM complexity routing,
|
|
4
4
|
* and cost-vs-frontier harness selection.
|
|
5
5
|
*
|
|
@@ -21,6 +21,7 @@ import { getModelCatalog, joinHarnessModels } from './model-catalog.mjs';
|
|
|
21
21
|
import { readReceipts, activeQuotaWalls, activeCredentialBlocks } from './learner.mjs';
|
|
22
22
|
import { resolvePool, exclusionReason, isLocalExecutionNode } from './pool.mjs';
|
|
23
23
|
import { readCodexRateLimits, measuredWalls } from './telemetry.mjs';
|
|
24
|
+
import { claudeProofCurrent, INCLUDED_AGENTS, readIncludedPolicy } from './included-usage.mjs';
|
|
24
25
|
import { derivedDir } from './paths.mjs';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -97,7 +98,7 @@ async function cachedMeteredQuota(options) {
|
|
|
97
98
|
async function readMeteredQuota(options) {
|
|
98
99
|
let meteredQuota = { billingModel: 'metered', creditRemainingUSD: null, usageUSD: null, usageDailyUSD: null, usageWeeklyUSD: null, usagePercent: null, quotaNote: 'No OpenRouter key in the auth store', status: 'unconfigured' };
|
|
99
100
|
try {
|
|
100
|
-
const key = options.openRouterKey !== undefined ? options.openRouterKey :
|
|
101
|
+
const key = options.openRouterKey !== undefined ? options.openRouterKey : runHostCli(['auth', 'secret', 'get', 'OPENROUTER_API_KEY']);
|
|
101
102
|
if (key) {
|
|
102
103
|
const response = await (options.fetchImpl || fetch)('https://openrouter.ai/api/v1/auth/key', { headers: { Authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(6000) });
|
|
103
104
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
@@ -139,6 +140,7 @@ async function readMeteredQuota(options) {
|
|
|
139
140
|
* Never fabricates or hardcodes synthetic subscription percentages.
|
|
140
141
|
*/
|
|
141
142
|
export async function inspectQuotas(options = {}) {
|
|
143
|
+
const policy = options.policy || await readIncludedPolicy();
|
|
142
144
|
const quotas = {};
|
|
143
145
|
const remote = options.node && options.node !== 'local';
|
|
144
146
|
const fleet = (options.fleet || fleetTelemetry)();
|
|
@@ -162,27 +164,37 @@ export async function inspectQuotas(options = {}) {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
// Harness state comes from the selected machine. Quota walls are this machine's receipts.
|
|
165
|
-
const
|
|
167
|
+
const claudePlan = policy.enabled ? policy.claudePlan?.organization : undefined;
|
|
168
|
+
const inspected = remote ? await (options.inspectNodeFn || inspectNode)(options.node) :
|
|
169
|
+
await (options.inspect || inspectAgents)({ env: process.env, cwd: options.cwd, claudePlan });
|
|
166
170
|
const codexLimits = remote ? null : readCodexRateLimits();
|
|
167
|
-
const
|
|
171
|
+
const receipts = remote ? [] : (await readReceipts()).filter(run => !policy.enabled || run.billingMode === 'included');
|
|
172
|
+
const walls = remote ? {} : { ...activeQuotaWalls(receipts), ...(!policy.enabled ? measuredWalls(codexLimits) : {}) };
|
|
168
173
|
const metered = METERED_HARNESSES;
|
|
169
174
|
|
|
170
|
-
const blocks =
|
|
171
|
-
for (const
|
|
175
|
+
const blocks = activeCredentialBlocks(receipts);
|
|
176
|
+
for (const installed of inspected.agents || []) {
|
|
177
|
+
const agent = policy.enabled && !remote && installed.id === 'claude' && !claudePlan ?
|
|
178
|
+
{ ...installed, authenticated: claudeProofCurrent(policy) } : installed;
|
|
172
179
|
const wall = walls[agent.id];
|
|
173
180
|
const block = blocks[agent.id];
|
|
174
|
-
const
|
|
181
|
+
const policyBlocked = policy.enabled && !INCLUDED_AGENTS.includes(agent.id);
|
|
182
|
+
const explicitOpenRouter = !remote && Boolean(policy.openRouter?.[agent.id]);
|
|
183
|
+
const reason = policyBlocked ? (explicitOpenRouter ? 'available through explicit OpenRouter dispatch' : 'outside the included usage route') : exclusionReason(agent, wall, block);
|
|
175
184
|
let status = 'ready';
|
|
176
185
|
if (!agent.installed) status = 'not_installed';
|
|
186
|
+
else if (policyBlocked) status = 'blocked';
|
|
177
187
|
else if (agent.authenticated === false || block) status = 'needs_auth';
|
|
178
188
|
else if (agent.blockedBy) status = 'blocked';
|
|
179
189
|
else if (wall) status = 'exhausted';
|
|
180
190
|
else if (agent.authenticated === 'unknown') status = 'unverified';
|
|
181
191
|
const quota = {
|
|
182
192
|
installed: Boolean(agent.installed), version: agent.version || null,
|
|
183
|
-
billingModel:
|
|
193
|
+
billingModel: policy.enabled && !remote && !claudePlan && agent.id === 'claude' ? 'included-api-credits' :
|
|
194
|
+
agent.id === 'ollama' ? 'local' : metered.has(agent.id) ? 'metered' : 'subscription',
|
|
184
195
|
rolling5hUsedPercent: null, weeklyUsedPercent: null, usagePercent: null,
|
|
185
196
|
quotaSource: 'none', quotaNote: reason || 'No usage figure is available from this harness', status,
|
|
197
|
+
...(explicitOpenRouter ? { explicitOpenRouter: true } : {}),
|
|
186
198
|
...(wall ? { resetsAt: wall.until, wallMessage: wall.message } : {}),
|
|
187
199
|
};
|
|
188
200
|
if (agent.id === 'codex' && codexLimits) {
|
|
@@ -196,7 +208,7 @@ export async function inspectQuotas(options = {}) {
|
|
|
196
208
|
quotas[agent.id] = quota;
|
|
197
209
|
}
|
|
198
210
|
|
|
199
|
-
const meteredQuota = options.skipMetered
|
|
211
|
+
const meteredQuota = options.skipMetered || policy.enabled
|
|
200
212
|
? { billingModel: 'metered', status: 'not-read', quotaNote: 'Not read: no metered harness is in this route pool' }
|
|
201
213
|
: await cachedMeteredQuota(options);
|
|
202
214
|
quotas.metered = meteredQuota;
|
|
@@ -241,9 +253,10 @@ export function placeLocalWork(fleet = {}) {
|
|
|
241
253
|
}
|
|
242
254
|
|
|
243
255
|
export async function routeTask(taskDescription, options = {}) {
|
|
256
|
+
const policy = await readIncludedPolicy();
|
|
244
257
|
let complexity = classifyTaskComplexity(taskDescription, options);
|
|
245
258
|
let classifier = { source: 'heuristic' };
|
|
246
|
-
if (options.jev) {
|
|
259
|
+
if (options.jev && !policy.enabled) {
|
|
247
260
|
try {
|
|
248
261
|
const decided = classifyWithDecision(taskDescription, options.decisionRun);
|
|
249
262
|
complexity = { ...complexity, tier: decided.tier, isFrontierRecommended: decided.tier === 'frontier', isLocalRecommended: decided.tier === 'local' };
|
|
@@ -260,17 +273,19 @@ export async function routeTask(taskDescription, options = {}) {
|
|
|
260
273
|
// fleet telemetry places local work, and the balance matters only to a metered harness.
|
|
261
274
|
const shared = scoped.inspect ? null : inspectAgents({ env: process.env });
|
|
262
275
|
const lean = shared ? { ...scoped, inspect: () => shared } : scoped;
|
|
263
|
-
const [pool, receipts] = await Promise.all([resolvePool(lean), readReceipts()]);
|
|
276
|
+
const [pool, receipts] = await Promise.all([resolvePool({ ...lean, policy }), readReceipts()]);
|
|
264
277
|
const quotaReport = await inspectQuotas({
|
|
265
278
|
...lean,
|
|
279
|
+
policy,
|
|
266
280
|
meteredTtlMs: METERED_TTL_MS,
|
|
267
|
-
skipMetered: lean.openRouterKey === undefined && !pool.ready.some(h => METERED_HARNESSES.has(h.id)),
|
|
281
|
+
skipMetered: policy.enabled || (lean.openRouterKey === undefined && !pool.ready.some(h => METERED_HARNESSES.has(h.id))),
|
|
268
282
|
...(remote || lean.fleet ? {} : { fleet: complexity.tier === 'local' ? cachedFleet : () => ({ ok: false, error: 'Not read: this route places no local work', nodes: [] }) }),
|
|
269
283
|
});
|
|
270
284
|
const quotas = quotaReport.quotas;
|
|
271
285
|
const routing = selectOptimalHarness(taskDescription, pool.ready.map(h => h.id), quotas, { ...options, tierOverride: complexity.tier, empiricalScorecard: computeEmpiricalScorecard(receipts) });
|
|
272
286
|
const selectedQuota = quotas[routing.selected] || {};
|
|
273
287
|
const warnings = [];
|
|
288
|
+
if (policy.enabled && options.jev) warnings.push('Included usage policy uses the local task classifier.');
|
|
274
289
|
if (typeof selectedQuota.usagePercent === 'number' && selectedQuota.usagePercent > 85) warnings.push(`${routing.selected} is at ${selectedQuota.usagePercent}% measured usage.`);
|
|
275
290
|
if (selectedQuota.status === 'unverified') warnings.push(`${routing.selected} has no login probe; only a completed run proves it works.`);
|
|
276
291
|
if (selectedQuota.billingModel === 'metered') warnings.push(`${routing.selected} bills per token.`);
|
|
@@ -278,6 +293,7 @@ export async function routeTask(taskDescription, options = {}) {
|
|
|
278
293
|
return {
|
|
279
294
|
ok: Boolean(routing.selected), task: taskDescription, classifier, complexity,
|
|
280
295
|
selected: routing.selected, tier: complexity.tier, candidates: routing.candidates, excluded: pool.excluded,
|
|
296
|
+
...(policy.enabled && !routing.selected ? { deferred: pool.excluded.find(row => row.failureKind === 'quota') || null } : {}),
|
|
281
297
|
placement: complexity.tier === 'local' ? placeLocalWork(quotaReport.fleet) : null,
|
|
282
298
|
scoring: 'Adapter quality and cost scores are configured priors, adjusted by pass rates measured on this machine.',
|
|
283
299
|
quotaSnapshot: { selectedHarnessStatus: selectedQuota.status || null, selectedHarnessUsage: selectedQuota.usagePercent ?? null, freeMemGB: quotas.system.freeMemGB ?? null },
|
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Runtime observations and explicit maintenance. Agent work belongs to dispatch. */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { getActiveAdapters, findBinary, runProcess, agentEnvironment } from './worker.mjs';
|
|
6
6
|
|
|
7
|
+
const DSH_OBSERVATIONS = ['status', 'sessions', 'tools', 'models', 'model', 'plugins', 'help'];
|
|
8
|
+
const MAINTENANCE = new Map([
|
|
9
|
+
['claude', [['auth', 'status']]],
|
|
10
|
+
['codex', [['login', 'status']]],
|
|
11
|
+
['cursor', [['status']]],
|
|
12
|
+
['dsh', [...DSH_OBSERVATIONS.map(operation => [operation]), ['model', '--cache']]],
|
|
13
|
+
['hermes', [['status'], ['status', '--full'], ['gateway', 'status'], ['skills', 'list'], ['plugins', 'list'], ['auth']]],
|
|
14
|
+
['ollama', [['list'], ['ps']]],
|
|
15
|
+
['openclaw', [['status'], ['health'], ['gateway', 'status'], ['plugins', 'list']]],
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export function validateRuntimeOperation(agent, args = []) {
|
|
19
|
+
if (typeof agent !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(agent)) throw new Error('Invalid agent adapter');
|
|
20
|
+
if (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid native runtime arguments');
|
|
21
|
+
if (args.some(arg => /(?:api[-_]?key|password|secret|access[-_]?token|refresh[-_]?token)/i.test(arg))) throw new Error('Credential values and mutations belong to the auth owner, never runtime argv');
|
|
22
|
+
const command = args.length ? args : [agent === 'dsh' ? 'status' : '--help'];
|
|
23
|
+
const supported = [['--help'], ['-h'], ['--version'], ...(MAINTENANCE.get(agent) || [])];
|
|
24
|
+
if (!supported.some(tuple => tuple.length === command.length && tuple.every((word, index) => word === command[index]))) {
|
|
25
|
+
throw new Error('Runtime accepts only supported maintenance commands. Use meta-harness dispatch or route --execute for agent work, or the owning plugin for service changes.');
|
|
26
|
+
}
|
|
27
|
+
return command;
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
export async function manageRuntime(agent, args = [], { env = process.env, probe = runProcess, cwd = process.cwd() } = {}) {
|
|
31
|
+
args = validateRuntimeOperation(agent, args);
|
|
8
32
|
const adapter = getActiveAdapters().find(item => item.id === agent);
|
|
9
33
|
if (!adapter) throw new Error('Unknown agent adapter');
|
|
10
|
-
if (
|
|
11
|
-
if (args.some(arg => /(?:api[-_]?key|password|secret|access[-_]?token|refresh[-_]?token)/i.test(arg))) throw new Error('Credential values and mutations belong to the auth owner, never runtime argv');
|
|
12
|
-
if (agent === 'dsh' && ['status','sessions','tools','models','model','plugins','help','--help','-h'].includes(args[0] || 'status')) {
|
|
34
|
+
if (agent === 'dsh' && DSH_OBSERVATIONS.includes(args[0])) {
|
|
13
35
|
const { run } = await import('./adapters/dsh.mjs');
|
|
14
36
|
const lines = [], errors = [], log = console.log, error = console.error;
|
|
15
37
|
const previous = process.exitCode;
|
|
@@ -31,6 +53,6 @@ export async function manageRuntime(agent, args = [], { env = process.env, probe
|
|
|
31
53
|
}
|
|
32
54
|
const binary = findBinary(adapter.binary, env);
|
|
33
55
|
if (!binary) throw new Error(`${adapter.name} is not installed on the selected node`);
|
|
34
|
-
const result = await probe(binary, args
|
|
35
|
-
return { ok:result.exitCode===0 && !result.reason, agent, exitCode:result.exitCode, output:result.stdout, error:result.reason || (result.exitCode ? result.stderr || 'Runtime command failed' : undefined) };
|
|
56
|
+
const result = await probe(binary, args, { cwd, timeoutMs:60000, env:agentEnvironment(env) });
|
|
57
|
+
return { ok:result.exitCode===0 && !result.reason, agent, exitCode:result.exitCode, output:result.stdout || (result.exitCode === 0 ? result.stderr : ''), error:result.reason || (result.exitCode ? result.stderr || 'Runtime command failed' : undefined) };
|
|
36
58
|
}
|
|
@@ -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
|
+
}
|