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.
Files changed (93) hide show
  1. package/docs/reference/cli-reference.md +3 -1
  2. package/package.json +3 -3
  3. package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
  4. package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
  5. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
  6. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
  7. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
  8. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
  9. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
  10. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
  11. package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
  12. package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
  13. package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
  14. package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
  15. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
  16. package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
  17. package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +14 -13
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +18 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
  26. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +14 -13
  27. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
  28. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +14 -13
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +14 -13
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
  34. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +14 -13
  36. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +3 -3
  37. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +14 -13
  39. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
  40. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +38 -5
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +38 -7
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +14 -13
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -3
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +85 -3
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +2 -1
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +12 -1
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +15 -3
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +38 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +42 -11
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +40 -13
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +1 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +5 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +1 -1
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +27 -11
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +322 -40
  63. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +14 -13
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  66. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +14 -13
  67. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
  68. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  69. package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
  70. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
  71. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
  72. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
  73. package/src/cli/connect.mjs +2 -29
  74. package/src/cli/context.mjs +1 -0
  75. package/src/cli/ingest-okf.mjs +4 -4
  76. package/src/cli/release.mjs +4 -1
  77. package/src/cli/update.mjs +4 -1
  78. package/src/core/automatic-brain-updates.mjs +11 -6
  79. package/src/core/brain-updates.mjs +11 -1
  80. package/src/core/context-compactor.mjs +7 -2
  81. package/src/core/context-compiler.mjs +6 -1
  82. package/src/core/context-policy.mjs +7 -3
  83. package/src/core/daemon-control.mjs +22 -0
  84. package/src/core/global-brain-updates.mjs +1 -1
  85. package/src/core/mesh-brain-updates.mjs +2 -1
  86. package/src/core/server-restart.mjs +128 -7
  87. package/src/core/ssss-kernel-bridge.mjs +1 -2
  88. package/src/core/surface.mjs +3 -3
  89. package/src/core/task-context.mjs +9 -8
  90. package/src/core/validated-write.mjs +4 -0
  91. package/src/core/vault-files.mjs +8 -2
  92. package/src/core/vault.mjs +1 -1
  93. 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
- if (Array.isArray(availableHarnesses) && availableHarnesses.length) return { ready: availableHarnesses.map(id => describe(id)), excluded: [], walls: {} };
28
- const [{ agents = [] }, entries] = await Promise.all([inspect({ env: process.env }), receipts()]);
29
- const walls = { ...activeQuotaWalls(entries), ...measuredWalls() };
30
- const blocks = activeCredentialBlocks(entries);
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 agent of agents) {
33
- const reason = exclusionReason(agent, walls[agent.id], blocks[agent.id], { allowUnknownAuth });
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 : (runHostCli(['auth', 'secret', 'get', 'DEVELOPER_OPENROUTER_API_KEY']) || runHostCli(['auth', 'secret', 'get', 'OPENROUTER_API_KEY']));
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 inspected = remote ? await (options.inspectNodeFn || inspectNode)(options.node) : await (options.inspect || inspectAgents)({ env: process.env });
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 walls = remote ? {} : { ...activeQuotaWalls(await readReceipts()), ...measuredWalls(codexLimits) };
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 = remote ? {} : activeCredentialBlocks(await readReceipts());
171
- for (const agent of inspected.agents || []) {
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 reason = exclusionReason(agent, wall, block);
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: agent.id === 'ollama' ? 'local' : metered.has(agent.id) ? 'metered' : 'subscription',
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
- /** One native runtime-management boundary for every installed agent. */
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 (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid native runtime arguments');
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.length ? args : ['--help'], { cwd, timeoutMs:60000, env:agentEnvironment(env) });
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
+ }