@compaction/cli 0.1.4 → 0.3.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.
- package/README.md +51 -12
- package/dist/cli/commands/activity.d.ts +8 -0
- package/dist/cli/commands/activity.js +35 -0
- package/dist/cli/commands/apply-context.js +68 -0
- package/dist/cli/commands/billing-delta.js +12 -1
- package/dist/cli/commands/capture-claude-code.d.ts +44 -0
- package/dist/cli/commands/capture-claude-code.js +206 -0
- package/dist/cli/commands/capture.js +283 -1
- package/dist/cli/commands/compact.js +24 -11
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/dev.d.ts +17 -0
- package/dist/cli/commands/dev.js +131 -0
- package/dist/cli/commands/gateway.d.ts +31 -0
- package/dist/cli/commands/gateway.js +312 -0
- package/dist/cli/commands/hooks.d.ts +5 -0
- package/dist/cli/commands/hooks.js +181 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +422 -17
- package/dist/cli/commands/input-compaction-ab.d.ts +2 -0
- package/dist/cli/commands/input-compaction-ab.js +125 -0
- package/dist/cli/commands/optimize-hosted.d.ts +54 -0
- package/dist/cli/commands/optimize-hosted.js +123 -0
- package/dist/cli/commands/optimize.js +8 -0
- package/dist/cli/commands/output-shaping-ab.d.ts +2 -0
- package/dist/cli/commands/output-shaping-ab.js +132 -0
- package/dist/cli/commands/output-shaping.d.ts +7 -0
- package/dist/cli/commands/output-shaping.js +43 -0
- package/dist/cli/commands/policies.d.ts +2 -0
- package/dist/cli/commands/policies.js +80 -0
- package/dist/cli/commands/precall.d.ts +19 -0
- package/dist/cli/commands/precall.js +436 -0
- package/dist/cli/commands/recommend.js +1 -1
- package/dist/cli/commands/run.js +310 -18
- package/dist/cli/commands/upgrade-status.d.ts +42 -0
- package/dist/cli/commands/upgrade-status.js +152 -0
- package/dist/cli/cursor-export-read.d.ts +13 -0
- package/dist/cli/cursor-export-read.js +53 -0
- package/dist/cli/cursor-live-preflight.d.ts +19 -0
- package/dist/cli/cursor-live-preflight.js +46 -0
- package/dist/cli/index.js +39 -2
- package/dist/cli/onboarding/GatewayTui.d.ts +33 -0
- package/dist/cli/onboarding/GatewayTui.js +113 -0
- package/dist/cli/onboarding/model.d.ts +32 -1
- package/dist/cli/onboarding/model.js +27 -2
- package/dist/cli/terminal-logo.d.ts +27 -0
- package/dist/cli/terminal-logo.js +49 -0
- package/dist/core/activity-event.d.ts +107 -0
- package/dist/core/activity-event.js +182 -0
- package/dist/core/activity-store.d.ts +63 -0
- package/dist/core/activity-store.js +254 -0
- package/dist/core/activity-view.d.ts +76 -0
- package/dist/core/activity-view.js +120 -0
- package/dist/core/api-client/index.d.ts +3 -1
- package/dist/core/api-client/index.js +4 -0
- package/dist/core/api-client/payload.d.ts +5 -1
- package/dist/core/api-client/payload.js +8 -6
- package/dist/core/api-client/persisted-config.d.ts +57 -0
- package/dist/core/api-client/persisted-config.js +156 -0
- package/dist/core/api-client/tool.d.ts +29 -0
- package/dist/core/api-client/tool.js +50 -0
- package/dist/core/api-client/types.d.ts +15 -4
- package/dist/core/auto-apply-ask.d.ts +58 -0
- package/dist/core/auto-apply-ask.js +105 -0
- package/dist/core/auto-apply-gates.d.ts +76 -0
- package/dist/core/auto-apply-gates.js +113 -0
- package/dist/core/before-call-activity.d.ts +35 -0
- package/dist/core/before-call-activity.js +103 -0
- package/dist/core/before-call-recovery.d.ts +19 -0
- package/dist/core/before-call-recovery.js +46 -0
- package/dist/core/before-call-stdin.d.ts +59 -0
- package/dist/core/before-call-stdin.js +78 -0
- package/dist/core/before-call.d.ts +149 -0
- package/dist/core/before-call.js +358 -0
- package/dist/core/billing-delta/billing-delta-record.d.ts +28 -0
- package/dist/core/billing-delta/billing-delta-record.js +26 -0
- package/dist/core/capture-record.d.ts +40 -0
- package/dist/core/capture-record.js +55 -0
- package/dist/core/claude-code-before-call.d.ts +37 -0
- package/dist/core/claude-code-before-call.js +120 -0
- package/dist/core/claude-code-connect.d.ts +31 -0
- package/dist/core/claude-code-connect.js +87 -0
- package/dist/core/claude-code-hook-record.d.ts +71 -0
- package/dist/core/claude-code-hook-record.js +0 -0
- package/dist/core/claude-code-hooks.d.ts +77 -0
- package/dist/core/claude-code-hooks.js +141 -0
- package/dist/core/codex-capture.d.ts +45 -0
- package/dist/core/codex-capture.js +204 -0
- package/dist/core/command-runner.js +5 -1
- package/dist/core/compaction-artifacts.d.ts +51 -0
- package/dist/core/compaction-artifacts.js +242 -0
- package/dist/core/compactor.d.ts +9 -0
- package/dist/core/compactor.js +95 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/cross-surface-event.d.ts +306 -0
- package/dist/core/cross-surface-event.js +330 -0
- package/dist/core/cursor-capture.d.ts +54 -0
- package/dist/core/cursor-capture.js +215 -0
- package/dist/core/cursor-preflight-probe.d.ts +8 -0
- package/dist/core/cursor-preflight-probe.js +88 -0
- package/dist/core/cursor-preflight.d.ts +86 -0
- package/dist/core/cursor-preflight.js +126 -0
- package/dist/core/gateway/apply-activation.d.ts +39 -0
- package/dist/core/gateway/apply-activation.js +84 -0
- package/dist/core/gateway/apply-policy.d.ts +64 -0
- package/dist/core/gateway/apply-policy.js +221 -0
- package/dist/core/gateway/apply-receipt.d.ts +36 -0
- package/dist/core/gateway/apply-receipt.js +75 -0
- package/dist/core/gateway/cache-proof.d.ts +45 -0
- package/dist/core/gateway/cache-proof.js +65 -0
- package/dist/core/gateway/configure.d.ts +50 -0
- package/dist/core/gateway/configure.js +169 -0
- package/dist/core/gateway/openai-usage.d.ts +56 -0
- package/dist/core/gateway/openai-usage.js +128 -0
- package/dist/core/gateway/receipt.d.ts +138 -0
- package/dist/core/gateway/receipt.js +120 -0
- package/dist/core/gateway/recovery.d.ts +23 -0
- package/dist/core/gateway/recovery.js +68 -0
- package/dist/core/gateway/server.d.ts +51 -0
- package/dist/core/gateway/server.js +276 -0
- package/dist/core/gateway/status.d.ts +45 -0
- package/dist/core/gateway/status.js +109 -0
- package/dist/core/hook-usage-aggregate.d.ts +47 -0
- package/dist/core/hook-usage-aggregate.js +161 -0
- package/dist/core/input-compaction-ab.d.ts +111 -0
- package/dist/core/input-compaction-ab.js +158 -0
- package/dist/core/local-run-record.d.ts +109 -0
- package/dist/core/local-run-record.js +223 -0
- package/dist/core/output-shaping-ab.d.ts +140 -0
- package/dist/core/output-shaping-ab.js +146 -0
- package/dist/core/output-shaping-attach.d.ts +31 -0
- package/dist/core/output-shaping-attach.js +57 -0
- package/dist/core/output-shaping.d.ts +56 -0
- package/dist/core/output-shaping.js +89 -0
- package/dist/core/policy-middleware.d.ts +121 -0
- package/dist/core/policy-middleware.js +919 -0
- package/dist/core/policy-preferences.d.ts +99 -0
- package/dist/core/policy-preferences.js +232 -0
- package/dist/core/report-generator.d.ts +19 -1
- package/dist/core/report-generator.js +51 -0
- package/dist/core/run-aggregator.d.ts +56 -1
- package/dist/core/run-aggregator.js +93 -2
- package/dist/core/run-flow-report.d.ts +82 -0
- package/dist/core/run-flow-report.js +71 -0
- package/dist/core/safety-report.js +8 -1
- package/dist/core/shim-capture-bridge.d.ts +32 -0
- package/dist/core/shim-capture-bridge.js +88 -0
- package/dist/core/skill-injection-policy.d.ts +72 -0
- package/dist/core/skill-injection-policy.js +183 -0
- package/dist/core/spend-attribution.js +1 -1
- package/dist/core/token-accounting.d.ts +1 -1
- package/dist/core/tool-shim.d.ts +129 -0
- package/dist/core/tool-shim.js +447 -0
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/trace-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +45 -2
- package/dist/core/waste-detector.d.ts +20 -0
- package/dist/core/waste-detector.js +160 -6
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** The two persistent PATH-shim tools this before-call layer covers (Claude Code is deferred). */
|
|
2
|
+
export type BeforeCallTool = "codex" | "cursor";
|
|
3
|
+
/** The deterministic policy this layer applies — exact-duplicate context-block removal. */
|
|
4
|
+
export declare const BEFORE_CALL_POLICY_NAME = "repeated_context_block_dedup";
|
|
5
|
+
/** The honest evidence label for every pre-call figure this layer produces (both tools). */
|
|
6
|
+
export declare const BEFORE_CALL_EVIDENCE_LABEL = "local-estimate (pre-call)";
|
|
7
|
+
/** The honest reduction label — spelled out so no reader upgrades it to a saving/provider figure. */
|
|
8
|
+
export declare const BEFORE_CALL_REDUCTION_LABEL: string;
|
|
9
|
+
/**
|
|
10
|
+
* A duplicate context block must be at least this many characters to be worth recommending removal of.
|
|
11
|
+
* Conservative: tiny repeated fragments (blank lines, short markers) are never flagged — the same
|
|
12
|
+
* spirit as the trace-level min-duplicate thresholds. This is a size floor, NOT a scoring heuristic.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MIN_DUPLICATE_BLOCK_CHARS = 40;
|
|
15
|
+
/**
|
|
16
|
+
* The TOOL-AGNOSTIC result of the deterministic avoidable-context analysis (the pure core reused by
|
|
17
|
+
* every before-call surface — Codex/Cursor argv+stdin, and the Claude Code UserPromptSubmit hook). It
|
|
18
|
+
* carries counts, policy, honest labels, and the compacted input, but NO tool tag: the surface adds its
|
|
19
|
+
* own tag/activity framing. Deterministic + content-free-by-contract (never persist `compacted_input`).
|
|
20
|
+
*/
|
|
21
|
+
export interface AvoidableContextResult {
|
|
22
|
+
/** True only when ≥1 exact-duplicate context block (≥ the size floor) was found in the input. */
|
|
23
|
+
has_avoidable_context: boolean;
|
|
24
|
+
/** The deterministic policy that would act (content-free identifier). */
|
|
25
|
+
policy: string;
|
|
26
|
+
/** LOCAL-ESTIMATE (chars/4) token count of the original input, pre-call. */
|
|
27
|
+
input_tokens_before: number;
|
|
28
|
+
/** LOCAL-ESTIMATE (chars/4) token count of the compacted input, pre-call. */
|
|
29
|
+
input_tokens_after_estimate: number;
|
|
30
|
+
/** `input_tokens_before - input_tokens_after_estimate` — a local-estimate delta, never a saving. */
|
|
31
|
+
reduction_tokens_estimate: number;
|
|
32
|
+
/** The honest reduction label (never billing-confirmed / provider-reported). */
|
|
33
|
+
reduction_label: string;
|
|
34
|
+
/** The honest evidence label for the figures above. */
|
|
35
|
+
evidence_label: string;
|
|
36
|
+
/** Content-free count: total blocks the input was split into. */
|
|
37
|
+
blocks_total: number;
|
|
38
|
+
/** Content-free count: how many duplicate blocks would be removed (0 when none). */
|
|
39
|
+
blocks_removed: number;
|
|
40
|
+
/**
|
|
41
|
+
* The compacted input, produced IN-PROCESS. Equals the original when `has_avoidable_context` is
|
|
42
|
+
* false. Used ONLY if the operator explicitly approves; the original is never mutated here.
|
|
43
|
+
*/
|
|
44
|
+
compacted_input: string;
|
|
45
|
+
}
|
|
46
|
+
/** A tool-tagged before-call recommendation = the tool-agnostic analysis + the surface tag. */
|
|
47
|
+
export interface BeforeCallRecommendation extends AvoidableContextResult {
|
|
48
|
+
tool: BeforeCallTool;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The TOOL-AGNOSTIC pure core: analyze one input for avoidable duplicated context. Deterministic +
|
|
52
|
+
* content-free-by-contract (the caller must not persist `compacted_input`). When the input has fewer
|
|
53
|
+
* than two blocks, or no duplicate block meets the size floor, returns `has_avoidable_context: false`
|
|
54
|
+
* with `compacted_input === input` (honest no-op). Reused by every before-call surface.
|
|
55
|
+
*/
|
|
56
|
+
export declare function detectAvoidableContext(input: string): AvoidableContextResult;
|
|
57
|
+
/**
|
|
58
|
+
* Analyze one input prompt for avoidable duplicated context, returning a before-call recommendation
|
|
59
|
+
* tagged with the tool. Thin wrapper over the tool-agnostic `detectAvoidableContext` core.
|
|
60
|
+
*/
|
|
61
|
+
export declare function analyzeBeforeCall(tool: BeforeCallTool, input: string): BeforeCallRecommendation;
|
|
62
|
+
export interface LocatedPrompt {
|
|
63
|
+
/** Index into `argv` of the prompt element. */
|
|
64
|
+
index: number;
|
|
65
|
+
/** The prompt text. For `--flag=value` forms this is the value portion only. */
|
|
66
|
+
value: string;
|
|
67
|
+
/** True when the element is a `--prompt=value` / `-p=value` inline form (value after `=`). */
|
|
68
|
+
inline: boolean;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Locate the prompt element in a shim's `argv` (the args AFTER the tool name — the shim's `"$@"`).
|
|
72
|
+
* - Codex (`exec --json "<prompt>"`): the prompt is the LAST element, required NOT to start with `-`.
|
|
73
|
+
* If the last element starts with `-` (an option/flag, or an option value we cannot disambiguate),
|
|
74
|
+
* returns `null` (fail-closed).
|
|
75
|
+
* - Cursor (`-p "<prompt>"` / `--prompt "<prompt>"`, incl. `-p=…`/`--prompt=…`): the element right
|
|
76
|
+
* after a standalone `-p`/`--prompt`, or the value of the inline `=` form. Returns `null` when no
|
|
77
|
+
* such flag is present.
|
|
78
|
+
*/
|
|
79
|
+
export declare function locatePromptArg(tool: BeforeCallTool, argv: string[]): LocatedPrompt | null;
|
|
80
|
+
/** The per-tool argv shape whitelist used to decide whether a mutation is provably safe. */
|
|
81
|
+
export interface SafeMutationSpec {
|
|
82
|
+
/**
|
|
83
|
+
* Flags whose FOLLOWING TOKEN (or inline `=value`) is the prompt — the ONLY tokens this layer may
|
|
84
|
+
* rewrite. EMPTY for both real tools today (their prompt is a bare positional). A non-empty entry is
|
|
85
|
+
* the single extension point that would ever make apply available.
|
|
86
|
+
*/
|
|
87
|
+
promptFlags: readonly string[];
|
|
88
|
+
/** Non-prompt flags that CONSUME a following value token (whose value we skip, never mutate). */
|
|
89
|
+
valueFlags: readonly string[];
|
|
90
|
+
/** Boolean flags (no value). Anything not in these three sets is treated as unknown → fail-closed. */
|
|
91
|
+
booleanFlags: readonly string[];
|
|
92
|
+
/**
|
|
93
|
+
* EXPLICIT VERSIONED ACTIVATION (founder rule, run-11): the dormant apply path must NEVER activate
|
|
94
|
+
* just because a future tool version adds a flag. A non-empty `promptFlags` whitelist is honored ONLY
|
|
95
|
+
* when this field pins the exact tool version whose `--help` surface was verified to expose that
|
|
96
|
+
* value-taking prompt flag. Absent/empty → apply stays fail-closed even if `promptFlags` is populated.
|
|
97
|
+
* Activating a tool therefore requires ALL of: (1) a whitelist entry, (2) this pinned version, (3)
|
|
98
|
+
* safe-form tests, (4) flipping the dormant-guard assertion in before-call-dormant-guard.test.ts.
|
|
99
|
+
*/
|
|
100
|
+
verifiedToolVersion?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Real per-tool specs. The load-bearing safety fact is `promptFlags: []` for BOTH tools — this alone
|
|
104
|
+
* guarantees `resolveSafeMutation` fail-closes for every real invocation. The value/boolean tables are
|
|
105
|
+
* only ever consulted on the (currently unreachable) whitelist path and are kept for documentation and
|
|
106
|
+
* for the day a real prompt flag appears.
|
|
107
|
+
*/
|
|
108
|
+
export declare const SAFE_MUTATION_SPECS: Readonly<Record<BeforeCallTool, SafeMutationSpec>>;
|
|
109
|
+
/** Result of the safe-mutation gate: either a proven-safe rewrite, or a fail-closed reason. */
|
|
110
|
+
export type SafeMutation = {
|
|
111
|
+
applyAvailable: true;
|
|
112
|
+
tool: BeforeCallTool;
|
|
113
|
+
/** The prompt flag that was matched (e.g. `--prompt`). */
|
|
114
|
+
promptFlag: string;
|
|
115
|
+
/** True when the match was the inline `--prompt=value` form. */
|
|
116
|
+
inline: boolean;
|
|
117
|
+
/** Index into argv of the token that holds the prompt value (the flag index for inline forms). */
|
|
118
|
+
promptIndex: number;
|
|
119
|
+
/** The original prompt value. */
|
|
120
|
+
originalPrompt: string;
|
|
121
|
+
/** Rebuild argv with ONLY the prompt value replaced — every other token byte-for-byte + in order. */
|
|
122
|
+
rebuild: (newPrompt: string) => string[];
|
|
123
|
+
} | {
|
|
124
|
+
applyAvailable: false;
|
|
125
|
+
reason: string;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Decide whether the prompt in `argv` can be SAFELY and provably isolated for mutation (charter
|
|
129
|
+
* run-11). FAIL-CLOSED: returns `{ applyAvailable: false, reason }` unless the prompt is the value of a
|
|
130
|
+
* whitelisted value-taking prompt flag AND every other token is a known flag. For both real tools the
|
|
131
|
+
* `promptFlags` whitelist is empty, so this ALWAYS returns not-available (the caller runs the original
|
|
132
|
+
* unchanged). The `spec` parameter is injectable ONLY so tests can exercise the (dormant) whitelist
|
|
133
|
+
* path with a synthetic prompt flag — production callers always use the real, empty-prompt-flag specs.
|
|
134
|
+
*/
|
|
135
|
+
export declare function resolveSafeMutation(tool: BeforeCallTool, argv: string[], spec?: SafeMutationSpec): SafeMutation;
|
|
136
|
+
/** Result of the stdin-boundary gate: stdin is the sole prompt (safe to mediate) or a fail-closed reason. */
|
|
137
|
+
export interface StdinPromptBoundary {
|
|
138
|
+
/** True ONLY when stdin is provably the WHOLE prompt for this argv (Codex `exec`, no positional). */
|
|
139
|
+
safe: boolean;
|
|
140
|
+
/** Honest, content-free reason (used for the recommendation / activity when not safe). */
|
|
141
|
+
reason: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Decide whether stdin is the SOLE, explicit prompt boundary for `argv` (charter run-12). Codex-only,
|
|
145
|
+
* `exec`-only, fail-closed. Reuses the Codex value/boolean flag tables (`SAFE_MUTATION_SPECS.codex`) to
|
|
146
|
+
* skip flag values, so a value like `-m gpt-5` is never mistaken for a positional prompt. `-` (the
|
|
147
|
+
* documented explicit stdin marker) counts as "stdin is the prompt", NOT as a positional.
|
|
148
|
+
*/
|
|
149
|
+
export declare function resolveStdinPromptBoundary(tool: BeforeCallTool, argv: string[]): StdinPromptBoundary;
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { estimateTextTokens } from "./token-estimator.js";
|
|
2
|
+
import { detectWaste, getCompactedMessageIds } from "./waste-detector.js";
|
|
3
|
+
/** The deterministic policy this layer applies — exact-duplicate context-block removal. */
|
|
4
|
+
export const BEFORE_CALL_POLICY_NAME = "repeated_context_block_dedup";
|
|
5
|
+
/** The honest evidence label for every pre-call figure this layer produces (both tools). */
|
|
6
|
+
export const BEFORE_CALL_EVIDENCE_LABEL = "local-estimate (pre-call)";
|
|
7
|
+
/** The honest reduction label — spelled out so no reader upgrades it to a saving/provider figure. */
|
|
8
|
+
export const BEFORE_CALL_REDUCTION_LABEL = "local-estimate input-token delta (chars/4, pre-call); NOT billing-confirmed; NOT provider-reported " +
|
|
9
|
+
"(provider usage does not exist until after the call); NOT a realized saving";
|
|
10
|
+
/**
|
|
11
|
+
* A duplicate context block must be at least this many characters to be worth recommending removal of.
|
|
12
|
+
* Conservative: tiny repeated fragments (blank lines, short markers) are never flagged — the same
|
|
13
|
+
* spirit as the trace-level min-duplicate thresholds. This is a size floor, NOT a scoring heuristic.
|
|
14
|
+
*/
|
|
15
|
+
export const MIN_DUPLICATE_BLOCK_CHARS = 40;
|
|
16
|
+
/**
|
|
17
|
+
* Split an input prompt into blocks on blank-line boundaries, PRESERVING the exact separators so the
|
|
18
|
+
* kept blocks can be rejoined byte-for-byte (minus removed duplicate blocks). `split` with a capturing
|
|
19
|
+
* group yields `[block0, sep0, block1, sep1, …]`: even indices are blocks, odd indices are the verbatim
|
|
20
|
+
* separators between them.
|
|
21
|
+
*/
|
|
22
|
+
function splitPreservingSeparators(input) {
|
|
23
|
+
return input.split(/(\n[ \t]*\n[\s]*)/);
|
|
24
|
+
}
|
|
25
|
+
/** Shape the input blocks into a synthetic block-trace the existing waste detector understands. */
|
|
26
|
+
function shapeBlockTrace(blocks) {
|
|
27
|
+
const blockIdByIndex = new Map();
|
|
28
|
+
const messages = [];
|
|
29
|
+
for (let i = 0; i < blocks.length; i += 2) {
|
|
30
|
+
const id = `block-${i}`;
|
|
31
|
+
blockIdByIndex.set(i, id);
|
|
32
|
+
messages.push({ id, role: "tool", content: blocks[i], timestamp: "1970-01-01T00:00:00.000Z", toolName: "context_block" });
|
|
33
|
+
}
|
|
34
|
+
const trace = {
|
|
35
|
+
id: "before-call-input",
|
|
36
|
+
title: "before-call input",
|
|
37
|
+
artifactVersion: "before-call-v0",
|
|
38
|
+
source: "manual",
|
|
39
|
+
createdAt: "1970-01-01T00:00:00.000Z",
|
|
40
|
+
generatedAt: "1970-01-01T00:00:00.000Z",
|
|
41
|
+
model: "unknown",
|
|
42
|
+
messages
|
|
43
|
+
};
|
|
44
|
+
return { trace, blockIdByIndex };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The TOOL-AGNOSTIC pure core: analyze one input for avoidable duplicated context. Deterministic +
|
|
48
|
+
* content-free-by-contract (the caller must not persist `compacted_input`). When the input has fewer
|
|
49
|
+
* than two blocks, or no duplicate block meets the size floor, returns `has_avoidable_context: false`
|
|
50
|
+
* with `compacted_input === input` (honest no-op). Reused by every before-call surface.
|
|
51
|
+
*/
|
|
52
|
+
export function detectAvoidableContext(input) {
|
|
53
|
+
const before = estimateTextTokens(input);
|
|
54
|
+
const parts = splitPreservingSeparators(input);
|
|
55
|
+
const blockCount = Math.ceil(parts.length / 2);
|
|
56
|
+
const noAvoidable = () => ({
|
|
57
|
+
has_avoidable_context: false,
|
|
58
|
+
policy: BEFORE_CALL_POLICY_NAME,
|
|
59
|
+
input_tokens_before: before,
|
|
60
|
+
input_tokens_after_estimate: before,
|
|
61
|
+
reduction_tokens_estimate: 0,
|
|
62
|
+
reduction_label: BEFORE_CALL_REDUCTION_LABEL,
|
|
63
|
+
evidence_label: BEFORE_CALL_EVIDENCE_LABEL,
|
|
64
|
+
blocks_total: blockCount,
|
|
65
|
+
blocks_removed: 0,
|
|
66
|
+
compacted_input: input
|
|
67
|
+
});
|
|
68
|
+
if (blockCount < 2)
|
|
69
|
+
return noAvoidable();
|
|
70
|
+
const { trace, blockIdByIndex } = shapeBlockTrace(parts);
|
|
71
|
+
const findings = detectWaste(trace, { minDuplicateCharacters: MIN_DUPLICATE_BLOCK_CHARS });
|
|
72
|
+
const removeIds = getCompactedMessageIds(findings);
|
|
73
|
+
if (removeIds.size === 0)
|
|
74
|
+
return noAvoidable();
|
|
75
|
+
// Rebuild the input from the surviving blocks. When a duplicate block is removed, also drop the
|
|
76
|
+
// separator that IMMEDIATELY PRECEDES it (or, for the very first block, the separator that follows
|
|
77
|
+
// it) so the rejoined text stays clean and byte-exact for every kept block.
|
|
78
|
+
const removeIndexes = new Set();
|
|
79
|
+
for (const [index, id] of blockIdByIndex) {
|
|
80
|
+
if (removeIds.has(id))
|
|
81
|
+
removeIndexes.add(index);
|
|
82
|
+
}
|
|
83
|
+
const keep = [];
|
|
84
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
85
|
+
const isBlock = i % 2 === 0;
|
|
86
|
+
if (isBlock) {
|
|
87
|
+
if (removeIndexes.has(i)) {
|
|
88
|
+
// Removing block i: also drop the separator BEFORE it (parts[i-1]) if we already emitted it,
|
|
89
|
+
// else the separator AFTER it (parts[i+1]) for the first block.
|
|
90
|
+
if (keep.length > 0 && keep[keep.length - 1] === parts[i - 1]) {
|
|
91
|
+
keep.pop();
|
|
92
|
+
}
|
|
93
|
+
else if (i + 1 < parts.length) {
|
|
94
|
+
// First-block case: skip the following separator by advancing the loop over parts[i+1].
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
keep.push(parts[i]);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
keep.push(parts[i]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const compacted = keep.join("");
|
|
106
|
+
const after = estimateTextTokens(compacted);
|
|
107
|
+
// Defensive honesty: if the rebuild did not actually shrink the estimate, do not claim a reduction.
|
|
108
|
+
if (after >= before)
|
|
109
|
+
return noAvoidable();
|
|
110
|
+
return {
|
|
111
|
+
has_avoidable_context: true,
|
|
112
|
+
policy: BEFORE_CALL_POLICY_NAME,
|
|
113
|
+
input_tokens_before: before,
|
|
114
|
+
input_tokens_after_estimate: after,
|
|
115
|
+
reduction_tokens_estimate: before - after,
|
|
116
|
+
reduction_label: BEFORE_CALL_REDUCTION_LABEL,
|
|
117
|
+
evidence_label: BEFORE_CALL_EVIDENCE_LABEL,
|
|
118
|
+
blocks_total: blockCount,
|
|
119
|
+
blocks_removed: removeIndexes.size,
|
|
120
|
+
compacted_input: compacted
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Analyze one input prompt for avoidable duplicated context, returning a before-call recommendation
|
|
125
|
+
* tagged with the tool. Thin wrapper over the tool-agnostic `detectAvoidableContext` core.
|
|
126
|
+
*/
|
|
127
|
+
export function analyzeBeforeCall(tool, input) {
|
|
128
|
+
return { tool, ...detectAvoidableContext(input) };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Locate the prompt element in a shim's `argv` (the args AFTER the tool name — the shim's `"$@"`).
|
|
132
|
+
* - Codex (`exec --json "<prompt>"`): the prompt is the LAST element, required NOT to start with `-`.
|
|
133
|
+
* If the last element starts with `-` (an option/flag, or an option value we cannot disambiguate),
|
|
134
|
+
* returns `null` (fail-closed).
|
|
135
|
+
* - Cursor (`-p "<prompt>"` / `--prompt "<prompt>"`, incl. `-p=…`/`--prompt=…`): the element right
|
|
136
|
+
* after a standalone `-p`/`--prompt`, or the value of the inline `=` form. Returns `null` when no
|
|
137
|
+
* such flag is present.
|
|
138
|
+
*/
|
|
139
|
+
export function locatePromptArg(tool, argv) {
|
|
140
|
+
if (tool === "cursor") {
|
|
141
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
142
|
+
const a = argv[i];
|
|
143
|
+
if (a === "-p" || a === "--prompt") {
|
|
144
|
+
if (i + 1 < argv.length)
|
|
145
|
+
return { index: i + 1, value: argv[i + 1], inline: false };
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const inline = /^(?:-p|--prompt)=(.*)$/s.exec(a);
|
|
149
|
+
if (inline)
|
|
150
|
+
return { index: i, value: inline[1], inline: true };
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
// codex
|
|
155
|
+
if (argv.length === 0)
|
|
156
|
+
return null;
|
|
157
|
+
const lastIndex = argv.length - 1;
|
|
158
|
+
const last = argv[lastIndex];
|
|
159
|
+
if (last.startsWith("-"))
|
|
160
|
+
return null; // an option/flag or an ambiguous option value — never guess
|
|
161
|
+
return { index: lastIndex, value: last, inline: false };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Real per-tool specs. The load-bearing safety fact is `promptFlags: []` for BOTH tools — this alone
|
|
165
|
+
* guarantees `resolveSafeMutation` fail-closes for every real invocation. The value/boolean tables are
|
|
166
|
+
* only ever consulted on the (currently unreachable) whitelist path and are kept for documentation and
|
|
167
|
+
* for the day a real prompt flag appears.
|
|
168
|
+
*/
|
|
169
|
+
export const SAFE_MUTATION_SPECS = {
|
|
170
|
+
// codex exec [OPTIONS] [PROMPT]: prompt is a BARE POSITIONAL (or stdin). `-p`/`--profile` is a
|
|
171
|
+
// config-profile VALUE flag, NOT the prompt. No value-taking prompt flag exists → apply never
|
|
172
|
+
// available.
|
|
173
|
+
codex: {
|
|
174
|
+
promptFlags: [],
|
|
175
|
+
valueFlags: [
|
|
176
|
+
"-c", "--config", "--enable", "--disable", "-i", "--image", "-m", "--model",
|
|
177
|
+
"--local-provider", "-p", "--profile", "-s", "--sandbox", "-C", "--cd", "--add-dir",
|
|
178
|
+
"--output-schema", "--color", "-o", "--output-last-message"
|
|
179
|
+
],
|
|
180
|
+
booleanFlags: [
|
|
181
|
+
"exec", "--strict-config", "--oss", "--dangerously-bypass-approvals-and-sandbox",
|
|
182
|
+
"--dangerously-bypass-hook-trust", "--skip-git-repo-check", "--ephemeral",
|
|
183
|
+
"--ignore-user-config", "--ignore-rules", "--json", "-h", "--help", "-V", "--version"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
// cursor `agent [options] [prompt...]`: prompt is a VARIADIC BARE POSITIONAL. `-p`/`--print` is a
|
|
187
|
+
// BOOLEAN flag (print responses), NOT a prompt-value flag. No value-taking prompt flag exists → apply
|
|
188
|
+
// never available.
|
|
189
|
+
cursor: {
|
|
190
|
+
promptFlags: [],
|
|
191
|
+
valueFlags: [
|
|
192
|
+
"--api-key", "-H", "--header", "--output-format", "--mode", "--model", "--sandbox",
|
|
193
|
+
"--workspace", "--add-dir", "--plugin-dir", "--worktree-base"
|
|
194
|
+
],
|
|
195
|
+
booleanFlags: [
|
|
196
|
+
"-p", "--print", "-v", "--version", "--stream-partial-output", "--plan", "--continue",
|
|
197
|
+
"-f", "--force", "--yolo", "--auto-review", "--approve-mcps", "--trust",
|
|
198
|
+
"--skip-worktree-setup", "-h", "--help"
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
/** Match `flag=value` for any flag in `flags`; returns the flag + value, or null. */
|
|
203
|
+
function matchInlineFlag(token, flags) {
|
|
204
|
+
const eq = token.indexOf("=");
|
|
205
|
+
if (eq <= 0)
|
|
206
|
+
return null;
|
|
207
|
+
const flag = token.slice(0, eq);
|
|
208
|
+
if (!flags.has(flag))
|
|
209
|
+
return null;
|
|
210
|
+
return { flag, value: token.slice(eq + 1) };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Decide whether the prompt in `argv` can be SAFELY and provably isolated for mutation (charter
|
|
214
|
+
* run-11). FAIL-CLOSED: returns `{ applyAvailable: false, reason }` unless the prompt is the value of a
|
|
215
|
+
* whitelisted value-taking prompt flag AND every other token is a known flag. For both real tools the
|
|
216
|
+
* `promptFlags` whitelist is empty, so this ALWAYS returns not-available (the caller runs the original
|
|
217
|
+
* unchanged). The `spec` parameter is injectable ONLY so tests can exercise the (dormant) whitelist
|
|
218
|
+
* path with a synthetic prompt flag — production callers always use the real, empty-prompt-flag specs.
|
|
219
|
+
*/
|
|
220
|
+
export function resolveSafeMutation(tool, argv, spec = SAFE_MUTATION_SPECS[tool]) {
|
|
221
|
+
const promptFlags = new Set(spec.promptFlags);
|
|
222
|
+
// EXPLICIT VERSIONED ACTIVATION GATE (founder rule, run-11): a non-empty whitelist is honored ONLY
|
|
223
|
+
// with a pinned verifiedToolVersion. This makes "a future tool version added a flag, so it just turns
|
|
224
|
+
// on" impossible — activation must be a deliberate, version-pinned, tested decision. Fail-closed.
|
|
225
|
+
if (promptFlags.size > 0 && !spec.verifiedToolVersion) {
|
|
226
|
+
return {
|
|
227
|
+
applyAvailable: false,
|
|
228
|
+
reason: `${tool}: a prompt-flag whitelist is set but no verifiedToolVersion is pinned — activation ` +
|
|
229
|
+
`requires an explicit versioned whitelist + tests (founder rule, run-11) — fail-closed`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (promptFlags.size === 0) {
|
|
233
|
+
return {
|
|
234
|
+
applyAvailable: false,
|
|
235
|
+
reason: `${tool}: no value-taking prompt flag exists (verified via --help); the prompt is a bare ` +
|
|
236
|
+
`positional and cannot be isolated with high confidence — recommendation-only (fail-closed)`
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const valueFlags = new Set(spec.valueFlags);
|
|
240
|
+
const booleanFlags = new Set(spec.booleanFlags);
|
|
241
|
+
let hit = null;
|
|
242
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
243
|
+
const a = argv[i];
|
|
244
|
+
const inlinePrompt = matchInlineFlag(a, promptFlags);
|
|
245
|
+
if (inlinePrompt) {
|
|
246
|
+
if (hit)
|
|
247
|
+
return { applyAvailable: false, reason: "multiple prompt flags — ambiguous (fail-closed)" };
|
|
248
|
+
hit = { flagIndex: i, valueIndex: i, inline: true, flag: inlinePrompt.flag, value: inlinePrompt.value };
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (promptFlags.has(a)) {
|
|
252
|
+
if (hit)
|
|
253
|
+
return { applyAvailable: false, reason: "multiple prompt flags — ambiguous (fail-closed)" };
|
|
254
|
+
if (i + 1 >= argv.length)
|
|
255
|
+
return { applyAvailable: false, reason: "prompt flag has no following value (fail-closed)" };
|
|
256
|
+
const v = argv[i + 1];
|
|
257
|
+
if (v.startsWith("-"))
|
|
258
|
+
return { applyAvailable: false, reason: "prompt-flag value looks like a flag — ambiguous (fail-closed)" };
|
|
259
|
+
hit = { flagIndex: i, valueIndex: i + 1, inline: false, flag: a, value: v };
|
|
260
|
+
i += 1; // skip the value token — it is the prompt, already recorded
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (matchInlineFlag(a, valueFlags))
|
|
264
|
+
continue; // known inline value flag (e.g. --model=x) — skip
|
|
265
|
+
if (valueFlags.has(a)) {
|
|
266
|
+
i += 1; // known value flag — skip ITS value token (never mutate a non-prompt value)
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (booleanFlags.has(a))
|
|
270
|
+
continue;
|
|
271
|
+
if (a.startsWith("-")) {
|
|
272
|
+
return { applyAvailable: false, reason: `unknown flag '${a}' — cannot prove argv is safe (fail-closed)` };
|
|
273
|
+
}
|
|
274
|
+
// A bare positional that is NOT a prompt-flag value: could be the prompt, a subcommand, or an
|
|
275
|
+
// argument — the prompt is not provably isolated. Fail-closed (this is the real-tool case).
|
|
276
|
+
return { applyAvailable: false, reason: "bare positional token present — prompt not provably isolated (fail-closed)" };
|
|
277
|
+
}
|
|
278
|
+
if (!hit)
|
|
279
|
+
return { applyAvailable: false, reason: "no prompt flag found in argv (fail-closed)" };
|
|
280
|
+
const matched = hit;
|
|
281
|
+
return {
|
|
282
|
+
applyAvailable: true,
|
|
283
|
+
tool,
|
|
284
|
+
promptFlag: matched.flag,
|
|
285
|
+
inline: matched.inline,
|
|
286
|
+
promptIndex: matched.valueIndex,
|
|
287
|
+
originalPrompt: matched.value,
|
|
288
|
+
rebuild: (newPrompt) => {
|
|
289
|
+
const out = argv.slice();
|
|
290
|
+
out[matched.valueIndex] = matched.inline ? `${matched.flag}=${newPrompt}` : newPrompt;
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Decide whether stdin is the SOLE, explicit prompt boundary for `argv` (charter run-12). Codex-only,
|
|
297
|
+
* `exec`-only, fail-closed. Reuses the Codex value/boolean flag tables (`SAFE_MUTATION_SPECS.codex`) to
|
|
298
|
+
* skip flag values, so a value like `-m gpt-5` is never mistaken for a positional prompt. `-` (the
|
|
299
|
+
* documented explicit stdin marker) counts as "stdin is the prompt", NOT as a positional.
|
|
300
|
+
*/
|
|
301
|
+
export function resolveStdinPromptBoundary(tool, argv) {
|
|
302
|
+
if (tool !== "codex") {
|
|
303
|
+
return {
|
|
304
|
+
safe: false,
|
|
305
|
+
reason: `${tool}: no documented stdin prompt boundary (verified via --help) — recommendation-only (fail-closed)`
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const spec = SAFE_MUTATION_SPECS.codex;
|
|
309
|
+
const valueFlags = new Set(spec.valueFlags);
|
|
310
|
+
const booleanFlags = new Set(spec.booleanFlags);
|
|
311
|
+
let sawExec = false;
|
|
312
|
+
let positionals = 0;
|
|
313
|
+
let sawDash = false;
|
|
314
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
315
|
+
const a = argv[i];
|
|
316
|
+
if (a === "exec") {
|
|
317
|
+
sawExec = true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (a === "-") {
|
|
321
|
+
// The documented explicit stdin marker — stdin is the prompt (NOT a positional prompt, NOT a flag).
|
|
322
|
+
sawDash = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (matchInlineFlag(a, valueFlags))
|
|
326
|
+
continue; // known inline value flag (e.g. --model=x)
|
|
327
|
+
if (valueFlags.has(a)) {
|
|
328
|
+
i += 1; // known value flag — skip ITS value token (never counted as a positional)
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (booleanFlags.has(a))
|
|
332
|
+
continue;
|
|
333
|
+
if (a.startsWith("-")) {
|
|
334
|
+
return { safe: false, reason: `unknown flag '${a}' — cannot prove the stdin boundary (fail-closed)` };
|
|
335
|
+
}
|
|
336
|
+
// A bare positional that is NOT `exec` and NOT `-`: a positional PROMPT (or a nested subcommand like
|
|
337
|
+
// resume/review). Either way stdin is NOT the sole prompt → the boundary is ambiguous → fail-closed.
|
|
338
|
+
positionals += 1;
|
|
339
|
+
}
|
|
340
|
+
if (!sawExec) {
|
|
341
|
+
return { safe: false, reason: "not the `codex exec` batch form — no stdin prompt boundary (fail-closed)" };
|
|
342
|
+
}
|
|
343
|
+
if (positionals > 0) {
|
|
344
|
+
return {
|
|
345
|
+
safe: false,
|
|
346
|
+
reason: "a positional prompt is present, so stdin is not the whole prompt (Codex would append it as a " +
|
|
347
|
+
"<stdin> block) — the boundary is ambiguous (fail-closed to the original)"
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
// No positional prompt (optionally a lone `-`): stdin is provably the ENTIRE prompt → safe to mediate.
|
|
351
|
+
return {
|
|
352
|
+
safe: true,
|
|
353
|
+
reason: sawDash
|
|
354
|
+
? "codex exec with an explicit `-` — stdin is the whole prompt (safe boundary)"
|
|
355
|
+
: "codex exec with no positional prompt — stdin is the whole prompt (safe boundary)"
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=before-call.js.map
|
|
@@ -49,6 +49,26 @@ export declare const BILLING_DELTA_CRITERION: string;
|
|
|
49
49
|
export declare const BILLING_DELTA_INTERVAL_K = 2;
|
|
50
50
|
/** Verbatim-ish anecdote note required by the accepted doc (§2.4: "a single A/B run is an anecdote, not proof"). */
|
|
51
51
|
export declare const BILLING_DELTA_ANECDOTE_NOTE = "A single A/B pair is an anecdote, not proof: N=1 per arm cannot distinguish a compaction effect from run-to-run noise.";
|
|
52
|
+
/**
|
|
53
|
+
* The api_metered billing-confirmed criteria, VERBATIM from the cross-surface evidence contract
|
|
54
|
+
* (docs/ops/cross-surface-evidence-contract.md §5, founder decision D6). A billing-confirmed
|
|
55
|
+
* figure is possible ONLY on api_metered workflows, ONLY through this billing-delta flow, and
|
|
56
|
+
* ONLY when ALL of these hold. This module never verifies them and never emits
|
|
57
|
+
* `billing_confirmed: true` — the checklist is surfaced so the operator can see exactly what a
|
|
58
|
+
* billing-confirmed figure would require. Even then the claim is workflow-scoped only;
|
|
59
|
+
* generalized billing-confirmed = future only.
|
|
60
|
+
*/
|
|
61
|
+
export declare const API_METERED_BILLING_CONFIRMED_CRITERIA: readonly string[];
|
|
62
|
+
/**
|
|
63
|
+
* FIXED-PLAN GUARDRAIL (cross-surface contract §4, D5 — binding, no exceptions): fixed-plan
|
|
64
|
+
* workflows are NEVER billing-confirmed. This flow measures API-METERED workflows only (billed
|
|
65
|
+
* per-run cost is its input); there is no billed per-run delta on a fixed plan, so a fixed-plan
|
|
66
|
+
* workflow cannot honestly produce the operator-entered billed costs this record requires — and
|
|
67
|
+
* regardless of what an operator enters, every `billing_confirmed` field in this module is the
|
|
68
|
+
* LITERAL `false` type (record, report, token delta): fixed-plan-as-billing-confirmed is
|
|
69
|
+
* structurally unexpressible. Enforced by the structural guards below + tests.
|
|
70
|
+
*/
|
|
71
|
+
export declare const BILLING_DELTA_FIXED_PLAN_NOTE = "API-metered workflows only: a fixed plan has no billed per-run delta, so a fixed-plan workflow can NEVER be billing-confirmed \u2014 this flow cannot express it (billing_confirmed is structurally always false).";
|
|
52
72
|
/** Provenance link to an apply-context approval record (the treatment context provenance), recorded by path + hash. */
|
|
53
73
|
export interface TreatmentApprovalLink {
|
|
54
74
|
/** Path to the apply-approval-record JSON emitted by `compaction apply-context --approve-in-workflow-use`. */
|
|
@@ -288,3 +308,11 @@ export declare function addRun(record: BillingDeltaMeasurementRecord, input: Add
|
|
|
288
308
|
*/
|
|
289
309
|
export declare function computeTokenDelta(record: BillingDeltaMeasurementRecord): BillingDeltaTokenDelta;
|
|
290
310
|
export declare function computeReport(record: BillingDeltaMeasurementRecord, generatedAt: string): BillingDeltaReport;
|
|
311
|
+
type Assert<T extends true> = T;
|
|
312
|
+
/** The measurement record can never carry billing_confirmed: true. */
|
|
313
|
+
export type StructuralGuard_RecordNeverBillingConfirmed = Assert<BillingDeltaMeasurementRecord["billing_confirmed"] extends false ? true : false>;
|
|
314
|
+
/** The report can never carry billing_confirmed: true. */
|
|
315
|
+
export type StructuralGuard_ReportNeverBillingConfirmed = Assert<BillingDeltaReport["billing_confirmed"] extends false ? true : false>;
|
|
316
|
+
/** The token-delta section can never carry billing_confirmed: true. */
|
|
317
|
+
export type StructuralGuard_TokenDeltaNeverBillingConfirmed = Assert<BillingDeltaTokenDelta["billing_confirmed"] extends false ? true : false>;
|
|
318
|
+
export {};
|
|
@@ -39,6 +39,32 @@ export const BILLING_DELTA_CRITERION = "delta-of-means interval = [mean_delta -
|
|
|
39
39
|
export const BILLING_DELTA_INTERVAL_K = 2;
|
|
40
40
|
/** Verbatim-ish anecdote note required by the accepted doc (§2.4: "a single A/B run is an anecdote, not proof"). */
|
|
41
41
|
export const BILLING_DELTA_ANECDOTE_NOTE = "A single A/B pair is an anecdote, not proof: N=1 per arm cannot distinguish a compaction effect from run-to-run noise.";
|
|
42
|
+
/**
|
|
43
|
+
* The api_metered billing-confirmed criteria, VERBATIM from the cross-surface evidence contract
|
|
44
|
+
* (docs/ops/cross-surface-evidence-contract.md §5, founder decision D6). A billing-confirmed
|
|
45
|
+
* figure is possible ONLY on api_metered workflows, ONLY through this billing-delta flow, and
|
|
46
|
+
* ONLY when ALL of these hold. This module never verifies them and never emits
|
|
47
|
+
* `billing_confirmed: true` — the checklist is surfaced so the operator can see exactly what a
|
|
48
|
+
* billing-confirmed figure would require. Even then the claim is workflow-scoped only;
|
|
49
|
+
* generalized billing-confirmed = future only.
|
|
50
|
+
*/
|
|
51
|
+
export const API_METERED_BILLING_CONFIRMED_CRITERIA = [
|
|
52
|
+
"**cost-isolated key / quiet window** — spend attributable to the measured workflow;",
|
|
53
|
+
"**same model + version**, same workflow / same input (pinned workflow identity);",
|
|
54
|
+
"**3+ control runs and 3+ treatment runs** (the accepted N ≥ 3 / ±2·SE-excludes-zero criterion, human-accepted 2026-06-11);",
|
|
55
|
+
"**provider-billed or operator-entered cost** per run (`cost_source` says which — never a price-table estimate relabeled);",
|
|
56
|
+
"**task outcome recorded** (a cheaper failed run is not a saving)."
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* FIXED-PLAN GUARDRAIL (cross-surface contract §4, D5 — binding, no exceptions): fixed-plan
|
|
60
|
+
* workflows are NEVER billing-confirmed. This flow measures API-METERED workflows only (billed
|
|
61
|
+
* per-run cost is its input); there is no billed per-run delta on a fixed plan, so a fixed-plan
|
|
62
|
+
* workflow cannot honestly produce the operator-entered billed costs this record requires — and
|
|
63
|
+
* regardless of what an operator enters, every `billing_confirmed` field in this module is the
|
|
64
|
+
* LITERAL `false` type (record, report, token delta): fixed-plan-as-billing-confirmed is
|
|
65
|
+
* structurally unexpressible. Enforced by the structural guards below + tests.
|
|
66
|
+
*/
|
|
67
|
+
export const BILLING_DELTA_FIXED_PLAN_NOTE = "API-metered workflows only: a fixed plan has no billed per-run delta, so a fixed-plan workflow can NEVER be billing-confirmed — this flow cannot express it (billing_confirmed is structurally always false).";
|
|
42
68
|
/**
|
|
43
69
|
* Claim-level labels for the token-delta section. Token-level, provider-reported — never a
|
|
44
70
|
* billing claim. The label is selected by the ACTUAL sign/status of the token delta so it
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared content-free record step for the unified capture→compact→record→report flow (PUBLIC CLI/SDK).
|
|
3
|
+
* Design: `docs/design/unified-run-flow.md` (accepted 2026-06-28).
|
|
4
|
+
*
|
|
5
|
+
* After any tool's capture (Codex live wrapper, Claude Code capture/hook, …) this records a CONTENT-FREE
|
|
6
|
+
* usage event to the hosted control-plane: `provider_metadata {tool, provider, model}` + `token_usage`
|
|
7
|
+
* with **input and output counted SEPARATELY** and an honest **`token_source`**
|
|
8
|
+
* (provider-reported | local-estimate). It uploads NO trace content (metrics_only) and only runs when the
|
|
9
|
+
* user has configured the hosted path (URL + key) — never a default network call.
|
|
10
|
+
*
|
|
11
|
+
* Honesty rails (founder D2, 2026-06-28): the three axes stay distinct — provider-reported vs
|
|
12
|
+
* locally-observed/estimated vs SAVINGS. This records tokens (input + output) with their source; it does
|
|
13
|
+
* NOT compute or send any output-savings figure (output savings stay unavailable until the output-shaping
|
|
14
|
+
* policy family produces measured + eval-confirmed evidence).
|
|
15
|
+
*/
|
|
16
|
+
import { type ApiConfig, type TokenSource, type ToolName } from "./api-client/index.js";
|
|
17
|
+
import type { UsageMetadata } from "./usage-metadata.js";
|
|
18
|
+
/** Map a capture's UsageMetadata to the honest wire token source. */
|
|
19
|
+
export declare function captureTokenSource(usage: UsageMetadata): TokenSource;
|
|
20
|
+
export interface CaptureRecordOptions {
|
|
21
|
+
usage: UsageMetadata;
|
|
22
|
+
tool: ToolName;
|
|
23
|
+
/** A local reference (e.g. captured-trace.json path) — NOT content; the body is metrics_only. */
|
|
24
|
+
reference: string;
|
|
25
|
+
}
|
|
26
|
+
/** Build the CONTENT-FREE (`metrics_only`) record request. Input/output sent separately; honest source. */
|
|
27
|
+
export declare function buildCaptureRecordRequest(opts: CaptureRecordOptions): import("./api-client/types.js").OptimizeRequest;
|
|
28
|
+
export type CaptureRecordResult = {
|
|
29
|
+
recorded: true;
|
|
30
|
+
tool: ToolName;
|
|
31
|
+
source: TokenSource;
|
|
32
|
+
} | {
|
|
33
|
+
recorded: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Send the content-free record. Best-effort: a transport/HTTP failure returns `recorded: false` with a
|
|
38
|
+
* sanitized reason (never the key, headers, or body) — it must NEVER fail the user's capture.
|
|
39
|
+
*/
|
|
40
|
+
export declare function recordCaptureContentFree(config: ApiConfig, opts: CaptureRecordOptions): Promise<CaptureRecordResult>;
|