@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 +104 -0
- package/dist/index.js +34 -8
- package/dist/index.js.map +1 -1
- package/dist/services/agent-sessions.d.ts +90 -0
- package/dist/services/agent-sessions.d.ts.map +1 -0
- package/dist/services/agent-sessions.js +120 -0
- package/dist/services/agent-sessions.js.map +1 -0
- package/dist/services/cc-cli.d.ts +78 -5
- package/dist/services/cc-cli.d.ts.map +1 -1
- package/dist/services/cc-cli.js +172 -26
- package/dist/services/cc-cli.js.map +1 -1
- package/dist/services/codex-cli.d.ts +1 -0
- package/dist/services/codex-cli.d.ts.map +1 -1
- package/dist/services/codex-cli.js +1 -1
- package/dist/services/codex-cli.js.map +1 -1
- package/dist/services/exec-remote.d.ts +18 -0
- package/dist/services/exec-remote.d.ts.map +1 -1
- package/dist/services/exec-remote.js +37 -2
- package/dist/services/exec-remote.js.map +1 -1
- package/dist/services/session-resolver.d.ts +6 -0
- package/dist/services/session-resolver.d.ts.map +1 -1
- package/dist/services/session-resolver.js +37 -3
- package/dist/services/session-resolver.js.map +1 -1
- package/dist/startup.d.ts +19 -7
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +101 -22
- package/dist/startup.js.map +1 -1
- package/dist/tools/register-peer.d.ts.map +1 -1
- package/dist/tools/register-peer.js +40 -0
- package/dist/tools/register-peer.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
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
|
|
82
|
-
|
|
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
|
-
},
|
|
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
|
|
97
|
-
|
|
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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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":"
|
|
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"}
|