claudelink 1.4.3 → 1.5.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.
Files changed (47) hide show
  1. package/README.md +34 -1
  2. package/dist/arm-compact.d.ts +2 -0
  3. package/dist/arm-compact.js +182 -0
  4. package/dist/arm-compact.js.map +1 -0
  5. package/dist/cli.js +145 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compact-analyzer.d.ts +67 -0
  8. package/dist/compact-analyzer.js +309 -0
  9. package/dist/compact-analyzer.js.map +1 -0
  10. package/dist/compact-decision.d.ts +19 -0
  11. package/dist/compact-decision.js +47 -0
  12. package/dist/compact-decision.js.map +1 -0
  13. package/dist/compact-executor.d.ts +27 -0
  14. package/dist/compact-executor.js +160 -0
  15. package/dist/compact-executor.js.map +1 -0
  16. package/dist/context-watcher-settings.d.ts +21 -0
  17. package/dist/context-watcher-settings.js +176 -0
  18. package/dist/context-watcher-settings.js.map +1 -0
  19. package/dist/context-watcher.d.ts +70 -0
  20. package/dist/context-watcher.js +1005 -0
  21. package/dist/context-watcher.js.map +1 -0
  22. package/dist/db.d.ts +22 -1
  23. package/dist/db.js +184 -5
  24. package/dist/db.js.map +1 -1
  25. package/dist/hooks/stop-hook.js +30 -1
  26. package/dist/hooks/stop-hook.js.map +1 -1
  27. package/dist/index.js +144 -12
  28. package/dist/index.js.map +1 -1
  29. package/dist/manual-decision.d.ts +23 -0
  30. package/dist/manual-decision.js +67 -0
  31. package/dist/manual-decision.js.map +1 -0
  32. package/dist/prompt-clear.d.ts +8 -0
  33. package/dist/prompt-clear.js +33 -0
  34. package/dist/prompt-clear.js.map +1 -0
  35. package/dist/recovery-watcher.d.ts +6 -0
  36. package/dist/recovery-watcher.js +20 -0
  37. package/dist/recovery-watcher.js.map +1 -1
  38. package/dist/role-collision.d.ts +6 -0
  39. package/dist/role-collision.js +51 -0
  40. package/dist/role-collision.js.map +1 -0
  41. package/dist/ui-server.d.ts +4 -0
  42. package/dist/ui-server.js +899 -20
  43. package/dist/ui-server.js.map +1 -1
  44. package/dist/usage-reader.d.ts +79 -0
  45. package/dist/usage-reader.js +553 -0
  46. package/dist/usage-reader.js.map +1 -0
  47. package/package.json +8 -2
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ // Compact-loss analyzer (read-only) — the calibration engine for intelligent
3
+ // auto-compact. Scans the fleet's existing transcripts for compact events and,
4
+ // for each, records the ground-truth effect + four intelligence-LOSS signals
5
+ // in the surrounding turns. Run on NATURALLY-OCCURRING compacts (the founder's
6
+ // manual /compact discipline + Claude Code's built-in auto-compact), so we
7
+ // build a labeled loss dataset and validate the heuristics BEFORE the watcher
8
+ // ever auto-triggers one. No injection, no terminal contact.
9
+ //
10
+ // Ground truth is exact: Claude Code records each compact as a transcript line
11
+ // { type:"system", subtype:"compact_boundary",
12
+ // compactMetadata:{ trigger:"auto"|"manual", preTokens, postTokens, durationMs } }
13
+ // so we know the real context size before/after — no estimation.
14
+ //
15
+ // CAVEAT (carried into the case study): manual compacts fire at task
16
+ // boundaries; the auto-trigger fires at an economic threshold (mid-task). These
17
+ // are different populations. This analyzer validates the loss-signal DETECTION
18
+ // MECHANICS on real events; the loss RATES it reports for manual compacts are
19
+ // NOT extrapolatable to auto-trigger rates — the armed soak measures those.
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.pathALossBaseline = pathALossBaseline;
25
+ exports.analyzeCompactEvents = analyzeCompactEvents;
26
+ const fs_1 = __importDefault(require("fs"));
27
+ const path_1 = __importDefault(require("path"));
28
+ const readline_1 = __importDefault(require("readline"));
29
+ const usage_reader_js_1 = require("./usage-reader.js");
30
+ // Window sizes (in real user/assistant turns) for signal computation, kept
31
+ // tight so legitimate repeats far from a compact don't register as loss.
32
+ const PRE_WINDOW = 12;
33
+ const POST_WINDOW = 12;
34
+ const REEXPLAIN_CHARS = 1500; // a human user message longer than this = re-explain
35
+ // Primary distinguishing argument per tool, so a signature captures "same
36
+ // action" without being so loose it matches everything.
37
+ function toolSignature(name, input) {
38
+ const i = input || {};
39
+ if (name === "Bash")
40
+ return "Bash:" + String(i.command || "").slice(0, 80);
41
+ if (["Read", "Edit", "Write", "NotebookEdit"].includes(name))
42
+ return name + ":" + (i.file_path || "");
43
+ if (["Grep", "Glob"].includes(name))
44
+ return name + ":" + (i.pattern || i.glob || "");
45
+ // generic: name + a short stable slice of the args
46
+ try {
47
+ return name + ":" + JSON.stringify(i).slice(0, 80);
48
+ }
49
+ catch {
50
+ return name;
51
+ }
52
+ }
53
+ const READ_TOOLS = new Set(["Read", "Grep", "Glob"]);
54
+ function parseTurn(o, prevWasBoundary) {
55
+ const t = o?.type;
56
+ if (t !== "user" && t !== "assistant")
57
+ return null;
58
+ const m = o?.message;
59
+ if (!m)
60
+ return null;
61
+ const toolSigs = [];
62
+ const readSigs = [];
63
+ let text = "";
64
+ let isToolResult = false;
65
+ if (Array.isArray(m.content)) {
66
+ for (const b of m.content) {
67
+ if (!b || typeof b !== "object")
68
+ continue;
69
+ if (b.type === "tool_use") {
70
+ const sig = toolSignature(b.name, b.input);
71
+ toolSigs.push(sig);
72
+ if (READ_TOOLS.has(b.name))
73
+ readSigs.push(sig);
74
+ }
75
+ else if (b.type === "text") {
76
+ text += b.text || "";
77
+ }
78
+ else if (b.type === "tool_result") {
79
+ isToolResult = true;
80
+ }
81
+ }
82
+ }
83
+ else if (typeof m.content === "string") {
84
+ text = m.content;
85
+ }
86
+ // A user line that is purely a tool_result is not a human turn; skip for
87
+ // re-explain purposes but it's still a turn for recovery counting.
88
+ return {
89
+ role: t,
90
+ text,
91
+ textLen: text.length,
92
+ toolSigs,
93
+ readSigs,
94
+ // The compact summary is the large user message immediately after the
95
+ // boundary (and is flagged by the harness as a compact summary in content).
96
+ isCompactSummary: t === "user" && prevWasBoundary && !isToolResult && text.length > 0,
97
+ };
98
+ }
99
+ async function analyzeFile(file, seenBoundaries, out, projectLabel, roles) {
100
+ const seq = [];
101
+ let prevWasBoundary = false;
102
+ const rl = readline_1.default.createInterface({ input: fs_1.default.createReadStream(file, { encoding: "utf-8" }), crlfDelay: Infinity });
103
+ for await (const line of rl) {
104
+ let o;
105
+ try {
106
+ o = JSON.parse(line);
107
+ }
108
+ catch {
109
+ continue;
110
+ }
111
+ if (o?.type === "system" && o?.subtype === "compact_boundary") {
112
+ seq.push({ kind: "boundary", meta: o.compactMetadata || {}, uuid: o.uuid || `${file}:${seq.length}` });
113
+ prevWasBoundary = true;
114
+ continue;
115
+ }
116
+ const turn = parseTurn(o, prevWasBoundary);
117
+ if (turn) {
118
+ seq.push({ kind: "turn", turn });
119
+ prevWasBoundary = false;
120
+ }
121
+ }
122
+ // Index the turn positions for windowing.
123
+ for (let i = 0; i < seq.length; i++) {
124
+ const it = seq[i];
125
+ if (it.kind !== "boundary")
126
+ continue;
127
+ if (seenBoundaries.has(it.uuid))
128
+ continue; // dedupe forked re-emits
129
+ seenBoundaries.add(it.uuid);
130
+ const meta = it.meta;
131
+ const pre = meta.preTokens ?? null;
132
+ const post = meta.postTokens ?? null;
133
+ // Gather pre/post turns (real turns only) within the windows.
134
+ const preTurns = [];
135
+ for (let j = i - 1; j >= 0 && preTurns.length < PRE_WINDOW; j--) {
136
+ if (seq[j].kind === "turn")
137
+ preTurns.push(seq[j].turn);
138
+ }
139
+ const postTurns = [];
140
+ for (let j = i + 1; j < seq.length && postTurns.length < POST_WINDOW; j++) {
141
+ if (seq[j].kind === "turn")
142
+ postTurns.push(seq[j].turn);
143
+ if (seq[j].kind === "boundary")
144
+ break; // stop at the next compact
145
+ }
146
+ const preToolSigs = new Set();
147
+ const preReadSigs = new Set();
148
+ for (const t of preTurns) {
149
+ t.toolSigs.forEach((s) => preToolSigs.add(s));
150
+ t.readSigs.forEach((s) => preReadSigs.add(s));
151
+ }
152
+ let reworkHits = 0;
153
+ let refetchHits = 0;
154
+ let turnsToRecover = null;
155
+ let userReexplain = false;
156
+ let recoverCount = 0;
157
+ for (const t of postTurns) {
158
+ recoverCount++;
159
+ // rework: a post-compact tool action whose signature appeared pre-compact
160
+ for (const s of t.toolSigs)
161
+ if (preToolSigs.has(s))
162
+ reworkHits++;
163
+ // refetch (re-ask proxy): a post-compact read of something read pre-compact
164
+ for (const s of t.readSigs)
165
+ if (preReadSigs.has(s))
166
+ refetchHits++;
167
+ // recovery: first post-compact assistant turn issuing a NEW tool action
168
+ if (turnsToRecover === null && t.role === "assistant") {
169
+ const hasNew = t.toolSigs.some((s) => !preToolSigs.has(s));
170
+ if (hasNew)
171
+ turnsToRecover = recoverCount;
172
+ }
173
+ // re-explain: a human user message (not the summary, not a tool_result)
174
+ // longer than the threshold
175
+ if (!t.isCompactSummary && t.role === "user" && t.toolSigs.length === 0 && t.textLen > REEXPLAIN_CHARS) {
176
+ userReexplain = true;
177
+ }
178
+ }
179
+ out.push({
180
+ project: projectLabel,
181
+ roles,
182
+ trigger: meta.trigger ?? null,
183
+ preTokens: pre,
184
+ postTokens: post,
185
+ reductionPct: pre && post && pre > 0 ? Math.round((100 * (pre - post)) / pre) : null,
186
+ durationMs: meta.durationMs ?? null,
187
+ reworkHits,
188
+ refetchHits,
189
+ turnsToRecover,
190
+ userReexplain,
191
+ });
192
+ }
193
+ }
194
+ function median(nums) {
195
+ if (nums.length === 0)
196
+ return 0;
197
+ const s = [...nums].sort((a, b) => a - b);
198
+ return s[Math.floor(s.length / 2)];
199
+ }
200
+ const BUCKETS = [
201
+ ["<100K", 0, 100000],
202
+ ["100-250K", 100000, 250000],
203
+ ["250-500K", 250000, 500000],
204
+ ["500K-1M", 500000, 1000000],
205
+ [">=1M", 1000000, Infinity],
206
+ ];
207
+ function pathALossBaseline(events) {
208
+ const rate = (es, pred) => es.length ? es.filter(pred).length / es.length : 0;
209
+ const byPreTokenBucket = BUCKETS.map(([label, lo, hi]) => {
210
+ const es = events.filter((e) => (e.preTokens ?? 0) >= lo && (e.preTokens ?? 0) < hi);
211
+ const recov = es.map((e) => e.turnsToRecover).filter((n) => n !== null);
212
+ return {
213
+ bucket: label,
214
+ count: es.length,
215
+ reworkRate: rate(es, (e) => e.reworkHits > 0),
216
+ reexplainRate: rate(es, (e) => e.userReexplain),
217
+ medianTurnsToRecover: recov.length ? median(recov) : null,
218
+ };
219
+ }).filter((b) => b.count > 0);
220
+ const worstCases = [...events]
221
+ .sort((a, b) => b.reworkHits - a.reworkHits || (b.turnsToRecover ?? 0) - (a.turnsToRecover ?? 0))
222
+ .slice(0, 5)
223
+ .map((e) => ({
224
+ project: e.project,
225
+ preTokens: e.preTokens,
226
+ reworkHits: e.reworkHits,
227
+ turnsToRecover: e.turnsToRecover,
228
+ userReexplain: e.userReexplain,
229
+ }));
230
+ const recovAll = events.map((e) => e.turnsToRecover).filter((n) => n !== null);
231
+ return {
232
+ source: "historical Path-A (Claude Code /compact); HEURISTIC signals; NON-representative of auto-trigger rates (manual = task-boundary timing). Validates detection mechanics, not arming-phase loss rates.",
233
+ totalEvents: events.length,
234
+ byPreTokenBucket,
235
+ worstCases,
236
+ comparison: {
237
+ pathA: {
238
+ source: "historical /compact (this dataset)",
239
+ events: events.length,
240
+ reworkRate: rate(events, (e) => e.reworkHits > 0),
241
+ reexplainRate: rate(events, (e) => e.userReexplain),
242
+ medianTurnsToRecover: recovAll.length ? median(recovAll) : null,
243
+ },
244
+ pathB: null,
245
+ defaultSoakPath: "A",
246
+ note: "Path B (CLEAR+reinject) has no historical data and cannot be measured read-only — its loss signals only exist if the path is executed in the founder-gated soak. A is the conservative-soak default; B is tested against A's measured baseline.",
247
+ },
248
+ };
249
+ }
250
+ async function analyzeCompactEvents(agents, windowDays = 30) {
251
+ const windowStartMs = Date.now() - windowDays * 24 * 60 * 60 * 1000;
252
+ const byProject = new Map();
253
+ for (const a of agents) {
254
+ const cwd = (0, usage_reader_js_1.cwdForPid)(a.pid);
255
+ if (!cwd)
256
+ continue;
257
+ const id = (0, usage_reader_js_1.projectIdFromCwd)(cwd);
258
+ const e = byProject.get(id) || { roles: [], cwd };
259
+ e.roles.push(a.role);
260
+ byProject.set(id, e);
261
+ }
262
+ const events = [];
263
+ const seen = new Set();
264
+ for (const [id, { roles, cwd }] of byProject) {
265
+ const dir = path_1.default.join(usage_reader_js_1.PROJECTS_DIR, id);
266
+ const label = roles.length === 1 ? roles[0] : `${path_1.default.basename(cwd)} (${roles.length} agents)`;
267
+ let files = [];
268
+ try {
269
+ files = fs_1.default.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path_1.default.join(dir, f));
270
+ }
271
+ catch {
272
+ continue;
273
+ }
274
+ for (const file of files) {
275
+ try {
276
+ if (fs_1.default.statSync(file).mtimeMs < windowStartMs)
277
+ continue;
278
+ }
279
+ catch {
280
+ continue;
281
+ }
282
+ await analyzeFile(file, seen, events, label, roles);
283
+ }
284
+ }
285
+ // Roll up by trigger type.
286
+ const byTrigger = {};
287
+ for (const trig of new Set(events.map((e) => e.trigger || "unknown"))) {
288
+ const es = events.filter((e) => (e.trigger || "unknown") === trig);
289
+ const recov = es.map((e) => e.turnsToRecover).filter((n) => n !== null);
290
+ byTrigger[trig] = {
291
+ count: es.length,
292
+ medianPreTokens: median(es.map((e) => e.preTokens || 0)),
293
+ medianPostTokens: median(es.map((e) => e.postTokens || 0)),
294
+ medianReductionPct: median(es.map((e) => e.reductionPct || 0)),
295
+ reworkRate: es.length ? es.filter((e) => e.reworkHits > 0).length / es.length : 0,
296
+ refetchRate: es.length ? es.filter((e) => e.refetchHits > 0).length / es.length : 0,
297
+ userReexplainRate: es.length ? es.filter((e) => e.userReexplain).length / es.length : 0,
298
+ medianTurnsToRecover: recov.length ? median(recov) : null,
299
+ };
300
+ }
301
+ return {
302
+ generatedAt: new Date().toISOString(),
303
+ windowDays,
304
+ totalEvents: events.length,
305
+ byTrigger,
306
+ events,
307
+ };
308
+ }
309
+ //# sourceMappingURL=compact-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-analyzer.js","sourceRoot":"","sources":["../src/compact-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export type CompactAction = {
2
+ kind: "fire";
3
+ } | {
4
+ kind: "ask";
5
+ } | {
6
+ kind: "skip";
7
+ reason: string;
8
+ };
9
+ export interface CompactDecisionInput {
10
+ idle: boolean;
11
+ ambiguous: boolean;
12
+ safeToClear: boolean;
13
+ consentAgeMs: number | null;
14
+ consentFreshMs: number;
15
+ economicGreen: boolean;
16
+ msSinceLastAsk: number | null;
17
+ askCooldownMs: number;
18
+ }
19
+ export declare function decideCompactAction(i: CompactDecisionInput): CompactAction;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // T1 consent handshake — pure decision core for the armed /compact path.
3
+ //
4
+ // Background: before this, the watcher auto-fired /compact the moment an
5
+ // allowlisted agent was idle at a turn boundary (the Stop hook touches
6
+ // checkpoint_ts every turn, so the old "fresh_consent" gate was satisfied
7
+ // automatically — the agent never actually said yes). This module replaces
8
+ // that silent fire with a two-phase handshake:
9
+ //
10
+ // ASK — when occupancy is high + the agent is idle + compacting is
11
+ // economically worth a turn, type a prompt asking the agent to flush
12
+ // its handoff and CONSENT via signal_checkpoint(safe_to_clear=true).
13
+ // FIRE — only once a GENUINE consent is on record (checkpoint_safe_to_clear
14
+ // === 1, set solely by the explicit signal_checkpoint MCP call and
15
+ // unforgeable by the per-turn hook touch) AND that consent is fresh
16
+ // AND the agent is idle right now, type /compact.
17
+ //
18
+ // The function is pure so the gate ordering + freshness + cooldown logic can be
19
+ // pinned by tests independently of the watcher's transcript/DB plumbing.
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.decideCompactAction = decideCompactAction;
22
+ // Precedence: ambiguous → FIRE (durable consent) → ASK (throttled) → skip.
23
+ // FIRE is evaluated BEFORE the ask-cooldown so a yes is never starved by the
24
+ // throttle that exists only to stop us re-asking every tick. A fresh consent
25
+ // with no prior ask still fires — an agent that volunteered safe_to_clear=true
26
+ // on its own is just as valid as one answering our prompt.
27
+ function decideCompactAction(i) {
28
+ if (i.ambiguous)
29
+ return { kind: "skip", reason: "ambiguous-session" };
30
+ const consentFresh = i.safeToClear && i.consentAgeMs !== null && i.consentAgeMs < i.consentFreshMs;
31
+ if (consentFresh) {
32
+ // Consent is necessary, not sufficient — re-check idle at FIRE time.
33
+ if (!i.idle)
34
+ return { kind: "skip", reason: "consented-but-not-idle" };
35
+ return { kind: "fire" };
36
+ }
37
+ // No fire-eligible consent → consider asking. Asking costs the agent a turn,
38
+ // so only when idle (won't interrupt) and economically worth it.
39
+ if (!i.idle)
40
+ return { kind: "skip", reason: "not-idle" };
41
+ if (!i.economicGreen)
42
+ return { kind: "skip", reason: "economics-below-overhead" };
43
+ if (i.msSinceLastAsk !== null && i.msSinceLastAsk < i.askCooldownMs)
44
+ return { kind: "skip", reason: "ask-cooldown" };
45
+ return { kind: "ask" };
46
+ }
47
+ //# sourceMappingURL=compact-decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-decision.js","sourceRoot":"","sources":["../src/compact-decision.ts"],"names":[],"mappings":";AAAA,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,0EAA0E;AAC1E,2EAA2E;AAC3E,+CAA+C;AAC/C,EAAE;AACF,sEAAsE;AACtE,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,2DAA2D;AAC3D,EAAE;AACF,gFAAgF;AAChF,yEAAyE;;AAuCzE,kDAkBC;AAvBD,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,2DAA2D;AAC3D,SAAgB,mBAAmB,CAAC,CAAuB;IACzD,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAEtE,MAAM,YAAY,GAChB,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,cAAc,CAAC;IAChF,IAAI,YAAY,EAAE,CAAC;QACjB,qEAAqE;QACrE,IAAI,CAAC,CAAC,CAAC,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;QACvE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAED,6EAA6E;IAC7E,iEAAiE;IACjE,IAAI,CAAC,CAAC,CAAC,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzD,IAAI,CAAC,CAAC,CAAC,aAAa;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;IAClF,IAAI,CAAC,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,aAAa;QACjE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAClD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type CompactPath = "A" | "B";
2
+ export declare const DEFAULT_PATH: CompactPath;
3
+ export declare const HANDOFF_DIR: string;
4
+ export declare function isHandoffPathSafe(p: string): boolean;
5
+ export declare const HANDOFF_TEMPLATE = "# ClaudeLink handoff \u2014 <role>\n\n## Current task & progress\n<what you are doing right now and how far along>\n\n## Key decisions / context you must NOT lose\n<constraints, choices already made, facts that won't be re-derivable>\n\n## Exact next step\n<the very next action to take on resume>\n\n## Open threads\n<anything in flight, awaited, or deferred>\n";
6
+ export declare function handoffPathFor(agentId: string): string;
7
+ export declare function preparePrompt(perTurnCostUsd: number, handoffFile: string): string;
8
+ export declare function postCompactPrompt(p: CompactPath, handoffFile: string): string;
9
+ export declare function verifyHandoff(file: string): {
10
+ ok: boolean;
11
+ bytes: number;
12
+ };
13
+ export interface CompactStep {
14
+ action: "send-prepare-prompt" | "await-ready" | "verify-handoff" | "inject-compact" | "inject-clear" | "reinject-handoff" | "send-postcompact-prompt";
15
+ detail: string;
16
+ injects: boolean;
17
+ }
18
+ export declare function planCompact(p: CompactPath, perTurnCostUsd: number, handoffFile: string): CompactStep[];
19
+ export type ExecMode = "dry" | "armed";
20
+ export interface ExecResult {
21
+ path: CompactPath;
22
+ mode: ExecMode;
23
+ steps: CompactStep[];
24
+ executed: boolean;
25
+ note: string;
26
+ }
27
+ export declare function executeCompact(agentId: string, p: CompactPath, perTurnCostUsd: number, mode?: ExecMode): ExecResult;
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ // Compact executor — the prepare-then-compact handshake and the two
3
+ // state-preservation paths (§C of the auto-compact design), built in DRY mode:
4
+ // every flow is a PLANNER that logs the ordered steps it WOULD take. The actual
5
+ // terminal-injection points are guarded stubs — nothing types into a terminal
6
+ // here. Arming (real injection) is founder-gated and happens in the supervised
7
+ // soak, where the A-vs-B winner is MEASURED (it cannot be measured read-only:
8
+ // a path's loss-signals only exist if that path is executed).
9
+ //
10
+ // HYPOTHESIS (explicitly a hypothesis, not a measurement) — informs the soak's
11
+ // starting posture, does NOT decide it:
12
+ // Path A (/compact + handoff-file) = belt-and-suspenders. Claude Code's own
13
+ // summary PLUS the agent's structured handoff. If the handoff misses
14
+ // something, the model summary is a safety net. Lower risk FLOOR.
15
+ // Path B (CLEAR + handoff-reinject) = total control. Skip /compact entirely;
16
+ // the handoff IS the new context. No dependency on /compact's opaque
17
+ // summarization — potentially a higher ceiling — but if the handoff misses
18
+ // something it is GONE, no safety net. Higher risk floor.
19
+ // => Start the soak with A as the CONSERVATIVE DEFAULT; test B as the
20
+ // optimization against A's measured baseline. (Per senior review + pushback #4.)
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.HANDOFF_TEMPLATE = exports.HANDOFF_DIR = exports.DEFAULT_PATH = void 0;
26
+ exports.isHandoffPathSafe = isHandoffPathSafe;
27
+ exports.handoffPathFor = handoffPathFor;
28
+ exports.preparePrompt = preparePrompt;
29
+ exports.postCompactPrompt = postCompactPrompt;
30
+ exports.verifyHandoff = verifyHandoff;
31
+ exports.planCompact = planCompact;
32
+ exports.executeCompact = executeCompact;
33
+ const fs_1 = __importDefault(require("fs"));
34
+ const path_1 = __importDefault(require("path"));
35
+ const os_1 = __importDefault(require("os"));
36
+ exports.DEFAULT_PATH = "A"; // conservative-soak default
37
+ exports.HANDOFF_DIR = path_1.default.join(os_1.default.homedir(), ".claudelink", "handoffs");
38
+ // Marker substrings from HANDOFF_TEMPLATE — used by verifyHandoff to detect a
39
+ // "filled in nothing, just kept the stub" handoff. The template itself is
40
+ // ~270 bytes which sails over the >200-byte size gate, so a byte-count check
41
+ // alone is bypassable. Updated together with HANDOFF_TEMPLATE.
42
+ const HANDOFF_PLACEHOLDERS = [
43
+ "<role>",
44
+ "<what you are doing right now",
45
+ "<constraints, choices already made",
46
+ "<the very next action to take on resume>",
47
+ "<anything in flight, awaited, or deferred>",
48
+ ];
49
+ // True iff `p` resolves under ~/.claudelink/handoffs/. Used at the
50
+ // signal_checkpoint ingress to reject agent-supplied paths that would let a
51
+ // confused agent satisfy the handoff gate with any large file on disk
52
+ // (e.g. /etc/passwd, a node_modules tarball). Returns false for any path that
53
+ // can't be resolved.
54
+ function isHandoffPathSafe(p) {
55
+ try {
56
+ const resolved = path_1.default.resolve(p);
57
+ const root = path_1.default.resolve(exports.HANDOFF_DIR);
58
+ return resolved === root || resolved.startsWith(root + path_1.default.sep);
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ // The structured essence the agent preserves across a compact. The 780K it
65
+ // re-reads every turn is mostly dead weight; this is the few-KB that matters.
66
+ exports.HANDOFF_TEMPLATE = `# ClaudeLink handoff — <role>
67
+
68
+ ## Current task & progress
69
+ <what you are doing right now and how far along>
70
+
71
+ ## Key decisions / context you must NOT lose
72
+ <constraints, choices already made, facts that won't be re-derivable>
73
+
74
+ ## Exact next step
75
+ <the very next action to take on resume>
76
+
77
+ ## Open threads
78
+ <anything in flight, awaited, or deferred>
79
+ `;
80
+ function handoffPathFor(agentId) {
81
+ return path_1.default.join(exports.HANDOFF_DIR, `${agentId}.md`);
82
+ }
83
+ // Step 2 of the handshake: the prepare-for-compact prompt (NOT a blind /compact).
84
+ function preparePrompt(perTurnCostUsd, handoffFile) {
85
+ return (`Your context is now costing ~$${perTurnCostUsd.toFixed(2)}/turn to re-read. ` +
86
+ `Before I compact it, write a handoff to ${handoffFile} capturing: current task + progress; ` +
87
+ `key decisions/context you must not lose; the exact next step; open threads. ` +
88
+ `Reply READY when the file is written.`);
89
+ }
90
+ // Step 6: the post-compact re-orient prompt, points the agent at its essence.
91
+ function postCompactPrompt(p, handoffFile) {
92
+ if (p === "A") {
93
+ return `Context compacted. Resume from your handoff at ${handoffFile} (plus the compact summary above).`;
94
+ }
95
+ return `Fresh context. Your handoff is at ${handoffFile} — resume from it; that is your full working state.`;
96
+ }
97
+ // Verify the agent actually wrote a non-trivial handoff before compacting.
98
+ // Two checks, both required: file must be >200 bytes AND must NOT contain any
99
+ // of the HANDOFF_TEMPLATE placeholders unfilled. A byte-count alone is
100
+ // bypassable because the template itself is ~270 bytes — an agent that wrote
101
+ // the template verbatim with nothing else would satisfy size but have zero
102
+ // real content. The placeholder scan catches that.
103
+ function verifyHandoff(file) {
104
+ try {
105
+ const st = fs_1.default.statSync(file);
106
+ if (st.size <= 200)
107
+ return { ok: false, bytes: st.size };
108
+ const body = fs_1.default.readFileSync(file, "utf-8");
109
+ for (const ph of HANDOFF_PLACEHOLDERS) {
110
+ if (body.includes(ph))
111
+ return { ok: false, bytes: st.size };
112
+ }
113
+ return { ok: true, bytes: st.size };
114
+ }
115
+ catch {
116
+ return { ok: false, bytes: 0 };
117
+ }
118
+ }
119
+ // The ordered plan for a given path. This is what the soak would execute; in dry
120
+ // mode we only LOG it. The `injects` flags mark exactly which steps touch the
121
+ // terminal — all of them stay stubbed until founder-armed.
122
+ function planCompact(p, perTurnCostUsd, handoffFile) {
123
+ const common = [
124
+ { action: "send-prepare-prompt", detail: preparePrompt(perTurnCostUsd, handoffFile), injects: true },
125
+ { action: "await-ready", detail: "wait for the agent to reply READY (bounded timeout)", injects: false },
126
+ { action: "verify-handoff", detail: `verify ${handoffFile} exists and is non-trivial`, injects: false },
127
+ ];
128
+ if (p === "A") {
129
+ return [
130
+ ...common,
131
+ { action: "inject-compact", detail: "type /compact (Claude Code summarizes)", injects: true },
132
+ { action: "send-postcompact-prompt", detail: postCompactPrompt("A", handoffFile), injects: true },
133
+ ];
134
+ }
135
+ return [
136
+ ...common,
137
+ { action: "inject-clear", detail: "type /clear (fresh context, no model summary)", injects: true },
138
+ { action: "reinject-handoff", detail: "inject the handoff file contents as the new starting message", injects: true },
139
+ { action: "send-postcompact-prompt", detail: postCompactPrompt("B", handoffFile), injects: true },
140
+ ];
141
+ }
142
+ // Execute (or, in dry mode, PLAN) a compact via the chosen path. In dry mode it
143
+ // returns the plan without touching anything. "armed" mode is NOT reachable yet:
144
+ // it throws, because real injection is founder-gated and must be wired with the
145
+ // idle-prompt safety detector under supervision — not enabled from here.
146
+ function executeCompact(agentId, p, perTurnCostUsd, mode = "dry") {
147
+ const handoffFile = handoffPathFor(agentId);
148
+ const steps = planCompact(p, perTurnCostUsd, handoffFile);
149
+ if (mode === "armed") {
150
+ throw new Error("compact-executor: armed injection is not enabled — founder-gated, requires the idle-prompt safety detector and a supervised soak.");
151
+ }
152
+ return {
153
+ path: p,
154
+ mode: "dry",
155
+ steps,
156
+ executed: false,
157
+ note: "dry plan only — no terminal contact; injection steps marked injects:true are stubbed pending founder-armed soak",
158
+ };
159
+ }
160
+ //# sourceMappingURL=compact-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-executor.js","sourceRoot":"","sources":["../src/compact-executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export type WatcherMode = "observe" | "inject";
2
+ export interface ContextWatcherSettings {
3
+ enabled: boolean;
4
+ mode: WatcherMode;
5
+ intervalSec: number;
6
+ contextOccupancyThreshold: number;
7
+ dollarPerTurnThreshold: number;
8
+ handshakeOverheadTokens: number;
9
+ activeWindowMin: number;
10
+ thresholdTokens: number;
11
+ compactBaselineTokens: number;
12
+ cooldownMin: number;
13
+ message: string;
14
+ askMessage: string;
15
+ consentFreshMin: number;
16
+ injectAllowlist: string[];
17
+ oneShot: boolean;
18
+ activeSince: string | null;
19
+ }
20
+ export declare function readContextWatcherSettings(): ContextWatcherSettings;
21
+ export declare function writeContextWatcherSettings(partial: Partial<ContextWatcherSettings>): ContextWatcherSettings;