total-recall-brain 3.43.2 → 3.44.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-server-release.mjs +4 -0
- package/docs/reference/cli-reference.md +14 -0
- package/package.json +5 -4
- 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 +14 -13
- 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 +18 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +39 -6
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +14 -13
- 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 +14 -13
- 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 +14 -13
- 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-sentinel/package-lock.json +14 -13
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +14 -13
- 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 +38 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +38 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +14 -13
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +85 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +15 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +38 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +42 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +40 -13
- 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/learner.mjs +3 -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 +1 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +5 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +27 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
- 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/worker.mjs +322 -40
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +14 -13
- 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/security/package-lock.json +14 -13
- 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/src/cli/connect.mjs +2 -29
- package/src/cli/context.mjs +1 -0
- package/src/cli/ingest-okf.mjs +4 -4
- package/src/cli/release.mjs +27 -2
- package/src/cli/startup.mjs +1 -0
- package/src/cli/update.mjs +14 -3
- package/src/core/automatic-brain-updates.mjs +13 -3
- package/src/core/autopull-install.mjs +14 -0
- package/src/core/brain-updates.mjs +12 -2
- package/src/core/context-compactor.mjs +7 -2
- package/src/core/context-compiler.mjs +6 -1
- package/src/core/context-policy.mjs +7 -3
- package/src/core/daemon-control.mjs +22 -0
- package/src/core/global-brain-updates.mjs +3 -3
- package/src/core/mesh-brain-updates.mjs +20 -5
- package/src/core/running-server.mjs +76 -0
- package/src/core/server-release.mjs +487 -0
- package/src/core/server-restart.mjs +128 -7
- package/src/core/source-brain-updates.mjs +35 -6
- package/src/core/ssss-kernel-bridge.mjs +1 -2
- package/src/core/startup-health.mjs +13 -2
- package/src/core/surface.mjs +3 -3
- package/src/core/task-context.mjs +9 -8
- package/src/core/validated-write.mjs +4 -0
- package/src/core/vault-files.mjs +8 -2
- package/src/core/vault.mjs +1 -1
- package/src/server/index.mjs +17 -1
|
@@ -3,6 +3,10 @@ import path from 'node:path';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { runWorker, runProcess, getActiveAdapters, notReady, agentEnvironment } from './worker.mjs';
|
|
5
5
|
import { dispatchWithReceipt } from './dispatch.mjs';
|
|
6
|
+
import { commandPermissions, destinationDispatchRequest } from './dispatch-request.mjs';
|
|
7
|
+
import { readIncludedPolicy } from './included-usage.mjs';
|
|
8
|
+
|
|
9
|
+
export { DEFAULT_PERMISSIONS, commandPermissions } from './dispatch-request.mjs';
|
|
6
10
|
|
|
7
11
|
async function hostModule(name) {
|
|
8
12
|
const selected = process.env.TR_PACKAGE_ROOT;
|
|
@@ -12,13 +16,6 @@ async function hostModule(name) {
|
|
|
12
16
|
return import(pathToFileURL(path.join(root, 'src/core', `${name}.mjs`)).href);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export const DEFAULT_PERMISSIONS = Object.freeze({ read: true, write: false, send: false, destructive: false });
|
|
16
|
-
export function commandPermissions(value = DEFAULT_PERMISSIONS) {
|
|
17
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Command permissions must be boolean values');
|
|
18
|
-
for (const key of Object.keys(value)) if (!Object.hasOwn(DEFAULT_PERMISSIONS, key) || typeof value[key] !== 'boolean') throw new Error(`Invalid permission ${key}`);
|
|
19
|
-
return { ...DEFAULT_PERMISSIONS, ...value };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
19
|
export async function listTargets() {
|
|
23
20
|
try {
|
|
24
21
|
const mesh = await hostModule('mesh');
|
|
@@ -27,10 +24,11 @@ export async function listTargets() {
|
|
|
27
24
|
nodes: nodes.length > 0 ? nodes : [{ id: 'local', name: 'Local Host', online: true, self: true, accessConfigured: true }],
|
|
28
25
|
adapters: getActiveAdapters().map(({ id, name, docs, custom }) => ({ id, name, docs, custom: Boolean(custom) }))
|
|
29
26
|
};
|
|
30
|
-
} catch {
|
|
27
|
+
} catch (error) {
|
|
31
28
|
return {
|
|
32
29
|
nodes: [{ id: 'local', name: 'Local Host', online: true, self: true, accessConfigured: true }],
|
|
33
|
-
adapters: getActiveAdapters().map(({ id, name, docs, custom }) => ({ id, name, docs, custom: Boolean(custom) }))
|
|
30
|
+
adapters: getActiveAdapters().map(({ id, name, docs, custom }) => ({ id, name, docs, custom: Boolean(custom) })),
|
|
31
|
+
meshError: String(error?.message || error).slice(0, 400),
|
|
34
32
|
};
|
|
35
33
|
}
|
|
36
34
|
}
|
|
@@ -40,19 +38,48 @@ export function workerProgram(request) {
|
|
|
40
38
|
return source + '\nrunWorker(' + JSON.stringify(request) + ').then(result=>console.log(JSON.stringify(result))).catch(error=>{console.error(String(error?.message||"Selected agent worker failed"));process.exitCode=1;});\n';
|
|
41
39
|
}
|
|
42
40
|
|
|
41
|
+
function destinationDispatchResult(result, node, request) {
|
|
42
|
+
let payload;
|
|
43
|
+
try { payload = JSON.parse(result.output); } catch { /* No structured result from the installed owner. */ }
|
|
44
|
+
if (!payload || typeof payload.ok !== 'boolean' || (payload.ok && !result.ok)) {
|
|
45
|
+
throw notReady(`Installed Meta Harness on ${node} returned no valid dispatch outcome (exit ${result.exitCode ?? 'unknown'})`);
|
|
46
|
+
}
|
|
47
|
+
return { ...payload, execution: { node, workspace: request.cwd, remote: true,
|
|
48
|
+
...(typeof payload.receiptId === 'string' ? { receiptOwner: 'destination' } : {}) } };
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
export async function executeOnNode(node, request, { worker = runWorker, processRunner = runProcess, mesh, access } = {}) {
|
|
44
52
|
if (!node || node === 'local') return worker(request);
|
|
45
|
-
mesh ||= await hostModule('mesh');
|
|
53
|
+
mesh ||= await hostModule('mesh');
|
|
46
54
|
const target = mesh.findMeshNode(node);
|
|
47
55
|
if (!target) throw new Error('Selected mesh node is not recorded');
|
|
48
|
-
if (target.self) return worker(request);
|
|
56
|
+
if (target.self && request.operation !== 'dispatch') return worker(request);
|
|
57
|
+
// Route simple dispatch through the installed owner, just like a routed, coordinated or managed job.
|
|
58
|
+
const dispatch = request.operation === 'dispatch';
|
|
59
|
+
let transport = request;
|
|
60
|
+
if (dispatch) {
|
|
61
|
+
const input = JSON.stringify(destinationDispatchRequest(request));
|
|
62
|
+
if (Buffer.byteLength(input) > 65536) throw new Error('Remote dispatch request is too large');
|
|
63
|
+
transport = { operation: 'direct', cwd: request.cwd, argv: ['total-recall', 'meta-harness', 'dispatch', '--stdin', '--json'], input, timeoutMs: 600000 };
|
|
64
|
+
}
|
|
65
|
+
const managed = dispatch || (transport.argv?.[1] === 'meta-harness' &&
|
|
66
|
+
['route', 'coordinate', 'review', 'process', 'run-command', 'usage-retry'].includes(transport.argv[2]));
|
|
67
|
+
if (managed && (await readIncludedPolicy()).enabled) transport = { ...transport, requireIncluded: true };
|
|
68
|
+
if (target.self) {
|
|
69
|
+
const result = destinationDispatchResult(await worker(transport), 'local', request);
|
|
70
|
+
result.execution.remote = false;
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
49
73
|
if (target.online === false) throw new Error('Selected mesh node is offline');
|
|
74
|
+
access ||= await hostModule('mesh-access');
|
|
50
75
|
const resolved = access.resolveNodeAccess(target);
|
|
51
76
|
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'] });
|
|
52
77
|
if (!args) throw new Error('Selected mesh node has no verified SSH access');
|
|
53
|
-
const result = await processRunner('ssh', args, { input: workerProgram(
|
|
78
|
+
const result = await processRunner('ssh', args, { input: workerProgram(transport), timeoutMs: transport.operation === 'inspect' ? 120000 : (transport.timeoutMs || 900000) + 30000, maxBytes: 17 * 1024 * 1024, env: { ...agentEnvironment(), ...(process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {}) } });
|
|
54
79
|
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)}`);
|
|
55
|
-
|
|
80
|
+
let payload;
|
|
81
|
+
try { payload = JSON.parse(result.stdout); } catch { throw new Error('Selected mesh worker returned invalid data'); }
|
|
82
|
+
return dispatch ? destinationDispatchResult(payload, target.hostname || node, request) : payload;
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
export async function inspectNode(node = 'local', dependencies) {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/** Admission for the operator's included-usage policy. No provider generation happens here. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
import { configDir } from './paths.mjs';
|
|
9
|
+
import { agentEnvironment, CODEX_PLAN_ARGS, findBinary, OPENROUTER_AGENTS, readClaudeMaxOrganization } from './worker.mjs';
|
|
10
|
+
import { activeQuotaWalls, readReceipts } from './learner.mjs';
|
|
11
|
+
|
|
12
|
+
export const INCLUDED_AGENTS = ['claude', 'codex'];
|
|
13
|
+
const VERSION = 1;
|
|
14
|
+
const DEFAULT = { version: VERSION, enabled: false, claude: null };
|
|
15
|
+
const sha256 = key => createHash('sha256').update(key).digest('hex');
|
|
16
|
+
const validKeyName = name => typeof name === 'string' && /^[A-Z][A-Z0-9_]{1,100}$/.test(name);
|
|
17
|
+
const validModel = model => typeof model === 'string' && /^[\w.-]+\/[\w./:-]+$/.test(model) && model.length <= 180;
|
|
18
|
+
const validOpenRouterBinding = (agent, binding) => OPENROUTER_AGENTS.includes(agent) &&
|
|
19
|
+
validKeyName(binding?.keyName) && validModel(binding?.model);
|
|
20
|
+
|
|
21
|
+
async function policyStore() {
|
|
22
|
+
const { PluginStateStore } = await import('./ssss-store.mjs');
|
|
23
|
+
return new PluginStateStore(path.join(configDir(), 'included-usage.json'), 'meta-harness');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function readIncludedPolicy(store) {
|
|
27
|
+
store ??= await policyStore();
|
|
28
|
+
const state = store.read(DEFAULT);
|
|
29
|
+
if (state?.version !== VERSION || typeof state.enabled !== 'boolean' ||
|
|
30
|
+
(state.claude !== null && (!/^[a-f0-9]{64}$/.test(state.claude?.fingerprint || '') ||
|
|
31
|
+
typeof state.claude.keyName !== 'string' || typeof state.claude.organization !== 'string' ||
|
|
32
|
+
!Number.isFinite(Date.parse(state.claude.expiresAt)))) ||
|
|
33
|
+
(state.claudePlan !== undefined && (!/^[\w-]{1,100}$/.test(state.claudePlan?.organization || '') ||
|
|
34
|
+
state.claudePlan.usageCreditsEnabled !== false || !Number.isFinite(Date.parse(state.claudePlan.verifiedAt)))) ||
|
|
35
|
+
(state.openRouter !== undefined && (!state.openRouter || typeof state.openRouter !== 'object' ||
|
|
36
|
+
Array.isArray(state.openRouter) || !Object.entries(state.openRouter).every(([agent, binding]) => validOpenRouterBinding(agent, binding))))) {
|
|
37
|
+
throw new Error('Included usage policy is invalid; dispatch stopped.');
|
|
38
|
+
}
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requireOperator() {
|
|
43
|
+
if (process.env.TR_META_HARNESS_RUN) throw new Error('Only the operator may change the included usage policy.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function enforceIncludedUsage() {
|
|
47
|
+
requireOperator();
|
|
48
|
+
const store = await policyStore();
|
|
49
|
+
const state = await readIncludedPolicy(store);
|
|
50
|
+
if (!state.enabled) store.write({ ...state, enabled: true });
|
|
51
|
+
return includedUsageStatus();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function authRuntime() {
|
|
55
|
+
const root = process.env.TR_PACKAGE_ROOT;
|
|
56
|
+
if (!root || !path.isAbsolute(root) ||
|
|
57
|
+
JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name !== 'total-recall-brain') {
|
|
58
|
+
throw new Error('Verified Total Recall Auth runtime required.');
|
|
59
|
+
}
|
|
60
|
+
const { loadPluginModule } = await import(pathToFileURL(path.join(root, 'src/core/plugin-runtime.mjs')).href);
|
|
61
|
+
const { hostSecretsBrainDir } = await loadPluginModule('auth', 'host-secrets-store', { activeRequired: true });
|
|
62
|
+
return { brain: hostSecretsBrainDir(), load: module => loadPluginModule('auth', module, { activeRequired: true }) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function authValue(keyName) {
|
|
66
|
+
const { brain, load } = await authRuntime();
|
|
67
|
+
const { getSecret } = await load('secrets-store');
|
|
68
|
+
const result = await getSecret(brain, keyName, { action: 'use', actor: 'meta-harness' });
|
|
69
|
+
return result.found && typeof result.value === 'string' ? result.value : '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function authKey(keyName) {
|
|
73
|
+
const value = await authValue(keyName);
|
|
74
|
+
if (!/^sk-ant-api[0-9]+-[\w-]+$/.test(value)) {
|
|
75
|
+
throw new Error('The selected Anthropic API key is missing or invalid in Total Recall Auth.');
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function verifyOpenRouterKey(keyName) {
|
|
81
|
+
const { brain, load } = await authRuntime();
|
|
82
|
+
const { syncSecretAccount } = await load('provider-account-sync');
|
|
83
|
+
const result = await syncSecretAccount(brain, keyName);
|
|
84
|
+
return result.provider === 'openrouter' && result.key_valid === true && result.tracking_status === 'ok';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** OpenRouter opt-in is per harness and per destination, with no subscription fallback. */
|
|
88
|
+
export async function bindOpenRouter(agent, keyName, model, { verify = verifyOpenRouterKey } = {}) {
|
|
89
|
+
requireOperator();
|
|
90
|
+
const binding = { keyName, model };
|
|
91
|
+
if (!validOpenRouterBinding(agent, binding)) throw new Error('Select a supported OpenRouter harness, an Auth key name and a catalog model ID.');
|
|
92
|
+
if (!(await verify(keyName))) throw new Error('Total Recall Auth could not verify this OpenRouter key.');
|
|
93
|
+
const store = await policyStore();
|
|
94
|
+
const state = await readIncludedPolicy(store);
|
|
95
|
+
store.write({ ...state, enabled: true, openRouter: { ...state.openRouter, [agent]: binding } });
|
|
96
|
+
return { ok: true, agent, ...binding };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function openRouterAdmission(agent, policy, { readKey = authValue } = {}) {
|
|
100
|
+
const binding = policy.openRouter?.[agent];
|
|
101
|
+
if (!validOpenRouterBinding(agent, binding)) return { ok: false, failureKind: 'billing-policy', error: 'No approved OpenRouter binding for this harness.' };
|
|
102
|
+
const key = await readKey(binding.keyName);
|
|
103
|
+
if (!/^sk-or-v1-[\w-]+$/.test(key)) return { ok: false, failureKind: 'auth', error: 'The approved OpenRouter key is unavailable in Total Recall Auth.' };
|
|
104
|
+
return { ok: true, openRouter: { key, keyName: binding.keyName, model: binding.model } };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Bind the operator's Console observation to the actual secret without recording its value or hint. */
|
|
108
|
+
export async function bindClaudeIncludedKey(evidence, { readKey = authKey, now = Date.now() } = {}) {
|
|
109
|
+
requireOperator();
|
|
110
|
+
const { keyName, organization, keyHint, expiresAt, creditSource, autoReload, paymentMethod, invoiced, paidBalanceCents } = evidence || {};
|
|
111
|
+
const expiry = Date.parse(expiresAt);
|
|
112
|
+
const hint = typeof keyHint === 'string' && /^(sk-ant-api[0-9]+-[\w-]{2,20})(?:\.{3}|…)([\w-]{4,12})$/.exec(keyHint);
|
|
113
|
+
if (!/^[A-Z][A-Z0-9_]{1,100}$/.test(keyName || '') || !/^[\w-]{1,100}$/.test(organization || '') ||
|
|
114
|
+
!hint || !Number.isFinite(expiry) || expiry <= now || expiry > now + 35 * 86400000 ||
|
|
115
|
+
creditSource !== 'claude-max' || autoReload !== false || paymentMethod !== false || invoiced !== false || paidBalanceCents !== 0) {
|
|
116
|
+
throw new Error('Binding requires verified Max credit expiry, Console key hint, and a prepaid organization with no paid balance, auto-reload, or payment method.');
|
|
117
|
+
}
|
|
118
|
+
const key = await readKey(keyName);
|
|
119
|
+
if (!key.startsWith(hint[1]) || !key.endsWith(hint[2])) throw new Error('The Console key hint does not match the selected Total Recall Auth key.');
|
|
120
|
+
const store = await policyStore();
|
|
121
|
+
const state = await readIncludedPolicy(store);
|
|
122
|
+
store.write({ ...state, enabled: true, claude: { keyName, organization, expiresAt: new Date(expiry).toISOString(),
|
|
123
|
+
verifiedAt: new Date(now).toISOString(), fingerprint: sha256(key) } });
|
|
124
|
+
return includedUsageStatus();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const claudeProofCurrent = (policy, now = Date.now()) => Date.parse(policy.claude?.expiresAt) > now;
|
|
128
|
+
|
|
129
|
+
/** Bind the observed account setting to this host's native Claude Max login. */
|
|
130
|
+
export async function bindClaudeMaxPlan(evidence, { readOrganization = readClaudeMaxOrganization } = {}) {
|
|
131
|
+
requireOperator();
|
|
132
|
+
const { organization, usageCreditsEnabled } = evidence || {};
|
|
133
|
+
if (!/^[\w-]{1,100}$/.test(organization || '') || usageCreditsEnabled !== false) {
|
|
134
|
+
throw new Error('Confirm Usage credits are off in the signed-in Max account before binding it.');
|
|
135
|
+
}
|
|
136
|
+
if (organization !== await readOrganization(process.cwd())) {
|
|
137
|
+
throw new Error('The native Claude Max login does not match the verified organization.');
|
|
138
|
+
}
|
|
139
|
+
const store = await policyStore();
|
|
140
|
+
const state = await readIncludedPolicy(store);
|
|
141
|
+
store.write({ ...state, enabled: true, claudePlan: { organization, usageCreditsEnabled, verifiedAt: new Date().toISOString() } });
|
|
142
|
+
return includedUsageStatus();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function includedUsageStatus({ probe = false } = {}) {
|
|
146
|
+
const policy = await readIncludedPolicy();
|
|
147
|
+
const claude = policy.claude ? { organization: policy.claude.organization, verifiedAt: policy.claude.verifiedAt,
|
|
148
|
+
expiresAt: policy.claude.expiresAt, current: claudeProofCurrent(policy) } : null;
|
|
149
|
+
return { ok: true, policyVersion: VERSION, enabled: policy.enabled, claude, claudePlan: policy.claudePlan ?? null,
|
|
150
|
+
openRouter: policy.openRouter ?? {},
|
|
151
|
+
...(probe ? { codex: await readCodexAllowance(process.cwd()) } : {}) };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Use only the documented account reads on stdio. No turn/start is sent. */
|
|
155
|
+
export function queryCodexAccount(cwd, { env = process.env, timeoutMs = 10000 } = {}) {
|
|
156
|
+
const binary = findBinary('codex', env);
|
|
157
|
+
if (!binary) throw new Error('Codex CLI is unavailable.');
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const child = spawn(binary, [...CODEX_PLAN_ARGS, 'app-server', '--listen', 'stdio://'], {
|
|
160
|
+
cwd, env: agentEnvironment(env), stdio: ['pipe', 'pipe', 'ignore'],
|
|
161
|
+
});
|
|
162
|
+
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
163
|
+
let account, limits, size = 0, outcome;
|
|
164
|
+
const send = message => child.stdin.write(JSON.stringify(message) + '\n');
|
|
165
|
+
const fail = error => {
|
|
166
|
+
outcome ||= { error };
|
|
167
|
+
child.kill('SIGTERM');
|
|
168
|
+
};
|
|
169
|
+
const timer = setTimeout(() => { outcome ||= { error: 'Codex account read timed out.' }; child.kill('SIGKILL'); }, timeoutMs);
|
|
170
|
+
const closed = () => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
lines.close();
|
|
173
|
+
if (outcome?.result) resolve(outcome.result);
|
|
174
|
+
else reject(new Error(outcome?.error || 'Codex account read ended without a complete result.'));
|
|
175
|
+
};
|
|
176
|
+
child.once('error', () => { outcome = { error: 'Could not start Codex account read.' }; });
|
|
177
|
+
child.once('close', closed);
|
|
178
|
+
child.stdin.on('error', () => fail('Codex account read input closed.'));
|
|
179
|
+
child.stdout.on('data', chunk => { size += chunk.length; if (size > 256 * 1024) fail('Codex account response exceeded its bound.'); });
|
|
180
|
+
lines.on('line', line => {
|
|
181
|
+
if (outcome) return;
|
|
182
|
+
let frame;
|
|
183
|
+
try { frame = JSON.parse(line); } catch { fail('Codex returned invalid account data.'); return; }
|
|
184
|
+
if (![1, 2, 3].includes(frame?.id)) return;
|
|
185
|
+
if (!frame.result || frame.error) { fail('Codex could not read the signed-in account or its limits.'); return; }
|
|
186
|
+
if (frame.id === 1) {
|
|
187
|
+
send({ method: 'initialized', params: {} });
|
|
188
|
+
send({ id: 2, method: 'account/read', params: { refreshToken: false } });
|
|
189
|
+
send({ id: 3, method: 'account/rateLimits/read' });
|
|
190
|
+
} else if (frame.id === 2) account = frame.result;
|
|
191
|
+
else limits = frame.result;
|
|
192
|
+
if (account && limits) {
|
|
193
|
+
outcome = { result: { account, limits } };
|
|
194
|
+
child.stdin.end();
|
|
195
|
+
child.kill('SIGTERM');
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
send({ id: 1, method: 'initialize', params: { clientInfo: { name: 'meta_harness', title: 'Meta Harness', version: '1' } } });
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function evaluateCodexAllowance({ account, limits }, now = Date.now()) {
|
|
203
|
+
const plan = account?.account?.planType;
|
|
204
|
+
if (account?.account?.type !== 'chatgpt' || !/^(plus|pro|prolite|go|free)(?:[_-]\w+)?$/i.test(plan || '')) {
|
|
205
|
+
return { ok: false, failureKind: 'auth', error: 'Codex requires a verified personal ChatGPT plan with included limits.' };
|
|
206
|
+
}
|
|
207
|
+
const rate = limits?.rateLimits;
|
|
208
|
+
const buckets = [rate, ...Object.values(limits?.rateLimitsByLimitId || {})].filter(Boolean);
|
|
209
|
+
const windows = [rate?.primary, rate?.secondary].filter(Boolean);
|
|
210
|
+
if (!windows.length || windows.some(window => typeof window.usedPercent !== 'number')) {
|
|
211
|
+
return { ok: false, plan, failureKind: 'billing-policy', error: 'Codex did not report current included usage.' };
|
|
212
|
+
}
|
|
213
|
+
const credits = buckets.map(bucket => bucket.credits).filter(Boolean);
|
|
214
|
+
if (!credits.length || credits.some(c => c.hasCredits !== false || c.unlimited !== false ||
|
|
215
|
+
(c.balance != null && !/^0(?:\.0+)?$/.test(String(c.balance))))) {
|
|
216
|
+
return { ok: false, plan, failureKind: 'billing-policy', error: 'Codex did not confirm that paid credits are unavailable.' };
|
|
217
|
+
}
|
|
218
|
+
const full = windows.filter(window => window.usedPercent >= 100);
|
|
219
|
+
if (full.length) {
|
|
220
|
+
const resets = full.map(window => window.resetsAt * 1000);
|
|
221
|
+
return { ok: false, plan, failureKind: 'quota', error: 'Codex has reached an included usage limit.',
|
|
222
|
+
resetsAt: resets.every(reset => Number.isFinite(reset) && reset > now) ? new Date(Math.max(...resets)).toISOString() : null };
|
|
223
|
+
}
|
|
224
|
+
return { ok: true, plan, usagePercent: Math.max(...windows.map(window => window.usedPercent)) };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function readCodexAllowance(cwd, { query = queryCodexAccount } = {}) {
|
|
228
|
+
try { return evaluateCodexAllowance(await query(cwd)); }
|
|
229
|
+
catch (error) { return { ok: false, failureKind: 'billing-policy', error: error.message }; }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function includedAdmission(agent, cwd, policy, { readKey = authKey, codex = readCodexAllowance,
|
|
233
|
+
receipts = readReceipts } = {}) {
|
|
234
|
+
if (!INCLUDED_AGENTS.includes(agent)) return { ok: false, failureKind: 'billing-policy', error: 'This harness is outside the included usage policy.' };
|
|
235
|
+
if (agent === 'claude' && !policy.claudePlan && !claudeProofCurrent(policy)) {
|
|
236
|
+
return { ok: false, failureKind: 'billing-policy', error: 'Verify the native Claude Max login with Usage credits off, or bind a verified included grant.' };
|
|
237
|
+
}
|
|
238
|
+
const wall = activeQuotaWalls((await receipts()).filter(run => run.billingMode === 'included'))[agent];
|
|
239
|
+
if (wall) return { ok: false, failureKind: 'quota', error: 'This harness is waiting for included usage.', resetsAt: wall.resetReported ? wall.until : null };
|
|
240
|
+
if (agent === 'codex') return codex(cwd);
|
|
241
|
+
if (policy.claudePlan) return { ok: true, claudePlan: policy.claudePlan.organization };
|
|
242
|
+
try {
|
|
243
|
+
const key = await readKey(policy.claude.keyName);
|
|
244
|
+
if (sha256(key) !== policy.claude.fingerprint) throw new Error('The Claude key changed; verify the new key against the Max grant.');
|
|
245
|
+
return { ok: true, key };
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return { ok: false, failureKind: 'auth', error: error.message };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -49,6 +49,9 @@ export async function recordRunReceipt(receipt = {}) {
|
|
|
49
49
|
success: receipt.success === true,
|
|
50
50
|
durationMs: receipt.durationMs || 0,
|
|
51
51
|
failureKind: receipt.failureKind || null,
|
|
52
|
+
...(receipt.billingMode ? { billingMode: receipt.billingMode } : {}),
|
|
53
|
+
...(Array.isArray(receipt.tools) && receipt.tools.length ? { tools: receipt.tools.map(({ tool, status, exitCode }) =>
|
|
54
|
+
({ tool, status, ...(Number.isInteger(exitCode) ? { exitCode } : {}) })) } : {}),
|
|
52
55
|
error: receipt.error ? String(receipt.error).slice(0, 500) : null,
|
|
53
56
|
resetsAt: receipt.resetsAt || null,
|
|
54
57
|
gate: receipt.gate || null,
|
|
@@ -3,6 +3,7 @@ import { ADAPTER_CATALOG } from '../capability/index.mjs';
|
|
|
3
3
|
import { inspectAgents, getActiveAdapters } from './worker.mjs';
|
|
4
4
|
import { readReceipts, activeQuotaWalls, activeCredentialBlocks } from './learner.mjs';
|
|
5
5
|
import { measuredWalls } from './telemetry.mjs';
|
|
6
|
+
import { claudeProofCurrent, INCLUDED_AGENTS, readIncludedPolicy } from './included-usage.mjs';
|
|
6
7
|
import os from 'node:os';
|
|
7
8
|
|
|
8
9
|
const describe = (id, extra = {}) => {
|
|
@@ -23,16 +24,23 @@ export function exclusionReason(agent, wall, credentialBlock, { allowUnknownAuth
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/** An explicit list is the caller's decision and is not filtered. */
|
|
26
|
-
export async function resolvePool({ availableHarnesses, inspect = inspectAgents, receipts = readReceipts, allowUnknownAuth = false } = {}) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const
|
|
27
|
+
export async function resolvePool({ availableHarnesses, inspect = inspectAgents, receipts = readReceipts, allowUnknownAuth = false, policy } = {}) {
|
|
28
|
+
policy ??= await readIncludedPolicy();
|
|
29
|
+
if (!policy.enabled && Array.isArray(availableHarnesses) && availableHarnesses.length) return { ready: availableHarnesses.map(id => describe(id)), excluded: [], walls: {} };
|
|
30
|
+
const claudePlan = policy.enabled ? policy.claudePlan?.organization : undefined;
|
|
31
|
+
const [{ agents = [] }, entries] = await Promise.all([inspect({ env: process.env, claudePlan }), receipts()]);
|
|
32
|
+
const billingEntries = policy.enabled ? entries.filter(run => run.billingMode === 'included') : entries;
|
|
33
|
+
const walls = { ...activeQuotaWalls(billingEntries), ...(!policy.enabled ? measuredWalls() : {}) };
|
|
34
|
+
const blocks = activeCredentialBlocks(billingEntries);
|
|
31
35
|
const ready = [], excluded = [];
|
|
32
|
-
for (const
|
|
33
|
-
const
|
|
36
|
+
for (const installed of agents) {
|
|
37
|
+
const agent = policy.enabled && installed.id === 'claude' && !claudePlan ? { ...installed, authenticated: claudeProofCurrent(policy) } : installed;
|
|
38
|
+
const policyBlocked = policy.enabled && (!INCLUDED_AGENTS.includes(agent.id) || (Array.isArray(availableHarnesses) && !availableHarnesses.includes(agent.id)));
|
|
39
|
+
const reason = policyBlocked ? 'outside the included usage route' : exclusionReason(agent, walls[agent.id], blocks[agent.id], { allowUnknownAuth: !policy.enabled && allowUnknownAuth });
|
|
34
40
|
if (!reason) ready.push(describe(agent.id, { authVerified: agent.authenticated === true }));
|
|
35
|
-
else if (agent.installed) excluded.push({ harness: agent.id, reason
|
|
41
|
+
else if (agent.installed) excluded.push({ harness: agent.id, reason,
|
|
42
|
+
...(policy.enabled && !policyBlocked && agent.authenticated === true && agent.versionVerified && !agent.blockedBy && walls[agent.id]
|
|
43
|
+
? { failureKind: 'quota', resetsAt: walls[agent.id].resetReported ? walls[agent.id].until : null } : {}) });
|
|
36
44
|
}
|
|
37
45
|
return { ready, excluded, walls };
|
|
38
46
|
}
|
|
@@ -170,6 +170,7 @@ export async function executeProcessTask(job, dependencies = {}) {
|
|
|
170
170
|
if (branch && !commit) { (dependencies.deleteBranch || deleteBranch)(job.cwd, branch); branch = null; }
|
|
171
171
|
} catch (error) { result = { ...result, ok: false, error: `${result.error ? result.error + '; ' : ''}Isolation cleanup failed: ${error.message}` }; }
|
|
172
172
|
return updateAgentRecord(job.id, { status: result.ok ? 'completed' : 'failed', exitCode: result.exitCode ?? (result.ok ? 0 : 1), error: result.ok ? null : result.error,
|
|
173
|
+
...(result.queueId ? { queueId: result.queueId } : {}),
|
|
173
174
|
result: result.response || null, branch, commit, worktreePath: worktree, endedAt: new Date().toISOString() });
|
|
174
175
|
}
|
|
175
176
|
|
|
@@ -81,6 +81,11 @@ export async function callLocalRuntime(prompt, system, config, dependencies = {}
|
|
|
81
81
|
model: agent.model || undefined, write: false, role: 'Reasoning', allowUnknownAuth: config.allowUnknownAuth === true,
|
|
82
82
|
});
|
|
83
83
|
if (result.ok === true || result.success === true) return result.response || '';
|
|
84
|
+
if (result.queued || result.queueError || result.failureKind === 'billing-policy') {
|
|
85
|
+
const error = new Error(result.queued ? `Included usage exhausted. Queued as ${result.queueId}.` : result.queueError || result.error);
|
|
86
|
+
error.queueId = result.queueId;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
84
89
|
failures.push(`${agent.name}: ${result.error || 'dispatch failed'}`);
|
|
85
90
|
}
|
|
86
91
|
throw new Error(`All CLI agents failed (${failures.join('; ')})`);
|
|
@@ -60,7 +60,7 @@ export async function forwardRemoteOperation(operation, task, options = {}) {
|
|
|
60
60
|
error: transport.error || 'Remote operation returned no valid structured result' };
|
|
61
61
|
}
|
|
62
62
|
const result = { ...payload, ok: transport.ok === true && payload.ok === true, execution, transportExitCode: transport.exitCode ?? null,
|
|
63
|
-
...(!transport.ok ? { failureKind: 'transport' } : {}) };
|
|
63
|
+
...(!transport.ok && !payload.failureKind ? { failureKind: 'transport' } : {}) };
|
|
64
64
|
if (!result.ok && !result.error) result.error = transport.error || `Remote ${operation} failed`;
|
|
65
65
|
if (result.ok && payload.branch) result.deliverable = { node: options.node, workspace: options.cwd, branch: payload.branch, commit: payload.commit ?? null };
|
|
66
66
|
const threadId = payload.threadId || options.threadId;
|
|
@@ -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
|
}
|