@essentialai/cogent-bridge 3.23.9 → 3.24.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,3 +1,112 @@
1
+ ## 3.24.1 — 2026-09-15
2
+
3
+ Patch release. One product fix, one user-facing discoverability fix, and three gate fixes — every
4
+ one of them a case of a check reporting something that was not true.
5
+
6
+ ### `cogent_register_peer` could never auto-create a channel (product)
7
+
8
+ Registering with **no channel configured** — the zero-config onboarding path — has never worked.
9
+ The client posted `` label: `Auto-created by ${label}` `` while the relay's own
10
+ `CreateSessionRequestSchema` requires `/^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/`: lowercase, digits and
11
+ hyphens. A capital `A` and spaces cannot match, so every auto-create returned **HTTP 400
12
+ INVALID_INPUT**. Measured against a real relay:
13
+
14
+ ```
15
+ { secret, label: "Auto-created by Payment Orchestrator" } -> 400 invalid_format
16
+ { secret } -> 201 label "slim-tap-27be"
17
+ ```
18
+
19
+ The 400 was visible as far back as 3.5.2 and was read as "avoid falling into auto-create", so the
20
+ symptom was routed around while the path stayed broken. Fixed by omitting the label and letting the
21
+ relay name the channel — it already does so with collision-retry scoped to the org. The generated
22
+ label is now persisted so a human can learn which channel to join.
23
+
24
+ ### Slash commands say what input they expect
25
+
26
+ `/cogent:register` parses a channel name, a password, a peer name and an optional ORGID out of free
27
+ text, and falls back to the project `.env` when given nothing. None of that was discoverable: the
28
+ picker showed only a description. All commands that parse `$ARGUMENTS` now declare `argument-hint`:
29
+
30
+ ```
31
+ /cogent:register [channel "<name>" password "<pw>" peer "<peerId>" [ORGID "<org-id>"]] — omit to use .env
32
+ /cogent:send [peerId] [message] — omit to pick from the peer list
33
+ /cogent:init [<project-path> <project-path> ...] [--share <shared-path>]
34
+ ```
35
+
36
+ ### Gates: three false verdicts fixed
37
+
38
+ - **An unanswered `--human-inbound` window is BLOCKED, not a product break.** When nobody typed the
39
+ line, the harness printed `PRODUCT FAILURES ❌ … customer-visible Slack regression` and exited 1
40
+ — a regression claimed out of an empty chair. Now stamped with a `NO_HUMAN_INPUT` code and
41
+ classified first, so it exits 2 ("proves nothing"). The adjacent real failure — Slack seeing the
42
+ message while the adapter drops it — is asserted to stay exit 1.
43
+ - **Harnesses target the SANDBOX by default.** Seven of them hardcoded
44
+ `argVal("--endpoint", "https://cogent.tools")`, so running the tests was indistinguishable from
45
+ exercising production. `scripts/target-lib.mjs` is now the single source of truth, production is
46
+ an explicit `--target prod`, and an unreachable target BLOCKS rather than degrading — there is
47
+ deliberately no fallback. This immediately exposed `wake-race-test`, which was labelled
48
+ `runnable_offline: true` while silently POSTing to the production relay on every run; it now uses
49
+ the local relay rig.
50
+ - **Per-agent vendor liveness preflight** (`scripts/vendor-liveness-test.mjs`). Asks each vendor
51
+ whether it is alive **through the product's own spawn path** before a matrix burns an hour
52
+ proving the same thing. Exit 2 on a dead vendor, never 1: an expired quota is not a Cogent defect.
53
+
54
+ ### Unchanged
55
+
56
+ No wire-format change. No relay behaviour change beyond the advertised plugin version.
57
+ ## 3.24.0 — 2026-09-14
58
+
59
+ ### An agent knows whether it is IN its channel — and puts itself back
60
+
61
+ **The defect.** Registration is an explicit act: something must call `cogent_register_peer`. A
62
+ bridge starting does NOT register — it only publishes the tools. So an agent session could come up
63
+ holding valid credentials while being ABSENT from its own channel, invisible to everyone messaging
64
+ it. That state was silent AND self-perpetuating, because every repair sat behind a successful
65
+ registration:
66
+
67
+ - `announceBridge()` — written at register time
68
+ - `reapStaleBridges()` — "only after a peer is genuinely registered"
69
+ - `triggerReRegistration()` — reads in-memory state, which is null in a fresh process
70
+
71
+ When registration was the thing that did not happen, nothing recovered and nothing cleaned up.
72
+ Measured on one checkout: **18 live bridges at 3.23.9**, only 2 announcements (both dead pids), last
73
+ successful register five days earlier, and the peer absent from a healthy Team channel whose two
74
+ other agents were present and waiting on it.
75
+
76
+ **Presence**, in the vocabulary this codebase already used:
77
+
78
+ | state | meaning |
79
+ |---|---|
80
+ | `local` | no relay; presence does not apply |
81
+ | `deferred` | cloud-configured, never joined |
82
+ | `offline` | owns a peer for this cwd but has not registered it — nobody can reach it |
83
+ | `online` | registered; the channel lists this agent |
84
+
85
+ Evaluated once at startup, fire-and-forget after the transport connects, and **reported**:
86
+ `offline -> online` at INFO, a stuck `offline` at WARN. `cogent_health_check` now carries
87
+ `presence`, so an agent can ASK its own state instead of inferring it from transport internals.
88
+
89
+ - **`COGENT_AUTO_PRESENCE`** (default ON) — set `false` to observe the state without acting on it.
90
+ - **`cogent_deregister_peer` now clears the stored `peerId`.** That field is the CONSENT SIGNAL:
91
+ without clearing it, the next start would put an agent back into a channel it deliberately left.
92
+ The rest of the credential is kept — channel membership is still valid.
93
+
94
+ **Safety.** Credentials are keyed per working directory, so reading this directory's own file
95
+ cannot inherit another project's session. The agent-session id need not be correct here; auto-relay
96
+ re-resolves it on every wake. We register rather than merely announce: announcing without
97
+ registering would make every leftover bridge immortal — exempt from reap signal 4, yet never
98
+ superseding itself.
99
+
100
+ ### Fixed
101
+
102
+ - **The presence restore now replays the stored `peerSecret`.** `registerPeer()` takes it as the
103
+ 11th, OPTIONAL parameter and the restore passed ten. A peer owned by an EARLIER token — the normal
104
+ state after a re-join — answered `PEER_NOT_YOURS`, the restore failed, and the agent stayed
105
+ offline: precisely the state the feature exists to leave. Per the TypeScript vault
106
+ (`concepts/Optional_Parameters`), an omitted optional parameter is silently `undefined`, so
107
+ **deleting the argument left both `tsc --noEmit` and the unit tests at exit 0**. The positional
108
+ tuple is now extracted as `presenceRegisterArgs()` and every position is pinned by test.
109
+
1
110
  ## 3.23.9 — 2026-09-06
