total-recall-brain 3.43.3 → 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/docs/reference/cli-reference.md +3 -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 +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/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 +4 -1
- package/src/cli/update.mjs +4 -1
- package/src/core/automatic-brain-updates.mjs +11 -6
- package/src/core/brain-updates.mjs +11 -1
- 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 +1 -1
- package/src/core/mesh-brain-updates.mjs +2 -1
- package/src/core/server-restart.mjs +128 -7
- package/src/core/ssss-kernel-bridge.mjs +1 -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 +3 -1
|
@@ -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
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** SSSS-backed queue for work deferred by included usage limits. */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { configDir } from './paths.mjs';
|
|
5
|
+
import { destinationDispatchRequest } from './dispatch-request.mjs';
|
|
6
|
+
import { readIncludedPolicy } from './included-usage.mjs';
|
|
7
|
+
|
|
8
|
+
async function queueStore() {
|
|
9
|
+
const { PluginStateStore } = await import('./ssss-store.mjs');
|
|
10
|
+
return new PluginStateStore(path.join(configDir(), 'usage-queue.json'), 'meta-harness');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readRows(store) {
|
|
14
|
+
const jobs = store.read([]);
|
|
15
|
+
if (!Array.isArray(jobs) || jobs.some(job => !job?.id || typeof job.task !== 'string' || !path.isAbsolute(job.workspace || '') ||
|
|
16
|
+
!['waiting', 'running', 'completed', 'cancelled', 'failed'].includes(job.status))) {
|
|
17
|
+
throw new Error('Invalid included usage queue.');
|
|
18
|
+
}
|
|
19
|
+
return jobs;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function mutateQueue(change) {
|
|
23
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
24
|
+
const store = await queueStore();
|
|
25
|
+
const jobs = readRows(store);
|
|
26
|
+
const result = change(jobs);
|
|
27
|
+
try { store.write(jobs); return result; }
|
|
28
|
+
catch (error) { if (!error.message.includes('version conflict') || attempt === 7) throw error; }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function publicJob({ replay, task, ...job }) {
|
|
33
|
+
return { ...job, task: task.slice(0, 160), canRetry: Boolean(replay) && !job.needsReview };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function queueIncludedJob(request, result, { role, replay, launchPrevented = false } = {}) {
|
|
37
|
+
const workspace = request.contextCwd || request.cwd;
|
|
38
|
+
const direct = !replay && role === 'Dispatch' && workspace === request.cwd;
|
|
39
|
+
if (direct) replay = { kind: 'dispatch', request: destinationDispatchRequest(request) };
|
|
40
|
+
const job = {
|
|
41
|
+
id: randomUUID(), status: 'waiting', agent: request.agent, workspace, role: role || 'Dispatch',
|
|
42
|
+
task: String(request.contextTask || request.prompt).slice(0, direct ? 160 : 65536),
|
|
43
|
+
reason: 'included-usage-exhausted',
|
|
44
|
+
resetsAt: Number.isFinite(Date.parse(result.resetsAt)) ? result.resetsAt : null,
|
|
45
|
+
queuedAt: new Date().toISOString(), attempts: 0,
|
|
46
|
+
needsReview: !replay || (!launchPrevented && request.permissions?.write === true),
|
|
47
|
+
replay: replay || null,
|
|
48
|
+
};
|
|
49
|
+
return mutateQueue(jobs => {
|
|
50
|
+
if (jobs.filter(item => ['waiting', 'running'].includes(item.status)).length >= 200) throw new Error('Included usage queue is full.');
|
|
51
|
+
const history = jobs.filter(item => !['waiting', 'running'].includes(item.status)).slice(0, -50);
|
|
52
|
+
for (const item of history) jobs.splice(jobs.indexOf(item), 1);
|
|
53
|
+
jobs.push(job);
|
|
54
|
+
if (Buffer.byteLength(JSON.stringify(jobs)) > 4 * 1024 * 1024) throw new Error('Included usage queue exceeds its storage bound.');
|
|
55
|
+
return publicJob(job);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function listIncludedJobs() {
|
|
60
|
+
const jobs = readRows(await queueStore());
|
|
61
|
+
return jobs.map(publicJob);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function updateJob(jobs, id, change) {
|
|
65
|
+
const index = jobs.findIndex(job => job.id === id);
|
|
66
|
+
if (index < 0) throw new Error('Included usage job was not found.');
|
|
67
|
+
return jobs[index] = change(jobs[index]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function cancelIncludedJob(id) {
|
|
71
|
+
return mutateQueue(jobs => publicJob(updateJob(jobs, id, job => {
|
|
72
|
+
if (job.status !== 'waiting') throw new Error('Only a waiting job can be cancelled.');
|
|
73
|
+
return { ...job, status: 'cancelled', replay: null };
|
|
74
|
+
})));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Claim atomically before retry, so two callers cannot start the same job. */
|
|
78
|
+
export async function retryIncludedJob(id, { dispatch, route, now = Date.now() } = {}) {
|
|
79
|
+
if (!(await readIncludedPolicy()).enabled) throw new Error('Included usage policy must be enabled to retry.');
|
|
80
|
+
const claimed = await mutateQueue(jobs => updateJob(jobs, id, job => {
|
|
81
|
+
if (job.status !== 'waiting') throw new Error('Only a waiting job can be retried.');
|
|
82
|
+
if (job.needsReview || !job.replay) throw new Error('Review the prior attempt and resubmit this task from its original workspace.');
|
|
83
|
+
if (Date.parse(job.resetsAt) > now) throw new Error('The provider-reported reset time has not arrived.');
|
|
84
|
+
return { ...job, status: 'running', attempts: job.attempts + 1, startedAt: new Date(now).toISOString() };
|
|
85
|
+
}));
|
|
86
|
+
let result;
|
|
87
|
+
try {
|
|
88
|
+
if (claimed.replay.kind === 'dispatch') {
|
|
89
|
+
dispatch ??= (await import('./dispatch.mjs')).dispatchWithReceipt;
|
|
90
|
+
result = await dispatch(claimed.replay.request, { role: 'Dispatch', retryId: id });
|
|
91
|
+
} else if (claimed.replay.kind === 'route') {
|
|
92
|
+
route ??= (await import('./cascade.mjs')).executeRoutedTask;
|
|
93
|
+
result = await route(claimed.task, { cwd: claimed.workspace, gate: claimed.replay.gate, gateOptions: claimed.replay.gateOptions,
|
|
94
|
+
route: { selected: claimed.agent }, pool: [claimed.agent], retryId: id });
|
|
95
|
+
} else throw new Error('Unknown included usage job operation.');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
result = { ok: false, failureKind: 'execution', error: error.message };
|
|
98
|
+
}
|
|
99
|
+
const held = ['quota', 'billing-policy', 'auth'].includes(result.failureKind);
|
|
100
|
+
const updated = await mutateQueue(jobs => updateJob(jobs, id, job => {
|
|
101
|
+
if (job.status !== 'running') throw new Error('Included usage job lost its claim.');
|
|
102
|
+
return { ...job, status: result.ok ? 'completed' : held ? 'waiting' : 'failed',
|
|
103
|
+
resetsAt: held && Number.isFinite(Date.parse(result.resetsAt)) ? result.resetsAt : null,
|
|
104
|
+
needsReview: job.needsReview || result.needsReview === true ||
|
|
105
|
+
(held && !result.launchPrevented && (job.replay.kind === 'route' || job.replay.request?.permissions?.write === true)),
|
|
106
|
+
lastError: result.ok ? null : result.error,
|
|
107
|
+
replay: held ? job.replay : null, finishedAt: new Date().toISOString() };
|
|
108
|
+
}));
|
|
109
|
+
return { ...result, job: publicJob(updated) };
|
|
110
|
+
}
|