@mnemom/mnemom 0.17.34 → 0.17.36

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 (177) hide show
  1. package/dist/commands/agent-config.js +3 -0
  2. package/dist/commands/agent-goal.d.ts +8 -3
  3. package/dist/commands/agent-goal.js +22 -8
  4. package/dist/commands/agent-run.d.ts +14 -2
  5. package/dist/commands/agent-run.js +28 -7
  6. package/dist/commands/agent-share-render.js +1 -1
  7. package/dist/commands/agent-slack.js +32 -0
  8. package/dist/commands/durable-inbox.d.ts +51 -0
  9. package/dist/commands/durable-inbox.js +134 -0
  10. package/dist/commands/durable-questions.d.ts +64 -0
  11. package/dist/commands/durable-questions.js +161 -0
  12. package/dist/commands/scan.d.ts +13 -3
  13. package/dist/commands/scan.js +48 -10
  14. package/dist/durable/seat-proxy.d.ts +12 -1
  15. package/dist/durable/seat-proxy.js +55 -9
  16. package/dist/durable/seat-socket.d.ts +20 -2
  17. package/dist/durable/seat-socket.js +58 -0
  18. package/dist/durable/turn-chain.d.ts +8 -0
  19. package/dist/durable/turn-chain.js +25 -4
  20. package/dist/host/client.d.ts +41 -0
  21. package/dist/host/client.js +7 -0
  22. package/dist/host/control.d.ts +7 -0
  23. package/dist/host/control.js +15 -0
  24. package/dist/host/daemon.d.ts +20 -0
  25. package/dist/host/daemon.js +74 -1
  26. package/dist/host/inbox-delivery.d.ts +174 -0
  27. package/dist/host/inbox-delivery.js +453 -0
  28. package/dist/host/run.d.ts +18 -0
  29. package/dist/host/run.js +61 -0
  30. package/dist/host/s1-names.d.ts +2 -0
  31. package/dist/host/s1-names.js +2 -0
  32. package/dist/host/tmux.d.ts +7 -0
  33. package/dist/host/tmux.js +19 -0
  34. package/dist/index.js +62 -3
  35. package/dist/lib/api.d.ts +5 -0
  36. package/dist/lib/api.js +16 -0
  37. package/dist/lib/config.d.ts +7 -0
  38. package/dist/lib/config.js +21 -0
  39. package/dist/lib/durable/api.d.ts +11 -0
  40. package/dist/lib/durable/api.js +28 -3
  41. package/dist/lib/durable/gate.d.ts +1 -1
  42. package/dist/lib/durable/gate.js +8 -1
  43. package/dist/lib/durable/live.d.ts +5 -0
  44. package/dist/lib/durable/live.js +19 -0
  45. package/dist/lib/experimental.js +3 -0
  46. package/dist/lib/goal-api.d.ts +11 -4
  47. package/dist/lib/goal-api.js +4 -3
  48. package/dist/lib/launcher-config.d.ts +5 -0
  49. package/dist/lib/launcher-config.js +17 -2
  50. package/dist/lib/prompt.d.ts +4 -2
  51. package/dist/lib/prompt.js +15 -5
  52. package/dist/lib/runner/job.d.ts +2 -2
  53. package/dist/lib/runner/job.js +16 -3
  54. package/dist/lib/runner/pr-body.d.ts +17 -0
  55. package/dist/lib/runner/pr-body.js +42 -0
  56. package/dist/lib/runner/proxy.d.ts +18 -3
  57. package/dist/lib/runner/proxy.js +24 -5
  58. package/dist/lib/scan/pr-source.js +2 -0
  59. package/dist/lib/scan/record.d.ts +60 -9
  60. package/dist/lib/scan/record.js +157 -39
  61. package/dist/lib/scan/repo-picker.d.ts +32 -0
  62. package/dist/lib/scan/repo-picker.js +80 -0
  63. package/dist/lib/scan/report.d.ts +6 -8
  64. package/dist/lib/scan/report.js +21 -7
  65. package/dist/lib/scan/share.d.ts +5 -1
  66. package/dist/lib/scan/share.js +15 -5
  67. package/dist/lib/slack-durable.d.ts +123 -0
  68. package/dist/lib/slack-durable.js +237 -0
  69. package/dist/lib/slack-router.d.ts +7 -0
  70. package/dist/lib/slack-router.js +50 -4
  71. package/dist/vendor/durable-contract/index.js +992 -34
  72. package/dist/vendor/shared/durable-contract/admission.d.ts +81 -2
  73. package/dist/vendor/shared/durable-contract/admission.js +319 -2
  74. package/dist/vendor/shared/durable-contract/cli-surface.js +21 -0
  75. package/dist/vendor/shared/durable-contract/codes.d.ts +60 -0
  76. package/dist/vendor/shared/durable-contract/codes.js +22 -0
  77. package/dist/vendor/shared/durable-contract/events.d.ts +4 -2
  78. package/dist/vendor/shared/durable-contract/events.js +55 -1
  79. package/dist/vendor/shared/durable-contract/freeze-record.d.ts +14 -0
  80. package/dist/vendor/shared/durable-contract/inbox.d.ts +299 -0
  81. package/dist/vendor/shared/durable-contract/inbox.js +462 -0
  82. package/dist/vendor/shared/durable-contract/index.d.ts +2 -0
  83. package/dist/vendor/shared/durable-contract/index.js +2 -0
  84. package/dist/vendor/shared/durable-contract/question.d.ts +218 -0
  85. package/dist/vendor/shared/durable-contract/question.js +373 -0
  86. package/dist/vendor/shared/durable-contract/routes.d.ts +44 -2
  87. package/dist/vendor/shared/durable-contract/routes.js +43 -1
  88. package/dist/vendor/shared/durable-contract/turn.d.ts +15 -1
  89. package/dist/vendor/shared/durable-contract/turn.js +33 -1
  90. package/dist/vendor/shared/durable-contract/vectors.d.ts +45 -0
  91. package/dist/vendor/shared/durable-contract/vectors.js +49 -0
  92. package/node_modules/@mnemom/aip-verifier/LICENSE +190 -0
  93. package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.cts +478 -0
  94. package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.ts +478 -0
  95. package/node_modules/@mnemom/aip-verifier/dist/index.cjs +971 -0
  96. package/node_modules/@mnemom/aip-verifier/dist/index.cjs.map +1 -0
  97. package/node_modules/@mnemom/aip-verifier/dist/index.d.cts +330 -0
  98. package/node_modules/@mnemom/aip-verifier/dist/index.d.ts +330 -0
  99. package/node_modules/@mnemom/aip-verifier/dist/index.js +933 -0
  100. package/node_modules/@mnemom/aip-verifier/dist/index.js.map +1 -0
  101. package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs +879 -0
  102. package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs.map +1 -0
  103. package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.cts +96 -0
  104. package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.ts +96 -0
  105. package/node_modules/@mnemom/aip-verifier/dist/render-lines.js +875 -0
  106. package/node_modules/@mnemom/aip-verifier/dist/render-lines.js.map +1 -0
  107. package/node_modules/@mnemom/aip-verifier/package.json +86 -0
  108. package/node_modules/@noble/ed25519/LICENSE +21 -0
  109. package/node_modules/@noble/ed25519/README.md +371 -0
  110. package/node_modules/@noble/ed25519/index.d.ts +391 -0
  111. package/node_modules/@noble/ed25519/index.js +933 -0
  112. package/node_modules/@noble/ed25519/index.ts +1134 -0
  113. package/node_modules/@noble/ed25519/package.json +54 -0
  114. package/node_modules/@noble/hashes/LICENSE +21 -0
  115. package/node_modules/@noble/hashes/README.md +478 -0
  116. package/node_modules/@noble/hashes/_blake.d.ts +20 -0
  117. package/node_modules/@noble/hashes/_blake.js +59 -0
  118. package/node_modules/@noble/hashes/_md.d.ts +95 -0
  119. package/node_modules/@noble/hashes/_md.js +209 -0
  120. package/node_modules/@noble/hashes/_u64.d.ts +36 -0
  121. package/node_modules/@noble/hashes/_u64.js +77 -0
  122. package/node_modules/@noble/hashes/argon2.d.ts +148 -0
  123. package/node_modules/@noble/hashes/argon2.js +577 -0
  124. package/node_modules/@noble/hashes/blake1.d.ts +162 -0
  125. package/node_modules/@noble/hashes/blake1.js +630 -0
  126. package/node_modules/@noble/hashes/blake2.d.ts +203 -0
  127. package/node_modules/@noble/hashes/blake2.js +573 -0
  128. package/node_modules/@noble/hashes/blake3.d.ts +68 -0
  129. package/node_modules/@noble/hashes/blake3.js +310 -0
  130. package/node_modules/@noble/hashes/eskdf.d.ts +91 -0
  131. package/node_modules/@noble/hashes/eskdf.js +225 -0
  132. package/node_modules/@noble/hashes/hkdf.d.ts +70 -0
  133. package/node_modules/@noble/hashes/hkdf.js +152 -0
  134. package/node_modules/@noble/hashes/hmac.d.ts +50 -0
  135. package/node_modules/@noble/hashes/hmac.js +93 -0
  136. package/node_modules/@noble/hashes/index.d.ts +1 -0
  137. package/node_modules/@noble/hashes/index.js +34 -0
  138. package/node_modules/@noble/hashes/legacy.d.ts +107 -0
  139. package/node_modules/@noble/hashes/legacy.js +348 -0
  140. package/node_modules/@noble/hashes/package.json +91 -0
  141. package/node_modules/@noble/hashes/pbkdf2.d.ts +66 -0
  142. package/node_modules/@noble/hashes/pbkdf2.js +176 -0
  143. package/node_modules/@noble/hashes/scrypt.d.ts +98 -0
  144. package/node_modules/@noble/hashes/scrypt.js +304 -0
  145. package/node_modules/@noble/hashes/sha2.d.ts +176 -0
  146. package/node_modules/@noble/hashes/sha2.js +446 -0
  147. package/node_modules/@noble/hashes/sha3-addons.d.ts +533 -0
  148. package/node_modules/@noble/hashes/sha3-addons.js +973 -0
  149. package/node_modules/@noble/hashes/sha3.d.ts +208 -0
  150. package/node_modules/@noble/hashes/sha3.js +475 -0
  151. package/node_modules/@noble/hashes/src/_blake.ts +67 -0
  152. package/node_modules/@noble/hashes/src/_md.ts +227 -0
  153. package/node_modules/@noble/hashes/src/_u64.ts +111 -0
  154. package/node_modules/@noble/hashes/src/argon2.ts +693 -0
  155. package/node_modules/@noble/hashes/src/blake1.ts +677 -0
  156. package/node_modules/@noble/hashes/src/blake2.ts +648 -0
  157. package/node_modules/@noble/hashes/src/blake3.ts +344 -0
  158. package/node_modules/@noble/hashes/src/eskdf.ts +265 -0
  159. package/node_modules/@noble/hashes/src/hkdf.ts +161 -0
  160. package/node_modules/@noble/hashes/src/hmac.ts +130 -0
  161. package/node_modules/@noble/hashes/src/index.ts +33 -0
  162. package/node_modules/@noble/hashes/src/legacy.ts +361 -0
  163. package/node_modules/@noble/hashes/src/pbkdf2.ts +219 -0
  164. package/node_modules/@noble/hashes/src/scrypt.ts +375 -0
  165. package/node_modules/@noble/hashes/src/sha2.ts +486 -0
  166. package/node_modules/@noble/hashes/src/sha3-addons.ts +1157 -0
  167. package/node_modules/@noble/hashes/src/sha3.ts +525 -0
  168. package/node_modules/@noble/hashes/src/utils.ts +978 -0
  169. package/node_modules/@noble/hashes/src/webcrypto.ts +283 -0
  170. package/node_modules/@noble/hashes/utils.d.ts +560 -0
  171. package/node_modules/@noble/hashes/utils.js +687 -0
  172. package/node_modules/@noble/hashes/webcrypto.d.ts +121 -0
  173. package/node_modules/@noble/hashes/webcrypto.js +228 -0
  174. package/package.json +5 -2
  175. package/plugins/integrity-manifest.json +6 -2
  176. package/plugins/mnemom/hooks/hooks.json +12 -0
  177. package/plugins/mnemom/hooks/seat-ask.mjs +221 -0
