@mono-agent/agent-app 0.6.2 → 0.9.0

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 (313) hide show
  1. package/README.md +259 -19
  2. package/dist/account-home.d.ts +6 -0
  3. package/dist/account-home.d.ts.map +1 -0
  4. package/dist/account-home.js +9 -0
  5. package/dist/account-home.js.map +1 -0
  6. package/dist/adapter-send-proxy.d.ts +18 -0
  7. package/dist/adapter-send-proxy.d.ts.map +1 -0
  8. package/dist/adapter-send-proxy.js +229 -0
  9. package/dist/adapter-send-proxy.js.map +1 -0
  10. package/dist/adapter-send-tools-main.js +62 -4
  11. package/dist/adapter-send-tools-main.js.map +1 -1
  12. package/dist/adapter-send-tools.d.ts +33 -6
  13. package/dist/adapter-send-tools.d.ts.map +1 -1
  14. package/dist/adapter-send-tools.js +485 -87
  15. package/dist/adapter-send-tools.js.map +1 -1
  16. package/dist/app-config.d.ts +3 -1
  17. package/dist/app-config.d.ts.map +1 -1
  18. package/dist/app-config.js +16 -5
  19. package/dist/app-config.js.map +1 -1
  20. package/dist/app.d.ts +27 -0
  21. package/dist/app.d.ts.map +1 -1
  22. package/dist/app.js +761 -147
  23. package/dist/app.js.map +1 -1
  24. package/dist/background-environment.d.ts +16 -0
  25. package/dist/background-environment.d.ts.map +1 -0
  26. package/dist/background-environment.js +68 -0
  27. package/dist/background-environment.js.map +1 -0
  28. package/dist/background-runtime.d.ts +62 -0
  29. package/dist/background-runtime.d.ts.map +1 -0
  30. package/dist/background-runtime.js +1115 -0
  31. package/dist/background-runtime.js.map +1 -0
  32. package/dist/background-snapshot-key.d.ts +13 -0
  33. package/dist/background-snapshot-key.d.ts.map +1 -0
  34. package/dist/background-snapshot-key.js +194 -0
  35. package/dist/background-snapshot-key.js.map +1 -0
  36. package/dist/background-snapshot.d.ts +96 -0
  37. package/dist/background-snapshot.d.ts.map +1 -0
  38. package/dist/background-snapshot.js +450 -0
  39. package/dist/background-snapshot.js.map +1 -0
  40. package/dist/background-worker-lease.d.ts +39 -0
  41. package/dist/background-worker-lease.d.ts.map +1 -0
  42. package/dist/background-worker-lease.js +356 -0
  43. package/dist/background-worker-lease.js.map +1 -0
  44. package/dist/background.d.ts +74 -2
  45. package/dist/background.d.ts.map +1 -1
  46. package/dist/background.js +815 -73
  47. package/dist/background.js.map +1 -1
  48. package/dist/broadcast-recorder.d.ts.map +1 -1
  49. package/dist/broadcast-recorder.js +24 -2
  50. package/dist/broadcast-recorder.js.map +1 -1
  51. package/dist/channel-plugins.d.ts +2 -0
  52. package/dist/channel-plugins.d.ts.map +1 -1
  53. package/dist/channel-plugins.js +8 -0
  54. package/dist/channel-plugins.js.map +1 -1
  55. package/dist/channels.d.ts +10 -0
  56. package/dist/channels.d.ts.map +1 -1
  57. package/dist/channels.js +87 -9
  58. package/dist/channels.js.map +1 -1
  59. package/dist/cli.d.ts +86 -4
  60. package/dist/cli.d.ts.map +1 -1
  61. package/dist/cli.js +2421 -143
  62. package/dist/cli.js.map +1 -1
  63. package/dist/config-reference.d.ts.map +1 -1
  64. package/dist/config-reference.js +448 -6
  65. package/dist/config-reference.js.map +1 -1
  66. package/dist/configuration-proposal-main.d.ts +3 -0
  67. package/dist/configuration-proposal-main.d.ts.map +1 -0
  68. package/dist/configuration-proposal-main.js +13 -0
  69. package/dist/configuration-proposal-main.js.map +1 -0
  70. package/dist/configuration-proposal-tool.d.ts +33 -0
  71. package/dist/configuration-proposal-tool.d.ts.map +1 -0
  72. package/dist/configuration-proposal-tool.js +188 -0
  73. package/dist/configuration-proposal-tool.js.map +1 -0
  74. package/dist/configured-agent.d.ts +16 -0
  75. package/dist/configured-agent.d.ts.map +1 -1
  76. package/dist/configured-agent.js +165 -18
  77. package/dist/configured-agent.js.map +1 -1
  78. package/dist/consumer-contract.d.ts +3 -0
  79. package/dist/consumer-contract.d.ts.map +1 -1
  80. package/dist/consumer-contract.js +44 -1
  81. package/dist/consumer-contract.js.map +1 -1
  82. package/dist/continuation-command.d.ts +15 -0
  83. package/dist/continuation-command.d.ts.map +1 -0
  84. package/dist/continuation-command.js +170 -0
  85. package/dist/continuation-command.js.map +1 -0
  86. package/dist/continuation-config.d.ts +25 -0
  87. package/dist/continuation-config.d.ts.map +1 -0
  88. package/dist/continuation-config.js +236 -0
  89. package/dist/continuation-config.js.map +1 -0
  90. package/dist/continuation-service.d.ts +99 -0
  91. package/dist/continuation-service.d.ts.map +1 -0
  92. package/dist/continuation-service.js +1313 -0
  93. package/dist/continuation-service.js.map +1 -0
  94. package/dist/continuation-store.d.ts +104 -0
  95. package/dist/continuation-store.d.ts.map +1 -0
  96. package/dist/continuation-store.js +688 -0
  97. package/dist/continuation-store.js.map +1 -0
  98. package/dist/continuations.d.ts +167 -0
  99. package/dist/continuations.d.ts.map +1 -0
  100. package/dist/continuations.js +71 -0
  101. package/dist/continuations.js.map +1 -0
  102. package/dist/doctor.d.ts +29 -5
  103. package/dist/doctor.d.ts.map +1 -1
  104. package/dist/doctor.js +1611 -116
  105. package/dist/doctor.js.map +1 -1
  106. package/dist/first-run-managed-memory.d.ts +39 -0
  107. package/dist/first-run-managed-memory.d.ts.map +1 -0
  108. package/dist/first-run-managed-memory.js +584 -0
  109. package/dist/first-run-managed-memory.js.map +1 -0
  110. package/dist/first-run-readiness.d.ts +111 -0
  111. package/dist/first-run-readiness.d.ts.map +1 -0
  112. package/dist/first-run-readiness.js +728 -0
  113. package/dist/first-run-readiness.js.map +1 -0
  114. package/dist/index.d.ts +17 -3
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +8 -1
  117. package/dist/index.js.map +1 -1
  118. package/dist/init.d.ts +95 -3
  119. package/dist/init.d.ts.map +1 -1
  120. package/dist/init.js +1218 -24
  121. package/dist/init.js.map +1 -1
  122. package/dist/interaction-bridge.d.ts +25 -1
  123. package/dist/interaction-bridge.d.ts.map +1 -1
  124. package/dist/interaction-bridge.js +281 -19
  125. package/dist/interaction-bridge.js.map +1 -1
  126. package/dist/launchd.d.ts +16 -6
  127. package/dist/launchd.d.ts.map +1 -1
  128. package/dist/launchd.js +30 -15
  129. package/dist/launchd.js.map +1 -1
  130. package/dist/local-configuration.d.ts +99 -0
  131. package/dist/local-configuration.d.ts.map +1 -0
  132. package/dist/local-configuration.js +1299 -0
  133. package/dist/local-configuration.js.map +1 -0
  134. package/dist/managed-runtime-packages.d.ts +9 -0
  135. package/dist/managed-runtime-packages.d.ts.map +1 -0
  136. package/dist/managed-runtime-packages.js +56 -0
  137. package/dist/managed-runtime-packages.js.map +1 -0
  138. package/dist/memory-command.d.ts +4 -0
  139. package/dist/memory-command.d.ts.map +1 -1
  140. package/dist/memory-command.js +751 -27
  141. package/dist/memory-command.js.map +1 -1
  142. package/dist/memory-embedding-service.d.ts +33 -0
  143. package/dist/memory-embedding-service.d.ts.map +1 -0
  144. package/dist/memory-embedding-service.js +207 -0
  145. package/dist/memory-embedding-service.js.map +1 -0
  146. package/dist/memory-recall.d.ts +43 -14
  147. package/dist/memory-recall.d.ts.map +1 -1
  148. package/dist/memory-recall.js +103 -33
  149. package/dist/memory-recall.js.map +1 -1
  150. package/dist/memory-retrieval.d.ts +66 -0
  151. package/dist/memory-retrieval.d.ts.map +1 -0
  152. package/dist/memory-retrieval.js +332 -0
  153. package/dist/memory-retrieval.js.map +1 -0
  154. package/dist/modules/base.d.ts +6 -5
  155. package/dist/modules/base.d.ts.map +1 -1
  156. package/dist/modules/base.js +11 -7
  157. package/dist/modules/base.js.map +1 -1
  158. package/dist/modules/catalog.d.ts.map +1 -1
  159. package/dist/modules/catalog.js +141 -25
  160. package/dist/modules/catalog.js.map +1 -1
  161. package/dist/modules/index.d.ts +2 -2
  162. package/dist/modules/index.d.ts.map +1 -1
  163. package/dist/modules/index.js +1 -1
  164. package/dist/modules/index.js.map +1 -1
  165. package/dist/modules/known-tools.d.ts +3 -0
  166. package/dist/modules/known-tools.d.ts.map +1 -1
  167. package/dist/modules/known-tools.js +11 -1
  168. package/dist/modules/known-tools.js.map +1 -1
  169. package/dist/modules/types.d.ts +20 -6
  170. package/dist/modules/types.d.ts.map +1 -1
  171. package/dist/modules/types.js.map +1 -1
  172. package/dist/notify-runtime.d.ts +26 -0
  173. package/dist/notify-runtime.d.ts.map +1 -0
  174. package/dist/notify-runtime.js +27 -0
  175. package/dist/notify-runtime.js.map +1 -0
  176. package/dist/notify-tool.d.ts +51 -0
  177. package/dist/notify-tool.d.ts.map +1 -0
  178. package/dist/notify-tool.js +182 -0
  179. package/dist/notify-tool.js.map +1 -0
  180. package/dist/package-version.d.ts +3 -0
  181. package/dist/package-version.d.ts.map +1 -0
  182. package/dist/package-version.js +14 -0
  183. package/dist/package-version.js.map +1 -0
  184. package/dist/pi-auth-store-inspection.d.ts +21 -0
  185. package/dist/pi-auth-store-inspection.d.ts.map +1 -0
  186. package/dist/pi-auth-store-inspection.js +120 -0
  187. package/dist/pi-auth-store-inspection.js.map +1 -0
  188. package/dist/pi-oauth-login-main.d.ts +3 -0
  189. package/dist/pi-oauth-login-main.d.ts.map +1 -0
  190. package/dist/pi-oauth-login-main.js +14 -0
  191. package/dist/pi-oauth-login-main.js.map +1 -0
  192. package/dist/pi-oauth-login.d.ts +22 -0
  193. package/dist/pi-oauth-login.d.ts.map +1 -0
  194. package/dist/pi-oauth-login.js +79 -0
  195. package/dist/pi-oauth-login.js.map +1 -0
  196. package/dist/proactive-notify.d.ts +2 -0
  197. package/dist/proactive-notify.d.ts.map +1 -1
  198. package/dist/proactive-notify.js +1 -0
  199. package/dist/proactive-notify.js.map +1 -1
  200. package/dist/process-incarnation.d.ts +23 -0
  201. package/dist/process-incarnation.d.ts.map +1 -0
  202. package/dist/process-incarnation.js +208 -0
  203. package/dist/process-incarnation.js.map +1 -0
  204. package/dist/project-skills.d.ts +40 -0
  205. package/dist/project-skills.d.ts.map +1 -0
  206. package/dist/project-skills.js +573 -0
  207. package/dist/project-skills.js.map +1 -0
  208. package/dist/provider-setup.d.ts +117 -2
  209. package/dist/provider-setup.d.ts.map +1 -1
  210. package/dist/provider-setup.js +1505 -49
  211. package/dist/provider-setup.js.map +1 -1
  212. package/dist/readiness-probe-worker.d.ts +12 -0
  213. package/dist/readiness-probe-worker.d.ts.map +1 -0
  214. package/dist/readiness-probe-worker.js +276 -0
  215. package/dist/readiness-probe-worker.js.map +1 -0
  216. package/dist/readiness-probe.d.ts +100 -0
  217. package/dist/readiness-probe.d.ts.map +1 -0
  218. package/dist/readiness-probe.js +780 -0
  219. package/dist/readiness-probe.js.map +1 -0
  220. package/dist/recipes/base.d.ts +15 -0
  221. package/dist/recipes/base.d.ts.map +1 -0
  222. package/dist/recipes/base.js +51 -0
  223. package/dist/recipes/base.js.map +1 -0
  224. package/dist/recipes/catalog.d.ts +4 -0
  225. package/dist/recipes/catalog.d.ts.map +1 -0
  226. package/dist/recipes/catalog.js +525 -0
  227. package/dist/recipes/catalog.js.map +1 -0
  228. package/dist/recipes/index.d.ts +11 -0
  229. package/dist/recipes/index.d.ts.map +1 -0
  230. package/dist/recipes/index.js +14 -0
  231. package/dist/recipes/index.js.map +1 -0
  232. package/dist/recipes/types.d.ts +70 -0
  233. package/dist/recipes/types.d.ts.map +1 -0
  234. package/dist/recipes/types.js +15 -0
  235. package/dist/recipes/types.js.map +1 -0
  236. package/dist/request-model-override.d.ts +43 -1
  237. package/dist/request-model-override.d.ts.map +1 -1
  238. package/dist/request-model-override.js +178 -19
  239. package/dist/request-model-override.js.map +1 -1
  240. package/dist/run-history.d.ts +32 -0
  241. package/dist/run-history.d.ts.map +1 -0
  242. package/dist/run-history.js +859 -0
  243. package/dist/run-history.js.map +1 -0
  244. package/dist/runs-health.d.ts.map +1 -1
  245. package/dist/runs-health.js +12 -4
  246. package/dist/runs-health.js.map +1 -1
  247. package/dist/runtime-option-extensions.d.ts +14 -0
  248. package/dist/runtime-option-extensions.d.ts.map +1 -0
  249. package/dist/runtime-option-extensions.js +98 -0
  250. package/dist/runtime-option-extensions.js.map +1 -0
  251. package/dist/runtime-routes.d.ts +9 -0
  252. package/dist/runtime-routes.d.ts.map +1 -0
  253. package/dist/runtime-routes.js +13 -0
  254. package/dist/runtime-routes.js.map +1 -0
  255. package/dist/sandbox-manager.d.ts +86 -0
  256. package/dist/sandbox-manager.d.ts.map +1 -0
  257. package/dist/sandbox-manager.js +1402 -0
  258. package/dist/sandbox-manager.js.map +1 -0
  259. package/dist/setup.d.ts +29 -0
  260. package/dist/setup.d.ts.map +1 -0
  261. package/dist/setup.js +97 -0
  262. package/dist/setup.js.map +1 -0
  263. package/dist/supermemory-plugin.d.ts +56 -0
  264. package/dist/supermemory-plugin.d.ts.map +1 -0
  265. package/dist/supermemory-plugin.js +142 -0
  266. package/dist/supermemory-plugin.js.map +1 -0
  267. package/dist/tui-command.d.ts +56 -0
  268. package/dist/tui-command.d.ts.map +1 -1
  269. package/dist/tui-command.js +305 -31
  270. package/dist/tui-command.js.map +1 -1
  271. package/dist/web-command.d.ts +8 -0
  272. package/dist/web-command.d.ts.map +1 -1
  273. package/dist/web-command.js +174 -17
  274. package/dist/web-command.js.map +1 -1
  275. package/dist/wizard/answers.d.ts +31 -4
  276. package/dist/wizard/answers.d.ts.map +1 -1
  277. package/dist/wizard/answers.js +141 -29
  278. package/dist/wizard/answers.js.map +1 -1
  279. package/dist/wizard/from-flags.d.ts +7 -0
  280. package/dist/wizard/from-flags.d.ts.map +1 -1
  281. package/dist/wizard/from-flags.js +36 -2
  282. package/dist/wizard/from-flags.js.map +1 -1
  283. package/dist/wizard/index.d.ts +4 -4
  284. package/dist/wizard/index.d.ts.map +1 -1
  285. package/dist/wizard/index.js +2 -2
  286. package/dist/wizard/index.js.map +1 -1
  287. package/dist/wizard/model-discovery.d.ts +33 -2
  288. package/dist/wizard/model-discovery.d.ts.map +1 -1
  289. package/dist/wizard/model-discovery.js +826 -108
  290. package/dist/wizard/model-discovery.js.map +1 -1
  291. package/dist/wizard/presets.d.ts +1 -1
  292. package/dist/wizard/presets.d.ts.map +1 -1
  293. package/dist/wizard/presets.js +7 -12
  294. package/dist/wizard/presets.js.map +1 -1
  295. package/dist/wizard/prompts.d.ts +56 -9
  296. package/dist/wizard/prompts.d.ts.map +1 -1
  297. package/dist/wizard/prompts.js +172 -20
  298. package/dist/wizard/prompts.js.map +1 -1
  299. package/dist/wizard/run.d.ts +59 -3
  300. package/dist/wizard/run.d.ts.map +1 -1
  301. package/dist/wizard/run.js +1290 -191
  302. package/dist/wizard/run.js.map +1 -1
  303. package/package.json +21 -16
  304. package/resources/srt/package-lock.json +66 -0
  305. package/resources/srt/package.json +9 -0
  306. package/schema/mono-agent.config.schema.json +587 -20
  307. package/skills/mono-agent-composer/SKILL.md +6 -6
  308. package/skills/mono-agent-composer/references/config-blueprint.md +11 -10
  309. package/skills/mono-agent-composer/references/discovery-questions.md +33 -18
  310. package/skills/mono-agent-composer/references/feature-coverage.md +14 -12
  311. package/skills/mono-agent-composer/references/package-map.md +2 -2
  312. package/skills/mono-agent-composer/references/playbooks.md +8 -7
  313. package/skills/mono-agent-composer/references/validation.md +3 -3
