@wlfi-agent/cli 1.4.16 → 1.4.18

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.
Files changed (97) hide show
  1. package/Cargo.lock +26 -20
  2. package/Cargo.toml +1 -1
  3. package/README.md +61 -28
  4. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  5. package/crates/vault-cli-admin/src/main.rs +639 -16
  6. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  7. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  8. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  9. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  10. package/crates/vault-cli-agent/Cargo.toml +1 -0
  11. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  12. package/crates/vault-cli-agent/src/main.rs +648 -32
  13. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  14. package/crates/vault-cli-daemon/src/main.rs +617 -67
  15. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  16. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  17. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  18. package/crates/vault-daemon/src/persistence.rs +637 -100
  19. package/crates/vault-daemon/src/tests.rs +1013 -3
  20. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  21. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  22. package/crates/vault-domain/src/nonce.rs +4 -0
  23. package/crates/vault-domain/src/tests.rs +616 -0
  24. package/crates/vault-policy/src/engine.rs +55 -32
  25. package/crates/vault-policy/src/tests.rs +195 -0
  26. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  27. package/crates/vault-signer/Cargo.toml +3 -0
  28. package/crates/vault-signer/src/lib.rs +266 -40
  29. package/crates/vault-transport-unix/src/lib.rs +653 -5
  30. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  31. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  32. package/dist/cli.cjs +663 -190
  33. package/dist/cli.cjs.map +1 -1
  34. package/package.json +5 -2
  35. package/packages/cache/.turbo/turbo-build.log +53 -52
  36. package/packages/cache/coverage/clover.xml +529 -394
  37. package/packages/cache/coverage/coverage-final.json +2 -2
  38. package/packages/cache/coverage/index.html +21 -21
  39. package/packages/cache/coverage/src/client/index.html +1 -1
  40. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.html +1 -1
  42. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  43. package/packages/cache/coverage/src/index.html +1 -1
  44. package/packages/cache/coverage/src/index.ts.html +1 -1
  45. package/packages/cache/coverage/src/service/index.html +21 -21
  46. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  47. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  48. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  49. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  50. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  51. package/packages/cache/dist/index.cjs +2 -2
  52. package/packages/cache/dist/index.js +1 -1
  53. package/packages/cache/dist/service/index.cjs +2 -2
  54. package/packages/cache/dist/service/index.js +1 -1
  55. package/packages/cache/node_modules/.bin/tsc +2 -2
  56. package/packages/cache/node_modules/.bin/tsserver +2 -2
  57. package/packages/cache/node_modules/.bin/tsup +2 -2
  58. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  59. package/packages/cache/node_modules/.bin/vitest +4 -4
  60. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  61. package/packages/cache/src/service/index.test.ts +165 -19
  62. package/packages/cache/src/service/index.ts +38 -1
  63. package/packages/config/.turbo/turbo-build.log +18 -17
  64. package/packages/config/dist/index.cjs +0 -17
  65. package/packages/config/dist/index.cjs.map +1 -1
  66. package/packages/config/src/index.ts +0 -17
  67. package/packages/rpc/.turbo/turbo-build.log +32 -31
  68. package/packages/rpc/dist/index.cjs +0 -17
  69. package/packages/rpc/dist/index.cjs.map +1 -1
  70. package/packages/rpc/src/index.js +1 -0
  71. package/packages/ui/.turbo/turbo-build.log +44 -43
  72. package/packages/ui/dist/components/badge.d.ts +1 -1
  73. package/packages/ui/dist/components/button.d.ts +1 -1
  74. package/packages/ui/node_modules/.bin/tsc +2 -2
  75. package/packages/ui/node_modules/.bin/tsserver +2 -2
  76. package/packages/ui/node_modules/.bin/tsup +2 -2
  77. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  78. package/scripts/install-cli-launcher.mjs +37 -0
  79. package/scripts/install-rust-binaries.mjs +112 -0
  80. package/scripts/run-tests-isolated.mjs +210 -0
  81. package/src/cli.ts +310 -50
  82. package/src/lib/admin-reset.ts +15 -30
  83. package/src/lib/admin-setup.ts +246 -55
  84. package/src/lib/agent-auth-migrate.ts +5 -1
  85. package/src/lib/asset-broadcast.ts +15 -4
  86. package/src/lib/config-amounts.ts +6 -4
  87. package/src/lib/hidden-tty-prompt.js +1 -0
  88. package/src/lib/hidden-tty-prompt.ts +105 -0
  89. package/src/lib/keychain.ts +1 -0
  90. package/src/lib/local-admin-access.ts +4 -29
  91. package/src/lib/rust.ts +129 -33
  92. package/src/lib/signed-tx.ts +1 -0
  93. package/src/lib/sudo.ts +15 -5
  94. package/src/lib/wallet-profile.ts +3 -0
  95. package/src/lib/wallet-setup.ts +52 -0
  96. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  97. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import readline from 'node:readline';
2
1
  import { createSudoSession } from './sudo.js';
2
+ import { promptHiddenTty } from './hidden-tty-prompt.js';
3
3
 
4
4
  const MAX_SECRET_STDIN_BYTES = 16 * 1024;
5
5
 
@@ -27,40 +27,15 @@ function currentProcessIsRoot(): boolean {
27
27
  }
28
28
 
