@phnx-labs/agents-cli 1.20.64 → 1.20.66
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 +51 -3
- package/README.md +156 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.d.ts +12 -0
- package/dist/commands/apply.js +274 -0
- package/dist/commands/browser.js +2 -2
- package/dist/commands/cloud.js +32 -2
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/exec.d.ts +48 -0
- package/dist/commands/exec.js +159 -49
- package/dist/commands/feed.js +25 -11
- package/dist/commands/hosts.js +44 -6
- package/dist/commands/mcp.js +55 -5
- package/dist/commands/monitors.d.ts +12 -0
- package/dist/commands/monitors.js +748 -0
- package/dist/commands/output.js +2 -2
- package/dist/commands/plugins.js +28 -7
- package/dist/commands/routines.js +23 -2
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +215 -64
- package/dist/commands/serve.js +31 -0
- package/dist/commands/sessions-browser.d.ts +82 -0
- package/dist/commands/sessions-browser.js +320 -0
- package/dist/commands/sessions-export.js +8 -3
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +157 -10
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +150 -0
- package/dist/commands/ssh.js +54 -3
- package/dist/commands/versions.js +7 -3
- package/dist/commands/view.d.ts +26 -0
- package/dist/commands/view.js +32 -9
- package/dist/commands/webhook.js +10 -2
- package/dist/index.js +35 -14
- package/dist/lib/agents.d.ts +46 -0
- package/dist/lib/agents.js +121 -3
- package/dist/lib/auto-dispatch-provider.js +7 -2
- package/dist/lib/auto-dispatch.d.ts +3 -0
- package/dist/lib/auto-dispatch.js +3 -0
- package/dist/lib/browser/chrome.js +2 -2
- package/dist/lib/cloud/antigravity.js +2 -2
- package/dist/lib/cloud/host.d.ts +59 -0
- package/dist/lib/cloud/host.js +224 -0
- package/dist/lib/cloud/registry.js +4 -0
- package/dist/lib/cloud/types.d.ts +6 -4
- package/dist/lib/computer-rpc.js +3 -1
- package/dist/lib/crabbox/cli.js +5 -1
- package/dist/lib/crabbox/runtimes.js +11 -2
- package/dist/lib/daemon.d.ts +20 -4
- package/dist/lib/daemon.js +62 -19
- package/dist/lib/devices/connect.d.ts +18 -1
- package/dist/lib/devices/connect.js +10 -2
- package/dist/lib/devices/fleet.d.ts +3 -2
- package/dist/lib/devices/fleet.js +9 -0
- package/dist/lib/devices/known-hosts.d.ts +62 -0
- package/dist/lib/devices/known-hosts.js +137 -0
- package/dist/lib/devices/registry.d.ts +15 -0
- package/dist/lib/devices/registry.js +9 -0
- package/dist/lib/exec.d.ts +19 -2
- package/dist/lib/exec.js +41 -13
- package/dist/lib/fleet/apply.d.ts +63 -0
- package/dist/lib/fleet/apply.js +214 -0
- package/dist/lib/fleet/auth-sync.d.ts +67 -0
- package/dist/lib/fleet/auth-sync.js +142 -0
- package/dist/lib/fleet/manifest.d.ts +29 -0
- package/dist/lib/fleet/manifest.js +127 -0
- package/dist/lib/fleet/types.d.ts +129 -0
- package/dist/lib/fleet/types.js +13 -0
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +34 -2
- package/dist/lib/hosts/dispatch.d.ts +29 -8
- package/dist/lib/hosts/dispatch.js +66 -20
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/providers/devices.d.ts +27 -0
- package/dist/lib/hosts/providers/devices.js +98 -0
- package/dist/lib/hosts/registry.d.ts +10 -16
- package/dist/lib/hosts/registry.js +17 -50
- package/dist/lib/hosts/remote-cmd.d.ts +23 -0
- package/dist/lib/hosts/remote-cmd.js +79 -4
- package/dist/lib/hosts/run-target.d.ts +84 -0
- package/dist/lib/hosts/run-target.js +99 -0
- package/dist/lib/hosts/types.d.ts +23 -5
- package/dist/lib/hosts/types.js +22 -4
- package/dist/lib/linear-autoclose.d.ts +30 -0
- package/dist/lib/linear-autoclose.js +22 -0
- package/dist/lib/mcp.d.ts +27 -1
- package/dist/lib/mcp.js +126 -12
- package/dist/lib/monitors/config.d.ts +161 -0
- package/dist/lib/monitors/config.js +372 -0
- package/dist/lib/monitors/dispatch.d.ts +28 -0
- package/dist/lib/monitors/dispatch.js +91 -0
- package/dist/lib/monitors/engine.d.ts +61 -0
- package/dist/lib/monitors/engine.js +205 -0
- package/dist/lib/monitors/sources/command.d.ts +11 -0
- package/dist/lib/monitors/sources/command.js +31 -0
- package/dist/lib/monitors/sources/device.d.ts +13 -0
- package/dist/lib/monitors/sources/device.js +45 -0
- package/dist/lib/monitors/sources/file.d.ts +14 -0
- package/dist/lib/monitors/sources/file.js +57 -0
- package/dist/lib/monitors/sources/http.d.ts +10 -0
- package/dist/lib/monitors/sources/http.js +34 -0
- package/dist/lib/monitors/sources/index.d.ts +14 -0
- package/dist/lib/monitors/sources/index.js +31 -0
- package/dist/lib/monitors/sources/poll.d.ts +9 -0
- package/dist/lib/monitors/sources/poll.js +9 -0
- package/dist/lib/monitors/sources/types.d.ts +18 -0
- package/dist/lib/monitors/sources/types.js +9 -0
- package/dist/lib/monitors/sources/webhook.d.ts +23 -0
- package/dist/lib/monitors/sources/webhook.js +47 -0
- package/dist/lib/monitors/sources/ws.d.ts +14 -0
- package/dist/lib/monitors/sources/ws.js +45 -0
- package/dist/lib/monitors/state.d.ts +69 -0
- package/dist/lib/monitors/state.js +144 -0
- package/dist/lib/picker.d.ts +53 -0
- package/dist/lib/picker.js +214 -1
- package/dist/lib/platform/exec.d.ts +16 -0
- package/dist/lib/platform/exec.js +17 -0
- package/dist/lib/plugins.d.ts +31 -1
- package/dist/lib/plugins.js +175 -15
- package/dist/lib/redact.d.ts +14 -1
- package/dist/lib/redact.js +47 -1
- package/dist/lib/remote-agents-json.js +7 -1
- package/dist/lib/rotate.d.ts +6 -3
- package/dist/lib/rotate.js +0 -1
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +19 -0
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +102 -9
- package/dist/lib/secrets/agent.d.ts +83 -10
- package/dist/lib/secrets/agent.js +237 -70
- package/dist/lib/secrets/bundles.d.ts +26 -0
- package/dist/lib/secrets/bundles.js +59 -8
- package/dist/lib/secrets/filestore.d.ts +9 -0
- package/dist/lib/secrets/filestore.js +21 -8
- package/dist/lib/secrets/index.d.ts +7 -0
- package/dist/lib/secrets/index.js +26 -6
- package/dist/lib/secrets/mcp.js +4 -2
- package/dist/lib/secrets/remote.d.ts +17 -0
- package/dist/lib/secrets/remote.js +40 -0
- package/dist/lib/self-update.d.ts +20 -0
- package/dist/lib/self-update.js +54 -1
- package/dist/lib/serve/control.d.ts +95 -0
- package/dist/lib/serve/control.js +260 -0
- package/dist/lib/serve/server.d.ts +35 -1
- package/dist/lib/serve/server.js +106 -76
- package/dist/lib/serve/stream.d.ts +43 -0
- package/dist/lib/serve/stream.js +116 -0
- package/dist/lib/serve/token.d.ts +35 -0
- package/dist/lib/serve/token.js +85 -0
- package/dist/lib/session/bundle.d.ts +14 -0
- package/dist/lib/session/bundle.js +12 -1
- package/dist/lib/session/remote-list.js +5 -1
- package/dist/lib/session/state.d.ts +7 -25
- package/dist/lib/session/state.js +16 -6
- package/dist/lib/session/sync/config.js +8 -2
- package/dist/lib/session/types.d.ts +30 -0
- package/dist/lib/share/capture.d.ts +29 -0
- package/dist/lib/share/capture.js +140 -0
- package/dist/lib/share/config.d.ts +35 -0
- package/dist/lib/share/config.js +100 -0
- package/dist/lib/share/og.d.ts +25 -0
- package/dist/lib/share/og.js +84 -0
- package/dist/lib/share/provision.d.ts +10 -0
- package/dist/lib/share/provision.js +91 -0
- package/dist/lib/share/publish.d.ts +57 -0
- package/dist/lib/share/publish.js +145 -0
- package/dist/lib/share/worker-template.d.ts +2 -0
- package/dist/lib/share/worker-template.js +82 -0
- package/dist/lib/shims.d.ts +13 -0
- package/dist/lib/shims.js +42 -2
- package/dist/lib/ssh-exec.d.ts +24 -0
- package/dist/lib/ssh-exec.js +15 -3
- package/dist/lib/ssh-tunnel.d.ts +19 -1
- package/dist/lib/ssh-tunnel.js +86 -7
- package/dist/lib/startup/command-registry.d.ts +3 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.js +12 -0
- package/dist/lib/tmux/session.d.ts +7 -0
- package/dist/lib/tmux/session.js +3 -1
- package/dist/lib/triggers/webhook.d.ts +18 -0
- package/dist/lib/triggers/webhook.js +105 -0
- package/dist/lib/types.d.ts +36 -1
- package/dist/lib/usage.js +7 -5
- package/dist/lib/versions.js +14 -11
- package/dist/lib/workflows.d.ts +20 -0
- package/dist/lib/workflows.js +24 -0
- package/package.json +2 -1
package/dist/lib/shims.js
CHANGED
|
@@ -16,7 +16,7 @@ import { confirm, select } from '@inquirer/prompts';
|
|
|
16
16
|
import { IS_WINDOWS, prependToWindowsUserPath } from './platform/index.js';
|
|
17
17
|
import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from './state.js';
|
|
18
18
|
export { getShimsDir };
|
|
19
|
-
import { AGENTS, agentConfigDirName } from './agents.js';
|
|
19
|
+
import { AGENTS, agentConfigDirName, readAuthAccountIdentity } from './agents.js';
|
|
20
20
|
/**
|
|
21
21
|
* Files and directories to always skip during conflict detection and migration.
|
|
22
22
|
* These are never user config that should be migrated.
|
|
@@ -1143,6 +1143,21 @@ function detectMigrationConflicts(agent, version) {
|
|
|
1143
1143
|
* preserved so the "freshest" comparison stays stable and switches don't
|
|
1144
1144
|
* ping-pong. Best-effort: a failed copy just means the user re-logs in.
|
|
1145
1145
|
*/
|
|
1146
|
+
/**
|
|
1147
|
+
* Best-effort account identity for the credential *directory* of a file-auth
|
|
1148
|
+
* agent (droid / kimi / antigravity), or null when the directory holds no
|
|
1149
|
+
* decodable account claim. Delegates to readAuthAccountIdentity, which decrypts
|
|
1150
|
+
* / decodes each agent's REAL on-disk format (droid AES-256-GCM + WorkOS JWT,
|
|
1151
|
+
* kimi access-token JWT, antigravity refresh-token) — the earlier plaintext
|
|
1152
|
+
* top-level-key scan matched NO real credential file, so the guard below never
|
|
1153
|
+
* engaged. Two dirs for the SAME account compare equal; DIFFERENT accounts
|
|
1154
|
+
* compare distinct. Used by carryForwardAuthFiles to refuse overwriting one
|
|
1155
|
+
* account's login with a credential that belongs to a DIFFERENT account
|
|
1156
|
+
* (RUSH-1764).
|
|
1157
|
+
*/
|
|
1158
|
+
export function readAuthFileIdentity(agent, configDir) {
|
|
1159
|
+
return readAuthAccountIdentity(agent, configDir);
|
|
1160
|
+
}
|
|
1146
1161
|
export function carryForwardAuthFiles(agent, toConfigDir) {
|
|
1147
1162
|
const authFiles = AGENTS[agent].authFiles;
|
|
1148
1163
|
if (!authFiles || authFiles.length === 0)
|
|
@@ -1158,12 +1173,33 @@ export function carryForwardAuthFiles(agent, toConfigDir) {
|
|
|
1158
1173
|
catch {
|
|
1159
1174
|
return; // no installed versions to source from
|
|
1160
1175
|
}
|
|
1176
|
+
// Account identity currently installed at the destination home (null when the
|
|
1177
|
+
// dest is empty or its credential can't be decoded). When it IS known, only a
|
|
1178
|
+
// source home whose credential decodes to the SAME account is eligible to
|
|
1179
|
+
// overwrite it — so a newer login for a DIFFERENT account sitting in another
|
|
1180
|
+
// version-home can't silently replace the account the user is signed into
|
|
1181
|
+
// (RUSH-1764). An empty destination still seeds from the freshest source (the
|
|
1182
|
+
// version-switch case). Identity is a per-DIR/account property, not per-file:
|
|
1183
|
+
// for droid it comes from decrypting auth.v2.file with auth.v2.key, so it must
|
|
1184
|
+
// gate BOTH files as a unit (never carry account B's key over account A's).
|
|
1185
|
+
const toResolved = path.resolve(toConfigDir);
|
|
1186
|
+
const destIdentity = readAuthFileIdentity(agent, toConfigDir);
|
|
1187
|
+
const identityCache = new Map();
|
|
1188
|
+
const dirIdentity = (dir) => {
|
|
1189
|
+
const key = path.resolve(dir);
|
|
1190
|
+
if (!identityCache.has(key))
|
|
1191
|
+
identityCache.set(key, readAuthFileIdentity(agent, dir));
|
|
1192
|
+
return identityCache.get(key) ?? null;
|
|
1193
|
+
};
|
|
1161
1194
|
for (const rel of authFiles) {
|
|
1162
1195
|
const dest = path.join(toConfigDir, rel);
|
|
1163
1196
|
const destResolved = path.resolve(dest);
|
|
1164
|
-
// Newest existing source copy across all version homes (excluding dest)
|
|
1197
|
+
// Newest existing source copy across all version homes (excluding dest),
|
|
1198
|
+
// constrained to the destination's account identity when it is known.
|
|
1165
1199
|
let newest = null;
|
|
1166
1200
|
for (const dir of sourceDirs) {
|
|
1201
|
+
if (path.resolve(dir) === toResolved)
|
|
1202
|
+
continue; // never source from self
|
|
1167
1203
|
const src = path.join(dir, rel);
|
|
1168
1204
|
if (path.resolve(src) === destResolved)
|
|
1169
1205
|
continue;
|
|
@@ -1176,6 +1212,10 @@ export function carryForwardAuthFiles(agent, toConfigDir) {
|
|
|
1176
1212
|
}
|
|
1177
1213
|
if (!st.isFile())
|
|
1178
1214
|
continue;
|
|
1215
|
+
// Account-identity guard: never carry a different account's credential over
|
|
1216
|
+
// an existing login. Only enforced when the destination's identity is known.
|
|
1217
|
+
if (destIdentity !== null && dirIdentity(dir) !== destIdentity)
|
|
1218
|
+
continue;
|
|
1179
1219
|
if (!newest || st.mtimeMs > newest.mtimeMs)
|
|
1180
1220
|
newest = { path: src, mtimeMs: st.mtimeMs };
|
|
1181
1221
|
}
|
package/dist/lib/ssh-exec.d.ts
CHANGED
|
@@ -29,6 +29,16 @@ export declare function shellQuote(s: string): string;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare const SSH_OPTS: readonly string[];
|
|
31
31
|
export declare function controlOpts(): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Compose an ssh connection-option prefix.
|
|
34
|
+
*
|
|
35
|
+
* `hostKeyOpts` (a caller's host-key posture, e.g. a pinned
|
|
36
|
+
* `StrictHostKeyChecking=yes` + managed `UserKnownHostsFile`) go FIRST, ahead of
|
|
37
|
+
* the `SSH_OPTS` baseline: ssh honors the *first* value it sees for each option,
|
|
38
|
+
* so an override placed after the baseline's `accept-new` would be silently
|
|
39
|
+
* ignored. Pure so the ordering contract is unit-testable. See RUSH-1767.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sshConnectOpts(mux: string[], hostKeyOpts?: string[]): string[];
|
|
32
42
|
export interface SshExecOptions {
|
|
33
43
|
/** Piped to the remote command's stdin (never interpolated into the shell). */
|
|
34
44
|
input?: string;
|
|
@@ -38,6 +48,13 @@ export interface SshExecOptions {
|
|
|
38
48
|
extraSshArgs?: string[];
|
|
39
49
|
/** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
|
|
40
50
|
multiplex?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
|
|
53
|
+
* ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
|
|
54
|
+
* force strict verification against the managed known_hosts store on the
|
|
55
|
+
* credential-copy path (RUSH-1767).
|
|
56
|
+
*/
|
|
57
|
+
hostKeyOpts?: string[];
|
|
41
58
|
}
|
|
42
59
|
export interface SshExecResult {
|
|
43
60
|
/** Remote exit status, or null if ssh itself failed / timed out. */
|
|
@@ -79,6 +96,13 @@ export interface SshStreamOptions {
|
|
|
79
96
|
tty?: boolean;
|
|
80
97
|
/** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
|
|
81
98
|
multiplex?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
|
|
101
|
+
* ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
|
|
102
|
+
* force strict verification against the managed known_hosts store on the
|
|
103
|
+
* interactive credential-copy path (RUSH-1767).
|
|
104
|
+
*/
|
|
105
|
+
hostKeyOpts?: string[];
|
|
82
106
|
}
|
|
83
107
|
/**
|
|
84
108
|
* Foreground counterpart to `sshExec`: run `remoteCmd` on `target` with the
|
package/dist/lib/ssh-exec.js
CHANGED
|
@@ -88,6 +88,18 @@ export function controlOpts() {
|
|
|
88
88
|
'-o', 'ControlPersist=60s',
|
|
89
89
|
];
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Compose an ssh connection-option prefix.
|
|
93
|
+
*
|
|
94
|
+
* `hostKeyOpts` (a caller's host-key posture, e.g. a pinned
|
|
95
|
+
* `StrictHostKeyChecking=yes` + managed `UserKnownHostsFile`) go FIRST, ahead of
|
|
96
|
+
* the `SSH_OPTS` baseline: ssh honors the *first* value it sees for each option,
|
|
97
|
+
* so an override placed after the baseline's `accept-new` would be silently
|
|
98
|
+
* ignored. Pure so the ordering contract is unit-testable. See RUSH-1767.
|
|
99
|
+
*/
|
|
100
|
+
export function sshConnectOpts(mux, hostKeyOpts) {
|
|
101
|
+
return [...(hostKeyOpts ?? []), ...SSH_OPTS, ...mux];
|
|
102
|
+
}
|
|
91
103
|
/**
|
|
92
104
|
* Run `remoteCmd` on `target` over ssh and capture stdout/stderr/exit.
|
|
93
105
|
*
|
|
@@ -97,7 +109,7 @@ export function controlOpts() {
|
|
|
97
109
|
export function sshExec(target, remoteCmd, opts = {}) {
|
|
98
110
|
assertValidSshTarget(target);
|
|
99
111
|
const mux = opts.multiplex === false ? [] : controlOpts();
|
|
100
|
-
const args = [...
|
|
112
|
+
const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...(opts.extraSshArgs ?? []), target, remoteCmd];
|
|
101
113
|
const res = spawnSync('ssh', args, {
|
|
102
114
|
input: opts.input,
|
|
103
115
|
encoding: 'utf-8',
|
|
@@ -123,7 +135,7 @@ export function sshExec(target, remoteCmd, opts = {}) {
|
|
|
123
135
|
export function sshExecRaw(target, remoteCmd, opts = {}) {
|
|
124
136
|
assertValidSshTarget(target);
|
|
125
137
|
const mux = opts.multiplex === false ? [] : controlOpts();
|
|
126
|
-
const args = [...
|
|
138
|
+
const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...(opts.extraSshArgs ?? []), target, remoteCmd];
|
|
127
139
|
const res = spawnSync('ssh', args, {
|
|
128
140
|
input: opts.input,
|
|
129
141
|
// No `encoding` → spawnSync returns Buffers.
|
|
@@ -154,7 +166,7 @@ export function sshStream(target, remoteCmd, opts = {}) {
|
|
|
154
166
|
assertValidSshTarget(target);
|
|
155
167
|
const mux = opts.multiplex === false ? [] : controlOpts();
|
|
156
168
|
const tty = opts.tty ? ['-tt'] : [];
|
|
157
|
-
const args = [...
|
|
169
|
+
const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...tty, target, remoteCmd];
|
|
158
170
|
const res = spawnSync('ssh', args, { stdio: 'inherit' });
|
|
159
171
|
if (typeof res.status === 'number')
|
|
160
172
|
return res.status;
|
package/dist/lib/ssh-tunnel.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export declare const REMOTE_HELPER_PORT = 8765;
|
|
|
49
49
|
export declare const REMOTE_TASK_NAME = "AgentsComputerHelper";
|
|
50
50
|
/** Basename of the cross-published exe under native/computer-win/dist. */
|
|
51
51
|
export declare const WIN_HELPER_EXE = "computer-helper-win.exe";
|
|
52
|
+
/** Basename of the daemon's shared-secret token file under %LOCALAPPDATA%\agents. */
|
|
53
|
+
export declare const WIN_HELPER_TOKEN_FILE = "helper-token";
|
|
52
54
|
/**
|
|
53
55
|
* Locate a locally built Windows daemon exe — a repo-checkout build output from
|
|
54
56
|
* `scripts/build-win.sh`, or one bundled next to the package. Local paths are
|
|
@@ -101,6 +103,16 @@ export declare function remoteStatePath(device: string): string;
|
|
|
101
103
|
export declare function readRemoteState(device: string): RemoteTunnelState | null;
|
|
102
104
|
export declare function writeRemoteState(state: RemoteTunnelState): void;
|
|
103
105
|
export declare function clearRemoteState(device: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Local file holding the shared-secret token for a device's helper daemon.
|
|
108
|
+
* Written at `setup --host` (0600), read at `start --host` so the RPC client
|
|
109
|
+
* authenticates. The remote daemon reads the same value from its own
|
|
110
|
+
* `--token-file`; the CLI is the source of truth and never round-trips it back.
|
|
111
|
+
*/
|
|
112
|
+
export declare function helperTokenPath(device: string): string;
|
|
113
|
+
export declare function readHelperToken(device: string): string | null;
|
|
114
|
+
export declare function writeHelperToken(device: string, token: string): void;
|
|
115
|
+
export declare function clearHelperToken(device: string): void;
|
|
104
116
|
/** Resolve a registered device to its ssh pieces, or throw a clear error. */
|
|
105
117
|
export declare function resolveRemoteDevice(name: string): Promise<{
|
|
106
118
|
device: DeviceProfile;
|
|
@@ -123,7 +135,13 @@ export declare function buildVerifyPushScript(remotePath: string, expectedBytes:
|
|
|
123
135
|
* survives ssh disconnect — the same rationale as the browser WMI launch. The
|
|
124
136
|
* task is started immediately so the caller need not log out/in.
|
|
125
137
|
*/
|
|
126
|
-
|
|
138
|
+
/**
|
|
139
|
+
* PowerShell that writes the shared-secret token under %LOCALAPPDATA%\agents
|
|
140
|
+
* with an owner-only ACL (inheritance removed, read granted only to the current
|
|
141
|
+
* user) and echoes the resolved path so the caller can pass it to `--token-file`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function buildWriteTokenScript(token: string): string;
|
|
144
|
+
export declare function buildRegisterTaskScript(port: number, taskName: string, tokenPath: string): string;
|
|
127
145
|
/** PowerShell that unregisters the task and stops any running daemon process. */
|
|
128
146
|
export declare function buildUnregisterTaskScript(taskName: string): string;
|
|
129
147
|
/** Convert a Windows path returned by PowerShell into the scp/SFTP path form. */
|
package/dist/lib/ssh-tunnel.js
CHANGED
|
@@ -103,6 +103,8 @@ export const REMOTE_HELPER_PORT = 8765;
|
|
|
103
103
|
export const REMOTE_TASK_NAME = 'AgentsComputerHelper';
|
|
104
104
|
/** Basename of the cross-published exe under native/computer-win/dist. */
|
|
105
105
|
export const WIN_HELPER_EXE = 'computer-helper-win.exe';
|
|
106
|
+
/** Basename of the daemon's shared-secret token file under %LOCALAPPDATA%\agents. */
|
|
107
|
+
export const WIN_HELPER_TOKEN_FILE = 'helper-token';
|
|
106
108
|
/**
|
|
107
109
|
* Locate a locally built Windows daemon exe — a repo-checkout build output from
|
|
108
110
|
* `scripts/build-win.sh`, or one bundled next to the package. Local paths are
|
|
@@ -234,6 +236,37 @@ export function clearRemoteState(device) {
|
|
|
234
236
|
/* already gone */
|
|
235
237
|
}
|
|
236
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Local file holding the shared-secret token for a device's helper daemon.
|
|
241
|
+
* Written at `setup --host` (0600), read at `start --host` so the RPC client
|
|
242
|
+
* authenticates. The remote daemon reads the same value from its own
|
|
243
|
+
* `--token-file`; the CLI is the source of truth and never round-trips it back.
|
|
244
|
+
*/
|
|
245
|
+
export function helperTokenPath(device) {
|
|
246
|
+
return path.join(remoteStateDir(), `${device}.token`);
|
|
247
|
+
}
|
|
248
|
+
export function readHelperToken(device) {
|
|
249
|
+
try {
|
|
250
|
+
const t = fs.readFileSync(helperTokenPath(device), 'utf-8').trim();
|
|
251
|
+
return t.length > 0 ? t : null;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export function writeHelperToken(device, token) {
|
|
258
|
+
const dir = remoteStateDir();
|
|
259
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
260
|
+
fs.writeFileSync(helperTokenPath(device), token, { mode: 0o600 });
|
|
261
|
+
}
|
|
262
|
+
export function clearHelperToken(device) {
|
|
263
|
+
try {
|
|
264
|
+
fs.unlinkSync(helperTokenPath(device));
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
/* already gone */
|
|
268
|
+
}
|
|
269
|
+
}
|
|
237
270
|
/** Resolve a registered device to its ssh pieces, or throw a clear error. */
|
|
238
271
|
export async function resolveRemoteDevice(name) {
|
|
239
272
|
const device = await getDevice(name);
|
|
@@ -284,10 +317,26 @@ export function buildVerifyPushScript(remotePath, expectedBytes) {
|
|
|
284
317
|
* survives ssh disconnect — the same rationale as the browser WMI launch. The
|
|
285
318
|
* task is started immediately so the caller need not log out/in.
|
|
286
319
|
*/
|
|
287
|
-
|
|
320
|
+
/**
|
|
321
|
+
* PowerShell that writes the shared-secret token under %LOCALAPPDATA%\agents
|
|
322
|
+
* with an owner-only ACL (inheritance removed, read granted only to the current
|
|
323
|
+
* user) and echoes the resolved path so the caller can pass it to `--token-file`.
|
|
324
|
+
*/
|
|
325
|
+
export function buildWriteTokenScript(token) {
|
|
326
|
+
return [
|
|
327
|
+
`$dir = Join-Path $env:LOCALAPPDATA 'agents'`,
|
|
328
|
+
`New-Item -ItemType Directory -Force -Path $dir | Out-Null`,
|
|
329
|
+
`$tok = Join-Path $dir '${WIN_HELPER_TOKEN_FILE}'`,
|
|
330
|
+
`Set-Content -LiteralPath $tok -Value ${psSingleQuote(token)} -NoNewline -Encoding ascii`,
|
|
331
|
+
`icacls $tok /inheritance:r /grant:r ("$($env:USERNAME):(R)") | Out-Null`,
|
|
332
|
+
`Write-Output $tok`,
|
|
333
|
+
].join('; ');
|
|
334
|
+
}
|
|
335
|
+
export function buildRegisterTaskScript(port, taskName, tokenPath) {
|
|
336
|
+
const argument = `--port ${port} --token-file "${tokenPath}"`;
|
|
288
337
|
return [
|
|
289
338
|
`$exe = Join-Path (Join-Path $env:LOCALAPPDATA 'agents') '${WIN_HELPER_EXE}'`,
|
|
290
|
-
`$action = New-ScheduledTaskAction -Execute $exe -Argument
|
|
339
|
+
`$action = New-ScheduledTaskAction -Execute $exe -Argument ${psSingleQuote(argument)}`,
|
|
291
340
|
`$trigger = New-ScheduledTaskTrigger -AtLogOn`,
|
|
292
341
|
`$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest`,
|
|
293
342
|
`$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero)`,
|
|
@@ -369,13 +418,28 @@ export async function setupRemoteHelper(name) {
|
|
|
369
418
|
if (verify.code !== 0) {
|
|
370
419
|
throw new Error(`verifying helper exe on '${name}' failed (exit ${verify.code ?? 'null'}): ${verify.stderr.trim() || verify.stdout.trim()}`);
|
|
371
420
|
}
|
|
372
|
-
//
|
|
373
|
-
|
|
421
|
+
// Provision the auth token: generate it locally, write it on the remote with
|
|
422
|
+
// an owner-only ACL, and register the task with --token-file. The daemon now
|
|
423
|
+
// refuses to start without a token, so a token-less (open-to-any-local-process)
|
|
424
|
+
// daemon can no longer be stood up through the CLI.
|
|
425
|
+
const token = generateToken();
|
|
426
|
+
const tokWrite = sshExec(target, encodePowerShell(buildWriteTokenScript(token)), { timeoutMs: 60_000 });
|
|
427
|
+
if (tokWrite.code !== 0) {
|
|
428
|
+
throw new Error(`writing helper token on '${name}' failed (exit ${tokWrite.code ?? 'null'}): ${tokWrite.stderr.trim() || tokWrite.stdout.trim()}`);
|
|
429
|
+
}
|
|
430
|
+
const remoteTokenPath = tokWrite.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
431
|
+
if (!remoteTokenPath) {
|
|
432
|
+
throw new Error(`writing helper token on '${name}' did not return a destination path`);
|
|
433
|
+
}
|
|
434
|
+
// Register + start the LOGON task, pointing it at the token file.
|
|
435
|
+
const reg = sshExec(target, encodePowerShell(buildRegisterTaskScript(REMOTE_HELPER_PORT, REMOTE_TASK_NAME, remoteTokenPath)), {
|
|
374
436
|
timeoutMs: 60_000,
|
|
375
437
|
});
|
|
376
438
|
if (reg.code !== 0) {
|
|
377
439
|
throw new Error(`registering scheduled task on '${name}' failed (exit ${reg.code ?? 'null'}): ${reg.stderr.trim() || reg.stdout.trim()}`);
|
|
378
440
|
}
|
|
441
|
+
// Persist the token locally so `start --host` authenticates to the daemon.
|
|
442
|
+
writeHelperToken(name, token);
|
|
379
443
|
return { target, taskName: REMOTE_TASK_NAME };
|
|
380
444
|
}
|
|
381
445
|
/** Reserve a free local TCP port by binding :0 and reading the assigned port. */
|
|
@@ -403,9 +467,17 @@ export async function startRemoteTunnel(name) {
|
|
|
403
467
|
const tunnelPid = tunnel.pid ?? 0;
|
|
404
468
|
// Verify the daemon answers through the tunnel before we record it. This is
|
|
405
469
|
// the real end-to-end check: tunnel up + daemon listening + RPC round-trips.
|
|
406
|
-
|
|
470
|
+
// The daemon now requires a token, so the probe must authenticate with the one
|
|
471
|
+
// provisioned at `setup`. A missing token means setup predates auth — guide
|
|
472
|
+
// the user to re-run it rather than silently failing the RPC.
|
|
473
|
+
const token = readHelperToken(name);
|
|
407
474
|
const prevTcp = process.env.COMPUTER_HELPER_TCP;
|
|
475
|
+
const prevTok = process.env.COMPUTER_HELPER_TOKEN;
|
|
408
476
|
process.env.COMPUTER_HELPER_TCP = `127.0.0.1:${localPort}`;
|
|
477
|
+
if (token)
|
|
478
|
+
process.env.COMPUTER_HELPER_TOKEN = token;
|
|
479
|
+
else
|
|
480
|
+
delete process.env.COMPUTER_HELPER_TOKEN;
|
|
409
481
|
const client = openComputerClient();
|
|
410
482
|
let ok = false;
|
|
411
483
|
let probeErr = '';
|
|
@@ -424,6 +496,10 @@ export async function startRemoteTunnel(name) {
|
|
|
424
496
|
delete process.env.COMPUTER_HELPER_TCP;
|
|
425
497
|
else
|
|
426
498
|
process.env.COMPUTER_HELPER_TCP = prevTcp;
|
|
499
|
+
if (prevTok === undefined)
|
|
500
|
+
delete process.env.COMPUTER_HELPER_TOKEN;
|
|
501
|
+
else
|
|
502
|
+
process.env.COMPUTER_HELPER_TOKEN = prevTok;
|
|
427
503
|
}
|
|
428
504
|
if (!ok) {
|
|
429
505
|
try {
|
|
@@ -431,8 +507,10 @@ export async function startRemoteTunnel(name) {
|
|
|
431
507
|
process.kill(tunnelPid);
|
|
432
508
|
}
|
|
433
509
|
catch { /* gone */ }
|
|
434
|
-
|
|
435
|
-
`Is it installed? Run: agents computer setup --host ${name}`
|
|
510
|
+
const hint = token
|
|
511
|
+
? `Is it installed? Run: agents computer setup --host ${name}`
|
|
512
|
+
: `No auth token on record for '${name}' — re-run: agents computer setup --host ${name}`;
|
|
513
|
+
throw new Error(`tunnel to '${name}' opened but the daemon did not answer (${probeErr}). ${hint}`);
|
|
436
514
|
}
|
|
437
515
|
const state = {
|
|
438
516
|
device: name,
|
|
@@ -473,6 +551,7 @@ export async function stopRemoteHelper(name) {
|
|
|
473
551
|
/* device gone / offline — local teardown still succeeds */
|
|
474
552
|
}
|
|
475
553
|
clearRemoteState(name);
|
|
554
|
+
clearHelperToken(name);
|
|
476
555
|
return { tunnelKilled, taskRemoved };
|
|
477
556
|
}
|
|
478
557
|
/**
|
|
@@ -43,6 +43,7 @@ export declare const loadImport: ModuleLoader;
|
|
|
43
43
|
export declare const loadPackages: ModuleLoader;
|
|
44
44
|
export declare const loadDaemon: ModuleLoader;
|
|
45
45
|
export declare const loadRoutines: ModuleLoader;
|
|
46
|
+
export declare const loadMonitors: ModuleLoader;
|
|
46
47
|
export declare const loadRun: ModuleLoader;
|
|
47
48
|
export declare const loadDefaults: ModuleLoader;
|
|
48
49
|
export declare const loadModels: ModuleLoader;
|
|
@@ -50,6 +51,7 @@ export declare const loadPrune: ModuleLoader;
|
|
|
50
51
|
export declare const loadTrash: ModuleLoader;
|
|
51
52
|
export declare const loadRestore: ModuleLoader;
|
|
52
53
|
export declare const loadDoctor: ModuleLoader;
|
|
54
|
+
export declare const loadApply: ModuleLoader;
|
|
53
55
|
export declare const loadCheck: ModuleLoader;
|
|
54
56
|
export declare const loadStatus: ModuleLoader;
|
|
55
57
|
export declare const loadProfiles: ModuleLoader;
|
|
@@ -89,6 +91,7 @@ export declare const loadMessage: ModuleLoader;
|
|
|
89
91
|
export declare const loadFeed: ModuleLoader;
|
|
90
92
|
export declare const loadMailboxes: ModuleLoader;
|
|
91
93
|
export declare const loadServe: ModuleLoader;
|
|
94
|
+
export declare const loadShare: ModuleLoader;
|
|
92
95
|
export declare const loadAudit: ModuleLoader;
|
|
93
96
|
export declare const loadWebhook: ModuleLoader;
|
|
94
97
|
export declare const loadFunnel: ModuleLoader;
|
|
@@ -21,6 +21,7 @@ export const loadImport = async () => (await import('../../commands/import.js'))
|
|
|
21
21
|
export const loadPackages = async () => (await import('../../commands/packages.js')).registerPackagesCommands;
|
|
22
22
|
export const loadDaemon = async () => (await import('../../commands/daemon.js')).registerDaemonCommands;
|
|
23
23
|
export const loadRoutines = async () => (await import('../../commands/routines.js')).registerRoutinesCommands;
|
|
24
|
+
export const loadMonitors = async () => (await import('../../commands/monitors.js')).registerMonitorsCommands;
|
|
24
25
|
export const loadRun = async () => (await import('../../commands/exec.js')).registerRunCommand;
|
|
25
26
|
export const loadDefaults = async () => (await import('../../commands/defaults.js')).registerDefaultsCommands;
|
|
26
27
|
export const loadModels = async () => (await import('../../commands/models.js')).registerModelsCommand;
|
|
@@ -28,6 +29,7 @@ export const loadPrune = async () => (await import('../../commands/prune.js')).r
|
|
|
28
29
|
export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
|
|
29
30
|
export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
|
|
30
31
|
export const loadDoctor = async () => (await import('../../commands/doctor.js')).registerDoctorCommand;
|
|
32
|
+
export const loadApply = async () => (await import('../../commands/apply.js')).registerApplyCommand;
|
|
31
33
|
export const loadCheck = async () => (await import('../../commands/check.js')).registerCheckCommand;
|
|
32
34
|
export const loadStatus = async () => (await import('../../commands/status.js')).registerStatusCommand;
|
|
33
35
|
export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
|
|
@@ -67,6 +69,7 @@ export const loadMessage = async () => (await import('../../commands/message.js'
|
|
|
67
69
|
export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
|
|
68
70
|
export const loadMailboxes = async () => (await import('../../commands/mailboxes.js')).registerMailboxesCommand;
|
|
69
71
|
export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
|
|
72
|
+
export const loadShare = async () => (await import('../../commands/share.js')).registerShareCommands;
|
|
70
73
|
export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
|
|
71
74
|
export const loadWebhook = async () => (await import('../../commands/webhook.js')).registerWebhookCommand;
|
|
72
75
|
export const loadFunnel = async () => (await import('../../commands/funnel.js')).registerFunnelCommand;
|
|
@@ -121,12 +124,14 @@ export const COMMAND_LOADERS = {
|
|
|
121
124
|
install: [loadPackages],
|
|
122
125
|
daemon: [loadDaemon],
|
|
123
126
|
routines: [loadRoutines],
|
|
127
|
+
monitors: [loadMonitors],
|
|
124
128
|
run: [loadRun],
|
|
125
129
|
defaults: [loadDefaults],
|
|
126
130
|
models: [loadModels],
|
|
127
131
|
trash: [loadTrash],
|
|
128
132
|
restore: [loadRestore],
|
|
129
133
|
doctor: [loadDoctor],
|
|
134
|
+
apply: [loadApply],
|
|
130
135
|
check: [loadCheck],
|
|
131
136
|
status: [loadStatus],
|
|
132
137
|
profiles: [loadProfiles],
|
|
@@ -175,6 +180,7 @@ export const COMMAND_LOADERS = {
|
|
|
175
180
|
mailboxes: [loadMailboxes],
|
|
176
181
|
mailbox: [loadMailboxes],
|
|
177
182
|
serve: [loadServe],
|
|
183
|
+
share: [loadShare],
|
|
178
184
|
audit: [loadAudit],
|
|
179
185
|
webhook: [loadWebhook],
|
|
180
186
|
funnel: [loadFunnel],
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -128,6 +128,11 @@ export declare function getSystemRoutinesDir(): string;
|
|
|
128
128
|
export declare function getProjectRoutinesDir(cwd?: string): string | null;
|
|
129
129
|
/** Path to routine execution logs (~/.agents/.history/runs/). */
|
|
130
130
|
export declare function getRunsDir(): string;
|
|
131
|
+
/** Path to monitor YAML definitions (~/.agents/monitors/). */
|
|
132
|
+
export declare function getMonitorsDir(): string;
|
|
133
|
+
/** Path to the durable per-monitor state-diff store + fire history
|
|
134
|
+
* (~/.agents/.history/monitors/). */
|
|
135
|
+
export declare function getMonitorsHistoryDir(): string;
|
|
131
136
|
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
132
137
|
export declare function getMailboxRootDir(): string;
|
|
133
138
|
/** Root for open-block feed records (~/.agents/.history/feed/). */
|
package/dist/lib/state.js
CHANGED
|
@@ -83,12 +83,19 @@ const HISTORY_DIR = path.join(USER_AGENTS_DIR, '.history');
|
|
|
83
83
|
const CACHE_DIR = path.join(USER_AGENTS_DIR, '.cache');
|
|
84
84
|
// Top-level user dirs (config/definitions only — runtime moves into .history/.cache).
|
|
85
85
|
const ROUTINES_DIR = path.join(USER_AGENTS_DIR, 'routines');
|
|
86
|
+
// Monitor definitions (event-triggered watchers). Sibling of ROUTINES_DIR: a
|
|
87
|
+
// monitor is a routine whose trigger is a watched source instead of a clock.
|
|
88
|
+
const MONITORS_DIR = path.join(USER_AGENTS_DIR, 'monitors');
|
|
86
89
|
const TEAMS_DIR = path.join(USER_AGENTS_DIR, 'teams');
|
|
87
90
|
// History bucket (durable).
|
|
88
91
|
const SESSIONS_DIR = path.join(HISTORY_DIR, 'sessions');
|
|
89
92
|
const SESSIONS_DB_PATH = path.join(SESSIONS_DIR, 'sessions.db');
|
|
90
93
|
const VERSIONS_DIR = path.join(HISTORY_DIR, 'versions');
|
|
91
94
|
const RUNS_DIR = path.join(HISTORY_DIR, 'runs');
|
|
95
|
+
// Durable per-monitor state-diff store + fire history (last-seen value/hash,
|
|
96
|
+
// fires/<id>/). Sibling of RUNS_DIR — the native diff store that replaces the
|
|
97
|
+
// hand-rolled markdown memory files monitors used to need.
|
|
98
|
+
const MONITORS_HISTORY_DIR = path.join(HISTORY_DIR, 'monitors');
|
|
92
99
|
const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
|
|
93
100
|
const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
|
|
94
101
|
const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
|
|
@@ -324,6 +331,11 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
|
|
|
324
331
|
}
|
|
325
332
|
/** Path to routine execution logs (~/.agents/.history/runs/). */
|
|
326
333
|
export function getRunsDir() { return RUNS_DIR; }
|
|
334
|
+
/** Path to monitor YAML definitions (~/.agents/monitors/). */
|
|
335
|
+
export function getMonitorsDir() { return MONITORS_DIR; }
|
|
336
|
+
/** Path to the durable per-monitor state-diff store + fire history
|
|
337
|
+
* (~/.agents/.history/monitors/). */
|
|
338
|
+
export function getMonitorsHistoryDir() { return MONITORS_HISTORY_DIR; }
|
|
327
339
|
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
328
340
|
export function getMailboxRootDir() { return MAILBOX_DIR; }
|
|
329
341
|
/** Root for open-block feed records (~/.agents/.history/feed/). */
|
|
@@ -33,6 +33,13 @@ export interface SessionMeta {
|
|
|
33
33
|
export interface CreateSessionOptions {
|
|
34
34
|
name: string;
|
|
35
35
|
cmd?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Redacted copy of `cmd` to persist in SessionMeta.cmd instead of the real
|
|
38
|
+
* command. When set, `cmd` is still executed but only `metaCmd` is written to
|
|
39
|
+
* disk — used to keep resolved secret values out of the informational cmd
|
|
40
|
+
* field (see exec.ts buildTmuxAgentCommand, RUSH-1758). Defaults to `cmd`.
|
|
41
|
+
*/
|
|
42
|
+
metaCmd?: string;
|
|
36
43
|
cwd?: string;
|
|
37
44
|
env?: NodeJS.ProcessEnv;
|
|
38
45
|
socket?: string;
|
package/dist/lib/tmux/session.js
CHANGED
|
@@ -106,7 +106,9 @@ export async function createSession(opts) {
|
|
|
106
106
|
name: opts.name,
|
|
107
107
|
socket,
|
|
108
108
|
createdAt: Date.now(),
|
|
109
|
-
|
|
109
|
+
// Persist the redacted copy when provided so resolved secret values in the
|
|
110
|
+
// launch command never land on disk (RUSH-1758).
|
|
111
|
+
cmd: opts.metaCmd ?? opts.cmd,
|
|
110
112
|
cwd: opts.cwd,
|
|
111
113
|
source: opts.source ?? 'cli',
|
|
112
114
|
labels: opts.labels,
|
|
@@ -99,6 +99,18 @@ export interface DeliveryStore {
|
|
|
99
99
|
markJob(id: string, jobName: string): void;
|
|
100
100
|
}
|
|
101
101
|
export declare function createMemoryDeliveryStore(maxEntries?: number): DeliveryStore;
|
|
102
|
+
/**
|
|
103
|
+
* A durable, disk-backed delivery store. Unlike `createMemoryDeliveryStore`,
|
|
104
|
+
* seen delivery ids survive a process restart and are bounded by AGE, not by a
|
|
105
|
+
* fixed entry count — so a captured valid delivery cannot re-fire after a
|
|
106
|
+
* restart or after count-based LRU eviction would have dropped it.
|
|
107
|
+
*
|
|
108
|
+
* `retentionMs` doubles as the replay-acceptance window: a delivery whose id is
|
|
109
|
+
* still on record (younger than the window) is rejected as a duplicate; entries
|
|
110
|
+
* older than the window are pruned (keeping the file bounded) since a webhook
|
|
111
|
+
* source will not legitimately retry a delivery that old.
|
|
112
|
+
*/
|
|
113
|
+
export declare function createFileDeliveryStore(filePath: string, retentionMs?: number): DeliveryStore;
|
|
102
114
|
export interface RateLimiter {
|
|
103
115
|
take(key: string): boolean;
|
|
104
116
|
}
|
|
@@ -117,6 +129,12 @@ export interface WebhookServerOptions {
|
|
|
117
129
|
rateLimiter?: RateLimiter;
|
|
118
130
|
rateLimitPerMinute?: number;
|
|
119
131
|
maxBodyBytes?: number;
|
|
132
|
+
/** Per-IP ingress throttle applied BEFORE the body read (bad-sig flood guard). */
|
|
133
|
+
ipRateLimiter?: RateLimiter;
|
|
134
|
+
/** Per-source-IP requests/minute allowed through to the body read. Default 120. */
|
|
135
|
+
ipRateLimitPerMinute?: number;
|
|
136
|
+
/** Max concurrent TCP connections the receiver accepts. Default 256. */
|
|
137
|
+
maxConnections?: number;
|
|
120
138
|
}
|
|
121
139
|
/**
|
|
122
140
|
* Start a localhost-bound receiver. It accepts only:
|