@@ -0,0 +1,121 @@
1
+ import { type Pbkdf2Opt } from './pbkdf2.ts';
2
+ import { type KDFInput, type TArg, type TRet } from './utils.ts';
3
+ /** Callable WebCrypto hash function descriptor. */
4
+ export type WebHash = {
5
+ /**
6
+ * Hashes one message with the selected WebCrypto digest.
7
+ * @param msg - message bytes to hash
8
+ * @returns Promise resolving to digest bytes.
9
+ */
10
+ (msg: TArg<Uint8Array>): Promise<TRet<Uint8Array>>;
11
+ /** WebCrypto algorithm name passed to `crypto.subtle`. */
12
+ webCryptoName: string;
13
+ /** Digest size in bytes. */
14
+ outputLen: number;
15
+ /** Input block size in bytes. */
16
+ blockLen: number;
17
+ };
18
+ /** WebCrypto SHA1 (RFC 3174) legacy hash function. It was cryptographically broken. */
19
+ /**
20
+ * WebCrypto SHA2-256 hash function from RFC 6234.
21
+ * @param msg - message bytes to hash
22
+ * @returns Promise resolving to digest bytes.
23
+ * @example
24
+ * Hash a message with WebCrypto SHA2-256.
25
+ * ```ts
26
+ * await sha256(new Uint8Array([97, 98, 99]));
27
+ * ```
28
+ */
29
+ export declare const sha256: TRet<WebHash>;
30
+ /**
31
+ * WebCrypto SHA2-384 hash function from RFC 6234.
32
+ * @param msg - message bytes to hash
33
+ * @returns Promise resolving to digest bytes.
34
+ * @example
35
+ * Hash a message with WebCrypto SHA2-384.
36
+ * ```ts
37
+ * await sha384(new Uint8Array([97, 98, 99]));
38
+ * ```
39
+ */
40
+ export declare const sha384: TRet<WebHash>;
41
+ /**
42
+ * WebCrypto SHA2-512 hash function from RFC 6234.
43
+ * @param msg - message bytes to hash
44
+ * @returns Promise resolving to digest bytes.
45
+ * @example
46
+ * Hash a message with WebCrypto SHA2-512.
47
+ * ```ts
48
+ * await sha512(new Uint8Array([97, 98, 99]));
49
+ * ```
50
+ */
51
+ export declare const sha512: TRet<WebHash>;
52
+ /** WebCrypto HMAC function type. See {@link hmac} for usage. */
53
+ type WebHmacFn = {
54
+ (hash: TArg<WebHash>, key: TArg<Uint8Array>, message: TArg<Uint8Array>): Promise<TRet<Uint8Array>>;
55
+ create(hash: TArg<WebHash>, key: TArg<Uint8Array>): any;
56
+ };
57
+ /**
58
+ * WebCrypto HMAC: RFC2104 message authentication code.
59
+ * @param hash - function that would be used e.g. sha256. Webcrypto version.
60
+ * @param key - authentication key bytes
61
+ * @param message - message bytes to authenticate
62
+ * @returns Promise resolving to authentication tag bytes.
63
+ * `.create()` exists only to mirror the synchronous API surface
64
+ * and always throws `not implemented`.
65
+ * @example
66
+ * Compute an RFC 2104 HMAC with WebCrypto.
67
+ * ```ts
68
+ * import { hmac, sha256 } from '@noble/hashes/webcrypto.js';
69
+ * const key = new Uint8Array([1, 2, 3]);
70
+ * const message = new Uint8Array([4, 5, 6]);
71
+ * const mac = await hmac(sha256, key, message);
72
+ * ```
73
+ */
74
+ export declare const hmac: TRet<WebHmacFn>;
75
+ /**
76
+ * WebCrypto HKDF (RFC 5869): derive keys from an initial input.
77
+ * Combines hkdf_extract + hkdf_expand in one step
78
+ * @param hash - hash function that would be used (e.g. sha256). Webcrypto version.
79
+ * @param ikm - input keying material, the initial key
80
+ * @param salt - optional salt value (a non-secret random value)
81
+ * @param info - optional context and application specific information bytes
82
+ * @param length - length of output keying material in bytes.
83
+ * RFC 5869 §2.3 allows `0..255*HashLen`, so `0` requests an empty OKM.
84
+ * @returns Promise resolving to derived key bytes.
85
+ * The RFC `L <= 255 * HashLen` bound is enforced before calling WebCrypto.
86
+ * @throws If the current runtime does not provide `crypto.subtle`. {@link Error}
87
+ * @example
88
+ * WebCrypto HKDF (RFC 5869): derive keys from an initial input.
89
+ * ```ts
90
+ * import { hkdf, sha256 } from '@noble/hashes/webcrypto.js';
91
+ * import { randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';
92
+ * const inputKey = randomBytes(32);
93
+ * const salt = randomBytes(32);
94
+ * const info = utf8ToBytes('application-key');
95
+ * const okm = await hkdf(sha256, inputKey, salt, info, 32);
96
+ * ```
97
+ */
98
+ export declare function hkdf(hash: TArg<WebHash>, ikm: TArg<Uint8Array>, salt: TArg<Uint8Array | undefined>, info: TArg<Uint8Array | undefined>, length: number): Promise<TRet<Uint8Array>>;
99
+ /**
100
+ * WebCrypto PBKDF2-HMAC: RFC 8018 key derivation function.
101
+ * @param hash - hash function that would be used e.g. sha256. Webcrypto version.
102
+ * @param password - password from which a derived key is generated; string
103
+ * inputs are normalized through `kdfInputToBytes()`, i.e. UTF-8
104
+ * @param salt - cryptographic salt; string inputs are normalized through
105
+ * `kdfInputToBytes()`, i.e. UTF-8
106
+ * @param opts - PBKDF2 work factor and output settings. `dkLen`, if provided,
107
+ * must be `>= 1` per RFC 8018 §5.2. See {@link Pbkdf2Opt}.
108
+ * @returns Promise resolving to derived key bytes.
109
+ * Positive-iteration enforcement is currently delegated to backend
110
+ * `deriveBits()` rejection (for example `c = 0`), not a dedicated
111
+ * library-side guard. Values above the signed 32-bit backend range are rejected locally.
112
+ * @throws If the current runtime does not provide `crypto.subtle`. {@link Error}
113
+ * @example
114
+ * WebCrypto PBKDF2-HMAC: RFC 2898 key derivation function.
115
+ * ```ts
116
+ * import { pbkdf2, sha256 } from '@noble/hashes/webcrypto.js';
117
+ * const key = await pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) });
118
+ * ```
119
+ */
120
+ export declare function pbkdf2(hash: TArg<WebHash>, password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: Pbkdf2Opt): Promise<TRet<Uint8Array>>;
121
+ export {};
@@ -0,0 +1,228 @@
1
+ import {} from "./pbkdf2.js";
2
+ import { abytes, ahash, anumber, checkOpts, clean, copyBytes, kdfInputToBytes, } from "./utils.js";
3
+ function _subtle() {
4
+ const cr = typeof globalThis === 'object' ? globalThis.crypto : null;
5
+ const sb = cr?.subtle;
6
+ if (typeof sb === 'object' && sb != null)
7
+ return sb;
8
+ throw new Error('crypto.subtle must be defined');
9
+ }
10
+ function createWebHash(name, blockLen, outputLen) {
11
+ const hashC = async (msg) => {
12
+ abytes(msg);
13
+ const crypto = _subtle();
14
+ return new Uint8Array(await crypto.digest(name, msg));
15
+ };
16
+ hashC.webCryptoName = name; // make sure it won't interfere with function name
17
+ hashC.outputLen = outputLen;
18
+ hashC.blockLen = blockLen;
19
+ hashC.create = () => {
20
+ // Present only so this async wrapper satisfies the shared
21
+ // hash-wrapper shape checked by `ahashWeb()`.
22
+ throw new Error('not implemented');
23
+ };
24
+ // Later WebCrypto HMAC/HKDF/PBKDF2 calls read descriptor metadata directly, so freezing prevents
25
+ // callers from retargeting a `sha256` wrapper into a different backend digest by mutation.
26
+ return Object.freeze(hashC);
27
+ }
28
+ function ahashWeb(hash) {
29
+ ahash(hash);
30
+ const name = hash.webCryptoName;
31
+ if (typeof name !== 'string')
32
+ throw new Error('non-web hash');
33
+ return name;
34
+ }
35
+ /** WebCrypto SHA1 (RFC 3174) legacy hash function. It was cryptographically broken. */
36
+ // export const sha1: WebHash = createHash('SHA-1', 64, 20);
37
+ /**
38
+ * WebCrypto SHA2-256 hash function from RFC 6234.
39
+ * @param msg - message bytes to hash
40
+ * @returns Promise resolving to digest bytes.
41
+ * @example
42
+ * Hash a message with WebCrypto SHA2-256.
43
+ * ```ts
44
+ * await sha256(new Uint8Array([97, 98, 99]));
45
+ * ```
46
+ */
47
+ export const sha256 = /* @__PURE__ */ createWebHash('SHA-256', 64, 32);
48
+ /**
49
+ * WebCrypto SHA2-384 hash function from RFC 6234.
50
+ * @param msg - message bytes to hash
51
+ * @returns Promise resolving to digest bytes.
52
+ * @example
53
+ * Hash a message with WebCrypto SHA2-384.
54
+ * ```ts
55
+ * await sha384(new Uint8Array([97, 98, 99]));
56
+ * ```
57
+ */
58
+ export const sha384 = /* @__PURE__ */ createWebHash('SHA-384', 128, 48);
59
+ /**
60
+ * WebCrypto SHA2-512 hash function from RFC 6234.
61
+ * @param msg - message bytes to hash
62
+ * @returns Promise resolving to digest bytes.
63
+ * @example
64
+ * Hash a message with WebCrypto SHA2-512.
65
+ * ```ts
66
+ * await sha512(new Uint8Array([97, 98, 99]));
67
+ * ```
68
+ */
69
+ export const sha512 = /* @__PURE__ */ createWebHash('SHA-512', 128, 64);
70
+ /**
71
+ * WebCrypto HMAC: RFC2104 message authentication code.
72
+ * @param hash - function that would be used e.g. sha256. Webcrypto version.
73
+ * @param key - authentication key bytes
74
+ * @param message - message bytes to authenticate
75
+ * @returns Promise resolving to authentication tag bytes.
76
+ * `.create()` exists only to mirror the synchronous API surface
77
+ * and always throws `not implemented`.
78
+ * @example
79
+ * Compute an RFC 2104 HMAC with WebCrypto.
80
+ * ```ts
81
+ * import { hmac, sha256 } from '@noble/hashes/webcrypto.js';
82
+ * const key = new Uint8Array([1, 2, 3]);
83
+ * const message = new Uint8Array([4, 5, 6]);
84
+ * const mac = await hmac(sha256, key, message);
85
+ * ```
86
+ */
87
+ export const hmac = /* @__PURE__ */ (() => {
88
+ const hmac_ = async (hash, key, message) => {
89
+ const crypto = _subtle();
90
+ abytes(key, undefined, 'key');
91
+ abytes(message, undefined, 'message');
92
+ const hashName = ahashWeb(hash);
93
+ // importKey() snapshots key synchronously, but message is not passed to sign() until after
94
+ // importKey() resolves. Keep the wrapper's inputs stable across that await.
95
+ const _message = copyBytes(message);
96
+ try {
97
+ // WebCrypto keys can't be zeroized
98
+ // prettier-ignore
99
+ const wkey = await crypto.importKey('raw', key, { name: 'HMAC', hash: hashName }, false, ['sign']);
100
+ return new Uint8Array(await crypto.sign('HMAC', wkey, _message));
101
+ }
102
+ finally {
103
+ clean(_message);
104
+ }
105
+ };
106
+ hmac_.create = (_hash, _key) => {
107
+ throw new Error('not implemented');
108
+ };
109
+ return hmac_;
110
+ })();
111
+ /**
112
+ * WebCrypto HKDF (RFC 5869): derive keys from an initial input.
113
+ * Combines hkdf_extract + hkdf_expand in one step
114
+ * @param hash - hash function that would be used (e.g. sha256). Webcrypto version.
115
+ * @param ikm - input keying material, the initial key
116
+ * @param salt - optional salt value (a non-secret random value)
117
+ * @param info - optional context and application specific information bytes
118
+ * @param length - length of output keying material in bytes.
119
+ * RFC 5869 §2.3 allows `0..255*HashLen`, so `0` requests an empty OKM.
120
+ * @returns Promise resolving to derived key bytes.
121
+ * The RFC `L <= 255 * HashLen` bound is enforced before calling WebCrypto.
122
+ * @throws If the current runtime does not provide `crypto.subtle`. {@link Error}
123
+ * @example
124
+ * WebCrypto HKDF (RFC 5869): derive keys from an initial input.
125
+ * ```ts
126
+ * import { hkdf, sha256 } from '@noble/hashes/webcrypto.js';
127
+ * import { randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';
128
+ * const inputKey = randomBytes(32);
129
+ * const salt = randomBytes(32);
130
+ * const info = utf8ToBytes('application-key');
131
+ * const okm = await hkdf(sha256, inputKey, salt, info, 32);
132
+ * ```
133
+ */
134
+ export async function hkdf(hash, ikm, salt, info, length) {
135
+ const crypto = _subtle();
136
+ const hashName = ahashWeb(hash);
137
+ const hashOutputLen = hash.outputLen;
138
+ abytes(ikm, undefined, 'ikm');
139
+ anumber(length, 'length');
140
+ if (length > 255 * hashOutputLen)
141
+ throw new Error('Length must be <= 255*HashLen');
142
+ if (salt !== undefined)
143
+ abytes(salt, undefined, 'salt');
144
+ if (info !== undefined)
145
+ abytes(info, undefined, 'info');
146
+ // salt and info reach deriveBits() only after importKey() resolves, so snapshot them now.
147
+ const _salt = salt === undefined ? new Uint8Array(0) : copyBytes(salt);
148
+ const _info = info === undefined ? new Uint8Array(0) : copyBytes(info);
149
+ try {
150
+ const wkey = await crypto.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
151
+ const opts = { name: 'HKDF', hash: hashName, salt: _salt, info: _info };
152
+ const out = new Uint8Array(await crypto.deriveBits(opts, wkey, 8 * length));
153
+ if (out.length !== length) {
154
+ clean(out);
155
+ throw new Error('WebCrypto returned an invalid derived key length');
156
+ }
157
+ return out;
158
+ }
159
+ finally {
160
+ clean(_salt, _info);
161
+ }
162
+ }
163
+ /**
164
+ * WebCrypto PBKDF2-HMAC: RFC 8018 key derivation function.
165
+ * @param hash - hash function that would be used e.g. sha256. Webcrypto version.
166
+ * @param password - password from which a derived key is generated; string
167
+ * inputs are normalized through `kdfInputToBytes()`, i.e. UTF-8
168
+ * @param salt - cryptographic salt; string inputs are normalized through
169
+ * `kdfInputToBytes()`, i.e. UTF-8
170
+ * @param opts - PBKDF2 work factor and output settings. `dkLen`, if provided,
171
+ * must be `>= 1` per RFC 8018 §5.2. See {@link Pbkdf2Opt}.
172
+ * @returns Promise resolving to derived key bytes.
173
+ * Positive-iteration enforcement is currently delegated to backend
174
+ * `deriveBits()` rejection (for example `c = 0`), not a dedicated
175
+ * library-side guard. Values above the signed 32-bit backend range are rejected locally.
176
+ * @throws If the current runtime does not provide `crypto.subtle`. {@link Error}
177
+ * @example
178
+ * WebCrypto PBKDF2-HMAC: RFC 2898 key derivation function.
179
+ * ```ts
180
+ * import { pbkdf2, sha256 } from '@noble/hashes/webcrypto.js';
181
+ * const key = await pbkdf2(sha256, 'password', 'salt', { dkLen: 32, c: Math.pow(2, 18) });
182
+ * ```
183
+ */
184
+ export async function pbkdf2(hash, password, salt, opts) {
185
+ const crypto = _subtle();
186
+ const hashName = ahashWeb(hash);
187
+ const _opts = checkOpts({ dkLen: 32 }, opts);
188
+ const { c, dkLen } = _opts;
189
+ anumber(c, 'c');
190
+ anumber(dkLen, 'dkLen');
191
+ // Node's native WebCrypto PBKDF2 binding accepts only a signed 32-bit iteration count and aborts
192
+ // the process on larger values instead of returning a rejected promise.
193
+ if (c > 0x7fffffff)
194
+ throw new Error('"c" exceeds WebCrypto backend limit');
195
+ // RFC 8018 §5.2 defines dkLen as a positive integer.
196
+ if (dkLen < 1)
197
+ throw new Error('"dkLen" must be >= 1');
198
+ // SubtleCrypto.deriveBits() accepts an unsigned-long bit count. Byte lengths at or above
199
+ // 2^29 would wrap after multiplication by eight instead of requesting the intended length.
200
+ if (dkLen >= 2 ** 29)
201
+ throw new Error('derived key too long');
202
+ const _password = kdfInputToBytes(password, 'password');
203
+ try {
204
+ const saltBytes = kdfInputToBytes(salt, 'salt');
205
+ // String conversion already returns an owned array. Caller-owned byte salts need a snapshot
206
+ // because deriveBits() does not receive them until after importKey() resolves.
207
+ const _salt = typeof salt === 'string' ? saltBytes : copyBytes(saltBytes);
208
+ try {
209
+ const key = await crypto.importKey('raw', _password, 'PBKDF2', false, [
210
+ 'deriveBits',
211
+ ]);
212
+ const deriveOpts = { name: 'PBKDF2', salt: _salt, iterations: c, hash: hashName };
213
+ const out = new Uint8Array(await crypto.deriveBits(deriveOpts, key, 8 * dkLen));
214
+ if (out.length !== dkLen) {
215
+ clean(out);
216
+ throw new Error('WebCrypto returned an invalid derived key length');
217
+ }
218
+ return out;
219
+ }
220
+ finally {
221
+ clean(_salt);
222
+ }
223
+ }
224
+ finally {
225
+ if (typeof password === 'string')
226
+ clean(_password);
227
+ }
228
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.34",
3
+ "version": "0.17.36",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "test": "vitest"
19
19
  },
