@securecoms/agent-bridge 0.30.0 → 0.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,13 +35,9 @@ The server still sees ciphertext only. The trust boundary is the machine running
35
35
  - **Node.js 24 LTS or newer.** The daemon requires >= 24 (node:sqlite stable API).
36
36
  - A valid SecureComs agent credential (`agent.json`, `chmod 600`)
37
37
 
38
- You don't have to check your Node version yourself. The installer self-checks before anything else runs: on an older Node it prints a clear diagnosis instead of crashing, and on macOS it offers to install Node 24 LTS for you. The install is consent-gated and names its source before asking. If Homebrew is present it runs `brew install node` (user-space, no sudo); otherwise it downloads the official installer from nodejs.org over TLS and verifies the published SHA-256 checksum (an integrity check on the download) before running it.
38
+ You don't have to check your Node version yourself. The installer checks it before anything else runs. On an older Node it says so plainly, and on macOS it offers to install Node 24 from nodejs.org after asking you. That step needs your computer's administrator password. It checks the download's published SHA-256 checksum before running it.
39
39
 
40
- To install Node 24 manually instead:
41
-
42
- 1. Official installer (recommended): download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it.
43
- 2. Homebrew (macOS): `brew install node`
44
- 3. nvm: `nvm install 24 && nvm use 24`
40
+ To install Node 24 yourself, download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it. Then run the install command again.
45
41
 
46
42
  Always re-run the install command after upgrading Node — the background service pins the Node path it was installed with.
47
43
 
@@ -935,6 +931,81 @@ copied after the owner's next typed message.
935
931
 
936
932
  ---
937
933
 
934
+ ## Release notes: 0.30.2
935
+
936
+ <!-- [COPY, approved by Eric 2026-10-15] -->
937
+
938
+ 0.30.2 fixes the guided Node upgrade, makes reinstall safe while the agent is
939
+ running, and prints plain messages when the agent cannot start.
940
+
941
+ - **The Node upgrade works again on macOS.** When this computer has an older
942
+ Node, install offers to install Node 24 from nodejs.org. Before 0.30.2 this
943
+ step could not find the installer and stopped without saying why. It now
944
+ finds it, checks its published SHA-256 checksum, and asks first:
945
+ `SecureComs needs Node 24. Install it now from nodejs.org? This needs your
946
+ computer's administrator password. [y/N]`. If the step fails after you say
947
+ yes, it prints: `Couldn't install Node 24 from nodejs.org.`
948
+ - **Homebrew is no longer used.** The guided upgrade always uses the
949
+ nodejs.org installer.
950
+ - **Reinstall while the agent runs.** Install now waits up to 10 seconds for
951
+ the running Connector to stop before it starts the new one. Before, the new
952
+ copy could start too early, find the old copy still running, and stay
953
+ stopped. If the old copy does not stop in time, install starts nothing and
954
+ tells you to run install again.
955
+ - **Plain messages when the agent cannot start.** A missing, unreadable or
956
+ invalid key file, or a bad setting in it, now each print one short line
957
+ that says what to do. File paths are printed only in a terminal, not in the
958
+ service log.
959
+ - **No key file found.** If the copied install command finds no key file, it
960
+ prints: `No key file was found in your Downloads folder. Download the key
961
+ file again from SecureComs, or add --credential <path>.` An empty
962
+ `--credential` no longer falls back to the `SECURECOMS_CREDENTIAL`
963
+ environment variable.
964
+ - **Node 24 or newer.** The package now declares `node >= 24`, which the
965
+ Connector already required to run.
966
+
967
+ You do not need to change anything after the upgrade. Run the install command
968
+ again only if you want the new install behavior now.
969
+
970
+ ---
971
+
972
+ ## Release notes: 0.30.1
973
+
974
+ <!-- [COPY, approved by Eric 2026-10-14] -->
975
+
976
+ 0.30.1 makes sure only one copy of an agent runs, and makes install say
977
+ clearly when the agent did not start.
978
+
979
+ - **Reinstall once after you upgrade.** Run the install command again for
980
+ each agent. It writes a new service file. The old file restarts the
981
+ Connector every time it stops, so a refused second copy would keep
982
+ restarting and print the "already running" message every 10 seconds.
983
+ - **One copy per agent on a computer.** A second copy of the same agent
984
+ stops before it connects and prints: `This agent is already running on this
985
+ computer (process <pid>). Stop that copy first, or run: agent-bridge status`
986
+ - **Port already in use.** If another program holds the Connector's port, the
987
+ agent does not start and prints: `Port <n> is already in use by another
988
+ program on this computer, so the agent did not start.`
989
+ - **Install checks that the agent really started.** Install now waits up to
990
+ 75 seconds for the Connector to answer its health check. If it does not
991
+ answer, install fails and names the service log.
992
+ - **Install refuses a second install of the same agent.** If the agent is
993
+ already installed in another folder or with a different key, install stops
994
+ before it changes anything and prints: `This agent is already installed in
995
+ <dir> with a different key. Uninstall it first: agent-bridge uninstall
996
+ --label <label>`. A downloaded copy such as `margo (1).json` installs as the
997
+ same agent, not as a new one.
998
+ - **The service restarts only after a failure.** On macOS the new service
999
+ file restarts the Connector only when it exits with an error. On Linux the
1000
+ unit uses `Restart=on-failure`.
1001
+ - **Prepared for one active connection per agent.** When the server reports
1002
+ that the agent connected from another place, this copy stops all network
1003
+ traffic and prints: `This agent connected from another place, so this copy
1004
+ stopped. Stop the other copy, then restart this one.` It stays stopped and
1005
+ does not restart itself. The server side of this check is not turned on yet.
1006
+
1007
+ ---
1008
+
938
1009
  ## Release notes: 0.30.0
