@mnemom/mnemom 0.17.4-next.3 → 0.17.4-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-url-proxy.mjs +154 -0
- package/dist/commands/agent-first-run.d.ts +35 -0
- package/dist/commands/agent-first-run.js +78 -0
- package/dist/commands/agent.d.ts +24 -0
- package/dist/commands/agent.js +130 -14
- package/dist/index.js +10 -0
- package/dist/lib/agent.d.ts +112 -0
- package/dist/lib/agent.js +184 -0
- package/dist/lib/first-run/backtest.d.ts +92 -0
- package/dist/lib/first-run/backtest.js +196 -0
- package/dist/lib/first-run/governed.d.ts +65 -0
- package/dist/lib/first-run/governed.js +253 -0
- package/dist/lib/first-run/index.d.ts +75 -0
- package/dist/lib/first-run/index.js +255 -0
- package/dist/lib/first-run/lines.d.ts +16 -0
- package/dist/lib/first-run/lines.js +80 -0
- package/dist/lib/first-run/scope.d.ts +108 -0
- package/dist/lib/first-run/scope.js +308 -0
- package/dist/lib/first-run/screen.d.ts +91 -0
- package/dist/lib/first-run/screen.js +324 -0
- package/dist/lib/first-run/seat.d.ts +75 -0
- package/dist/lib/first-run/seat.js +250 -0
- package/dist/rc-proxy.mjs +16 -1
- package/dist/turn-origin.mjs +330 -0
- package/package.json +1 -1
package/dist/lib/agent.d.ts
CHANGED
|
@@ -116,6 +116,118 @@ export declare function sanitizeAgentName(raw: string): string;
|
|
|
116
116
|
* post`) can identify themselves by the same name the claude.ai session list shows.
|
|
117
117
|
*/
|
|
118
118
|
export declare const SESSION_NAME_ENV = "MNEMOM_AGENT_SESSION_NAME";
|
|
119
|
+
/**
|
|
120
|
+
* Claude Code's tool-search switch. Behind an ANTHROPIC_BASE_URL that is not an Anthropic host
|
|
121
|
+
* (the gateway door), Claude Code turns tool search OFF unless this is set, and then inlines
|
|
122
|
+
* every tool schema — MCP included — on every request (EIM: 311 tools, a ~245k-token fixed
|
|
123
|
+
* prefix, a 200k session that starts at "0% left" and compaction that can never get under it).
|
|
124
|
+
* Claude Code 2.1.282 / 2.1.285 log `[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=… is
|
|
125
|
+
* not a first-party Anthropic host. Set ENABLE_TOOL_SEARCH=true (or auto / auto:N) if your proxy
|
|
126
|
+
* forwards tool_reference blocks.` and parse: `force`, `auto`, `auto:N` (0–100), a truthy value
|
|
127
|
+
* (1/true/yes/on) ⇒ tool search on, a falsy one (0/false/no/off) ⇒ off; empty/unset ⇒ off behind
|
|
128
|
+
* a proxy.
|
|
129
|
+
*/
|
|
130
|
+
export declare const TOOL_SEARCH_ENV = "ENABLE_TOOL_SEARCH";
|
|
131
|
+
/**
|
|
132
|
+
* Turn Claude Code's tool search on for a gateway launch: sets ENABLE_TOOL_SEARCH=true in the
|
|
133
|
+
* child env unless the user already gave it a value (any non-blank value — `false`, `auto:50`,
|
|
134
|
+
* `force` — is theirs and is kept). A blank value counts as unset: Claude Code reads it as unset
|
|
135
|
+
* too. Mutates and returns `env`.
|
|
136
|
+
*/
|
|
137
|
+
export declare function applyToolSearchDefault(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
138
|
+
/** The value of a passthrough `--model <id>` / `--model=<id>`, else null. */
|
|
139
|
+
export declare function passthroughModel(passthrough: readonly string[]): string | null;
|
|
140
|
+
/** Where a launch's model came from (resolveLaunchModel). */
|
|
141
|
+
export type LaunchModelSource = "flag" | "passthrough" | "env" | "none";
|
|
142
|
+
export interface LaunchModel {
|
|
143
|
+
/** The model this launch runs on as far as the launcher can tell (the ledger records it). */
|
|
144
|
+
model: string | null;
|
|
145
|
+
source: LaunchModelSource;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The model a launch runs on, kept VERBATIM — a `[1m]` suffix included, which is what makes
|
|
149
|
+
* Claude Code send the `context-1m-2025-08-07` beta and size its window at 1M (verified against
|
|
150
|
+
* real claude 2.1.285 behind a local base URL, for `--model` and for ANTHROPIC_MODEL):
|
|
151
|
+
* 1. `--model` (or launcher.toml `model`) → what buildCliArgs injects / the server shape's
|
|
152
|
+
* ANTHROPIC_MODEL;
|
|
153
|
+
* 2. a passthrough `-- --model <id>` → already on the CLI's argv;
|
|
154
|
+
* 3. ANTHROPIC_MODEL in the environment → every launch shape's child inherits it;
|
|
155
|
+
* 4. nothing → Claude Code's own default (on `--resume` Claude Code restores the session's
|
|
156
|
+
* own model, `[1m]` included), recorded as null.
|
|
157
|
+
* Only `--model` is ever injected; the rest is what the ledger records so `mnemom agent
|
|
158
|
+
* sessions` shows the window a session ran with instead of null.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveLaunchModel(input: {
|
|
161
|
+
explicit?: string;
|
|
162
|
+
passthrough: readonly string[];
|
|
163
|
+
env: Record<string, string | undefined>;
|
|
164
|
+
}): LaunchModel;
|
|
165
|
+
/**
|
|
166
|
+
* Claude Code's own 1M opt-out. Its window code short-circuits on it (`function xD(){return
|
|
167
|
+
* a.CLAUDE_CODE_DISABLE_1M_CONTEXT}`; every `[1m]` path checks `if(xD())return!1` first, and a
|
|
168
|
+
* `[1m]` model switch under it fails with "1M context is turned off (CLAUDE_CODE_DISABLE_1M_CONTEXT
|
|
169
|
+
* is set)"). Set in the environment or in a settings `env` block, it is the user's explicit
|
|
170
|
+
* 200k choice and the launcher then adds no `[1m]`.
|
|
171
|
+
*/
|
|
172
|
+
export declare const DISABLE_1M_ENV = "CLAUDE_CODE_DISABLE_1M_CONTEXT";
|
|
173
|
+
/** The window suffix Claude Code reads off a model id (`/\[1m\]/i` → 1M + `context-1m` beta). */
|
|
174
|
+
export declare const ONE_M_SUFFIX = "[1m]";
|
|
175
|
+
/**
|
|
176
|
+
* Whether `[1m]` may be appended to `model`. Claude Code does NOT check this itself: given
|
|
177
|
+
* `--model haiku[1m]` or `default[1m]` it sends the 1M beta (and, for `default`, a model id of
|
|
178
|
+
* `default`) upstream (measured, claude 2.1.282 / 2.1.285). So the launcher allows only the
|
|
179
|
+
* `opus` / `sonnet` aliases and concrete `claude-opus-*` / `claude-sonnet-*` ids, minus the ids
|
|
180
|
+
* Claude Code itself lists as having no 1M window (`function mde(e){return
|
|
181
|
+
* e.includes("claude-3-")||e==="claude-opus-4-0"||e==="claude-opus-4-1"||e==="claude-opus-4-5"||
|
|
182
|
+
* e==="claude-haiku-4-5"}`) — `claude-opus-4` / `-4-0` / `-4-1` / `-4-5`, dated or not. Anything
|
|
183
|
+
* else (haiku, non-Claude router models, unknown families) is left as given.
|
|
184
|
+
*/
|
|
185
|
+
export declare function supportsOneM(model: string): boolean;
|
|
186
|
+
/** The parts of Claude Code's settings files the window default reads. */
|
|
187
|
+
export interface CcSettingsView {
|
|
188
|
+
model?: string;
|
|
189
|
+
env?: Record<string, string | undefined>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Claude Code's settings files, highest precedence first: `<cwd>/.claude/settings.local.json`,
|
|
193
|
+
* `<cwd>/.claude/settings.json`, then the user's `settings.json` under CLAUDE_CONFIG_DIR (else
|
|
194
|
+
* `~/.claude`). Unreadable or malformed files are skipped. Managed (MDM) settings are not read.
|
|
195
|
+
*/
|
|
196
|
+
export declare function readCcSettings(opts: {
|
|
197
|
+
cwd: string;
|
|
198
|
+
configDir: string;
|
|
199
|
+
readFile?: (path: string) => string;
|
|
200
|
+
}): CcSettingsView[];
|
|
201
|
+
/** Why the launch model came out the way it did (resolveWindowModel). */
|
|
202
|
+
export type WindowReason = "one-m" | "opt-out" | "passthrough" | "suffixed" | "unsupported" | "resume" | "cc-default";
|
|
203
|
+
export interface WindowModel {
|
|
204
|
+
/** The model to launch with (`--model`, or the server shape's ANTHROPIC_MODEL); undefined = none. */
|
|
205
|
+
model: string | undefined;
|
|
206
|
+
reason: WindowReason;
|
|
207
|
+
/** The model before `[1m]` was appended (reason "one-m" only). */
|
|
208
|
+
from?: string;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The launch model with the 1M window as the default. The model the launch would run on — the
|
|
212
|
+
* `--model` flag (or launcher.toml `model`), else an inherited ANTHROPIC_MODEL, else Claude Code's
|
|
213
|
+
* settings (`env.ANTHROPIC_MODEL`, then `model`) — gets `[1m]` appended when it has no window
|
|
214
|
+
* suffix and supports 1M. Claude Code reads the window off the id: `[1m]` ⇒ a 1M window and the
|
|
215
|
+
* `context-1m-2025-08-07` beta (`function ift(e){return/\[1m\]/i.test(e)}`; `Ob()` returns 1e6
|
|
216
|
+
* when it matches). Without it, claude 2.1.282 runs a bare `opus` / `claude-opus-4-8` at 200k on
|
|
217
|
+
* both auth paths (the EIM thrash window); 2.1.285 already gives those ids 1M, where `[1m]` is
|
|
218
|
+
* harmless.
|
|
219
|
+
*
|
|
220
|
+
* Never overrides an explicit choice: CLAUDE_CODE_DISABLE_1M_CONTEXT (env or settings env), a
|
|
221
|
+
* passthrough `-- --model`, and any model that already names a window are left exactly as
|
|
222
|
+
* given. A resume with no model injects nothing (Claude Code restores the session's model,
|
|
223
|
+
* `[1m]` included), and so does a launch with no model anywhere (Claude Code's default).
|
|
224
|
+
*/
|
|
225
|
+
export declare function resolveWindowModel(input: {
|
|
226
|
+
explicit?: string;
|
|
227
|
+
passthrough: readonly string[];
|
|
228
|
+
env: Record<string, string | undefined>;
|
|
229
|
+
settings: () => CcSettingsView[];
|
|
230
|
+
}): WindowModel;
|
|
119
231
|
/**
|
|
120
232
|
* Claude Code's opt-in for its gateway hint headers (`x-claude-code-request-class`,
|
|
121
233
|
* `x-claude-code-agent-type`, `x-claude-code-prompt-id`). CC sends them to api.anthropic.com on
|
package/dist/lib/agent.js
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* NAMED), but prod is never selected silently — see `resolveGatewayHost`.
|
|
21
21
|
*/
|
|
22
22
|
import { createHash, randomUUID } from "node:crypto";
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
23
25
|
import { getGatewayUrl } from "./config.js";
|
|
24
26
|
/** The us-2/prod gateway host. The default door — never any other cell silently. */
|
|
25
27
|
export const PROD_GATEWAY_HOST = "https://gateway.mnemom.ai";
|
|
@@ -117,6 +119,188 @@ export function sanitizeAgentName(raw) {
|
|
|
117
119
|
* post`) can identify themselves by the same name the claude.ai session list shows.
|
|
118
120
|
*/
|
|
119
121
|
export const SESSION_NAME_ENV = "MNEMOM_AGENT_SESSION_NAME";
|
|
122
|
+
/**
|
|
123
|
+
* Claude Code's tool-search switch. Behind an ANTHROPIC_BASE_URL that is not an Anthropic host
|
|
124
|
+
* (the gateway door), Claude Code turns tool search OFF unless this is set, and then inlines
|
|
125
|
+
* every tool schema — MCP included — on every request (EIM: 311 tools, a ~245k-token fixed
|
|
126
|
+
* prefix, a 200k session that starts at "0% left" and compaction that can never get under it).
|
|
127
|
+
* Claude Code 2.1.282 / 2.1.285 log `[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=… is
|
|
128
|
+
* not a first-party Anthropic host. Set ENABLE_TOOL_SEARCH=true (or auto / auto:N) if your proxy
|
|
129
|
+
* forwards tool_reference blocks.` and parse: `force`, `auto`, `auto:N` (0–100), a truthy value
|
|
130
|
+
* (1/true/yes/on) ⇒ tool search on, a falsy one (0/false/no/off) ⇒ off; empty/unset ⇒ off behind
|
|
131
|
+
* a proxy.
|
|
132
|
+
*/
|
|
133
|
+
export const TOOL_SEARCH_ENV = "ENABLE_TOOL_SEARCH";
|
|
134
|
+
/**
|
|
135
|
+
* Turn Claude Code's tool search on for a gateway launch: sets ENABLE_TOOL_SEARCH=true in the
|
|
136
|
+
* child env unless the user already gave it a value (any non-blank value — `false`, `auto:50`,
|
|
137
|
+
* `force` — is theirs and is kept). A blank value counts as unset: Claude Code reads it as unset
|
|
138
|
+
* too. Mutates and returns `env`.
|
|
139
|
+
*/
|
|
140
|
+
export function applyToolSearchDefault(env) {
|
|
141
|
+
if ((env[TOOL_SEARCH_ENV] ?? "").trim() === "")
|
|
142
|
+
env[TOOL_SEARCH_ENV] = "true";
|
|
143
|
+
return env;
|
|
144
|
+
}
|
|
145
|
+
/** The value of a passthrough `--model <id>` / `--model=<id>`, else null. */
|
|
146
|
+
export function passthroughModel(passthrough) {
|
|
147
|
+
for (let i = 0; i < passthrough.length; i++) {
|
|
148
|
+
const a = passthrough[i];
|
|
149
|
+
if (a === "--model") {
|
|
150
|
+
const next = (passthrough[i + 1] ?? "").trim();
|
|
151
|
+
return next && !next.startsWith("-") ? next : null;
|
|
152
|
+
}
|
|
153
|
+
if (a.startsWith("--model="))
|
|
154
|
+
return a.slice("--model=".length).trim() || null;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The model a launch runs on, kept VERBATIM — a `[1m]` suffix included, which is what makes
|
|
160
|
+
* Claude Code send the `context-1m-2025-08-07` beta and size its window at 1M (verified against
|
|
161
|
+
* real claude 2.1.285 behind a local base URL, for `--model` and for ANTHROPIC_MODEL):
|
|
162
|
+
* 1. `--model` (or launcher.toml `model`) → what buildCliArgs injects / the server shape's
|
|
163
|
+
* ANTHROPIC_MODEL;
|
|
164
|
+
* 2. a passthrough `-- --model <id>` → already on the CLI's argv;
|
|
165
|
+
* 3. ANTHROPIC_MODEL in the environment → every launch shape's child inherits it;
|
|
166
|
+
* 4. nothing → Claude Code's own default (on `--resume` Claude Code restores the session's
|
|
167
|
+
* own model, `[1m]` included), recorded as null.
|
|
168
|
+
* Only `--model` is ever injected; the rest is what the ledger records so `mnemom agent
|
|
169
|
+
* sessions` shows the window a session ran with instead of null.
|
|
170
|
+
*/
|
|
171
|
+
export function resolveLaunchModel(input) {
|
|
172
|
+
const explicit = (input.explicit ?? "").trim();
|
|
173
|
+
if (explicit)
|
|
174
|
+
return { model: explicit, source: "flag" };
|
|
175
|
+
const fromArgv = passthroughModel(input.passthrough);
|
|
176
|
+
if (fromArgv)
|
|
177
|
+
return { model: fromArgv, source: "passthrough" };
|
|
178
|
+
const fromEnv = (input.env.ANTHROPIC_MODEL ?? "").trim();
|
|
179
|
+
if (fromEnv)
|
|
180
|
+
return { model: fromEnv, source: "env" };
|
|
181
|
+
return { model: null, source: "none" };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Claude Code's own 1M opt-out. Its window code short-circuits on it (`function xD(){return
|
|
185
|
+
* a.CLAUDE_CODE_DISABLE_1M_CONTEXT}`; every `[1m]` path checks `if(xD())return!1` first, and a
|
|
186
|
+
* `[1m]` model switch under it fails with "1M context is turned off (CLAUDE_CODE_DISABLE_1M_CONTEXT
|
|
187
|
+
* is set)"). Set in the environment or in a settings `env` block, it is the user's explicit
|
|
188
|
+
* 200k choice and the launcher then adds no `[1m]`.
|
|
189
|
+
*/
|
|
190
|
+
export const DISABLE_1M_ENV = "CLAUDE_CODE_DISABLE_1M_CONTEXT";
|
|
191
|
+
/** The window suffix Claude Code reads off a model id (`/\[1m\]/i` → 1M + `context-1m` beta). */
|
|
192
|
+
export const ONE_M_SUFFIX = "[1m]";
|
|
193
|
+
const FALSY = new Set(["0", "false", "no", "off"]);
|
|
194
|
+
/** True when an env-style value turns a switch on: non-blank and not 0/false/no/off. */
|
|
195
|
+
function envOn(v) {
|
|
196
|
+
const t = (v ?? "").trim().toLowerCase();
|
|
197
|
+
return t !== "" && !FALSY.has(t);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Whether `[1m]` may be appended to `model`. Claude Code does NOT check this itself: given
|
|
201
|
+
* `--model haiku[1m]` or `default[1m]` it sends the 1M beta (and, for `default`, a model id of
|
|
202
|
+
* `default`) upstream (measured, claude 2.1.282 / 2.1.285). So the launcher allows only the
|
|
203
|
+
* `opus` / `sonnet` aliases and concrete `claude-opus-*` / `claude-sonnet-*` ids, minus the ids
|
|
204
|
+
* Claude Code itself lists as having no 1M window (`function mde(e){return
|
|
205
|
+
* e.includes("claude-3-")||e==="claude-opus-4-0"||e==="claude-opus-4-1"||e==="claude-opus-4-5"||
|
|
206
|
+
* e==="claude-haiku-4-5"}`) — `claude-opus-4` / `-4-0` / `-4-1` / `-4-5`, dated or not. Anything
|
|
207
|
+
* else (haiku, non-Claude router models, unknown families) is left as given.
|
|
208
|
+
*/
|
|
209
|
+
export function supportsOneM(model) {
|
|
210
|
+
const id = model.trim().toLowerCase();
|
|
211
|
+
if (id === "opus" || id === "sonnet")
|
|
212
|
+
return true;
|
|
213
|
+
if (!/^claude-(opus|sonnet)-/.test(id))
|
|
214
|
+
return false;
|
|
215
|
+
return !/^claude-opus-4(-[015])?(-\d{8})?$/.test(id);
|
|
216
|
+
}
|
|
217
|
+
/** True when `model` already names a window (`[1m]`, `[2m]`, any `[…]` suffix): left as given. */
|
|
218
|
+
function hasWindowSuffix(model) {
|
|
219
|
+
return /\[[^\]]*\]\s*$/.test(model);
|
|
220
|
+
}
|
|
221
|
+
/** Passthrough `--resume` / `-r` / `--continue` / `-c`: Claude Code restores the session's model. */
|
|
222
|
+
function resumesSession(passthrough) {
|
|
223
|
+
return passthrough.some((a) => a === "--resume" ||
|
|
224
|
+
a === "-r" ||
|
|
225
|
+
a === "--continue" ||
|
|
226
|
+
a === "-c" ||
|
|
227
|
+
a.startsWith("--resume="));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Claude Code's settings files, highest precedence first: `<cwd>/.claude/settings.local.json`,
|
|
231
|
+
* `<cwd>/.claude/settings.json`, then the user's `settings.json` under CLAUDE_CONFIG_DIR (else
|
|
232
|
+
* `~/.claude`). Unreadable or malformed files are skipped. Managed (MDM) settings are not read.
|
|
233
|
+
*/
|
|
234
|
+
export function readCcSettings(opts) {
|
|
235
|
+
const read = opts.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const p of [
|
|
238
|
+
join(opts.cwd, ".claude", "settings.local.json"),
|
|
239
|
+
join(opts.cwd, ".claude", "settings.json"),
|
|
240
|
+
join(opts.configDir, "settings.json"),
|
|
241
|
+
]) {
|
|
242
|
+
try {
|
|
243
|
+
const raw = JSON.parse(read(p));
|
|
244
|
+
if (!raw || typeof raw !== "object")
|
|
245
|
+
continue;
|
|
246
|
+
const o = raw;
|
|
247
|
+
const view = {};
|
|
248
|
+
if (typeof o.model === "string")
|
|
249
|
+
view.model = o.model;
|
|
250
|
+
if (o.env && typeof o.env === "object") {
|
|
251
|
+
view.env = Object.fromEntries(Object.entries(o.env).filter((e) => typeof e[1] === "string"));
|
|
252
|
+
}
|
|
253
|
+
out.push(view);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// absent / unreadable / not JSON: Claude Code ignores it too
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The launch model with the 1M window as the default. The model the launch would run on — the
|
|
263
|
+
* `--model` flag (or launcher.toml `model`), else an inherited ANTHROPIC_MODEL, else Claude Code's
|
|
264
|
+
* settings (`env.ANTHROPIC_MODEL`, then `model`) — gets `[1m]` appended when it has no window
|
|
265
|
+
* suffix and supports 1M. Claude Code reads the window off the id: `[1m]` ⇒ a 1M window and the
|
|
266
|
+
* `context-1m-2025-08-07` beta (`function ift(e){return/\[1m\]/i.test(e)}`; `Ob()` returns 1e6
|
|
267
|
+
* when it matches). Without it, claude 2.1.282 runs a bare `opus` / `claude-opus-4-8` at 200k on
|
|
268
|
+
* both auth paths (the EIM thrash window); 2.1.285 already gives those ids 1M, where `[1m]` is
|
|
269
|
+
* harmless.
|
|
270
|
+
*
|
|
271
|
+
* Never overrides an explicit choice: CLAUDE_CODE_DISABLE_1M_CONTEXT (env or settings env), a
|
|
272
|
+
* passthrough `-- --model`, and any model that already names a window are left exactly as
|
|
273
|
+
* given. A resume with no model injects nothing (Claude Code restores the session's model,
|
|
274
|
+
* `[1m]` included), and so does a launch with no model anywhere (Claude Code's default).
|
|
275
|
+
*/
|
|
276
|
+
export function resolveWindowModel(input) {
|
|
277
|
+
const explicit = (input.explicit ?? "").trim() || undefined;
|
|
278
|
+
const given = (reason) => ({ model: explicit, reason });
|
|
279
|
+
if (passthroughModel(input.passthrough) !== null)
|
|
280
|
+
return given("passthrough");
|
|
281
|
+
let settings;
|
|
282
|
+
const loadSettings = () => (settings ??= input.settings());
|
|
283
|
+
if (envOn(input.env[DISABLE_1M_ENV]) ||
|
|
284
|
+
loadSettings().some((s) => envOn(s.env?.[DISABLE_1M_ENV]))) {
|
|
285
|
+
return given("opt-out");
|
|
286
|
+
}
|
|
287
|
+
let base = explicit ?? ((input.env.ANTHROPIC_MODEL ?? "").trim() || undefined);
|
|
288
|
+
if (base === undefined) {
|
|
289
|
+
if (resumesSession(input.passthrough))
|
|
290
|
+
return given("resume");
|
|
291
|
+
const s = loadSettings();
|
|
292
|
+
base =
|
|
293
|
+
s.map((v) => (v.env?.ANTHROPIC_MODEL ?? "").trim()).find((m) => m !== "") ??
|
|
294
|
+
s.map((v) => (v.model ?? "").trim()).find((m) => m !== "");
|
|
295
|
+
if (base === undefined)
|
|
296
|
+
return given("cc-default");
|
|
297
|
+
}
|
|
298
|
+
if (hasWindowSuffix(base))
|
|
299
|
+
return { model: explicit, reason: "suffixed" };
|
|
300
|
+
if (!supportsOneM(base))
|
|
301
|
+
return { model: explicit, reason: "unsupported" };
|
|
302
|
+
return { model: `${base}${ONE_M_SUFFIX}`, reason: "one-m", from: base };
|
|
303
|
+
}
|
|
120
304
|
/**
|
|
121
305
|
* Claude Code's opt-in for its gateway hint headers (`x-claude-code-request-class`,
|
|
122
306
|
* `x-claude-code-agent-type`, `x-claude-code-prompt-id`). CC sends them to api.anthropic.com on
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first-run cost backtest: price each request in a Claude Code transcript at API list
|
|
3
|
+
* price, and estimate what the same request would have cost through Mnemom from the
|
|
4
|
+
* EMPIRICAL saving curve measured on paired stock-vs-Mnemom runs.
|
|
5
|
+
*
|
|
6
|
+
* Read-only; no network. Only token counts, model ids and timestamps are read from a
|
|
7
|
+
* transcript line — never message content.
|
|
8
|
+
*
|
|
9
|
+
* The curve (lower 95% bootstrap bound of the size-matched per-request saving, rounded
|
|
10
|
+
* DOWN to 0.1 pt) comes from the S43 / S44 chain45 and d240p1 marathon paired arms:
|
|
11
|
+
* mnemom-adw scratchpad/wow-first-run/CURVE-REPORT.md, curve/curve.json. The strategy is
|
|
12
|
+
* chosen PER REQUEST from that request's original context (operator policy 2026-09-30):
|
|
13
|
+
*
|
|
14
|
+
* < 200k none (no fold) → 0 on screen (the measured balanced rate there is
|
|
15
|
+
* slightly negative; floored, raw kept for the audit)
|
|
16
|
+
* 200k – 700k aggressive → the measured lower bound of its bin
|
|
17
|
+
* 700k – 1M aggressive → the 400–700k bound, EXTRAPOLATED (no measured pair)
|
|
18
|
+
* ≥ 1M balanced → 0 (live sessions only; a stock history cannot get
|
|
19
|
+
* there because Claude Code compacts first — counted)
|
|
20
|
+
*/
|
|
21
|
+
/** One billed request, deduped by message id. `t` is epoch seconds. */
|
|
22
|
+
export interface Row {
|
|
23
|
+
t: number;
|
|
24
|
+
model: string;
|
|
25
|
+
inp: number;
|
|
26
|
+
cr: number;
|
|
27
|
+
cw5: number;
|
|
28
|
+
cw1h: number;
|
|
29
|
+
out: number;
|
|
30
|
+
/** false for a sidechain (Task / subagent) request inside a top-level transcript. */
|
|
31
|
+
mainThread: boolean;
|
|
32
|
+
/** true when the row came from a subagents/ transcript. */
|
|
33
|
+
subagent: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* List prices, USD per MTok: [input, output, cache read, cache write 5m, cache write 1h].
|
|
37
|
+
* Source (verified 2026-09-30): Claude Code 2.1.285's own model registry + pricing table
|
|
38
|
+
* read out of the installed binary (models[].pricing → tier_*). Longest prefix wins; an
|
|
39
|
+
* unknown model is priced as DEFAULT_PRICE.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PRICES: Readonly<Record<string, readonly [number, number, number, number, number]>>;
|
|
42
|
+
export declare function price(model: string): readonly [number, number, number, number, number];
|
|
43
|
+
/** List-price cost of one request, USD. */
|
|
44
|
+
export declare function rowCost(r: Row): number;
|
|
45
|
+
export declare function actualBill(rows: readonly Row[]): number;
|
|
46
|
+
/** An ungoverned request's original context = its billed prompt. */
|
|
47
|
+
export declare function origContext(r: Row): number;
|
|
48
|
+
/**
|
|
49
|
+
* Billed requests of one transcript, deduped by message id (last copy wins, first-seen
|
|
50
|
+
* order). With `seen`, an id already billed in an earlier transcript (a resumed / forked
|
|
51
|
+
* session copies messages) is skipped, so each request is billed once, to its origin.
|
|
52
|
+
*/
|
|
53
|
+
export declare function loadTranscript(path: string, seen?: Set<string>): Row[];
|
|
54
|
+
/** Original-context bin edges (tokens). */
|
|
55
|
+
export declare const CURVE_EDGES: readonly [0, 100000, 200000, 300000, 400000, 700000, 1000000];
|
|
56
|
+
/**
|
|
57
|
+
* Lower 95% bound per bin, rounded down to 0.1 pt (curve/curve.json `strategies`).
|
|
58
|
+
* aggressive: no measured pair below 100k or at/above 700k.
|
|
59
|
+
*/
|
|
60
|
+
export declare const CURVE_LOWER: Readonly<Record<"aggressive" | "balanced", Record<number, number>>>;
|
|
61
|
+
export declare const NO_FOLD_BELOW = 200000;
|
|
62
|
+
export declare const EXTRAPOLATE_FROM = 700000;
|
|
63
|
+
export declare const BALANCED_FROM = 1000000;
|
|
64
|
+
export declare const POLICY = "none <200k / aggressive 200k-1M / balanced 1M+ (1M+ applies only to live sessions)";
|
|
65
|
+
export declare const ESTIMATOR_METHOD: string;
|
|
66
|
+
export type Strategy = "none" | "aggressive" | "balanced_1M+";
|
|
67
|
+
export type RateNote = "floored_at_0" | "extrapolated" | "exception_1M_plus" | null;
|
|
68
|
+
/** The curve bin of an original context (0 = below 100k; the top bin is open-ended). */
|
|
69
|
+
export declare function binOf(x: number): number;
|
|
70
|
+
/** The screen never claims a negative saving: a negative measured rate shows as 0. */
|
|
71
|
+
export declare function floorRate(raw: number): {
|
|
72
|
+
screen: number;
|
|
73
|
+
note: "floored_at_0" | null;
|
|
74
|
+
};
|
|
75
|
+
/** The per-request rate the screen uses (`screen`) and the honest measured one (`raw`). */
|
|
76
|
+
export declare function ruleRate(x: number): {
|
|
77
|
+
strategy: Strategy;
|
|
78
|
+
raw: number;
|
|
79
|
+
screen: number;
|
|
80
|
+
note: RateNote;
|
|
81
|
+
};
|
|
82
|
+
export interface Estimate {
|
|
83
|
+
actual: number;
|
|
84
|
+
saved: number;
|
|
85
|
+
/** The saving with the < 200k band NOT floored (audit only; never shown). */
|
|
86
|
+
rawSaved: number;
|
|
87
|
+
peak: number;
|
|
88
|
+
requests: number;
|
|
89
|
+
notes: Partial<Record<Exclude<RateNote, null>, number>>;
|
|
90
|
+
}
|
|
91
|
+
/** The one swap point: rows → list-price actual and the conservative saving. */
|
|
92
|
+
export declare function estimate(rows: readonly Row[]): Estimate;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first-run cost backtest: price each request in a Claude Code transcript at API list
|
|
3
|
+
* price, and estimate what the same request would have cost through Mnemom from the
|
|
4
|
+
* EMPIRICAL saving curve measured on paired stock-vs-Mnemom runs.
|
|
5
|
+
*
|
|
6
|
+
* Read-only; no network. Only token counts, model ids and timestamps are read from a
|
|
7
|
+
* transcript line — never message content.
|
|
8
|
+
*
|
|
9
|
+
* The curve (lower 95% bootstrap bound of the size-matched per-request saving, rounded
|
|
10
|
+
* DOWN to 0.1 pt) comes from the S43 / S44 chain45 and d240p1 marathon paired arms:
|
|
11
|
+
* mnemom-adw scratchpad/wow-first-run/CURVE-REPORT.md, curve/curve.json. The strategy is
|
|
12
|
+
* chosen PER REQUEST from that request's original context (operator policy 2026-09-30):
|
|
13
|
+
*
|
|
14
|
+
* < 200k none (no fold) → 0 on screen (the measured balanced rate there is
|
|
15
|
+
* slightly negative; floored, raw kept for the audit)
|
|
16
|
+
* 200k – 700k aggressive → the measured lower bound of its bin
|
|
17
|
+
* 700k – 1M aggressive → the 400–700k bound, EXTRAPOLATED (no measured pair)
|
|
18
|
+
* ≥ 1M balanced → 0 (live sessions only; a stock history cannot get
|
|
19
|
+
* there because Claude Code compacts first — counted)
|
|
20
|
+
*/
|
|
21
|
+
import { forEachLine, isoSeconds, parseJson } from "./lines.js";
|
|
22
|
+
/**
|
|
23
|
+
* List prices, USD per MTok: [input, output, cache read, cache write 5m, cache write 1h].
|
|
24
|
+
* Source (verified 2026-09-30): Claude Code 2.1.285's own model registry + pricing table
|
|
25
|
+
* read out of the installed binary (models[].pricing → tier_*). Longest prefix wins; an
|
|
26
|
+
* unknown model is priced as DEFAULT_PRICE.
|
|
27
|
+
*/
|
|
28
|
+
export const PRICES = {
|
|
29
|
+
"claude-opus-5-5": [4.0, 20.0, 0.2, 5.0, 8.0],
|
|
30
|
+
"claude-opus-5": [5.0, 25.0, 0.5, 6.25, 10.0],
|
|
31
|
+
"claude-opus-4": [5.0, 25.0, 0.5, 6.25, 10.0],
|
|
32
|
+
"claude-opus-4-0": [15.0, 75.0, 1.5, 18.75, 30.0],
|
|
33
|
+
"claude-opus-4-1": [15.0, 75.0, 1.5, 18.75, 30.0],
|
|
34
|
+
"claude-fable-5-1": [10.0, 50.0, 0.25, 12.5, 20.0],
|
|
35
|
+
"claude-fable-5": [10.0, 50.0, 1.0, 12.5, 20.0],
|
|
36
|
+
"claude-mythos": [10.0, 50.0, 0.25, 12.5, 20.0],
|
|
37
|
+
"claude-sonnet-5": [2.0, 10.0, 0.2, 2.5, 4.0],
|
|
38
|
+
"claude-sonnet-4": [3.0, 15.0, 0.3, 3.75, 6.0],
|
|
39
|
+
"claude-haiku-4-5": [1.0, 5.0, 0.1, 1.25, 2.0],
|
|
40
|
+
"claude-3": [3.0, 15.0, 0.3, 3.75, 6.0],
|
|
41
|
+
"claude-3-5-haiku": [0.8, 4.0, 0.08, 1.0, 1.6],
|
|
42
|
+
};
|
|
43
|
+
const DEFAULT_PRICE = "claude-opus-5";
|
|
44
|
+
export function price(model) {
|
|
45
|
+
const m = model.replace(/\[[^\]]*\]$/, "");
|
|
46
|
+
let best = null;
|
|
47
|
+
for (const k of Object.keys(PRICES)) {
|
|
48
|
+
if (m.startsWith(k) && (best === null || k.length > best.length))
|
|
49
|
+
best = k;
|
|
50
|
+
}
|
|
51
|
+
return PRICES[best ?? DEFAULT_PRICE];
|
|
52
|
+
}
|
|
53
|
+
/** List-price cost of one request, USD. */
|
|
54
|
+
export function rowCost(r) {
|
|
55
|
+
const [pi, po, pr, w5, w1] = price(r.model);
|
|
56
|
+
return (r.inp * pi + r.out * po + r.cr * pr + r.cw5 * w5 + r.cw1h * w1) / 1e6;
|
|
57
|
+
}
|
|
58
|
+
export function actualBill(rows) {
|
|
59
|
+
let s = 0;
|
|
60
|
+
for (const r of rows)
|
|
61
|
+
s += rowCost(r);
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
/** An ungoverned request's original context = its billed prompt. */
|
|
65
|
+
export function origContext(r) {
|
|
66
|
+
return r.inp + r.cr + r.cw5 + r.cw1h;
|
|
67
|
+
}
|
|
68
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
69
|
+
const USAGE = Buffer.from('"usage"');
|
|
70
|
+
const ASSISTANT = Buffer.from('"assistant"');
|
|
71
|
+
/**
|
|
72
|
+
* Billed requests of one transcript, deduped by message id (last copy wins, first-seen
|
|
73
|
+
* order). With `seen`, an id already billed in an earlier transcript (a resumed / forked
|
|
74
|
+
* session copies messages) is skipped, so each request is billed once, to its origin.
|
|
75
|
+
*/
|
|
76
|
+
export function loadTranscript(path, seen) {
|
|
77
|
+
const byId = new Map();
|
|
78
|
+
const subagent = path.includes("/subagents/");
|
|
79
|
+
let anon = 0;
|
|
80
|
+
forEachLine(path, (line) => {
|
|
81
|
+
if (line.indexOf(USAGE) === -1 || line.indexOf(ASSISTANT) === -1)
|
|
82
|
+
return;
|
|
83
|
+
const d = parseJson(line);
|
|
84
|
+
if (!d || d.type !== "assistant")
|
|
85
|
+
return;
|
|
86
|
+
const m = d.message;
|
|
87
|
+
if (!m || typeof m !== "object")
|
|
88
|
+
return;
|
|
89
|
+
const u = m.usage;
|
|
90
|
+
if (!u || typeof u !== "object")
|
|
91
|
+
return;
|
|
92
|
+
const model = typeof m.model === "string" ? m.model : "";
|
|
93
|
+
if (!model || model.startsWith("<"))
|
|
94
|
+
return;
|
|
95
|
+
const t = isoSeconds(d.timestamp);
|
|
96
|
+
if (t === null)
|
|
97
|
+
return;
|
|
98
|
+
const cc = u.cache_creation ?? {};
|
|
99
|
+
const cw = num(u.cache_creation_input_tokens);
|
|
100
|
+
const cw1h = num(cc.ephemeral_1h_input_tokens);
|
|
101
|
+
// Older Claude Code versions omit the 5m split (or the whole cache_creation object):
|
|
102
|
+
// derive 5m as total − 1h, and never let a reported 5m share leave writes unbilled.
|
|
103
|
+
const cw5 = Math.max(num(cc.ephemeral_5m_input_tokens), cw - cw1h);
|
|
104
|
+
const id = (typeof m.id === "string" && m.id) ||
|
|
105
|
+
(typeof d.uuid === "string" && d.uuid) ||
|
|
106
|
+
`anon-${path}-${anon++}`;
|
|
107
|
+
byId.set(id, {
|
|
108
|
+
t,
|
|
109
|
+
model,
|
|
110
|
+
inp: num(u.input_tokens),
|
|
111
|
+
cr: num(u.cache_read_input_tokens),
|
|
112
|
+
cw5: Math.max(cw5, 0),
|
|
113
|
+
cw1h,
|
|
114
|
+
out: num(u.output_tokens),
|
|
115
|
+
mainThread: d.isSidechain !== true,
|
|
116
|
+
subagent,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const [id, row] of byId) {
|
|
121
|
+
if (seen) {
|
|
122
|
+
if (seen.has(id))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(id);
|
|
125
|
+
}
|
|
126
|
+
out.push(row);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
// ── the empirical curve ──────────────────────────────────────────────────────
|
|
131
|
+
/** Original-context bin edges (tokens). */
|
|
132
|
+
export const CURVE_EDGES = [0, 100_000, 200_000, 300_000, 400_000, 700_000, 1_000_000];
|
|
133
|
+
/**
|
|
134
|
+
* Lower 95% bound per bin, rounded down to 0.1 pt (curve/curve.json `strategies`).
|
|
135
|
+
* aggressive: no measured pair below 100k or at/above 700k.
|
|
136
|
+
*/
|
|
137
|
+
export const CURVE_LOWER = {
|
|
138
|
+
// bin 1 (100-200k) is measured at -0.059 but never used: < 200k runs no fold.
|
|
139
|
+
aggressive: { 2: 0.137, 3: 0.282, 4: 0.342 },
|
|
140
|
+
balanced: { 0: -0.222, 1: -0.028, 2: 0.055, 3: 0.081, 4: 0.163, 5: 0.43 },
|
|
141
|
+
};
|
|
142
|
+
export const NO_FOLD_BELOW = 200_000;
|
|
143
|
+
export const EXTRAPOLATE_FROM = 700_000;
|
|
144
|
+
export const BALANCED_FROM = 1_000_000;
|
|
145
|
+
export const POLICY = "none <200k / aggressive 200k-1M / balanced 1M+ (1M+ applies only to live sessions)";
|
|
146
|
+
export const ESTIMATOR_METHOD = "empirical curve (lower 95% bound, rounded down), strategy per request by its original context, " +
|
|
147
|
+
`policy ${POLICY}: < 200k the balanced arm's measured rate floored at 0, 200k-1M aggressive by bin ` +
|
|
148
|
+
"(>= 700k: the 400-700k rate, extrapolated); request count and output held at stock";
|
|
149
|
+
/** The curve bin of an original context (0 = below 100k; the top bin is open-ended). */
|
|
150
|
+
export function binOf(x) {
|
|
151
|
+
let i = 0;
|
|
152
|
+
while (i < CURVE_EDGES.length - 2 && x >= CURVE_EDGES[i + 1])
|
|
153
|
+
i++;
|
|
154
|
+
return i;
|
|
155
|
+
}
|
|
156
|
+
/** The screen never claims a negative saving: a negative measured rate shows as 0. */
|
|
157
|
+
export function floorRate(raw) {
|
|
158
|
+
return raw < 0 ? { screen: 0, note: "floored_at_0" } : { screen: raw, note: null };
|
|
159
|
+
}
|
|
160
|
+
/** The per-request rate the screen uses (`screen`) and the honest measured one (`raw`). */
|
|
161
|
+
export function ruleRate(x) {
|
|
162
|
+
if (x >= BALANCED_FROM) {
|
|
163
|
+
return { strategy: "balanced_1M+", raw: 0, screen: 0, note: "exception_1M_plus" };
|
|
164
|
+
}
|
|
165
|
+
if (x < NO_FOLD_BELOW) {
|
|
166
|
+
const raw = CURVE_LOWER.balanced[binOf(x)];
|
|
167
|
+
return { strategy: "none", raw, ...floorRate(raw) };
|
|
168
|
+
}
|
|
169
|
+
if (x >= EXTRAPOLATE_FROM) {
|
|
170
|
+
const r = CURVE_LOWER.aggressive[binOf(400_000)];
|
|
171
|
+
return { strategy: "aggressive", raw: r, screen: r, note: "extrapolated" };
|
|
172
|
+
}
|
|
173
|
+
const r = CURVE_LOWER.aggressive[binOf(x)];
|
|
174
|
+
return { strategy: "aggressive", raw: r, screen: r, note: null };
|
|
175
|
+
}
|
|
176
|
+
/** The one swap point: rows → list-price actual and the conservative saving. */
|
|
177
|
+
export function estimate(rows) {
|
|
178
|
+
let actual = 0;
|
|
179
|
+
let saved = 0;
|
|
180
|
+
let rawSaved = 0;
|
|
181
|
+
let peak = 0;
|
|
182
|
+
const notes = {};
|
|
183
|
+
for (const r of rows) {
|
|
184
|
+
const x = origContext(r);
|
|
185
|
+
if (x > peak)
|
|
186
|
+
peak = x;
|
|
187
|
+
const rate = ruleRate(x);
|
|
188
|
+
const c = rowCost(r);
|
|
189
|
+
actual += c;
|
|
190
|
+
saved += rate.screen * c;
|
|
191
|
+
rawSaved += rate.raw * c;
|
|
192
|
+
if (rate.note)
|
|
193
|
+
notes[rate.note] = (notes[rate.note] ?? 0) + 1;
|
|
194
|
+
}
|
|
195
|
+
return { actual, saved, rawSaved, peak, requests: rows.length, notes };
|
|
196
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Did a Claude Code transcript already run through Mnemom? A session is GOVERNED — and
|
|
3
|
+
* left out of the first-run look — if ANY signal fires. When in doubt, exclude.
|
|
4
|
+
*
|
|
5
|
+
* registry_cc_id the transcript's session id is a `cc_session_id` in the launch
|
|
6
|
+
* ledger (~/.mnemom/sessions.jsonl, lib/agent-ledger.ts)
|
|
7
|
+
* registry_conv_id … or a ledger `conversation_id`
|
|
8
|
+
* registry_window its first event is within [launch-60s, launch+10min] of a ledger
|
|
9
|
+
* launch that recorded no cc_session_id (--server, older rows)
|
|
10
|
+
* rc_proxy_window the same window match against a Remote Control proxy log
|
|
11
|
+
* text_marker the bytes carry a gateway marker ('[mnemom', 'x-mnemom', 'covers=',
|
|
12
|
+
* …). Also fires on sessions that merely DISCUSS Mnemom: over-excludes,
|
|
13
|
+
* the safe direction
|
|
14
|
+
* settings_env a settings file (global or the session cwd's) or the transcript
|
|
15
|
+
* text points ANTHROPIC_BASE_URL / HTTPS_PROXY at a mnemom host or a
|
|
16
|
+
* localhost proxy
|
|
17
|
+
* agent_sdk_host entrypoint sdk-py / sdk-ts: an Agent-SDK host can route through
|
|
18
|
+
* the gateway with env no transcript records
|
|
19
|
+
* (a session is governed if its top transcript OR any subagent transcript fires)
|
|
20
|
+
*
|
|
21
|
+
* Read-only on ~/.claude and ~/.mnemom. No network.
|
|
22
|
+
*/
|
|
23
|
+
export declare const AGENT_SDK_ENTRYPOINTS: Set<string>;
|
|
24
|
+
/** Lowercase byte markers. Candidate lines are found by the case-variant probes below. */
|
|
25
|
+
export declare const TEXT_MARKERS: string[];
|
|
26
|
+
export declare function lineHasMarker(line: Buffer): boolean;
|
|
27
|
+
export declare function lineHasEnv(line: Buffer): boolean;
|
|
28
|
+
export interface FileHead {
|
|
29
|
+
firstTs: number | null;
|
|
30
|
+
cwd: string | null;
|
|
31
|
+
sessionId: string | null;
|
|
32
|
+
entrypoint: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface FileResult extends FileHead {
|
|
35
|
+
signals: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface GovernanceInputs {
|
|
38
|
+
home?: string;
|
|
39
|
+
/** Directory holding the launch ledger (MNEMOM_DIR). */
|
|
40
|
+
mnemomDir?: string;
|
|
41
|
+
tmp?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Session id of a transcript path: its own uuid, or the parent's for a subagent file. */
|
|
44
|
+
export declare function sessionIdFor(path: string): string;
|
|
45
|
+
export declare class Governance {
|
|
46
|
+
readonly ccIds: Set<string>;
|
|
47
|
+
readonly convIds: Set<string>;
|
|
48
|
+
readonly windows: Array<["registry_window" | "rc_proxy_window", number]>;
|
|
49
|
+
readonly globalSettingsEnv: boolean;
|
|
50
|
+
registryRows: number;
|
|
51
|
+
private readonly cwdCache;
|
|
52
|
+
constructor(inp?: GovernanceInputs);
|
|
53
|
+
private cwdEnv;
|
|
54
|
+
/** The first records of a file: first timestamp, cwd, session id, entrypoint. */
|
|
55
|
+
head(path: string, maxBytes?: number): FileHead;
|
|
56
|
+
/** Signals that follow from a file's head records alone. */
|
|
57
|
+
private headSignals;
|
|
58
|
+
/**
|
|
59
|
+
* Classify one transcript. The cheap signals (ledger, head records, settings, windows)
|
|
60
|
+
* are checked first; the full byte scan for markers runs only when none fired (the
|
|
61
|
+
* session is excluded either way, so the scan would change nothing). The full scan
|
|
62
|
+
* also completes head fields that a large first record pushed past the head read.
|
|
63
|
+
*/
|
|
64
|
+
classify(path: string): FileResult;
|
|
65
|
+
}
|