@openclaw/lobster 2026.9.4 → 2026.9.5

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 (322) hide show
  1. package/assets/activity.svg +4 -0
  2. package/assets/icon.png +0 -0
  3. package/dist/index.js +39 -10
  4. package/node_modules/@clawdbot/lobster/README.md +112 -1
  5. package/node_modules/@clawdbot/lobster/bin/clawd.invoke.js +13 -7
  6. package/node_modules/@clawdbot/lobster/bin/lobster.js +7 -7
  7. package/node_modules/@clawdbot/lobster/bin/openclaw.invoke.js +16 -10
  8. package/node_modules/@clawdbot/lobster/dist/src/abortable_process.js +297 -0
  9. package/node_modules/@clawdbot/lobster/dist/src/abortable_process.js.map +1 -0
  10. package/node_modules/@clawdbot/lobster/dist/src/cli.js +96 -248
  11. package/node_modules/@clawdbot/lobster/dist/src/cli.js.map +1 -1
  12. package/node_modules/@clawdbot/lobster/dist/src/commands/commands_list.js.map +1 -1
  13. package/node_modules/@clawdbot/lobster/dist/src/commands/registry.js +2 -0
  14. package/node_modules/@clawdbot/lobster/dist/src/commands/registry.js.map +1 -1
  15. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/approve.js +2 -0
  16. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/approve.js.map +1 -1
  17. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/ask.js +3 -1
  18. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/ask.js.map +1 -1
  19. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js.map +1 -1
  20. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/diff_last.js +6 -1
  21. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/diff_last.js.map +1 -1
  22. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/email_triage.js.map +1 -1
  23. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/exec.js +30 -34
  24. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/exec.js.map +1 -1
  25. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_search.js +12 -25
  26. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_search.js.map +1 -1
  27. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_send.js +19 -25
  28. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_send.js.map +1 -1
  29. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js.map +1 -1
  30. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/head.js.map +1 -1
  31. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/json.js.map +1 -1
  32. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js +596 -42
  33. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js.map +1 -1
  34. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js.map +1 -1
  35. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_agent.js +139 -0
  36. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_agent.js.map +1 -0
  37. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_invoke.js +20 -2
  38. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_invoke.js.map +1 -1
  39. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/pick.js.map +1 -1
  40. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js.map +1 -1
  41. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js +48 -20
  42. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js.map +1 -1
  43. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/table.js.map +1 -1
  44. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js.map +1 -1
  45. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/where.js.map +1 -1
  46. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_list.js.map +1 -1
  47. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js.map +1 -1
  48. package/node_modules/@clawdbot/lobster/dist/src/core/cost_tracker.js +45 -3
  49. package/node_modules/@clawdbot/lobster/dist/src/core/cost_tracker.js.map +1 -1
  50. package/node_modules/@clawdbot/lobster/dist/src/core/filters.js.map +1 -1
  51. package/node_modules/@clawdbot/lobster/dist/src/core/retry.js.map +1 -1
  52. package/node_modules/@clawdbot/lobster/dist/src/core/tool_runtime.js +147 -21
  53. package/node_modules/@clawdbot/lobster/dist/src/core/tool_runtime.js.map +1 -1
  54. package/node_modules/@clawdbot/lobster/dist/src/input_request.js +4 -2
  55. package/node_modules/@clawdbot/lobster/dist/src/input_request.js.map +1 -1
  56. package/node_modules/@clawdbot/lobster/dist/src/parser.js +4 -3
  57. package/node_modules/@clawdbot/lobster/dist/src/parser.js.map +1 -1
  58. package/node_modules/@clawdbot/lobster/dist/src/pipeline_resume_state.js +241 -74
  59. package/node_modules/@clawdbot/lobster/dist/src/pipeline_resume_state.js.map +1 -1
  60. package/node_modules/@clawdbot/lobster/dist/src/read_line.js +52 -7
  61. package/node_modules/@clawdbot/lobster/dist/src/read_line.js.map +1 -1
  62. package/node_modules/@clawdbot/lobster/dist/src/read_response_text.js +86 -0
  63. package/node_modules/@clawdbot/lobster/dist/src/read_response_text.js.map +1 -0
  64. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js +4 -2
  65. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js.map +1 -1
  66. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js +20 -34
  67. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js.map +1 -1
  68. package/node_modules/@clawdbot/lobster/dist/src/recipes/registry.js.map +1 -1
  69. package/node_modules/@clawdbot/lobster/dist/src/renderers/json.js.map +1 -1
  70. package/node_modules/@clawdbot/lobster/dist/src/resume.js.map +1 -1
  71. package/node_modules/@clawdbot/lobster/dist/src/runtime.js +187 -17
  72. package/node_modules/@clawdbot/lobster/dist/src/runtime.js.map +1 -1
  73. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js +10 -2
  74. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js.map +1 -1
  75. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js.map +1 -1
  76. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js +13 -82
  77. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js.map +1 -1
  78. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js +22 -32
  79. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js.map +1 -1
  80. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js +9 -101
  81. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js.map +1 -1
  82. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js +2 -1
  83. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js.map +1 -1
  84. package/node_modules/@clawdbot/lobster/dist/src/shell.js.map +1 -1
  85. package/node_modules/@clawdbot/lobster/dist/src/state/store.js +619 -19
  86. package/node_modules/@clawdbot/lobster/dist/src/state/store.js.map +1 -1
  87. package/node_modules/@clawdbot/lobster/dist/src/token.js.map +1 -1
  88. package/node_modules/@clawdbot/lobster/dist/src/validation.js.map +1 -1
  89. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js +963 -283
  90. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js.map +1 -1
  91. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js +27 -28
  92. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js.map +1 -1
  93. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js +9 -2
  94. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js.map +1 -1
  95. package/node_modules/@clawdbot/lobster/dist/src/workflows/registry.js.map +1 -1
  96. package/node_modules/@clawdbot/lobster/dist/test/approval_id.test.js.map +1 -1
  97. package/node_modules/@clawdbot/lobster/dist/test/approve_preview.test.js.map +1 -1
  98. package/node_modules/@clawdbot/lobster/dist/test/bin_smoke.test.js +25 -0
  99. package/node_modules/@clawdbot/lobster/dist/test/bin_smoke.test.js.map +1 -0
  100. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js +5170 -0
  101. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js.map +1 -0
  102. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke.test.js +22 -0
  103. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke.test.js.map +1 -1
  104. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke_legacy.test.js.map +1 -1
  105. package/node_modules/@clawdbot/lobster/dist/test/cli_run_file_args_json.test.js.map +1 -1
  106. package/node_modules/@clawdbot/lobster/dist/test/commands_list.test.js.map +1 -1
  107. package/node_modules/@clawdbot/lobster/dist/test/condition_comparison.test.js.map +1 -1
  108. package/node_modules/@clawdbot/lobster/dist/test/core_tool_runtime.test.js.map +1 -1
  109. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js +1733 -1
  110. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js.map +1 -1
  111. package/node_modules/@clawdbot/lobster/dist/test/dedupe.test.js.map +1 -1
  112. package/node_modules/@clawdbot/lobster/dist/test/diff_last.test.js.map +1 -1
  113. package/node_modules/@clawdbot/lobster/dist/test/doctor.test.js.map +1 -1
  114. package/node_modules/@clawdbot/lobster/dist/test/dry_run.test.js.map +1 -1
  115. package/node_modules/@clawdbot/lobster/dist/test/email_triage.test.js.map +1 -1
  116. package/node_modules/@clawdbot/lobster/dist/test/exec_stdin.test.js.map +1 -1
  117. package/node_modules/@clawdbot/lobster/dist/test/for_each.test.js +254 -0
  118. package/node_modules/@clawdbot/lobster/dist/test/for_each.test.js.map +1 -1
  119. package/node_modules/@clawdbot/lobster/dist/test/github_pr_notify_format.test.js.map +1 -1
  120. package/node_modules/@clawdbot/lobster/dist/test/github_pr_summary.test.js.map +1 -1
  121. package/node_modules/@clawdbot/lobster/dist/test/github_pr_view.test.js +176 -0
  122. package/node_modules/@clawdbot/lobster/dist/test/github_pr_view.test.js.map +1 -0
  123. package/node_modules/@clawdbot/lobster/dist/test/gog_gmail_send.test.js +100 -0
  124. package/node_modules/@clawdbot/lobster/dist/test/gog_gmail_send.test.js.map +1 -0
  125. package/node_modules/@clawdbot/lobster/dist/test/group_by.test.js.map +1 -1
  126. package/node_modules/@clawdbot/lobster/dist/test/helpers/wait_for_pid.js +19 -0
  127. package/node_modules/@clawdbot/lobster/dist/test/helpers/wait_for_pid.js.map +1 -0
  128. package/node_modules/@clawdbot/lobster/dist/test/http_response_body_cap.test.js +318 -0
  129. package/node_modules/@clawdbot/lobster/dist/test/http_response_body_cap.test.js.map +1 -0
  130. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js +946 -1
  131. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js.map +1 -1
  132. package/node_modules/@clawdbot/lobster/dist/test/llm_task_invoke.test.js +103 -0
  133. package/node_modules/@clawdbot/lobster/dist/test/llm_task_invoke.test.js.map +1 -1
  134. package/node_modules/@clawdbot/lobster/dist/test/map.test.js.map +1 -1
  135. package/node_modules/@clawdbot/lobster/dist/test/multi_approval_resume.test.js.map +1 -1
  136. package/node_modules/@clawdbot/lobster/dist/test/on_error.test.js.map +1 -1
  137. package/node_modules/@clawdbot/lobster/dist/test/openclaw_agent.test.js +169 -0
  138. package/node_modules/@clawdbot/lobster/dist/test/openclaw_agent.test.js.map +1 -0
  139. package/node_modules/@clawdbot/lobster/dist/test/openclaw_dispatch_retry.test.js +96 -0
  140. package/node_modules/@clawdbot/lobster/dist/test/openclaw_dispatch_retry.test.js.map +1 -0
  141. package/node_modules/@clawdbot/lobster/dist/test/openclaw_invoke_alias.test.js.map +1 -1
  142. package/node_modules/@clawdbot/lobster/dist/test/parallel.test.js.map +1 -1
  143. package/node_modules/@clawdbot/lobster/dist/test/parser.test.js +57 -0
  144. package/node_modules/@clawdbot/lobster/dist/test/parser.test.js.map +1 -1
  145. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js +173 -0
  146. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js.map +1 -0
  147. package/node_modules/@clawdbot/lobster/dist/test/read_line.test.js +54 -0
  148. package/node_modules/@clawdbot/lobster/dist/test/read_line.test.js.map +1 -1
  149. package/node_modules/@clawdbot/lobster/dist/test/release_changelog.test.js +39 -0
  150. package/node_modules/@clawdbot/lobster/dist/test/release_changelog.test.js.map +1 -0
  151. package/node_modules/@clawdbot/lobster/dist/test/request_input.test.js +1 -1
  152. package/node_modules/@clawdbot/lobster/dist/test/request_input.test.js.map +1 -1
  153. package/node_modules/@clawdbot/lobster/dist/test/resume.test.js.map +1 -1
  154. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js +201 -0
  155. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js.map +1 -0
  156. package/node_modules/@clawdbot/lobster/dist/test/sdk_lobster.test.js.map +1 -1
  157. package/node_modules/@clawdbot/lobster/dist/test/shell.test.js.map +1 -1
  158. package/node_modules/@clawdbot/lobster/dist/test/sort.test.js.map +1 -1
  159. package/node_modules/@clawdbot/lobster/dist/test/state.test.js +584 -1
  160. package/node_modules/@clawdbot/lobster/dist/test/state.test.js.map +1 -1
  161. package/node_modules/@clawdbot/lobster/dist/test/step_retry.test.js.map +1 -1
  162. package/node_modules/@clawdbot/lobster/dist/test/step_stdin_epipe.test.js +44 -0
  163. package/node_modules/@clawdbot/lobster/dist/test/step_stdin_epipe.test.js.map +1 -0
  164. package/node_modules/@clawdbot/lobster/dist/test/step_timeout.test.js +67 -1
  165. package/node_modules/@clawdbot/lobster/dist/test/step_timeout.test.js.map +1 -1
  166. package/node_modules/@clawdbot/lobster/dist/test/template.test.js.map +1 -1
  167. package/node_modules/@clawdbot/lobster/dist/test/template_filters.test.js +37 -0
  168. package/node_modules/@clawdbot/lobster/dist/test/template_filters.test.js.map +1 -1
  169. package/node_modules/@clawdbot/lobster/dist/test/tool_envelope_version.test.js.map +1 -1
  170. package/node_modules/@clawdbot/lobster/dist/test/tool_mode.test.js.map +1 -1
  171. package/node_modules/@clawdbot/lobster/dist/test/validation.test.js.map +1 -1
  172. package/node_modules/@clawdbot/lobster/dist/test/where.test.js +94 -0
  173. package/node_modules/@clawdbot/lobster/dist/test/where.test.js.map +1 -0
  174. package/node_modules/@clawdbot/lobster/dist/test/workflow_args_env.test.js +5 -3
  175. package/node_modules/@clawdbot/lobster/dist/test/workflow_args_env.test.js.map +1 -1
  176. package/node_modules/@clawdbot/lobster/dist/test/workflow_composition.test.js.map +1 -1
  177. package/node_modules/@clawdbot/lobster/dist/test/workflow_file.test.js +647 -1
  178. package/node_modules/@clawdbot/lobster/dist/test/workflow_file.test.js.map +1 -1
  179. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js +12 -2
  180. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js.map +1 -1
  181. package/node_modules/@clawdbot/lobster/dist/test/workflows.test.js.map +1 -1
  182. package/node_modules/@clawdbot/lobster/package.json +74 -73
  183. package/node_modules/typebox/build/compile/validator.d.mts +1 -1
  184. package/node_modules/typebox/build/compile/validator.mjs +1 -1
  185. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +7 -5
  186. package/node_modules/typebox/build/format/iri.mjs +5 -1
  187. package/node_modules/typebox/build/guard/emit.mjs +2 -4
  188. package/node_modules/typebox/build/guard/guard.mjs +6 -18
  189. package/node_modules/typebox/build/guard/string.mjs +16 -24
  190. package/node_modules/typebox/build/schema/build.mjs +2 -2
  191. package/node_modules/typebox/build/schema/check.mjs +1 -1
  192. package/node_modules/typebox/build/schema/compile.d.mts +3 -2
  193. package/node_modules/typebox/build/schema/compile.mjs +8 -8
  194. package/node_modules/typebox/build/schema/engine/_context.d.mts +6 -7
  195. package/node_modules/typebox/build/schema/engine/_context.mjs +23 -15
  196. package/node_modules/typebox/build/schema/engine/_functions.d.mts +2 -2
  197. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -1
  198. package/node_modules/typebox/build/schema/engine/_reducer.d.mts +3 -3
  199. package/node_modules/typebox/build/schema/engine/_refine.d.mts +5 -5
  200. package/node_modules/typebox/build/schema/engine/_refine.mjs +4 -7
  201. package/node_modules/typebox/build/schema/engine/_stack.d.mts +34 -16
  202. package/node_modules/typebox/build/schema/engine/_stack.mjs +148 -104
  203. package/node_modules/typebox/build/schema/engine/additionalItems.d.mts +4 -4
  204. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +8 -8
  205. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +26 -18
  206. package/node_modules/typebox/build/schema/engine/allOf.d.mts +4 -4
  207. package/node_modules/typebox/build/schema/engine/allOf.mjs +3 -3
  208. package/node_modules/typebox/build/schema/engine/anyOf.d.mts +4 -4
  209. package/node_modules/typebox/build/schema/engine/anyOf.mjs +5 -10
  210. package/node_modules/typebox/build/schema/engine/boolean.d.mts +4 -4
  211. package/node_modules/typebox/build/schema/engine/boolean.mjs +2 -6
  212. package/node_modules/typebox/build/schema/engine/const.d.mts +4 -4
  213. package/node_modules/typebox/build/schema/engine/const.mjs +2 -6
  214. package/node_modules/typebox/build/schema/engine/contains.d.mts +4 -4
  215. package/node_modules/typebox/build/schema/engine/contains.mjs +2 -6
  216. package/node_modules/typebox/build/schema/engine/dependencies.d.mts +4 -4
  217. package/node_modules/typebox/build/schema/engine/dependencies.mjs +3 -6
  218. package/node_modules/typebox/build/schema/engine/dependentRequired.d.mts +4 -4
  219. package/node_modules/typebox/build/schema/engine/dependentRequired.mjs +2 -6
  220. package/node_modules/typebox/build/schema/engine/dependentSchemas.d.mts +4 -4
  221. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +4 -4
  222. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +11 -7
  223. package/node_modules/typebox/build/schema/engine/enum.d.mts +4 -4
  224. package/node_modules/typebox/build/schema/engine/enum.mjs +2 -6
  225. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.d.mts +4 -4
  226. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.mjs +2 -6
  227. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.d.mts +4 -4
  228. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.mjs +2 -6
  229. package/node_modules/typebox/build/schema/engine/format.d.mts +4 -4
  230. package/node_modules/typebox/build/schema/engine/format.mjs +2 -6
  231. package/node_modules/typebox/build/schema/engine/if.d.mts +4 -4
  232. package/node_modules/typebox/build/schema/engine/if.mjs +7 -15
  233. package/node_modules/typebox/build/schema/engine/items.d.mts +4 -4
  234. package/node_modules/typebox/build/schema/engine/items.mjs +1 -1
  235. package/node_modules/typebox/build/schema/engine/maxContains.d.mts +4 -4
  236. package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -6
  237. package/node_modules/typebox/build/schema/engine/maxItems.d.mts +4 -4
  238. package/node_modules/typebox/build/schema/engine/maxItems.mjs +2 -6
  239. package/node_modules/typebox/build/schema/engine/maxLength.d.mts +4 -4
  240. package/node_modules/typebox/build/schema/engine/maxLength.mjs +2 -6
  241. package/node_modules/typebox/build/schema/engine/maxProperties.d.mts +4 -4
  242. package/node_modules/typebox/build/schema/engine/maxProperties.mjs +2 -6
  243. package/node_modules/typebox/build/schema/engine/maximum.d.mts +4 -4
  244. package/node_modules/typebox/build/schema/engine/maximum.mjs +2 -6
  245. package/node_modules/typebox/build/schema/engine/minContains.d.mts +4 -4
  246. package/node_modules/typebox/build/schema/engine/minContains.mjs +2 -6
  247. package/node_modules/typebox/build/schema/engine/minItems.d.mts +5 -5
  248. package/node_modules/typebox/build/schema/engine/minItems.mjs +2 -6
  249. package/node_modules/typebox/build/schema/engine/minLength.d.mts +4 -4
  250. package/node_modules/typebox/build/schema/engine/minLength.mjs +2 -6
  251. package/node_modules/typebox/build/schema/engine/minProperties.d.mts +4 -4
  252. package/node_modules/typebox/build/schema/engine/minProperties.mjs +2 -6
  253. package/node_modules/typebox/build/schema/engine/minimum.d.mts +4 -4
  254. package/node_modules/typebox/build/schema/engine/minimum.mjs +2 -6
  255. package/node_modules/typebox/build/schema/engine/multipleOf.d.mts +4 -4
  256. package/node_modules/typebox/build/schema/engine/multipleOf.mjs +2 -6
  257. package/node_modules/typebox/build/schema/engine/not.d.mts +4 -4
  258. package/node_modules/typebox/build/schema/engine/not.mjs +4 -8
  259. package/node_modules/typebox/build/schema/engine/oneOf.d.mts +4 -4
  260. package/node_modules/typebox/build/schema/engine/oneOf.mjs +6 -11
  261. package/node_modules/typebox/build/schema/engine/pattern.d.mts +4 -4
  262. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -7
  263. package/node_modules/typebox/build/schema/engine/patternProperties.d.mts +4 -4
  264. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +1 -1
  265. package/node_modules/typebox/build/schema/engine/prefixItems.d.mts +4 -4
  266. package/node_modules/typebox/build/schema/engine/prefixItems.mjs +1 -1
  267. package/node_modules/typebox/build/schema/engine/properties.d.mts +4 -4
  268. package/node_modules/typebox/build/schema/engine/properties.mjs +1 -1
  269. package/node_modules/typebox/build/schema/engine/propertyNames.d.mts +4 -4
  270. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +4 -10
  271. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +4 -4
  272. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +11 -7
  273. package/node_modules/typebox/build/schema/engine/ref.d.mts +4 -4
  274. package/node_modules/typebox/build/schema/engine/ref.mjs +19 -15
  275. package/node_modules/typebox/build/schema/engine/required.d.mts +4 -4
  276. package/node_modules/typebox/build/schema/engine/required.mjs +2 -6
  277. package/node_modules/typebox/build/schema/engine/schema.d.mts +7 -7
  278. package/node_modules/typebox/build/schema/engine/schema.mjs +137 -133
  279. package/node_modules/typebox/build/schema/engine/type.d.mts +4 -4
  280. package/node_modules/typebox/build/schema/engine/type.mjs +2 -6
  281. package/node_modules/typebox/build/schema/engine/unevaluatedItems.d.mts +4 -4
  282. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +5 -9
  283. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.d.mts +4 -4
  284. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +5 -9
  285. package/node_modules/typebox/build/schema/engine/uniqueItems.d.mts +4 -4
  286. package/node_modules/typebox/build/schema/engine/uniqueItems.mjs +2 -6
  287. package/node_modules/typebox/build/schema/errors.d.mts +2 -2
  288. package/node_modules/typebox/build/schema/errors.mjs +8 -14
  289. package/node_modules/typebox/build/schema/intern/index.d.mts +1 -0
  290. package/node_modules/typebox/build/schema/intern/index.mjs +1 -0
  291. package/node_modules/typebox/build/schema/intern/intern.d.mts +11 -0
  292. package/node_modules/typebox/build/schema/intern/intern.mjs +293 -0
  293. package/node_modules/typebox/build/schema/meta/draft_2019_09.d.mts +2 -0
  294. package/node_modules/typebox/build/schema/meta/draft_2019_09.mjs +288 -0
  295. package/node_modules/typebox/build/schema/meta/draft_2020_12.d.mts +2 -0
  296. package/node_modules/typebox/build/schema/meta/draft_2020_12.mjs +300 -0
  297. package/node_modules/typebox/build/schema/meta/draft_3.d.mts +2 -0
  298. package/node_modules/typebox/build/schema/meta/draft_3.mjs +143 -0
  299. package/node_modules/typebox/build/schema/meta/draft_4.d.mts +2 -0
  300. package/node_modules/typebox/build/schema/meta/draft_4.mjs +149 -0
  301. package/node_modules/typebox/build/schema/meta/draft_6.d.mts +2 -0
  302. package/node_modules/typebox/build/schema/meta/draft_6.mjs +155 -0
  303. package/node_modules/typebox/build/schema/meta/draft_7.d.mts +2 -0
  304. package/node_modules/typebox/build/schema/meta/draft_7.mjs +172 -0
  305. package/node_modules/typebox/build/schema/meta/index.d.mts +2 -0
  306. package/node_modules/typebox/build/schema/meta/index.mjs +2 -0
  307. package/node_modules/typebox/build/schema/meta/meta.d.mts +9 -0
  308. package/node_modules/typebox/build/schema/meta/meta.mjs +15 -0
  309. package/node_modules/typebox/build/schema/meta/schema.d.mts +6 -0
  310. package/node_modules/typebox/build/schema/meta/schema.mjs +1 -0
  311. package/node_modules/typebox/build/schema/parse.d.mts +1 -0
  312. package/node_modules/typebox/build/schema/parse.mjs +10 -5
  313. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +8 -3
  314. package/node_modules/typebox/build/schema/resolve/resolve.mjs +268 -139
  315. package/node_modules/typebox/build/schema/schema.d.mts +2 -0
  316. package/node_modules/typebox/build/schema/schema.mjs +2 -0
  317. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -12
  318. package/node_modules/typebox/build/value/errors/errors.mjs +2 -7
  319. package/node_modules/typebox/package.json +1 -1
  320. package/node_modules/typebox/readme.md +59 -89
  321. package/openclaw.plugin.json +1 -1
  322. package/package.json +10 -7