939
1010
 
940
1011
  <!-- [COPY, approved by Eric 2026-10-12] -->
package/dist/bin.d.ts CHANGED
@@ -1,9 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * SecureComs Agent Bridge — HTTP daemon entry point.
4
+ * Long-running diagnostics go to stderr through the content-free service
5
+ * logger. The local bearer token is persisted to the `bridge-token` file (see
6
+ * tokenFile below); startup emits only a fixed token-loaded event, never the
7
+ * token, its fingerprint, or its path. This prevents the former
8
+ * `BRIDGE_TOKEN=<value>` leak into journald/launchd logs.
9
+ */
10
+ import { type BridgeConfig, type ConfigErrorReason } from "./config.js";
2
11
  import { BridgeAgent } from "./bridge-agent.js";
3
12
  import { createHttpServer } from "./http-server.js";
4
13
  import { attachAgentWebSocket } from "./ws-server.js";
14
+ import { createServer as netCreateServer } from "node:net";
15
+ import { type DaemonHealth } from "./service/install-prep.js";
5
16
  import { type ResolveNodeDeps } from "./service/resolve-node.js";
6
17
  import { type Harness } from "./service/wire.js";
18
+ import { type ServiceLog } from "./service-log.js";
19
+ import { type LockResult } from "./service/instance-lock.js";
7
20
  export declare function nodeMajorOk(version: string): boolean;