20
20
  "dependencies": {
21
- "@mnemom/aip-verifier": "https://github.com/mnemom/aip/releases/download/aip-verifier-v0.6.0/mnemom-aip-verifier-0.6.0.tgz",
21
+ "@mnemom/aip-verifier": "file:vendor/mnemom-aip-verifier-0.6.0.tgz",
22
22
  "@mnemom/policy-engine": "^0.3.0",
23
23
  "chalk": "^5.3.0",
24
24
  "commander": "^12.0.0",
@@ -34,6 +34,9 @@
34
34
  "typescript": "^6.0.3",
35
35
  "vitest": "^4.1.12"
36
36
  },
37
+ "bundleDependencies": [
38
+ "@mnemom/aip-verifier"
39
+ ],
37
40
  "files": [
38
41
  "dist",
39
42
  "!dist/__tests__",
@@ -12,12 +12,16 @@
12
12
  },
13
13
  {
14
14
  "path": "hooks/hooks.json",
15
- "sha256": "c9fe91672a14aa8fc74d8fe523e412b7fdbe16a281cdc1d3e51b162fef3a7ed3"
15
+ "sha256": "fe45556f57011f0f1805fc56d2c86d5e2462cd1ba998ef7b54778b26556cfc4e"
16
16
  },
17
17
  {
18
18
  "path": "hooks/register.tsx",
19
19
  "sha256": "98da58c198d4015a5d454110249b1bc5c9fe0a1e2271c310f46c1185935d4436"
20
20
  },
