@compaction/cli 0.2.0 → 0.3.1
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 +142 -64
- 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 +9 -5
- 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/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 +297 -12
- 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 +36 -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/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/run-aggregator.d.ts +11 -1
- package/dist/core/run-aggregator.js +29 -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-parser.d.ts +13 -13
- package/dist/core/trace-parser.js +6 -6
- package/dist/core/types.d.ts +12 -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,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transparent capturing PATH-shim infrastructure (PUBLIC CLI/SDK code — engine-free, ships in the
|
|
3
|
+
* npm package). This is the TRUE always-on mechanism for Codex and Cursor (charter 2026-07-04-run-9),
|
|
4
|
+
* mirroring the run-8 Claude Code connect→install→RE-READ→VERIFY pattern
|
|
5
|
+
* (`src/core/claude-code-connect.ts`): a "connected/active" claim may rest ONLY on a resolve-check —
|
|
6
|
+
* never on the mere act of writing a file.
|
|
7
|
+
*
|
|
8
|
+
* Mechanism (charter order: native hook → shell alias → PATH shim → else stop): a Compaction-owned
|
|
9
|
+
* shim directory (`~/.compaction/shims/`, overridable via `COMPACTION_SHIM_DIR` / `COMPACTION_HOME`
|
|
10
|
+
* for tests) holds a small bash shim named exactly like the tool (`codex`, `cursor-agent`). When that
|
|
11
|
+
* directory is on `PATH` AHEAD of the real binary, running the tool normally routes through the shim,
|
|
12
|
+
* which TRANSPARENTLY execs the REAL binary (its absolute path resolved BEFORE install and baked in so
|
|
13
|
+
* the shim can never recurse into itself) with ALL args, inherited stdio, and the preserved exit code.
|
|
14
|
+
* For the MEASURABLE batch form ONLY (`codex exec --json` / `cursor-agent … --output-format json`) it
|
|
15
|
+
* tees stdout to a temp copy and, after the real binary exits, invokes the CONTENT-FREE
|
|
16
|
+
* `compaction capture <tool> --from-shim` bridge (usage token fields only; no prompt/response/message
|
|
17
|
+
* text is ever read into the event). Interactive / other invocations PASS THROUGH untouched and are
|
|
18
|
+
* honestly NOT measured (never faked).
|
|
19
|
+
*
|
|
20
|
+
* Safety rails (charter §HARD RAILS):
|
|
21
|
+
* - NEVER overwrites or replaces the real `codex`/`cursor-agent` binaries.
|
|
22
|
+
* - NEVER edits the user's shell rc without an explicit `--write-shell-config` gesture AND a reversible
|
|
23
|
+
* `.bak` backup (see `writeShellConfigPathLine` / `removeShellConfigPathLine`).
|
|
24
|
+
* - NEVER claims active without a resolve-verification (`verifyShimActive`).
|
|
25
|
+
* - Local file I/O only — no network, no new dependency.
|
|
26
|
+
*/
|
|
27
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync, copyFileSync } from "node:fs";
|
|
28
|
+
import { homedir } from "node:os";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
export const SHIM_TOOLS = {
|
|
31
|
+
codex: { shimName: "codex", captureTool: "codex", forwardArgs: false },
|
|
32
|
+
cursor: { shimName: "cursor-agent", captureTool: "cursor", forwardArgs: true }
|
|
33
|
+
};
|
|
34
|
+
/** Marker line embedded in every generated shim: how verify + uninstall confirm the file is OURS. */
|
|
35
|
+
export const SHIM_MARKER = "COMPACTION_SHIM";
|
|
36
|
+
/** The Compaction home directory: `COMPACTION_HOME` or `<home>/.compaction`. */
|
|
37
|
+
export function resolveCompactionHome(env = process.env) {
|
|
38
|
+
if (env.COMPACTION_HOME && env.COMPACTION_HOME.trim() !== "")
|
|
39
|
+
return env.COMPACTION_HOME;
|
|
40
|
+
const home = env.HOME && env.HOME.trim() !== "" ? env.HOME : homedir();
|
|
41
|
+
return path.join(home, ".compaction");
|
|
42
|
+
}
|
|
43
|
+
/** The Compaction-owned shim directory: `COMPACTION_SHIM_DIR`, else `<compaction-home>/shims`. */
|
|
44
|
+
export function resolveShimDir(env = process.env) {
|
|
45
|
+
if (env.COMPACTION_SHIM_DIR && env.COMPACTION_SHIM_DIR.trim() !== "")
|
|
46
|
+
return env.COMPACTION_SHIM_DIR;
|
|
47
|
+
return path.join(resolveCompactionHome(env), "shims");
|
|
48
|
+
}
|
|
49
|
+
export function shimPathFor(tool, env = process.env) {
|
|
50
|
+
return path.join(resolveShimDir(env), SHIM_TOOLS[tool].shimName);
|
|
51
|
+
}
|
|
52
|
+
/* ------------------------------------------------------------------------------------------------
|
|
53
|
+
* Resolve the REAL binary on the current PATH, EXCLUDING our shim dir (so the shim never records or
|
|
54
|
+
* execs itself). This is `which <name>` minus the shim directory.
|
|
55
|
+
* ---------------------------------------------------------------------------------------------- */
|
|
56
|
+
function isExecutableFile(file) {
|
|
57
|
+
try {
|
|
58
|
+
const st = statSync(file);
|
|
59
|
+
return st.isFile() && (st.mode & 0o111) !== 0;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* First executable named `name` on `PATH`, skipping any directory in `excludeDirs` (the shim dir).
|
|
67
|
+
* Returns the absolute path or `undefined`. Used at INSTALL time to record the real binary before the
|
|
68
|
+
* shim can shadow it, and to detect "tool not installed" (never shim a command that does not exist).
|
|
69
|
+
*/
|
|
70
|
+
export function resolveExecutableOnPath(name, env = process.env, excludeDirs = []) {
|
|
71
|
+
const pathValue = env.PATH ?? "";
|
|
72
|
+
const excluded = new Set(excludeDirs.map((d) => path.resolve(d)));
|
|
73
|
+
for (const dir of pathValue.split(path.delimiter)) {
|
|
74
|
+
if (dir.trim() === "")
|
|
75
|
+
continue;
|
|
76
|
+
if (excluded.has(path.resolve(dir)))
|
|
77
|
+
continue;
|
|
78
|
+
const candidate = path.join(dir, name);
|
|
79
|
+
if (isExecutableFile(candidate))
|
|
80
|
+
return path.resolve(candidate);
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
/* ------------------------------------------------------------------------------------------------
|
|
85
|
+
* Shim script generation.
|
|
86
|
+
* ---------------------------------------------------------------------------------------------- */
|
|
87
|
+
/** Single-quote a value for safe embedding in the generated bash (no interpolation, no injection). */
|
|
88
|
+
function bashSingleQuote(value) {
|
|
89
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
90
|
+
}
|
|
91
|
+
/** The measurable-form detector snippet per tool (sets `__measurable=1` only for the batch form). */
|
|
92
|
+
function detectorSnippet(tool) {
|
|
93
|
+
if (tool === "codex") {
|
|
94
|
+
return [
|
|
95
|
+
"# Codex measurable batch form: `codex exec ... --json` (an interactive `codex` is NOT measured).",
|
|
96
|
+
"__has_exec=0; __has_json=0",
|
|
97
|
+
'for __a in "$@"; do',
|
|
98
|
+
' case "$__a" in',
|
|
99
|
+
" exec) __has_exec=1 ;;",
|
|
100
|
+
" --json) __has_json=1 ;;",
|
|
101
|
+
" esac",
|
|
102
|
+
"done",
|
|
103
|
+
'if [ "$__has_exec" = "1" ] && [ "$__has_json" = "1" ]; then __measurable=1; fi'
|
|
104
|
+
].join("\n");
|
|
105
|
+
}
|
|
106
|
+
// cursor
|
|
107
|
+
return [
|
|
108
|
+
"# Cursor measurable batch form: `--output-format json` (or =json / stream-json). Interactive is NOT measured.",
|
|
109
|
+
'__prev=""',
|
|
110
|
+
'for __a in "$@"; do',
|
|
111
|
+
' case "$__a" in',
|
|
112
|
+
" --output-format=json|--output-format=stream-json) __measurable=1 ;;",
|
|
113
|
+
' json|stream-json) if [ "$__prev" = "--output-format" ]; then __measurable=1; fi ;;',
|
|
114
|
+
" esac",
|
|
115
|
+
' __prev="$__a"',
|
|
116
|
+
"done"
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* STDIN-BOUNDARY apply block (charter 2026-07-04-run-12 — the FIRST real before-call mutation surface).
|
|
121
|
+
* ONLY Codex has a documented stdin prompt boundary (`codex exec` reads instructions from stdin when no
|
|
122
|
+
* positional prompt is given), so this block is emitted ONLY for the codex shim; the cursor shim stays
|
|
123
|
+
* byte-for-byte unchanged (recommendation-only). When stdin is PIPED, the shim buffers it, lets
|
|
124
|
+
* `precall` decide (safe boundary + avoidable context + explicit `/dev/tty` approval → compacted stdin
|
|
125
|
+
* written to a file; anything else → nothing written), and feeds the chosen stream to the real binary.
|
|
126
|
+
* Every failure feeds the ORIGINAL buffered stdin unchanged (fail-open); the exit code is preserved via
|
|
127
|
+
* PIPESTATUS. Interactive runs (stdin is a tty) skip this and use the recommendation-only path.
|
|
128
|
+
*/
|
|
129
|
+
function stdinApplyBlock(tool) {
|
|
130
|
+
if (tool !== "codex")
|
|
131
|
+
return ""; // Cursor: no documented stdin prompt boundary → no mediation.
|
|
132
|
+
const cfg = SHIM_TOOLS[tool];
|
|
133
|
+
return ` # STDIN-BOUNDARY apply (Codex, run-12) — mutate the model's input ONLY here, ONLY after approval.
|
|
134
|
+
# Engage ONLY when stdin is PIPED (not a tty) AND an ARGV-ONLY probe confirms stdin is the whole prompt
|
|
135
|
+
# (no positional prompt). The probe reads NO stdin, so we never buffer (never risk blocking) unless
|
|
136
|
+
# stdin genuinely IS the prompt. Anything else falls through to the recommendation-only path below.
|
|
137
|
+
if [ ! -t 0 ] && "$COMPACTION_BIN" precall ${cfg.captureTool} --stdin-boundary-check -- "$@" </dev/null >/dev/null 2>&1; then
|
|
138
|
+
__ttyavail=0; if [ -t 1 ]; then __ttyavail=1; fi
|
|
139
|
+
__sfile="$(mktemp "\${TMPDIR:-/tmp}/compaction-${cfg.shimName}-stdin-XXXXXX" 2>/dev/null)"
|
|
140
|
+
__cfile="$(mktemp "\${TMPDIR:-/tmp}/compaction-${cfg.shimName}-cpct-XXXXXX" 2>/dev/null)"
|
|
141
|
+
if [ -n "$__sfile" ] && [ -n "$__cfile" ]; then
|
|
142
|
+
cat > "$__sfile"
|
|
143
|
+
# precall writes the compacted stdin to __cfile ONLY on explicit approval; its own stdin is
|
|
144
|
+
# /dev/null and its stdout is discarded (the [y/n/v] prompt goes to /dev/tty). '|| true' → the
|
|
145
|
+
# step can never break the tool. An empty __cfile means "feed the ORIGINAL" (the fail-closed default).
|
|
146
|
+
"$COMPACTION_BIN" precall ${cfg.captureTool} --interactive "$__ttyavail" --stdin-file "$__sfile" --compacted-out "$__cfile" -- "$@" </dev/null >/dev/null 2>&1 || true
|
|
147
|
+
__infile="$__sfile"
|
|
148
|
+
if [ -s "$__cfile" ]; then __infile="$__cfile"; fi
|
|
149
|
+
__tmp="$(mktemp "\${TMPDIR:-/tmp}/compaction-${cfg.shimName}-XXXXXX" 2>/dev/null)" || { "$REAL_BIN" "$@" < "$__infile"; __c=$?; rm -f "$__sfile" "$__cfile" >/dev/null 2>&1 || true; exit "$__c"; }
|
|
150
|
+
"$REAL_BIN" "$@" < "$__infile" | tee "$__tmp"
|
|
151
|
+
__code=\${PIPESTATUS[0]}
|
|
152
|
+
${captureSnippet(tool)}
|
|
153
|
+
rm -f "$__tmp" "$__sfile" "$__cfile" >/dev/null 2>&1 || true
|
|
154
|
+
exit "$__code"
|
|
155
|
+
fi
|
|
156
|
+
rm -f "$__sfile" "$__cfile" >/dev/null 2>&1 || true
|
|
157
|
+
fi
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
/** The content-free capture call the shim runs AFTER the real binary exits (best-effort, silenced). */
|
|
161
|
+
function captureSnippet(tool) {
|
|
162
|
+
const cfg = SHIM_TOOLS[tool];
|
|
163
|
+
if (cfg.forwardArgs) {
|
|
164
|
+
// Cursor: forward the original invocation so the bridge can LOCAL-ESTIMATE the input count
|
|
165
|
+
// (content-free — only the token COUNT rides on the event; the prompt text is never stored).
|
|
166
|
+
return `"$COMPACTION_BIN" capture ${cfg.captureTool} --from-shim "$__tmp" -- ${cfg.shimName} "$@" >/dev/null 2>&1 || true`;
|
|
167
|
+
}
|
|
168
|
+
return `"$COMPACTION_BIN" capture ${cfg.captureTool} --from-shim "$__tmp" >/dev/null 2>&1 || true`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Generate the transparent capturing shim script for `tool`, baking in the resolved absolute
|
|
172
|
+
* `realBin` path. The script: (1) execs the real binary with all args + inherited stdio + preserved
|
|
173
|
+
* exit code; (2) for the measurable batch form ONLY, runs a FAIL-OPEN, CONTENT-FREE before-call
|
|
174
|
+
* recommendation step (`compaction precall`; recommendation-only — it never mutates the input), then
|
|
175
|
+
* tees stdout to a temp copy and calls the content-free capture bridge, then deletes the temp;
|
|
176
|
+
* (3) passes everything else through untouched.
|
|
177
|
+
*/
|
|
178
|
+
export function generateShimScript(tool, realBin) {
|
|
179
|
+
const cfg = SHIM_TOOLS[tool];
|
|
180
|
+
return `#!/usr/bin/env bash
|
|
181
|
+
# ${SHIM_MARKER}: ${tool}
|
|
182
|
+
# Compaction transparent capturing shim for "${cfg.shimName}" — installed by \`compaction init --connect\`.
|
|
183
|
+
#
|
|
184
|
+
# It TRANSPARENTLY execs the REAL binary below with ALL args, inherited stdio, and the preserved exit
|
|
185
|
+
# code. For the MEASURABLE batch form ONLY it captures provider usage token fields as a CONTENT-FREE
|
|
186
|
+
# side effect (no prompt/response/message text is ever stored). Interactive / other invocations pass
|
|
187
|
+
# through untouched and are NOT measured (never faked).
|
|
188
|
+
#
|
|
189
|
+
# The real binary path was resolved on your PATH BEFORE this shim was installed, so the shim can never
|
|
190
|
+
# recurse into itself. This file NEVER replaces the real binary.
|
|
191
|
+
# Uninstall (reversible): compaction init --disconnect ${tool}
|
|
192
|
+
set -u
|
|
193
|
+
|
|
194
|
+
REAL_BIN=${bashSingleQuote(realBin)}
|
|
195
|
+
COMPACTION_BIN="\${COMPACTION_BIN:-compaction}"
|
|
196
|
+
|
|
197
|
+
if [ ! -x "$REAL_BIN" ]; then
|
|
198
|
+
echo "compaction shim: the recorded ${cfg.shimName} binary was not found at $REAL_BIN — re-run 'compaction init --connect' to re-resolve it." 1>&2
|
|
199
|
+
exit 127
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
__measurable=0
|
|
203
|
+
${detectorSnippet(tool)}
|
|
204
|
+
|
|
205
|
+
if [ "$__measurable" = "1" ]; then
|
|
206
|
+
${stdinApplyBlock(tool)} # BEFORE-CALL recommendation (charter 2026-07-04-run-10) — RECOMMENDATION-ONLY, FAIL-OPEN, CONTENT-FREE.
|
|
207
|
+
# 'compaction precall' is invoked purely for its side effects: when the input carries avoidable
|
|
208
|
+
# duplicated context it surfaces a recommendation on /dev/tty (interactive terminals only) and records
|
|
209
|
+
# ONE content-free activity event. It NEVER mutates the input — the real binary below always runs on
|
|
210
|
+
# the ORIGINAL "$@". precall's stdin is /dev/null (so it can never steal the tool's stdin) and its
|
|
211
|
+
# stdout/stderr are discarded (so it can never alter the tool's output); the human recommendation goes
|
|
212
|
+
# to /dev/tty directly. '|| true' makes any precall failure a no-op, so this step can never break or
|
|
213
|
+
# block the tool. NO-TTY / piped runs (__itty=0) get NO prompt and NO mutation.
|
|
214
|
+
__itty=0
|
|
215
|
+
if [ -t 0 ] && [ -t 1 ]; then __itty=1; fi
|
|
216
|
+
"$COMPACTION_BIN" precall ${cfg.captureTool} --interactive "$__itty" -- "$@" </dev/null >/dev/null 2>&1 || true
|
|
217
|
+
|
|
218
|
+
__tmp="$(mktemp "\${TMPDIR:-/tmp}/compaction-${cfg.shimName}-XXXXXX" 2>/dev/null)" || exec "$REAL_BIN" "$@"
|
|
219
|
+
# Transparent: the user sees identical stdout via tee; stderr passes straight through; the real
|
|
220
|
+
# binary's exit code is preserved via PIPESTATUS (never tee's or the capture call's).
|
|
221
|
+
"$REAL_BIN" "$@" | tee "$__tmp"
|
|
222
|
+
__code=\${PIPESTATUS[0]}
|
|
223
|
+
# Content-free capture side effect — never touches the user's stdout/stderr or exit code.
|
|
224
|
+
${captureSnippet(tool)}
|
|
225
|
+
rm -f "$__tmp" >/dev/null 2>&1 || true
|
|
226
|
+
exit "$__code"
|
|
227
|
+
else
|
|
228
|
+
exec "$REAL_BIN" "$@"
|
|
229
|
+
fi
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
/** Does the file at `shimPath` look like a Compaction shim (marker present)? Content-free identity. */
|
|
233
|
+
export function fileIsCompactionShim(shimPath) {
|
|
234
|
+
try {
|
|
235
|
+
return readFileSync(shimPath, "utf8").includes(`${SHIM_MARKER}:`);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** The exact PATH line the user adds (or `--write-shell-config` appends) to activate the shim dir. */
|
|
242
|
+
export function shimExportLine(env = process.env) {
|
|
243
|
+
const dir = resolveShimDir(env);
|
|
244
|
+
// Prefer a $HOME-relative form for the default location (matches what the user will recognize).
|
|
245
|
+
const home = env.HOME && env.HOME.trim() !== "" ? env.HOME : homedir();
|
|
246
|
+
const defaultDir = path.join(home, ".compaction", "shims");
|
|
247
|
+
const shown = path.resolve(dir) === path.resolve(defaultDir) ? '$HOME/.compaction/shims' : dir;
|
|
248
|
+
return `export PATH="${shown}:$PATH"`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve-verify whether the shim for `tool` is ACTIVE: the shim file exists AND the tool name
|
|
252
|
+
* resolves to that shim on the current PATH (the shim dir is on PATH ahead of the real binary).
|
|
253
|
+
*/
|
|
254
|
+
export function verifyShimActive(tool, env = process.env) {
|
|
255
|
+
const shimDir = resolveShimDir(env);
|
|
256
|
+
const shimPath = shimPathFor(tool, env);
|
|
257
|
+
const installed = existsSync(shimPath) && fileIsCompactionShim(shimPath);
|
|
258
|
+
// Resolve the shim NAME on the FULL PATH (do NOT exclude the shim dir — we want to know whether it
|
|
259
|
+
// wins). onPath is true only when that first match IS our shim file.
|
|
260
|
+
const resolvedPath = resolveExecutableOnPath(SHIM_TOOLS[tool].shimName, env);
|
|
261
|
+
const onPath = installed && resolvedPath !== undefined && path.resolve(resolvedPath) === path.resolve(shimPath);
|
|
262
|
+
return {
|
|
263
|
+
tool,
|
|
264
|
+
shimPath,
|
|
265
|
+
installed,
|
|
266
|
+
...(resolvedPath !== undefined ? { resolvedPath } : {}),
|
|
267
|
+
onPath,
|
|
268
|
+
active: installed && onPath,
|
|
269
|
+
exportLine: shimExportLine(env),
|
|
270
|
+
shimDir
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const RECORD_FILENAME = ".shim-record.json";
|
|
274
|
+
function recordPath(env) {
|
|
275
|
+
return path.join(resolveShimDir(env), RECORD_FILENAME);
|
|
276
|
+
}
|
|
277
|
+
function readRecord(env) {
|
|
278
|
+
try {
|
|
279
|
+
const parsed = JSON.parse(readFileSync(recordPath(env), "utf8"));
|
|
280
|
+
if (parsed && typeof parsed === "object" && parsed.shims)
|
|
281
|
+
return parsed;
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
/* fall through to empty */
|
|
285
|
+
}
|
|
286
|
+
return { version: 1, shims: {} };
|
|
287
|
+
}
|
|
288
|
+
function writeRecord(env, record) {
|
|
289
|
+
writeFileSync(recordPath(env), `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Install the shim for `tool`: resolve the real binary (excluding our shim dir so it can never point
|
|
293
|
+
* at itself), write the shim script + a real-path record, then RE-READ the shim file to confirm it
|
|
294
|
+
* landed with the marker and the recorded real path, and finally resolve-verify PATH activation. The
|
|
295
|
+
* caller may only present "connected" for `installed-active` / `already-active` — never for
|
|
296
|
+
* `installed-not-on-path` (print the export line) or `verify-failed` (nothing is claimed).
|
|
297
|
+
*/
|
|
298
|
+
export function installToolShim(tool, env = process.env, options = {}) {
|
|
299
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
300
|
+
const shimDir = resolveShimDir(env);
|
|
301
|
+
const shimPath = shimPathFor(tool, env);
|
|
302
|
+
const shimName = SHIM_TOOLS[tool].shimName;
|
|
303
|
+
const exportLine = shimExportLine(env);
|
|
304
|
+
// Idempotent: already installed + active → report without rewriting.
|
|
305
|
+
const before = verifyShimActive(tool, env);
|
|
306
|
+
if (before.active) {
|
|
307
|
+
const record = readRecord(env);
|
|
308
|
+
return {
|
|
309
|
+
tool,
|
|
310
|
+
status: "already-active",
|
|
311
|
+
shimName,
|
|
312
|
+
shimDir,
|
|
313
|
+
shimPath,
|
|
314
|
+
...(record.shims[tool]?.realBin ? { realBin: record.shims[tool].realBin } : {}),
|
|
315
|
+
verification: before,
|
|
316
|
+
exportLine
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// Resolve the REAL binary BEFORE (re)writing the shim, excluding the shim dir (never itself).
|
|
320
|
+
const realBin = resolveExecutableOnPath(shimName, env, [shimDir]);
|
|
321
|
+
if (realBin === undefined) {
|
|
322
|
+
return {
|
|
323
|
+
tool,
|
|
324
|
+
status: "no-real-binary",
|
|
325
|
+
shimName,
|
|
326
|
+
shimDir,
|
|
327
|
+
shimPath,
|
|
328
|
+
verification: before,
|
|
329
|
+
exportLine
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
mkdirSync(shimDir, { recursive: true });
|
|
333
|
+
writeFileSync(shimPath, generateShimScript(tool, realBin), "utf8");
|
|
334
|
+
chmodSync(shimPath, 0o755);
|
|
335
|
+
const record = readRecord(env);
|
|
336
|
+
record.shims[tool] = { shimName, realBin, installedAt: now() };
|
|
337
|
+
writeRecord(env, record);
|
|
338
|
+
// RE-READ to verify the write landed (marker present + recorded real path baked in + executable).
|
|
339
|
+
let reReadOk = false;
|
|
340
|
+
try {
|
|
341
|
+
const contents = readFileSync(shimPath, "utf8");
|
|
342
|
+
const st = statSync(shimPath);
|
|
343
|
+
reReadOk = contents.includes(`${SHIM_MARKER}: ${tool}`) && contents.includes(realBin) && (st.mode & 0o111) !== 0;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
reReadOk = false;
|
|
347
|
+
}
|
|
348
|
+
if (!reReadOk) {
|
|
349
|
+
return { tool, status: "verify-failed", shimName, shimDir, shimPath, realBin, verification: verifyShimActive(tool, env), exportLine };
|
|
350
|
+
}
|
|
351
|
+
const verification = verifyShimActive(tool, env);
|
|
352
|
+
return {
|
|
353
|
+
tool,
|
|
354
|
+
status: verification.active ? "installed-active" : "installed-not-on-path",
|
|
355
|
+
shimName,
|
|
356
|
+
shimDir,
|
|
357
|
+
shimPath,
|
|
358
|
+
realBin,
|
|
359
|
+
verification,
|
|
360
|
+
exportLine
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** Remove the shim file + its record entry (reversible: the real binary was never touched). */
|
|
364
|
+
export function uninstallToolShim(tool, env = process.env) {
|
|
365
|
+
const shimPath = shimPathFor(tool, env);
|
|
366
|
+
const existed = existsSync(shimPath);
|
|
367
|
+
if (existed)
|
|
368
|
+
rmSync(shimPath, { force: true });
|
|
369
|
+
const record = readRecord(env);
|
|
370
|
+
if (record.shims[tool]) {
|
|
371
|
+
delete record.shims[tool];
|
|
372
|
+
try {
|
|
373
|
+
writeRecord(env, record);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
/* record dir may already be gone; not fatal */
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return { tool, status: existed ? "removed" : "not-installed", shimPath };
|
|
380
|
+
}
|
|
381
|
+
/* ------------------------------------------------------------------------------------------------
|
|
382
|
+
* OPT-IN, reversible shell-rc editing (only with an explicit --write-shell-config gesture).
|
|
383
|
+
* ---------------------------------------------------------------------------------------------- */
|
|
384
|
+
const RC_BLOCK_START = "# >>> compaction shim PATH (added by `compaction init --connect --write-shell-config`) >>>";
|
|
385
|
+
const RC_BLOCK_END = "# <<< compaction shim PATH <<<";
|
|
386
|
+
/** Pick the user's shell rc file from $SHELL (zsh → ~/.zshrc, else ~/.bashrc). Content-free. */
|
|
387
|
+
export function resolveShellRcPath(env = process.env) {
|
|
388
|
+
const home = env.HOME && env.HOME.trim() !== "" ? env.HOME : homedir();
|
|
389
|
+
const shell = env.SHELL ?? "";
|
|
390
|
+
if (shell.includes("zsh"))
|
|
391
|
+
return path.join(home, ".zshrc");
|
|
392
|
+
return path.join(home, ".bashrc");
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Append the shim PATH line to the user's shell rc — ONLY when the caller passes an explicit
|
|
396
|
+
* `--write-shell-config` gesture. A `.compaction.bak` backup is written FIRST (reversible), the block
|
|
397
|
+
* is clearly marked, and the append is idempotent (already-present → no change). Never overwrites.
|
|
398
|
+
*/
|
|
399
|
+
export function writeShellConfigPathLine(env = process.env) {
|
|
400
|
+
const rcPath = resolveShellRcPath(env);
|
|
401
|
+
const exportLine = shimExportLine(env);
|
|
402
|
+
const existing = existsSync(rcPath) ? readFileSync(rcPath, "utf8") : "";
|
|
403
|
+
if (existing.includes(RC_BLOCK_START) || existing.includes(exportLine)) {
|
|
404
|
+
return { status: "already-present", rcPath, exportLine };
|
|
405
|
+
}
|
|
406
|
+
const backupPath = `${rcPath}.compaction.bak`;
|
|
407
|
+
let createdBackup;
|
|
408
|
+
if (existsSync(rcPath)) {
|
|
409
|
+
copyFileSync(rcPath, backupPath);
|
|
410
|
+
createdBackup = backupPath;
|
|
411
|
+
}
|
|
412
|
+
const block = `\n${RC_BLOCK_START}\n${exportLine}\n${RC_BLOCK_END}\n`;
|
|
413
|
+
writeFileSync(rcPath, existing + block, "utf8");
|
|
414
|
+
return {
|
|
415
|
+
status: existing === "" ? "no-rc-file-created" : "appended",
|
|
416
|
+
rcPath,
|
|
417
|
+
...(createdBackup ? { backupPath: createdBackup } : {}),
|
|
418
|
+
exportLine
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Reverse `writeShellConfigPathLine`: strip the marked block from the rc file. If a
|
|
423
|
+
* `.compaction.bak` backup exists it is restored verbatim (the fully reversible path); otherwise the
|
|
424
|
+
* marked block is removed in place.
|
|
425
|
+
*/
|
|
426
|
+
export function removeShellConfigPathLine(env = process.env) {
|
|
427
|
+
const rcPath = resolveShellRcPath(env);
|
|
428
|
+
if (!existsSync(rcPath))
|
|
429
|
+
return { status: "no-rc-file", rcPath };
|
|
430
|
+
const backupPath = `${rcPath}.compaction.bak`;
|
|
431
|
+
const contents = readFileSync(rcPath, "utf8");
|
|
432
|
+
if (!contents.includes(RC_BLOCK_START))
|
|
433
|
+
return { status: "not-present", rcPath };
|
|
434
|
+
if (existsSync(backupPath)) {
|
|
435
|
+
copyFileSync(backupPath, rcPath);
|
|
436
|
+
rmSync(backupPath, { force: true });
|
|
437
|
+
return { status: "removed", rcPath, backupRestored: backupPath };
|
|
438
|
+
}
|
|
439
|
+
// No backup (block appended to a then-nonexistent file): remove the marked block only.
|
|
440
|
+
const stripped = contents.replace(new RegExp(`\\n?${escapeRegExp(RC_BLOCK_START)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}\\n?`, "g"), "\n");
|
|
441
|
+
writeFileSync(rcPath, stripped, "utf8");
|
|
442
|
+
return { status: "removed", rcPath };
|
|
443
|
+
}
|
|
444
|
+
function escapeRegExp(value) {
|
|
445
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=tool-shim.js.map
|
|
@@ -6,7 +6,7 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
6
6
|
title: z.ZodString;
|
|
7
7
|
artifactVersion: z.ZodOptional<z.ZodString>;
|
|
8
8
|
artifact_version: z.ZodOptional<z.ZodString>;
|
|
9
|
-
source: z.ZodOptional<z.ZodEnum<["manual", "cli_wrapper", "local_command", "demo", "real_captured", "provider_usage", "codex_import"]>>;
|
|
9
|
+
source: z.ZodOptional<z.ZodEnum<["manual", "cli_wrapper", "local_command", "demo", "real_captured", "provider_usage", "codex_import", "cursor_import"]>>;
|
|
10
10
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
11
11
|
created_at: z.ZodOptional<z.ZodString>;
|
|
12
12
|
generatedAt: z.ZodOptional<z.ZodString>;
|
|
@@ -20,13 +20,13 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
20
20
|
}, "strip", z.ZodTypeAny, {
|
|
21
21
|
command: string;
|
|
22
22
|
args: string[];
|
|
23
|
-
cwd?: string | undefined;
|
|
24
23
|
shell?: string | undefined;
|
|
24
|
+
cwd?: string | undefined;
|
|
25
25
|
}, {
|
|
26
26
|
command: string;
|
|
27
27
|
args: string[];
|
|
28
|
-
cwd?: string | undefined;
|
|
29
28
|
shell?: string | undefined;
|
|
29
|
+
cwd?: string | undefined;
|
|
30
30
|
}>>;
|
|
31
31
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
32
32
|
duration_ms: z.ZodOptional<z.ZodNumber>;
|
|
@@ -69,12 +69,12 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
69
69
|
command?: {
|
|
70
70
|
command: string;
|
|
71
71
|
args: string[];
|
|
72
|
-
cwd?: string | undefined;
|
|
73
72
|
shell?: string | undefined;
|
|
73
|
+
cwd?: string | undefined;
|
|
74
74
|
} | undefined;
|
|
75
75
|
artifactVersion?: string | undefined;
|
|
76
76
|
artifact_version?: string | undefined;
|
|
77
|
-
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | undefined;
|
|
77
|
+
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import" | undefined;
|
|
78
78
|
createdAt?: string | undefined;
|
|
79
79
|
created_at?: string | undefined;
|
|
80
80
|
generatedAt?: string | undefined;
|
|
@@ -98,12 +98,12 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
98
98
|
command?: {
|
|
99
99
|
command: string;
|
|
100
100
|
args: string[];
|
|
101
|
-
cwd?: string | undefined;
|
|
102
101
|
shell?: string | undefined;
|
|
102
|
+
cwd?: string | undefined;
|
|
103
103
|
} | undefined;
|
|
104
104
|
artifactVersion?: string | undefined;
|
|
105
105
|
artifact_version?: string | undefined;
|
|
106
|
-
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | undefined;
|
|
106
|
+
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import" | undefined;
|
|
107
107
|
createdAt?: string | undefined;
|
|
108
108
|
created_at?: string | undefined;
|
|
109
109
|
generatedAt?: string | undefined;
|
|
@@ -127,12 +127,12 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
127
127
|
command?: {
|
|
128
128
|
command: string;
|
|
129
129
|
args: string[];
|
|
130
|
-
cwd?: string | undefined;
|
|
131
130
|
shell?: string | undefined;
|
|
131
|
+
cwd?: string | undefined;
|
|
132
132
|
} | undefined;
|
|
133
133
|
artifactVersion?: string | undefined;
|
|
134
134
|
artifact_version?: string | undefined;
|
|
135
|
-
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | undefined;
|
|
135
|
+
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import" | undefined;
|
|
136
136
|
createdAt?: string | undefined;
|
|
137
137
|
created_at?: string | undefined;
|
|
138
138
|
generatedAt?: string | undefined;
|
|
@@ -156,12 +156,12 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
156
156
|
command?: {
|
|
157
157
|
command: string;
|
|
158
158
|
args: string[];
|
|
159
|
-
cwd?: string | undefined;
|
|
160
159
|
shell?: string | undefined;
|
|
160
|
+
cwd?: string | undefined;
|
|
161
161
|
} | undefined;
|
|
162
162
|
artifactVersion?: string | undefined;
|
|
163
163
|
artifact_version?: string | undefined;
|
|
164
|
-
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | undefined;
|
|
164
|
+
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import" | undefined;
|
|
165
165
|
createdAt?: string | undefined;
|
|
166
166
|
created_at?: string | undefined;
|
|
167
167
|
generatedAt?: string | undefined;
|
|
@@ -185,12 +185,12 @@ export declare const agentTraceSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
185
185
|
command?: {
|
|
186
186
|
command: string;
|
|
187
187
|
args: string[];
|
|
188
|
-
cwd?: string | undefined;
|
|
189
188
|
shell?: string | undefined;
|
|
189
|
+
cwd?: string | undefined;
|
|
190
190
|
} | undefined;
|
|
191
191
|
artifactVersion?: string | undefined;
|
|
192
192
|
artifact_version?: string | undefined;
|
|
193
|
-
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | undefined;
|
|
193
|
+
source?: "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import" | undefined;
|
|
194
194
|
createdAt?: string | undefined;
|
|
195
195
|
created_at?: string | undefined;
|
|
196
196
|
generatedAt?: string | undefined;
|
|
@@ -16,11 +16,11 @@ const commandMetadataSchema = z.object({
|
|
|
16
16
|
cwd: z.string().min(1).optional(),
|
|
17
17
|
shell: z.string().min(1).optional()
|
|
18
18
|
});
|
|
19
|
-
// `codex_import`
|
|
20
|
-
// JSONL export carries no command/exitCode/durationMs
|
|
21
|
-
// subjected to command-trace validation (that is why the
|
|
22
|
-
// reuse `cli_wrapper`/`local_command`). Keep this exclusion in
|
|
23
|
-
// `imported_local` evidence mapping in safety-report.ts.
|
|
19
|
+
// `codex_import` (and `cursor_import`) are deliberately NOT command traces: an imported
|
|
20
|
+
// `codex exec --json` JSONL / Cursor session export carries no command/exitCode/durationMs
|
|
21
|
+
// provenance, so they must not be subjected to command-trace validation (that is why the
|
|
22
|
+
// import paths could not reuse `cli_wrapper`/`local_command`). Keep this exclusion in
|
|
23
|
+
// lockstep with the `imported_local` evidence mapping in safety-report.ts.
|
|
24
24
|
function isCommandTrace(source) {
|
|
25
25
|
return source === "cli_wrapper" || source === "local_command";
|
|
26
26
|
}
|
|
@@ -30,7 +30,7 @@ const rawAgentTraceSchema = z
|
|
|
30
30
|
title: z.string().min(1),
|
|
31
31
|
artifactVersion: z.string().min(1).optional(),
|
|
32
32
|
artifact_version: z.string().min(1).optional(),
|
|
33
|
-
source: z.enum(["manual", "cli_wrapper", "local_command", "demo", "real_captured", "provider_usage", "codex_import"]).optional(),
|
|
33
|
+
source: z.enum(["manual", "cli_wrapper", "local_command", "demo", "real_captured", "provider_usage", "codex_import", "cursor_import"]).optional(),
|
|
34
34
|
createdAt: isoTimestampSchema.optional(),
|
|
35
35
|
created_at: isoTimestampSchema.optional(),
|
|
36
36
|
generatedAt: isoTimestampSchema.optional(),
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type TraceRole = "system" | "user" | "assistant" | "tool" | "stdout" | "stderr";
|
|
2
|
-
export type TraceSource = "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import";
|
|
2
|
+
export type TraceSource = "manual" | "cli_wrapper" | "local_command" | "demo" | "real_captured" | "provider_usage" | "codex_import" | "cursor_import";
|
|
3
3
|
export interface TraceCommandMetadata {
|
|
4
4
|
command: string;
|
|
5
5
|
args: string[];
|
|
@@ -43,7 +43,17 @@ export interface CostEstimate {
|
|
|
43
43
|
totalCostUsd: number;
|
|
44
44
|
}
|
|
45
45
|
export interface WasteFinding {
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* `superseded_same_source_read` (input-compaction category 1, accepted design
|
|
48
|
+
* docs/design/input-compaction-policy-reach.md D2): the SAME source was read multiple times and
|
|
49
|
+
* a LATER read is authoritative; an EARLIER full copy whose content GENUINELY DIFFERS (whitespace-
|
|
50
|
+
* normalized) from the latest can be replaced by a recoverable pointer to the retained original.
|
|
51
|
+
* ADDITIVE to `repeated_tool_output` (which handles identical-up-to-whitespace repeats) and never
|
|
52
|
+
* weakens it: an identical earlier copy is LEFT to `repeated_tool_output`, never category-1-compacted.
|
|
53
|
+
* Convention for this category's `messageIds`: `[0]` = the KEPT latest authoritative read (verbatim),
|
|
54
|
+
* `[1..]` = the earlier superseded reads to compact (each recoverable from the retained original).
|
|
55
|
+
*/
|
|
56
|
+
category: "repeated_tool_output" | "stale_context" | "verbose_observation" | "repeated_skill_injection" | "superseded_same_source_read";
|
|
47
57
|
messageIds: string[];
|
|
48
58
|
summary: string;
|
|
49
59
|
estimatedTokens: number;
|
|
@@ -5,4 +5,24 @@ export interface WasteDetectionThresholds {
|
|
|
5
5
|
minDuplicateCharacters?: number;
|
|
6
6
|
}
|
|
7
7
|
export declare function detectWaste(trace: AgentTrace, thresholds?: WasteDetectionThresholds): WasteFinding[];
|
|
8
|
+
/**
|
|
9
|
+
* Input-compaction category 1: detect SUPERSEDED same-source reads.
|
|
10
|
+
*
|
|
11
|
+
* When the SAME source (provable source identity) is read ≥2 times, the LATEST read is authoritative
|
|
12
|
+
* (preserved verbatim). Each EARLIER read whose whitespace-normalized content GENUINELY DIFFERS from
|
|
13
|
+
* the latest's is a superseded candidate that can be replaced by a recoverable pointer to the
|
|
14
|
+
* retained original. Conservative & additive:
|
|
15
|
+
* - Outputs with NO provable source identity are skipped (not eligible).
|
|
16
|
+
* - An earlier output that is identical-up-to-whitespace to the latest is LEFT to the existing
|
|
17
|
+
* `repeated_tool_output` policy (NOT category-1-compacted) — no overlap, no double-compaction.
|
|
18
|
+
* - Each compacted (earlier) output must meet the SAME thresholds detectWaste uses
|
|
19
|
+
* (minDuplicateTokens / minDuplicateCharacters).
|
|
20
|
+
*
|
|
21
|
+
* Finding convention: `messageIds[0]` = KEPT latest authoritative read; `messageIds[1..]` = earlier
|
|
22
|
+
* superseded reads to compact (estimatedTokens = sum of the compacted earlier reads' estimates).
|
|
23
|
+
*
|
|
24
|
+
* NO scoring formula, weight, relevance, or learned logic — only deterministic source-identity
|
|
25
|
+
* equality + whitespace-normalized content difference + the existing min-tokens/chars thresholds.
|
|
26
|
+
*/
|
|
27
|
+
export declare function detectSupersededSameSourceReads(trace: AgentTrace, thresholds?: WasteDetectionThresholds): WasteFinding[];
|
|
8
28
|
export declare function getCompactedMessageIds(findings: WasteFinding[]): Set<string>;
|