2
111
 
3
112
  ### Fixed — `cogent_health_check` described the RELAY, not this bridge
package/README.md CHANGED
@@ -116,7 +116,9 @@ codex mcp add cogent \
116
116
  ### Gemini
117
117
 
118
118
  Gemini joins a channel as a **standalone poll-agent**, not a plugin — it polls the relay and
119
- answers on its own. Nothing to install as an MCP server, and nothing to `plugin update`.
119
+ answers on its own. Nothing to install as an MCP server, and nothing to `plugin update`: you
120
+ restart the process to pick up a new version. A systemd unit is provided at
121
+ `scripts/cogent-gemini-agent.service`.
120
122
 
121
123
  ```bash
122
124
  export GEMINI_API_KEY=<your-key>
@@ -124,8 +126,18 @@ COGENT_GEMINI_CHANNEL=<channel> COGENT_GEMINI_SECRET=<secret> COGENT_GEMINI_PEER
124
126
  node scripts/cogent-gemini-agent.mjs
125
127
  ```
126
128
 
127
- It registers itself and answers directed messages and human broadcasts like any other agent.
128
- A systemd unit is provided at `scripts/cogent-gemini-agent.service`.
129
+ It registers itself, answers directed messages and human broadcasts, and shows up in
130
+ `/cogent peers` like any other peer. Three differences are worth knowing before you wire it in:
131
+
132
+ 1. **It replies on a poll, not a wake.** Claude Code and Codex are resumed the instant a message
133
+ arrives. Gemini checks the relay on an interval (`COGENT_GEMINI_POLL_MS`, default 4000 ms), so
134
+ its answers typically land a few seconds later than a plugin agent's.
135
+ 2. **It answers; it does not start conversations.** A poll-agent has no `cogent_send_message`
136
+ tool, so it cannot post unprompted or open a thread — it only responds to what it is sent.
137
+ 3. **An API key is required.** Google no longer supports subscription auth for the Gemini CLI, so
138
+ `GEMINI_API_KEY` is the only rail. If the key is missing, disabled or out of quota the agent
139
+ still registers and joins the channel — it then replies with the vendor's error instead of
140
+ going silent, so check its replies, not just the peer list.
129
141
 