29
29
  async function promptHidden(query: string, label: string): Promise<string> {
30
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
31
- throw new Error(`${label} is required; rerun on a local TTY`);
32
- }
33
-
34
- const rl = readline.createInterface({
35
- input: process.stdin,
36
- output: process.stdout,
37
- terminal: true,
38
- }) as readline.Interface & { stdoutMuted?: boolean; _writeToOutput?: (value: string) => void };
39
-
40
- rl.stdoutMuted = true;
41
- rl._writeToOutput = (value: string) => {
42
- if (value.includes(query)) {
43
- (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
44
- return;
45
- }
46
- if (!rl.stdoutMuted) {
47
- (rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
48
- }
49
- };
50
-
51
- const answer = await new Promise<string>((resolve) => {
52
- rl.question(query, resolve);
53
- });
54
- rl.close();
55
- process.stdout.write('\n');
30
+ const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
56
31
  return validateSecret(answer, label);
57
32
  }
58
33
 
59
34
  const sudoSession = createSudoSession({
60
35
  promptPassword: async () =>
61
36
  await promptHidden(
62
- 'Root password (input hidden; required to change local admin chain and token configuration): ',
63
- 'root password',
37
+ 'macOS admin password for sudo (input hidden; required to change local admin chain and token configuration): ',
38
+ 'macOS admin password for sudo',
64
39
  ),
65
40
  });
66
41
 
package/src/lib/rust.ts CHANGED
@@ -1,15 +1,23 @@
1
- import { spawn } from 'node:child_process';
2
- import fs from 'node:fs';
3
- import type { WlfiConfig } from '../../packages/config/src/index.js';
4
- import * as configModule from '../../packages/config/src/index.js';
5
- import { assertTrustedExecutablePath } from './fs-trust.js';
6
- import { resolveValidatedPassthroughDaemonSocket } from './passthrough-security.js';
7
- import { prepareSpawnOptions, type RunRustBinaryOptions } from './rust-spawn-options.js';
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import type { WlfiConfig } from "../../packages/config/src/index.js";
4
+ import * as configModule from "../../packages/config/src/index.js";
5
+ import { assertTrustedExecutablePath } from "./fs-trust.js";
6
+ import { resolveValidatedPassthroughDaemonSocket } from "./passthrough-security.js";
7
+ import {
8
+ prepareSpawnOptions,
9
+ type RunRustBinaryOptions,
10
+ } from "./rust-spawn-options.js";
8
11
 
9
12
  const { readConfig, resolveRustBinaryPath } =
10
- configModule as typeof import('../../packages/config/src/index.js');
13
+ configModule as typeof import("../../packages/config/src/index.js");
11
14
 
12
- export type RustBinaryName = 'wlfi-agent-daemon' | 'wlfi-agent-admin' | 'wlfi-agent-agent';
15
+ export type RustBinaryName =
16
+ | "wlfi-agent-daemon"
17
+ | "wlfi-agent-admin"
18
+ | "wlfi-agent-agent";
19
+
20
+ const MAX_STDOUT_SNIPPET_CHARS = 200;
13
21
 
14
22
  export class RustBinaryExitError extends Error {
15
23
  readonly binaryName: RustBinaryName;
@@ -17,9 +25,14 @@ export class RustBinaryExitError extends Error {
17
25
  readonly stdout: string;
18
26
  readonly stderr: string;
19
27
 
20
- constructor(binaryName: RustBinaryName, code: number, stdout: string, stderr: string) {
28
+ constructor(
29
+ binaryName: RustBinaryName,
30
+ code: number,
31
+ stdout: string,
32
+ stderr: string,
33
+ ) {
21
34
  super(stderr.trim() || `${binaryName} exited with code ${code}`);
22
- this.name = 'RustBinaryExitError';
35
+ this.name = "RustBinaryExitError";
23
36
  this.binaryName = binaryName;
24
37
  this.code = code;
25
38
  this.stdout = stdout;
@@ -27,7 +40,30 @@ export class RustBinaryExitError extends Error {
27
40
  }
28
41
  }
29
42
 
43
+ export class RustBinaryJsonParseError extends Error {
44
+ readonly binaryName: RustBinaryName;
45
+ readonly stdout: string;
46
+ readonly cause: unknown;
47
+
48
+ constructor(binaryName: RustBinaryName, stdout: string, cause: unknown) {
49
+ const snippet =
50
+ stdout.length > MAX_STDOUT_SNIPPET_CHARS
51
+ ? `${stdout.slice(0, MAX_STDOUT_SNIPPET_CHARS)}...`
52
+ : stdout;
53
+ const message =
54
+ cause instanceof Error
55
+ ? `${binaryName} produced invalid JSON: ${cause.message}. stdout: ${snippet}`
56
+ : `${binaryName} produced invalid JSON. stdout: ${snippet}`;
57
+ super(message);
58
+ this.name = "RustBinaryJsonParseError";
59
+ this.binaryName = binaryName;
60
+ this.stdout = stdout;
61
+ this.cause = cause;
62
+ }
63
+ }
64
+
30
65
  function ensureBinary(binaryName: RustBinaryName, config?: WlfiConfig): string {
66
+ /* c8 ignore next -- both provided-config and readConfig fallback paths are exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
31
67
  const resolved = resolveRustBinaryPath(binaryName, config ?? readConfig());
32
68
  if (!fs.existsSync(resolved)) {
33
69
  throw new Error(
@@ -40,15 +76,56 @@ function ensureBinary(binaryName: RustBinaryName, config?: WlfiConfig): string {
40
76
 
41
77
  function forwardedArgsIncludeDaemonSocket(args: string[]): boolean {
42
78
  return args.some(
43
- (arg, _index) => arg === '--daemon-socket' || arg.startsWith('--daemon-socket='),
79
+ (arg, _index) =>
80
+ arg === "--daemon-socket" || arg.startsWith("--daemon-socket="),
44
81
  );
45
82
  }
46
83
 
84
+ async function writeChildStdin(child: ReturnType<typeof spawn>, stdin: string): Promise<void> {
85
+ const stream = child.stdin;
86
+ /* c8 ignore next 3 -- child.stdin is expected whenever this helper is used, but keep the guard for defensive mocked/process edge cases */
87
+ if (!stream) {
88
+ return;
89
+ }
90
+
91
+ await new Promise<void>((resolve, reject) => {
92
+ let settled = false;
93
+
94
+ const finish = () => {
95
+ /* c8 ignore next 3 -- defensive re-entry guard */
96
+ if (settled) {
97
+ return;
98
+ }
99
+ settled = true;
100
+ stream.off('error', handleError);
101
+ resolve();
102
+ };
103
+
104
+ const handleError = (error: NodeJS.ErrnoException) => {
105
+ /* c8 ignore next 3 -- defensive re-entry guard */
106
+ if (settled) {
107
+ return;
108
+ }
109
+ if (error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') {
110
+ finish();
111
+ return;
112
+ }
113
+ settled = true;
114
+ stream.off('error', handleError);
115
+ reject(error);
116
+ };
117
+
118
+ stream.on('error', handleError);
119
+ stream.end(stdin, finish);
120
+ });
121
+ }
122
+
47
123
  export async function passthroughRustBinary(
48
124
  binaryName: RustBinaryName,
49
125
  args: string[],
50
126
  config?: WlfiConfig,
51
127
  ): Promise<number> {
128
+ /* c8 ignore next -- both provided-config and readConfig fallback paths are exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
52
129
  const resolvedConfig = config ?? readConfig();
53
130
  const resolvedDaemonSocket = resolveValidatedPassthroughDaemonSocket(
54
131
  binaryName,
@@ -61,23 +138,28 @@ export async function passthroughRustBinary(
61
138
  if (
62
139
  resolvedDaemonSocket &&
63
140
  !forwardedArgsIncludeDaemonSocket(args) &&
141
+ /* c8 ignore next -- explicit env override is exercised, but c8 misattributes this optional-chain/nullish check */
64
142
  !env.WLFI_DAEMON_SOCKET?.trim()
65
143
  ) {
66
144
  env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
67
145
  }
68
146
  const child = spawn(executable, prepared.args, {
69
- stdio: [prepared.stdin !== undefined ? 'pipe' : 'inherit', 'inherit', 'inherit'],
147
+ stdio: [
148
+ prepared.stdin !== undefined ? "pipe" : "inherit",
149
+ "inherit",
150
+ "inherit",
151
+ ],
70
152
  env,
71
153
  });
72
154
 
73
- if (prepared.stdin !== undefined) {
74
- child.stdin?.end(prepared.stdin);
75
- }
76
-
77
- return await new Promise((resolve, reject) => {
78
- child.on('error', reject);
79
- child.on('close', (code) => resolve(code ?? 1));
155
+ const codePromise = new Promise<number>((resolve, reject) => {
156
+ child.on("error", reject);
157
+ child.on("close", (code) => resolve(code ?? 1));
80
158
  });
159
+ const stdinPromise =
160
+ prepared.stdin !== undefined ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
161
+ const [code] = await Promise.all([codePromise, stdinPromise]);
162
+ return code;
81
163
  }
82
164
 
83
165
  export async function runRustBinary(
@@ -103,27 +185,37 @@ export async function runRustBinary(
103
185
  env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
104
186
  }
105
187
  const child = spawn(executable, prepared.args, {
106
- stdio: [prepared.stdin !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'],
188
+ stdio: [prepared.stdin !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
107
189
  env,
108
190
  });
109
191
 
110
- let stdout = '';
111
- let stderr = '';
112
- child.stdout?.on('data', (chunk) => {
192
+ let stdout = "";
193
+ let stderr = "";
194
+ child.stdout?.on("data", (chunk) => {
113
195
  stdout += chunk.toString();
114
196
  });
115
- child.stderr?.on('data', (chunk) => {
197
+ child.stderr?.on("data", (chunk) => {
116
198
  stderr += chunk.toString();
117
199
  });
118
200
 
119
- if (prepared.stdin !== undefined) {
120
- child.stdin?.end(prepared.stdin);
121
- }
122
-
123
- const code = await new Promise<number>((resolve, reject) => {
124
- child.on('error', reject);
125
- child.on('close', (value) => resolve(value ?? 1));
201
+ const codePromise = new Promise<number>((resolve, reject) => {
202
+ child.on("error", reject);
203
+ child.on("close", (code, signal) => {
204
+ if (code !== null && code !== undefined) {
205
+ resolve(code);
206
+ } else {
207
+ // Terminated by signal. Preserve the remote behavior while still waiting for stdin writes.
208
+ resolve(
209
+ signal
210
+ ? 128 + (require("node:os").constants.errno?.SIGTERM ?? 143)
211
+ : 1,
212
+ );
213
+ }
214
+ });
126
215
  });
216
+ const stdinPromise =
217
+ prepared.stdin !== undefined ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
218
+ const [code] = await Promise.all([codePromise, stdinPromise]);
127
219
 
128
220
  if (code !== 0) {
129
221
  throw new RustBinaryExitError(binaryName, code, stdout, stderr);
@@ -139,5 +231,9 @@ export async function runRustBinaryJson<T>(
139
231
  options: RunRustBinaryOptions = {},
140
232
  ): Promise<T> {
141
233
  const { stdout } = await runRustBinary(binaryName, args, config, options);
142
- return JSON.parse(stdout) as T;
234
+ try {
235
+ return JSON.parse(stdout) as T;
236
+ } catch (error) {
237
+ throw new RustBinaryJsonParseError(binaryName, stdout, error);
238
+ }
143
239
  }
@@ -84,6 +84,7 @@ export async function assertSignedBroadcastTransactionMatchesRequest(
84
84
  throw new Error(`signed raw transaction to mismatch: expected ${expected.to}, received ${parsedTo ?? 'null'}`);
85
85
  }
86
86
 
87
+ /* c8 ignore next 3 -- supported signed transactions produced by viem always include a nonce */
87
88
  if (parsed.nonce === undefined) {
88
89
  throw new Error('signed raw transaction nonce is missing');
89
90
  }
package/src/lib/sudo.ts CHANGED
@@ -32,6 +32,20 @@ function isSudoAuthenticationFailure(output: string): boolean {
32
32
  );
33
33
  }
34
34
 
35
+ function formatSudoFailureMessage(result: SudoCommandResult): string {
36
+ const combinedOutput = `${result.stderr}\n${result.stdout}`;
37
+ if (isSudoAuthenticationFailure(combinedOutput)) {
38
+ return (
39
+ 'sudo authentication failed. Enter your system admin password used for sudo, not the WLFI vault password.'
40
+ );
41
+ }
42
+ return (
43
+ result.stderr.trim() ||
44
+ result.stdout.trim() ||
45
+ `sudo credential check failed (exit code ${result.code})`
46
+ );
47
+ }
48
+
35
49
  async function runCommand(
36
50
  command: string,
37
51
  args: string[],
@@ -103,11 +117,7 @@ export function createSudoSession(deps: CreateSudoSessionDeps) {
103
117
  );
104
118
 
105
119
  if (result.code !== 0) {
106
- throw new Error(
107
- result.stderr.trim() ||
108
- result.stdout.trim() ||
109
- `sudo credential check failed (exit code ${result.code})`,
110
- );
120
+ throw new Error(formatSudoFailureMessage(result));
111
121
  }
112
122
 
113
123
  primed = true;
@@ -31,6 +31,7 @@ function deriveWalletAddress(vaultPublicKey: string): string | undefined {
31
31
  }
32
32
 
33
33
  function findMatchingBootstrapSummary(profile: WalletProfile | undefined): BootstrapSetupSummary | undefined {
34
+ /* c8 ignore next 3 -- resolveWalletProfile only calls this helper when config.wallet is present */
34
35
  if (!profile) {
35
36
  return undefined;
36
37
  }
@@ -101,6 +102,7 @@ export function resolveWalletProfile(config: WlfiConfig): WalletProfile {
101
102
 
102
103
  export function resolveWalletAddress(config: WlfiConfig): Address {
103
104
  const profile = resolveWalletProfile(config);
105
+ /* c8 ignore next -- explicit-address and derived-address paths are both exercised, but c8 misattributes this nullish expression under --experimental-strip-types */
104
106
  const address = presentString(profile.address) ?? deriveWalletAddress(profile.vaultPublicKey);
105
107
  if (!address || !isAddress(address)) {
106
108
  throw new Error(
@@ -175,6 +177,7 @@ function collectWalletBalanceTargets(config: WlfiConfig): WalletBalanceTarget[]
175
177
  symbol: tokenProfile.symbol,
176
178
  name: tokenProfile.name,
177
179
  chainKey,
180
+ /* c8 ignore next -- resolveChainProfile only yields usable balance targets when a chain name is available */
178
181
  chainName: resolvedChain?.name ?? chainKey,
179
182
  chainId: chainProfile.chainId,
180
183
  rpcUrl,
@@ -62,6 +62,8 @@ export interface WalletSetupAdminArgsInput {
62
62
  attachPolicyId?: string[];
63
63
  attachBootstrapPolicies?: boolean;
64
64
  fromSharedConfig?: boolean;
65
+ existingVaultKeyId?: string;
66
+ existingVaultPublicKey?: string;
65
67
  bootstrapOutputPath: string;
66
68
  }
67
69
 
@@ -250,6 +252,7 @@ function assertNoSymlinkAncestorDirectories(targetPath: string, label: string):
250
252
  for (const segment of relativeParent.split(path.sep).filter(Boolean)) {
251
253
  currentPath = path.join(currentPath, segment);
252
254
  const stats = readLstat(currentPath);
255
+ /* c8 ignore next 3 -- missing ancestors short-circuit preview traversal defensively; behavior is exercised indirectly but c8 misattributes this break */
253
256
  if (!stats) {
254
257
  break;
255
258
  }
@@ -283,6 +286,7 @@ function currentEffectiveUid(): number | null {
283
286
  if (typeof process.geteuid === 'function') {
284
287
  return process.geteuid();
285
288
  }
289
+ /* c8 ignore next 4 -- fallback runtimes without geteuid are defensive-only in this CLI environment */
286
290
  if (typeof process.getuid === 'function') {
287
291
  return process.getuid();
288
292
  }
@@ -290,6 +294,7 @@ function currentEffectiveUid(): number | null {
290
294
  }
291
295
 
292
296
  function canCurrentProcessTightenDirectoryMode(stats: fs.Stats): boolean {
297
+ /* c8 ignore next 3 -- win32/uid-less stats short-circuit is defensive and platform-specific */
293
298
  if (process.platform === 'win32' || typeof stats.uid !== 'number') {
294
299
  return true;
295
300
  }
@@ -636,6 +641,28 @@ function resolveValidatedWalletSetupPolicyIds(values: string[] | undefined): str
636
641
  );
637
642
  }
638
643
 
644
+ function resolveValidatedExistingVaultReuse(input: {
645
+ existingVaultKeyId?: string;
646
+ existingVaultPublicKey?: string;
647
+ }): { keyId?: string; publicKey?: string } {
648
+ const keyId = presentString(input.existingVaultKeyId);
649
+ const publicKey = presentString(input.existingVaultPublicKey);
650
+
651
+ if (!keyId && !publicKey) {
652
+ return {};
653
+ }
654
+ if (!keyId || !publicKey) {
655
+ throw new Error(
656
+ 'existingVaultKeyId and existingVaultPublicKey must be provided together to reuse an existing wallet',
657
+ );
658
+ }
659
+
660
+ return {
661
+ keyId: assertWalletSetupUuid(keyId, 'existingVaultKeyId'),
662
+ publicKey,
663
+ };
664
+ }
665
+
639
666
  function previewBootstrapOutputPath(inputPath?: string): WalletSetupBootstrapOutput {
640
667
  const explicitPath = presentString(inputPath);
641
668
  if (explicitPath) {
@@ -794,6 +821,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
794
821
  summary.perTxMaxCalldataBytesPolicyId,
795
822
  ].filter((value): value is string => typeof value === 'string' && value.length > 0);
796
823
 
824
+ /* c8 ignore next -- bootstrap parsing normalizes omitted destination overrides to [], but c8 still tracks the nullish fallback separately */
797
825
  for (const destinationOverride of summary.destinationOverrides ?? []) {
798
826
  ids.push(
799
827
  destinationOverride.perTxPolicyId,
@@ -817,6 +845,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
817
845
  }
818
846
  }
819
847
 
848
+ /* c8 ignore next -- bootstrap parsing normalizes omitted token policies to [], but c8 still tracks the nullish fallback separately */
820
849
  for (const tokenPolicy of summary.tokenPolicies ?? []) {
821
850
  ids.push(tokenPolicy.perTxPolicyId, tokenPolicy.dailyPolicyId, tokenPolicy.weeklyPolicyId);
822
851
  if (tokenPolicy.gasPolicyId) {
@@ -836,6 +865,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
836
865
  }
837
866
  }
838
867
 
868
+ /* c8 ignore next -- bootstrap parsing normalizes omitted token destination overrides to [], but c8 still tracks the nullish fallback separately */
839
869
  for (const destinationOverride of summary.tokenDestinationOverrides ?? []) {
840
870
  ids.push(
841
871
  destinationOverride.perTxPolicyId,
@@ -859,6 +889,7 @@ function createdBootstrapPolicyIds(summary: BootstrapSetupSummary): string[] {
859
889
  }
860
890
  }
861
891
 
892
+ /* c8 ignore next -- bootstrap parsing normalizes omitted manual approval policies to [], but c8 still tracks the nullish fallback separately */
862
893
  for (const manualApproval of summary.tokenManualApprovalPolicies ?? []) {
863
894
  ids.push(manualApproval.policyId);
864
895
  }
@@ -1186,7 +1217,9 @@ function formatBooleanPlanValue(value: boolean): string {
1186
1217
 
1187
1218
  function formatWalletSetupScope(plan: WalletSetupPlan): string[] {
1188
1219
  return [
1220
+ /* c8 ignore next -- both default-network and explicit-network renderings are exercised, but c8 misattributes this ternary under --experimental-strip-types */
1189
1221
  `- Network: ${plan.policyScope.network === null ? 'daemon default' : String(plan.policyScope.network)}`,
1222
+ /* c8 ignore next -- both default and explicit chain-name renderings are exercised, but c8 misattributes this nullish expression */
1190
1223
  `- Chain Name: ${plan.policyScope.chainName ?? 'daemon default'}`,
1191
1224
  `- Recipient: ${plan.policyScope.recipient ?? 'all recipients'}`,
1192
1225
  `- Asset Mode: ${plan.policyScope.assets.mode}`,
@@ -1218,6 +1251,7 @@ function formatWalletSetupPreflight(plan: WalletSetupPlan): string[] {
1218
1251
  return [
1219
1252
  `- Daemon Socket Trusted: ${formatBooleanPlanValue(plan.preflight.daemonSocketTrusted)}`,
1220
1253
  plan.preflight.daemonSocketError ? ` ${plan.preflight.daemonSocketError}` : null,
1254
+ /* c8 ignore next -- both null and boolean RPC preflight states are exercised, but c8 misattributes this ternary */
1221
1255
  `- RPC URL Trusted: ${
1222
1256
  plan.preflight.rpcUrlTrusted === null
1223
1257
  ? 'not applicable'
@@ -1230,10 +1264,12 @@ function formatWalletSetupPreflight(plan: WalletSetupPlan): string[] {
1230
1264
  }
1231
1265
 
1232
1266
  export function formatWalletSetupPlanText(plan: WalletSetupPlan): string {
1267
+ /* c8 ignore next -- explicit and auto-generated bootstrap outputs are both exercised, but c8 misattributes this ternary */
1233
1268
  const bootstrapOutputLabel = `${plan.bootstrapOutput.path} (${plan.bootstrapOutput.autoGenerated ? 'auto-generated' : 'explicit'}, ${plan.bootstrapOutput.cleanupAction} after import)`;
1234
1269
 
1235
1270
  const lines = [
1236
1271
  'Wallet Setup Preview',
1272
+ /* c8 ignore next -- allowed and blocked admin access renderings are both exercised, but c8 misattributes this ternary */
1237
1273
  `Admin Access: ${plan.adminAccess.permitted ? 'allowed' : 'blocked'} (${plan.adminAccess.mode})`,
1238
1274
  `Admin Access Reason: ${plan.adminAccess.reason}`,
1239
1275
  `Daemon Socket: ${plan.daemonSocket}`,
@@ -1260,8 +1296,11 @@ export function formatWalletSetupPlanText(plan: WalletSetupPlan): string {
1260
1296
  'Config After Setup',
1261
1297
  `- Agent Key ID: ${plan.configAfterSetup.agentKeyId}`,
1262
1298
  `- Daemon Socket: ${plan.configAfterSetup.daemonSocket}`,
1299
+ /* c8 ignore next -- unchanged and explicit chain-id renderings are both exercised, but c8 misattributes this ternary */
1263
1300
  `- Chain ID: ${plan.configAfterSetup.chainId === null ? 'unchanged' : String(plan.configAfterSetup.chainId)}`,
1301
+ /* c8 ignore next -- unchanged and explicit chain-name renderings are both exercised, but c8 misattributes this nullish expression */
1264
1302
  `- Chain Name: ${plan.configAfterSetup.chainName ?? 'unchanged'}`,
1303
+ /* c8 ignore next -- unchanged and explicit RPC URL renderings are both exercised, but c8 misattributes this nullish expression */
1265
1304
  `- RPC URL: ${plan.configAfterSetup.rpcUrl ?? 'unchanged'}`,
1266
1305
  '',
1267
1306
  'Preflight',
@@ -1318,6 +1357,7 @@ export function buildWalletSetupAdminArgs(input: WalletSetupAdminArgsInput): str
1318
1357
  const tokens = resolveValidatedWalletSetupTokenList(input.token);
1319
1358
  const policyIds = resolveValidatedWalletSetupPolicyIds(input.attachPolicyId);
1320
1359
  const fromSharedConfig = shouldBootstrapFromSharedConfig(input);
1360
+ const existingVaultReuse = resolveValidatedExistingVaultReuse(input);
1321
1361
 
1322
1362
  if (input.vaultPassword) {
1323
1363
  throw new Error(
@@ -1338,6 +1378,14 @@ export function buildWalletSetupAdminArgs(input: WalletSetupAdminArgsInput): str
1338
1378
  if (fromSharedConfig) {
1339
1379
  args.push('--from-shared-config');
1340
1380
  }
1381
+ if (existingVaultReuse.keyId && existingVaultReuse.publicKey) {
1382
+ args.push(
1383
+ '--existing-vault-key-id',
1384
+ existingVaultReuse.keyId,
1385
+ '--existing-vault-public-key',
1386
+ existingVaultReuse.publicKey,
1387
+ );
1388
+ }
1341
1389
 
1342
1390
  const appendValue = (flag: string, value: string | undefined) => {
1343
1391
  if (value) {
@@ -1376,6 +1424,7 @@ export function completeWalletSetup(
1376
1424
  options: CompleteWalletSetupOptions,
1377
1425
  deps: CompleteWalletSetupDeps = {},
1378
1426
  ): CompleteWalletSetupResult {
1427
+ /* c8 ignore next -- tests cover both explicit darwin and non-darwin behavior, but c8 misattributes this nullish expression */
1379
1428
  const platform = deps.platform ?? process.platform;
1380
1429
  if (platform !== 'darwin') {
1381
1430
  throw new Error(
@@ -1392,6 +1441,7 @@ export function completeWalletSetup(
1392
1441
  throw new Error('--chain-name requires --network');
1393
1442
  }
1394
1443
 
1444
+ /* c8 ignore next -- default keychain storage is environment-coupled, so tests exercise the injected path instead */
1395
1445
  const storeAgentAuthToken = deps.storeAgentAuthToken ?? storeAgentAuthTokenInKeychain;
1396
1446
  const loadConfig = deps.readConfig ?? readConfig;
1397
1447
  const persistConfig = deps.writeConfig ?? writeConfig;
@@ -1416,6 +1466,7 @@ export function completeWalletSetup(
1416
1466
  const { summary, credentials } = readBootstrapSetupFile(options.bootstrapOutputPath);
1417
1467
  assertBootstrapSetupSummaryLeaseIsActive(summary);
1418
1468
 
1469
+ /* c8 ignore next 5 -- summary and credentials currently parse the same agent_key_id field, so this mismatch guard is a defensive invariant */
1419
1470
  if (summary.agentKeyId !== credentials.agentKeyId) {
1420
1471
  throw new Error(
1421
1472
  'bootstrap credentials file agent_key_id does not match setup summary agent_key_id',
@@ -1438,6 +1489,7 @@ export function completeWalletSetup(
1438
1489
  nextConfig.chainId = assertPositiveChainId(options.network);
1439
1490
  nextConfig.chainName =
1440
1491
  normalizedChainName ??
1492
+ /* c8 ignore next -- configured-chain and synthetic fallback label paths are exercised, but c8 misattributes this nullish expression */
1441
1493
  configuredChainName(nextConfig.chainId, currentConfig) ??
1442
1494
  `chain-${nextConfig.chainId}`;
1443
1495
  nextConfig.rpcUrl = normalizedRpcUrl ?? configuredRpcUrl(nextConfig.chainId, currentConfig);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/cache/dist/chunk-QF4XKEIA.cjs","../src/service/index.ts"],"names":[],"mappings":"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","file":"/Users/hanzhi/Documents/WLFI/wlfi-agent-sdk/packages/cache/dist/chunk-QF4XKEIA.cjs","sourcesContent":[null,"import { createHash, randomBytes, randomUUID } from 'node:crypto';\nimport type Redis from 'ioredis';\nimport { getCacheClient } from '../client/index.js';\nimport { CacheError, cacheErrorCodes, toCacheError } from '../errors/index.js';\n\nexport const relayApprovalStatuses = [\n 'pending',\n 'approved',\n 'rejected',\n 'completed',\n 'expired',\n] as const;\nexport type RelayApprovalStatus = (typeof relayApprovalStatuses)[number];\n\nexport const relayUpdateStatuses = [\n 'pending',\n 'inflight',\n 'applied',\n 'rejected',\n 'failed',\n] as const;\nexport type RelayUpdateStatus = (typeof relayUpdateStatuses)[number];\n\nexport interface RelayDaemonProfile {\n daemonId: string;\n daemonPublicKey: string;\n ethereumAddress: string;\n label?: string;\n lastSeenAt: string;\n registeredAt: string;\n relayUrl?: string;\n signerBackend?: string;\n status: 'active' | 'paused';\n updatedAt: string;\n version?: string;\n}\n\nexport interface RelayPolicyRecord {\n action: string;\n amountMaxWei?: string;\n amountMinWei?: string;\n chainId?: number;\n daemonId: string;\n destination: string;\n metadata?: Record<string, string>;\n policyId: string;\n requiresManualApproval: boolean;\n scope: 'default' | 'override';\n tokenAddress?: string;\n updatedAt: string;\n}\n\nexport interface RelayAgentKeyRecord {\n agentKeyId: string;\n createdAt?: string;\n daemonId: string;\n label?: string;\n metadata?: Record<string, string>;\n status: 'active' | 'revoked';\n updatedAt: string;\n}\n\nexport interface RelayApprovalRequestRecord {\n agentKeyId?: string;\n amountWei?: string;\n approvalRequestId: string;\n chainId?: number;\n daemonId: string;\n destination: string;\n metadata?: Record<string, string>;\n network?: string;\n reason?: string;\n requestedAt: string;\n status: RelayApprovalStatus;\n tokenAddress?: string;\n transactionType: string;\n updatedAt: string;\n}\n\nexport interface RelayEncryptedPayload {\n aadBase64?: string;\n algorithm: string;\n ciphertextBase64: string;\n contentSha256Hex?: string;\n encapsulatedKeyBase64: string;\n nonceBase64: string;\n schemaVersion: number;\n}\n\nexport interface RelayUpdateFeedbackRecord {\n daemonId: string;\n details?: Record<string, string>;\n feedbackAt: string;\n message?: string;\n status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;\n updateId: string;\n}\n\nexport interface RelayEncryptedUpdateRecord {\n claimToken?: string;\n claimUntil?: string;\n createdAt: string;\n daemonId: string;\n feedback?: RelayUpdateFeedbackRecord;\n lastDeliveredAt?: string;\n metadata?: Record<string, string>;\n payload: RelayEncryptedPayload;\n status: RelayUpdateStatus;\n targetApprovalRequestId?: string;\n type: string;\n updateId: string;\n updatedAt: string;\n}\n\nexport interface SyncDaemonRegistrationInput {\n agentKeys?: RelayAgentKeyRecord[];\n approvalRequests?: RelayApprovalRequestRecord[];\n daemon: RelayDaemonProfile;\n policies?: RelayPolicyRecord[];\n}\n\nexport interface ApprovalRequestFilters {\n daemonId?: string;\n destination?: string;\n limit?: number;\n status?: RelayApprovalStatus;\n tokenAddress?: string;\n}\n\nexport interface CreateEncryptedUpdateInput {\n daemonId: string;\n metadata?: Record<string, string>;\n payload: RelayEncryptedPayload;\n targetApprovalRequestId?: string;\n type: string;\n updateId?: string;\n}\n\nexport interface ClaimEncryptedUpdatesInput {\n daemonId: string;\n leaseSeconds?: number;\n limit?: number;\n}\n\nexport interface SubmitUpdateFeedbackInput {\n claimToken: string;\n daemonId: string;\n details?: Record<string, string>;\n message?: string;\n status: Extract<RelayUpdateStatus, 'applied' | 'failed' | 'rejected'>;\n updateId: string;\n}\n\nexport interface ApprovalCapabilityFailureRecord {\n attempts: number;\n blockedUntil?: string;\n firstFailedAt: string;\n lastFailedAt: string;\n}\n\nexport interface RecordApprovalCapabilityFailureResult {\n attempts: number;\n blocked: boolean;\n blockedUntil: string | null;\n}\n\ninterface JsonCache {\n del(key: string): Promise<number>;\n get(key: string): Promise<string | null>;\n ping(): Promise<string>;\n quit(): Promise<string>;\n sadd(key: string, ...members: string[]): Promise<number>;\n set(key: string, value: string, mode?: 'NX' | 'XX'): Promise<'OK' | null>;\n smembers(key: string): Promise<string[]>;\n zadd(key: string, ...args: (string | number)[]): Promise<number>;\n zrange(key: string, start: number, stop: number, ...args: string[]): Promise<string[]>;\n zrem(key: string, ...members: string[]): Promise<number>;\n}\n\nconst defaultNamespace = 'wlfi:relay';\nconst approvalCapabilityFailureWindowMs = 5 * 60 * 1000;\nconst approvalCapabilityMaxFailures = 5;\nconst approvalCapabilityBlockWindowMs = 10 * 60 * 1000;\n\nconst toIsoTimestamp = (value = new Date()): string => value.toISOString();\n\nconst dedupe = <T>(values: T[]): T[] => [...new Set(values)];\n\nconst matchesOptionalFilter = (\n value: string | undefined,\n expected: string | undefined,\n): boolean => {\n if (!expected) {\n return true;\n }\n\n return value?.toLowerCase() === expected.toLowerCase();\n};\n\nconst clampLimit = (limit: number | undefined, fallback: number, max: number): number => {\n if (!limit || Number.isNaN(limit)) {\n return fallback;\n }\n\n return Math.max(1, Math.min(limit, max));\n};\n\nconst createApprovalCapabilityToken = (): string => randomBytes(32).toString('hex');\n\nconst approvalCapabilityHash = (token: string): string =>\n createHash('sha256').update(token, 'utf8').digest('hex');\n\nconst isActiveApprovalUpdateRecord = (\n record: RelayEncryptedUpdateRecord | null | undefined,\n approvalRequestId: string,\n): record is RelayEncryptedUpdateRecord =>\n Boolean(\n record &&\n record.type === 'manual_approval_decision' &&\n record.targetApprovalRequestId === approvalRequestId &&\n (record.status === 'pending' || record.status === 'inflight'),\n );\n\nconst preserveRotatedApprovalCapability = (\n incoming: RelayApprovalRequestRecord,\n existing: RelayApprovalRequestRecord | null,\n): RelayApprovalRequestRecord => {\n const existingMetadata = existing?.metadata;\n const incomingMetadata = incoming.metadata;\n const preservedCapabilityToken = existingMetadata?.approvalCapabilityToken?.trim();\n const preservedCapabilityHash = existingMetadata?.approvalCapabilityHash?.trim();\n\n if (!preservedCapabilityToken && !preservedCapabilityHash) {\n return incoming;\n }\n\n return {\n ...incoming,\n metadata: {\n ...(incomingMetadata ?? {}),\n ...(preservedCapabilityToken\n ? { approvalCapabilityToken: preservedCapabilityToken }\n : {}),\n ...(preservedCapabilityHash ? { approvalCapabilityHash: preservedCapabilityHash } : {}),\n },\n };\n};\n\nexport class RelayCacheService {\n private readonly client: JsonCache;\n private readonly namespace: string;\n\n constructor(options: { client?: Redis; namespace?: string } = {}) {\n this.client = (options.client ?? getCacheClient()) as unknown as JsonCache;\n this.namespace = options.namespace ?? defaultNamespace;\n }\n\n async ping(): Promise<string> {\n try {\n return await this.client.ping();\n } catch (error) {\n throw toCacheError(error, { operation: 'ping' });\n }\n }\n\n async syncDaemonRegistration(input: SyncDaemonRegistrationInput): Promise<{\n agentKeyCount: number;\n approvalRequestCount: number;\n policyCount: number;\n }> {\n const profile = {\n ...input.daemon,\n lastSeenAt: input.daemon.lastSeenAt || toIsoTimestamp(),\n updatedAt: input.daemon.updatedAt || toIsoTimestamp(),\n } satisfies RelayDaemonProfile;\n\n try {\n await this.writeJson(this.daemonProfileKey(profile.daemonId), profile);\n await this.client.sadd(this.daemonIndexKey(), profile.daemonId);\n\n if (input.policies) {\n const policies = input.policies.map((policy) => ({\n ...policy,\n daemonId: profile.daemonId,\n }));\n await this.writeJson(this.daemonPoliciesKey(profile.daemonId), policies);\n }\n\n if (input.agentKeys) {\n const agentKeys = input.agentKeys.map((agentKey) => ({\n ...agentKey,\n daemonId: profile.daemonId,\n }));\n await this.writeJson(this.daemonAgentKeysKey(profile.daemonId), agentKeys);\n }\n\n if (input.approvalRequests) {\n for (const approvalRequest of input.approvalRequests) {\n const existing = await this.readJson<RelayApprovalRequestRecord>(\n this.approvalKey(approvalRequest.approvalRequestId),\n );\n const normalized = preserveRotatedApprovalCapability(\n { ...approvalRequest, daemonId: profile.daemonId },\n existing,\n );\n await this.writeJson(this.approvalKey(normalized.approvalRequestId), normalized);\n await this.client.zadd(\n this.daemonApprovalsKey(profile.daemonId),\n Date.parse(normalized.requestedAt),\n normalized.approvalRequestId,\n );\n }\n }\n\n return {\n agentKeyCount: input.agentKeys?.length ?? 0,\n approvalRequestCount: input.approvalRequests?.length ?? 0,\n policyCount: input.policies?.length ?? 0,\n };\n } catch (error) {\n throw toCacheError(error, {\n key: this.daemonProfileKey(profile.daemonId),\n operation: 'syncDaemonRegistration',\n });\n }\n }\n\n async listDaemons(): Promise<RelayDaemonProfile[]> {\n const daemonIds = await this.client.smembers(this.daemonIndexKey());\n const profiles = await Promise.all(\n daemonIds.map((daemonId) =>\n this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId)),\n ),\n );\n\n return profiles.filter((profile): profile is RelayDaemonProfile => Boolean(profile));\n }\n\n async getDaemonProfile(daemonId: string): Promise<RelayDaemonProfile | null> {\n return await this.readJson<RelayDaemonProfile>(this.daemonProfileKey(daemonId));\n }\n\n async getDaemonPolicies(daemonId: string): Promise<RelayPolicyRecord[]> {\n return (await this.readJson<RelayPolicyRecord[]>(this.daemonPoliciesKey(daemonId))) ?? [];\n }\n\n async getDaemonAgentKeys(daemonId: string): Promise<RelayAgentKeyRecord[]> {\n return (await this.readJson<RelayAgentKeyRecord[]>(this.daemonAgentKeysKey(daemonId))) ?? [];\n }\n\n async getApprovalRequest(approvalRequestId: string): Promise<RelayApprovalRequestRecord | null> {\n return await this.readJson<RelayApprovalRequestRecord>(this.approvalKey(approvalRequestId));\n }\n\n async listApprovalRequests(\n filters: ApprovalRequestFilters = {},\n ): Promise<RelayApprovalRequestRecord[]> {\n const limit = clampLimit(filters.limit, 100, 500);\n const daemonIds = filters.daemonId\n ? [filters.daemonId]\n : await this.client.smembers(this.daemonIndexKey());\n const requestIdsByDaemon = await Promise.all(\n daemonIds.map((daemonId) =>\n this.client.zrange(this.daemonApprovalsKey(daemonId), 0, limit * 2, 'REV'),\n ),\n );\n const requestIds = dedupe(requestIdsByDaemon.flat()).slice(0, limit * 3);\n const requests = await Promise.all(\n requestIds.map((requestId) =>\n this.readJson<RelayApprovalRequestRecord>(this.approvalKey(requestId)),\n ),\n );\n\n return requests\n .filter((request): request is RelayApprovalRequestRecord => Boolean(request))\n .filter((request) => (filters.daemonId ? request.daemonId === filters.daemonId : true))\n .filter((request) => (filters.status ? request.status === filters.status : true))\n .filter((request) => matchesOptionalFilter(request.destination, filters.destination))\n .filter((request) => matchesOptionalFilter(request.tokenAddress, filters.tokenAddress))\n .sort((left, right) => Date.parse(right.requestedAt) - Date.parse(left.requestedAt))\n .slice(0, limit);\n }\n\n async createEncryptedUpdate(\n input: CreateEncryptedUpdateInput,\n ): Promise<RelayEncryptedUpdateRecord> {\n if (input.type === 'manual_approval_decision') {\n if (!input.targetApprovalRequestId) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n message: 'Manual approval updates require a target approval request id',\n operation: 'createEncryptedUpdate',\n });\n }\n\n const approvalKey = this.approvalKey(input.targetApprovalRequestId);\n const approval = await this.readJson<RelayApprovalRequestRecord>(approvalKey);\n if (!approval) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key: approvalKey,\n message: `Unknown approval '${input.targetApprovalRequestId}'`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n if (approval.daemonId !== input.daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: approvalKey,\n message: `Approval '${input.targetApprovalRequestId}' belongs to daemon '${approval.daemonId}', not '${input.daemonId}'`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n if (approval.status !== 'pending') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: approvalKey,\n message: `Approval '${input.targetApprovalRequestId}' is '${approval.status}' and cannot accept new updates`,\n operation: 'createEncryptedUpdate',\n });\n }\n }\n\n const updateId = input.updateId ?? randomUUID();\n const now = toIsoTimestamp();\n const record: RelayEncryptedUpdateRecord = {\n createdAt: now,\n daemonId: input.daemonId,\n metadata: input.metadata,\n payload: input.payload,\n status: 'pending',\n targetApprovalRequestId: input.targetApprovalRequestId,\n type: input.type,\n updateId,\n updatedAt: now,\n };\n\n const activeApprovalKey =\n input.type === 'manual_approval_decision' && input.targetApprovalRequestId\n ? this.activeApprovalUpdateKey(input.targetApprovalRequestId)\n : null;\n const updateKey = this.updateKey(updateId);\n let ownsActiveApprovalSlot = false;\n\n await this.writeJson(updateKey, record);\n\n try {\n if (activeApprovalKey) {\n const reserved = await this.client.set(activeApprovalKey, updateId, 'NX');\n if (reserved !== 'OK') {\n const existingUpdateId = await this.client.get(activeApprovalKey);\n const existingRecord = existingUpdateId\n ? await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(existingUpdateId))\n : null;\n\n if (isActiveApprovalUpdateRecord(existingRecord, input.targetApprovalRequestId!)) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: activeApprovalKey,\n message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,\n operation: 'createEncryptedUpdate',\n });\n }\n\n await this.client.del(activeApprovalKey);\n const retriedReservation = await this.client.set(activeApprovalKey, updateId, 'NX');\n if (retriedReservation !== 'OK') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key: activeApprovalKey,\n message: `Approval '${input.targetApprovalRequestId}' already has a queued operator update`,\n operation: 'createEncryptedUpdate',\n });\n }\n }\n\n ownsActiveApprovalSlot = true;\n }\n\n await this.client.zadd(this.daemonUpdatesKey(input.daemonId), Date.now(), updateId);\n } catch (error) {\n await this.client.del(updateKey);\n if (activeApprovalKey && ownsActiveApprovalSlot) {\n await this.client.del(activeApprovalKey);\n }\n throw error;\n }\n\n return record;\n }\n\n async hasActiveApprovalUpdate(daemonId: string, approvalRequestId: string): Promise<boolean> {\n const indexedUpdateId = await this.client.get(this.activeApprovalUpdateKey(approvalRequestId));\n if (indexedUpdateId) {\n const indexedRecord = await this.readJson<RelayEncryptedUpdateRecord>(\n this.updateKey(indexedUpdateId),\n );\n if (isActiveApprovalUpdateRecord(indexedRecord, approvalRequestId)) {\n return true;\n }\n\n await this.client.del(this.activeApprovalUpdateKey(approvalRequestId));\n }\n\n const updateIds = await this.client.zrange(this.daemonUpdatesKey(daemonId), 0, -1, 'REV');\n\n for (const updateId of updateIds) {\n const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n if (isActiveApprovalUpdateRecord(record, approvalRequestId)) {\n await this.client.set(this.activeApprovalUpdateKey(approvalRequestId), updateId, 'NX');\n return true;\n }\n }\n\n return false;\n }\n\n async consumeApprovalCapability(\n approvalRequestId: string,\n capabilityHash: string,\n ): Promise<boolean> {\n try {\n const result = await this.client.set(\n this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n toIsoTimestamp(),\n 'NX',\n );\n return result === 'OK';\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n operation: 'consumeApprovalCapability',\n });\n }\n }\n\n async releaseApprovalCapabilityConsumption(\n approvalRequestId: string,\n capabilityHash: string,\n ): Promise<void> {\n try {\n await this.client.del(this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash));\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityConsumedKey(approvalRequestId, capabilityHash),\n operation: 'releaseApprovalCapabilityConsumption',\n });\n }\n }\n\n async clearApprovalCapabilityFailures(approvalRequestId: string): Promise<void> {\n try {\n await this.client.del(this.approvalCapabilityFailuresKey(approvalRequestId));\n } catch (error) {\n throw toCacheError(error, {\n key: this.approvalCapabilityFailuresKey(approvalRequestId),\n operation: 'clearApprovalCapabilityFailures',\n });\n }\n }\n\n async recordApprovalCapabilityFailure(\n approvalRequestId: string,\n ): Promise<RecordApprovalCapabilityFailureResult> {\n const key = this.approvalCapabilityFailuresKey(approvalRequestId);\n const now = new Date();\n const nowMs = now.getTime();\n const existing = await this.readJson<ApprovalCapabilityFailureRecord>(key);\n\n if (existing?.blockedUntil && Date.parse(existing.blockedUntil) > nowMs) {\n return {\n attempts: existing.attempts,\n blocked: true,\n blockedUntil: existing.blockedUntil,\n };\n }\n\n const firstFailedAtMs = existing?.firstFailedAt\n ? Date.parse(existing.firstFailedAt)\n : Number.NaN;\n const withinWindow =\n Number.isFinite(firstFailedAtMs) &&\n nowMs - firstFailedAtMs <= approvalCapabilityFailureWindowMs;\n const attempts = withinWindow && existing ? existing.attempts + 1 : 1;\n const firstFailedAt = withinWindow && existing ? existing.firstFailedAt : now.toISOString();\n const blockedUntil =\n attempts >= approvalCapabilityMaxFailures\n ? new Date(nowMs + approvalCapabilityBlockWindowMs).toISOString()\n : undefined;\n\n await this.writeJson(key, {\n attempts,\n blockedUntil,\n firstFailedAt,\n lastFailedAt: now.toISOString(),\n } satisfies ApprovalCapabilityFailureRecord);\n\n return {\n attempts,\n blocked: blockedUntil !== undefined,\n blockedUntil: blockedUntil ?? null,\n };\n }\n\n async rotateApprovalCapability(approvalRequestId: string): Promise<RelayApprovalRequestRecord> {\n const key = this.approvalKey(approvalRequestId);\n const approval = await this.readJson<RelayApprovalRequestRecord>(key);\n\n if (!approval) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown approval '${approvalRequestId}'`,\n operation: 'rotateApprovalCapability',\n });\n }\n\n if (approval.status !== 'pending') {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key,\n message: `Approval '${approvalRequestId}' is '${approval.status}' and cannot accept a new secure approval link`,\n operation: 'rotateApprovalCapability',\n });\n }\n\n const capabilityToken = createApprovalCapabilityToken();\n const nextRecord: RelayApprovalRequestRecord = {\n ...approval,\n metadata: {\n ...(approval.metadata ?? {}),\n approvalCapabilityHash: approvalCapabilityHash(capabilityToken),\n approvalCapabilityToken: capabilityToken,\n },\n updatedAt: toIsoTimestamp(),\n };\n\n await this.writeJson(key, nextRecord);\n await this.clearApprovalCapabilityFailures(approvalRequestId);\n\n return nextRecord;\n }\n\n async claimEncryptedUpdates(\n input: ClaimEncryptedUpdatesInput,\n ): Promise<RelayEncryptedUpdateRecord[]> {\n const limit = clampLimit(input.limit, 25, 100);\n const leaseSeconds = clampLimit(input.leaseSeconds, 30, 300);\n const now = new Date();\n const nowMs = now.getTime();\n const claimUntil = new Date(nowMs + leaseSeconds * 1000).toISOString();\n const updateIds = await this.client.zrange(\n this.daemonUpdatesKey(input.daemonId),\n 0,\n limit * 4,\n 'REV',\n );\n const claimed: RelayEncryptedUpdateRecord[] = [];\n\n for (const updateId of updateIds) {\n if (claimed.length >= limit) {\n break;\n }\n\n const claimLockKey = this.updateClaimLockKey(updateId);\n let ownsClaimLock = false;\n\n try {\n const reserved = await this.client.set(claimLockKey, claimUntil, 'NX');\n if (reserved !== 'OK') {\n const existingClaimLockUntil = await this.client.get(claimLockKey);\n if (\n existingClaimLockUntil &&\n Number.isFinite(Date.parse(existingClaimLockUntil)) &&\n Date.parse(existingClaimLockUntil) > nowMs\n ) {\n continue;\n }\n\n await this.client.del(claimLockKey);\n const retriedReservation = await this.client.set(claimLockKey, claimUntil, 'NX');\n if (retriedReservation !== 'OK') {\n continue;\n }\n }\n\n ownsClaimLock = true;\n\n const record = await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n if (!record) {\n continue;\n }\n\n if (\n record.status === 'applied' ||\n record.status === 'failed' ||\n record.status === 'rejected'\n ) {\n continue;\n }\n\n if (\n record.status === 'inflight' &&\n record.claimUntil &&\n Date.parse(record.claimUntil) > nowMs\n ) {\n continue;\n }\n\n const nextRecord: RelayEncryptedUpdateRecord = {\n ...record,\n claimToken: randomUUID(),\n claimUntil,\n lastDeliveredAt: now.toISOString(),\n status: 'inflight',\n updatedAt: now.toISOString(),\n };\n await this.writeJson(this.updateKey(updateId), nextRecord);\n claimed.push(nextRecord);\n } finally {\n if (ownsClaimLock) {\n await this.client.del(claimLockKey);\n }\n }\n }\n\n return claimed;\n }\n\n async submitUpdateFeedback(\n input: SubmitUpdateFeedbackInput,\n ): Promise<RelayEncryptedUpdateRecord> {\n const key = this.updateKey(input.updateId);\n const record = await this.readJson<RelayEncryptedUpdateRecord>(key);\n\n if (!record || record.daemonId !== input.daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown update '${input.updateId}' for daemon '${input.daemonId}'`,\n operation: 'submitUpdateFeedback',\n });\n }\n\n if (!record.claimToken || record.claimToken !== input.claimToken) {\n throw new CacheError({\n code: cacheErrorCodes.invalidPayload,\n key,\n message: `Claim token mismatch for update '${input.updateId}'`,\n operation: 'submitUpdateFeedback',\n });\n }\n\n const feedback: RelayUpdateFeedbackRecord = {\n daemonId: input.daemonId,\n details: input.details,\n feedbackAt: toIsoTimestamp(),\n message: input.message,\n status: input.status,\n updateId: input.updateId,\n };\n const nextRecord: RelayEncryptedUpdateRecord = {\n ...record,\n claimToken: undefined,\n claimUntil: undefined,\n feedback,\n status: input.status,\n updatedAt: toIsoTimestamp(),\n };\n\n await this.writeJson(key, nextRecord);\n if (record.targetApprovalRequestId && record.type === 'manual_approval_decision') {\n const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);\n const indexedUpdateId = await this.client.get(activeApprovalKey);\n if (indexedUpdateId === input.updateId) {\n await this.client.del(activeApprovalKey);\n }\n }\n return nextRecord;\n }\n\n async getEncryptedUpdate(updateId: string): Promise<RelayEncryptedUpdateRecord | null> {\n return await this.readJson<RelayEncryptedUpdateRecord>(this.updateKey(updateId));\n }\n\n async removeEncryptedUpdate(daemonId: string, updateId: string): Promise<void> {\n const key = this.updateKey(updateId);\n const record = await this.readJson<RelayEncryptedUpdateRecord>(key);\n if (!record || record.daemonId !== daemonId) {\n throw new CacheError({\n code: cacheErrorCodes.notFound,\n key,\n message: `Unknown update '${updateId}' for daemon '${daemonId}'`,\n operation: 'removeEncryptedUpdate',\n });\n }\n\n await this.client.zrem(this.daemonUpdatesKey(daemonId), updateId);\n await this.client.del(key);\n if (record.targetApprovalRequestId && record.type === 'manual_approval_decision') {\n const activeApprovalKey = this.activeApprovalUpdateKey(record.targetApprovalRequestId);\n const indexedUpdateId = await this.client.get(activeApprovalKey);\n if (indexedUpdateId === updateId) {\n await this.client.del(activeApprovalKey);\n }\n }\n }\n\n private readonly daemonIndexKey = (): string => `${this.namespace}:daemons`;\n private readonly daemonProfileKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:profile`;\n private readonly daemonPoliciesKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:policies`;\n private readonly daemonAgentKeysKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:agent-keys`;\n private readonly daemonApprovalsKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:approvals`;\n private readonly daemonUpdatesKey = (daemonId: string): string =>\n `${this.namespace}:daemon:${daemonId}:updates`;\n private readonly approvalKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}`;\n private readonly approvalCapabilityConsumedKey = (\n approvalRequestId: string,\n capabilityHash: string,\n ): string =>\n `${this.namespace}:approval:${approvalRequestId}:capability:${capabilityHash}:consumed`;\n private readonly approvalCapabilityFailuresKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}:capability-failures`;\n private readonly activeApprovalUpdateKey = (approvalRequestId: string): string =>\n `${this.namespace}:approval:${approvalRequestId}:active-update`;\n private readonly updateClaimLockKey = (updateId: string): string =>\n `${this.namespace}:update:${updateId}:claim-lock`;\n private readonly updateKey = (updateId: string): string => `${this.namespace}:update:${updateId}`;\n\n private async readJson<T>(key: string): Promise<T | null> {\n try {\n const payload = await this.client.get(key);\n if (payload === null) {\n return null;\n }\n\n return JSON.parse(payload) as T;\n } catch (error) {\n throw toCacheError(error, { key, operation: 'readJson' });\n }\n }\n\n private async writeJson(key: string, value: unknown): Promise<void> {\n try {\n await this.client.set(key, JSON.stringify(value));\n } catch (error) {\n throw toCacheError(error, { key, operation: 'writeJson' });\n }\n }\n}\n\nexport const createRelayCacheService = (options: { client?: Redis; namespace?: string } = {}) => {\n return new RelayCacheService(options);\n};\n"]}