@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 +37 -0
- package/dist/bin.d.ts +82 -0
- package/dist/bin.js +942 -150
- package/dist/bridge-agent.d.ts +60 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +295 -21
- package/dist/service/install-prep.d.ts +21 -1
- package/dist/service/instance-lock.d.ts +88 -0
- package/dist/service/launchd.d.ts +1 -1
- package/dist/service/status.d.ts +3 -5
- package/dist/service/systemd.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +4 -0
- package/package.json +1 -1
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>;
|