@securecoms/agent-bridge 0.30.0 → 0.30.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/README.md CHANGED
@@ -935,6 +935,43 @@ copied after the owner's next typed message.
935
935
 
936
936
  ---
937
937
 
938
+ ## Release notes: 0.30.1
939
+
940
+ <!-- [COPY, approved by Eric 2026-10-14] -->
941
+
942
+ 0.30.1 makes sure only one copy of an agent runs, and makes install say
943
+ clearly when the agent did not start.
944
+
945
+ - **Reinstall once after you upgrade.** Run the install command again for
946
+ each agent. It writes a new service file. The old file restarts the
947
+ Connector every time it stops, so a refused second copy would keep
948
+ restarting and print the "already running" message every 10 seconds.
949
+ - **One copy per agent on a computer.** A second copy of the same agent
950
+ stops before it connects and prints: `This agent is already running on this
951
+ computer (process <pid>). Stop that copy first, or run: agent-bridge status`
952
+ - **Port already in use.** If another program holds the Connector's port, the
953
+ agent does not start and prints: `Port <n> is already in use by another
954
+ program on this computer, so the agent did not start.`
955
+ - **Install checks that the agent really started.** Install now waits up to
956
+ 75 seconds for the Connector to answer its health check. If it does not
957
+ answer, install fails and names the service log.
958
+ - **Install refuses a second install of the same agent.** If the agent is
959
+ already installed in another folder or with a different key, install stops
960
+ before it changes anything and prints: `This agent is already installed in
961
+ <dir> with a different key. Uninstall it first: agent-bridge uninstall
962
+ --label <label>`. A downloaded copy such as `margo (1).json` installs as the
963
+ same agent, not as a new one.
964
+ - **The service restarts only after a failure.** On macOS the new service
965
+ file restarts the Connector only when it exits with an error. On Linux the
966
+ unit uses `Restart=on-failure`.
967
+ - **Prepared for one active connection per agent.** When the server reports
968
+ that the agent connected from another place, this copy stops all network
969
+ traffic and prints: `This agent connected from another place, so this copy
970
+ stopped. Stop the other copy, then restart this one.` It stays stopped and
971
+ does not restart itself. The server side of this check is not turned on yet.
972
+
973
+ ---
974
+
938
975
  ## Release notes: 0.30.0
939
976
 
940
977
  <!-- [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 } 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,69 @@ 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
+ log: ServiceLog;
102
+ }
103
+ /**
104
+ * The daemon's run mode (spec §4.1.1). Order, each step gated on the last:
105
+ * 1. loadConfig (+ daemon args, bind check);
106
+ * 2. canonical-UUID check of identity_id (it becomes a path);
107
+ * 3. B1 per-identity lock at ~/.securecoms/locks/<identity_id>.lock;
108
+ * 4. resolveToken (it can WRITE the token file, so only after the lock);
109
+ * 5. B2 pre-bind of daemon.port: a bare net server that destroys every
110
+ * connection, so a taken port refuses before the store opens or any
111
+ * call to SecureComs is made;
112
+ * 6. new BridgeAgent (opens the SQLite MLS store);
113
+ * 7. startThenServe, then hand the pre-bound handle to the HTTP server.
114
+ * The lock is released only after agent.close() settles (audit H-2): in the
115
+ * close().finally, in the hard-exit fallback, and in a synchronous exit
116
+ * backstop. Never at shutdown() entry: a second copy must not open the MLS
117
+ * store while this one is still draining it.
118
+ */
119
+ export declare function runMode(deps: RunModeDeps): Promise<void>;