@@ -2,16 +2,25 @@ import os from "node:os";
2
2
  import path from "node:path";
3
3
  import { promises as fsp } from "node:fs";
4
4
  import { randomBytes } from "node:crypto";
5
+ const CONSUMED_RESUME_STATE_TYPE = "lobster.consumed-resume-state.v1";
6
+ export function isConsumedResumeState(value) {
7
+ return (value !== null &&
8
+ typeof value === "object" &&
9
+ value.type === CONSUMED_RESUME_STATE_TYPE);
10
+ }
5
11
  export function defaultStateDir(env) {
6
12
  return ((env?.LOBSTER_STATE_DIR && String(env.LOBSTER_STATE_DIR).trim()) ||
7
13
  path.join(os.homedir(), ".lobster", "state"));
8
14
  }
9
15
  export function keyToPath(stateDir, key) {
10
- const safe = String(key)
16
+ let safe = String(key)
11
17
  .toLowerCase()
12
18
  .replace(/[^a-z0-9._-]+/g, "_")
13
- .replace(/_+/g, "_")
14
- .replace(/^_+|_+$/g, "");
19
+ .replace(/_+/g, "_");
20
+ if (safe.startsWith("_"))
21
+ safe = safe.slice(1);
22
+ if (safe.endsWith("_"))
23
+ safe = safe.slice(0, -1);
15
24
  if (!safe)
16
25
  throw new Error("state key is empty/invalid");
17
26
  return path.join(stateDir, `${safe}.json`);
@@ -26,6 +35,22 @@ export function stableStringify(value) {
26
35
  return v;
27
36
  });