21
+ {
22
+ "path": "hooks/seat-ask.mjs",
23
+ "sha256": "dcd0b419fc2a6fb1275aff34abb3d107311cecb4c8d7eac3c49b0c8a600db4a3"
24
+ },
21
25
  {
22
26
  "path": "hooks/seat-hello.mjs",
23
27
  "sha256": "4d6cd4e2fed2dc701bc6990ed5c43b37fb4aaf202b25012c3a052845d624daee"
@@ -31,5 +35,5 @@
31
35
  "sha256": "b877ea4d4d6fe11ae2eb10e035b5f017b225905f7f9ec015b3c3010609cad27b"
32
36
  }
33
37
  ],
34
- "dir_hash": "ad16249ecbdd1e2fc6913529f9aefb357d516da7230f26a4e1c2c0ccf09735c5"
38
+ "dir_hash": "845a702134fb66daa9cbe8cc9a51f6a06ce08a08392ed5f0cf68ac4af7625398"
35
39
  }
@@ -12,6 +12,18 @@
12
12
  }
13
13
  ]
14
14
  }
15
+ ],
16
+ "PreToolUse": [
17
+ {
18
+ "matcher": "AskUserQuestion",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "[ -z \"$MNEMOM_SEAT_SOCKET\" ] || node \"${CLAUDE_PLUGIN_ROOT}/hooks/seat-ask.mjs\"",
23
+ "timeout": 600
24
+ }
25
+ ]
26
+ }
15
27
  ]
