@warlock.js/ai 4.3.0 → 4.5.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 (541) hide show
  1. package/CHANGELOG.md +52 -48
  2. package/cjs/index.cjs +221 -14144
  3. package/cjs/src-C02yzsLs.cjs +22991 -0
  4. package/cjs/src-C02yzsLs.cjs.map +1 -0
  5. package/cjs/src-DFibP2FQ.cjs +30 -0
  6. package/esm/agent/agent-config.type.d.mts +90 -1
  7. package/esm/agent/agent-config.type.d.mts.map +1 -1
  8. package/esm/agent/agent-input-builder.mjs +27 -6
  9. package/esm/agent/agent-input-builder.mjs.map +1 -1
  10. package/esm/agent/agent.d.mts +40 -1
  11. package/esm/agent/agent.d.mts.map +1 -1
  12. package/esm/agent/agent.mjs +203 -30
  13. package/esm/agent/agent.mjs.map +1 -1
  14. package/esm/agent/index.d.mts +2 -1
  15. package/esm/agent/index.mjs +1 -0
  16. package/esm/agent/judge-config.type.d.mts +33 -0
  17. package/esm/agent/judge-config.type.d.mts.map +1 -0
  18. package/esm/agent/judge-config.type.mjs +13 -0
  19. package/esm/agent/judge-config.type.mjs.map +1 -0
  20. package/esm/ai-openai/src/embedder.mjs +4 -0
  21. package/esm/ai-openai/src/index.mjs +4 -0
  22. package/esm/ai-openai/src/model.mjs +5 -0
  23. package/esm/ai-openai/src/sdk.mjs +6 -0
  24. package/esm/ai-openai/src/utils/index.mjs +4 -0
  25. package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
  26. package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
  27. package/esm/ai.d.mts +119 -53
  28. package/esm/ai.d.mts.map +1 -1
  29. package/esm/ai.mjs +43 -8
  30. package/esm/ai.mjs.map +1 -1
  31. package/esm/batch/batch.d.mts.map +1 -1
  32. package/esm/batch/batch.mjs +21 -1
  33. package/esm/batch/batch.mjs.map +1 -1
  34. package/esm/batch/batch.type.d.mts +11 -4
  35. package/esm/batch/batch.type.d.mts.map +1 -1
  36. package/esm/config.d.mts +39 -3
  37. package/esm/config.d.mts.map +1 -1
  38. package/esm/config.mjs +26 -2
  39. package/esm/config.mjs.map +1 -1
  40. package/esm/contracts/agent/agent-options.type.d.mts +11 -3
  41. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
  42. package/esm/contracts/agent/eval.type.d.mts +43 -2
  43. package/esm/contracts/agent/eval.type.d.mts.map +1 -1
  44. package/esm/contracts/attachment-policy.type.d.mts +51 -0
  45. package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
  46. package/esm/contracts/attachment.type.d.mts +16 -7
  47. package/esm/contracts/attachment.type.d.mts.map +1 -1
  48. package/esm/contracts/content-part.type.d.mts +14 -5
  49. package/esm/contracts/content-part.type.d.mts.map +1 -1
  50. package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
  51. package/esm/contracts/index.d.mts +6 -4
  52. package/esm/contracts/model.contract.d.mts +11 -8
  53. package/esm/contracts/model.contract.d.mts.map +1 -1
  54. package/esm/contracts/orchestrator/index.d.mts +1 -0
  55. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
  56. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
  57. package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
  58. package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
  59. package/esm/contracts/planner/index.d.mts +1 -1
  60. package/esm/contracts/planner/planner-config.type.d.mts +31 -0
  61. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
  62. package/esm/contracts/planner/planner-execute-options.type.d.mts +48 -1
  63. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
  64. package/esm/contracts/planner/planner-result.type.d.mts +19 -5
  65. package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
  66. package/esm/contracts/planner/planner.contract.d.mts +1 -1
  67. package/esm/contracts/result/agent-result.type.d.mts +6 -4
  68. package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
  69. package/esm/contracts/result/base-report.type.d.mts +21 -3
  70. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  71. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  72. package/esm/contracts/result/execution-report.type.d.mts +53 -1
  73. package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
  74. package/esm/contracts/result/orchestrator-result.type.d.mts +15 -11
  75. package/esm/contracts/result/orchestrator-result.type.d.mts.map +1 -1
  76. package/esm/contracts/result/supervisor-result.type.d.mts +13 -1
  77. package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
  78. package/esm/contracts/result/tool-call.type.d.mts +2 -2
  79. package/esm/contracts/result/tool-call.type.d.mts.map +1 -1
  80. package/esm/contracts/result/workflow-result.type.d.mts +1 -0
  81. package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
  82. package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
  83. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
  84. package/esm/contracts/system-prompt.contract.d.mts +103 -1
  85. package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
  86. package/esm/contracts/team/index.d.mts +1 -0
  87. package/esm/contracts/team/team-config.type.d.mts +127 -0
  88. package/esm/contracts/team/team-config.type.d.mts.map +1 -0
  89. package/esm/contracts/tool.contract.d.mts +4 -2
  90. package/esm/contracts/tool.contract.d.mts.map +1 -1
  91. package/esm/contracts/workflow/step.contract.d.mts +30 -5
  92. package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
  93. package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
  94. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
  95. package/esm/errors/error-code.type.d.mts +1 -1
  96. package/esm/errors/index.d.mts +1 -0
  97. package/esm/errors/index.mjs +1 -0
  98. package/esm/errors/outbound-policy-error.d.mts +27 -0
  99. package/esm/errors/outbound-policy-error.d.mts.map +1 -0
  100. package/esm/errors/outbound-policy-error.mjs +32 -0
  101. package/esm/errors/outbound-policy-error.mjs.map +1 -0
  102. package/esm/eval/dataset.d.mts +28 -0
  103. package/esm/eval/dataset.d.mts.map +1 -0
  104. package/esm/eval/dataset.mjs +112 -0
  105. package/esm/eval/dataset.mjs.map +1 -0
  106. package/esm/eval/dataset.type.d.mts +53 -0
  107. package/esm/eval/dataset.type.d.mts.map +1 -0
  108. package/esm/eval/eval-runner.d.mts.map +1 -1
  109. package/esm/eval/eval-runner.mjs +16 -2
  110. package/esm/eval/eval-runner.mjs.map +1 -1
  111. package/esm/eval/index.d.mts +20 -1
  112. package/esm/eval/index.d.mts.map +1 -1
  113. package/esm/eval/index.mjs +20 -2
  114. package/esm/eval/index.mjs.map +1 -1
  115. package/esm/eval/regression.d.mts +30 -0
  116. package/esm/eval/regression.d.mts.map +1 -0
  117. package/esm/eval/regression.mjs +51 -0
  118. package/esm/eval/regression.mjs.map +1 -0
  119. package/esm/eval/report-json.d.mts +30 -0
  120. package/esm/eval/report-json.d.mts.map +1 -0
  121. package/esm/eval/report-json.mjs +33 -0
  122. package/esm/eval/report-json.mjs.map +1 -0
  123. package/esm/eval/report-junit.d.mts +22 -0
  124. package/esm/eval/report-junit.d.mts.map +1 -0
  125. package/esm/eval/report-junit.mjs +60 -0
  126. package/esm/eval/report-junit.mjs.map +1 -0
  127. package/esm/guard/contracts/guard-options.type.d.mts +165 -0
  128. package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
  129. package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
  130. package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
  131. package/esm/guard/contracts/index.d.mts +4 -0
  132. package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
  133. package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
  134. package/esm/guard/contracts/verdict.type.d.mts +76 -0
  135. package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
  136. package/esm/guard/detectors/index.d.mts +4 -0
  137. package/esm/guard/detectors/index.mjs +6 -0
  138. package/esm/guard/detectors/injection.d.mts +34 -0
  139. package/esm/guard/detectors/injection.d.mts.map +1 -0
  140. package/esm/guard/detectors/injection.mjs +254 -0
  141. package/esm/guard/detectors/injection.mjs.map +1 -0
  142. package/esm/guard/detectors/moderation.d.mts +32 -0
  143. package/esm/guard/detectors/moderation.d.mts.map +1 -0
  144. package/esm/guard/detectors/moderation.mjs +134 -0
  145. package/esm/guard/detectors/moderation.mjs.map +1 -0
  146. package/esm/guard/detectors/pii.d.mts +41 -0
  147. package/esm/guard/detectors/pii.d.mts.map +1 -0
  148. package/esm/guard/detectors/pii.mjs +199 -0
  149. package/esm/guard/detectors/pii.mjs.map +1 -0
  150. package/esm/guard/detectors/topic.d.mts +29 -0
  151. package/esm/guard/detectors/topic.d.mts.map +1 -0
  152. package/esm/guard/detectors/topic.mjs +99 -0
  153. package/esm/guard/detectors/topic.mjs.map +1 -0
  154. package/esm/guard/errors.d.mts +12 -0
  155. package/esm/guard/errors.d.mts.map +1 -0
  156. package/esm/guard/errors.mjs +18 -0
  157. package/esm/guard/errors.mjs.map +1 -0
  158. package/esm/guard/guard.d.mts +67 -0
  159. package/esm/guard/guard.d.mts.map +1 -0
  160. package/esm/guard/guard.mjs +209 -0
  161. package/esm/guard/guard.mjs.map +1 -0
  162. package/esm/guard/guardrail.d.mts +39 -0
  163. package/esm/guard/guardrail.d.mts.map +1 -0
  164. package/esm/guard/guardrail.mjs +22 -0
  165. package/esm/guard/guardrail.mjs.map +1 -0
  166. package/esm/human/contracts/approval.type.d.mts +154 -0
  167. package/esm/human/contracts/approval.type.d.mts.map +1 -0
  168. package/esm/human/contracts/human-approval.type.d.mts +38 -0
  169. package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
  170. package/esm/human/contracts/index.d.mts +4 -0
  171. package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
  172. package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
  173. package/esm/human/contracts/resume.type.d.mts +77 -0
  174. package/esm/human/contracts/resume.type.d.mts.map +1 -0
  175. package/esm/human/errors.d.mts +84 -0
  176. package/esm/human/errors.d.mts.map +1 -0
  177. package/esm/human/errors.mjs +60 -0
  178. package/esm/human/errors.mjs.map +1 -0
  179. package/esm/human/human-approval.d.mts +57 -0
  180. package/esm/human/human-approval.d.mts.map +1 -0
  181. package/esm/human/human-approval.mjs +170 -0
  182. package/esm/human/human-approval.mjs.map +1 -0
  183. package/esm/human/policy.d.mts +55 -0
  184. package/esm/human/policy.d.mts.map +1 -0
  185. package/esm/human/policy.mjs +67 -0
  186. package/esm/human/policy.mjs.map +1 -0
  187. package/esm/human/register.mjs +37 -0
  188. package/esm/human/register.mjs.map +1 -0
  189. package/esm/human/resume-seed.mjs +53 -0
  190. package/esm/human/resume-seed.mjs.map +1 -0
  191. package/esm/human/resume.d.mts +54 -0
  192. package/esm/human/resume.d.mts.map +1 -0
  193. package/esm/human/resume.mjs +121 -0
  194. package/esm/human/resume.mjs.map +1 -0
  195. package/esm/human/stores/index.mjs +5 -0
  196. package/esm/human/stores/memory.d.mts +30 -0
  197. package/esm/human/stores/memory.d.mts.map +1 -0
  198. package/esm/human/stores/memory.mjs +91 -0
  199. package/esm/human/stores/memory.mjs.map +1 -0
  200. package/esm/human/stores/pg.d.mts +59 -0
  201. package/esm/human/stores/pg.d.mts.map +1 -0
  202. package/esm/human/stores/pg.mjs +220 -0
  203. package/esm/human/stores/pg.mjs.map +1 -0
  204. package/esm/human/stores/redis.d.mts +56 -0
  205. package/esm/human/stores/redis.d.mts.map +1 -0
  206. package/esm/human/stores/redis.mjs +201 -0
  207. package/esm/human/stores/redis.mjs.map +1 -0
  208. package/esm/index.d.mts +113 -22
  209. package/esm/index.mjs +82 -11
  210. package/esm/middleware/builtins/budget.mjs +6 -2
  211. package/esm/middleware/builtins/budget.mjs.map +1 -1
  212. package/esm/middleware/utils/extract-user-text.d.mts +8 -1
  213. package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
  214. package/esm/middleware/utils/extract-user-text.mjs +8 -1
  215. package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
  216. package/esm/object-stream/index.d.mts +2 -0
  217. package/esm/object-stream/index.mjs +4 -0
  218. package/esm/object-stream/parse-partial-json.d.mts +22 -0
  219. package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
  220. package/esm/object-stream/parse-partial-json.mjs +78 -0
  221. package/esm/object-stream/parse-partial-json.mjs.map +1 -0
  222. package/esm/object-stream/stream-object.d.mts +68 -0
  223. package/esm/object-stream/stream-object.d.mts.map +1 -0
  224. package/esm/object-stream/stream-object.mjs +104 -0
  225. package/esm/object-stream/stream-object.mjs.map +1 -0
  226. package/esm/observe/index.mjs +4 -0
  227. package/esm/observe/observer-registry.d.mts +30 -0
  228. package/esm/observe/observer-registry.d.mts.map +1 -0
  229. package/esm/observe/observer-registry.mjs +51 -0
  230. package/esm/observe/observer-registry.mjs.map +1 -0
  231. package/esm/observe/observer.contract.d.mts +40 -0
  232. package/esm/observe/observer.contract.d.mts.map +1 -0
  233. package/esm/observe/resolve-observers.d.mts +40 -0
  234. package/esm/observe/resolve-observers.d.mts.map +1 -0
  235. package/esm/observe/resolve-observers.mjs +73 -0
  236. package/esm/observe/resolve-observers.mjs.map +1 -0
  237. package/esm/orchestrator/execution.d.mts.map +1 -1
  238. package/esm/orchestrator/execution.mjs +5 -2
  239. package/esm/orchestrator/execution.mjs.map +1 -1
  240. package/esm/orchestrator/index.d.mts +1 -0
  241. package/esm/orchestrator/index.mjs +1 -0
  242. package/esm/orchestrator/orchestrator.d.mts.map +1 -1
  243. package/esm/orchestrator/orchestrator.mjs +39 -6
  244. package/esm/orchestrator/orchestrator.mjs.map +1 -1
  245. package/esm/orchestrator/session-lock.d.mts +25 -0
  246. package/esm/orchestrator/session-lock.d.mts.map +1 -0
  247. package/esm/orchestrator/session-lock.mjs +83 -0
  248. package/esm/orchestrator/session-lock.mjs.map +1 -0
  249. package/esm/planner/dag-scheduler.mjs +97 -0
  250. package/esm/planner/dag-scheduler.mjs.map +1 -0
  251. package/esm/planner/plan-prompt.d.mts +1 -1
  252. package/esm/planner/plan-prompt.d.mts.map +1 -1
  253. package/esm/planner/plan-prompt.mjs +2 -1
  254. package/esm/planner/plan-prompt.mjs.map +1 -1
  255. package/esm/planner/plan-schema.mjs +17 -14
  256. package/esm/planner/plan-schema.mjs.map +1 -1
  257. package/esm/planner/planner-run.d.mts.map +1 -1
  258. package/esm/planner/planner-run.mjs +300 -29
  259. package/esm/planner/planner-run.mjs.map +1 -1
  260. package/esm/planner/planner.mjs +1 -1
  261. package/esm/planner/planner.mjs.map +1 -1
  262. package/esm/prompt/errors.d.mts +57 -0
  263. package/esm/prompt/errors.d.mts.map +1 -0
  264. package/esm/prompt/errors.mjs +73 -0
  265. package/esm/prompt/errors.mjs.map +1 -0
  266. package/esm/prompt/index.d.mts +3 -0
  267. package/esm/prompt/index.mjs +4 -0
  268. package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
  269. package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
  270. package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
  271. package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
  272. package/esm/prompt/prompt-validate.mjs +170 -0
  273. package/esm/prompt/prompt-validate.mjs.map +1 -0
  274. package/esm/prompt/prompt.d.mts +54 -0
  275. package/esm/prompt/prompt.d.mts.map +1 -0
  276. package/esm/prompt/prompt.mjs +218 -0
  277. package/esm/prompt/prompt.mjs.map +1 -0
  278. package/esm/prompt/prompt.type.d.mts +174 -0
  279. package/esm/prompt/prompt.type.d.mts.map +1 -0
  280. package/esm/prompts/index.d.mts +3 -0
  281. package/esm/prompts/index.mjs +3 -0
  282. package/esm/prompts/prompts-manager.contract.d.mts +154 -0
  283. package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
  284. package/esm/prompts/prompts-manager.d.mts +38 -0
  285. package/esm/prompts/prompts-manager.d.mts.map +1 -0
  286. package/esm/prompts/prompts-manager.mjs +410 -0
  287. package/esm/prompts/prompts-manager.mjs.map +1 -0
  288. package/esm/prompts/prompts-manager.type.d.mts +172 -0
  289. package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
  290. package/esm/prompts/prompts-validate.mjs +200 -0
  291. package/esm/prompts/prompts-validate.mjs.map +1 -0
  292. package/esm/rag/as-tool.mjs +48 -0
  293. package/esm/rag/as-tool.mjs.map +1 -0
  294. package/esm/rag/chunk/chunk.d.mts +24 -0
  295. package/esm/rag/chunk/chunk.d.mts.map +1 -0
  296. package/esm/rag/chunk/chunk.mjs +44 -0
  297. package/esm/rag/chunk/chunk.mjs.map +1 -0
  298. package/esm/rag/chunk/fixed.mjs +32 -0
  299. package/esm/rag/chunk/fixed.mjs.map +1 -0
  300. package/esm/rag/chunk/markdown.mjs +75 -0
  301. package/esm/rag/chunk/markdown.mjs.map +1 -0
  302. package/esm/rag/chunk/recursive.mjs +132 -0
  303. package/esm/rag/chunk/recursive.mjs.map +1 -0
  304. package/esm/rag/chunk/sentence.mjs +73 -0
  305. package/esm/rag/chunk/sentence.mjs.map +1 -0
  306. package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
  307. package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
  308. package/esm/rag/contracts/citation.type.d.mts +35 -0
  309. package/esm/rag/contracts/citation.type.d.mts.map +1 -0
  310. package/esm/rag/contracts/index.d.mts +4 -0
  311. package/esm/rag/contracts/rag-config.type.d.mts +68 -0
  312. package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
  313. package/esm/rag/contracts/rag-document.type.d.mts +21 -0
  314. package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
  315. package/esm/rag/hybrid/bm25.d.mts +23 -0
  316. package/esm/rag/hybrid/bm25.d.mts.map +1 -0
  317. package/esm/rag/hybrid/bm25.mjs +51 -0
  318. package/esm/rag/hybrid/bm25.mjs.map +1 -0
  319. package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
  320. package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
  321. package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
  322. package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
  323. package/esm/rag/hybrid/rrf.d.mts +25 -0
  324. package/esm/rag/hybrid/rrf.d.mts.map +1 -0
  325. package/esm/rag/hybrid/rrf.mjs +30 -0
  326. package/esm/rag/hybrid/rrf.mjs.map +1 -0
  327. package/esm/rag/index.d.mts +15 -0
  328. package/esm/rag/index.mjs +11 -0
  329. package/esm/rag/rag.d.mts +38 -0
  330. package/esm/rag/rag.d.mts.map +1 -0
  331. package/esm/rag/rag.mjs +126 -0
  332. package/esm/rag/rag.mjs.map +1 -0
  333. package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
  334. package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
  335. package/esm/rag/rerank/keyword-reranker.mjs +58 -0
  336. package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
  337. package/esm/rag/rerank/llm-reranker.d.mts +36 -0
  338. package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
  339. package/esm/rag/rerank/llm-reranker.mjs +85 -0
  340. package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
  341. package/esm/rag/rerank/reranker.contract.d.mts +28 -0
  342. package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
  343. package/esm/rag/retrieve.mjs +68 -0
  344. package/esm/rag/retrieve.mjs.map +1 -0
  345. package/esm/rag/store/cache-vector-store.d.mts +27 -0
  346. package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
  347. package/esm/rag/store/cache-vector-store.mjs +48 -0
  348. package/esm/rag/store/cache-vector-store.mjs.map +1 -0
  349. package/esm/rag/store/vector-store.contract.d.mts +38 -0
  350. package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
  351. package/esm/rag/transforms/multi-query.d.mts +27 -0
  352. package/esm/rag/transforms/multi-query.d.mts.map +1 -0
  353. package/esm/rag/transforms/multi-query.mjs +41 -0
  354. package/esm/rag/transforms/multi-query.mjs.map +1 -0
  355. package/esm/security/index.mjs +5 -0
  356. package/esm/security/outbound-policy.d.mts +46 -0
  357. package/esm/security/outbound-policy.d.mts.map +1 -0
  358. package/esm/security/outbound-policy.mjs +187 -0
  359. package/esm/security/outbound-policy.mjs.map +1 -0
  360. package/esm/security/outbound-policy.type.d.mts +74 -0
  361. package/esm/security/outbound-policy.type.d.mts.map +1 -0
  362. package/esm/security/private-ip.d.mts +15 -0
  363. package/esm/security/private-ip.d.mts.map +1 -0
  364. package/esm/security/private-ip.mjs +48 -0
  365. package/esm/security/private-ip.mjs.map +1 -0
  366. package/esm/security/redact.d.mts +59 -0
  367. package/esm/security/redact.d.mts.map +1 -0
  368. package/esm/security/redact.mjs +122 -0
  369. package/esm/security/redact.mjs.map +1 -0
  370. package/esm/serve/serve.d.mts +50 -0
  371. package/esm/serve/serve.d.mts.map +1 -0
  372. package/esm/serve/serve.mjs +90 -0
  373. package/esm/serve/serve.mjs.map +1 -0
  374. package/esm/serve/sse.d.mts +20 -0
  375. package/esm/serve/sse.d.mts.map +1 -0
  376. package/esm/serve/sse.mjs +25 -0
  377. package/esm/serve/sse.mjs.map +1 -0
  378. package/esm/serve/stream-to-sse.d.mts +29 -0
  379. package/esm/serve/stream-to-sse.d.mts.map +1 -0
  380. package/esm/serve/stream-to-sse.mjs +37 -0
  381. package/esm/serve/stream-to-sse.mjs.map +1 -0
  382. package/esm/skills/catalog.d.mts +49 -0
  383. package/esm/skills/catalog.d.mts.map +1 -0
  384. package/esm/skills/catalog.mjs +140 -0
  385. package/esm/skills/catalog.mjs.map +1 -0
  386. package/esm/skills/contracts/skill-record.type.d.mts +37 -0
  387. package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
  388. package/esm/skills/contracts/skills-config.type.d.mts +108 -0
  389. package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
  390. package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
  391. package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
  392. package/esm/skills/contracts/skills.contract.d.mts +43 -0
  393. package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
  394. package/esm/skills/index.d.mts +16 -0
  395. package/esm/skills/index.mjs +14 -0
  396. package/esm/skills/load-skill-tool.d.mts +38 -0
  397. package/esm/skills/load-skill-tool.d.mts.map +1 -0
  398. package/esm/skills/load-skill-tool.mjs +65 -0
  399. package/esm/skills/load-skill-tool.mjs.map +1 -0
  400. package/esm/skills/review-gate.d.mts +33 -0
  401. package/esm/skills/review-gate.d.mts.map +1 -0
  402. package/esm/skills/review-gate.mjs +60 -0
  403. package/esm/skills/review-gate.mjs.map +1 -0
  404. package/esm/skills/save-skill-tool.d.mts +39 -0
  405. package/esm/skills/save-skill-tool.d.mts.map +1 -0
  406. package/esm/skills/save-skill-tool.mjs +65 -0
  407. package/esm/skills/save-skill-tool.mjs.map +1 -0
  408. package/esm/skills/skills.d.mts +33 -0
  409. package/esm/skills/skills.d.mts.map +1 -0
  410. package/esm/skills/skills.mjs +109 -0
  411. package/esm/skills/skills.mjs.map +1 -0
  412. package/esm/skills/sources/directory-source.d.mts +19 -0
  413. package/esm/skills/sources/directory-source.d.mts.map +1 -0
  414. package/esm/skills/sources/directory-source.mjs +108 -0
  415. package/esm/skills/sources/directory-source.mjs.map +1 -0
  416. package/esm/skills/sources/index.d.mts +18 -0
  417. package/esm/skills/sources/index.d.mts.map +1 -0
  418. package/esm/skills/sources/index.mjs +27 -0
  419. package/esm/skills/sources/index.mjs.map +1 -0
  420. package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
  421. package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
  422. package/esm/skills/sources/parse-frontmatter.mjs +46 -0
  423. package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
  424. package/esm/skills/sources/store-source.d.mts +14 -0
  425. package/esm/skills/sources/store-source.d.mts.map +1 -0
  426. package/esm/skills/sources/store-source.mjs +15 -0
  427. package/esm/skills/sources/store-source.mjs.map +1 -0
  428. package/esm/skills/sources/url-source.d.mts +29 -0
  429. package/esm/skills/sources/url-source.d.mts.map +1 -0
  430. package/esm/skills/sources/url-source.mjs +117 -0
  431. package/esm/skills/sources/url-source.mjs.map +1 -0
  432. package/esm/skills/store/mock-skills-store.d.mts +57 -0
  433. package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
  434. package/esm/skills/store/mock-skills-store.mjs +100 -0
  435. package/esm/skills/store/mock-skills-store.mjs.map +1 -0
  436. package/esm/skills/store/procedural-skill-store.d.mts +30 -0
  437. package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
  438. package/esm/skills/store/procedural-skill-store.mjs +125 -0
  439. package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
  440. package/esm/supervisor/as-tool.mjs +2 -2
  441. package/esm/supervisor/as-tool.mjs.map +1 -1
  442. package/esm/supervisor/execution.d.mts.map +1 -1
  443. package/esm/supervisor/execution.mjs +31 -28
  444. package/esm/supervisor/execution.mjs.map +1 -1
  445. package/esm/supervisor/supervisor.d.mts.map +1 -1
  446. package/esm/supervisor/supervisor.mjs +8 -3
  447. package/esm/supervisor/supervisor.mjs.map +1 -1
  448. package/esm/system-prompt/index.d.mts +4 -0
  449. package/esm/system-prompt/system-prompt.d.mts +68 -4
  450. package/esm/system-prompt/system-prompt.d.mts.map +1 -1
  451. package/esm/system-prompt/system-prompt.mjs +89 -5
  452. package/esm/system-prompt/system-prompt.mjs.map +1 -1
  453. package/esm/team/gates.mjs +48 -0
  454. package/esm/team/gates.mjs.map +1 -0
  455. package/esm/team/index.d.mts +1 -0
  456. package/esm/team/index.mjs +3 -0
  457. package/esm/team/team.d.mts +42 -0
  458. package/esm/team/team.d.mts.map +1 -0
  459. package/esm/team/team.mjs +94 -0
  460. package/esm/team/team.mjs.map +1 -0
  461. package/esm/tool/executable-as-tool.d.mts.map +1 -1
  462. package/esm/tool/executable-as-tool.mjs +2 -2
  463. package/esm/tool/executable-as-tool.mjs.map +1 -1
  464. package/esm/tool/tool.d.mts.map +1 -1
  465. package/esm/tool/tool.mjs +2 -2
  466. package/esm/tool/tool.mjs.map +1 -1
  467. package/esm/utils/compute-cost.d.mts +17 -1
  468. package/esm/utils/compute-cost.d.mts.map +1 -1
  469. package/esm/utils/compute-cost.mjs +26 -1
  470. package/esm/utils/compute-cost.mjs.map +1 -1
  471. package/esm/utils/extract-json-lenient.d.mts +42 -0
  472. package/esm/utils/extract-json-lenient.d.mts.map +1 -0
  473. package/esm/utils/extract-json-lenient.mjs +97 -0
  474. package/esm/utils/extract-json-lenient.mjs.map +1 -0
  475. package/esm/utils/index.d.mts +4 -2
  476. package/esm/utils/index.mjs +3 -1
  477. package/esm/utils/json-schema.d.mts +1 -1
  478. package/esm/utils/prepare-attachment-part.d.mts +10 -1
  479. package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
  480. package/esm/utils/prepare-attachment-part.mjs +103 -11
  481. package/esm/utils/prepare-attachment-part.mjs.map +1 -1
  482. package/esm/utils/resolve-attachment.d.mts +4 -3
  483. package/esm/utils/resolve-attachment.d.mts.map +1 -1
  484. package/esm/utils/resolve-attachment.mjs +4 -3
  485. package/esm/utils/resolve-attachment.mjs.map +1 -1
  486. package/esm/utils/run-context.d.mts +94 -0
  487. package/esm/utils/run-context.d.mts.map +1 -0
  488. package/esm/utils/run-context.mjs +98 -0
  489. package/esm/utils/run-context.mjs.map +1 -0
  490. package/esm/vcr/cassette-io.mjs +57 -0
  491. package/esm/vcr/cassette-io.mjs.map +1 -0
  492. package/esm/vcr/errors.d.mts +42 -0
  493. package/esm/vcr/errors.d.mts.map +1 -0
  494. package/esm/vcr/errors.mjs +37 -0
  495. package/esm/vcr/errors.mjs.map +1 -0
  496. package/esm/vcr/hash-request.d.mts +28 -0
  497. package/esm/vcr/hash-request.d.mts.map +1 -0
  498. package/esm/vcr/hash-request.mjs +118 -0
  499. package/esm/vcr/hash-request.mjs.map +1 -0
  500. package/esm/vcr/index.d.mts +4 -0
  501. package/esm/vcr/index.mjs +5 -0
  502. package/esm/vcr/vcr.d.mts +32 -0
  503. package/esm/vcr/vcr.d.mts.map +1 -0
  504. package/esm/vcr/vcr.mjs +248 -0
  505. package/esm/vcr/vcr.mjs.map +1 -0
  506. package/esm/vcr/vcr.type.d.mts +118 -0
  507. package/esm/vcr/vcr.type.d.mts.map +1 -0
  508. package/esm/workflow/as-tool.mjs +2 -2
  509. package/esm/workflow/as-tool.mjs.map +1 -1
  510. package/esm/workflow/engine.mjs +1 -0
  511. package/esm/workflow/engine.mjs.map +1 -1
  512. package/esm/workflow/step-runner.mjs +19 -20
  513. package/esm/workflow/step-runner.mjs.map +1 -1
  514. package/esm/workflow/workflow.d.mts.map +1 -1
  515. package/esm/workflow/workflow.mjs +8 -3
  516. package/esm/workflow/workflow.mjs.map +1 -1
  517. package/llms-full.txt +1683 -68
  518. package/llms.txt +16 -4
  519. package/package.json +7 -3
  520. package/skills/README.md +40 -4
  521. package/skills/ai-dx-helpers/SKILL.md +2 -2
  522. package/skills/approve-tool-calls/SKILL.md +134 -0
  523. package/skills/attach-ai-middleware/SKILL.md +1 -1
  524. package/skills/detect-and-redact-pii/SKILL.md +104 -0
  525. package/skills/durable-resume/SKILL.md +128 -0
  526. package/skills/escalate-block-to-human/SKILL.md +85 -0
  527. package/skills/eval-datasets-and-ci/SKILL.md +117 -0
  528. package/skills/guard-input-output/SKILL.md +117 -0
  529. package/skills/manage-prompts/SKILL.md +186 -0
  530. package/skills/observe-ai-flows/SKILL.md +94 -0
  531. package/skills/record-replay-llm/SKILL.md +92 -0
  532. package/skills/run-ai-agent/SKILL.md +26 -1
  533. package/skills/run-ai-rag/SKILL.md +139 -0
  534. package/skills/run-ai-team/SKILL.md +107 -0
  535. package/skills/run-orchestrator/SKILL.md +2 -0
  536. package/skills/run-planner/SKILL.md +73 -8
  537. package/skills/run-supervisor/SKILL.md +20 -1
  538. package/skills/use-runtime-skills/SKILL.md +106 -0
  539. package/skills/write-system-prompt/SKILL.md +30 -1
  540. package/cjs/index.cjs.map +0 -1
  541. package/esm/contracts/result/index.d.mts +0 -15