28
37
  }
38
+ function markAtomicWritePublished(err) {
39
+ if (err && (typeof err === "object" || typeof err === "function")) {
40
+ Object.defineProperty(err, "atomicWritePublished", {
41
+ value: true,
42
+ configurable: true,
43
+ });
44
+ }
45
+ return err;
46
+ }
47
+ export function atomicWriteWasPublished(err) {
48
+ return Boolean(err?.atomicWritePublished);
49
+ }
50
+ const STATE_LOCK_RETRY_MS = 10;
51
+ const STATE_LOCK_ORPHAN_MS = 30_000;
52
+ const STATE_LOCK_HEARTBEAT_MS = 250;
53
+ const TERMINAL_RESUME_CLEANUP_TIMEOUT_MS = STATE_LOCK_RETRY_MS * 10;
29
54
  function isDirectorySyncUnsupportedError(err) {
30
55
  return [
31
56
  "EACCES",
@@ -63,9 +88,32 @@ async function syncDirectory(dir) {
63
88
  await handle.close().catch(() => { });
64
89
  }
65
90
  }
91
+ /**
92
+ * On Windows, `fs.mkdir(..., { recursive: true })` reports the first created
93
+ * directory as an extended-length path (`\\?\C:\...`). `path.resolve` keeps
94
+ * that prefix, so such a path never compares equal to the plain drive path we
95
+ * walk toward and `path.relative` between the two yields an absolute path.
96
+ * Map the namespaces that have a plain equivalent back to it so both ends of the
97
+ * chain share one root form. The UNC marker is matched without regard to case,
98
+ * because Windows accepts a lowercase "unc" namespace component just as well.
99
+ *
100
+ * Device namespaces with no drive-letter or UNC equivalent, such as
101
+ * `\\?\Volume{GUID}\...`, are returned unchanged: stripping their prefix would
102
+ * leave a relative path and break an explicitly configured state directory.
103
+ */
104
+ export function stripExtendedLengthPrefix(target) {
105
+ if (!target.startsWith("\\\\?\\"))
106
+ return target;
107
+ const rest = target.slice(4);
108
+ if (/^UNC\\/i.test(rest))
109
+ return `\\\\${rest.slice(4)}`;
110
+ if (/^[A-Za-z]:[\\/]/.test(rest))
111
+ return rest;
112
+ return target;
113
+ }
66
114
  async function syncCreatedDirectoryChain(firstCreated, finalDir) {
67
- const final = path.resolve(finalDir);
68
- let current = path.resolve(firstCreated);
115
+ const final = path.resolve(stripExtendedLengthPrefix(finalDir));
116
+ let current = path.resolve(stripExtendedLengthPrefix(firstCreated));
69
117
  await syncDirectory(path.dirname(current));
70
118
  while (current !== final) {
71
119
  await syncDirectory(current);
@@ -84,6 +132,275 @@ export async function ensureDirectory(dir) {
84
132
  export function isJsonSyntaxError(err) {
85
133
  return err instanceof SyntaxError;
86
134
  }
135
+ async function waitForStateLock(signal) {
136
+ await new Promise((resolve, reject) => {
137
+ const onAbort = () => {
138
+ clearTimeout(timer);
139
+ try {
140
+ signal?.throwIfAborted();
141
+ }
142
+ catch (err) {
143
+ reject(err);
144
+ }
145
+ };
146
+ const timer = setTimeout(() => {
147
+ signal?.removeEventListener("abort", onAbort);
148
+ resolve();
149
+ }, STATE_LOCK_RETRY_MS);
150
+ if (signal?.aborted) {
151
+ onAbort();
152
+ return;
153
+ }
154
+ signal?.addEventListener("abort", onAbort, { once: true });
155
+ });
156
+ }
157
+ function isProcessAlive(pid) {
158
+ try {
159
+ process.kill(pid, 0);
160
+ return true;
161
+ }
162
+ catch (err) {
163
+ return err?.code !== "ESRCH";
164
+ }
165
+ }
166
+ function parseStateLockOwner(ownerText) {
167
+ const parts = ownerText.trim().split(":");
168
+ if (parts.length !== 3)
169
+ return null;
170
+ const pid = Number(parts[0]);
171
+ const processStartIdentity = parts[1] || null;
172
+ const nonce = parts[2];
173
+ if (!Number.isInteger(pid) || pid <= 0 || !nonce)
174
+ return null;
175
+ return { pid, processStartIdentity, nonce };
176
+ }
177
+ async function readProcessStartIdentity(pid) {
178
+ if (process.platform !== "linux")
179
+ return null;
180
+ try {
181
+ const stat = await fsp.readFile(`/proc/${pid}/stat`, "utf8");
182
+ const closeParen = stat.lastIndexOf(")");
183
+ if (closeParen < 0)
184
+ return null;
185
+ // The remainder starts at procfs field 3; starttime is field 22.
186
+ const fields = stat
187
+ .slice(closeParen + 1)
188
+ .trim()
189
+ .split(/\s+/);
190
+ return fields[19] || null;
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ }
196
+ async function isStateLockOld(lockPath) {
197
+ try {
198
+ const stat = await fsp.stat(lockPath);
199
+ return Date.now() - stat.mtimeMs >= STATE_LOCK_ORPHAN_MS;
200
+ }
201
+ catch (err) {
202
+ if (err?.code === "ENOENT")
203
+ return true;
204
+ throw err;
205
+ }
206
+ }
207
+ async function hasExpiredStateLockLease(lockPath) {
208
+ let first;
209
+ try {
210
+ first = await fsp.stat(lockPath);
211
+ }
212
+ catch (err) {
213
+ if (err?.code === "ENOENT")
214
+ return true;
215
+ throw err;
216
+ }
217
+ if (Date.now() - first.mtimeMs < STATE_LOCK_ORPHAN_MS)
218
+ return false;
219
+ await new Promise((resolve) => setTimeout(resolve, STATE_LOCK_HEARTBEAT_MS));
220
+ try {
221
+ const second = await fsp.stat(lockPath);
222
+ return second.mtimeMs === first.mtimeMs && Date.now() - second.mtimeMs >= STATE_LOCK_ORPHAN_MS;
223
+ }
224
+ catch (err) {
225
+ if (err?.code === "ENOENT")
226
+ return true;
227
+ throw err;
228
+ }
229
+ }
230
+ async function reclaimOrphanedStateLock(lockPath) {
231
+ let observedLock;
232
+ try {
233
+ const stat = await fsp.lstat(lockPath);
234
+ observedLock = { dev: stat.dev, ino: stat.ino };
235
+ }
236
+ catch (err) {
237
+ if (err?.code === "ENOENT")
238
+ return true;
239
+ throw err;
240
+ }
241
+ const ownerPath = path.join(lockPath, "owner");
242
+ let observedOwner;
243
+ let stale = false;
244
+ try {
245
+ observedOwner = await fsp.readFile(ownerPath, "utf8");
246
+ const owner = parseStateLockOwner(observedOwner);
247
+ if (owner && isProcessAlive(owner.pid)) {
248
+ const processStartIdentity = await readProcessStartIdentity(owner.pid);
249
+ if (owner.processStartIdentity && processStartIdentity) {
250
+ stale = owner.processStartIdentity !== processStartIdentity;
251
+ }
252
+ else {
253
+ // A live PID without a matching process-instance identity may have
254
+ // been reused. Require a conservative expired lease and a second
255
+ // unchanged observation before reclaiming it.
256
+ stale = await hasExpiredStateLockLease(ownerPath);
257
+ }
258
+ }
259
+ else if (owner) {
260
+ stale = true;
261
+ }
262
+ else {
263
+ stale = await isStateLockOld(lockPath);
264
+ }
265
+ }
266
+ catch (err) {
267
+ if (err?.code === "ENOENT") {
268
+ stale = await isStateLockOld(lockPath);
269
+ }
270
+ else {
271
+ throw err;
272
+ }
273
+ }
274
+ if (!stale)
275
+ return false;
276
+ // Claim reclamation inside the observed directory before removing it. Renaming
277
+ // `lockPath` directly is unsafe: another reclaimer can replace that pathname
278
+ // with a live lock after the stale observation, and a later recursive cleanup
279
+ // would then delete the new owner's lock while it is in use.
280
+ const reclaimPath = path.join(lockPath, ".reclaiming");
281
+ try {
282
+ await fsp.mkdir(reclaimPath, { mode: 0o700 });
283
+ }
284
+ catch (err) {
285
+ if (err?.code === "ENOENT")
286
+ return true;
287
+ if (err?.code === "EEXIST")
288
+ return false;
289
+ throw err;
290
+ }
291
+ try {
292
+ const currentLock = await fsp.lstat(lockPath);
293
+ if (currentLock.dev !== observedLock.dev || currentLock.ino !== observedLock.ino) {
294
+ return false;
295
+ }
296
+ // A replacement directory can reuse the inode; its owner must still match too.
297
+ let currentOwner;
298
+ try {
299
+ currentOwner = await fsp.readFile(ownerPath, "utf8");
300
+ }
301
+ catch (err) {
302
+ if (err?.code !== "ENOENT")
303
+ throw err;
304
+ }
305
+ if (currentOwner !== observedOwner)
306
+ return false;
307
+ await fsp.rm(lockPath, { recursive: true, force: true });
308
+ }
309
+ finally {
310
+ // If the path changed before the reclamation marker was claimed, leave the
311
+ // replacement lock intact and only remove our harmless marker.
312
+ await fsp.rmdir(reclaimPath).catch(() => { });
313
+ }
314
+ return true;
315
+ }
316
+ async function stillOwnsStateLock(ownerPath, owner) {
317
+ for (let attempt = 0; attempt < 2; attempt++) {
318
+ try {
319
+ return (await fsp.readFile(ownerPath, "utf8")) === owner;
320
+ }
321
+ catch (err) {
322
+ if (err?.code === "ENOENT" || attempt === 1)
323
+ return false;
324
+ await new Promise((resolve) => setTimeout(resolve, STATE_LOCK_RETRY_MS));
325
+ }
326
+ }
327
+ return false;
328
+ }
329
+ async function withStateKeyLock({ env, key, signal, task, }) {
330
+ const stateDir = defaultStateDir(env);
331
+ return withFileLock({
332
+ filePath: keyToPath(stateDir, key),
333
+ signal,
334
+ task,
335
+ });
336
+ }
337
+ /**
338
+ * Serialize a transition for an arbitrary durable file. Readers that need a
339
+ * publish-or-rollback decision use the same lock as writers, rather than
340
+ * observing an atomic rename that cancellation may still need to undo.
341
+ */
342
+ export async function withFileLock({ filePath, signal, task, }) {
343
+ await ensureDirectory(path.dirname(filePath));
344
+ const lockPath = `${filePath}.lock`;
345
+ let acquired = false;
346
+ let owner;
347
+ let ownerWritten = false;
348
+ let heartbeat;
349
+ try {
350
+ while (!acquired) {
351
+ signal?.throwIfAborted();
352
+ try {
353
+ await fsp.mkdir(lockPath, { mode: 0o700 });
354
+ acquired = true;
355
+ const processStartIdentity = await readProcessStartIdentity(process.pid);
356
+ owner = `${process.pid}:${processStartIdentity ?? ""}:${randomBytes(6).toString("hex")}\n`;
357
+ await fsp.writeFile(path.join(lockPath, "owner"), owner, {
358
+ encoding: "utf8",
359
+ mode: 0o600,
360
+ });
361
+ ownerWritten = true;
362
+ const ownerPath = path.join(lockPath, "owner");
363
+ heartbeat = setInterval(() => {
364
+ void fsp.utimes(ownerPath, new Date(), new Date()).catch(() => { });
365
+ }, STATE_LOCK_HEARTBEAT_MS);
366
+ heartbeat.unref?.();
367
+ }
368
+ catch (err) {
369
+ if (acquired)
370
+ throw err;
371
+ if (err?.code !== "EEXIST")
372
+ throw err;
373
+ if (!(await reclaimOrphanedStateLock(lockPath))) {
374
+ await waitForStateLock(signal);
375
+ }
376
+ }
377
+ }
378
+ return await task();
379
+ }
380
+ finally {
381
+ if (heartbeat)
382
+ clearInterval(heartbeat);
383
+ if (acquired) {
384
+ const ownerPath = path.join(lockPath, "owner");
385
+ if (!ownerWritten || !owner || (await stillOwnsStateLock(ownerPath, owner))) {
386
+ // Detach an owned lock before best-effort cleanup. If a filesystem error
387
+ // prevents removing the detached directory, the canonical lock path is
388
+ // already free for a subsequent operation; leaving it in place would
389
+ // instead look like a live lock owned by this still-running process.
390
+ const cleanupPath = `${lockPath}.release-${process.pid}-${randomBytes(6).toString("hex")}`;
391
+ try {
392
+ await fsp.rename(lockPath, cleanupPath);
393
+ await fsp.rm(cleanupPath, { recursive: true, force: true }).catch(() => { });
394
+ }
395
+ catch (err) {
396
+ if (err?.code !== "ENOENT") {
397
+ await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => { });
398
+ }
399
+ }
400
+ }
401
+ }
402
+ }
403
+ }
87
404
  function isLinkUnsupportedError(err) {
88
405
  return ["ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"].includes(err?.code);
89
406
  }
@@ -125,9 +442,19 @@ export async function writeFileAtomic(filePath, data, options = {}) {
125
442
  await handle.sync();
126
443
  await handle.close();
127
444
  handle = undefined;
445
+ options.signal?.throwIfAborted();
128
446
  await renameFile(tmpPath, filePath);
129
- await syncDir(filePath);
130
447
  cleanup = false;
448
+ // The rename is the irreversible publication point. Keep propagating a
449
+ // directory-sync failure, but mark it so a state transition can reconcile
450
+ // the visible replacement before deciding whether dispatch is safe.
451
+ try {
452
+ await syncDir(filePath);
453
+ }
454
+ catch (err) {
455
+ throw markAtomicWritePublished(err);
456
+ }
457
+ return { signalAbortedAfterCommit: options.signal?.aborted === true };
131
458
  }
132
459
  finally {
133
460
  if (handle)
@@ -176,12 +503,15 @@ export async function writeFileAtomicExclusive(filePath, data, options = {}) {
176
503
  await fsp.rm(tmpPath, { force: true }).catch(() => { });
177
504
  }
178
505
  }
179
- export async function readStateJson({ env, key }) {
506
+ export async function readStateJson({ env, key, signal = undefined, }) {
507
+ signal?.throwIfAborted();
180
508
  const stateDir = defaultStateDir(env);
181
509
  const filePath = keyToPath(stateDir, key);
182
510
  try {
183
511
  const text = await fsp.readFile(filePath, "utf8");
184
- return JSON.parse(text);
512
+ const value = JSON.parse(text);
513
+ signal?.throwIfAborted();
514
+ return value;
185
515
  }
186
516
  catch (err) {
187
517
  if (err?.code === "ENOENT")
@@ -189,13 +519,187 @@ export async function readStateJson({ env, key }) {
189
519
  throw err;
190
520
  }
191
521
  }
192
- export async function writeStateJson({ env, key, value }) {
522
+ /**
523
+ * Read a state record only after any in-progress publish-or-rollback transition
524
+ * for the same key has settled. Callers that compose state with another durable
525
+ * resource use this to avoid observing a value that cancellation may undo.
526
+ */
527
+ export async function readStateJsonWithLock({ env, key, signal = undefined, }) {
528
+ try {
529
+ return await withStateKeyLock({ env, key, signal, task: () => readStateJson({ env, key }) });
530
+ }
531
+ catch (err) {
532
+ // A readable state directory can deliberately be mounted read-only. In that
533
+ // case no writer can begin a paired publish-or-rollback transition, so use
534
+ // the non-mutating JSON read instead of requiring creation of a lock path.
535
+ if (["EACCES", "EPERM", "EROFS"].includes(err?.code)) {
536
+ return readStateJson({ env, key, signal });
537
+ }
538
+ throw err;
539
+ }
540
+ }
541
+ async function writeStateJsonUnlocked({ env, key, value, signal = undefined, atomicWriteOptions = undefined, }) {
193
542
  const stateDir = defaultStateDir(env);
194
543
  const filePath = keyToPath(stateDir, key);
195
544
  await ensureDirectory(stateDir);
196
- await writeFileAtomic(filePath, JSON.stringify(value, null, 2) + "\n");
545
+ return writeFileAtomic(filePath, JSON.stringify(value, null, 2) + "\n", {
546
+ ...atomicWriteOptions,
547
+ signal,
548
+ });
549
+ }
550
+ export async function writeStateJson({ env, key, value, signal = undefined, atomicWriteOptions = undefined, }) {
551
+ return withStateKeyLock({
552
+ env,
553
+ key,
554
+ signal,
555
+ task: () => writeStateJsonUnlocked({ env, key, value, signal, atomicWriteOptions }),
556
+ });
197
557
  }
198
- export async function deleteStateJson({ env, key }) {
558
+ /**
559
+ * Retire a resume capability before its first unsafe execution boundary. The
560
+ * replacement is deliberately persistent: a failed later unlink must not make
561
+ * the original token replayable.
562
+ */
563
+ export async function consumeResumeState({ env, key, expectedState, signal = undefined, }) {
564
+ return withStateKeyLock({
565
+ env,
566
+ key,
567
+ signal,
568
+ task: async () => {
569
+ const currentState = await readStateJson({ env, key });
570
+ // A resume snapshot is loaded before command/workflow setup. Re-check it
571
+ // while holding the state lock so two callers cannot both turn the same
572
+ // approval into an executable invocation.
573
+ if (stableStringify(currentState) !== stableStringify(expectedState)) {
574
+ return { consumed: false };
575
+ }
576
+ const claimId = randomBytes(16).toString("hex");
577
+ try {
578
+ const result = await writeStateJsonUnlocked({
579
+ env,
580
+ key,
581
+ value: {
582
+ type: CONSUMED_RESUME_STATE_TYPE,
583
+ consumedAt: new Date().toISOString(),
584
+ claimId,
585
+ },
586
+ signal,
587
+ });
588
+ return {
589
+ consumed: true,
590
+ claimId,
591
+ signalAbortedAfterCommit: result?.signalAbortedAfterCommit === true,
592
+ };
593
+ }
594
+ catch (err) {
595
+ if (atomicWriteWasPublished(err)) {
596
+ const latest = await readStateJson({ env, key });
597
+ if (isConsumedResumeState(latest) && latest.claimId === claimId) {
598
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
599
+ }
600
+ }
601
+ throw err;
602
+ }
603
+ },
604
+ });
605
+ }
606
+ /**
607
+ * Undo a just-published consumed marker only when it is still owned by the
608
+ * caller's pre-dispatch claim. This never overwrites a replacement state or a
609
+ * concurrent claimant's marker.
610
+ */
611
+ export async function restoreConsumedResumeState({ env, key, expectedState, claimId, }) {
612
+ return withStateKeyLock({
613
+ env,
614
+ key,
615
+ task: async () => {
616
+ const currentState = await readStateJson({ env, key });
617
+ if (!isConsumedResumeState(currentState) || currentState.claimId !== claimId) {
618
+ return false;
619
+ }
620
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
621
+ return true;
622
+ },
623
+ });
624
+ }
625
+ /**
626
+ * Retire an unconsumed capability at a safe terminal boundary. The snapshot is
627
+ * first replaced by a caller-owned marker under the state lock, so a
628
+ * cancellation can restore only the state this caller claimed. A stale resume
629
+ * that merely observed an earlier snapshot can never recreate a state another
630
+ * resume has already settled.
631
+ */
632
+ export async function deleteResumeStateWithRollback({ env, key, expectedState, signal = undefined, }) {
633
+ return withStateKeyLock({
634
+ env,
635
+ key,
636
+ signal,
637
+ task: async () => {
638
+ signal?.throwIfAborted();
639
+ const currentState = await readStateJson({ env, key });
640
+ if (stableStringify(currentState) !== stableStringify(expectedState))
641
+ return false;
642
+ const claimId = randomBytes(16).toString("hex");
643
+ let claimPublished = false;
644
+ let claimMayBePublished = false;
645
+ let deleted = false;
646
+ try {
647
+ let result;
648
+ try {
649
+ result = await writeStateJsonUnlocked({
650
+ env,
651
+ key,
652
+ value: {
653
+ type: CONSUMED_RESUME_STATE_TYPE,
654
+ consumedAt: new Date().toISOString(),
655
+ claimId,
656
+ },
657
+ signal,
658
+ });
659
+ }
660
+ catch (err) {
661
+ claimMayBePublished = atomicWriteWasPublished(err);
662
+ throw err;
663
+ }
664
+ claimPublished = true;
665
+ if (result?.signalAbortedAfterCommit)
666
+ signal?.throwIfAborted();
667
+ signal?.throwIfAborted();
668
+ await deleteStateJsonUnlocked({ env, key });
669
+ deleted = true;
670
+ signal?.throwIfAborted();
671
+ return true;
672
+ }
673
+ catch (err) {
674
+ if ((signal?.aborted && claimPublished) || claimMayBePublished) {
675
+ const latest = await readStateJson({ env, key });
676
+ if ((deleted && latest === null) ||
677
+ (isConsumedResumeState(latest) && latest.claimId === claimId)) {
678
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
679
+ }
680
+ }
681
+ throw err;
682
+ }
683
+ },
684
+ });
685
+ }
686
+ /**
687
+ * Check physical state presence without parsing it. Cancellation uses this to
688
+ * retain the authoritative workflow spelling even if the state file is corrupt.
689
+ */
690
+ export async function stateJsonExists({ env, key, }) {
691
+ const filePath = keyToPath(defaultStateDir(env), key);
692
+ try {
693
+ await fsp.access(filePath);
694
+ return true;
695
+ }
696
+ catch (err) {
697
+ if (err?.code === "ENOENT")
698
+ return false;
699
+ throw err;
700
+ }
701
+ }
702
+ async function deleteStateJsonUnlocked({ env, key }) {
199
703
  const stateDir = defaultStateDir(env);
200
704
  const filePath = keyToPath(stateDir, key);
201
705
  try {
@@ -207,6 +711,62 @@ export async function deleteStateJson({ env, key }) {
207
711
  throw err;
208
712
  }
209
713
  }
714
+ export async function deleteStateJson({ env, key, signal = undefined, }) {
715
+ return withStateKeyLock({
716
+ env,
717
+ key,
718
+ signal,
719
+ task: () => deleteStateJsonUnlocked({ env, key }),
720
+ });
721
+ }
722
+ /**
723
+ * After an effect has started, its consumed marker already prevents replay.
724
+ * Give terminal cleanup a small, bounded opportunity to remove that marker,
725
+ * but never let a live state writer turn cancellation into an unbounded wait.
726
+ */
727
+ export async function deleteStateJsonWithBoundedResumeCleanup({ env, key, }) {
728
+ const controller = new AbortController();
729
+ const timeout = setTimeout(() => controller.abort(), TERMINAL_RESUME_CLEANUP_TIMEOUT_MS);
730
+ try {
731
+ await deleteStateJson({ env, key, signal: controller.signal });
732
+ }
733
+ finally {
734
+ clearTimeout(timeout);
735
+ }
736
+ }
737
+ /**
738
+ * Retire a resume capability only while it is still a live, unclaimed state.
739
+ * A consumed marker belongs to a resume that has already started its atomic
740
+ * predecessor-to-successor handoff, so cancellation must not delete it and
741
+ * report success while that successor remains executable.
742
+ */
743
+ export async function deleteUnconsumedResumeState({ env, key, signal = undefined, }) {
744
+ return withStateKeyLock({
745
+ env,
746
+ key,
747
+ signal,
748
+ task: async () => {
749
+ let currentState;
750
+ try {
751
+ currentState = await readStateJson({ env, key });
752
+ }
753
+ catch (err) {
754
+ // A corrupt state is not resumable, so explicit cancellation may still
755
+ // remove it. This keeps the legacy workflow-alias recovery behavior.
756
+ if (!isJsonSyntaxError(err))
757
+ throw err;
758
+ await deleteStateJsonUnlocked({ env, key });
759
+ return "deleted";
760
+ }
761
+ if (currentState === null)
762
+ return "missing";
763
+ if (isConsumedResumeState(currentState))
764
+ return "claimed";
765
+ await deleteStateJsonUnlocked({ env, key });
766
+ return "deleted";
767
+ },
768
+ });
769
+ }
210
770
  function sanitizeApprovalId(approvalId) {
211
771
  return approvalId.replace(/[^a-f0-9]/g, "");
212
772
  }
@@ -324,14 +884,54 @@ export async function cleanupApprovalIndexByStateKey({ env, stateKey, }) {
324
884
  }
325
885
  }
326
886
  }
327
- export async function diffAndStore({ env, key, value }) {
328
- const before = await readStateJson({ env, key }).catch((err) => {
329
- if (isJsonSyntaxError(err))
330
- return null;
331
- throw err;
887
+ export async function diffAndStore({ env, key, value, signal = undefined, atomicWriteOptions = undefined, afterStore = undefined, }) {
888
+ return withStateKeyLock({
889
+ env,
890
+ key,
891
+ signal,
892
+ task: async () => {
893
+ const filePath = keyToPath(defaultStateDir(env), key);
894
+ let beforeExists = true;
895
+ try {
896
+ await fsp.access(filePath);
897
+ }
898
+ catch (err) {
899
+ if (err?.code !== "ENOENT")
900
+ throw err;
901
+ beforeExists = false;
902
+ }
903
+ const before = await readStateJson({ env, key }).catch((err) => {
904
+ if (isJsonSyntaxError(err))
905
+ return null;
906
+ throw err;
907
+ });
908
+ const changed = stableStringify(before) !== stableStringify(value);
909
+ const snapshot = { before, after: value, changed };
910
+ let stored = false;
911
+ try {
912
+ signal?.throwIfAborted();
913
+ await writeStateJsonUnlocked({ env, key, value, signal, atomicWriteOptions });
914
+ stored = true;
915
+ signal?.throwIfAborted();
916
+ await afterStore?.(snapshot);
917
+ }
918
+ catch (err) {
919
+ // A caller can publish another resource while this state lock is held.
920
+ // If that coordinated publication fails, restore the state snapshot before
921
+ // releasing the lock so readers never reuse a cancelled result.
922
+ const stateWasPublished = stored || atomicWriteWasPublished(err);
923
+ if (stateWasPublished && (signal?.aborted || afterStore || atomicWriteWasPublished(err))) {
924
+ if (!beforeExists) {
925
+ await deleteStateJsonUnlocked({ env, key });
926
+ }
927
+ else {
928
+ await writeStateJsonUnlocked({ env, key, value: before });
929
+ }
930
+ }
931
+ throw err;
932
+ }
933
+ return snapshot;
934
+ },
332
935
  });
333
- const changed = stableStringify(before) !== stableStringify(value);
334
- await writeStateJson({ env, key, value });
335
- return { before, after: value, changed };
336
936
  }
337
937
  //# sourceMappingURL=store.js.map