8
21
  /**
9
22
  * A redacted bearer-token hint for explicit foreground install output: a short
@@ -14,7 +27,10 @@ export declare function tokenFingerprint(token: string): string;
14
27
  export declare function parseVerb(argv: string[]): "install" | "uninstall" | "status" | "wire" | "unwire" | "models" | "pin" | null;
15
28
  export declare function slugFromCredentialPath(credentialPath: string): string;
16
29
  export declare function chooseInstallExecPath(runningExecPath: string, override: string | undefined, deps: ResolveNodeDeps): string;
30
+ export declare function realGetHealth(port: number): DaemonHealth | null;
17
31
  export declare function realCopyConnector(srcDir: string, dstDir: string, harness: Harness): void;
32
+ /** The stderr line (with newline) a failed service verb prints. */
33
+ export declare function formatServiceError(verb: string, e: unknown): string;
18
34
  /**
19
35
  * Markdown memory 7a, N-L5: start the agent, THEN create the HTTP server and
20
36
  * attach /v1/ws. start() applies the runtime config (and any clear issued
@@ -35,3 +51,88 @@ export declare function startThenServe(agent: BridgeAgent, opts: {
35
51
  closeAllSseStreams: () => void;
36
52
  agentWs: ReturnType<typeof attachAgentWebSocket>;
37
53
  }>;
54
+ /** B1 `held` poll (spec §4.1.2, audit r2 M-3): the holder may be this same
55
+ * service's previous process, still inside shutdown()'s 8 s drain window. */
56
+ export declare const HELD_POLL_TOTAL_MS = 10000;
57
+ export declare const HELD_POLL_INTERVAL_MS = 500;
58
+ /** Extra acquires after the poll when "held" carries no holder pid (a takeover
59
+ * race). M1 names a real pid or is not printed. */
60
+ export declare const HELD_NO_PID_REACQUIRES = 3;
61
+ /** shutdown()'s hard-exit fallback for a hung agent.close() drain (ADR-0008 H1). */
62
+ export declare const SHUTDOWN_HARD_EXIT_MS = 8000;
63
+ /** The lock surface runMode uses (InstanceLock in production). */
64
+ export interface RunModeLock {
65
+ acquire(): {
66
+ result: LockResult;
67
+ holderPid?: number;
68
+ };
69
+ release(): void;
70
+ lastError?: unknown;
71
+ }
72
+ /**
73
+ * Everything run mode touches outside the process, injected so the start
74
+ * order, the refusal exits and the release timing are testable
75
+ * (bin-run-mode.test.ts). `exit` never returns in production; runMode
76
+ * returns right after calling it so a recording test double also stops.
77
+ */
78
+ export interface RunModeDeps {
79
+ credentialPath: string;
80
+ storePath?: string;
81
+ argv: string[];
82
+ env: NodeJS.ProcessEnv;
83
+ home: string;
84
+ newLock(path: string): RunModeLock;
85
+ newAgent(cfg: BridgeConfig): BridgeAgent;
86
+ /** The B2 pre-bind's socket layer (node:net in production). */
87
+ net: {
88
+ createServer: typeof netCreateServer;
89
+ };
90
+ serve: typeof startThenServe;
91
+ now(): number;
92
+ sleep(ms: number): Promise<void>;
93
+ /** Arms the shutdown hard-exit fallback; returns its cancel. */
94
+ setHardExitTimer(cb: () => void, ms: number): () => void;
95
+ onSignal(signal: "SIGINT" | "SIGTERM", handler: () => void): void;
96
+ /** Synchronous `process.once("exit")` registration. */
97
+ onExit(handler: () => void): void;
98
+ exit(code: number): void;
99
+ /** One line of approved user-facing copy (spec §8). */
100
+ print(line: string): void;
101
+ /**
102
+ * Whether stderr is a terminal. Under launchd and systemd stderr IS the
103
+ * service log, so a printed line may carry a path only when this is true
104
+ * (spec 2026-10-15 §5.3 path rule).
105
+ */
106
+ isTTY: boolean;
107
+ log: ServiceLog;
108
+ }
109
+ export type StartupConfigReason = ConfigErrorReason | "identity_id_invalid" | "bind_refused" | "unknown";
110
+ /**
111
+ * The one human line for a run-mode config refusal (spec 2026-10-15 §5.3,
112
+ * E1-E7 approved; E5 with source=env is the BT-5 draft). The path is shown
113
+ * only on a TTY; `field` is one of the six fixed REQUIRED names. `unknown`
114
+ * has no line. Never built from an error message.
115
+ */
116
+ export declare function configErrorLine(reason: StartupConfigReason, o: {
117
+ path: string;
118
+ isTTY: boolean;
119
+ field?: string;
120
+ source?: "file" | "env";
121
+ }): string | undefined;
122
+ /**
123
+ * The daemon's run mode (spec §4.1.1). Order, each step gated on the last:
124
+ * 1. loadConfig (+ daemon args, bind check);
125
+ * 2. canonical-UUID check of identity_id (it becomes a path);
126
+ * 3. B1 per-identity lock at ~/.securecoms/locks/<identity_id>.lock;
127
+ * 4. resolveToken (it can WRITE the token file, so only after the lock);
128
+ * 5. B2 pre-bind of daemon.port: a bare net server that destroys every
129
+ * connection, so a taken port refuses before the store opens or any
130
+ * call to SecureComs is made;
131
+ * 6. new BridgeAgent (opens the SQLite MLS store);
132
+ * 7. startThenServe, then hand the pre-bound handle to the HTTP server.
133
+ * The lock is released only after agent.close() settles (audit H-2): in the
134
+ * close().finally, in the hard-exit fallback, and in a synchronous exit
135
+ * backstop. Never at shutdown() entry: a second copy must not open the MLS
136
+ * store while this one is still draining it.
137
+ */
138
+ export declare function runMode(deps: RunModeDeps): Promise<void>;