@essentialai/cogent-bridge 3.20.7 → 3.21.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/CHANGELOG.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.21.1 — 2026-08-31
4
+
5
+ ### Fixed — a label session with an unreachable relay delayed the MCP handshake by ~2.4s
6
+
7
+ `runStartup()` is awaited BEFORE `server.connect(transport)`, so anything slow in it is dead air
8
+ the MCP client counts against its `initialize` timeout. Resolving a `COGENT_SESSION_ID` **label**
9
+ to a session UUID did `await fetch(...)` there, guarded by a 2500 ms `AbortSignal` and a comment
10
+ claiming the timeout kept the handshake safe. It did not: **bounding a blocking call does not make
11
+ it non-blocking, it only caps the stall.** Measured against a blackhole relay, it added a
12
+ reproducible **+2437 ms** to `initialize` — on top of process boot, on exactly the cold hosts where
13
+ this previously surfaced as "connection closed: initialize response".
14
+
15
+ The label resolve now runs AFTER connect, like the vendor-CLI preflight already did. Startup enters
16
+ the existing, fully supported deferred mode immediately; on success the post-connect resolve
17
+ upgrades the placeholder backend through `reinitCloudBackend()`, the same path
18
+ `cogent_join_session` uses. Measured after the fix: **+4 ms**.
19
+
20
+ ### Fixed — deferred agents never got the `cogent://inbox` resource
21
+
22
+ Registration was gated on `cloudInbox` existing *at startup*, which excluded every agent that joins
23
+ via `cogent_join_session` rather than pre-supplying credentials — i.e. the common case. Their inbox
24
+ is created later by `reinitCloudBackend()`, long after registration ran, so `cogent://inbox` was
25
+ never advertised while `sendResourceUpdated()` fired for a resource that did not exist. The resource
26
+ is now gated on cloud MODE and reads the inbox lazily, and the host notifier is bound through
27
+ startup so it follows the inbox across a swap instead of holding a reference to a discarded object.
28
+
29
+ ### Changed — the startup gate now measures blocking instead of machine speed
30
+
31
+ `scripts/startup-nonblocking-test.mjs` asserted an ABSOLUTE budget (spawn → `initialize` <= 2000 ms).
32
+ That number is dominated by Node boot plus loading a ~1 MB bundle, so on a busy machine it measured
33
+ LOAD: during the 3.21.0 release it went red on a box that was concurrently building and running
34
+ three live matrices, while an interleaved A/B against the previous commit showed no regression at
35
+ all. Worse, its second budget (4000 ms) had been calibrated to *accommodate* the 2.4 s stall above
36
+ rather than catch it — which is why it flickered instead of failing honestly.
37
+
38
+ It is now a controlled experiment: the same bridge is run with and without the slow knob,
39
+ interleaved, and the MEDIANS are compared. Boot and module load appear in both arms and cancel out,
40
+ so the result is load-independent. The control arm is reported and separately bounded, so the gate
41
+ distinguishes "the product is blocking" from "this machine is too loaded to measure" — previously
42
+ indistinguishable. Mutation-tested in both directions via the new `--bridge` flag.
43
+
44
+ ### Changed — test isolation is now explicit
45
+
46
+ `vitest.config.ts` pins `pool: "forks"` + `isolate: true`. Several suites resolve agent state through
47
+ `os.homedir()` and therefore mutate `process.env.HOME`; under a threads pool they share one
48
+ `process.env` and clobber each other. Verified: `--pool=threads` fails 3 of those tests, `forks`
49
+ passes all 21. This was a correctness requirement riding on an inherited default.
50
+
51
+ ## 3.21.0 — 2026-08-31
52
+
53
+ ### Fixed — auto-wake refused on machines whose agent state is not in the default place
54
+
55
+ A developer's Claude Code peer refused every wake with `no candidate sessions found for cwd`,
56
+ from its very first register. Codex→Claude failed while Claude→Codex worked — the asymmetry was
57
+ the clue: **only the Claude Code rail derived the transcript location, and it derived it twice
58
+ over.**
59
+
60
+ ```
61
+ <config root> / projects / <cwd with non-alphanumerics turned into dashes>
62
+ ```
63
+
64
+ Both halves were guesses. The root was `CLAUDE_CONFIG_DIR` **or** a hardcoded `~/.claude` — one
65
+ guess, never verified. The cwd half ran a raw `String.replace` on the path string the agent
66
+ passed to `cogent_register_peer`, so every other spelling of the same directory encoded to a
67
+ directory that does not exist: a trailing slash, a `.` segment, or a symlinked path (macOS
68
+ `/tmp` → `/private/tmp`, `/var` → `/private/var`). Both misses were swallowed by
69
+ `catch { return [] }`, indistinguishable from an empty machine. The Codex rail had already been
70
+ hardened against exactly this (`cwdCanonicals`); the Claude rail never was.
71
+
72
+ **Session discovery is now layered, and only the last layer guesses:**
73
+
74
+ 1. **Reported (authoritative).** The host hands every hook `cwd`, `session_id` and
75
+ `transcript_path`. A new `SessionStart` hook (`record-session.mjs`) and the existing Stop hook
76
+ persist that trio to `~/.cogent/agent-sessions/`. The resolver prefers it, so there is nothing
77
+ to derive — this works with **any** relocated config dir, with no configuration at all.
78
+ 2. **Discovery.** No record yet (e.g. Team agents, which run without the plugin) → roots are
79
+ enumerated instead of guessed: `CLAUDE_CONFIG_DIR`, then roots **learned** from transcript
80
+ paths agents reported, then `~/.claude` and `~/.config/claude` — each crossed with the
81
+ canonical forms of the cwd (`path.resolve` **and** `fs.realpath`).
82
+ 3. **Fail loud.** Nothing matched → the refusal now **names every path it scanned**, and a
83
+ zero-candidate result at register time is a WARN plus a `warning`/`scannedPaths` field in the
84
+ tool result. Previously registration silently pinned the *cloud* session id, which `--resume`
85
+ can never open, guaranteeing that every later wake was refused.
86
+
87
+ One recorded session teaches the machine its root, so other projects under the same root resolve
88
+ with no record of their own.
89
+
90
+ ### Fixed — "clean uninstall" removed nothing for users with a relocated config dir
91
+
92
+ `scripts/uninstall-cogent.sh` and the hosted `uninstall.sh` hardcoded `$HOME/.claude` and
93
+ `$HOME/.codex`, so a custom-dir user got a success report while every plugin file stayed on
94
+ disk — which then makes a "fresh install test" silently measure the old install. Both scripts now
95
+ enumerate the same roots the bridge does (env → learned → conventions) and clean each one, and
96
+ the new `~/.cogent/agent-sessions` state directory is purged.
97
+
98
+ ### Guarded
99
+
100
+ - The hook↔bridge record path and `rootFromTranscriptPath` are asserted identical by
101
+ `scripts/wake-race-test.mjs` (a mismatch would make the authoritative layer silently inert),
102
+ and `record-session.mjs` joins the byte-identical plugin-parity check.
103
+ - New tests: representation tolerance (trailing slash / `.` segment / symlink / dedupe), the
104
+ learned-record store, and an end-to-end proof against a config root no heuristic could guess.
105
+ Verified by mutation — each layer was disabled in turn and the relevant tests went red.
106
+
3
107
  ## 3.20.7 — 2026-08-31
