baychat 0.23.1 → 0.25.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 +120 -15
- package/dist/api.js +19 -2
- package/dist/claude-context-flush.js +219 -0
- package/dist/claude-context-state.js +191 -0
- package/dist/claude-context.js +311 -0
- package/dist/claude-settings.js +503 -0
- package/dist/codex-onboarding.js +33 -23
- package/dist/context-hooks-command.js +165 -0
- package/dist/context-report.js +307 -0
- package/dist/index.js +26 -497
- package/dist/main.js +511 -0
- package/dist/mcp-notes.js +110 -0
- package/dist/mcp.js +84 -5
- package/dist/protocol-content.js +1 -1
- package/dist/relay/acp/client.js +18 -0
- package/dist/relay/acp/prompt.js +54 -3
- package/dist/relay/acp/runner.js +100 -2
- package/dist/relay/acp/turn-queue.js +99 -3
- package/dist/relay/codex-context.js +242 -0
- package/dist/relay/compact-command.js +63 -0
- package/dist/relay/compact-focus.attacks.js +36 -0
- package/dist/relay/compact-focus.js +64 -0
- package/dist/relay/context-notice.js +144 -0
- package/dist/relay/daemon.js +429 -73
- package/dist/relay/fresh-start-verify.js +237 -0
- package/dist/relay/fresh-start.js +141 -0
- package/dist/relay/message-format.js +34 -15
- package/dist/relay/project-brief.js +195 -0
- package/dist/relay/project-notes.js +300 -0
- package/dist/relay/resume.js +25 -0
- package/dist/relay/server-registration.js +40 -0
- package/dist/relay/session-commands.js +2 -0
- package/dist/relay/terminal-text.js +13 -0
- package/dist/relay/transport.js +2 -1
- package/dist/relay/updates.js +46 -8
- package/dist/relay/ws.js +49 -6
- package/dist/runtimes.js +24 -6
- package/dist/tool-defs.js +93 -0
- package/package.json +1 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Claude Code's context numbers, read where Claude Code publishes them, and sent to BayChat.
|
|
3
|
+
//
|
|
4
|
+
// Three commands, all installed by `baychat context-hooks install` (see claude-settings.ts) and all run
|
|
5
|
+
// BY CLAUDE CODE, never by a person:
|
|
6
|
+
//
|
|
7
|
+
// baychat context-statusline Started IN THE BACKGROUND by the status-line
|
|
8
|
+
// program (claude-settings.ts), with a copy of the
|
|
9
|
+
// status JSON: notes the numbers, prints nothing.
|
|
10
|
+
// The person's own command runs in the shell, not
|
|
11
|
+
// here, so their line never waits for Node.
|
|
12
|
+
// (`--chain <b64>` is the first release's form, in
|
|
13
|
+
// which this process also ran their command; still
|
|
14
|
+
// honoured until `install` rewrites it.)
|
|
15
|
+
// baychat context-hook PreCompact / PostCompact (async): notes the tidy.
|
|
16
|
+
// baychat context-flush --claude <id> Detached, spawned by the two above: sends what
|
|
17
|
+
// was noted, then exits.
|
|
18
|
+
//
|
|
19
|
+
// ── NOTHING HERE MAY SLOW THE STATUS LINE OR THE SESSION ──────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// The noting is a few small local file operations and no network; if a report is due, a DETACHED
|
|
22
|
+
// `context-flush` sends it. Diagnostics go to ~/.baychat/context/errors.log, never the terminal.
|
|
23
|
+
// This module loads nothing heavy — `index.ts` routes these three commands here without ever
|
|
24
|
+
// requiring the rest of the CLI (MCP SDK and all).
|
|
25
|
+
//
|
|
26
|
+
// ── WHICH BAYCHAT SESSION IS THIS? ───────────────────────────────────────────────────────
|
|
27
|
+
//
|
|
28
|
+
// Identified, never guessed (the relay's rule, AGENT_RELAY.md): the relay's registry names the
|
|
29
|
+
// Claude session id it recorded at attach, or the transcript shows this Claude session calling
|
|
30
|
+
// BayChat's tools with `session: "<name>"`. Neither → nothing is sent. A device with one live
|
|
31
|
+
// BayChat session is NOT evidence that this Claude window is that session.
|
|
32
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
35
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
36
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
37
|
+
}
|
|
38
|
+
Object.defineProperty(o, k2, desc);
|
|
39
|
+
}) : (function(o, m, k, k2) {
|
|
40
|
+
if (k2 === undefined) k2 = k;
|
|
41
|
+
o[k2] = m[k];
|
|
42
|
+
}));
|
|
43
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
44
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
45
|
+
}) : function(o, v) {
|
|
46
|
+
o["default"] = v;
|
|
47
|
+
});
|
|
48
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
49
|
+
var ownKeys = function(o) {
|
|
50
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
51
|
+
var ar = [];
|
|
52
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
53
|
+
return ar;
|
|
54
|
+
};
|
|
55
|
+
return ownKeys(o);
|
|
56
|
+
};
|
|
57
|
+
return function (mod) {
|
|
58
|
+
if (mod && mod.__esModule) return mod;
|
|
59
|
+
var result = {};
|
|
60
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
61
|
+
__setModuleDefault(result, mod);
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
exports.FAST_CONTEXT_COMMANDS = exports.defaultClaudeContextDeps = exports.sessionFromTranscript = exports.runContextFlush = exports.identifySession = exports.readState = exports.readAll = exports.logContextError = exports.contextDir = exports.backoffUntil = void 0;
|
|
67
|
+
exports.noteStatus = noteStatus;
|
|
68
|
+
exports.runContextStatusline = runContextStatusline;
|
|
69
|
+
exports.lastCompactBoundary = lastCompactBoundary;
|
|
70
|
+
exports.noteCompactHook = noteCompactHook;
|
|
71
|
+
exports.runContextHook = runContextHook;
|
|
72
|
+
exports.runFastContextCommand = runFastContextCommand;
|
|
73
|
+
const child_process_1 = require("child_process");
|
|
74
|
+
const fs = __importStar(require("fs"));
|
|
75
|
+
const claude_context_flush_1 = require("./claude-context-flush");
|
|
76
|
+
const claude_context_state_1 = require("./claude-context-state");
|
|
77
|
+
const claude_settings_1 = require("./claude-settings");
|
|
78
|
+
const context_report_1 = require("./context-report");
|
|
79
|
+
// The state helpers and the sender live beside this file; re-exported so callers and tests have
|
|
80
|
+
// one module to import from.
|
|
81
|
+
var claude_context_state_2 = require("./claude-context-state");
|
|
82
|
+
Object.defineProperty(exports, "backoffUntil", { enumerable: true, get: function () { return claude_context_state_2.backoffUntil; } });
|
|
83
|
+
Object.defineProperty(exports, "contextDir", { enumerable: true, get: function () { return claude_context_state_2.contextDir; } });
|
|
84
|
+
Object.defineProperty(exports, "logContextError", { enumerable: true, get: function () { return claude_context_state_2.logContextError; } });
|
|
85
|
+
Object.defineProperty(exports, "readAll", { enumerable: true, get: function () { return claude_context_state_2.readAll; } });
|
|
86
|
+
Object.defineProperty(exports, "readState", { enumerable: true, get: function () { return claude_context_state_2.readState; } });
|
|
87
|
+
var claude_context_flush_2 = require("./claude-context-flush");
|
|
88
|
+
Object.defineProperty(exports, "identifySession", { enumerable: true, get: function () { return claude_context_flush_2.identifySession; } });
|
|
89
|
+
Object.defineProperty(exports, "runContextFlush", { enumerable: true, get: function () { return claude_context_flush_2.runContextFlush; } });
|
|
90
|
+
Object.defineProperty(exports, "sessionFromTranscript", { enumerable: true, get: function () { return claude_context_flush_2.sessionFromTranscript; } });
|
|
91
|
+
function defaultStartFlush(sid) {
|
|
92
|
+
// `process.argv[1]` is the CLI entry that is running now; `execArgv` carries a loader such as
|
|
93
|
+
// tsx when there is one. No PATH lookup, no shell.
|
|
94
|
+
const child = (0, child_process_1.spawn)(process.execPath, [...process.execArgv, process.argv[1], "context-flush", "--claude", sid], {
|
|
95
|
+
detached: true,
|
|
96
|
+
stdio: "ignore",
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
env: process.env,
|
|
99
|
+
});
|
|
100
|
+
child.on("error", (err) => (0, claude_context_state_1.logContextError)(`could not start context-flush: ${(0, claude_context_state_1.errText)(err)}`));
|
|
101
|
+
child.unref();
|
|
102
|
+
}
|
|
103
|
+
function defaultRunChained(command, input) {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
let child;
|
|
106
|
+
try {
|
|
107
|
+
// The person's command, in a shell, exactly as Claude Code would have run it (`sh -c` on
|
|
108
|
+
// macOS and Linux). stdout and stderr are OURS, inherited — not piped and re-written — so
|
|
109
|
+
// nothing we do can reorder, re-encode or delay a byte of what it prints.
|
|
110
|
+
child = (0, child_process_1.spawn)(command, { shell: true, stdio: ["pipe", "inherit", "inherit"], env: process.env });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
(0, claude_context_state_1.logContextError)(`your status-line command could not start: ${(0, claude_context_state_1.errText)(err)}`);
|
|
114
|
+
return resolve(0);
|
|
115
|
+
}
|
|
116
|
+
const forward = (signal) => () => child.kill(signal);
|
|
117
|
+
const handlers = ["SIGTERM", "SIGINT", "SIGHUP"].map((sig) => [sig, forward(sig)]);
|
|
118
|
+
for (const [sig, fn] of handlers)
|
|
119
|
+
process.once(sig, fn);
|
|
120
|
+
child.on("error", (err) => (0, claude_context_state_1.logContextError)(`your status-line command failed: ${(0, claude_context_state_1.errText)(err)}`));
|
|
121
|
+
child.stdin?.on("error", () => {
|
|
122
|
+
/* a command that does not read its input closes the pipe early — that is its right */
|
|
123
|
+
});
|
|
124
|
+
child.stdin?.end(input);
|
|
125
|
+
child.on("close", (code, signal) => {
|
|
126
|
+
for (const [sig, fn] of handlers)
|
|
127
|
+
process.removeListener(sig, fn);
|
|
128
|
+
resolve(code ?? (signal ? 1 : 0));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const defaultClaudeContextDeps = () => ({
|
|
133
|
+
now: Date.now,
|
|
134
|
+
env: process.env,
|
|
135
|
+
startFlush: defaultStartFlush,
|
|
136
|
+
runChained: defaultRunChained,
|
|
137
|
+
});
|
|
138
|
+
exports.defaultClaudeContextDeps = defaultClaudeContextDeps;
|
|
139
|
+
// ─── The status line ─────────────────────────────────────────────────────────
|
|
140
|
+
/** `autoCompactWindow` from Claude Code's settings, or the env var that overrides it. */
|
|
141
|
+
function autoCompactOverride(env) {
|
|
142
|
+
const fromEnv = Number(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW);
|
|
143
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
144
|
+
return fromEnv;
|
|
145
|
+
try {
|
|
146
|
+
const settings = JSON.parse(fs.readFileSync((0, claude_settings_1.claudeSettingsPath)(), "utf8"));
|
|
147
|
+
const v = settings.autoCompactWindow;
|
|
148
|
+
return typeof v === "number" && v > 0 ? v : undefined;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Note one status-line sample. Local files only, no network. Returns true when a flush was started.
|
|
156
|
+
* Throws only on a programming error; the caller logs it.
|
|
157
|
+
*/
|
|
158
|
+
function noteStatus(raw, deps) {
|
|
159
|
+
let status;
|
|
160
|
+
try {
|
|
161
|
+
status = JSON.parse(raw);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false; // not ours to judge; the person's command already got the same input
|
|
165
|
+
}
|
|
166
|
+
const sid = (0, claude_context_state_1.safeId)(status?.session_id);
|
|
167
|
+
const sample = (0, context_report_1.claudeUsageFromStatus)(status);
|
|
168
|
+
if (!sid || !sample)
|
|
169
|
+
return false;
|
|
170
|
+
const autoCompactAt = (0, context_report_1.claudeAutoCompactAt)(sample.size, autoCompactOverride(deps.env));
|
|
171
|
+
const now = deps.now();
|
|
172
|
+
const state = (0, claude_context_state_1.readState)(sid);
|
|
173
|
+
if (state.unidentifiedAt && now - state.unidentifiedAt < claude_context_state_1.UNIDENTIFIED_RETRY_MS)
|
|
174
|
+
return false;
|
|
175
|
+
if (!(0, context_report_1.usageDue)(state.lastQueued, { ...sample, autoCompactAt }, now))
|
|
176
|
+
return false;
|
|
177
|
+
(0, claude_context_state_1.enqueue)(state, { runtime: "claude-code", event: "usage", used: sample.used, size: sample.size, autoCompactAt, source: "exact" });
|
|
178
|
+
state.lastQueued = { ...sample, autoCompactAt, at: now };
|
|
179
|
+
if (typeof status.transcript_path === "string")
|
|
180
|
+
state.transcriptPath = status.transcript_path;
|
|
181
|
+
(0, claude_context_state_1.writeState)(state);
|
|
182
|
+
if ((0, claude_context_state_1.backoffUntil)() > now)
|
|
183
|
+
return false;
|
|
184
|
+
deps.startFlush(sid);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* `baychat context-statusline [--chain <b64>]`. Returns the chained command's exit code, so the
|
|
189
|
+
* status line behaves exactly as the person's own command did.
|
|
190
|
+
*/
|
|
191
|
+
async function runContextStatusline(argv, stdin = process.stdin, deps = (0, exports.defaultClaudeContextDeps)()) {
|
|
192
|
+
const i = argv.indexOf("--chain");
|
|
193
|
+
const encoded = i >= 0 ? argv[i + 1] : undefined;
|
|
194
|
+
const chained = encoded !== undefined ? (0, claude_settings_1.decodeChain)(encoded) : undefined;
|
|
195
|
+
if (encoded !== undefined && chained === undefined) {
|
|
196
|
+
(0, claude_context_state_1.logContextError)("the status-line command BayChat wraps could not be decoded — run `baychat context-hooks uninstall` and install again");
|
|
197
|
+
}
|
|
198
|
+
const raw = await (0, claude_context_state_1.readAll)(stdin);
|
|
199
|
+
// Theirs first: nothing below may delay the line they see.
|
|
200
|
+
const theirs = chained !== undefined ? deps.runChained(chained, raw) : Promise.resolve(0);
|
|
201
|
+
try {
|
|
202
|
+
noteStatus(raw, deps);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
(0, claude_context_state_1.logContextError)(`status line: ${(0, claude_context_state_1.errText)(err)}`);
|
|
206
|
+
}
|
|
207
|
+
return theirs;
|
|
208
|
+
}
|
|
209
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : undefined);
|
|
210
|
+
const trig = (v) => (v === "auto" || v === "manual" ? v : undefined);
|
|
211
|
+
/**
|
|
212
|
+
* The last `compact_boundary` in a Claude transcript.
|
|
213
|
+
*
|
|
214
|
+
* Measured on a real transcript (Claude Code 2.1.282): `{"type":"system","subtype":
|
|
215
|
+
* "compact_boundary", "timestamp": …, "compactMetadata": {"trigger":"manual","preTokens":534691,
|
|
216
|
+
* "postTokens":20936, …}}`. The snake_case spelling (`compact_metadata.pre_tokens`) that some
|
|
217
|
+
* write-ups describe is accepted too.
|
|
218
|
+
*/
|
|
219
|
+
function lastCompactBoundary(transcriptText) {
|
|
220
|
+
const lines = transcriptText.split("\n");
|
|
221
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
222
|
+
const line = lines[i];
|
|
223
|
+
if (!line.includes("compact_boundary"))
|
|
224
|
+
continue;
|
|
225
|
+
let entry;
|
|
226
|
+
try {
|
|
227
|
+
entry = JSON.parse(line);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (entry.subtype !== "compact_boundary")
|
|
233
|
+
continue;
|
|
234
|
+
const meta = (entry.compactMetadata ?? entry.compact_metadata);
|
|
235
|
+
const at = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
|
|
236
|
+
return {
|
|
237
|
+
trigger: trig(meta?.trigger),
|
|
238
|
+
preTokens: num(meta?.preTokens ?? meta?.pre_tokens),
|
|
239
|
+
postTokens: num(meta?.postTokens ?? meta?.post_tokens),
|
|
240
|
+
...(Number.isFinite(at) ? { at } : {}),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
/** Note one PreCompact / PostCompact. Returns true when a flush was started. */
|
|
246
|
+
function noteCompactHook(raw, deps) {
|
|
247
|
+
let input;
|
|
248
|
+
try {
|
|
249
|
+
input = JSON.parse(raw);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const sid = (0, claude_context_state_1.safeId)(input?.session_id);
|
|
255
|
+
const name = input?.hook_event_name;
|
|
256
|
+
if (!sid || (name !== "PreCompact" && name !== "PostCompact"))
|
|
257
|
+
return false;
|
|
258
|
+
const state = (0, claude_context_state_1.readState)(sid);
|
|
259
|
+
const trigger = trig(input.trigger);
|
|
260
|
+
const transcript = typeof input.transcript_path === "string" ? input.transcript_path : state.transcriptPath;
|
|
261
|
+
if (transcript)
|
|
262
|
+
state.transcriptPath = transcript;
|
|
263
|
+
const base = { runtime: "claude-code", source: "exact", ...(trigger ? { trigger } : {}) };
|
|
264
|
+
if (name === "PreCompact") {
|
|
265
|
+
const before = state.lastQueued?.used;
|
|
266
|
+
(0, claude_context_state_1.enqueue)(state, { ...base, event: "compact_start", ...(before !== undefined ? { beforeTokens: before } : {}) });
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const boundary = transcript ? lastCompactBoundary((0, claude_context_state_1.readTail)(transcript, claude_context_state_1.BOUNDARY_TAIL_BYTES)) : undefined;
|
|
270
|
+
const fresh = boundary && (boundary.at === undefined || deps.now() - boundary.at <= claude_context_state_1.BOUNDARY_FRESH_MS) ? boundary : undefined;
|
|
271
|
+
const before = fresh?.preTokens ?? state.lastQueued?.used;
|
|
272
|
+
(0, claude_context_state_1.enqueue)(state, {
|
|
273
|
+
...base,
|
|
274
|
+
...(trigger ? {} : fresh?.trigger ? { trigger: fresh.trigger } : {}),
|
|
275
|
+
event: "compact_end",
|
|
276
|
+
...(before !== undefined ? { beforeTokens: before } : {}),
|
|
277
|
+
...(fresh?.postTokens !== undefined ? { afterTokens: fresh.postTokens } : {}),
|
|
278
|
+
});
|
|
279
|
+
// The window was just rewritten: the next status line's number must go out at once.
|
|
280
|
+
state.lastQueued = undefined;
|
|
281
|
+
}
|
|
282
|
+
(0, claude_context_state_1.writeState)(state);
|
|
283
|
+
if ((0, claude_context_state_1.backoffUntil)() > deps.now())
|
|
284
|
+
return false;
|
|
285
|
+
deps.startFlush(sid);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
/** `baychat context-hook`. Prints nothing and always exits 0: exit 2 would BLOCK a compaction. */
|
|
289
|
+
async function runContextHook(stdin = process.stdin, deps = (0, exports.defaultClaudeContextDeps)()) {
|
|
290
|
+
try {
|
|
291
|
+
noteCompactHook(await (0, claude_context_state_1.readAll)(stdin), deps);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
(0, claude_context_state_1.logContextError)(`compaction hook: ${(0, claude_context_state_1.errText)(err)}`);
|
|
295
|
+
}
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
/** The three commands this module owns, for `index.ts`'s fast dispatch. */
|
|
299
|
+
exports.FAST_CONTEXT_COMMANDS = new Set(["context-statusline", "context-hook", "context-flush"]);
|
|
300
|
+
async function runFastContextCommand(command, argv) {
|
|
301
|
+
switch (command) {
|
|
302
|
+
case "context-statusline":
|
|
303
|
+
return runContextStatusline(argv);
|
|
304
|
+
case "context-hook":
|
|
305
|
+
return runContextHook();
|
|
306
|
+
case "context-flush":
|
|
307
|
+
return (0, claude_context_flush_1.runContextFlush)(argv);
|
|
308
|
+
default:
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
}
|