@@ -0,0 +1,220 @@
1
+ //#region ../@warlock.js/ai/src/human/stores/pg.ts
2
+ /**
3
+ * Default backing table — provisions the store with no extra config when
4
+ * the dev runs {@link InterruptStore.schema} through their migration tool.
5
+ */
6
+ const DEFAULT_TABLE = "warlock_ai_human_interrupts";
7
+ /**
8
+ * Allowed characters in a Postgres identifier (table name). The table name
9
+ * is interpolated into DDL/DML, so anything outside this conservative
10
+ * ASCII subset is rejected — interpolating an arbitrary string would be a
11
+ * SQL-injection footgun (mirrors `@warlock.js/ai`'s pg stores).
12
+ */
13
+ const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
14
+ /**
15
+ * Module specifier for the optional `pg` driver. Held in a `string`
16
+ * variable so the dynamic `import()` is not statically resolved at
17
+ * compile time — `pg` is an optional peer that need not be installed for
18
+ * this package to type-check or for a memory-only consumer to run.
19
+ */
20
+ const PG_MODULE = "pg";
21
+ /**
22
+ * Curated install string surfaced (at use time) when a `connectionString`
23
+ * is configured but the optional `pg` driver is absent. Never thrown at
24
+ * import — a memory-only consumer must be able to load this module.
25
+ */
26
+ const PG_INSTALL_INSTRUCTIONS = `
27
+ The @warlock.js/ai Postgres interrupt store requires the pg package.
28
+ Install it with:
29
+
30
+ npm install pg
31
+
32
+ Or with your preferred package manager:
33
+
34
+ pnpm add pg
35
+ yarn add pg
36
+ `.trim();
37
+ /**
38
+ * Lazily import `pg` and return a `Pool` built from `connectionString`. A
39
+ * bare `catch` rethrows the curated install string — a missing optional
40
+ * peer surfaces as actionable guidance, never a raw resolution error.
41
+ */
42
+ async function buildPgClient(connectionString) {
43
+ let sdk;
44
+ try {
45
+ sdk = await import(PG_MODULE);
46
+ } catch {
47
+ throw new Error(PG_INSTALL_INSTRUCTIONS);
48
+ }
49
+ return new sdk.Pool({ connectionString });
50
+ }
51
+ /**
52
+ * Coerce a Postgres timestamp/text column to an ISO string. `pg` returns
53
+ * `TIMESTAMPTZ` as a `Date`; normalize to the ISO wire shape the
54
+ * {@link PendingInterrupt} contract declares.
55
+ */
56
+ function toIso(value) {
57
+ if (value instanceof Date) return value.toISOString();
58
+ return value;
59
+ }
60
+ /**
61
+ * Map a raw DB row to a {@link PendingInterrupt}. Column names match the
62
+ * reference DDL 1:1; the `request` JSONB rides one column, so it is parsed
63
+ * defensively (node-postgres parses `JSONB` already, but some pool
64
+ * wrappers hand back the raw string).
65
+ */
66
+ function rowToRecord(row) {
67
+ const request = typeof row.request === "string" ? JSON.parse(row.request) : row.request;
68
+ return {
69
+ interruptId: row.interrupt_id,
70
+ request,
71
+ status: row.status,
72
+ savedAt: toIso(row.saved_at)
73
+ };
74
+ }
75
+ /**
76
+ * Postgres-backed {@link InterruptStore} — one durable row per pending
77
+ * interrupt, keyed by `interrupt_id`.
78
+ *
79
+ * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so
80
+ * a reviewer can rule out-of-process (a webhook approves hours later, in a
81
+ * different process), the reference DDL via {@link PgInterruptStore.schema},
82
+ * and prefix-filtered enumeration. Does NOT own: the connection lifecycle
83
+ * (a dev-supplied client is never closed; a store-built `Pool` from a
84
+ * `connectionString` is also left open for the process to reuse) or schema
85
+ * migration (the dev runs `schema()` through their own tool — never
86
+ * auto-migrated).
87
+ *
88
+ * Like the snapshot store, a call has exactly one live interrupt, so
89
+ * `save()` upserts on the `interrupt_id` primary key.
90
+ *
91
+ * Front it with the {@link pg} factory — callers never `new` it.
92
+ */
93
+ var PgInterruptStore = class {
94
+ constructor(options) {
95
+ const table = options.table ?? DEFAULT_TABLE;
96
+ if (!SAFE_IDENTIFIER.test(table)) throw new TypeError(`ai.human.interrupt.pg: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`);
97
+ this.table = table;
98
+ if (options.client) {
99
+ if (typeof options.client.query !== "function") throw new TypeError("ai.human.interrupt.pg requires a 'client' option implementing { query(text, params) } — pass a pg.Pool or pg.Client.");
100
+ this.clientPromise = Promise.resolve(options.client);
101
+ return;
102
+ }
103
+ if (options.connectionString) {
104
+ this.clientPromise = buildPgClient(options.connectionString);
105
+ return;
106
+ }
107
+ throw new TypeError("ai.human.interrupt.pg requires either a 'client' or a 'connectionString' option.");
108
+ }
109
+ /**
110
+ * Resolve the backing client, surfacing the lazy `pg` import's curated
111
+ * install string on the first call that needs it.
112
+ */
113
+ client() {
114
+ return this.clientPromise;
115
+ }
116
+ /**
117
+ * Persist a pending interrupt, keyed by its own `interrupt_id`. Upserts
118
+ * — a call has exactly one live interrupt, so a second save for the same
119
+ * id overwrites the payload rather than appending.
120
+ */
121
+ async save(record) {
122
+ await (await this.client()).query(`INSERT INTO ${this.table} (interrupt_id, request, status, saved_at)
123
+ VALUES ($1, $2::jsonb, $3, $4)
124
+ ON CONFLICT (interrupt_id) DO UPDATE
125
+ SET request = EXCLUDED.request,
126
+ status = EXCLUDED.status,
127
+ saved_at = EXCLUDED.saved_at`, [
128
+ record.interruptId,
129
+ JSON.stringify(record.request),
130
+ record.status,
131
+ record.savedAt
132
+ ]);
133
+ }
134
+ /**
135
+ * Load the interrupt for an `interruptId`, or `undefined` when none is
136
+ * recorded.
137
+ */
138
+ async load(interruptId) {
139
+ const { rows } = await (await this.client()).query(`SELECT interrupt_id, request, status, saved_at
140
+ FROM ${this.table}
141
+ WHERE interrupt_id = $1`, [interruptId]);
142
+ if (rows.length === 0) return;
143
+ return rowToRecord(rows[0]);
144
+ }
145
+ /**
146
+ * Drop the interrupt for an `interruptId`. Idempotent — deleting an
147
+ * absent id deletes zero rows.
148
+ */
149
+ async delete(interruptId) {
150
+ await (await this.client()).query(`DELETE FROM ${this.table} WHERE interrupt_id = $1`, [interruptId]);
151
+ }
152
+ /**
153
+ * List the interrupt ids known to the store, optionally filtered by a
154
+ * prefix. The `_` and `%` LIKE wildcards in the prefix are escaped so an
155
+ * opaque interruptId that happens to contain them is matched literally.
156
+ */
157
+ async list(prefix) {
158
+ const client = await this.client();
159
+ if (prefix === void 0) {
160
+ const { rows } = await client.query(`SELECT interrupt_id FROM ${this.table}`);
161
+ return rows.map((row) => row.interrupt_id);
162
+ }
163
+ const escaped = prefix.replace(/\\/g, "\\\\").replace(/_/g, "\\_").replace(/%/g, "\\%");
164
+ const { rows } = await client.query(`SELECT interrupt_id FROM ${this.table}
165
+ WHERE interrupt_id LIKE $1 ESCAPE '\\'`, [`${escaped}%`]);
166
+ return rows.map((row) => row.interrupt_id);
167
+ }
168
+ /**
169
+ * Return the reference DDL for this store's backing table, interpolating
170
+ * the configured table name. The dev runs it through their migration
171
+ * tool — the framework never auto-migrates.
172
+ *
173
+ * @example
174
+ * await pool.query(store.schema());
175
+ */
176
+ schema() {
177
+ return [
178
+ `CREATE TABLE IF NOT EXISTS ${this.table} (`,
179
+ ` interrupt_id TEXT PRIMARY KEY,`,
180
+ ` request JSONB NOT NULL,`,
181
+ ` status TEXT NOT NULL,`,
182
+ ` saved_at TIMESTAMPTZ NOT NULL DEFAULT now()`,
183
+ `);`,
184
+ `CREATE INDEX IF NOT EXISTS idx_${this.table}_saved_at`,
185
+ ` ON ${this.table} (saved_at);`
186
+ ].join("\n");
187
+ }
188
+ };
189
+ /**
190
+ * Create a Postgres-backed {@link InterruptStore}. Either pass a live
191
+ * `pg.Pool` / `pg.Client` (`{ client }`) — `@warlock.js/ai` never
192
+ * imports `pg` in that case — or a `{ connectionString }` and let the
193
+ * store lazily `import("pg")` to build its own pool. When `pg` is not
194
+ * installed, the curated install string surfaces on first use, never at
195
+ * import. Run {@link InterruptStore.schema} through your migration tool
196
+ * once before use; the store never auto-migrates.
197
+ *
198
+ * @example
199
+ * import { Pool } from "pg";
200
+ * import { ai } from "@warlock.js/ai";
201
+ *
202
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
203
+ * const store = ai.human.interrupt.pg({ client: pool });
204
+ *
205
+ * // Once, via your migration tooling:
206
+ * // await pool.query(store.schema());
207
+ *
208
+ * @example
209
+ * // Let the store build its own pool from a connection string:
210
+ * const store = ai.human.interrupt.pg({
211
+ * connectionString: process.env.DATABASE_URL,
212
+ * });
213
+ */
214
+ function pg(options) {
215
+ return new PgInterruptStore(options);
216
+ }
217
+
218
+ //#endregion
219
+ export { pg };
220
+ //# sourceMappingURL=pg.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/pg.ts"],"sourcesContent":["import type {\n InterruptStore,\n PendingInterrupt,\n PgClientLike,\n} from \"../contracts/interrupt-store.contract\";\n\n/**\n * Options for the Postgres {@link InterruptStore}.\n *\n * Two mutually-supportive ways to supply the connection:\n * - **`client`** — pass an already-built `pg.Pool` / `pg.Client` (anything\n * satisfying {@link PgClientLike}). The store only ever calls `query`\n * and never opens or closes it; a single pool can back both an\n * orchestrator's checkpoint/snapshot stores and this interrupt table.\n * - **`connectionString`** — let the store lazily `import(\"pg\")` and build\n * its own `Pool`. `@warlock.js/ai` takes **no** hard dependency on\n * `pg` (it is an optional peer); when it is absent the store throws a\n * curated install string at first use, never a raw module-resolution\n * stack trace at import.\n *\n * Exactly one of the two must be present.\n */\nexport interface PgInterruptOptions {\n /** An already-built `pg.Pool` / `pg.Client` — anything matching {@link PgClientLike}. */\n client?: PgClientLike;\n\n /** Connection string the store passes to a lazily-imported `pg.Pool`. */\n connectionString?: string;\n\n /**\n * Backing table name. Defaults to `warlock_ai_human_interrupts`. Must be\n * a safe SQL identifier — it is interpolated into DDL/DML.\n */\n table?: string;\n}\n\n/**\n * Default backing table — provisions the store with no extra config when\n * the dev runs {@link InterruptStore.schema} through their migration tool.\n */\nconst DEFAULT_TABLE = \"warlock_ai_human_interrupts\";\n\n/**\n * Allowed characters in a Postgres identifier (table name). The table name\n * is interpolated into DDL/DML, so anything outside this conservative\n * ASCII subset is rejected — interpolating an arbitrary string would be a\n * SQL-injection footgun (mirrors `@warlock.js/ai`'s pg stores).\n */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Module specifier for the optional `pg` driver. Held in a `string`\n * variable so the dynamic `import()` is not statically resolved at\n * compile time — `pg` is an optional peer that need not be installed for\n * this package to type-check or for a memory-only consumer to run.\n */\nconst PG_MODULE = \"pg\";\n\n/**\n * Curated install string surfaced (at use time) when a `connectionString`\n * is configured but the optional `pg` driver is absent. Never thrown at\n * import — a memory-only consumer must be able to load this module.\n */\nconst PG_INSTALL_INSTRUCTIONS = `\nThe @warlock.js/ai Postgres interrupt store requires the pg package.\nInstall it with:\n\n npm install pg\n\nOr with your preferred package manager:\n\n pnpm add pg\n yarn add pg\n`.trim();\n\n/**\n * Minimal structural view of a `pg.Pool` constructor — just enough of the\n * `pg` module surface for the store to build a client when handed a\n * `connectionString`. Declared locally (rather than `typeof import(\"pg\")`)\n * so this module type-checks even when `pg` is not installed.\n */\ninterface PgModuleLike {\n Pool: new (config: { connectionString: string }) => PgClientLike;\n}\n\n/**\n * Lazily import `pg` and return a `Pool` built from `connectionString`. A\n * bare `catch` rethrows the curated install string — a missing optional\n * peer surfaces as actionable guidance, never a raw resolution error.\n */\nasync function buildPgClient(connectionString: string): Promise<PgClientLike> {\n let sdk: PgModuleLike;\n\n try {\n sdk = (await import(PG_MODULE)) as unknown as PgModuleLike;\n } catch {\n throw new Error(PG_INSTALL_INSTRUCTIONS);\n }\n\n return new sdk.Pool({ connectionString });\n}\n\n/**\n * Coerce a Postgres timestamp/text column to an ISO string. `pg` returns\n * `TIMESTAMPTZ` as a `Date`; normalize to the ISO wire shape the\n * {@link PendingInterrupt} contract declares.\n */\nfunction toIso(value: unknown): string {\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return value as string;\n}\n\n/**\n * Map a raw DB row to a {@link PendingInterrupt}. Column names match the\n * reference DDL 1:1; the `request` JSONB rides one column, so it is parsed\n * defensively (node-postgres parses `JSONB` already, but some pool\n * wrappers hand back the raw string).\n */\nfunction rowToRecord(row: Record<string, unknown>): PendingInterrupt {\n const request =\n typeof row.request === \"string\" ? JSON.parse(row.request) : row.request;\n\n return {\n interruptId: row.interrupt_id as string,\n request: request as PendingInterrupt[\"request\"],\n status: row.status as PendingInterrupt[\"status\"],\n savedAt: toIso(row.saved_at),\n };\n}\n\n/**\n * Postgres-backed {@link InterruptStore} — one durable row per pending\n * interrupt, keyed by `interrupt_id`.\n *\n * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so\n * a reviewer can rule out-of-process (a webhook approves hours later, in a\n * different process), the reference DDL via {@link PgInterruptStore.schema},\n * and prefix-filtered enumeration. Does NOT own: the connection lifecycle\n * (a dev-supplied client is never closed; a store-built `Pool` from a\n * `connectionString` is also left open for the process to reuse) or schema\n * migration (the dev runs `schema()` through their own tool — never\n * auto-migrated).\n *\n * Like the snapshot store, a call has exactly one live interrupt, so\n * `save()` upserts on the `interrupt_id` primary key.\n *\n * Front it with the {@link pg} factory — callers never `new` it.\n */\nclass PgInterruptStore implements InterruptStore {\n /** Validated backing table name, safe to interpolate into SQL. */\n private readonly table: string;\n\n /**\n * A ready client, or a promise resolving to one the store builds lazily\n * from a `connectionString`. Resolved once and memoized so the optional\n * `pg` import happens at most once.\n */\n private clientPromise: Promise<PgClientLike>;\n\n public constructor(options: PgInterruptOptions) {\n const table = options.table ?? DEFAULT_TABLE;\n\n if (!SAFE_IDENTIFIER.test(table)) {\n throw new TypeError(\n `ai.human.interrupt.pg: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`,\n );\n }\n\n this.table = table;\n\n if (options.client) {\n if (typeof options.client.query !== \"function\") {\n throw new TypeError(\n \"ai.human.interrupt.pg requires a 'client' option implementing { query(text, params) } — pass a pg.Pool or pg.Client.\",\n );\n }\n\n this.clientPromise = Promise.resolve(options.client);\n\n return;\n }\n\n if (options.connectionString) {\n // Defer the optional `pg` import to first use — a curated install\n // string surfaces from `buildPgClient`, not at construction.\n this.clientPromise = buildPgClient(options.connectionString);\n\n return;\n }\n\n throw new TypeError(\n \"ai.human.interrupt.pg requires either a 'client' or a 'connectionString' option.\",\n );\n }\n\n /**\n * Resolve the backing client, surfacing the lazy `pg` import's curated\n * install string on the first call that needs it.\n */\n private client(): Promise<PgClientLike> {\n return this.clientPromise;\n }\n\n /**\n * Persist a pending interrupt, keyed by its own `interrupt_id`. Upserts\n * — a call has exactly one live interrupt, so a second save for the same\n * id overwrites the payload rather than appending.\n */\n public async save(record: PendingInterrupt): Promise<void> {\n const client = await this.client();\n\n await client.query(\n `INSERT INTO ${this.table} (interrupt_id, request, status, saved_at)\n VALUES ($1, $2::jsonb, $3, $4)\n ON CONFLICT (interrupt_id) DO UPDATE\n SET request = EXCLUDED.request,\n status = EXCLUDED.status,\n saved_at = EXCLUDED.saved_at`,\n [\n record.interruptId,\n JSON.stringify(record.request),\n record.status,\n record.savedAt,\n ],\n );\n }\n\n /**\n * Load the interrupt for an `interruptId`, or `undefined` when none is\n * recorded.\n */\n public async load(\n interruptId: string,\n ): Promise<PendingInterrupt | undefined> {\n const client = await this.client();\n\n const { rows } = await client.query(\n `SELECT interrupt_id, request, status, saved_at\n FROM ${this.table}\n WHERE interrupt_id = $1`,\n [interruptId],\n );\n\n if (rows.length === 0) {\n return undefined;\n }\n\n return rowToRecord(rows[0] as Record<string, unknown>);\n }\n\n /**\n * Drop the interrupt for an `interruptId`. Idempotent — deleting an\n * absent id deletes zero rows.\n */\n public async delete(interruptId: string): Promise<void> {\n const client = await this.client();\n\n await client.query(\n `DELETE FROM ${this.table} WHERE interrupt_id = $1`,\n [interruptId],\n );\n }\n\n /**\n * List the interrupt ids known to the store, optionally filtered by a\n * prefix. The `_` and `%` LIKE wildcards in the prefix are escaped so an\n * opaque interruptId that happens to contain them is matched literally.\n */\n public async list(prefix?: string): Promise<string[]> {\n const client = await this.client();\n\n if (prefix === undefined) {\n const { rows } = await client.query(\n `SELECT interrupt_id FROM ${this.table}`,\n );\n\n return rows.map(\n (row) => (row as Record<string, unknown>).interrupt_id as string,\n );\n }\n\n const escaped = prefix\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/_/g, \"\\\\_\")\n .replace(/%/g, \"\\\\%\");\n\n const { rows } = await client.query(\n `SELECT interrupt_id FROM ${this.table}\n WHERE interrupt_id LIKE $1 ESCAPE '\\\\'`,\n [`${escaped}%`],\n );\n\n return rows.map(\n (row) => (row as Record<string, unknown>).interrupt_id as string,\n );\n }\n\n /**\n * Return the reference DDL for this store's backing table, interpolating\n * the configured table name. The dev runs it through their migration\n * tool — the framework never auto-migrates.\n *\n * @example\n * await pool.query(store.schema());\n */\n public schema(): string {\n return [\n `CREATE TABLE IF NOT EXISTS ${this.table} (`,\n ` interrupt_id TEXT PRIMARY KEY,`,\n ` request JSONB NOT NULL,`,\n ` status TEXT NOT NULL,`,\n ` saved_at TIMESTAMPTZ NOT NULL DEFAULT now()`,\n `);`,\n `CREATE INDEX IF NOT EXISTS idx_${this.table}_saved_at`,\n ` ON ${this.table} (saved_at);`,\n ].join(\"\\n\");\n }\n}\n\n/**\n * Create a Postgres-backed {@link InterruptStore}. Either pass a live\n * `pg.Pool` / `pg.Client` (`{ client }`) — `@warlock.js/ai` never\n * imports `pg` in that case — or a `{ connectionString }` and let the\n * store lazily `import(\"pg\")` to build its own pool. When `pg` is not\n * installed, the curated install string surfaces on first use, never at\n * import. Run {@link InterruptStore.schema} through your migration tool\n * once before use; the store never auto-migrates.\n *\n * @example\n * import { Pool } from \"pg\";\n * import { ai } from \"@warlock.js/ai\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const store = ai.human.interrupt.pg({ client: pool });\n *\n * // Once, via your migration tooling:\n * // await pool.query(store.schema());\n *\n * @example\n * // Let the store build its own pool from a connection string:\n * const store = ai.human.interrupt.pg({\n * connectionString: process.env.DATABASE_URL,\n * });\n */\nexport function pg(options: PgInterruptOptions): InterruptStore {\n return new PgInterruptStore(options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { InterruptStore, RedisClientLike } from "../contracts/interrupt-store.contract.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/human/stores/redis.d.ts
4
+ /**
5
+ * Options for the Redis {@link InterruptStore}.
6
+ *
7
+ * Two mutually-supportive ways to supply the connection:
8
+ * - **`client`** — pass an already-connected `redis` client (anything
9
+ * satisfying {@link RedisClientLike}). The store only calls
10
+ * `get` / `set` / `del` and never connects or quits it.
11
+ * - **`url`** — let the store lazily `import("redis")`, build a client
12
+ * from the url, and connect it. `@warlock.js/ai` takes **no** hard
13
+ * dependency on `redis` (it is an optional peer); when it is absent the
14
+ * store throws a curated install string at first use, never a raw
15
+ * module-resolution stack trace at import.
16
+ *
17
+ * Exactly one of the two must be present.
18
+ */
19
+ interface RedisInterruptOptions {
20
+ /** An already-connected `redis` client — anything matching {@link RedisClientLike}. */
21
+ client?: RedisClientLike;
22
+ /** Connection url the store passes to a lazily-imported `createClient`. */
23
+ url?: string;
24
+ /**
25
+ * Key prefix prepended to every key this store writes. Lets one Redis
26
+ * database back multiple stores without collision. Defaults to
27
+ * `warlock:ai-human:interrupt:`.
28
+ */
29
+ prefix?: string;
30
+ }
31
+ /**
32
+ * Create a Redis-backed {@link InterruptStore}. Either pass a connected
33
+ * `redis` client (`{ client }`) — `@warlock.js/ai` never imports
34
+ * `redis` in that case — or a `{ url }` and let the store lazily
35
+ * `import("redis")`, build, and connect a client. When `redis` is not
36
+ * installed, the curated install string surfaces on first use, never at
37
+ * import. {@link InterruptStore.schema} returns an empty string; Redis
38
+ * needs no migration.
39
+ *
40
+ * @example
41
+ * import { createClient } from "redis";
42
+ * import { ai } from "@warlock.js/ai";
43
+ *
44
+ * const client = createClient({ url: process.env.REDIS_URL });
45
+ * await client.connect();
46
+ *
47
+ * const store = ai.human.interrupt.redis({ client });
48
+ *
49
+ * @example
50
+ * // Let the store build + connect its own client from a url:
51
+ * const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });
52
+ */
53
+ declare function redis(options: RedisInterruptOptions): InterruptStore;
54
+ //#endregion
55
+ export { RedisInterruptOptions, redis };
56
+ //# sourceMappingURL=redis.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/redis.ts"],"mappings":";;;;;AAqBA;;;;;;;;;AAYQ;AA+RR;;;UA3SiB,qBAAA;EA2Sc;EAzS7B,MAAA,GAAS,eAAe;EAyS6B;EAtSrD,GAAA;EAsSmE;;;;;EA/RnE,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;iBA+Rc,KAAA,CAAM,OAAA,EAAS,qBAAA,GAAwB,cAAc"}
@@ -0,0 +1,201 @@
1
+ //#region ../@warlock.js/ai/src/human/stores/redis.ts
2
+ /**
3
+ * Default key prefix — namespaces the store's keys inside a shared Redis
4
+ * database so interrupt records coexist with other data without collision.
5
+ */
6
+ const DEFAULT_PREFIX = "warlock:ai-human:interrupt:";
7
+ /**
8
+ * Index key (under the configured prefix) holding the JSON array of live
9
+ * interrupt ids. The structural {@link RedisClientLike} surface exposes no
10
+ * `SCAN` / `KEYS`, so enumeration for `list()` is self-maintained.
11
+ */
12
+ const INDEX_SUFFIX = "index";
13
+ /**
14
+ * Module specifier for the optional `redis` driver. Held in a `string`
15
+ * variable so the dynamic `import()` is not statically resolved at compile
16
+ * time — `redis` is an optional peer that need not be installed for this
17
+ * package to type-check or for a memory-only consumer to run.
18
+ */
19
+ const REDIS_MODULE = "redis";
20
+ /**
21
+ * Curated install string surfaced (at use time) when a `url` is configured
22
+ * but the optional `redis` driver is absent. Never thrown at import — a
23
+ * memory-only consumer must be able to load this module.
24
+ */
25
+ const REDIS_INSTALL_INSTRUCTIONS = `
26
+ The @warlock.js/ai Redis interrupt store requires the redis package.
27
+ Install it with:
28
+
29
+ npm install redis
30
+
31
+ Or with your preferred package manager:
32
+
33
+ pnpm add redis
34
+ yarn add redis
35
+ `.trim();
36
+ /**
37
+ * Lazily import `redis`, build a client from `url`, and connect it. A bare
38
+ * `catch` rethrows the curated install string — a missing optional peer
39
+ * surfaces as actionable guidance, never a raw resolution error.
40
+ */
41
+ async function buildRedisClient(url) {
42
+ let sdk;
43
+ try {
44
+ sdk = await import(REDIS_MODULE);
45
+ } catch {
46
+ throw new Error(REDIS_INSTALL_INSTRUCTIONS);
47
+ }
48
+ const client = sdk.createClient({ url });
49
+ await client.connect();
50
+ return client;
51
+ }
52
+ /**
53
+ * Redis-backed {@link InterruptStore} — one JSON string value per pending
54
+ * interrupt, under a namespaced key, plus a self-maintained id index so
55
+ * `list()` works without `SCAN`/`KEYS`.
56
+ *
57
+ * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so
58
+ * a reviewer can rule out-of-process, the namespaced key layout, and the
59
+ * per-store id index that backs enumeration. Does NOT own: durability
60
+ * guarantees beyond Redis's own, the connection lifecycle (a dev-supplied
61
+ * client is never disconnected; a store-built client from a `url` is left
62
+ * connected for the process to reuse), or migration —
63
+ * {@link RedisInterruptStore.schema} returns an empty string.
64
+ *
65
+ * A call has exactly one live interrupt, so `save()` overwrites the key.
66
+ *
67
+ * Front it with the {@link redis} factory — callers never `new` it.
68
+ */
69
+ var RedisInterruptStore = class {
70
+ constructor(options) {
71
+ this.prefix = options.prefix ?? DEFAULT_PREFIX;
72
+ if (options.client) {
73
+ if (typeof options.client.get !== "function" || typeof options.client.set !== "function" || typeof options.client.del !== "function") throw new TypeError("ai.human.interrupt.redis requires a 'client' option implementing { get, set, del } — pass a connected redis client.");
74
+ this.clientPromise = Promise.resolve(options.client);
75
+ return;
76
+ }
77
+ if (options.url) {
78
+ this.clientPromise = buildRedisClient(options.url);
79
+ return;
80
+ }
81
+ throw new TypeError("ai.human.interrupt.redis requires either a 'client' or a 'url' option.");
82
+ }
83
+ /**
84
+ * Resolve the backing client, surfacing the lazy `redis` import's
85
+ * curated install string on the first call that needs it.
86
+ */
87
+ client() {
88
+ return this.clientPromise;
89
+ }
90
+ /**
91
+ * Persist a pending interrupt, keyed by its own `interruptId`, and index
92
+ * the id for enumeration. Overwrites any prior record for the same id —
93
+ * a call has exactly one live interrupt.
94
+ */
95
+ async save(record) {
96
+ await (await this.client()).set(this.recordKey(record.interruptId), JSON.stringify(record));
97
+ await this.indexId(record.interruptId);
98
+ }
99
+ /**
100
+ * Load the interrupt for an `interruptId`, or `undefined` when the key is
101
+ * missing. Redis returns `null` for an absent key — converted to
102
+ * `undefined` at the boundary.
103
+ */
104
+ async load(interruptId) {
105
+ const raw = await (await this.client()).get(this.recordKey(interruptId));
106
+ if (raw === null) return;
107
+ return JSON.parse(raw);
108
+ }
109
+ /**
110
+ * Drop the interrupt for an `interruptId` and de-index its id. Idempotent
111
+ * — deleting an absent id is a no-op.
112
+ */
113
+ async delete(interruptId) {
114
+ await (await this.client()).del(this.recordKey(interruptId));
115
+ await this.deindexId(interruptId);
116
+ }
117
+ /**
118
+ * List the interrupt ids known to the store, optionally filtered by a
119
+ * prefix. Reads the self-maintained index document.
120
+ */
121
+ async list(prefix) {
122
+ const ids = await this.readIndex();
123
+ if (prefix === void 0) return ids;
124
+ return ids.filter((id) => id.startsWith(prefix));
125
+ }
126
+ /**
127
+ * Redis needs no relational table — there is nothing to migrate. Returns
128
+ * an empty string so callers can treat `schema()` uniformly across
129
+ * drivers.
130
+ */
131
+ schema() {
132
+ return "";
133
+ }
134
+ /**
135
+ * Read and parse the id index, defaulting to an empty list when absent.
136
+ */
137
+ async readIndex() {
138
+ const raw = await (await this.client()).get(this.indexKey());
139
+ if (raw === null) return [];
140
+ return JSON.parse(raw);
141
+ }
142
+ /**
143
+ * Add an interrupt id to the index, no-op when already present.
144
+ */
145
+ async indexId(interruptId) {
146
+ const ids = await this.readIndex();
147
+ if (ids.includes(interruptId)) return;
148
+ ids.push(interruptId);
149
+ await (await this.client()).set(this.indexKey(), JSON.stringify(ids));
150
+ }
151
+ /**
152
+ * Remove an interrupt id from the index, no-op when absent.
153
+ */
154
+ async deindexId(interruptId) {
155
+ const ids = await this.readIndex();
156
+ const next = ids.filter((id) => id !== interruptId);
157
+ if (next.length === ids.length) return;
158
+ await (await this.client()).set(this.indexKey(), JSON.stringify(next));
159
+ }
160
+ /**
161
+ * Key for a single interrupt record — `<prefix><interruptId>`.
162
+ */
163
+ recordKey(interruptId) {
164
+ return `${this.prefix}${interruptId}`;
165
+ }
166
+ /**
167
+ * Key for the self-maintained id index — `<prefix>index`.
168
+ */
169
+ indexKey() {
170
+ return `${this.prefix}${INDEX_SUFFIX}`;
171
+ }
172
+ };
173
+ /**
174
+ * Create a Redis-backed {@link InterruptStore}. Either pass a connected
175
+ * `redis` client (`{ client }`) — `@warlock.js/ai` never imports
176
+ * `redis` in that case — or a `{ url }` and let the store lazily
177
+ * `import("redis")`, build, and connect a client. When `redis` is not
178
+ * installed, the curated install string surfaces on first use, never at
179
+ * import. {@link InterruptStore.schema} returns an empty string; Redis
180
+ * needs no migration.
181
+ *
182
+ * @example
183
+ * import { createClient } from "redis";
184
+ * import { ai } from "@warlock.js/ai";
185
+ *
186
+ * const client = createClient({ url: process.env.REDIS_URL });
187
+ * await client.connect();
188
+ *
189
+ * const store = ai.human.interrupt.redis({ client });
190
+ *
191
+ * @example
192
+ * // Let the store build + connect its own client from a url:
193
+ * const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });
194
+ */
195
+ function redis(options) {
196
+ return new RedisInterruptStore(options);
197
+ }
198
+
199
+ //#endregion
200
+ export { redis };
201
+ //# sourceMappingURL=redis.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/human/stores/redis.ts"],"sourcesContent":["import type {\n InterruptStore,\n PendingInterrupt,\n RedisClientLike,\n} from \"../contracts/interrupt-store.contract\";\n\n/**\n * Options for the Redis {@link InterruptStore}.\n *\n * Two mutually-supportive ways to supply the connection:\n * - **`client`** — pass an already-connected `redis` client (anything\n * satisfying {@link RedisClientLike}). The store only calls\n * `get` / `set` / `del` and never connects or quits it.\n * - **`url`** — let the store lazily `import(\"redis\")`, build a client\n * from the url, and connect it. `@warlock.js/ai` takes **no** hard\n * dependency on `redis` (it is an optional peer); when it is absent the\n * store throws a curated install string at first use, never a raw\n * module-resolution stack trace at import.\n *\n * Exactly one of the two must be present.\n */\nexport interface RedisInterruptOptions {\n /** An already-connected `redis` client — anything matching {@link RedisClientLike}. */\n client?: RedisClientLike;\n\n /** Connection url the store passes to a lazily-imported `createClient`. */\n url?: string;\n\n /**\n * Key prefix prepended to every key this store writes. Lets one Redis\n * database back multiple stores without collision. Defaults to\n * `warlock:ai-human:interrupt:`.\n */\n prefix?: string;\n}\n\n/**\n * Default key prefix — namespaces the store's keys inside a shared Redis\n * database so interrupt records coexist with other data without collision.\n */\nconst DEFAULT_PREFIX = \"warlock:ai-human:interrupt:\";\n\n/**\n * Index key (under the configured prefix) holding the JSON array of live\n * interrupt ids. The structural {@link RedisClientLike} surface exposes no\n * `SCAN` / `KEYS`, so enumeration for `list()` is self-maintained.\n */\nconst INDEX_SUFFIX = \"index\";\n\n/**\n * Module specifier for the optional `redis` driver. Held in a `string`\n * variable so the dynamic `import()` is not statically resolved at compile\n * time — `redis` is an optional peer that need not be installed for this\n * package to type-check or for a memory-only consumer to run.\n */\nconst REDIS_MODULE = \"redis\";\n\n/**\n * Curated install string surfaced (at use time) when a `url` is configured\n * but the optional `redis` driver is absent. Never thrown at import — a\n * memory-only consumer must be able to load this module.\n */\nconst REDIS_INSTALL_INSTRUCTIONS = `\nThe @warlock.js/ai Redis interrupt store requires the redis package.\nInstall it with:\n\n npm install redis\n\nOr with your preferred package manager:\n\n pnpm add redis\n yarn add redis\n`.trim();\n\n/**\n * Minimal structural view of the `redis` module surface — just enough to\n * build and connect a client from a url. Declared locally (rather than\n * `typeof import(\"redis\")`) so this module type-checks even when `redis`\n * is not installed.\n */\ninterface RedisModuleLike {\n createClient(config: {\n url: string;\n }): RedisClientLike & { connect(): Promise<unknown> };\n}\n\n/**\n * Lazily import `redis`, build a client from `url`, and connect it. A bare\n * `catch` rethrows the curated install string — a missing optional peer\n * surfaces as actionable guidance, never a raw resolution error.\n */\nasync function buildRedisClient(url: string): Promise<RedisClientLike> {\n let sdk: RedisModuleLike;\n\n try {\n sdk = (await import(REDIS_MODULE)) as unknown as RedisModuleLike;\n } catch {\n throw new Error(REDIS_INSTALL_INSTRUCTIONS);\n }\n\n const client = sdk.createClient({ url });\n await client.connect();\n\n return client;\n}\n\n/**\n * Redis-backed {@link InterruptStore} — one JSON string value per pending\n * interrupt, under a namespaced key, plus a self-maintained id index so\n * `list()` works without `SCAN`/`KEYS`.\n *\n * Owns: durable round-tripping of the {@link PendingInterrupt} envelope so\n * a reviewer can rule out-of-process, the namespaced key layout, and the\n * per-store id index that backs enumeration. Does NOT own: durability\n * guarantees beyond Redis's own, the connection lifecycle (a dev-supplied\n * client is never disconnected; a store-built client from a `url` is left\n * connected for the process to reuse), or migration —\n * {@link RedisInterruptStore.schema} returns an empty string.\n *\n * A call has exactly one live interrupt, so `save()` overwrites the key.\n *\n * Front it with the {@link redis} factory — callers never `new` it.\n */\nclass RedisInterruptStore implements InterruptStore {\n /** Key prefix namespacing every key this store writes. */\n private readonly prefix: string;\n\n /**\n * A ready client, or a promise resolving to one the store builds lazily\n * from a `url`. Resolved once and memoized so the optional `redis`\n * import + connect happens at most once.\n */\n private clientPromise: Promise<RedisClientLike>;\n\n public constructor(options: RedisInterruptOptions) {\n this.prefix = options.prefix ?? DEFAULT_PREFIX;\n\n if (options.client) {\n if (\n typeof options.client.get !== \"function\" ||\n typeof options.client.set !== \"function\" ||\n typeof options.client.del !== \"function\"\n ) {\n throw new TypeError(\n \"ai.human.interrupt.redis requires a 'client' option implementing { get, set, del } — pass a connected redis client.\",\n );\n }\n\n this.clientPromise = Promise.resolve(options.client);\n\n return;\n }\n\n if (options.url) {\n // Defer the optional `redis` import to first use — a curated install\n // string surfaces from `buildRedisClient`, not at construction.\n this.clientPromise = buildRedisClient(options.url);\n\n return;\n }\n\n throw new TypeError(\n \"ai.human.interrupt.redis requires either a 'client' or a 'url' option.\",\n );\n }\n\n /**\n * Resolve the backing client, surfacing the lazy `redis` import's\n * curated install string on the first call that needs it.\n */\n private client(): Promise<RedisClientLike> {\n return this.clientPromise;\n }\n\n /**\n * Persist a pending interrupt, keyed by its own `interruptId`, and index\n * the id for enumeration. Overwrites any prior record for the same id —\n * a call has exactly one live interrupt.\n */\n public async save(record: PendingInterrupt): Promise<void> {\n const client = await this.client();\n\n await client.set(this.recordKey(record.interruptId), JSON.stringify(record));\n await this.indexId(record.interruptId);\n }\n\n /**\n * Load the interrupt for an `interruptId`, or `undefined` when the key is\n * missing. Redis returns `null` for an absent key — converted to\n * `undefined` at the boundary.\n */\n public async load(\n interruptId: string,\n ): Promise<PendingInterrupt | undefined> {\n const client = await this.client();\n const raw = await client.get(this.recordKey(interruptId));\n\n if (raw === null) {\n return undefined;\n }\n\n return JSON.parse(raw) as PendingInterrupt;\n }\n\n /**\n * Drop the interrupt for an `interruptId` and de-index its id. Idempotent\n * — deleting an absent id is a no-op.\n */\n public async delete(interruptId: string): Promise<void> {\n const client = await this.client();\n\n await client.del(this.recordKey(interruptId));\n await this.deindexId(interruptId);\n }\n\n /**\n * List the interrupt ids known to the store, optionally filtered by a\n * prefix. Reads the self-maintained index document.\n */\n public async list(prefix?: string): Promise<string[]> {\n const ids = await this.readIndex();\n\n if (prefix === undefined) {\n return ids;\n }\n\n return ids.filter((id) => id.startsWith(prefix));\n }\n\n /**\n * Redis needs no relational table — there is nothing to migrate. Returns\n * an empty string so callers can treat `schema()` uniformly across\n * drivers.\n */\n public schema(): string {\n return \"\";\n }\n\n /**\n * Read and parse the id index, defaulting to an empty list when absent.\n */\n private async readIndex(): Promise<string[]> {\n const client = await this.client();\n const raw = await client.get(this.indexKey());\n\n if (raw === null) {\n return [];\n }\n\n return JSON.parse(raw) as string[];\n }\n\n /**\n * Add an interrupt id to the index, no-op when already present.\n */\n private async indexId(interruptId: string): Promise<void> {\n const ids = await this.readIndex();\n\n if (ids.includes(interruptId)) {\n return;\n }\n\n ids.push(interruptId);\n\n const client = await this.client();\n await client.set(this.indexKey(), JSON.stringify(ids));\n }\n\n /**\n * Remove an interrupt id from the index, no-op when absent.\n */\n private async deindexId(interruptId: string): Promise<void> {\n const ids = await this.readIndex();\n const next = ids.filter((id) => id !== interruptId);\n\n if (next.length === ids.length) {\n return;\n }\n\n const client = await this.client();\n await client.set(this.indexKey(), JSON.stringify(next));\n }\n\n /**\n * Key for a single interrupt record — `<prefix><interruptId>`.\n */\n private recordKey(interruptId: string): string {\n return `${this.prefix}${interruptId}`;\n }\n\n /**\n * Key for the self-maintained id index — `<prefix>index`.\n */\n private indexKey(): string {\n return `${this.prefix}${INDEX_SUFFIX}`;\n }\n}\n\n/**\n * Create a Redis-backed {@link InterruptStore}. Either pass a connected\n * `redis` client (`{ client }`) — `@warlock.js/ai` never imports\n * `redis` in that case — or a `{ url }` and let the store lazily\n * `import(\"redis\")`, build, and connect a client. When `redis` is not\n * installed, the curated install string surfaces on first use, never at\n * import. {@link InterruptStore.schema} returns an empty string; Redis\n * needs no migration.\n *\n * @example\n * import { createClient } from \"redis\";\n * import { ai } from \"@warlock.js/ai\";\n *\n * const client = createClient({ url: process.env.REDIS_URL });\n * await client.connect();\n *\n * const store = ai.human.interrupt.redis({ client });\n *\n * @example\n * // Let the store build + connect its own client from a url:\n * const store = ai.human.interrupt.redis({ url: process.env.REDIS_URL });\n */\nexport function redis(options: RedisInterruptOptions): InterruptStore {\n return new RedisInterruptStore(options);\n}\n"],"mappings":"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"}