Files
stack/packages/bus/src/usage.mjs
T
jason.woltje c216f47dbb plugins, cli, bus, business, webui: harness, model and thinking from the instance vars, a fallback route, and a harness plug-in kind (row 65, #1558)
Adds the harness plug-in kind (interface 1). packages/plugins/harness/pi
and claude-code hold what core branched on: the adapter, thinking levels,
preflight, version probe, sandbox mounts, builtins line, typed tool names,
gate timeout, bundle files, adapter env, turn end and session-files path.
Core reaches a module only through the loader.

A session's route is the instance's harness, model and thinking vars, or
the three fallback. vars; a missing one refuses no-route, and the system
file's execution keys are never read for a session. The launcher checks
the primary route, then the fallback: harness-off, harness-refused,
thinking-unsupported, then preflight. A failed preflight falls back when
a fallback is set, and any refusal of the fallback names the primary's
failure too. Account checks run
only when the route's module counts an account. The manifest records
route, routeReason and harnessVersion, replacing piVersion and
claudeVersion. pi gets --thinking, Claude Code --effort, both from
MOSAIC_THINKING.

The business file refuses a partial fallback. routes and routes.set are
human-only bus verbs outside ORGANIZATION_KEYS; checkRoutes is shared by
routes.set, instances.set and limits.set and checks only changed routes.
The chief of staff changes an instance's model, and the assistant's whole
vars (Q169), through limits.set, so its route write runs the same checks.
A model outside launch.models refuses unknown-model, primary or fallback,
and routes.set passes that code through.
A new run binds only on a switched-on harness module name and records
its transcript path, which the usage reader uses. Settings gains a
Routing table with six selects per instance and loses the Role models
fieldset; it posts changed rows to /api/routes. webui/src/bus.mjs is
unchanged.

Until Jason's `mosaic plugins set` adds "harness": ["claude-code"], a new
launch refuses harness-off.
2026-10-19 11:27:19 -05:00

136 lines
7.6 KiB
JavaScript

// REQ-LIMIT-4: the host calls sources only through the registry loader.
import { createLoader } from '../../plugins/src/loader.mjs';
const KINDS = ['input', 'output', 'cacheWrite', 'cacheRead'];
const zero = () => ({ input: 0, output: 0, cacheWrite: 0, cacheRead: 0 });
const unknown = code => ({ status: 'unknown', code });
function snapshot(value) {
if (value?.status === 'unknown') return unknown(/^transcript-(path|size|line|entries|records|changed|parse|usage|harness)$/.test(value.code) ? value.code : 'source-unknown');
if (value?.status !== 'known' || !value.entries || typeof value.entries !== 'object' || Array.isArray(value.entries))
return unknown('source-invalid');
const entries = Object.entries(value.entries);
if (entries.length > 20000) return unknown('source-invalid');
for (const [id, v] of entries) {
if (id.length > 512 || !v || Object.keys(v).length !== 4 ||
KINDS.some(k => !Number.isSafeInteger(v[k]) || v[k] < 0)) return unknown('source-invalid');
}
return { status: 'known', entries: value.entries };
}
function delta(before, after) {
if (!before || before.status !== 'known') return unknown('baseline-unknown');
if (after.status !== 'known') return after;
for (const [id, v] of Object.entries(before.entries)) {
if (!Object.hasOwn(after.entries, id) || KINDS.some(k => after.entries[id][k] !== v[k])) return unknown('transcript-rewritten');
}
const sum = zero(); let added = false;
for (const [id, v] of Object.entries(after.entries)) {
if (Object.hasOwn(before.entries, id)) continue;
added = true;
for (const k of KINDS) {
sum[k] += v[k];
if (!Number.isSafeInteger(sum[k])) return unknown('usage-overflow');
}
}
return added ? { status: 'known', ...sum } : unknown('no-new-usage');
}
// One record per (run, turn, source): the latest row, an end row winning. A turn is known for a
// source only when that row is an end row with status known; a begin row alone is unknown.
// view() and the limit checks (row 125, packages/bus/src/limits.mjs) count the same records.
export function turnRecords(store, business) {
const turns = new Map();
for (const row of store.all(`SELECT * FROM usage_records WHERE business=? ORDER BY seq`, business)) {
const key = JSON.stringify([row.run, row.turn, row.source]);
if (row.phase === 'end' || !turns.has(key)) turns.set(key, row);
}
return [...turns.values()].map(row => {
const known = row.phase === 'end' && row.status === 'known';
return {
at: row.at, run: row.run, turn: row.turn, source: row.source, instance: row.instance,
provider: row.provider, model: row.model, account: row.account, status: known ? 'known' : 'unknown',
input: known ? row.input : null, output: known ? row.output : null,
cacheWrite: known ? row.cache_write : null, cacheRead: known ? row.cache_read : null,
};
});
}
export async function createUsage({ store, businesses, log }) {
const loaders = new Map();
for (const [id, b] of Object.entries(businesses)) {
const loader = createLoader({ log });
await loader.load(b.plugins ?? {});
loaders.set(id, loader);
}
// This map serializes retries per run, without keeping a SQL transaction across source awaits.
const pending = new Map();
async function perform(session, phase, turn) {
const { business, run, role: instance, harness, provider = null, model = null, account = null } = session;
const existing = store.all('SELECT * FROM usage_records WHERE business=? AND run=? AND turn=? AND phase=?', business, run, turn, phase);
if (existing.length) return { recorded: true };
const loader = loaders.get(business);
const modules = loader.modules('usage-source'), names = loader.loaded()['usage-source'];
const bases = store.all("SELECT * FROM usage_records WHERE business=? AND run=? AND turn=? AND phase='begin'", business, run, turn);
const sources = modules.length ? modules.map((module, i) => ({ module, name: names[i] })) : [{ name: '@none' }];
// Preserve a failed/disabled baseline even if configuration changes between calls/restarts.
for (const base of bases) if (!sources.some(s => s.name === base.source)) sources.push({ name: base.source });
const rows = [];
// Row 65 (#1558): where the run's harness keeps its session files is the module's transcript(),
// which the launcher binds into the run record. A run bound without one has no reading.
const root = session.transcript ?? null;
for (const { module, name } of sources) {
let sample = unknown('no-source');
if (module && root === null) sample = unknown('no-transcript');
else if (module) {
try { sample = snapshot(await module.readTurn({ harness, root })); }
catch { sample = unknown('source-failed'); }
}
const base = bases.find(row => row.source === name);
const result = phase === 'begin' ? sample : delta(base?.cursor ? JSON.parse(base.cursor) : null, sample);
rows.push({ name, result });
}
store.transaction(() => {
for (const { name, result } of rows) {
const known = result.status === 'known';
const counts = phase === 'begin' ? zero() : result;
store.run(`INSERT INTO usage_records(at,business,run,instance,provider,model,account,turn,source,phase,status,code,cursor,input,output,cache_write,cache_read)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, new Date().toISOString(), business, run, instance,
provider, model, account, turn, name, phase, result.status, result.code ?? null,
phase === 'begin' ? JSON.stringify(result) : null,
known ? counts.input : null, known ? counts.output : null, known ? counts.cacheWrite : null, known ? counts.cacheRead : null);
}
});
for (const { name, result } of rows) if (phase === 'end' && result.status === 'unknown') {
log({ event: 'usage-unknown', business, run, instance, turn, source: name, code: result.code });
}
return { recorded: true };
}
function collect(session, phase, turn) {
const key = session.business + ':' + session.run;
const op = (pending.get(key) ?? Promise.resolve()).then(() => perform(session, phase, turn));
const tail = op.catch(() => {});
pending.set(key, tail);
return op.finally(() => { if (pending.get(key) === tail) pending.delete(key); });
}
function view(business) {
const groups = { byRun: new Map(), byInstance: new Map(), byProvider: new Map(), byModel: new Map(), byAccount: new Map() };
const total = { ...zero(), unknown: new Set() };
const add = (target, row) => {
if (row.status !== 'known') target.unknown.add(row.run + ':' + row.turn);
else for (const key of KINDS) target[key] = BigInt(target[key]) + BigInt(row[key]);
};
for (const row of turnRecords(store, business)) {
add(total, row);
for (const [name, identity] of Object.entries({ byRun: { run: row.run }, byInstance: { instance: row.instance },
byProvider: { provider: row.provider }, byModel: { provider: row.provider, model: row.model }, byAccount: { account: row.account } })) {
const key = JSON.stringify(identity);
if (!groups[name].has(key)) groups[name].set(key, { ...identity, ...zero(), unknown: new Set() });
add(groups[name].get(key), row);
}
}
const finish = value => {
const { unknown, ...rest } = value;
for (const kind of KINDS) rest[kind] = BigInt(rest[kind]) <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(rest[kind]) : String(rest[kind]);
return { ...rest, unknownTurns: unknown.size };
};
return { total: finish(total), ...Object.fromEntries(Object.entries(groups).map(([key, map]) => [key, [...map.values()].map(finish)])) };
}
return { collect, view };
}