4
108
 
5
109
  ### Fixed — `cogent-codex` did nothing when installed from npm (silent exit)
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { SERVER_NAME, SERVER_VERSION } from "./constants.js";
4
4
  import { checkForUpdate, setLastNudge } from "./backend/npm-update-check.js";
5
- import { runStartup, runPreflights, cloudWsClient, cloudInbox, autoRelay } from "./startup.js";
5
+ import { runStartup, runPreflights, resolvePendingLabel, isCloudMode, setInboxNotifier, cloudWsClient, cloudInbox, autoRelay } from "./startup.js";
6
6
  import { logger } from "./logger.js";
7
7
  import { registerRegisterPeerTool } from "./tools/register-peer.js";
8
8
  import { registerSendMessageTool } from "./tools/send-message.js";
@@ -78,23 +78,39 @@ async function main() {
78
78
  registerSendMailTool(server);
79
79
  registerFetchMailTool(server);
80
80
  registerRotateMailCredsTool(server);
81
- // Register MCP inbox resource if cloud mode is active
82
- if (cloudInbox) {
81
+ // Register the MCP inbox resource for cloud mode.
82
+ //
83
+ // 🔴 This used to be gated on `cloudInbox` EXISTING AT STARTUP, which quietly
84
+ // excluded every deferred agent — i.e. anyone who joins with
85
+ // cogent_join_session rather than pre-supplying credentials. Their inbox is
86
+ // created later by reinitCloudBackend(), long after this registration ran, so
87
+ // `cogent://inbox` was never advertised and the sendResourceUpdated() below
88
+ // fired for a resource that did not exist. Gate on cloud MODE and read the
89
+ // inbox LAZILY inside the handler, so deferred and pre-credentialed agents get
90
+ // the same surface.
91
+ if (isCloudMode()) {
83
92
  server.registerResource("cogent_inbox", "cogent://inbox", {
84
93
  description: "Incoming messages received via cloud bridge WebSocket. Read this resource to see new messages from other peers.",
85
94
  mimeType: "application/json",
86
- }, async (uri) => ({
95
+ },
96
+ // Read the inbox LAZILY: in deferred mode it does not exist yet and is
97
+ // created later by reinitCloudBackend(). An empty inbox is the honest
98
+ // answer for "connected to cloud, no session joined yet".
99
+ async (uri) => ({
87
100
  contents: [{
88
101
  uri: uri.href,
89
102
  mimeType: "application/json",
90
103
  text: JSON.stringify({
91
- messages: cloudInbox.getUnread(),
92
- lastMessageId: cloudInbox.getLastMessageId(),
104
+ messages: cloudInbox ? cloudInbox.getUnread() : [],
105
+ lastMessageId: cloudInbox ? cloudInbox.getLastMessageId() : null,
93
106
  }),
94
107
  }],
95
108
  }));
96
- // When WS messages arrive, notify the MCP host (Claude Code) that new messages are available
97
- cloudInbox.onNewMessage(() => {
109
+ // When WS messages arrive, notify the MCP host that new messages are available.
110
+ // Registered through startup so it also binds to an inbox created LATER
111
+ // (deferred join) — attaching directly here would only ever see the
112
+ // startup-time inbox, i.e. never in deferred mode.
113
+ setInboxNotifier(() => {
98
114
  try {
99
115
  server.server.sendResourceUpdated({ uri: "cogent://inbox" });
100
116
  }
@@ -109,6 +125,16 @@ async function main() {
109
125
  await server.connect(transport);
110
126
  // Startup banner: always prints regardless of log level
111
127
  process.stderr.write(`${SERVER_NAME} v${SERVER_VERSION} running on stdio\n`);
128
+ // A COGENT_SESSION_ID *label* needs a relay round-trip to become a session UUID.
129
+ // It runs HERE, after connect, for exactly the same reason preflight does:
130
+ // awaiting it beforehand added a reproducible +2437 ms to `initialize` against an
131
+ // unreachable relay (measured by scripts/startup-nonblocking-test.mjs). Bounding
132
+ // that fetch with a 2500 ms AbortSignal — what the code used to do — caps the
133
+ // stall but does not remove it. Fire-and-forget; on success it upgrades the
134
+ // deferred backend via reinitCloudBackend().
135
+ void resolvePendingLabel().catch((err) => {
136
+ logger.warn(`Label resolve error (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
137
+ });
112
138
  // Platform CLI + sandbox preflight runs AFTER the transport connects: it SPAWNS
113
139
  // the vendor CLI (`codex/claude --help`, capped at 10s) and must never precede
114
140
  // the `initialize` handshake — a slow spawn there closed the MCP connection on
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * LEARNED AGENT SESSION RECORDS — "the agent told us where it lives".
3
+ *
4
+ * 🔴 WHY THIS EXISTS. Everything downstream of the wake rails used to DERIVE the
5
+ * location of an agent's session transcript:
6
+ *
7
+ * <config root> / projects / <cwd with non-alphanumerics turned into dashes>
8
+ *
9
+ * Both halves of that are guesses. The config root was `CLAUDE_CONFIG_DIR` or a
10
+ * hardcoded `~/.claude` — one guess, never verified. The cwd half assumed the
11
+ * caller passed the exact canonical spelling of the directory. Either guess being
12
+ * wrong produced the same dead end: zero candidates, wake refused, and a log line
13
+ * that never named the path it looked in.
14
+ *
15
+ * The agents already publish the truth. Claude Code hands every hook a payload
16
+ * containing `cwd`, `session_id` and `transcript_path` — an ABSOLUTE path to the
17
+ * live transcript, with no encoding to reverse and no root to guess. This module
18
+ * is where that fact is persisted so the resolver can prefer it over inference.
19
+ *
20
+ * Layering (see docs + session-resolver):
21
+ * 1. this record — authoritative, works with ANY relocated config dir
22
+ * 2. root discovery — inference, for agents running without our plugin
23
+ * 3. fail loud — name every path probed
24
+ *
25
+ * Storage mirrors the existing per-cwd convention exactly (credential-store.ts,
26
+ * wake-inflight.ts): `~/.cogent/<kind>/<sha256(path.resolve(cwd))[..16]>.json`,
27
+ * homedir-based on purpose so the bridge and the hook — two processes with
28
+ * different environments — always agree on the path.
29
+ */
30
+ /** Platforms that keep a per-cwd session transcript we can learn. */
31
+ export type AgentPlatform = "cc" | "codex";
32
+ export interface AgentSessionRecord {
33
+ /** The cwd as the agent reported it (already resolved when written). */
34
+ cwd: string;
35
+ /** The agent's own session id — what `--resume` / `resume` takes. */
36
+ sessionId: string;
37
+ /** Absolute path to the transcript. The whole point: no derivation. */
38
+ transcriptPath: string;
39
+ /** Config root implied by transcriptPath, when it could be recovered. */
40
+ root?: string;
41
+ platform: AgentPlatform;
42
+ /** ISO timestamp of the last write. */
43
+ updatedAt: string;
44
+ }
45
+ /** MUST equal credential-store's hash and check-on-stop.lib.mjs credHashForCwd. */
46
+ export declare function hashCwd(cwd: string): string;
47
+ /** `~/.cogent/agent-sessions/` — the learned-record directory. */
48
+ export declare function agentSessionsDir(): string;
49
+ /**
50
+ * `~/.cogent/agent-sessions/<hash(cwd)>.<platform>.json`.
51
+ *
52
+ * Keyed by platform as well as cwd because one directory can host both a Claude
53
+ * Code and a Codex agent (that is the normal two-agent Cogent setup) and their
54
+ * transcripts live in completely different stores.
55
+ */
56
+ export declare function agentSessionPath(cwd: string, platform: AgentPlatform): string;
57
+ /**
58
+ * Recover the config ROOT from an absolute transcript path.
59
+ *
60
+ * Claude Code lays transcripts out as `<root>/projects/<encoded-cwd>/<id>.jsonl`,
61
+ * so the root is whatever sits above the `projects` segment — recovered by
62
+ * structure, never by matching a known prefix, which is what lets a completely
63
+ * unknown custom location teach us its root.
64
+ *
65
+ * Returns null when the shape does not match (a Codex rollout, a temp file, an
66
+ * agent that changes its layout) — callers must treat the root as optional.
67
+ */
68
+ export declare function rootFromTranscriptPath(transcriptPath: string): string | null;
69
+ /** Read the learned record for a cwd, or null. Never throws. */
70
+ export declare function readAgentSession(cwd: string, platform: AgentPlatform): Promise<AgentSessionRecord | null>;
71
+ /**
72
+ * Persist a learned record. Atomic (temp + rename) so a hook and the bridge
73
+ * writing concurrently can never leave a half-written file for the other to
74
+ * read — the same discipline peer-registry uses for shared state.
75
+ * Never throws: learning is an optimisation, never a reason to fail a wake.
76
+ */
77
+ export declare function writeAgentSession(rec: Omit<AgentSessionRecord, "updatedAt" | "root"> & {
78
+ root?: string;
79
+ }): Promise<boolean>;
80
+ /**
81
+ * Every distinct config root this machine has ever taught us, newest first.
82
+ *
83
+ * This is what turns one lucky resolution into permanent knowledge: a user with
84
+ * `CLAUDE_CONFIG_DIR=/data/claude` teaches us that root once, and afterwards
85
+ * EVERY project on the machine resolves against it — including ones opened in a
86
+ * shell where the variable was never exported. Bounded work: one readdir plus a
87
+ * small number of tiny JSON reads.
88
+ */
89
+ export declare function learnedRoots(platform: AgentPlatform): Promise<string[]>;
90
+ //# sourceMappingURL=agent-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-sessions.d.ts","sourceRoot":"","sources":["../../src/services/agent-sessions.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,qEAAqE;AACrE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC;AAE3C,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,aAAa,CAAC;IACxB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM3C;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,aAAa,GACtB,MAAM,CAER;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ5E;AAED,gEAAgE;AAChE,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUpC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,MAAM,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA4B7E"}
@@ -0,0 +1,120 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ /** MUST equal credential-store's hash and check-on-stop.lib.mjs credHashForCwd. */
6
+ export function hashCwd(cwd) {
7
+ return crypto
8
+ .createHash("sha256")
9
+ .update(path.resolve(cwd))
10
+ .digest("hex")
11
+ .slice(0, 16);
12
+ }
13
+ /** `~/.cogent/agent-sessions/` — the learned-record directory. */
14
+ export function agentSessionsDir() {
15
+ return path.join(os.homedir(), ".cogent", "agent-sessions");
16
+ }
17
+ /**
18
+ * `~/.cogent/agent-sessions/<hash(cwd)>.<platform>.json`.
19
+ *
20
+ * Keyed by platform as well as cwd because one directory can host both a Claude
21
+ * Code and a Codex agent (that is the normal two-agent Cogent setup) and their
22
+ * transcripts live in completely different stores.
23
+ */
24
+ export function agentSessionPath(cwd, platform) {
25
+ return path.join(agentSessionsDir(), `${hashCwd(cwd)}.${platform}.json`);
26
+ }
27
+ /**
28
+ * Recover the config ROOT from an absolute transcript path.
29
+ *
30
+ * Claude Code lays transcripts out as `<root>/projects/<encoded-cwd>/<id>.jsonl`,
31
+ * so the root is whatever sits above the `projects` segment — recovered by
32
+ * structure, never by matching a known prefix, which is what lets a completely
33
+ * unknown custom location teach us its root.
34
+ *
35
+ * Returns null when the shape does not match (a Codex rollout, a temp file, an
36
+ * agent that changes its layout) — callers must treat the root as optional.
37
+ */
38
+ export function rootFromTranscriptPath(transcriptPath) {
39
+ if (!transcriptPath)
40
+ return null;
41
+ const parts = path.resolve(transcriptPath).split(path.sep);
42
+ // …/<root>/projects/<encoded>/<id>.jsonl → need "projects" with a parent.
43
+ const i = parts.lastIndexOf("projects");
44
+ if (i <= 0)
45
+ return null;
46
+ const root = parts.slice(0, i).join(path.sep);
47
+ return root || path.sep;
48
+ }
49
+ /** Read the learned record for a cwd, or null. Never throws. */
50
+ export async function readAgentSession(cwd, platform) {
51
+ try {
52
+ const raw = await fs.readFile(agentSessionPath(cwd, platform), "utf-8");
53
+ const rec = JSON.parse(raw);
54
+ // A record without the two fields that make it useful is not a record.
55
+ if (!rec?.sessionId || !rec?.transcriptPath)
56
+ return null;
57
+ return rec;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * Persist a learned record. Atomic (temp + rename) so a hook and the bridge
65
+ * writing concurrently can never leave a half-written file for the other to
66
+ * read — the same discipline peer-registry uses for shared state.
67
+ * Never throws: learning is an optimisation, never a reason to fail a wake.
68
+ */
69
+ export async function writeAgentSession(rec) {
70
+ try {
71
+ const full = {
72
+ ...rec,
73
+ cwd: path.resolve(rec.cwd),
74
+ root: rec.root ?? rootFromTranscriptPath(rec.transcriptPath) ?? undefined,
75
+ updatedAt: new Date().toISOString(),
76
+ };
77
+ const target = agentSessionPath(full.cwd, full.platform);
78
+ await fs.mkdir(path.dirname(target), { recursive: true });
79
+ const tmp = `${target}.${process.pid}.tmp`;
80
+ await fs.writeFile(tmp, JSON.stringify(full, null, 2), "utf-8");
81
+ await fs.rename(tmp, target);
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * Every distinct config root this machine has ever taught us, newest first.
90
+ *
91
+ * This is what turns one lucky resolution into permanent knowledge: a user with
92
+ * `CLAUDE_CONFIG_DIR=/data/claude` teaches us that root once, and afterwards
93
+ * EVERY project on the machine resolves against it — including ones opened in a
94
+ * shell where the variable was never exported. Bounded work: one readdir plus a
95
+ * small number of tiny JSON reads.
96
+ */
97
+ export async function learnedRoots(platform) {
98
+ try {
99
+ const dir = agentSessionsDir();
100
+ const files = (await fs.readdir(dir)).filter((f) => f.endsWith(`.${platform}.json`));
101
+ const recs = await Promise.all(files.map(async (f) => {
102
+ try {
103
+ return JSON.parse(await fs.readFile(path.join(dir, f), "utf-8"));
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }));
109
+ return [
110
+ ...new Set(recs
111
+ .filter((r) => Boolean(r?.root))
112
+ .sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1))
113
+ .map((r) => r.root)),
114
+ ];
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ }
120
+ //# sourceMappingURL=agent-sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-sessions.js","sourceRoot":"","sources":["../../src/services/agent-sessions.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,79 @@
1
1
  import { CliExecResult } from "../types.js";
2
+ /**
3
+ * Every transcript directory that could hold this cwd's sessions.
4
+ *
5
+ * 🔴 WHY THIS IS NOT ONE PATH. Claude Code names a project dir by replacing each
6
+ * non-alphanumeric char of the cwd with "-", so EVERY representation of the same
7
+ * directory encodes to a DIFFERENT name: a trailing slash, a "." segment, or a
8
+ * symlinked path (macOS `/tmp` -> `/private/tmp`, `/var` -> `/private/var`) each
9
+ * produce a dir that does not exist. The cwd here is a string the AGENT supplied
10
+ * to cogent_register_peer — it is not guaranteed to be the canonical form.
11
+ *
12
+ * That mismatch is how a fresh dev machine got ZERO candidates from its very
13
+ * first register (2026-08-31) and refused every wake thereafter. The Codex rail
14
+ * had already canonicalised for this reason (codex-cli.ts `cwdCanonicals`); this
15
+ * is the Claude Code rail reaching parity.
16
+ *
17
+ * Exported so callers can LOG what was probed — a miss used to be indistinguishable
18
+ * from an empty dir, which is what made the failure undiagnosable from the logs.
19
+ */
20
+ export declare function projectDirsFor(cwd: string): Promise<string[]>;
21
+ /**
22
+ * Every config root that could hold this machine's Claude Code transcripts,
23
+ * best-first.
24
+ *
25
+ * 🔴 WHY THIS IS A LIST. `claudeConfigDir()` returns ONE guess — the env var if
26
+ * this process happens to see it, otherwise `~/.claude`. A single guess has no
27
+ * way to be wrong loudly: it just scans a directory that does not exist and
28
+ * reports "no sessions". Users relocate their agent state (small system disks,
29
+ * synced or encrypted home dirs, multi-account machines, containers), and the
30
+ * env var does not always reach the process that needs it.
31
+ *
32
+ * Order is deliberate:
33
+ * 1. `CLAUDE_CONFIG_DIR` — an explicit instruction always wins.
34
+ * 2. LEARNED roots — recovered from transcript paths the agent itself reported
35
+ * (agent-sessions.ts). These are the only entries PROVEN to have held a real
36
+ * transcript, which is why they outrank the conventional defaults.
37
+ * 3. `~/.claude`, then `~/.config/claude` — the conventional locations.
38
+ *
39
+ * Probing a few extra directories costs one failed `readdir` each; missing the
40
+ * user's actual directory costs them the entire auto-wake feature.
41
+ */
42
+ export declare function claudeConfigRoots(): Promise<string[]>;
43
+ /**
44
+ * The transcript this cwd's agent REPORTED for itself, if the record is still
45
+ * valid on disk. This is layer 1 — the authoritative path — and it bypasses both
46
+ * the root guess and the cwd-encoding guess entirely.
47
+ *
48
+ * Returns null when nothing was ever recorded (an agent running without our
49
+ * plugin) or when the recorded transcript has since been removed, in which case
50
+ * the caller falls back to discovery.
51
+ */
52
+ export declare function recordedCandidate(cwd: string): Promise<{
53
+ sessionId: string;
54
+ mtimeMs: number;
55
+ } | null>;
56
+ /**
57
+ * One-line description of the transcript BASE dir and how many project dirs it
58
+ * holds. Used only when a resolve finds zero candidates, because it separates the
59
+ * two causes that look identical in the logs:
60
+ * • base absent / 0 project dirs → the bridge is looking at the WRONG config dir
61
+ * entirely (Claude Code's environment lives elsewhere and CLAUDE_CONFIG_DIR did
62
+ * not reach this process).
63
+ * • base holds N project dirs but none match → the CWD is the wrong one (or a
64
+ * representation this build cannot map — see {@link projectDirsFor}).
65
+ * Never throws.
66
+ */
67
+ export declare function transcriptBaseSummary(): Promise<string>;
2
68
  /**
3
69
  * List all Claude Code session candidates for a cwd as {sessionId, mtimeMs}.
4
- * Scans <claudeConfigDir()>/projects/<escapedCwd>/*.jsonl — honoring
5
- * CLAUDE_CONFIG_DIR (default ~/.claude). Returns [] on any error or an
6
- * empty/absent dir (the resolver treats [] as "no candidates"). Never throws —
7
- * Vault: Returning-null/Option error pattern (typescript-master-vault
8
- * concepts/Error_Handling). Stats run in parallel (Vault: Parallelism).
70
+ * Scans every {@link projectDirsFor} dir for *.jsonl — honoring CLAUDE_CONFIG_DIR
71
+ * (default ~/.claude). Returns [] on any error or an empty/absent dir (the
72
+ * resolver treats [] as "no candidates"). Never throws — Vault: Returning-null/
73
+ * Option error pattern (typescript-master-vault concepts/Error_Handling). Dirs
74
+ * and stats are probed in parallel (Vault: Parallelism); results are deduped by
75
+ * sessionId so two cwd forms resolving to one dir cannot inflate the count (a
76
+ * false ">1" would make the resolver call it "ambiguous" and refuse).
9
77
  */
10
78
  export declare function listCandidates(cwd: string): Promise<{
11
79
  sessionId: string;
@@ -20,6 +88,11 @@ export declare function detectCurrentSession(cwd: string): Promise<string | null
20
88
  /**
21
89
  * Check whether a Claude Code session file exists on disk.
22
90
  * Returns false if the file is missing (session ended or ID is wrong).
91
+ *
92
+ * Probes every {@link projectDirsFor} form — a pin validated under the canonical
93
+ * cwd must still validate when the agent registered a symlinked/trailing-slash
94
+ * representation of the same directory, or the resolver silently drops the pin
95
+ * and falls back to guessing by mtime.
23
96
  */
24
97
  export declare function validateSession(sessionId: string, cwd: string): Promise<boolean>;
25
98
  export declare function execClaude(sessionId: string, message: string, cwd: string, timeoutMs?: number): Promise<CliExecResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"cc-cli.d.ts","sourceRoot":"","sources":["../../src/services/cc-cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAenD;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKxB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAiLxB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,GACV;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAYpD"}
1
+ {"version":3,"file":"cc-cli.d.ts","sourceRoot":"","sources":["../../src/services/cc-cli.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM5C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWnE;AAcD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ3D;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CASxD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CAa7D;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAiCnD;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAiLxB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,GACV;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAYpD"}