16
28
  }
17
29
  }
@@ -0,0 +1,221 @@
1
+ // The seat question hook (durable S1 amendment 61, signed off by Wassim 2026-11-02): a COMMAND hook on
2
+ // PreToolUse for AskUserQuestion. In a durable seat nobody watches the screen, so a drawn dialog
3
+ // would wait forever. This hook makes the question a journaled durable fact instead:
4
+ //
5
+ // 1. ask: {op:"question_ask", session_id, spawn_id, epoch, tool_use_id, questions} over the
6
+ // per-seat socket. The seat proxy relays it to the host daemon, which alone holds the
7
+ // executor key and calls exec_question; the api journals question.asked.
8
+ // 2. poll: {op:"question_poll", ..., approval_id} every POLL_INTERVAL_MS until WAIT_MS ends.
9
+ // answered -> stdout permissionDecision "allow" with updatedInput {questions, answers}:
10
+ // the dialog never draws and the tool result carries the owner's answer.
11
+ // dismissed -> "deny" with the dismissed reason (the session is not suspended).
12
+ // expired -> "deny" with the expired reason.
13
+ // created:false is this same call asked again (the daemon passes on only an answer whose
14
+ // question_sha is this ask's), so it is polled like a new one. A 409 durable_question_busy means
15
+ // an earlier question is still with the owner: this one was not recorded, the hook denies with
16
+ // PENDING_REASON at once and releases nothing (the earlier question belongs to its own wait).
17
+ // 3. release: when the window ends first, {op:"question_release", ..., approval_id}, then "deny"
18
+ // with the hand-off reason: the model ends its turn and the owner's answer arrives
19
+ // later as an amendment 60 inbox message, which the daemon delivers unattended.
20
+ //
21
+ // Without MNEMOM_SEAT_SOCKET (every launch that is not a durable seat) it does nothing. If the ask
22
+ // cannot be made (no socket, the daemon or api refuses), it writes nothing and exits 0, so Claude
23
+ // Code shows its own dialog as it always did: this hook only ever removes the screen dependency, it
24
+ // never blocks a question. It never sees a key: the ids are non-secret.
25
+ //
26
+ // The reason strings are S1 question.ts's QUESTION_HANDOFF_REASON and QUESTION_DISMISSED_REASON
27
+ // (a plugin file cannot import the contract; a CLI test pins them equal).
28
+ import net from "node:net";
29
+ import { createHash } from "node:crypto";
30
+ import { pathToFileURL } from "node:url";
31
+
32
+ export const SEAT_SOCKET_ENV = "MNEMOM_SEAT_SOCKET";
33
+ export const SEAT_SESSION_ENV = "MNEMOM_SEAT_SESSION_ID";
34
+ export const SEAT_SPAWN_ENV = "MNEMOM_SEAT_SPAWN_ID";
35
+ export const SEAT_EPOCH_ENV = "MNEMOM_SEAT_EPOCH";
36
+
37
+ /** hooks.json gives this hook 600 s; it stops waiting well before, to release and answer cleanly. */
38
+ export const WAIT_MS = 540_000;
39
+ export const POLL_INTERVAL_MS = 3_000;
40
+ /** One socket round trip (the seat proxy relays it to the daemon and the daemon to the api). */
41
+ export const OP_TIMEOUT_MS = 20_000;
42
+ export const MAX_ANSWER_BYTES = 256 * 1024;
43
+
44
+ export const HANDOFF_REASON =
45
+ "This question is now with your owner. End your turn now without guessing; the owner's answer will arrive as your next message.";
46
+ export const DISMISSED_REASON =
47
+ "Your owner dismissed this question without answering. Decide without it, or ask differently.";
48
+ /**
49
+ * The session already has a different question with the owner (one per session: asking another is
50
+ * 409 durable_question_busy). This call's question was not recorded; the model must not take the
51
+ * earlier question's answer for this one.
52
+ */
53
+ export const PENDING_REASON =
54
+ "This question was not sent: an earlier question of yours is still waiting for your owner. End your turn now without guessing; the owner's answer to that earlier question will arrive as your next message. Ask this one again after that if it still matters.";
55
+ export const EXPIRED_REASON =
56
+ "Your owner did not answer this question before it expired. Decide without it, or ask again later.";
57
+
58
+ /** S1 codes.ts durable_question_busy: another question of this session's is open. */
59
+ const BUSY_CODE = "durable_question_busy";
60
+ const TOOL_USE_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
61
+
62
+ /** The seat ids from the env: `{ skip: true }` outside a seat or with an incomplete seat env. */
63
+ export function seatFromEnv(env) {
64
+ const socketPath = env[SEAT_SOCKET_ENV];
65
+ const sessionId = env[SEAT_SESSION_ENV];
66
+ const spawnId = env[SEAT_SPAWN_ENV];
67
+ const epoch = env[SEAT_EPOCH_ENV];
68
+ if (!socketPath || !sessionId || !spawnId || !/^\d{1,9}$/.test(epoch ?? ""))
69
+ return { skip: true };
70
+ return { socketPath, session_id: sessionId, spawn_id: spawnId, epoch: Number(epoch) };
71
+ }
72
+
73
+ /**
74
+ * The tool call from the hook's stdin JSON, or null when it is not an AskUserQuestion call with
75
+ * questions. tool_use_id comes from the input; when the engine gives none, a stable id is derived
76
+ * from the questions (asking again returns the session's open question either way).
77
+ */
78
+ export function toolCallOf(input) {
79
+ if (!input || typeof input !== "object") return null;
80
+ if (input.tool_name !== "AskUserQuestion") return null;
81
+ const ti = input.tool_input;
82
+ if (!ti || typeof ti !== "object" || !Array.isArray(ti.questions)) return null;
83
+ let id = typeof input.tool_use_id === "string" ? input.tool_use_id : "";
84
+ if (!TOOL_USE_ID_RE.test(id))
85
+ id = `h${createHash("sha256").update(JSON.stringify(ti.questions)).digest("hex").slice(0, 40)}`;
86
+ return { tool_use_id: id, tool_input: ti, questions: ti.questions };
87
+ }
88
+
89
+ /**
90
+ * One JSON line out, the first line back, parsed. Rejects on a socket error, close or timeout, and
91
+ * on an answer line over MAX_ANSWER_BYTES however the bytes were chunked.
92
+ */
93
+ export function sendFrame(socketPath, frame, timeoutMs = OP_TIMEOUT_MS) {
94
+ return new Promise((resolve, reject) => {
95
+ const sock = net.createConnection({ path: socketPath });
96
+ let buf = Buffer.alloc(0);
97
+ const done = (fn, v) => {
98
+ clearTimeout(timer);
99
+ sock.destroy();
100
+ fn(v);
101
+ };
102
+ const timer = setTimeout(() => done(reject, new Error("no answer in time")), timeoutMs);
103
+ sock.on("connect", () => sock.write(`${JSON.stringify(frame)}\n`));
104
+ sock.on("data", (d) => {
105
+ buf = Buffer.concat([buf, d]);
106
+ const i = buf.indexOf(0x0a);
107
+ // The line's bytes, or all the bytes so far while no newline has come.
108
+ if ((i < 0 ? buf.length : i) > MAX_ANSWER_BYTES)
109
+ return done(reject, new Error("answer too long"));
110
+ if (i < 0) return;
111
+ try {
112
+ done(resolve, JSON.parse(buf.subarray(0, i).toString("utf8")));
113
+ } catch {
114
+ done(reject, new Error("answer is not JSON"));
115
+ }
116
+ });
117
+ sock.on("error", (e) => done(reject, e));
118
+ sock.on("close", () => {
119
+ if (buf.indexOf(0x0a) < 0) done(reject, new Error("closed without an answer"));
120
+ });
121
+ });
122
+ }
123
+
124
+ /** The api body of a relayed answer `{ok, status, body}` when ok, else null. */
125
+ function okBody(reply) {
126
+ return reply &&
127
+ typeof reply === "object" &&
128
+ reply.ok === true &&
129
+ reply.body &&
130
+ typeof reply.body === "object"
131
+ ? reply.body
132
+ : null;
133
+ }
134
+
135
+ export function decision(permissionDecision, extra) {
136
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision, ...extra } };
137
+ }
138
+
139
+ /**
140
+ * The hook. Resolves to the stdout object to print, or null to print nothing (Claude Code then
141
+ * draws its dialog as usual). Never throws.
142
+ */
143
+ export async function runSeatAsk(env, input, deps = {}) {
144
+ const send = deps.send ?? sendFrame;
145
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
146
+ const now = deps.now ?? (() => Date.now());
147
+ const warn = deps.warn ?? ((l) => process.stderr.write(`${l}\n`));
148
+ const waitMs = deps.waitMs ?? WAIT_MS;
149
+ const seat = seatFromEnv(env);
150
+ if (seat.skip) return null;
151
+ const call = toolCallOf(input);
152
+ if (!call) return null;
153
+ const { socketPath, ...ids } = seat;
154
+ let reply;
155
+ try {
156
+ reply = await send(socketPath, {
157
+ op: "question_ask",
158
+ ...ids,
159
+ tool_use_id: call.tool_use_id,
160
+ questions: call.questions,
161
+ });
162
+ } catch (e) {
163
+ warn(`mnemom: question not recorded: ${e instanceof Error ? e.message : String(e)}`);
164
+ return null;
165
+ }
166
+ if (reply?.status === 409 && reply.body?.error?.code === BUSY_CODE)
167
+ return decision("deny", { permissionDecisionReason: PENDING_REASON });
168
+ const asked = okBody(reply);
169
+ if (!asked || typeof asked.approval_id !== "string") {
170
+ warn("mnemom: question not recorded (refused); showing the dialog");
171
+ return null;
172
+ }
173
+ const approval_id = asked.approval_id;
174
+ const deadline = now() + waitMs;
175
+ while (now() < deadline) {
176
+ await sleep(POLL_INTERVAL_MS);
177
+ let st;
178
+ try {
179
+ st = okBody(await send(socketPath, { op: "question_poll", ...ids, approval_id }));
180
+ } catch {
181
+ continue; // a transient socket or daemon blip: keep the turn held and try again
182
+ }
183
+ if (!st) continue;
184
+ if (st.state === "answered" && st.answers && typeof st.answers === "object")
185
+ return decision("allow", {
186
+ permissionDecisionReason: `answered by the owner (question ${approval_id})`,
187
+ updatedInput: { ...call.tool_input, answers: st.answers },
188
+ });
189
+ if (st.state === "dismissed")
190
+ return decision("deny", { permissionDecisionReason: DISMISSED_REASON });
191
+ if (st.state === "expired")
192
+ return decision("deny", { permissionDecisionReason: EXPIRED_REASON });
193
+ if (st.state === "handed_off") break;
194
+ }
195
+ try {
196
+ await send(socketPath, { op: "question_release", ...ids, approval_id });
197
+ } catch (e) {
198
+ // The question stays open at the api and in own_questions; hand off all the same, the model
199
+ // must not sit on a dialog nobody can see.
200
+ warn(`mnemom: question release failed: ${e instanceof Error ? e.message : String(e)}`);
201
+ }
202
+ return decision("deny", { permissionDecisionReason: HANDOFF_REASON });
203
+ }
204
+
205
+ async function readStdin() {
206
+ let s = "";
207
+ for await (const c of process.stdin) s += c;
208
+ return s;
209
+ }
210
+
211
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
212
+ let input;
213
+ try {
214
+ input = JSON.parse(await readStdin());
215
+ } catch {
216
+ input = null;
217
+ }
218
+ const out = await runSeatAsk(process.env, input);
219
+ if (out) process.stdout.write(`${JSON.stringify(out)}\n`);
220
+ process.exitCode = 0;
221
+ }