total-recall-brain 3.43.2 → 3.44.0

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