130
142
  <!-- COGENT:COMMANDS:START -->
131
143
  <!-- Generated by scripts/sync-docs.mjs from server/src/install/commands.ts.
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 { startUpdateCheckLoop } from "./backend/npm-update-check.js";
5
- import { runStartup, runPreflights, resolvePendingLabel, isCloudMode, setInboxNotifier, cloudWsClient, cloudInbox, autoRelay } from "./startup.js";
5
+ import { runStartup, runPreflights, resolvePendingLabel, isCloudMode, setInboxNotifier, cloudWsClient, cloudInbox, autoRelay, restorePeerPresence } 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";
@@ -145,6 +145,18 @@ async function main() {
145
145
  void runPreflights().catch((err) => {
146
146
  logger.warn(`Preflight error (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
147
147
  });
148
+ // PRESENCE. Work out whether this agent is actually IN its channel, and if it owns a
149
+ // peer there but has not registered it, put it back. An agent whose bridge is running
150
+ // while its peer is absent is invisible to everyone messaging it, and every repair in
151
+ // this client used to sit behind a successful register — so when registration was the
152
+ // thing that did not happen, nothing recovered and nothing cleaned up. Measured: 18
153
+ // live bridges on one checkout, peer absent from a healthy channel for five days.
154
+ // States and the full explanation: services/peer-presence.ts.
155
+ // Fire-and-forget for the same reason as the two above: it does network I/O and must
156
+ // never precede `initialize`. Never throws; guarded anyway.
157
+ void restorePeerPresence().catch((err) => {
158
+ logger.warn(`Presence restore error (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
159
+ });
148
160
  // Best-effort proactive update nudge. Fire-and-forget: never blocks the server
149
161
  // becoming ready, never throws, and writes only to STDERR (stdout is the MCP
150
162
  // protocol). Stays silent unless a newer cogent-bridge is published on npm.
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,77 @@
1
+ /** The four states an agent can be in with respect to its channel. */
2
+ export type PresenceState = "local" | "deferred" | "offline" | "online";
3
+ /** What a presence evaluation concluded, and what it did about it. */
4
+ export type PresenceOutcome = {
5
+ state: "local" | "deferred" | "online";
6
+ restored: false;
7
+ detail: string;
8
+ } | {
9
+ state: "online";
10
+ restored: true;
11
+ peerId: string;
12
+ detail: string;
13
+ } | {
14
+ state: "offline";
15
+ restored: false;
16
+ detail: string;
17
+ };
18
+ export interface PresenceDeps {
19
+ /** True only when the ACTIVE backend really is the cloud one — not a configuration flag. */
20
+ isCloudConfigured: () => boolean;
21
+ /** True when something already registered in THIS process; its state is authoritative. */
22
+ alreadyRegisteredInProcess: () => boolean;
23
+ /**
24
+ * Is a LIVE bridge already announcing this peer for this working directory?
25
+ *
26
+ * 🔴 THE SHARED-CHECKOUT CASE. There is ONE credential file per cwd, so every bridge in a
27
+ * checkout reads the SAME peerId. Two agents legitimately share a checkout under DIFFERENT
28
+ * peer identities (marvin-coder + marvin-mobile — which is why the ownership registry is keyed
29
+ * on (cwd, peerId) and not cwd alone). Without this check, a second bridge restores the first
30
+ * agent's peerId, supersession stands one of them down, and an agent silently loses its own
31
+ * identity. Proven by controlled A/B: with the restore unguarded, wake-dedup-matrix-test
32
+ * --topology two-peer could no longer detect its own break (STAYED_GREEN, 0 overlapping
33
+ * resumes); with presence off it went RED with 1. The gate was telling the truth.
34
+ */
35
+ peerAlreadyServed: (peerId: string) => boolean;
36
+ loadCredentials: () => Promise<{
37
+ endpoint?: string;
38
+ sessionId?: string;
39
+ token?: string;
40
+ peerId?: string;
41
+ /** AUD-003 ownership proof, issued once when this peer was created. */
42
+ peerSecret?: string;
43
+ } | null>;
44
+ /** Go cloud-live and register, restoring the channel entry. Throws on failure. */
45
+ goOnline: (peer: {
46
+ peerId: string;
47
+ endpoint: string;
48
+ sessionId: string;
49
+ token: string;
50
+ cwd: string;
51
+ label: string;
52
+ /** Replayed on register so a peer owned by an EARLIER token can be reclaimed. */
53
+ peerSecret?: string;
54
+ }) => Promise<void>;
55
+ cwd: () => string;
56
+ env?: NodeJS.ProcessEnv;
57
+ }
58
+ /**
59
+ * Automatic presence restore. ON by default — a default-off self-heal heals nobody.
60
+ * Opt out with COGENT_AUTO_PRESENCE=false to observe the state without acting on it.
61
+ */
62
+ export declare function autoPresenceEnabled(env?: NodeJS.ProcessEnv): boolean;
63
+ /**
64
+ * Determine this agent's presence and, when it is `offline` with a peer it owns,
65
+ * bring it back `online`.
66
+ *
67
+ * NEVER THROWS. Runs fire-and-forget after the MCP transport connects — the
68
+ * `initialize` handshake must never wait on I/O (scripts/startup-nonblocking-test.mjs).
69
+ */
70
+ export declare function restorePresence(deps: PresenceDeps): Promise<PresenceOutcome>;
71
+ /** Record the evaluated presence. Called by startup after each evaluation. */
72
+ export declare function setPresence(outcome: PresenceOutcome): void;
73
+ /** The agent's own view of its channel presence. Null until first evaluated. */
74
+ export declare function getPresence(): PresenceOutcome | null;
75
+ /** Report the outcome once, at a level that matches how much it matters. */
76
+ export declare function logPresence(outcome: PresenceOutcome): void;
77
+ //# sourceMappingURL=peer-presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-presence.d.ts","sourceRoot":"","sources":["../../src/services/peer-presence.ts"],"names":[],"mappings":"AAuDA,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE,sEAAsE;AACtE,MAAM,MAAM,eAAe,GACvB;IAAE,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,4FAA4F;IAC5F,iBAAiB,EAAE,MAAM,OAAO,CAAC;IACjC,0FAA0F;IAC1F,0BAA0B,EAAE,MAAM,OAAO,CAAC;IAC1C;;;;;;;;;;;OAWG;IACH,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,eAAe,EAAE,MAAM,OAAO,CAAC;QAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,uEAAuE;QACvE,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI,CAAC,CAAC;IACV,kFAAkF;IAClF,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,iFAAiF;QACjF,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAIjF;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAkFlF;AAYD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAE1D;AAED,gFAAgF;AAChF,wBAAgB,WAAW,IAAI,eAAe,GAAG,IAAI,CAEpD;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAW1D"}
@@ -0,0 +1,181 @@
1
+ // ---------------------------------------------------------------------------
2
+ // PEER PRESENCE — what state this agent is in on its channel, why, and getting
3
+ // it back to `online` without anyone being told to restart something.
4
+ //
5
+ // ── THE VOCABULARY, taken from what this codebase already says ──────────────
6
+ // "local" COGENT_LOCAL / no relay. Presence is not a concept here.
7
+ // "deferred" EXISTING TERM (backend-provider.ts): cloud-CONFIGURED but never joined —
8
+ // there are no stored credentials for this working directory. Nothing to
9
+ // restore; the agent must join a channel first.
10
+ // "offline" Stored credentials name a peer for this working directory, but this
11
+ // process has not registered it. The bridge is running; the AGENT IS NOT
12
+ // IN THE CHANNEL. Other peers see it as absent and cannot address it.
13
+ // "online" Registered. The channel lists this agent and messages reach it.
14
+ //
15
+ // ── HOW AN AGENT ENDS UP `offline` — the mechanism, stated plainly ──────────
16
+ // Registration is an explicit act: something must call `cogent_register_peer`. The
17
+ // bridge starting does NOT register — it only publishes the tools. So every time an
18
+ // agent session starts, the bridge comes up holding valid credentials while the agent
19
+ // is absent from its own channel, and stays that way until the agent calls the tool.
20
+ // If the agent never calls it — it forgot, it crashed first, the session was resumed
21
+ // from a transcript that had already registered — the peer is never (re-)created, and
22
+ // the relay's stale sweep eventually deletes the entry left over from last time.
23
+ //
24
+ // That state used to be both SILENT and SELF-PERPETUATING, because every repair in
25
+ // this client sat behind a successful registration:
26
+ // - announceBridge() (auto-relay) — written at register time
27
+ // - reapStaleBridges() (auto-relay) — "only after a peer is genuinely registered"
28
+ // - triggerReRegistration()(startup) — reads in-memory state, null in a fresh process
29
+ // So when registration was the thing that did not happen, nothing recovered and nothing
30
+ // cleaned up. Measured 2026-09-14 on one checkout: 18 live bridges at 3.23.9, last
31
+ // successful register five days earlier, the peer absent from a healthy Team channel
32
+ // whose two other agents were present and waiting on it.
33
+ //
34
+ // ── WHY RESTORING IT IS SAFE ────────────────────────────────────────────────
35
+ // - Credentials are keyed PER WORKING DIRECTORY
36
+ // (~/.cogent/credentials/<sha256(cwd)[..16]>.json, commit f758d8ee). Reading this
37
+ // directory's own file cannot inherit another project's session — that was the
38
+ // LEGACY GLOBAL file, which is only ever `fs.access`'d for a warning and never read.
39
+ // - The agent-session id need not be correct here: auto-relay re-resolves it on every
40
+ // wake (auto-relay.ts, "Only adopt a resolved id") and register-peer itself falls
41
+ // back to the channel session id on an unresolved resolution.
42
+ // - `peerId` in the credential is the CONSENT SIGNAL. create/join write credentials
43
+ // without one, and deregister clears it, so a deliberate departure is never undone.
44
+ //
45
+ // ── WHY WE DO NOT JUST ANNOUNCE ─────────────────────────────────────────────
46
+ // Reap signal 4 skips any ANNOUNCED pid, reasoning that an announced bridge is 3.23.4+
47
+ // and stands down by itself. Announcing WITHOUT registering would make every leftover
48
+ // immortal: exempt from reaping, yet never superseding itself (supersession is only
49
+ // consulted on a wake, which a bridge with no peer never gets). The invariant is "a live
50
+ // bridge either REGISTERS — and so announces and can stand down — or it stays reapable."
51
+ // Restoring presence registers, so the announce it performs is correct.
52
+ // ---------------------------------------------------------------------------
53
+ import { logger } from "../logger.js";
54
+ /**
55
+ * Automatic presence restore. ON by default — a default-off self-heal heals nobody.
56
+ * Opt out with COGENT_AUTO_PRESENCE=false to observe the state without acting on it.
57
+ */
58
+ export function autoPresenceEnabled(env = process.env) {
59
+ const v = env.COGENT_AUTO_PRESENCE;
60
+ if (v === undefined || v === "")
61
+ return true;
62
+ return !/^(0|false|no|off)$/i.test(v.trim());
63
+ }
64
+ /**
65
+ * Determine this agent's presence and, when it is `offline` with a peer it owns,
66
+ * bring it back `online`.
67
+ *
68
+ * NEVER THROWS. Runs fire-and-forget after the MCP transport connects — the
69
+ * `initialize` handshake must never wait on I/O (scripts/startup-nonblocking-test.mjs).
70
+ */
71
+ export async function restorePresence(deps) {
72
+ try {
73
+ if (!deps.isCloudConfigured()) {
74
+ return { state: "local", restored: false, detail: "local mode — presence does not apply" };
75
+ }
76
+ if (deps.alreadyRegisteredInProcess()) {
77
+ return { state: "online", restored: false, detail: "registered by this process" };
78
+ }
79
+ const creds = await deps.loadCredentials();
80
+ if (!creds || !creds.endpoint || !creds.sessionId || !creds.token) {
81
+ return {
82
+ state: "deferred",
83
+ restored: false,
84
+ detail: "no stored channel credentials for this working directory — join a channel first",
85
+ };
86
+ }
87
+ if (!creds.peerId) {
88
+ // Credentials WITHOUT a peerId: created by join/create, or cleared by a deliberate
89
+ // deregister. Either way this agent is not meant to be in the channel right now.
90
+ return {
91
+ state: "deferred",
92
+ restored: false,
93
+ detail: "channel credentials exist but name no peer — nothing was registered here",
94
+ };
95
+ }
96
+ const peerId = creds.peerId;
97
+ // Someone live is already this peer. Registering again would take the identity from them.
98
+ if (deps.peerAlreadyServed(peerId)) {
99
+ return {
100
+ state: "online",
101
+ restored: false,
102
+ detail: `peer '${peerId}' is already served by a live bridge for this working directory`,
103
+ };
104
+ }
105
+ if (!autoPresenceEnabled(deps.env ?? process.env)) {
106
+ // Report the state, take no action. This is the "observe, do not act" mode.
107
+ return {
108
+ state: "offline",
109
+ restored: false,
110
+ detail: `peer '${peerId}' is not registered and COGENT_AUTO_PRESENCE is off — this agent is absent from its channel`,
111
+ };
112
+ }
113
+ await deps.goOnline({
114
+ peerId,
115
+ endpoint: creds.endpoint,
116
+ sessionId: creds.sessionId,
117
+ token: creds.token,
118
+ cwd: deps.cwd(),
119
+ // 🔴 THE PEER'S DISPLAY LABEL IS NOT PERSISTED ANYWHERE — only the CHANNEL's is,
120
+ // in credentials.label (create-session.ts writes `label: data.label`; join-session
121
+ // writes the resolved channel label). Using that would label the agent after the
122
+ // channel: a restore once put "paymentorchestrator" beside a sibling called "POFE".
123
+ // peerId is the one identifier we hold that genuinely names this agent. The
124
+ // operator's chosen label returns on the next cogent_register_peer, which carries it.
125
+ label: peerId,
126
+ // 🔴 WITHOUT THIS THE RESTORE CANNOT RECLAIM ITS OWN PEER. A re-join issues a NEW
127
+ // token, and AUD-003 ownership is proven by the token that CREATED the peer or by
128
+ // the secret issued with it. Omitting it means an entry owned by an earlier token
129
+ // answers PEER_NOT_YOURS and the agent stays offline — precisely the state this
130
+ // module exists to leave. The secret is in the credential file; send it.
131
+ peerSecret: creds.peerSecret,
132
+ });
133
+ return {
134
+ state: "online",
135
+ restored: true,
136
+ peerId,
137
+ // Says what THIS process did. Whether the relay now lists the peer is the relay's
138
+ // state, and asserting it from here would be a success nobody measured.
139
+ detail: `re-registered peer '${peerId}' from stored credentials`,
140
+ };
141
+ }
142
+ catch (err) {
143
+ const reason = err instanceof Error ? err.message : String(err);
144
+ return {
145
+ state: "offline",
146
+ restored: false,
147
+ detail: `presence restore failed: ${reason}`,
148
+ };
149
+ }
150
+ }
151
+ /**
152
+ * Last evaluated presence, so cogent_health_check can report the agent's own state
153
+ * without re-probing the relay.
154
+ *
155
+ * It lives HERE, in the leaf module, and not in startup.ts: a tool importing startup.ts
156
+ * creates an import cycle (tools -> startup -> tools) and the imported binding reads back
157
+ * `undefined` at call time. That cost 26 health-check tests once; do not move it.
158
+ */
159
+ let lastPresence = null;
160
+ /** Record the evaluated presence. Called by startup after each evaluation. */
161
+ export function setPresence(outcome) {
162
+ lastPresence = outcome;
163
+ }
164
+ /** The agent's own view of its channel presence. Null until first evaluated. */
165
+ export function getPresence() {
166
+ return lastPresence;
167
+ }
168
+ /** Report the outcome once, at a level that matches how much it matters. */
169
+ export function logPresence(outcome) {
170
+ if (outcome.state === "online" && outcome.restored) {
171
+ logger.info(`Presence: offline -> online — ${outcome.detail}. This agent is addressable on its ` +
172
+ `channel again, and leftover bridges for this checkout are now reapable.`);
173
+ }
174
+ else if (outcome.state === "offline") {
175
+ logger.warn(`Presence: OFFLINE — ${outcome.detail}.`);
176
+ }
177
+ else {
178
+ logger.debug(`Presence: ${outcome.state} — ${outcome.detail}.`);
179
+ }
180
+ }
181
+ //# sourceMappingURL=peer-presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-presence.js","sourceRoot":"","sources":["../../src/services/peer-presence.ts"],"names":[],"mappings":"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"}
package/dist/startup.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { CloudWsClient, MessageInbox } from "./cloud/index.js";
2
2
  import { HttpClient } from "./cloud/http-client.js";
3
+ import { type PresenceOutcome } from "./services/peer-presence.js";
3
4
  export { autoRelay } from "./services/auto-relay.js";
4
5
  export declare let cloudWsClient: CloudWsClient | null;
5
6
  export declare let cloudInbox: MessageInbox | null;
@@ -18,6 +19,33 @@ export declare function setInboxNotifier(fn: () => void): void;
18
19
  * Debounced: only one re-registration can run at a time.
19
20
  */
20
21
  export declare function triggerReRegistration(): Promise<void>;
22
+ /**
23
+ * Evaluate this agent's PRESENCE on its channel and, when it is `offline` with a peer it
24
+ * owns, bring it back `online`. See services/peer-presence.ts for the state vocabulary,
25
+ * how an agent ends up offline, and why restoring it is safe.
26
+ *
27
+ * Fire-and-forget from index.ts AFTER the transport connects — it performs network I/O
28
+ * and must never precede the `initialize` handshake (scripts/startup-nonblocking-test.mjs).
29
+ */
30
+ /**
31
+ * The exact positional argument tuple for a presence restore's registerPeer call.
32
+ *
33
+ * 🔴 EXTRACTED SO IT CAN BE TESTED. registerPeer's trailing parameters are OPTIONAL, and
34
+ * per the TypeScript vault (concepts/Optional_Parameters -> sources/.../0281) omitting one
35
+ * "will provide the function with an `undefined` value parameter" — silently. Measured:
36
+ * deleting `peerSecret` from the call site left BOTH `tsc --noEmit` AND the presence unit
37
+ * tests at exit 0, because those tests cover the module, not this wiring. A dropped
38
+ * ownership proof means a peer owned by an earlier token can never be reclaimed, and the
39
+ * agent stays offline. The type system cannot see it; this tuple + its test can.
40
+ */
41
+ export declare function presenceRegisterArgs(p: {
42
+ peerId: string;
43
+ sessionId: string;
44
+ cwd: string;
45
+ label: string;
46
+ peerSecret?: string;
47
+ }): [string, string, string, string, string, "agent", undefined, undefined, string, string, string | undefined];
48
+ export declare function restorePeerPresence(): Promise<PresenceOutcome>;
21
49
  /** Test seam: is a label still waiting to be resolved post-connect? */
22
50
  export declare function hasPendingLabelResolve(): boolean;
23
51
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAapD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAiBrD,eAAO,IAAI,aAAa,EAAE,aAAa,GAAG,IAAW,CAAC;AACtD,eAAO,IAAI,UAAU,EAAE,YAAY,GAAG,IAAW,CAAC;AAClD,eAAO,IAAI,eAAe,EAAE,UAAU,GAAG,IAAW,CAAC;AAKrD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AASD,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAGrD;AAiHD;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CA8C3D;AAqFD,uEAAuE;AACvE,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsCzD;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAwCf;AAMD,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAqMhD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBnD"}
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAa9G,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAiBrD,eAAO,IAAI,aAAa,EAAE,aAAa,GAAG,IAAW,CAAC;AACtD,eAAO,IAAI,UAAU,EAAE,YAAY,GAAG,IAAW,CAAC;AAClD,eAAO,IAAI,eAAe,EAAE,UAAU,GAAG,IAAW,CAAC;AAKrD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AASD,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAGrD;AAiHD;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CA8C3D;AAED;;;;;;;GAOG;AACH;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAc9G;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,eAAe,CAAC,CAsCpE;AAsFD,uEAAuE;AACvE,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsCzD;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAwCf;AAMD,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAqMhD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBnD"}
package/dist/startup.js CHANGED
@@ -14,6 +14,8 @@ import { loadCredentials, hasJoinedInProcess, legacyGlobalCredentialsExist, } fr
14
14
  import { CloudWsClient, MessageInbox } from "./cloud/index.js";
15
15
  import { HttpClient } from "./cloud/http-client.js";
16
16
  import { autoRelay } from "./services/auto-relay.js";
17
+ import { restorePresence, logPresence, setPresence } from "./services/peer-presence.js";
18
+ import { listLiveBridges } from "./services/wake-ownership.js";
17
19
  import { heartbeat } from "./services/heartbeat.js";
18
20
  import { preflightCodex, preflightCodexSandbox, preflightCodexServerCollision, } from "./services/codex-preflight.js";
19
21
  import { setSandboxOverride } from "./services/codex-cli.js";
@@ -184,6 +186,81 @@ export async function triggerReRegistration() {
184
186
  reRegistrationInProgress = false;
185
187
  }
186
188
  }
189
+ /**
190
+ * Evaluate this agent's PRESENCE on its channel and, when it is `offline` with a peer it
191
+ * owns, bring it back `online`. See services/peer-presence.ts for the state vocabulary,
192
+ * how an agent ends up offline, and why restoring it is safe.
193
+ *
194
+ * Fire-and-forget from index.ts AFTER the transport connects — it performs network I/O
195
+ * and must never precede the `initialize` handshake (scripts/startup-nonblocking-test.mjs).
196
+ */
197
+ /**
198
+ * The exact positional argument tuple for a presence restore's registerPeer call.
199
+ *
200
+ * 🔴 EXTRACTED SO IT CAN BE TESTED. registerPeer's trailing parameters are OPTIONAL, and
201
+ * per the TypeScript vault (concepts/Optional_Parameters -> sources/.../0281) omitting one
202
+ * "will provide the function with an `undefined` value parameter" — silently. Measured:
203
+ * deleting `peerSecret` from the call site left BOTH `tsc --noEmit` AND the presence unit
204
+ * tests at exit 0, because those tests cover the module, not this wiring. A dropped
205
+ * ownership proof means a peer owned by an earlier token can never be reclaimed, and the
206
+ * agent stays offline. The type system cannot see it; this tuple + its test can.
207
+ */
208
+ export function presenceRegisterArgs(p) {
209
+ return [
210
+ p.peerId,
211
+ p.sessionId,
212
+ p.cwd,
213
+ p.label,
214
+ CLIENT_VERSION,
215
+ "agent",
216
+ undefined, // channelSessionId — not tracked on a presence restore
217
+ undefined, // capabilities — likewise
218
+ deriveWorkspaceId(p.cwd),
219
+ p.sessionId, // threadId
220
+ p.peerSecret, // AUD-003 ownership proof — reclaims a peer owned by an earlier token
221
+ ];
222
+ }
223
+ export async function restorePeerPresence() {
224
+ const outcome = await restorePresence({
225
+ // The endpoint being a relay URL is what makes presence meaningful. We deliberately do
226
+ // NOT gate on the active backend being the cloud one: at startup it is still the
227
+ // deferred FileBackend placeholder, and gating on it made this whole path inert
228
+ // (measured: "skipped: not-cloud-mode" with valid credentials on disk).
229
+ isCloudConfigured: () => {
230
+ const ep = getConfig().COGENT_ENDPOINT;
231
+ return !!ep && isCloudEndpoint(ep);
232
+ },
233
+ alreadyRegisteredInProcess: () => autoRelay.getDiagnostics().registered,
234
+ peerAlreadyServed: (peerId) => {
235
+ try {
236
+ return listLiveBridges(process.cwd(), peerId).some((b) => b.pid !== process.pid);
237
+ }
238
+ catch {
239
+ return false; // unreadable registry ⇒ no proof of competition ⇒ proceed
240
+ }
241
+ },
242
+ loadCredentials: () => loadCredentials(),
243
+ cwd: () => process.cwd(),
244
+ goOnline: async ({ peerId, endpoint, sessionId, token, cwd, label, peerSecret }) => {
245
+ // Swap the deferred placeholder for a real cloud backend the supported way — the
246
+ // same call join-session uses. Building a second backend here instead would leave
247
+ // the tools on the placeholder and the presence path on another: a split brain.
248
+ await reinitCloudBackend(endpoint, sessionId, token);
249
+ const backend = getBackend();
250
+ await backend.registerPeer(...presenceRegisterArgs({ peerId, sessionId, cwd, label, peerSecret }));
251
+ // Wiring auto-relay is the POINT, not a side effect: setLocalPeer() is what calls
252
+ // announceBridge() and reapStaleBridges(), so restoring presence is also what makes
253
+ // leftover bridges for this checkout reapable again.
254
+ autoRelay.setLocalPeer(peerId, sessionId, cwd, "agent", label, sessionId);
255
+ if (cloudWsClient)
256
+ cloudWsClient.setPeerId(peerId);
257
+ heartbeat.restart();
258
+ },
259
+ });
260
+ setPresence(outcome);
261
+ logPresence(outcome);
262
+ return outcome;
263
+ }
187
264
  // ---------------------------------------------------------------------------
188
265
  // Helper to create a CloudWsClient with standard callbacks
189
266
  // ---------------------------------------------------------------------------