@@ -0,0 +1,688 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { chmod, lstat, mkdir, open, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { canonicalContinuationJson, continuationDigest, isContinuationMode, isContinuationState, } from "./continuations.js";
5
+ export const CONTINUATION_STORE_SCHEMA_VERSION = 1;
6
+ export const CONTINUATION_RECORD_STORE_SCHEMA_VERSION = 2;
7
+ const RECORDS_DIRECTORY = "records-v2";
8
+ const TRANSACTION_FILE = "continuation-transaction-v2.json";
9
+ const MANIFEST_FILE = "continuation-store-v2.json";
10
+ const OWNER_DATABASE_FILE = "continuations-owner.sqlite";
11
+ const MAX_RECORD_BYTES = 2 * 1024 * 1024;
12
+ const MAX_TRANSACTION_BYTES = 16 * 1024 * 1024;
13
+ const DEFAULT_TERMINAL_MAX_RECORDS = 50_000;
14
+ const DEFAULT_TERMINAL_MAX_AGE_MS = 365 * 24 * 60 * 60 * 1_000;
15
+ const DEFAULT_CAPTURED_TEXT_MAX_RECORDS = 1_000;
16
+ const DEFAULT_CAPTURED_TEXT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
17
+ /**
18
+ * Claim exclusive ownership of a continuation state directory for this process.
19
+ * SQLite holds an OS-backed exclusive transaction for the process lifetime.
20
+ * There is no stale-path cleanup race: close or process death releases the OS
21
+ * lock, and SQLite performs any required journal recovery for the next owner.
22
+ */
23
+ export async function acquireContinuationStoreLock(stateDir) {
24
+ await ensureOwnerOnlyDirectory(stateDir);
25
+ const path = join(stateDir, OWNER_DATABASE_FILE);
26
+ let database;
27
+ try {
28
+ if (await continuationPathExists(path)) {
29
+ await assertOwnerOnlyRegularFile(path, "Continuation owner database");
30
+ }
31
+ const { DatabaseSync } = await import("node:sqlite");
32
+ database = new DatabaseSync(path, { timeout: 0 });
33
+ if (process.platform !== "win32")
34
+ await chmod(path, 0o600);
35
+ await assertOwnerOnlyRegularFile(path, "Continuation owner database");
36
+ database.exec("PRAGMA journal_mode=DELETE; PRAGMA locking_mode=EXCLUSIVE; BEGIN EXCLUSIVE");
37
+ database.exec(`
38
+ CREATE TABLE IF NOT EXISTS ownership (
39
+ id INTEGER PRIMARY KEY CHECK (id = 1),
40
+ pid INTEGER NOT NULL,
41
+ acquired_at TEXT NOT NULL
42
+ );
43
+ `);
44
+ database.prepare(`
45
+ INSERT INTO ownership (id, pid, acquired_at)
46
+ VALUES (1, ?, ?)
47
+ ON CONFLICT(id) DO UPDATE SET pid = excluded.pid, acquired_at = excluded.acquired_at
48
+ `).run(process.pid, new Date().toISOString());
49
+ const journalPath = `${path}-journal`;
50
+ if (process.platform !== "win32" && await continuationPathExists(journalPath)) {
51
+ await chmod(journalPath, 0o600);
52
+ }
53
+ }
54
+ catch (error) {
55
+ try {
56
+ database?.close();
57
+ }
58
+ catch { /* best-effort close after failed acquisition */ }
59
+ if (isObject(error) && error.code === "ERR_SQLITE_ERROR" && String(error.message).includes("database is locked")) {
60
+ throw new Error(`Continuation state is already owned by another live process: ${stateDir}`, { cause: error });
61
+ }
62
+ throw error;
63
+ }
64
+ let released = false;
65
+ const owner = database;
66
+ return {
67
+ async release() {
68
+ if (released)
69
+ return;
70
+ released = true;
71
+ try {
72
+ if (owner.isTransaction)
73
+ owner.exec("ROLLBACK");
74
+ }
75
+ finally {
76
+ owner.close();
77
+ }
78
+ },
79
+ };
80
+ }
81
+ /**
82
+ * Open the bounded per-record store. Each continuation has its own atomic 0600
83
+ * file, so a result mutates O(1) durable data instead of rewriting every prior
84
+ * payload. A tiny write-ahead transaction makes multi-record maintenance
85
+ * restart-safe. The legacy v1 monolith is migrated idempotently on first open.
86
+ */
87
+ export async function openContinuationStore(stateDir, options = {}) {
88
+ await ensureOwnerOnlyDirectory(stateDir);
89
+ const recordsDir = join(stateDir, RECORDS_DIRECTORY);
90
+ await ensureOwnerOnlyDirectory(recordsDir);
91
+ const transactionPath = join(stateDir, TRANSACTION_FILE);
92
+ const manifestPath = join(stateDir, MANIFEST_FILE);
93
+ const legacyPath = join(stateDir, "continuations-v1.json");
94
+ const policy = resolveRetention(options.retention);
95
+ const now = options.now ?? (() => new Date());
96
+ const recoveredGeneration = await recoverRecordTransaction(recordsDir, transactionPath);
97
+ const records = await loadRecordDirectory(recordsDir);
98
+ const beforeOpen = cloneRecords(records);
99
+ const legacyExists = await continuationPathExists(legacyPath);
100
+ if (legacyExists) {
101
+ const legacy = await loadLegacyStore(legacyPath);
102
+ for (const [id, record] of legacy) {
103
+ const current = records.get(id);
104
+ if (current === undefined) {
105
+ records.set(id, record);
106
+ }
107
+ else if (canonicalContinuationJson(current) !== canonicalContinuationJson(record)) {
108
+ throw new Error(`Legacy and v2 continuation records conflict for id ${id}; refusing lossy migration.`);
109
+ }
110
+ }
111
+ }
112
+ applyRetention(records, policy, now());
113
+ const migrationGeneration = await persistRecordChanges(recordsDir, transactionPath, beforeOpen, records);
114
+ if (legacyExists) {
115
+ await rm(legacyPath, { force: true });
116
+ await syncDirectory(stateDir);
117
+ }
118
+ let generation = migrationGeneration ?? recoveredGeneration ?? randomUUID();
119
+ await persistManifest(manifestPath, generation, continuationStoreStats(records, policy), now());
120
+ let tail = Promise.resolve();
121
+ let poisoned;
122
+ async function locked(operation) {
123
+ const previous = tail;
124
+ let release;
125
+ tail = new Promise((resolve) => { release = resolve; });
126
+ await previous;
127
+ if (poisoned !== undefined) {
128
+ release();
129
+ throw new Error("Continuation store requires restart after a failed durable transaction.", { cause: poisoned });
130
+ }
131
+ const before = cloneRecords(records);
132
+ const draft = cloneRecords(records);
133
+ let result;
134
+ try {
135
+ result = await operation(draft);
136
+ }
137
+ catch (error) {
138
+ release();
139
+ throw error;
140
+ }
141
+ try {
142
+ applyRetention(draft, policy, now());
143
+ const committedGeneration = await persistRecordChanges(recordsDir, transactionPath, before, draft);
144
+ if (committedGeneration !== undefined)
145
+ generation = committedGeneration;
146
+ replaceRecords(records, draft);
147
+ await persistManifest(manifestPath, generation, continuationStoreStats(records, policy), now());
148
+ return result;
149
+ }
150
+ catch (error) {
151
+ try {
152
+ const recovered = await recoverRecordTransaction(recordsDir, transactionPath);
153
+ if (recovered !== undefined)
154
+ generation = recovered;
155
+ replaceRecords(records, await loadRecordDirectory(recordsDir));
156
+ }
157
+ catch (recoveryError) {
158
+ poisoned = new AggregateError([error, recoveryError], "Continuation durable commit and recovery both failed.");
159
+ throw poisoned;
160
+ }
161
+ poisoned = error;
162
+ throw error;
163
+ }
164
+ finally {
165
+ release();
166
+ }
167
+ }
168
+ return {
169
+ path: manifestPath,
170
+ async get(id) {
171
+ await tail;
172
+ return cloneRecord(records.get(id));
173
+ },
174
+ async list() {
175
+ await tail;
176
+ return [...records.values()].map((record) => cloneRecord(record));
177
+ },
178
+ async findClaim(input) {
179
+ await tail;
180
+ const found = [...records.values()].find((record) => record.serverName === input.serverName
181
+ && record.originRunId === input.originRunId
182
+ && record.taskKey === input.taskKey);
183
+ return cloneRecord(found);
184
+ },
185
+ async stats() {
186
+ await tail;
187
+ return continuationStoreStats(records, policy);
188
+ },
189
+ mutate: locked,
190
+ };
191
+ }
192
+ /** Persistent owner-only key used to derive restart-stable callback capabilities. */
193
+ export async function loadOrCreateContinuationSecret(stateDir) {
194
+ await ensureOwnerOnlyDirectory(stateDir);
195
+ const path = join(stateDir, "continuation-secret");
196
+ try {
197
+ const info = await lstat(path);
198
+ if (!info.isFile() || info.isSymbolicLink())
199
+ throw new Error(`Continuation secret is not a regular file: ${path}`);
200
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
201
+ throw new Error(`Continuation secret is not owned by the current user: ${path}`);
202
+ }
203
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
204
+ throw new Error(`Continuation secret permissions are not owner-only: ${path}`);
205
+ }
206
+ const encoded = (await readFile(path, "utf8")).trim();
207
+ const secret = Buffer.from(encoded, "base64url");
208
+ if (secret.length !== 32)
209
+ throw new Error(`Continuation secret has invalid contents: ${path}`);
210
+ return secret;
211
+ }
212
+ catch (error) {
213
+ if (!isMissing(error))
214
+ throw error;
215
+ }
216
+ const secret = randomBytes(32);
217
+ try {
218
+ await writeFile(path, `${secret.toString("base64url")}\n`, { flag: "wx", mode: 0o600 });
219
+ return secret;
220
+ }
221
+ catch (error) {
222
+ if (!isObject(error) || error.code !== "EEXIST")
223
+ throw error;
224
+ return await loadOrCreateContinuationSecret(stateDir);
225
+ }
226
+ }
227
+ async function ensureOwnerOnlyDirectory(path) {
228
+ await mkdir(path, { recursive: true, mode: 0o700 });
229
+ const info = await lstat(path);
230
+ if (!info.isDirectory() || info.isSymbolicLink()) {
231
+ throw new Error(`Continuation state path is not a real directory: ${path}`);
232
+ }
233
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
234
+ throw new Error(`Continuation state directory is not owned by the current user: ${path}`);
235
+ }
236
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
237
+ await chmod(path, 0o700);
238
+ const repaired = await lstat(path);
239
+ if ((repaired.mode & 0o077) !== 0) {
240
+ throw new Error(`Continuation state directory permissions are not owner-only: ${path}`);
241
+ }
242
+ }
243
+ }
244
+ async function loadLegacyStore(path) {
245
+ let raw;
246
+ try {
247
+ const info = await lstat(path);
248
+ if (!info.isFile() || info.isSymbolicLink()) {
249
+ throw new Error(`Continuation store is not a regular file: ${path}`);
250
+ }
251
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
252
+ throw new Error(`Continuation store is not owned by the current user: ${path}`);
253
+ }
254
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
255
+ throw new Error(`Continuation store permissions are not owner-only: ${path}`);
256
+ }
257
+ raw = await readFile(path, "utf8");
258
+ }
259
+ catch (error) {
260
+ if (isMissing(error))
261
+ return new Map();
262
+ throw error;
263
+ }
264
+ let parsed;
265
+ try {
266
+ parsed = JSON.parse(raw);
267
+ }
268
+ catch (error) {
269
+ throw new Error(`Continuation store contains invalid JSON: ${path}`, { cause: error });
270
+ }
271
+ if (!isStoreFile(parsed)) {
272
+ throw new Error(`Continuation store has an unsupported or malformed schema: ${path}`);
273
+ }
274
+ return new Map(Object.entries(parsed.records).map(([id, record]) => [id, record]));
275
+ }
276
+ async function loadRecordDirectory(path) {
277
+ const records = new Map();
278
+ let removedTemporary = false;
279
+ for (const entry of await readdir(path, { withFileTypes: true })) {
280
+ const filePath = join(path, entry.name);
281
+ if (entry.name.startsWith(".") && entry.name.endsWith(".tmp")) {
282
+ if (!entry.isFile() || entry.isSymbolicLink()) {
283
+ throw new Error(`Continuation temporary record is not a regular file: ${filePath}`);
284
+ }
285
+ await rm(filePath, { force: true });
286
+ removedTemporary = true;
287
+ continue;
288
+ }
289
+ if (!entry.name.endsWith(".json")) {
290
+ throw new Error(`Unexpected entry in continuation record directory: ${filePath}`);
291
+ }
292
+ await assertOwnerOnlyRegularFile(filePath, "Continuation record");
293
+ const raw = await readBoundedOwnerOnlyFile(filePath, MAX_RECORD_BYTES, "Continuation record");
294
+ let value;
295
+ try {
296
+ value = JSON.parse(raw);
297
+ }
298
+ catch (error) {
299
+ throw new Error(`Continuation record contains invalid JSON: ${filePath}`, { cause: error });
300
+ }
301
+ if (!isObject(value) || !requiredString(value.continuationId) || !isRecord(value, value.continuationId)) {
302
+ throw new Error(`Continuation record has a malformed schema: ${filePath}`);
303
+ }
304
+ const expectedName = continuationRecordFileName(value.continuationId);
305
+ if (entry.name !== expectedName) {
306
+ throw new Error(`Continuation record filename does not match its id: ${filePath}`);
307
+ }
308
+ if (records.has(value.continuationId)) {
309
+ throw new Error(`Duplicate continuation record: ${value.continuationId}`);
310
+ }
311
+ records.set(value.continuationId, structuredClone(value));
312
+ }
313
+ if (removedTemporary)
314
+ await syncDirectory(path);
315
+ return records;
316
+ }
317
+ async function persistRecordChanges(recordsDir, transactionPath, before, after) {
318
+ const writes = [...after.values()].filter((record) => {
319
+ const prior = before.get(record.continuationId);
320
+ return prior === undefined || JSON.stringify(prior) !== JSON.stringify(record);
321
+ }).map((record) => structuredClone(record));
322
+ const deletes = [...before.keys()].filter((id) => !after.has(id));
323
+ if (writes.length === 0 && deletes.length === 0)
324
+ return undefined;
325
+ let generation;
326
+ for (const transaction of createTransactionBatches(writes, deletes)) {
327
+ await writeJsonAtomic(transactionPath, transaction, true, MAX_TRANSACTION_BYTES);
328
+ await applyRecordTransaction(recordsDir, transaction);
329
+ await rm(transactionPath, { force: true });
330
+ await syncDirectory(dirname(transactionPath));
331
+ generation = transaction.generation;
332
+ }
333
+ return generation;
334
+ }
335
+ async function recoverRecordTransaction(recordsDir, transactionPath) {
336
+ if (!await continuationPathExists(transactionPath))
337
+ return undefined;
338
+ const raw = await readBoundedOwnerOnlyFile(transactionPath, MAX_TRANSACTION_BYTES, "Continuation transaction");
339
+ let value;
340
+ try {
341
+ value = JSON.parse(raw);
342
+ }
343
+ catch (error) {
344
+ throw new Error(`Continuation transaction contains invalid JSON: ${transactionPath}`, { cause: error });
345
+ }
346
+ if (!isRecordTransaction(value)) {
347
+ throw new Error(`Continuation transaction has a malformed schema: ${transactionPath}`);
348
+ }
349
+ await applyRecordTransaction(recordsDir, value);
350
+ await rm(transactionPath, { force: true });
351
+ await syncDirectory(dirname(transactionPath));
352
+ return value.generation;
353
+ }
354
+ async function applyRecordTransaction(recordsDir, transaction) {
355
+ for (const record of transaction.writes) {
356
+ await writeJsonAtomic(join(recordsDir, continuationRecordFileName(record.continuationId)), record, false, MAX_RECORD_BYTES);
357
+ }
358
+ for (const id of transaction.deletes) {
359
+ await rm(join(recordsDir, continuationRecordFileName(id)), { force: true });
360
+ }
361
+ await syncDirectory(recordsDir);
362
+ }
363
+ async function persistManifest(path, generation, stats, now) {
364
+ await writeJsonAtomic(path, {
365
+ schemaVersion: CONTINUATION_RECORD_STORE_SCHEMA_VERSION,
366
+ generation,
367
+ updatedAt: now.toISOString(),
368
+ stats,
369
+ });
370
+ }
371
+ async function writeJsonAtomic(path, value, syncParent = true, maxBytes = Number.MAX_SAFE_INTEGER) {
372
+ const body = `${JSON.stringify(value, null, 2)}\n`;
373
+ if (Buffer.byteLength(body, "utf8") > maxBytes) {
374
+ throw new Error(`Durable continuation file exceeds its ${String(maxBytes)} byte safety limit: ${path}`);
375
+ }
376
+ const temporary = join(dirname(path), `.${continuationDigest(path).slice(0, 12)}-${process.pid}-${randomUUID()}.tmp`);
377
+ let handle;
378
+ try {
379
+ handle = await open(temporary, "wx", 0o600);
380
+ await handle.writeFile(body, "utf8");
381
+ await handle.sync();
382
+ await handle.close();
383
+ handle = undefined;
384
+ await rename(temporary, path);
385
+ if (process.platform !== "win32")
386
+ await chmod(path, 0o600);
387
+ if (syncParent)
388
+ await syncDirectory(dirname(path));
389
+ }
390
+ finally {
391
+ await handle?.close().catch(() => undefined);
392
+ await rm(temporary, { force: true }).catch(() => undefined);
393
+ }
394
+ }
395
+ async function syncDirectory(path) {
396
+ let directory;
397
+ try {
398
+ directory = await open(path, "r");
399
+ await directory.sync();
400
+ }
401
+ catch (error) {
402
+ if (process.platform === "win32"
403
+ && isObject(error)
404
+ && (error.code === "EISDIR"
405
+ || error.code === "EPERM"
406
+ || error.code === "EACCES"
407
+ || error.code === "EINVAL"
408
+ || error.code === "EBADF")) {
409
+ return;
410
+ }
411
+ throw error;
412
+ }
413
+ finally {
414
+ await directory?.close().catch(() => undefined);
415
+ }
416
+ }
417
+ function createTransactionBatches(writes, deletes) {
418
+ const batches = [];
419
+ const createdAt = new Date().toISOString();
420
+ for (const record of writes) {
421
+ const bytes = Buffer.byteLength(`${JSON.stringify(record, null, 2)}\n`, "utf8");
422
+ if (bytes > MAX_RECORD_BYTES) {
423
+ throw new Error(`Continuation record exceeds its ${String(MAX_RECORD_BYTES)} byte safety limit: ${record.continuationId}`);
424
+ }
425
+ }
426
+ const changes = [
427
+ ...writes.map((record) => ({ kind: "write", record: structuredClone(record) })),
428
+ ...deletes.map((id) => ({ kind: "delete", id })),
429
+ ];
430
+ const makeTransaction = (entries) => ({
431
+ schemaVersion: CONTINUATION_RECORD_STORE_SCHEMA_VERSION,
432
+ generation: randomUUID(),
433
+ createdAt,
434
+ writes: entries.flatMap((entry) => entry.kind === "write" ? [entry.record] : []),
435
+ deletes: entries.flatMap((entry) => entry.kind === "delete" ? [entry.id] : []),
436
+ });
437
+ const appendBounded = (entries) => {
438
+ if (entries.length === 0)
439
+ return;
440
+ const candidate = makeTransaction(entries);
441
+ const bytes = Buffer.byteLength(`${JSON.stringify(candidate, null, 2)}\n`, "utf8");
442
+ if (bytes <= MAX_TRANSACTION_BYTES) {
443
+ batches.push(candidate);
444
+ return;
445
+ }
446
+ if (entries.length === 1) {
447
+ const only = entries[0];
448
+ throw new Error(only?.kind === "write"
449
+ ? `Continuation record is too large for a bounded durable transaction: ${only.record.continuationId}`
450
+ : `Continuation id is too large for a bounded durable transaction: ${only?.id ?? "unknown"}`);
451
+ }
452
+ const middle = Math.floor(entries.length / 2);
453
+ appendBounded(entries.slice(0, middle));
454
+ appendBounded(entries.slice(middle));
455
+ };
456
+ // Coarse compact-JSON estimates avoid repeatedly serializing an ever-growing
457
+ // migration batch. Exact pretty-JSON size is checked at flush and recursively
458
+ // split, so every persisted transaction remains within the hard bound.
459
+ const targetBytes = Math.floor(MAX_TRANSACTION_BYTES / 2);
460
+ let batch = [];
461
+ let estimatedBytes = 512;
462
+ for (const change of changes) {
463
+ const value = change.kind === "write" ? change.record : change.id;
464
+ const estimate = Buffer.byteLength(JSON.stringify(value), "utf8") + 64;
465
+ if (batch.length > 0 && estimatedBytes + estimate > targetBytes) {
466
+ appendBounded(batch);
467
+ batch = [];
468
+ estimatedBytes = 512;
469
+ }
470
+ batch.push(change);
471
+ estimatedBytes += estimate;
472
+ }
473
+ appendBounded(batch);
474
+ return batches;
475
+ }
476
+ function resolveRetention(options) {
477
+ const policy = {
478
+ terminalMaxRecords: options?.terminalMaxRecords ?? DEFAULT_TERMINAL_MAX_RECORDS,
479
+ terminalMaxAgeMs: options?.terminalMaxAgeMs ?? DEFAULT_TERMINAL_MAX_AGE_MS,
480
+ capturedTextMaxRecords: options?.capturedTextMaxRecords ?? DEFAULT_CAPTURED_TEXT_MAX_RECORDS,
481
+ capturedTextMaxAgeMs: options?.capturedTextMaxAgeMs ?? DEFAULT_CAPTURED_TEXT_MAX_AGE_MS,
482
+ };
483
+ for (const [name, value] of Object.entries(policy)) {
484
+ if (!Number.isSafeInteger(value) || value < 0) {
485
+ throw new Error(`Continuation retention ${name} must be a non-negative safe integer.`);
486
+ }
487
+ }
488
+ return policy;
489
+ }
490
+ const SETTLED_TERMINAL_STATES = new Set([
491
+ "delivered",
492
+ "expired",
493
+ "cancelled",
494
+ "dead_lettered",
495
+ ]);
496
+ function applyRetention(records, policy, now) {
497
+ const nowMs = now.getTime();
498
+ const captures = [...records.values()]
499
+ .filter((record) => SETTLED_TERMINAL_STATES.has(record.state)
500
+ && record.mode === "capture"
501
+ && record.synthesizedText !== undefined
502
+ && nowMs - Date.parse(record.updatedAt) <= policy.capturedTextMaxAgeMs)
503
+ .sort(newestFirst)
504
+ .slice(0, policy.capturedTextMaxRecords);
505
+ const retainedCaptureText = new Set(captures.map((record) => record.continuationId));
506
+ for (const record of records.values()) {
507
+ if (!SETTLED_TERMINAL_STATES.has(record.state))
508
+ continue;
509
+ if (record.resultPayload !== undefined) {
510
+ delete record.resultPayload;
511
+ }
512
+ if (record.synthesizedText !== undefined && !retainedCaptureText.has(record.continuationId)) {
513
+ delete record.synthesizedText;
514
+ }
515
+ if (record.compactedAt === undefined) {
516
+ record.compactedAt = now.toISOString();
517
+ }
518
+ }
519
+ const retainedTerminalIds = new Set([...records.values()]
520
+ .filter((record) => SETTLED_TERMINAL_STATES.has(record.state)
521
+ && nowMs - Date.parse(record.updatedAt) <= policy.terminalMaxAgeMs)
522
+ .sort(newestFirst)
523
+ .slice(0, policy.terminalMaxRecords)
524
+ .map((record) => record.continuationId));
525
+ for (const [id, record] of records) {
526
+ if (SETTLED_TERMINAL_STATES.has(record.state) && !retainedTerminalIds.has(id))
527
+ records.delete(id);
528
+ }
529
+ }
530
+ function newestFirst(left, right) {
531
+ const byUpdatedAt = right.updatedAt.localeCompare(left.updatedAt);
532
+ return byUpdatedAt === 0 ? right.continuationId.localeCompare(left.continuationId) : byUpdatedAt;
533
+ }
534
+ function continuationStoreStats(records, policy) {
535
+ const values = [...records.values()];
536
+ return {
537
+ format: "per-record-v2",
538
+ records: values.length,
539
+ active: values.filter((record) => !SETTLED_TERMINAL_STATES.has(record.state) && record.state !== "delivery_unknown").length,
540
+ unresolvedDelivery: values.filter((record) => record.state === "delivery_unknown").length,
541
+ deadLettered: values.filter((record) => record.state === "dead_lettered").length,
542
+ terminalTombstones: values.filter((record) => SETTLED_TERMINAL_STATES.has(record.state)).length,
543
+ compacted: values.filter((record) => record.compactedAt !== undefined).length,
544
+ capturedText: values.filter((record) => record.mode === "capture" && record.synthesizedText !== undefined).length,
545
+ historyDegraded: values.filter((record) => record.receipt?.historyRecorded === false).length,
546
+ limits: { ...policy },
547
+ };
548
+ }
549
+ function cloneRecords(records) {
550
+ return new Map([...records].map(([id, record]) => [id, structuredClone(record)]));
551
+ }
552
+ function replaceRecords(target, source) {
553
+ target.clear();
554
+ for (const [id, record] of source)
555
+ target.set(id, structuredClone(record));
556
+ }
557
+ function continuationRecordFileName(id) {
558
+ return `${continuationDigest(id)}.json`;
559
+ }
560
+ async function continuationPathExists(path) {
561
+ try {
562
+ await lstat(path);
563
+ return true;
564
+ }
565
+ catch (error) {
566
+ if (isMissing(error))
567
+ return false;
568
+ throw error;
569
+ }
570
+ }
571
+ async function assertOwnerOnlyRegularFile(path, label) {
572
+ const info = await lstat(path);
573
+ if (!info.isFile() || info.isSymbolicLink())
574
+ throw new Error(`${label} is not a regular file: ${path}`);
575
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
576
+ throw new Error(`${label} is not owned by the current user: ${path}`);
577
+ }
578
+ if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
579
+ throw new Error(`${label} permissions are not owner-only: ${path}`);
580
+ }
581
+ }
582
+ async function readBoundedOwnerOnlyFile(path, maxBytes, label) {
583
+ await assertOwnerOnlyRegularFile(path, label);
584
+ const info = await lstat(path);
585
+ if (info.size > maxBytes)
586
+ throw new Error(`${label} exceeds its ${String(maxBytes)} byte safety limit: ${path}`);
587
+ return await readFile(path, "utf8");
588
+ }
589
+ function isRecordTransaction(value) {
590
+ if (!isObject(value)
591
+ || value.schemaVersion !== CONTINUATION_RECORD_STORE_SCHEMA_VERSION
592
+ || !requiredString(value.generation)
593
+ || !requiredDate(value.createdAt)
594
+ || !Array.isArray(value.writes)
595
+ || !Array.isArray(value.deletes))
596
+ return false;
597
+ const writesValid = value.writes.every((record) => isObject(record)
598
+ && requiredString(record.continuationId)
599
+ && isRecord(record, record.continuationId));
600
+ if (!writesValid || !value.deletes.every(requiredString))
601
+ return false;
602
+ const writeIds = value.writes.map((record) => record.continuationId);
603
+ const deleteIds = value.deletes;
604
+ return new Set(writeIds).size === writeIds.length
605
+ && new Set(deleteIds).size === deleteIds.length
606
+ && !deleteIds.some((id) => writeIds.includes(id));
607
+ }
608
+ function isStoreFile(value) {
609
+ if (!isObject(value) || value.schemaVersion !== CONTINUATION_STORE_SCHEMA_VERSION || !isObject(value.records)) {
610
+ return false;
611
+ }
612
+ return Object.entries(value.records).every(([id, record]) => id.length > 0 && isRecord(record, id));
613
+ }
614
+ function isRecord(value, id) {
615
+ if (!isObject(value))
616
+ return false;
617
+ return value.continuationId === id
618
+ && requiredString(value.serverName)
619
+ && requiredString(value.originRunId)
620
+ && requiredString(value.originConversationId)
621
+ && optionalString(value.replyToConversationId)
622
+ && optionalString(value.historyBoundary)
623
+ && isContinuationMode(value.mode)
624
+ && optionalString(value.routeName)
625
+ && requiredString(value.taskKey)
626
+ && requiredString(value.taskHash)
627
+ && requiredString(value.claimFingerprint)
628
+ && /^[a-f0-9]{64}$/u.test(String(value.resultTokenHash))
629
+ && requiredDate(value.createdAt)
630
+ && requiredDate(value.updatedAt)
631
+ && requiredDate(value.deadline)
632
+ && isContinuationState(value.state)
633
+ && Number.isInteger(value.synthesisAttempts)
634
+ && Number(value.synthesisAttempts) >= 0
635
+ && Number.isInteger(value.deliveryAttempts)
636
+ && Number(value.deliveryAttempts) >= 0
637
+ && optionalString(value.resultIdempotencyKey)
638
+ && optionalString(value.resultPayloadHash)
639
+ && optionalDate(value.synthesisStartedAt)
640
+ && optionalString(value.synthesizedText)
641
+ && (value.actionable === undefined || typeof value.actionable === "boolean")
642
+ && optionalDate(value.deliveryStartedAt)
643
+ && optionalDate(value.nextAttemptAt)
644
+ && optionalString(value.leaseOwner)
645
+ && optionalDate(value.leaseUntil)
646
+ && optionalDate(value.compactedAt)
647
+ && (value.lastError === undefined || isLastError(value.lastError))
648
+ && (value.receipt === undefined || isReceipt(value.receipt));
649
+ }
650
+ function isLastError(value) {
651
+ return isObject(value) && requiredString(value.code) && requiredString(value.reason) && requiredDate(value.at);
652
+ }
653
+ function isReceipt(value) {
654
+ if (!isObject(value)
655
+ || (value.kind !== "delivered" && value.kind !== "suppressed" && value.kind !== "captured" && value.kind !== "silent")) {
656
+ return false;
657
+ }
658
+ return requiredDate(value.deliveredAt)
659
+ && optionalString(value.deliveryId)
660
+ && optionalString(value.channelId)
661
+ && (value.historyRecorded === undefined || typeof value.historyRecorded === "boolean")
662
+ && (value.historyErrorCode === undefined
663
+ || (typeof value.historyErrorCode === "string" && value.historyErrorCode.length > 0 && value.historyErrorCode.length <= 128))
664
+ && (value.historyErrorCode === undefined || value.historyRecorded === false)
665
+ && (value.kind === "delivered" || (value.historyRecorded === undefined && value.historyErrorCode === undefined));
666
+ }
667
+ function isObject(value) {
668
+ return typeof value === "object" && value !== null && !Array.isArray(value);
669
+ }
670
+ function requiredString(value) {
671
+ return typeof value === "string" && value.length > 0;
672
+ }
673
+ function optionalString(value) {
674
+ return value === undefined || typeof value === "string";
675
+ }
676
+ function requiredDate(value) {
677
+ return requiredString(value) && Number.isFinite(Date.parse(value));
678
+ }
679
+ function optionalDate(value) {
680
+ return value === undefined || requiredDate(value);
681
+ }
682
+ function isMissing(error) {
683
+ return isObject(error) && error.code === "ENOENT";
684
+ }
685
+ function cloneRecord(record) {
686
+ return record === undefined ? undefined : structuredClone(record);
687
+ }
688
+ //# sourceMappingURL=continuation-store.js.map