@mnemom/mnemom 0.17.15 → 0.17.16
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/README.md +31 -0
- package/dist/commands/agent-invite.d.ts +17 -2
- package/dist/commands/agent-invite.js +48 -5
- package/dist/commands/agent-sessions.d.ts +56 -1
- package/dist/commands/agent-sessions.js +196 -26
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +24 -6
- package/dist/index.js +21 -2
- package/dist/lib/agent.d.ts +56 -1
- package/dist/lib/agent.js +60 -6
- package/dist/lib/api.d.ts +18 -0
- package/dist/lib/experimental.d.ts +1 -1
- package/dist/lib/experimental.js +12 -0
- package/dist/lib/statusline-bin.js +2 -1
- package/dist/lib/statusline.d.ts +46 -1
- package/dist/lib/statusline.js +136 -33
- package/package.json +2 -2
package/dist/commands/agent.js
CHANGED
|
@@ -31,7 +31,7 @@ import { randomUUID } from "node:crypto";
|
|
|
31
31
|
import { tmpdir, homedir, hostname, userInfo } from "node:os";
|
|
32
32
|
import { basename, join, dirname } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
|
-
import { statuslineLaunch } from "../lib/statusline.js";
|
|
34
|
+
import { MEASURED_ENV, statuslineLaunch } from "../lib/statusline.js";
|
|
35
35
|
import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
|
|
36
36
|
import { createApiKey } from "../lib/api.js";
|
|
37
37
|
import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
|
|
@@ -46,7 +46,7 @@ import { authConfigured, chooseAuth, detectClaudeLogin, readClaudeLoginState, }
|
|
|
46
46
|
import { requireConsoleKey, readConsoleKey } from "../lib/console-key.js";
|
|
47
47
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
48
48
|
import { defaultAgentSlug, ensureClaimedServedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
49
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
49
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, assertCompactAtCompatible, assertOffProfileCompatible, compactAtWindowWarning, describeCompactAt, launchWindow, resolveCompactAt, applyGatewayHintHeaders, resolveCompactionIntercept, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, resolveUsageSavings, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, applyToolSearchDefault, DISABLE_1M_ENV, readCcSettings, resolveWindowModel, resolveAnthropicDoor, resolveLaunchModel, resolveConversationId, resolveGatewayHost, singleCellError, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, resumeCommand, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
50
50
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
51
51
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
52
52
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
@@ -176,7 +176,9 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
176
176
|
budget: options.budget,
|
|
177
177
|
maxTurns: options.maxTurns,
|
|
178
178
|
stall: options.stall,
|
|
179
|
+
mode: options.mode,
|
|
179
180
|
implicit: options.implicit,
|
|
181
|
+
leadDirected: options.leadDirected,
|
|
180
182
|
});
|
|
181
183
|
// ── implicit goal mode (no contract; the gateway infers the goal from the user's
|
|
182
184
|
// messages) — the guardrail ceilings ride on the mode header instead ──
|
|
@@ -232,17 +234,24 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
232
234
|
compactAt: options.compactAt,
|
|
233
235
|
});
|
|
234
236
|
const experimental = options.experimental ?? [];
|
|
237
|
+
// Decided from the REQUESTED CLI: the header is built before the executable is resolved.
|
|
238
|
+
const usageSavings = resolveUsageSavings({
|
|
239
|
+
experimental,
|
|
240
|
+
cliName: basename(requestedCli(options.cli, env)),
|
|
241
|
+
});
|
|
235
242
|
const sessionConfig = buildSessionConfig({
|
|
236
243
|
profile: contextProfile,
|
|
237
244
|
contextHandoff,
|
|
238
245
|
compactionIntercept,
|
|
239
246
|
compactAt,
|
|
247
|
+
usageSavings,
|
|
240
248
|
});
|
|
241
249
|
let sessionConfigB64 = buildSessionConfigB64({
|
|
242
250
|
profile: contextProfile,
|
|
243
251
|
contextHandoff,
|
|
244
252
|
compactionIntercept,
|
|
245
253
|
compactAt,
|
|
254
|
+
usageSavings,
|
|
246
255
|
experimental,
|
|
247
256
|
});
|
|
248
257
|
const expNotice = experimentalNotice(experimental);
|
|
@@ -269,7 +278,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
269
278
|
if (options.dryRun) {
|
|
270
279
|
const planCli = cli ?? {
|
|
271
280
|
bin: "(not found — pass --cli <path> or `mnemom agent config set cli <path>`)",
|
|
272
|
-
name: basename((options.cli
|
|
281
|
+
name: basename(requestedCli(options.cli, env)),
|
|
273
282
|
};
|
|
274
283
|
// A defaulted slug is `agent-<handle>` from the login (resolved for real after
|
|
275
284
|
// sign-in, below). Signed in: show that name. Signed out, or no usable handle:
|
|
@@ -475,6 +484,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
475
484
|
contextHandoff,
|
|
476
485
|
compactionIntercept,
|
|
477
486
|
compactAt,
|
|
487
|
+
usageSavings,
|
|
478
488
|
experimental,
|
|
479
489
|
});
|
|
480
490
|
}
|
|
@@ -571,6 +581,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
571
581
|
envelope,
|
|
572
582
|
goalMode,
|
|
573
583
|
sessionConfigB64,
|
|
584
|
+
usageSavings,
|
|
574
585
|
options: launchOptions,
|
|
575
586
|
yolo,
|
|
576
587
|
contextHint,
|
|
@@ -923,6 +934,10 @@ function storeKey(key) {
|
|
|
923
934
|
// best effort — the launch proceeds with the in-memory key either way
|
|
924
935
|
}
|
|
925
936
|
}
|
|
937
|
+
/** The CLI asked for — `--cli`, else MNEMOM_AGENT_CLI, else "claude" — before any lookup. */
|
|
938
|
+
export function requestedCli(cliOpt, env) {
|
|
939
|
+
return (cliOpt || env.MNEMOM_AGENT_CLI || "claude").trim();
|
|
940
|
+
}
|
|
926
941
|
/**
|
|
927
942
|
* Resolve which coding-agent CLI to launch. `--cli` (or MNEMOM_AGENT_CLI) is a
|
|
928
943
|
* name or a full path; default "claude". A value with a path separator is used
|
|
@@ -932,7 +947,7 @@ function storeKey(key) {
|
|
|
932
947
|
* a non-claude executable is launched through it with a warning.
|
|
933
948
|
*/
|
|
934
949
|
export function resolveCliBin(cliOpt, env) {
|
|
935
|
-
const requested = (cliOpt
|
|
950
|
+
const requested = requestedCli(cliOpt, env);
|
|
936
951
|
const name = basename(requested);
|
|
937
952
|
if (requested.includes("/")) {
|
|
938
953
|
if (existsSync(requested))
|
|
@@ -985,7 +1000,7 @@ export /**
|
|
|
985
1000
|
* so they never retype it). A non-interactive session re-throws the clear,
|
|
986
1001
|
* actionable error rather than hanging on a prompt.
|
|
987
1002
|
*/ async function promptForCliPath(cliOpt, env, origErr) {
|
|
988
|
-
const requested = (cliOpt
|
|
1003
|
+
const requested = requestedCli(cliOpt, env);
|
|
989
1004
|
if (!isInteractive()) {
|
|
990
1005
|
throw origErr ?? new Error(`mnemom agent: coding-agent CLI '${requested}' not found.`);
|
|
991
1006
|
}
|
|
@@ -1121,7 +1136,9 @@ function launchTerminal(ctx) {
|
|
|
1121
1136
|
if (ctx.options.slack)
|
|
1122
1137
|
args.push(...slackChannelArgs(sessionName));
|
|
1123
1138
|
// The status-bar savings counter (lib/statusline.ts). Claude Code only; the
|
|
1124
|
-
// user's own statusLine keeps running in front of it.
|
|
1139
|
+
// user's own statusLine keeps running in front of it. Measured mode is this launch's
|
|
1140
|
+
// decision alone: a value inherited from an outer `mnemom agent` session never rides along.
|
|
1141
|
+
Reflect.deleteProperty(childEnv, MEASURED_ENV);
|
|
1125
1142
|
if (ctx.cli.name === "claude") {
|
|
1126
1143
|
const sl = statuslineLaunch({
|
|
1127
1144
|
passthrough: ctx.options.passthrough ?? [],
|
|
@@ -1129,6 +1146,7 @@ function launchTerminal(ctx) {
|
|
|
1129
1146
|
cwd: process.cwd(),
|
|
1130
1147
|
node: process.execPath,
|
|
1131
1148
|
bin: join(dirname(fileURLToPath(import.meta.url)), "..", "lib", "statusline-bin.js"),
|
|
1149
|
+
measured: ctx.usageSavings,
|
|
1132
1150
|
});
|
|
1133
1151
|
if (sl) {
|
|
1134
1152
|
args.push(...sl.args);
|
package/dist/index.js
CHANGED
|
@@ -150,7 +150,12 @@ const agentCmd = program
|
|
|
150
150
|
.option("--budget <usd>", "Guardrail: spend ceiling in USD (positive number)")
|
|
151
151
|
.option("--max-turns <n>", "Guardrail: turn ceiling (positive integer)")
|
|
152
152
|
.option("--stall <n>", "Guardrail: consecutive no-write turns (positive integer)")
|
|
153
|
-
.
|
|
153
|
+
.option("--mode <nudge|enforce>", "Sealed contract mode (MNE-8199; default nudge). Under enforce, a crossed turn/budget/time " +
|
|
154
|
+
"ceiling REFUSES the next request on this conversation until the owner raises it via a " +
|
|
155
|
+
"contract update — under nudge the ceiling only nudges, unchanged. Requires --goal.")
|
|
156
|
+
.option("--lead-directed", "Seal the standard lead-directed requirement: own-branch push and draft/ready PR creation, " +
|
|
157
|
+
"from a git worktree, on the lead's direction — not merging. Requires --goal; idempotent.")
|
|
158
|
+
.addOption(new Option("--implicit", "EXPERIMENTAL (`mnemom experimental enable implicit`): implicit goal alignment — the gateway infers the goal from your messages and revises it as you steer (the default while the feature is on; not combinable with --goal/--requirement/--allow/--forbid/--goal-id/--mode/--lead-directed; the guardrail ceilings still apply)").hideHelp(hiddenUnless("implicit")))
|
|
154
159
|
.option("--no-implicit", "Turn implicit goal alignment OFF for this launch (overrides launcher.toml goal_mode = implicit)")
|
|
155
160
|
.option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
|
|
156
161
|
.option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
|
|
@@ -345,9 +350,11 @@ const agentCmd = program
|
|
|
345
350
|
forbid: opts.forbid,
|
|
346
351
|
goalId: opts.goalId,
|
|
347
352
|
implicit,
|
|
353
|
+
leadDirected: opts.leadDirected,
|
|
348
354
|
budget: opts.budget ?? (contractIntended ? numToStr(cfg.guardrails?.budgetUsd) : undefined),
|
|
349
355
|
maxTurns: opts.maxTurns ?? (contractIntended ? numToStr(cfg.guardrails?.maxTurns) : undefined),
|
|
350
356
|
stall: opts.stall ?? (contractIntended ? numToStr(cfg.guardrails?.stallTurns) : undefined),
|
|
357
|
+
mode: opts.mode,
|
|
351
358
|
agent: parentOpts.agent ?? cfg.agent,
|
|
352
359
|
conversationId: opts.conversationId,
|
|
353
360
|
cli: opts.cli,
|
|
@@ -878,9 +885,21 @@ agentCmd
|
|
|
878
885
|
.description("Show one session — the local launch record merged with the gateway's live goal state (sealed contract, latest verdict, guardrails). " +
|
|
879
886
|
"`ref` is a session name (newest match wins) or a unique conversation-id prefix; omitted = the newest session")
|
|
880
887
|
.option("--json", "Output the merged view as JSON (each section tagged with its source)")
|
|
888
|
+
.option("--follow", "After the snapshot, keep watching and print each new verdict as it lands (Ctrl-C to stop)")
|
|
889
|
+
.option("--follow-interval <seconds>", "Poll interval for --follow, in seconds (default 3; also settable via MNEMOM_AGENT_FOLLOW_INTERVAL)")
|
|
881
890
|
.action(async (ref, opts) => {
|
|
882
891
|
try {
|
|
883
|
-
|
|
892
|
+
let followIntervalMs;
|
|
893
|
+
if (opts.followInterval !== undefined) {
|
|
894
|
+
const seconds = Number(opts.followInterval);
|
|
895
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
896
|
+
console.error("Error: --follow-interval must be a positive number of seconds.");
|
|
897
|
+
process.exit(1);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
followIntervalMs = seconds * 1000;
|
|
901
|
+
}
|
|
902
|
+
await agentShowCommand(ref, { json: opts.json, follow: opts.follow, followIntervalMs }, liveSessionDeps);
|
|
884
903
|
}
|
|
885
904
|
catch (error) {
|
|
886
905
|
console.error("Error:", error instanceof Error ? error.message : error);
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -39,6 +39,15 @@ export interface SealedContract {
|
|
|
39
39
|
max_turns?: number;
|
|
40
40
|
budget_usd?: number;
|
|
41
41
|
stall_turns?: number;
|
|
42
|
+
/**
|
|
43
|
+
* `nudge` | `enforce` (MNE-8199, design D9/R9): under `enforce` a crossed turn/budget/time
|
|
44
|
+
* ceiling refuses the gateway's NEXT request on this conversation rather than only
|
|
45
|
+
* nudging. ALWAYS present once a contract is sealed (never omitted) — the gateway's
|
|
46
|
+
* `parseContractHeader` defaults an absent/invalid value to `"nudge"` unconditionally
|
|
47
|
+
* before hashing, so the CLI must set it here too (defaulting to `"nudge"`) or the sha
|
|
48
|
+
* printed here would disagree with the one the gateway seals (the MNE-7649 class of bug).
|
|
49
|
+
*/
|
|
50
|
+
mode?: "nudge" | "enforce";
|
|
42
51
|
}
|
|
43
52
|
/** The raw contract/guardrail flags as commander hands them to the command. */
|
|
44
53
|
export interface ContractFlags {
|
|
@@ -50,6 +59,8 @@ export interface ContractFlags {
|
|
|
50
59
|
maxTurns?: string | number;
|
|
51
60
|
budget?: string | number;
|
|
52
61
|
stall?: string | number;
|
|
62
|
+
/** `--mode nudge|enforce` (MNE-8199) — validated and defaulted in `assembleContract`. */
|
|
63
|
+
mode?: string;
|
|
53
64
|
/**
|
|
54
65
|
* `--implicit`: goal alignment WITHOUT a sealed contract. No `x-mnemom-contract`
|
|
55
66
|
* is sent; the gateway infers the goal from the user's own messages (revision 1
|
|
@@ -58,7 +69,26 @@ export interface ContractFlags {
|
|
|
58
69
|
* of inside a contract. Cannot be combined with the sealed-contract flags.
|
|
59
70
|
*/
|
|
60
71
|
implicit?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* `--lead-directed`: append `LEAD_DIRECTED_REQUIREMENT` to the sealed contract's
|
|
74
|
+
* `requirements` (MNE-8152, design `mnemom-agent-wave` D4). A lead-directed agent
|
|
75
|
+
* (e.g. an ADW lane lead) legitimately pushes its own branches and opens draft/ready
|
|
76
|
+
* PRs; under the sealed framing the judge reads only `goal`/`requirements`/fences, so
|
|
77
|
+
* without this line a push/PR step is flagged `violation` ("an effect of this step is
|
|
78
|
+
* not allowed by the requirements"). The mechanism is a requirement line, not a judge
|
|
79
|
+
* input — sealed mode takes no in-session steering, so the lead's direction must
|
|
80
|
+
* already be IN the seal. Idempotent: appended at most once, whether the flag is
|
|
81
|
+
* given once or the requirement text is already present (e.g. supplied verbatim via
|
|
82
|
+
* `--requirement`).
|
|
83
|
+
*/
|
|
84
|
+
leadDirected?: boolean;
|
|
61
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The standard requirement line `--lead-directed` seals: own-branch push and draft/ready
|
|
88
|
+
* PR creation, from a git worktree, on the lead's direction — merging is explicitly out.
|
|
89
|
+
* A single constant so every launch that opts in seals byte-identical wording (MNE-8152).
|
|
90
|
+
*/
|
|
91
|
+
export declare const LEAD_DIRECTED_REQUIREMENT: string;
|
|
62
92
|
/** The three launch shapes the command supports (see commands/code.ts). */
|
|
63
93
|
export type LaunchShape = "terminal" | "remote-control" | "server";
|
|
64
94
|
/**
|
|
@@ -325,6 +355,15 @@ export declare const CONTEXT_SIZE_POLICY_KEY = "CONTEXT_RUNTIME_SIZE_POLICY";
|
|
|
325
355
|
*/
|
|
326
356
|
export declare const USAGE_REAL_SIZE_KEY = "GATEWAY_USAGE_REAL_SIZE";
|
|
327
357
|
export declare const COMPACTION_INTERCEPT_KEY = "GATEWAY_COMPACTION_INTERCEPT";
|
|
358
|
+
/**
|
|
359
|
+
* The gateway env key that has it measure each request's saving for a single session and put
|
|
360
|
+
* it in the client-facing usage (`mnemom_savings` in the read `usage.iterations` entry), where
|
|
361
|
+
* the status bar reads it from Claude Code's transcript. Default OFF on the gateway; sent `"on"`
|
|
362
|
+
* only while the `measured-savings` experimental feature is on and the launched CLI is Claude
|
|
363
|
+
* Code (`resolveUsageSavings`). Independent of folding, so every profile sends it — `off` too,
|
|
364
|
+
* where the measured saving is honestly $0.00.
|
|
365
|
+
*/
|
|
366
|
+
export declare const USAGE_SAVINGS_KEY = "GATEWAY_USAGE_SAVINGS";
|
|
328
367
|
/**
|
|
329
368
|
* The named presets for how the gateway folds a session's context (`--context-profile`,
|
|
330
369
|
* launcher.toml `context_profile`, `MNEMOM_AGENT_CONTEXT_PROFILE`). Choosing NONE sends no fold
|
|
@@ -607,6 +646,7 @@ export declare function resolveContextProfile(opts: {
|
|
|
607
646
|
* (`CONTEXT_RUNTIME_ENABLED: "false"` + real-size and intercept `"off"` for `off`; the three fold-tuning keys + size policy off for
|
|
608
647
|
* `aggressive`; size policy off for `balanced`; size policy banded for `adaptive`; nothing when
|
|
609
648
|
* no profile was chosen), then `CONTEXT_HANDOFF_ENABLED: "true"` when context handoff is ON, then
|
|
649
|
+
* `GATEWAY_USAGE_SAVINGS: "on"` when measured savings is ON (under every profile), then
|
|
610
650
|
* `GATEWAY_USAGE_REAL_SIZE: "on"` + `GATEWAY_COMPACTION_INTERCEPT: "on"` when EITHER the
|
|
611
651
|
* `compaction_intercept` setting is ON OR `--compact-at` arms a target below 1M (the target
|
|
612
652
|
* cannot be honored without the intercept), then `GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS: "<N>"`
|
|
@@ -623,6 +663,8 @@ export declare function buildSessionConfig(opts: {
|
|
|
623
663
|
compactionIntercept?: boolean;
|
|
624
664
|
/** `resolveCompactAt` output: `null` (or absent) ⇒ no target, send nothing new. */
|
|
625
665
|
compactAt?: number | null;
|
|
666
|
+
/** `resolveUsageSavings` output: true ⇒ the gateway measures the session's saving. */
|
|
667
|
+
usageSavings?: boolean;
|
|
626
668
|
}): Record<string, string>;
|
|
627
669
|
/**
|
|
628
670
|
* The `x-mnemom-session-config` payload: `{ v: 1, config, experimental? }`. The active
|
|
@@ -634,6 +676,7 @@ export declare function buildSessionConfigPayload(opts: {
|
|
|
634
676
|
contextHandoff: boolean;
|
|
635
677
|
compactionIntercept?: boolean;
|
|
636
678
|
compactAt?: number | null;
|
|
679
|
+
usageSavings?: boolean;
|
|
637
680
|
experimental?: readonly string[];
|
|
638
681
|
}): {
|
|
639
682
|
v: 1;
|
|
@@ -656,7 +699,8 @@ export declare function buildSessionConfigPayload(opts: {
|
|
|
656
699
|
* - `contextHandoff: true` → `CONTEXT_HANDOFF_ENABLED: "true"` (the `context_handoff`
|
|
657
700
|
* setting) — the SAME header the `handoff` MCP tool's own call to
|
|
658
701
|
* `/mnemom/context/handoff` must also send, since the gateway resolves the flag
|
|
659
|
-
* per request (see `commands/agent.ts` handoff MCP wiring)
|
|
702
|
+
* per request (see `commands/agent.ts` handoff MCP wiring);
|
|
703
|
+
* - `usageSavings: true` → `GATEWAY_USAGE_SAVINGS: "on"` (`resolveUsageSavings`).
|
|
660
704
|
* base64url so it survives the gateway's inbound strip.
|
|
661
705
|
*/
|
|
662
706
|
export declare function buildSessionConfigB64(opts: {
|
|
@@ -664,6 +708,7 @@ export declare function buildSessionConfigB64(opts: {
|
|
|
664
708
|
contextHandoff: boolean;
|
|
665
709
|
compactionIntercept?: boolean;
|
|
666
710
|
compactAt?: number | null;
|
|
711
|
+
usageSavings?: boolean;
|
|
667
712
|
experimental?: readonly string[];
|
|
668
713
|
}): string | undefined;
|
|
669
714
|
/**
|
|
@@ -689,6 +734,16 @@ export declare function resolveCompactionIntercept(opts: {
|
|
|
689
734
|
compactionIntercept?: boolean;
|
|
690
735
|
env?: NodeJS.ProcessEnv;
|
|
691
736
|
}): boolean;
|
|
737
|
+
/**
|
|
738
|
+
* Does the gateway measure this launch's saving (`GATEWAY_USAGE_SAVINGS` in the header, and the
|
|
739
|
+
* status bar in measured mode)? Only with the `measured-savings` experimental feature on, and
|
|
740
|
+
* only for Claude Code — the one CLI whose status bar reads the measurement. No flag or
|
|
741
|
+
* launcher.toml key: `mnemom experimental enable measured-savings` is the switch.
|
|
742
|
+
*/
|
|
743
|
+
export declare function resolveUsageSavings(opts: {
|
|
744
|
+
experimental: readonly string[];
|
|
745
|
+
cliName: string;
|
|
746
|
+
}): boolean;
|
|
692
747
|
/** The name the `handoff` MCP tool server is registered under in `--mcp-config`. */
|
|
693
748
|
export declare const HANDOFF_MCP_SERVER_NAME = "mnemom-handoff";
|
|
694
749
|
/**
|
package/dist/lib/agent.js
CHANGED
|
@@ -25,6 +25,13 @@ import { join } from "node:path";
|
|
|
25
25
|
import { getGatewayUrl } from "./config.js";
|
|
26
26
|
/** The us-2/prod gateway host. The default door — never any other cell silently. */
|
|
27
27
|
export const PROD_GATEWAY_HOST = "https://gateway.mnemom.ai";
|
|
28
|
+
/**
|
|
29
|
+
* The standard requirement line `--lead-directed` seals: own-branch push and draft/ready
|
|
30
|
+
* PR creation, from a git worktree, on the lead's direction — merging is explicitly out.
|
|
31
|
+
* A single constant so every launch that opts in seals byte-identical wording (MNE-8152).
|
|
32
|
+
*/
|
|
33
|
+
export const LEAD_DIRECTED_REQUIREMENT = "On the lead's direction, this session may push its own branches and open draft or " +
|
|
34
|
+
"ready pull requests, from a git worktree, never the primary checkout; it may not merge them.";
|
|
28
35
|
/**
|
|
29
36
|
* Resolve the gateway HOST (scheme + host, no path). An explicit, non-empty
|
|
30
37
|
* `MNEMOM_AGENT_GATEWAY` wins verbatim; otherwise the gateway of the cell the rest
|
|
@@ -381,7 +388,9 @@ export function contractFlagsPresent(flags) {
|
|
|
381
388
|
hasText(flags.goalId) ||
|
|
382
389
|
flags.maxTurns !== undefined ||
|
|
383
390
|
flags.budget !== undefined ||
|
|
384
|
-
flags.stall !== undefined
|
|
391
|
+
flags.stall !== undefined ||
|
|
392
|
+
hasText(flags.mode) ||
|
|
393
|
+
flags.leadDirected === true);
|
|
385
394
|
}
|
|
386
395
|
function hasText(v) {
|
|
387
396
|
return typeof v === "string" && v.trim().length > 0;
|
|
@@ -413,11 +422,18 @@ export function assembleContract(flags) {
|
|
|
413
422
|
const statement = (flags.goal ?? "").trim();
|
|
414
423
|
if (!statement) {
|
|
415
424
|
throw new Error("--goal is required when any contract flag " +
|
|
416
|
-
"(--requirement/--allow/--forbid/--goal-id/--budget/--max-turns/--stall) is given.");
|
|
425
|
+
"(--requirement/--allow/--forbid/--goal-id/--budget/--max-turns/--stall/--mode/--lead-directed) is given.");
|
|
426
|
+
}
|
|
427
|
+
const requirements = cleanList(flags.requirement);
|
|
428
|
+
// Idempotent: append the standard line only when it is not already present, whether
|
|
429
|
+
// because `--lead-directed` was given more than once or because the exact wording
|
|
430
|
+
// was already supplied verbatim via `--requirement`.
|
|
431
|
+
if (flags.leadDirected === true && !requirements.includes(LEAD_DIRECTED_REQUIREMENT)) {
|
|
432
|
+
requirements.push(LEAD_DIRECTED_REQUIREMENT);
|
|
417
433
|
}
|
|
418
434
|
const contract = {
|
|
419
435
|
statement,
|
|
420
|
-
requirements
|
|
436
|
+
requirements,
|
|
421
437
|
allowed_paths: cleanList(flags.allow),
|
|
422
438
|
forbidden_paths: cleanList(flags.forbid),
|
|
423
439
|
};
|
|
@@ -442,6 +458,19 @@ export function assembleContract(flags) {
|
|
|
442
458
|
}
|
|
443
459
|
contract.stall_turns = Number(flags.stall);
|
|
444
460
|
}
|
|
461
|
+
// MNE-8199: set only for the non-default "enforce" — the gateway's own canonicalHash
|
|
462
|
+
// folds `mode` into the hash the same way (see its doc comment): a contract that never
|
|
463
|
+
// named `--mode` must keep hashing identically to the byte-identical contract sealed
|
|
464
|
+
// before the field existed, or every live conversation logs `goal_contract_ignored_change`
|
|
465
|
+
// on deploy. Setting a defaulted "nudge" here unconditionally would instead make the SHA
|
|
466
|
+
// this prints disagree with the one the gateway actually seals for the exact same launch —
|
|
467
|
+
// the MNE-7649 class of bug, just reintroduced from the client side.
|
|
468
|
+
const rawMode = (flags.mode ?? "").trim();
|
|
469
|
+
if (rawMode && rawMode !== "nudge" && rawMode !== "enforce") {
|
|
470
|
+
throw new Error(`--mode must be "nudge" or "enforce" (got '${flags.mode}').`);
|
|
471
|
+
}
|
|
472
|
+
if (rawMode === "enforce")
|
|
473
|
+
contract.mode = "enforce";
|
|
445
474
|
const canonical = canonicalJson(contract);
|
|
446
475
|
return {
|
|
447
476
|
contract,
|
|
@@ -487,6 +516,15 @@ export const CONTEXT_SIZE_POLICY_KEY = "CONTEXT_RUNTIME_SIZE_POLICY";
|
|
|
487
516
|
*/
|
|
488
517
|
export const USAGE_REAL_SIZE_KEY = "GATEWAY_USAGE_REAL_SIZE";
|
|
489
518
|
export const COMPACTION_INTERCEPT_KEY = "GATEWAY_COMPACTION_INTERCEPT";
|
|
519
|
+
/**
|
|
520
|
+
* The gateway env key that has it measure each request's saving for a single session and put
|
|
521
|
+
* it in the client-facing usage (`mnemom_savings` in the read `usage.iterations` entry), where
|
|
522
|
+
* the status bar reads it from Claude Code's transcript. Default OFF on the gateway; sent `"on"`
|
|
523
|
+
* only while the `measured-savings` experimental feature is on and the launched CLI is Claude
|
|
524
|
+
* Code (`resolveUsageSavings`). Independent of folding, so every profile sends it — `off` too,
|
|
525
|
+
* where the measured saving is honestly $0.00.
|
|
526
|
+
*/
|
|
527
|
+
export const USAGE_SAVINGS_KEY = "GATEWAY_USAGE_SAVINGS";
|
|
490
528
|
/**
|
|
491
529
|
* The named presets for how the gateway folds a session's context (`--context-profile`,
|
|
492
530
|
* launcher.toml `context_profile`, `MNEMOM_AGENT_CONTEXT_PROFILE`). Choosing NONE sends no fold
|
|
@@ -747,7 +785,7 @@ export function applyGatewayHintHeaders(childEnv) {
|
|
|
747
785
|
}
|
|
748
786
|
return childEnv;
|
|
749
787
|
}
|
|
750
|
-
const SEALED_ONLY_FLAGS = "--goal/--requirement/--allow/--forbid/--goal-id";
|
|
788
|
+
const SEALED_ONLY_FLAGS = "--goal/--requirement/--allow/--forbid/--goal-id/--mode/--lead-directed";
|
|
751
789
|
/**
|
|
752
790
|
* `--implicit` and a sealed contract are mutually exclusive: on the gateway a
|
|
753
791
|
* sealed `x-mnemom-contract` always wins and would silently switch implicit mode
|
|
@@ -759,7 +797,9 @@ function assertImplicitCompatible(flags) {
|
|
|
759
797
|
(flags.requirement?.length ?? 0) > 0 ||
|
|
760
798
|
(flags.allow?.length ?? 0) > 0 ||
|
|
761
799
|
(flags.forbid?.length ?? 0) > 0 ||
|
|
762
|
-
hasText(flags.goalId)
|
|
800
|
+
hasText(flags.goalId) ||
|
|
801
|
+
hasText(flags.mode) ||
|
|
802
|
+
flags.leadDirected === true;
|
|
763
803
|
if (sealed) {
|
|
764
804
|
throw new Error(`--implicit cannot be combined with ${SEALED_ONLY_FLAGS}: a sealed contract always wins on ` +
|
|
765
805
|
"the gateway and would silently switch implicit mode off. Drop --implicit to seal a " +
|
|
@@ -997,6 +1037,7 @@ export function resolveContextProfile(opts) {
|
|
|
997
1037
|
* (`CONTEXT_RUNTIME_ENABLED: "false"` + real-size and intercept `"off"` for `off`; the three fold-tuning keys + size policy off for
|
|
998
1038
|
* `aggressive`; size policy off for `balanced`; size policy banded for `adaptive`; nothing when
|
|
999
1039
|
* no profile was chosen), then `CONTEXT_HANDOFF_ENABLED: "true"` when context handoff is ON, then
|
|
1040
|
+
* `GATEWAY_USAGE_SAVINGS: "on"` when measured savings is ON (under every profile), then
|
|
1000
1041
|
* `GATEWAY_USAGE_REAL_SIZE: "on"` + `GATEWAY_COMPACTION_INTERCEPT: "on"` when EITHER the
|
|
1001
1042
|
* `compaction_intercept` setting is ON OR `--compact-at` arms a target below 1M (the target
|
|
1002
1043
|
* cannot be honored without the intercept), then `GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS: "<N>"`
|
|
@@ -1014,6 +1055,9 @@ export function buildSessionConfig(opts) {
|
|
|
1014
1055
|
}
|
|
1015
1056
|
if (opts.contextHandoff)
|
|
1016
1057
|
config[CONTEXT_HANDOFF_KEY] = "true";
|
|
1058
|
+
// Before the `off` return: measuring is not folding, so an off session sends it too.
|
|
1059
|
+
if (opts.usageSavings)
|
|
1060
|
+
config[USAGE_SAVINGS_KEY] = "on";
|
|
1017
1061
|
const hasCompactionTarget = opts.compactAt !== undefined && opts.compactAt !== null;
|
|
1018
1062
|
// A target needs the compaction intercept + real-size rewrite to arm (#2216) — without them
|
|
1019
1063
|
// the upload-target key is silently inert (compaction-align.ts: "armed only inside the
|
|
@@ -1059,7 +1103,8 @@ export function buildSessionConfigPayload(opts) {
|
|
|
1059
1103
|
* - `contextHandoff: true` → `CONTEXT_HANDOFF_ENABLED: "true"` (the `context_handoff`
|
|
1060
1104
|
* setting) — the SAME header the `handoff` MCP tool's own call to
|
|
1061
1105
|
* `/mnemom/context/handoff` must also send, since the gateway resolves the flag
|
|
1062
|
-
* per request (see `commands/agent.ts` handoff MCP wiring)
|
|
1106
|
+
* per request (see `commands/agent.ts` handoff MCP wiring);
|
|
1107
|
+
* - `usageSavings: true` → `GATEWAY_USAGE_SAVINGS: "on"` (`resolveUsageSavings`).
|
|
1063
1108
|
* base64url so it survives the gateway's inbound strip.
|
|
1064
1109
|
*/
|
|
1065
1110
|
export function buildSessionConfigB64(opts) {
|
|
@@ -1099,6 +1144,15 @@ export function resolveCompactionIntercept(opts) {
|
|
|
1099
1144
|
return false;
|
|
1100
1145
|
return raw === "1" || raw === "true" || raw === "on" || raw === "yes";
|
|
1101
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Does the gateway measure this launch's saving (`GATEWAY_USAGE_SAVINGS` in the header, and the
|
|
1149
|
+
* status bar in measured mode)? Only with the `measured-savings` experimental feature on, and
|
|
1150
|
+
* only for Claude Code — the one CLI whose status bar reads the measurement. No flag or
|
|
1151
|
+
* launcher.toml key: `mnemom experimental enable measured-savings` is the switch.
|
|
1152
|
+
*/
|
|
1153
|
+
export function resolveUsageSavings(opts) {
|
|
1154
|
+
return opts.experimental.includes("measured-savings") && opts.cliName === "claude";
|
|
1155
|
+
}
|
|
1102
1156
|
/** The name the `handoff` MCP tool server is registered under in `--mcp-config`. */
|
|
1103
1157
|
export const HANDOFF_MCP_SERVER_NAME = "mnemom-handoff";
|
|
1104
1158
|
/**
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1289,6 +1289,22 @@ export interface AgentSessionGuardrails {
|
|
|
1289
1289
|
};
|
|
1290
1290
|
} | null;
|
|
1291
1291
|
}
|
|
1292
|
+
/**
|
|
1293
|
+
* One entry of the gateway's verdict trail (R10, MNE-8200) — up to the last 50 judged turns,
|
|
1294
|
+
* oldest first (same order the gateway stores and returns them in). `request_id` is the request
|
|
1295
|
+
* that produced this verdict, or null when the judge ran without one. `fence_breach` is `true`/
|
|
1296
|
+
* `false` for a conclusive mechanical allowed/forbidden-paths check, `null` when the contract has
|
|
1297
|
+
* no fences or the verdict predates the check. This shape is also what MNE-8234 mirrors — keep
|
|
1298
|
+
* the field name and member shape stable.
|
|
1299
|
+
*/
|
|
1300
|
+
export interface AgentSessionVerdictTrailEntry {
|
|
1301
|
+
request_id: string | null;
|
|
1302
|
+
turn: number;
|
|
1303
|
+
verdict: "aligned" | "drifting" | "violation";
|
|
1304
|
+
reason: string;
|
|
1305
|
+
fence_breach: boolean | null;
|
|
1306
|
+
at: string;
|
|
1307
|
+
}
|
|
1292
1308
|
/** `GET /v1/agent/sessions/{conversationId}` 200 body. Nulls mean "nothing stored" — absence is data. */
|
|
1293
1309
|
export interface AgentSessionResponse {
|
|
1294
1310
|
conversation_id: string;
|
|
@@ -1301,6 +1317,8 @@ export interface AgentSessionResponse {
|
|
|
1301
1317
|
verdict: AgentSessionVerdict | null;
|
|
1302
1318
|
turn: number | null;
|
|
1303
1319
|
guardrails: AgentSessionGuardrails | null;
|
|
1320
|
+
/** Absent on an older server (back-compat) — never shown then, never an empty-array assumption. */
|
|
1321
|
+
verdict_trail?: AgentSessionVerdictTrailEntry[];
|
|
1304
1322
|
}
|
|
1305
1323
|
/**
|
|
1306
1324
|
* The live goal state of one `mnemom agent` session, read through mnemom-api
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Everything here is pure or dependency-injected; index.ts and the command
|
|
13
13
|
* modules are the thin drivers.
|
|
14
14
|
*/
|
|
15
|
-
export type ExperimentalId = "context-handoff" | "slack" | "implicit" | "compaction-intercept";
|
|
15
|
+
export type ExperimentalId = "context-handoff" | "slack" | "implicit" | "compaction-intercept" | "measured-savings";
|
|
16
16
|
/**
|
|
17
17
|
* Is a `compact_at` / `MNEMOM_COMPACT_AT` value (trimmed, case-insensitive) an ARMED gateway
|
|
18
18
|
* target, as opposed to "no target" (`""`, `"1m"`, `"off"`)? Deliberately duplicates
|
package/dist/lib/experimental.js
CHANGED
|
@@ -69,6 +69,18 @@ export const EXPERIMENTAL_FEATURES = [
|
|
|
69
69
|
env: ["MNEMOM_AGENT_COMPACTION_INTERCEPT"],
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
// No flag, launcher.toml key or env of its own: `mnemom experimental enable` is the switch.
|
|
74
|
+
id: "measured-savings",
|
|
75
|
+
summary: "The status bar shows the saving the gateway measured on this session instead of the benchmark estimate",
|
|
76
|
+
since: "0.17.16",
|
|
77
|
+
gates: {
|
|
78
|
+
flags: [],
|
|
79
|
+
configKeys: [],
|
|
80
|
+
commands: [],
|
|
81
|
+
env: [],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
72
84
|
];
|
|
73
85
|
export const EXPERIMENTAL_IDS = EXPERIMENTAL_FEATURES.map((f) => f.id);
|
|
74
86
|
export const EXPERIMENTAL_ENV = "MNEMOM_EXPERIMENTAL";
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { INNER_ENV, render } from "./statusline.js";
|
|
7
|
+
import { INNER_ENV, measuredMode, render } from "./statusline.js";
|
|
8
8
|
const chunks = [];
|
|
9
9
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
10
10
|
process.stdin.on("end", () => {
|
|
11
11
|
const line = render(Buffer.concat(chunks).toString("utf8"), {
|
|
12
12
|
stateDir: join(homedir(), ".mnemom", "statusline"),
|
|
13
13
|
inner: process.env[INNER_ENV] || undefined,
|
|
14
|
+
measured: measuredMode(process.env),
|
|
14
15
|
});
|
|
15
16
|
process.stdout.write(`${line}\n`);
|
|
16
17
|
});
|
package/dist/lib/statusline.d.ts
CHANGED
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
* keeps each file's byte offset and each message id's contribution, and a tick reads at
|
|
14
14
|
* most MAX_BYTES_PER_TICK of new bytes (a big resumed session catches up over ticks).
|
|
15
15
|
*
|
|
16
|
+
* Measured mode (the `measured-savings` experimental feature; the launch sets
|
|
17
|
+
* MNEMOM_STATUSLINE_MEASURED=1): the gateway measures each request's saving — the stock
|
|
18
|
+
* request's input cost minus what it billed — and writes it into the response usage as
|
|
19
|
+
* `mnemom_savings` inside a `usage.iterations` entry, the one place Claude Code keeps an
|
|
20
|
+
* unknown usage key in its transcript. The counter sums those per message id
|
|
21
|
+
* ("mnemom saved $X this session", no "~": it is measured) and makes no claim before the
|
|
22
|
+
* first one arrives. Its state lives in its own file (<session>.measured.json), so the two
|
|
23
|
+
* modes never share a count.
|
|
24
|
+
*
|
|
16
25
|
* A user's own statusLine is never replaced: the launch passes it in
|
|
17
26
|
* MNEMOM_STATUSLINE_INNER, it runs with the same stdin, and our segment is appended.
|
|
18
27
|
* Read-only on every transcript; no network. It lives outside lib/first-run because it runs
|
|
@@ -24,6 +33,8 @@ export declare const MAX_BYTES_PER_TICK: number;
|
|
|
24
33
|
export declare const INNER_ENV = "MNEMOM_STATUSLINE_INNER";
|
|
25
34
|
/** Set to 1 to launch without the counter. */
|
|
26
35
|
export declare const NO_STATUSLINE_ENV = "MNEMOM_AGENT_NO_STATUSLINE";
|
|
36
|
+
/** The launcher sets this to 1 when the gateway measures the session's saving. */
|
|
37
|
+
export declare const MEASURED_ENV = "MNEMOM_STATUSLINE_MEASURED";
|
|
27
38
|
export declare const INNER_TIMEOUT_MS = 2000;
|
|
28
39
|
export interface SessionState {
|
|
29
40
|
v: 1;
|
|
@@ -35,6 +46,17 @@ export interface SessionState {
|
|
|
35
46
|
saved: number;
|
|
36
47
|
}
|
|
37
48
|
export declare function freshState(): SessionState;
|
|
49
|
+
/** Measured mode's state (<session>.measured.json). */
|
|
50
|
+
export interface MeasuredState {
|
|
51
|
+
v: 1;
|
|
52
|
+
files: Record<string, number>;
|
|
53
|
+
/** Per message id: the gateway's measured saving (USD). A re-written id replaces its old share. */
|
|
54
|
+
ids: Record<string, number>;
|
|
55
|
+
saved: number;
|
|
56
|
+
/** Has any record carried a measurement yet? Until then the segment makes no claim. */
|
|
57
|
+
seen: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare function freshMeasuredState(): MeasuredState;
|
|
38
60
|
/** The main transcript, then its subagent transcripts (sorted). */
|
|
39
61
|
export declare function sessionFiles(transcriptPath: string): string[];
|
|
40
62
|
/**
|
|
@@ -47,17 +69,38 @@ export declare function readNewLines(path: string, offset: number, maxBytes: num
|
|
|
47
69
|
} | null;
|
|
48
70
|
/** Read what is new in the session's files into `st`. A file that shrank resets the count. */
|
|
49
71
|
export declare function update(st: SessionState, files: string[], maxBytes?: number): SessionState;
|
|
72
|
+
/** A `usage.iterations` entry's measured saving (`mnemom_savings` v1), or null. */
|
|
73
|
+
export declare function measuredSaving(entry: unknown): number | null;
|
|
74
|
+
/**
|
|
75
|
+
* One transcript line → the gateway's measured saving for its request, or null (not an
|
|
76
|
+
* assistant record, or no measurement in its `message.usage.iterations`). `id` is the message
|
|
77
|
+
* id (or record uuid); null when the record carries neither. Claude Code writes one record per
|
|
78
|
+
* content block, all with the same id and usage, so the id is what dedupes them.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseMeasuredLine(line: Buffer): {
|
|
81
|
+
id: string | null;
|
|
82
|
+
saved: number;
|
|
83
|
+
} | null;
|
|
84
|
+
/** Measured mode's `update`: sums each request's measured saving, replace-by-id. */
|
|
85
|
+
export declare function updateMeasured(st: MeasuredState, files: string[], maxBytes?: number): MeasuredState;
|
|
50
86
|
/** The segment, rounded DOWN to the cent and never below $0.00. */
|
|
51
87
|
export declare function segment(saved: number): string;
|
|
88
|
+
/** Measured mode's segment: no claim until the gateway has measured something; no "~". */
|
|
89
|
+
export declare function measuredSegment(st: MeasuredState): string;
|
|
52
90
|
export declare function loadState(path: string): SessionState;
|
|
53
|
-
export declare function
|
|
91
|
+
export declare function loadMeasuredState(path: string): MeasuredState;
|
|
92
|
+
export declare function saveState(path: string, st: SessionState | MeasuredState): void;
|
|
54
93
|
/** Run the user's own statusLine command with the same stdin; its first line, or "". */
|
|
55
94
|
export declare function runInner(command: string, input: string): string;
|
|
56
95
|
export interface RenderDeps {
|
|
57
96
|
stateDir: string;
|
|
58
97
|
inner?: string;
|
|
98
|
+
/** Measured mode (`measuredMode`): the gateway's measured saving, not the estimate. */
|
|
99
|
+
measured?: boolean;
|
|
59
100
|
runInner?: (command: string, input: string) => string;
|
|
60
101
|
}
|
|
102
|
+
/** Is the counter in measured mode? The launch's MEASURED_ENV, exactly "1". */
|
|
103
|
+
export declare function measuredMode(env: NodeJS.ProcessEnv): boolean;
|
|
61
104
|
/** Claude Code's status JSON → the one status line. Never throws. */
|
|
62
105
|
export declare function render(stdin: string, deps: RenderDeps): string;
|
|
63
106
|
export interface UserStatusLine {
|
|
@@ -72,6 +115,7 @@ export declare function userStatusLine(cwd: string, configDir: string): UserStat
|
|
|
72
115
|
/**
|
|
73
116
|
* Launch args + env that add the counter, or null when it should stay off: the opt-out
|
|
74
117
|
* env, or the user passing their own --settings (two would fight over precedence).
|
|
118
|
+
* `measured` puts the counter in measured mode (MEASURED_ENV).
|
|
75
119
|
*/
|
|
76
120
|
export declare function statuslineLaunch(o: {
|
|
77
121
|
passthrough: readonly string[];
|
|
@@ -80,6 +124,7 @@ export declare function statuslineLaunch(o: {
|
|
|
80
124
|
home?: string;
|
|
81
125
|
node: string;
|
|
82
126
|
bin: string;
|
|
127
|
+
measured?: boolean;
|
|
83
128
|
}): {
|
|
84
129
|
args: string[];
|
|
85
130
|
env: Record<string, string>;
|