@openclaw/lobster 2026.9.4 → 2026.9.6

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 (367) 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 +129 -131
  5. package/node_modules/@clawdbot/lobster/VISION.md +15 -238
  6. package/node_modules/@clawdbot/lobster/bin/clawd.invoke.js +2 -15
  7. package/node_modules/@clawdbot/lobster/bin/invoke.js +19 -0
  8. package/node_modules/@clawdbot/lobster/bin/lobster.js +2 -21
  9. package/node_modules/@clawdbot/lobster/bin/openclaw.invoke.js +2 -18
  10. package/node_modules/@clawdbot/lobster/dist/src/abortable_process.js +297 -0
  11. package/node_modules/@clawdbot/lobster/dist/src/abortable_process.js.map +1 -0
  12. package/node_modules/@clawdbot/lobster/dist/src/cli.js +128 -296
  13. package/node_modules/@clawdbot/lobster/dist/src/cli.js.map +1 -1
  14. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js +81 -0
  15. package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js.map +1 -0
  16. package/node_modules/@clawdbot/lobster/dist/src/commands/commands_list.js.map +1 -1
  17. package/node_modules/@clawdbot/lobster/dist/src/commands/registry.js +2 -0
  18. package/node_modules/@clawdbot/lobster/dist/src/commands/registry.js.map +1 -1
  19. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/approve.js +2 -0
  20. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/approve.js.map +1 -1
  21. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/ask.js +3 -1
  22. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/ask.js.map +1 -1
  23. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js +1 -12
  24. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js.map +1 -1
  25. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/diff_last.js +6 -1
  26. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/diff_last.js.map +1 -1
  27. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/email_triage.js.map +1 -1
  28. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/exec.js +30 -34
  29. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/exec.js.map +1 -1
  30. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_search.js +12 -25
  31. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_search.js.map +1 -1
  32. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_send.js +19 -25
  33. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/gog_gmail_send.js.map +1 -1
  34. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js +2 -14
  35. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js.map +1 -1
  36. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/head.js.map +1 -1
  37. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/json.js.map +1 -1
  38. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js +260 -42
  39. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js.map +1 -1
  40. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js +2 -13
  41. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js.map +1 -1
  42. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_agent.js +139 -0
  43. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_agent.js.map +1 -0
  44. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_invoke.js +20 -2
  45. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/openclaw_invoke.js.map +1 -1
  46. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/pick.js.map +1 -1
  47. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js +4 -21
  48. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js.map +1 -1
  49. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js +48 -20
  50. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js.map +1 -1
  51. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/table.js.map +1 -1
  52. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js +2 -13
  53. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js.map +1 -1
  54. package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/where.js.map +1 -1
  55. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_list.js.map +1 -1
  56. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js +0 -18
  57. package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js.map +1 -1
  58. package/node_modules/@clawdbot/lobster/dist/src/core/cost_tracker.js +45 -3
  59. package/node_modules/@clawdbot/lobster/dist/src/core/cost_tracker.js.map +1 -1
  60. package/node_modules/@clawdbot/lobster/dist/src/core/filters.js.map +1 -1
  61. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js +304 -0
  62. package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js.map +1 -0
  63. package/node_modules/@clawdbot/lobster/dist/src/core/retry.js.map +1 -1
  64. package/node_modules/@clawdbot/lobster/dist/src/core/tool_runtime.js +147 -21
  65. package/node_modules/@clawdbot/lobster/dist/src/core/tool_runtime.js.map +1 -1
  66. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js +11 -0
  67. package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js.map +1 -0
  68. package/node_modules/@clawdbot/lobster/dist/src/input_request.js +4 -2
  69. package/node_modules/@clawdbot/lobster/dist/src/input_request.js.map +1 -1
  70. package/node_modules/@clawdbot/lobster/dist/src/parser.js +5 -69
  71. package/node_modules/@clawdbot/lobster/dist/src/parser.js.map +1 -1
  72. package/node_modules/@clawdbot/lobster/dist/src/pipeline_resume_state.js +241 -74
  73. package/node_modules/@clawdbot/lobster/dist/src/pipeline_resume_state.js.map +1 -1
  74. package/node_modules/@clawdbot/lobster/dist/src/read_line.js +52 -7
  75. package/node_modules/@clawdbot/lobster/dist/src/read_line.js.map +1 -1
  76. package/node_modules/@clawdbot/lobster/dist/src/read_response_text.js +86 -0
  77. package/node_modules/@clawdbot/lobster/dist/src/read_response_text.js.map +1 -0
  78. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/index.js +1 -1
  79. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js +9 -66
  80. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js.map +1 -1
  81. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js +45 -0
  82. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js.map +1 -0
  83. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js +45 -0
  84. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js.map +1 -0
  85. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js +13 -65
  86. package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js.map +1 -1
  87. package/node_modules/@clawdbot/lobster/dist/src/recipes/registry.js.map +1 -1
  88. package/node_modules/@clawdbot/lobster/dist/src/renderers/json.js.map +1 -1
  89. package/node_modules/@clawdbot/lobster/dist/src/resume.js.map +1 -1
  90. package/node_modules/@clawdbot/lobster/dist/src/runtime.js +187 -17
  91. package/node_modules/@clawdbot/lobster/dist/src/runtime.js.map +1 -1
  92. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js +25 -134
  93. package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js.map +1 -1
  94. package/node_modules/@clawdbot/lobster/dist/src/sdk/index.js +1 -1
  95. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js +1 -2
  96. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js.map +1 -1
  97. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js +11 -86
  98. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js.map +1 -1
  99. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js +20 -96
  100. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js.map +1 -1
  101. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js +8 -106
  102. package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js.map +1 -1
  103. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js +2 -13
  104. package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js.map +1 -1
  105. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js +6 -0
  106. package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js.map +1 -0
  107. package/node_modules/@clawdbot/lobster/dist/src/shell.js.map +1 -1
  108. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js +192 -0
  109. package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js.map +1 -0
  110. package/node_modules/@clawdbot/lobster/dist/src/state/store.js +550 -136
  111. package/node_modules/@clawdbot/lobster/dist/src/state/store.js.map +1 -1
  112. package/node_modules/@clawdbot/lobster/dist/src/token.js.map +1 -1
  113. package/node_modules/@clawdbot/lobster/dist/src/validation.js.map +1 -1
  114. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js +262 -0
  115. package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js.map +1 -0
  116. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js +345 -0
  117. package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js.map +1 -0
  118. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js +950 -1285
  119. package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js.map +1 -1
  120. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js +20 -90
  121. package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js.map +1 -1
  122. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js +9 -8
  123. package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js.map +1 -1
  124. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js +347 -0
  125. package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js.map +1 -0
  126. package/node_modules/@clawdbot/lobster/dist/src/workflows/registry.js.map +1 -1
  127. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js +2 -0
  128. package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js.map +1 -0
  129. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js +93 -0
  130. package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js.map +1 -0
  131. package/node_modules/@clawdbot/lobster/dist/test/approval_id.test.js.map +1 -1
  132. package/node_modules/@clawdbot/lobster/dist/test/approve_preview.test.js.map +1 -1
  133. package/node_modules/@clawdbot/lobster/dist/test/bin_smoke.test.js +25 -0
  134. package/node_modules/@clawdbot/lobster/dist/test/bin_smoke.test.js.map +1 -0
  135. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js +5171 -0
  136. package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js.map +1 -0
  137. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke.test.js +22 -0
  138. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke.test.js.map +1 -1
  139. package/node_modules/@clawdbot/lobster/dist/test/clawd_invoke_legacy.test.js.map +1 -1
  140. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js +53 -0
  141. package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js.map +1 -0
  142. package/node_modules/@clawdbot/lobster/dist/test/cli_run_file_args_json.test.js.map +1 -1
  143. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js +82 -0
  144. package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js.map +1 -0
  145. package/node_modules/@clawdbot/lobster/dist/test/commands_list.test.js.map +1 -1
  146. package/node_modules/@clawdbot/lobster/dist/test/condition_comparison.test.js.map +1 -1
  147. package/node_modules/@clawdbot/lobster/dist/test/core_tool_runtime.test.js.map +1 -1
  148. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js +1774 -12
  149. package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js.map +1 -1
  150. package/node_modules/@clawdbot/lobster/dist/test/dedupe.test.js.map +1 -1
  151. package/node_modules/@clawdbot/lobster/dist/test/diff_last.test.js.map +1 -1
  152. package/node_modules/@clawdbot/lobster/dist/test/doctor.test.js.map +1 -1
  153. package/node_modules/@clawdbot/lobster/dist/test/dry_run.test.js.map +1 -1
  154. package/node_modules/@clawdbot/lobster/dist/test/email_triage.test.js.map +1 -1
  155. package/node_modules/@clawdbot/lobster/dist/test/exec_stdin.test.js.map +1 -1
  156. package/node_modules/@clawdbot/lobster/dist/test/for_each.test.js +254 -0
  157. package/node_modules/@clawdbot/lobster/dist/test/for_each.test.js.map +1 -1
  158. package/node_modules/@clawdbot/lobster/dist/test/github_pr_notify_format.test.js.map +1 -1
  159. package/node_modules/@clawdbot/lobster/dist/test/github_pr_summary.test.js.map +1 -1
  160. package/node_modules/@clawdbot/lobster/dist/test/github_pr_view.test.js +176 -0
  161. package/node_modules/@clawdbot/lobster/dist/test/github_pr_view.test.js.map +1 -0
  162. package/node_modules/@clawdbot/lobster/dist/test/gog_gmail_send.test.js +100 -0
  163. package/node_modules/@clawdbot/lobster/dist/test/gog_gmail_send.test.js.map +1 -0
  164. package/node_modules/@clawdbot/lobster/dist/test/group_by.test.js.map +1 -1
  165. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js +17 -0
  166. package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js.map +1 -0
  167. package/node_modules/@clawdbot/lobster/dist/test/helpers/wait_for_pid.js +19 -0
  168. package/node_modules/@clawdbot/lobster/dist/test/helpers/wait_for_pid.js.map +1 -0
  169. package/node_modules/@clawdbot/lobster/dist/test/http_response_body_cap.test.js +318 -0
  170. package/node_modules/@clawdbot/lobster/dist/test/http_response_body_cap.test.js.map +1 -0
  171. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js +948 -1
  172. package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js.map +1 -1
  173. package/node_modules/@clawdbot/lobster/dist/test/llm_task_invoke.test.js +103 -0
  174. package/node_modules/@clawdbot/lobster/dist/test/llm_task_invoke.test.js.map +1 -1
  175. package/node_modules/@clawdbot/lobster/dist/test/map.test.js.map +1 -1
  176. package/node_modules/@clawdbot/lobster/dist/test/multi_approval_resume.test.js.map +1 -1
  177. package/node_modules/@clawdbot/lobster/dist/test/on_error.test.js.map +1 -1
  178. package/node_modules/@clawdbot/lobster/dist/test/openclaw_agent.test.js +169 -0
  179. package/node_modules/@clawdbot/lobster/dist/test/openclaw_agent.test.js.map +1 -0
  180. package/node_modules/@clawdbot/lobster/dist/test/openclaw_dispatch_retry.test.js +96 -0
  181. package/node_modules/@clawdbot/lobster/dist/test/openclaw_dispatch_retry.test.js.map +1 -0
  182. package/node_modules/@clawdbot/lobster/dist/test/openclaw_invoke_alias.test.js.map +1 -1
  183. package/node_modules/@clawdbot/lobster/dist/test/parallel.test.js.map +1 -1
  184. package/node_modules/@clawdbot/lobster/dist/test/parser.test.js +57 -0
  185. package/node_modules/@clawdbot/lobster/dist/test/parser.test.js.map +1 -1
  186. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js +65 -0
  187. package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js.map +1 -0
  188. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js +175 -0
  189. package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js.map +1 -0
  190. package/node_modules/@clawdbot/lobster/dist/test/read_line.test.js +54 -0
  191. package/node_modules/@clawdbot/lobster/dist/test/read_line.test.js.map +1 -1
  192. package/node_modules/@clawdbot/lobster/dist/test/release_changelog.test.js +39 -0
  193. package/node_modules/@clawdbot/lobster/dist/test/release_changelog.test.js.map +1 -0
  194. package/node_modules/@clawdbot/lobster/dist/test/request_input.test.js +1 -1
  195. package/node_modules/@clawdbot/lobster/dist/test/request_input.test.js.map +1 -1
  196. package/node_modules/@clawdbot/lobster/dist/test/resume.test.js.map +1 -1
  197. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js +248 -0
  198. package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js.map +1 -0
  199. package/node_modules/@clawdbot/lobster/dist/test/sdk_lobster.test.js.map +1 -1
  200. package/node_modules/@clawdbot/lobster/dist/test/shell.test.js.map +1 -1
  201. package/node_modules/@clawdbot/lobster/dist/test/sort.test.js.map +1 -1
  202. package/node_modules/@clawdbot/lobster/dist/test/state.test.js +584 -1
  203. package/node_modules/@clawdbot/lobster/dist/test/state.test.js.map +1 -1
  204. package/node_modules/@clawdbot/lobster/dist/test/step_retry.test.js.map +1 -1
  205. package/node_modules/@clawdbot/lobster/dist/test/step_stdin_epipe.test.js +44 -0
  206. package/node_modules/@clawdbot/lobster/dist/test/step_stdin_epipe.test.js.map +1 -0
  207. package/node_modules/@clawdbot/lobster/dist/test/step_timeout.test.js +67 -1
  208. package/node_modules/@clawdbot/lobster/dist/test/step_timeout.test.js.map +1 -1
  209. package/node_modules/@clawdbot/lobster/dist/test/template.test.js.map +1 -1
  210. package/node_modules/@clawdbot/lobster/dist/test/template_filters.test.js +37 -0
  211. package/node_modules/@clawdbot/lobster/dist/test/template_filters.test.js.map +1 -1
  212. package/node_modules/@clawdbot/lobster/dist/test/tool_envelope_version.test.js.map +1 -1
  213. package/node_modules/@clawdbot/lobster/dist/test/tool_mode.test.js.map +1 -1
  214. package/node_modules/@clawdbot/lobster/dist/test/validation.test.js.map +1 -1
  215. package/node_modules/@clawdbot/lobster/dist/test/where.test.js +94 -0
  216. package/node_modules/@clawdbot/lobster/dist/test/where.test.js.map +1 -0
  217. package/node_modules/@clawdbot/lobster/dist/test/workflow_args_env.test.js +5 -3
  218. package/node_modules/@clawdbot/lobster/dist/test/workflow_args_env.test.js.map +1 -1
  219. package/node_modules/@clawdbot/lobster/dist/test/workflow_composition.test.js.map +1 -1
  220. package/node_modules/@clawdbot/lobster/dist/test/workflow_file.test.js +647 -1
  221. package/node_modules/@clawdbot/lobster/dist/test/workflow_file.test.js.map +1 -1
  222. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js +13 -3
  223. package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js.map +1 -1
  224. package/node_modules/@clawdbot/lobster/dist/test/workflows.test.js.map +1 -1
  225. package/node_modules/@clawdbot/lobster/package.json +72 -73
  226. package/node_modules/typebox/build/compile/validator.d.mts +1 -1
  227. package/node_modules/typebox/build/compile/validator.mjs +1 -1
  228. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +7 -5
  229. package/node_modules/typebox/build/format/iri.mjs +5 -1
  230. package/node_modules/typebox/build/guard/emit.mjs +2 -4
  231. package/node_modules/typebox/build/guard/guard.mjs +6 -18
  232. package/node_modules/typebox/build/guard/string.mjs +16 -24
  233. package/node_modules/typebox/build/schema/build.mjs +2 -2
  234. package/node_modules/typebox/build/schema/check.mjs +1 -1
  235. package/node_modules/typebox/build/schema/compile.d.mts +3 -2
  236. package/node_modules/typebox/build/schema/compile.mjs +8 -8
  237. package/node_modules/typebox/build/schema/engine/_context.d.mts +6 -7
  238. package/node_modules/typebox/build/schema/engine/_context.mjs +23 -15
  239. package/node_modules/typebox/build/schema/engine/_functions.d.mts +2 -2
  240. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -1
  241. package/node_modules/typebox/build/schema/engine/_reducer.d.mts +3 -3
  242. package/node_modules/typebox/build/schema/engine/_refine.d.mts +5 -5
  243. package/node_modules/typebox/build/schema/engine/_refine.mjs +4 -7
  244. package/node_modules/typebox/build/schema/engine/_stack.d.mts +34 -16
  245. package/node_modules/typebox/build/schema/engine/_stack.mjs +148 -104
  246. package/node_modules/typebox/build/schema/engine/additionalItems.d.mts +4 -4
  247. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +8 -8
  248. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +26 -18
  249. package/node_modules/typebox/build/schema/engine/allOf.d.mts +4 -4
  250. package/node_modules/typebox/build/schema/engine/allOf.mjs +3 -3
  251. package/node_modules/typebox/build/schema/engine/anyOf.d.mts +4 -4
  252. package/node_modules/typebox/build/schema/engine/anyOf.mjs +5 -10
  253. package/node_modules/typebox/build/schema/engine/boolean.d.mts +4 -4
  254. package/node_modules/typebox/build/schema/engine/boolean.mjs +2 -6
  255. package/node_modules/typebox/build/schema/engine/const.d.mts +4 -4
  256. package/node_modules/typebox/build/schema/engine/const.mjs +2 -6
  257. package/node_modules/typebox/build/schema/engine/contains.d.mts +4 -4
  258. package/node_modules/typebox/build/schema/engine/contains.mjs +2 -6
  259. package/node_modules/typebox/build/schema/engine/dependencies.d.mts +4 -4
  260. package/node_modules/typebox/build/schema/engine/dependencies.mjs +3 -6
  261. package/node_modules/typebox/build/schema/engine/dependentRequired.d.mts +4 -4
  262. package/node_modules/typebox/build/schema/engine/dependentRequired.mjs +2 -6
  263. package/node_modules/typebox/build/schema/engine/dependentSchemas.d.mts +4 -4
  264. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +4 -4
  265. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +11 -7
  266. package/node_modules/typebox/build/schema/engine/enum.d.mts +4 -4
  267. package/node_modules/typebox/build/schema/engine/enum.mjs +2 -6
  268. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.d.mts +4 -4
  269. package/node_modules/typebox/build/schema/engine/exclusiveMaximum.mjs +2 -6
  270. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.d.mts +4 -4
  271. package/node_modules/typebox/build/schema/engine/exclusiveMinimum.mjs +2 -6
  272. package/node_modules/typebox/build/schema/engine/format.d.mts +4 -4
  273. package/node_modules/typebox/build/schema/engine/format.mjs +2 -6
  274. package/node_modules/typebox/build/schema/engine/if.d.mts +4 -4
  275. package/node_modules/typebox/build/schema/engine/if.mjs +7 -15
  276. package/node_modules/typebox/build/schema/engine/items.d.mts +4 -4
  277. package/node_modules/typebox/build/schema/engine/items.mjs +1 -1
  278. package/node_modules/typebox/build/schema/engine/maxContains.d.mts +4 -4
  279. package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -6
  280. package/node_modules/typebox/build/schema/engine/maxItems.d.mts +4 -4
  281. package/node_modules/typebox/build/schema/engine/maxItems.mjs +2 -6
  282. package/node_modules/typebox/build/schema/engine/maxLength.d.mts +4 -4
  283. package/node_modules/typebox/build/schema/engine/maxLength.mjs +2 -6
  284. package/node_modules/typebox/build/schema/engine/maxProperties.d.mts +4 -4
  285. package/node_modules/typebox/build/schema/engine/maxProperties.mjs +2 -6
  286. package/node_modules/typebox/build/schema/engine/maximum.d.mts +4 -4
  287. package/node_modules/typebox/build/schema/engine/maximum.mjs +2 -6
  288. package/node_modules/typebox/build/schema/engine/minContains.d.mts +4 -4
  289. package/node_modules/typebox/build/schema/engine/minContains.mjs +2 -6
  290. package/node_modules/typebox/build/schema/engine/minItems.d.mts +5 -5
  291. package/node_modules/typebox/build/schema/engine/minItems.mjs +2 -6
  292. package/node_modules/typebox/build/schema/engine/minLength.d.mts +4 -4
  293. package/node_modules/typebox/build/schema/engine/minLength.mjs +2 -6
  294. package/node_modules/typebox/build/schema/engine/minProperties.d.mts +4 -4
  295. package/node_modules/typebox/build/schema/engine/minProperties.mjs +2 -6
  296. package/node_modules/typebox/build/schema/engine/minimum.d.mts +4 -4
  297. package/node_modules/typebox/build/schema/engine/minimum.mjs +2 -6
  298. package/node_modules/typebox/build/schema/engine/multipleOf.d.mts +4 -4
  299. package/node_modules/typebox/build/schema/engine/multipleOf.mjs +2 -6
  300. package/node_modules/typebox/build/schema/engine/not.d.mts +4 -4
  301. package/node_modules/typebox/build/schema/engine/not.mjs +4 -8
  302. package/node_modules/typebox/build/schema/engine/oneOf.d.mts +4 -4
  303. package/node_modules/typebox/build/schema/engine/oneOf.mjs +6 -11
  304. package/node_modules/typebox/build/schema/engine/pattern.d.mts +4 -4
  305. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -7
  306. package/node_modules/typebox/build/schema/engine/patternProperties.d.mts +4 -4
  307. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +1 -1
  308. package/node_modules/typebox/build/schema/engine/prefixItems.d.mts +4 -4
  309. package/node_modules/typebox/build/schema/engine/prefixItems.mjs +1 -1
  310. package/node_modules/typebox/build/schema/engine/properties.d.mts +4 -4
  311. package/node_modules/typebox/build/schema/engine/properties.mjs +1 -1
  312. package/node_modules/typebox/build/schema/engine/propertyNames.d.mts +4 -4
  313. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +4 -10
  314. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +4 -4
  315. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +11 -7
  316. package/node_modules/typebox/build/schema/engine/ref.d.mts +4 -4
  317. package/node_modules/typebox/build/schema/engine/ref.mjs +19 -15
  318. package/node_modules/typebox/build/schema/engine/required.d.mts +4 -4
  319. package/node_modules/typebox/build/schema/engine/required.mjs +2 -6
  320. package/node_modules/typebox/build/schema/engine/schema.d.mts +7 -7
  321. package/node_modules/typebox/build/schema/engine/schema.mjs +137 -133
  322. package/node_modules/typebox/build/schema/engine/type.d.mts +4 -4
  323. package/node_modules/typebox/build/schema/engine/type.mjs +2 -6
  324. package/node_modules/typebox/build/schema/engine/unevaluatedItems.d.mts +4 -4
  325. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +5 -9
  326. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.d.mts +4 -4
  327. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +5 -9
  328. package/node_modules/typebox/build/schema/engine/uniqueItems.d.mts +4 -4
  329. package/node_modules/typebox/build/schema/engine/uniqueItems.mjs +2 -6
  330. package/node_modules/typebox/build/schema/errors.d.mts +2 -2
  331. package/node_modules/typebox/build/schema/errors.mjs +8 -14
  332. package/node_modules/typebox/build/schema/intern/index.d.mts +1 -0
  333. package/node_modules/typebox/build/schema/intern/index.mjs +1 -0
  334. package/node_modules/typebox/build/schema/intern/intern.d.mts +11 -0
  335. package/node_modules/typebox/build/schema/intern/intern.mjs +293 -0
  336. package/node_modules/typebox/build/schema/meta/draft_2019_09.d.mts +2 -0
  337. package/node_modules/typebox/build/schema/meta/draft_2019_09.mjs +288 -0
  338. package/node_modules/typebox/build/schema/meta/draft_2020_12.d.mts +2 -0
  339. package/node_modules/typebox/build/schema/meta/draft_2020_12.mjs +300 -0
  340. package/node_modules/typebox/build/schema/meta/draft_3.d.mts +2 -0
  341. package/node_modules/typebox/build/schema/meta/draft_3.mjs +143 -0
  342. package/node_modules/typebox/build/schema/meta/draft_4.d.mts +2 -0
  343. package/node_modules/typebox/build/schema/meta/draft_4.mjs +149 -0
  344. package/node_modules/typebox/build/schema/meta/draft_6.d.mts +2 -0
  345. package/node_modules/typebox/build/schema/meta/draft_6.mjs +155 -0
  346. package/node_modules/typebox/build/schema/meta/draft_7.d.mts +2 -0
  347. package/node_modules/typebox/build/schema/meta/draft_7.mjs +172 -0
  348. package/node_modules/typebox/build/schema/meta/index.d.mts +2 -0
  349. package/node_modules/typebox/build/schema/meta/index.mjs +2 -0
  350. package/node_modules/typebox/build/schema/meta/meta.d.mts +9 -0
  351. package/node_modules/typebox/build/schema/meta/meta.mjs +15 -0
  352. package/node_modules/typebox/build/schema/meta/schema.d.mts +6 -0
  353. package/node_modules/typebox/build/schema/meta/schema.mjs +1 -0
  354. package/node_modules/typebox/build/schema/parse.d.mts +1 -0
  355. package/node_modules/typebox/build/schema/parse.mjs +10 -5
  356. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +8 -3
  357. package/node_modules/typebox/build/schema/resolve/resolve.mjs +268 -139
  358. package/node_modules/typebox/build/schema/schema.d.mts +2 -0
  359. package/node_modules/typebox/build/schema/schema.mjs +2 -0
  360. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -12
  361. package/node_modules/typebox/build/value/errors/errors.mjs +2 -7
  362. package/node_modules/typebox/package.json +1 -1
  363. package/node_modules/typebox/readme.md +59 -89
  364. package/openclaw.plugin.json +1 -1
  365. package/package.json +10 -7
  366. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js +0 -9
  367. package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js.map +0 -1
@@ -1,17 +1,28 @@
1
+ import { atomicWriteWasPublished, ensureDirectory, isAtomicExclusiveUnsupportedError, writeFileAtomic, writeFileAtomicExclusive, } from "./atomic_file.js";
2
+ export { atomicWriteWasPublished, ensureDirectory, isAtomicExclusiveUnsupportedError, writeFileAtomic, writeFileAtomicExclusive, stripExtendedLengthPrefix, } from "./atomic_file.js";
1
3
  import os from "node:os";
2
4
  import path from "node:path";
3
5
  import { promises as fsp } from "node:fs";
4
6
  import { randomBytes } from "node:crypto";
7
+ const CONSUMED_RESUME_STATE_TYPE = "lobster.consumed-resume-state.v1";
8
+ export function isConsumedResumeState(value) {
9
+ return (value !== null &&
10
+ typeof value === "object" &&
11
+ value.type === CONSUMED_RESUME_STATE_TYPE);
12
+ }
5
13
  export function defaultStateDir(env) {
6
14
  return ((env?.LOBSTER_STATE_DIR && String(env.LOBSTER_STATE_DIR).trim()) ||
7
15
  path.join(os.homedir(), ".lobster", "state"));
8
16
  }
9
17
  export function keyToPath(stateDir, key) {
10
- const safe = String(key)
18
+ let safe = String(key)
11
19
  .toLowerCase()
12
20
  .replace(/[^a-z0-9._-]+/g, "_")
13
- .replace(/_+/g, "_")
14
- .replace(/^_+|_+$/g, "");
21
+ .replace(/_+/g, "_");
22
+ if (safe.startsWith("_"))
23
+ safe = safe.slice(1);
24
+ if (safe.endsWith("_"))
25
+ safe = safe.slice(0, -1);
15
26
  if (!safe)
16
27
  throw new Error("state key is empty/invalid");
17
28
  return path.join(stateDir, `${safe}.json`);
@@ -26,162 +37,295 @@ export function stableStringify(value) {
26
37
  return v;
27
38
  });
28
39
  }
29
- function isDirectorySyncUnsupportedError(err) {
30
- return [
31
- "EACCES",
32
- "EBADF",
33
- "EINVAL",
34
- "EISDIR",
35
- "ENOSYS",
36
- "ENOTSUP",
37
- "EOPNOTSUPP",
38
- "EPERM",
39
- ].includes(err?.code);
40
- }
41
- async function syncParentDir(filePath) {
42
- await syncDirectory(path.dirname(filePath));
43
- }
44
- async function syncDirectory(dir) {
45
- let handle;
40
+ const STATE_LOCK_RETRY_MS = 10;
41
+ const STATE_LOCK_ORPHAN_MS = 30_000;
42
+ const STATE_LOCK_HEARTBEAT_MS = 250;
43
+ const TERMINAL_RESUME_CLEANUP_TIMEOUT_MS = STATE_LOCK_RETRY_MS * 10;
44
+ export function isJsonSyntaxError(err) {
45
+ return err instanceof SyntaxError;
46
+ }
47
+ async function waitForStateLock(signal) {
48
+ await new Promise((resolve, reject) => {
49
+ const onAbort = () => {
50
+ clearTimeout(timer);
51
+ try {
52
+ signal?.throwIfAborted();
53
+ }
54
+ catch (err) {
55
+ reject(err);
56
+ }
57
+ };
58
+ const timer = setTimeout(() => {
59
+ signal?.removeEventListener("abort", onAbort);
60
+ resolve();
61
+ }, STATE_LOCK_RETRY_MS);
62
+ if (signal?.aborted) {
63
+ onAbort();
64
+ return;
65
+ }
66
+ signal?.addEventListener("abort", onAbort, { once: true });
67
+ });
68
+ }
69
+ function isProcessAlive(pid) {
46
70
  try {
47
- handle = await fsp.open(dir, "r");
71
+ process.kill(pid, 0);
72
+ return true;
48
73
  }
49
74
  catch (err) {
50
- if (isDirectorySyncUnsupportedError(err))
51
- return;
75
+ return err?.code !== "ESRCH";
76
+ }
77
+ }
78
+ function parseStateLockOwner(ownerText) {
79
+ const parts = ownerText.trim().split(":");
80
+ if (parts.length !== 3)
81
+ return null;
82
+ const pid = Number(parts[0]);
83
+ const processStartIdentity = parts[1] || null;
84
+ const nonce = parts[2];
85
+ if (!Number.isInteger(pid) || pid <= 0 || !nonce)
86
+ return null;
87
+ return { pid, processStartIdentity, nonce };
88
+ }
89
+ async function readProcessStartIdentity(pid) {
90
+ if (process.platform !== "linux")
91
+ return null;
92
+ try {
93
+ const stat = await fsp.readFile(`/proc/${pid}/stat`, "utf8");
94
+ const closeParen = stat.lastIndexOf(")");
95
+ if (closeParen < 0)
96
+ return null;
97
+ // The remainder starts at procfs field 3; starttime is field 22.
98
+ const fields = stat
99
+ .slice(closeParen + 1)
100
+ .trim()
101
+ .split(/\s+/);
102
+ return fields[19] || null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ async function isStateLockOld(lockPath) {
109
+ try {
110
+ const stat = await fsp.stat(lockPath);
111
+ return Date.now() - stat.mtimeMs >= STATE_LOCK_ORPHAN_MS;
112
+ }
113
+ catch (err) {
114
+ if (err?.code === "ENOENT")
115
+ return true;
52
116
  throw err;
53
117
  }
118
+ }
119
+ async function hasExpiredStateLockLease(lockPath) {
120
+ let first;
54
121
  try {
55
- await handle.sync();
122
+ first = await fsp.stat(lockPath);
56
123
  }
57
124
  catch (err) {
58
- if (!isDirectorySyncUnsupportedError(err))
59
- throw err;
125
+ if (err?.code === "ENOENT")
126
+ return true;
127
+ throw err;
60
128
  }
61
- finally {
62
- if (handle)
63
- await handle.close().catch(() => { });
129
+ if (Date.now() - first.mtimeMs < STATE_LOCK_ORPHAN_MS)
130
+ return false;
131
+ await new Promise((resolve) => setTimeout(resolve, STATE_LOCK_HEARTBEAT_MS));
132
+ try {
133
+ const second = await fsp.stat(lockPath);
134
+ return second.mtimeMs === first.mtimeMs && Date.now() - second.mtimeMs >= STATE_LOCK_ORPHAN_MS;
64
135
  }
65
- }
66
- async function syncCreatedDirectoryChain(firstCreated, finalDir) {
67
- const final = path.resolve(finalDir);
68
- let current = path.resolve(firstCreated);
69
- await syncDirectory(path.dirname(current));
70
- while (current !== final) {
71
- await syncDirectory(current);
72
- const relative = path.relative(current, final);
73
- const next = relative.split(path.sep)[0];
74
- if (!next || next === "..")
75
- break;
76
- current = path.join(current, next);
136
+ catch (err) {
137
+ if (err?.code === "ENOENT")
138
+ return true;
139
+ throw err;
77
140
  }
78
141
  }
79
- export async function ensureDirectory(dir) {
80
- const created = await fsp.mkdir(dir, { recursive: true });
81
- if (created)
82
- await syncCreatedDirectoryChain(created, dir);
83
- }
84
- export function isJsonSyntaxError(err) {
85
- return err instanceof SyntaxError;
86
- }
87
- function isLinkUnsupportedError(err) {
88
- return ["ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"].includes(err?.code);
89
- }
90
- export function isAtomicExclusiveUnsupportedError(err) {
91
- return err?.code === "ENOTSUP" && err?.cause && isLinkUnsupportedError(err.cause);
92
- }
93
- function isOptionalApprovalIndexPersistenceError(err) {
94
- return (isAtomicExclusiveUnsupportedError(err) ||
95
- ["EACCES", "EDQUOT", "EIO", "ENOSPC", "EPERM", "EROFS"].includes(err?.code));
96
- }
97
- /**
98
- * Write a file atomically: stage to a sibling temp file, fsync, then rename
99
- * over the target. `rename(2)` is atomic on a single filesystem, so a reader
100
- * (or a crash) never observes a truncated/partial file — it sees either the
101
- * complete old content or the complete new content. Plain `fsp.writeFile`
102
- * truncates the target up front, leaving a corruption window on SIGKILL/OOM/
103
- * power loss. New state files are private by default; existing file modes are
104
- * preserved across replacement. The temp file is removed on any failed path.
105
- */
106
- export async function writeFileAtomic(filePath, data, options = {}) {
107
- const renameFile = options.renameFile ?? fsp.rename;
108
- const syncDir = options.syncParentDir ?? syncParentDir;
109
- const dir = path.dirname(filePath);
110
- const tmpPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString("hex")}.tmp`);
111
- let mode = 0o600;
112
- let handle;
113
- let cleanup = true;
142
+ async function reclaimOrphanedStateLock(lockPath) {
143
+ let observedLock;
114
144
  try {
115
- try {
116
- mode = (await fsp.stat(filePath)).mode & 0o777;
145
+ const stat = await fsp.lstat(lockPath);
146
+ observedLock = { dev: stat.dev, ino: stat.ino };
147
+ }
148
+ catch (err) {
149
+ if (err?.code === "ENOENT")
150
+ return true;
151
+ throw err;
152
+ }
153
+ const ownerPath = path.join(lockPath, "owner");
154
+ let observedOwner;
155
+ let stale = false;
156
+ try {
157
+ observedOwner = await fsp.readFile(ownerPath, "utf8");
158
+ const owner = parseStateLockOwner(observedOwner);
159
+ if (owner && isProcessAlive(owner.pid)) {
160
+ const processStartIdentity = await readProcessStartIdentity(owner.pid);
161
+ if (owner.processStartIdentity && processStartIdentity) {
162
+ stale = owner.processStartIdentity !== processStartIdentity;
163
+ }
164
+ else {
165
+ // A live PID without a matching process-instance identity may have
166
+ // been reused. Require a conservative expired lease and a second
167
+ // unchanged observation before reclaiming it.
168
+ stale = await hasExpiredStateLockLease(ownerPath);
169
+ }
117
170
  }
118
- catch (err) {
119
- if (err?.code !== "ENOENT")
120
- throw err;
171
+ else if (owner) {
172
+ stale = true;
173
+ }
174
+ else {
175
+ stale = await isStateLockOld(lockPath);
121
176
  }
122
- handle = await fsp.open(tmpPath, "wx", mode);
123
- await handle.writeFile(data, "utf8");
124
- await handle.chmod(mode);
125
- await handle.sync();
126
- await handle.close();
127
- handle = undefined;
128
- await renameFile(tmpPath, filePath);
129
- await syncDir(filePath);
130
- cleanup = false;
131
177
  }
132
- finally {
133
- if (handle)
134
- await handle.close().catch(() => { });
135
- if (cleanup)
136
- await fsp.rm(tmpPath, { force: true }).catch(() => { });
137
- }
138
- }
139
- export async function writeFileAtomicExclusive(filePath, data, options = {}) {
140
- const linkFile = options.linkFile ?? fsp.link;
141
- const syncDir = options.syncParentDir ?? syncParentDir;
142
- const dir = path.dirname(filePath);
143
- const tmpPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString("hex")}.tmp`);
144
- let handle;
178
+ catch (err) {
179
+ if (err?.code === "ENOENT") {
180
+ stale = await isStateLockOld(lockPath);
181
+ }
182
+ else {
183
+ throw err;
184
+ }
185
+ }
186
+ if (!stale)
187
+ return false;
188
+ // Claim reclamation inside the observed directory before removing it. Renaming
189
+ // `lockPath` directly is unsafe: another reclaimer can replace that pathname
190
+ // with a live lock after the stale observation, and a later recursive cleanup
191
+ // would then delete the new owner's lock while it is in use.
192
+ const reclaimPath = path.join(lockPath, ".reclaiming");
193
+ try {
194
+ await fsp.mkdir(reclaimPath, { mode: 0o700 });
195
+ }
196
+ catch (err) {
197
+ if (err?.code === "ENOENT")
198
+ return true;
199
+ if (err?.code === "EEXIST")
200
+ return false;
201
+ throw err;
202
+ }
145
203
  try {
146
- handle = await fsp.open(tmpPath, "wx", 0o600);
147
- await handle.writeFile(data, "utf8");
148
- await handle.chmod(0o600);
149
- await handle.sync();
150
- await handle.close();
151
- handle = undefined;
204
+ const currentLock = await fsp.lstat(lockPath);
205
+ if (currentLock.dev !== observedLock.dev || currentLock.ino !== observedLock.ino) {
206
+ return false;
207
+ }
208
+ // A replacement directory can reuse the inode; its owner must still match too.
209
+ let currentOwner;
152
210
  try {
153
- await linkFile(tmpPath, filePath);
211
+ currentOwner = await fsp.readFile(ownerPath, "utf8");
154
212
  }
155
213
  catch (err) {
156
- if (!isLinkUnsupportedError(err))
214
+ if (err?.code !== "ENOENT")
157
215
  throw err;
158
- const unsupported = new Error("Atomic exclusive file creation requires hard-link support on this filesystem");
159
- unsupported.code = "ENOTSUP";
160
- unsupported.cause = err;
161
- throw unsupported;
162
216
  }
217
+ if (currentOwner !== observedOwner)
218
+ return false;
219
+ await fsp.rm(lockPath, { recursive: true, force: true });
220
+ }
221
+ finally {
222
+ // If the path changed before the reclamation marker was claimed, leave the
223
+ // replacement lock intact and only remove our harmless marker.
224
+ await fsp.rmdir(reclaimPath).catch(() => { });
225
+ }
226
+ return true;
227
+ }
228
+ async function stillOwnsStateLock(ownerPath, owner) {
229
+ for (let attempt = 0; attempt < 2; attempt++) {
163
230
  try {
164
- await fsp.unlink(tmpPath);
165
- await syncDir(filePath);
231
+ return (await fsp.readFile(ownerPath, "utf8")) === owner;
166
232
  }
167
233
  catch (err) {
168
- await fsp.unlink(filePath).catch(() => { });
169
- await syncDir(filePath).catch(() => { });
170
- throw err;
234
+ if (err?.code === "ENOENT" || attempt === 1)
235
+ return false;
236
+ await new Promise((resolve) => setTimeout(resolve, STATE_LOCK_RETRY_MS));
171
237
  }
172
238
  }
239
+ return false;
240
+ }
241
+ async function withStateKeyLock({ env, key, signal, task, }) {
242
+ const stateDir = defaultStateDir(env);
243
+ return withFileLock({
244
+ filePath: keyToPath(stateDir, key),
245
+ signal,
246
+ task,
247
+ });
248
+ }
249
+ /**
250
+ * Serialize a transition for an arbitrary durable file. Readers that need a
251
+ * publish-or-rollback decision use the same lock as writers, rather than
252
+ * observing an atomic rename that cancellation may still need to undo.
253
+ */
254
+ export async function withFileLock({ filePath, signal, task, }) {
255
+ await ensureDirectory(path.dirname(filePath));
256
+ const lockPath = `${filePath}.lock`;
257
+ let acquired = false;
258
+ let owner;
259
+ let ownerWritten = false;
260
+ let heartbeat;
261
+ try {
262
+ while (!acquired) {
263
+ signal?.throwIfAborted();
264
+ try {
265
+ await fsp.mkdir(lockPath, { mode: 0o700 });
266
+ acquired = true;
267
+ const processStartIdentity = await readProcessStartIdentity(process.pid);
268
+ owner = `${process.pid}:${processStartIdentity ?? ""}:${randomBytes(6).toString("hex")}\n`;
269
+ await fsp.writeFile(path.join(lockPath, "owner"), owner, {
270
+ encoding: "utf8",
271
+ mode: 0o600,
272
+ });
273
+ ownerWritten = true;
274
+ const ownerPath = path.join(lockPath, "owner");
275
+ heartbeat = setInterval(() => {
276
+ void fsp.utimes(ownerPath, new Date(), new Date()).catch(() => { });
277
+ }, STATE_LOCK_HEARTBEAT_MS);
278
+ heartbeat.unref?.();
279
+ }
280
+ catch (err) {
281
+ if (acquired)
282
+ throw err;
283
+ if (err?.code !== "EEXIST")
284
+ throw err;
285
+ if (!(await reclaimOrphanedStateLock(lockPath))) {
286
+ await waitForStateLock(signal);
287
+ }
288
+ }
289
+ }
290
+ return await task();
291
+ }
173
292
  finally {
174
- if (handle)
175
- await handle.close().catch(() => { });
176
- await fsp.rm(tmpPath, { force: true }).catch(() => { });
293
+ if (heartbeat)
294
+ clearInterval(heartbeat);
295
+ if (acquired) {
296
+ const ownerPath = path.join(lockPath, "owner");
297
+ if (!ownerWritten || !owner || (await stillOwnsStateLock(ownerPath, owner))) {
298
+ // Detach an owned lock before best-effort cleanup. If a filesystem error
299
+ // prevents removing the detached directory, the canonical lock path is
300
+ // already free for a subsequent operation; leaving it in place would
301
+ // instead look like a live lock owned by this still-running process.
302
+ const cleanupPath = `${lockPath}.release-${process.pid}-${randomBytes(6).toString("hex")}`;
303
+ try {
304
+ await fsp.rename(lockPath, cleanupPath);
305
+ await fsp.rm(cleanupPath, { recursive: true, force: true }).catch(() => { });
306
+ }
307
+ catch (err) {
308
+ if (err?.code !== "ENOENT") {
309
+ await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => { });
310
+ }
311
+ }
312
+ }
313
+ }
177
314
  }
178
315
  }
179
- export async function readStateJson({ env, key }) {
316
+ function isOptionalApprovalIndexPersistenceError(err) {
317
+ return (isAtomicExclusiveUnsupportedError(err) ||
318
+ ["EACCES", "EDQUOT", "EIO", "ENOSPC", "EPERM", "EROFS"].includes(err?.code));
319
+ }
320
+ export async function readStateJson({ env, key, signal = undefined, }) {
321
+ signal?.throwIfAborted();
180
322
  const stateDir = defaultStateDir(env);
181
323
  const filePath = keyToPath(stateDir, key);
182
324
  try {
183
325
  const text = await fsp.readFile(filePath, "utf8");
184
- return JSON.parse(text);
326
+ const value = JSON.parse(text);
327
+ signal?.throwIfAborted();
328
+ return value;
185
329
  }
186
330
  catch (err) {
187
331
  if (err?.code === "ENOENT")
@@ -189,13 +333,187 @@ export async function readStateJson({ env, key }) {
189
333
  throw err;
190
334
  }
191
335
  }
192
- export async function writeStateJson({ env, key, value }) {
336
+ /**
337
+ * Read a state record only after any in-progress publish-or-rollback transition
338
+ * for the same key has settled. Callers that compose state with another durable
339
+ * resource use this to avoid observing a value that cancellation may undo.
340
+ */
341
+ export async function readStateJsonWithLock({ env, key, signal = undefined, }) {
342
+ try {
343
+ return await withStateKeyLock({ env, key, signal, task: () => readStateJson({ env, key }) });
344
+ }
345
+ catch (err) {
346
+ // A readable state directory can deliberately be mounted read-only. In that
347
+ // case no writer can begin a paired publish-or-rollback transition, so use
348
+ // the non-mutating JSON read instead of requiring creation of a lock path.
349
+ if (["EACCES", "EPERM", "EROFS"].includes(err?.code)) {
350
+ return readStateJson({ env, key, signal });
351
+ }
352
+ throw err;
353
+ }
354
+ }
355
+ async function writeStateJsonUnlocked({ env, key, value, signal = undefined, atomicWriteOptions = undefined, }) {
193
356
  const stateDir = defaultStateDir(env);
194
357
  const filePath = keyToPath(stateDir, key);
195
358
  await ensureDirectory(stateDir);
196
- await writeFileAtomic(filePath, JSON.stringify(value, null, 2) + "\n");
359
+ return writeFileAtomic(filePath, JSON.stringify(value, null, 2) + "\n", {
360
+ ...atomicWriteOptions,
361
+ signal,
362
+ });
363
+ }
364
+ export async function writeStateJson({ env, key, value, signal = undefined, atomicWriteOptions = undefined, }) {
365
+ return withStateKeyLock({
366
+ env,
367
+ key,
368
+ signal,
369
+ task: () => writeStateJsonUnlocked({ env, key, value, signal, atomicWriteOptions }),
370
+ });
371
+ }
372
+ /**
373
+ * Retire a resume capability before its first unsafe execution boundary. The
374
+ * replacement is deliberately persistent: a failed later unlink must not make
375
+ * the original token replayable.
376
+ */
377
+ export async function consumeResumeState({ env, key, expectedState, signal = undefined, }) {
378
+ return withStateKeyLock({
379
+ env,
380
+ key,
381
+ signal,
382
+ task: async () => {
383
+ const currentState = await readStateJson({ env, key });
384
+ // A resume snapshot is loaded before command/workflow setup. Re-check it
385
+ // while holding the state lock so two callers cannot both turn the same
386
+ // approval into an executable invocation.
387
+ if (stableStringify(currentState) !== stableStringify(expectedState)) {
388
+ return { consumed: false };
389
+ }
390
+ const claimId = randomBytes(16).toString("hex");
391
+ try {
392
+ const result = await writeStateJsonUnlocked({
393
+ env,
394
+ key,
395
+ value: {
396
+ type: CONSUMED_RESUME_STATE_TYPE,
397
+ consumedAt: new Date().toISOString(),
398
+ claimId,
399
+ },
400
+ signal,
401
+ });
402
+ return {
403
+ consumed: true,
404
+ claimId,
405
+ signalAbortedAfterCommit: result?.signalAbortedAfterCommit === true,
406
+ };
407
+ }
408
+ catch (err) {
409
+ if (atomicWriteWasPublished(err)) {
410
+ const latest = await readStateJson({ env, key });
411
+ if (isConsumedResumeState(latest) && latest.claimId === claimId) {
412
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
413
+ }
414
+ }
415
+ throw err;
416
+ }
417
+ },
418
+ });
419
+ }
420
+ /**
421
+ * Undo a just-published consumed marker only when it is still owned by the
422
+ * caller's pre-dispatch claim. This never overwrites a replacement state or a
423
+ * concurrent claimant's marker.
424
+ */
425
+ export async function restoreConsumedResumeState({ env, key, expectedState, claimId, }) {
426
+ return withStateKeyLock({
427
+ env,
428
+ key,
429
+ task: async () => {
430
+ const currentState = await readStateJson({ env, key });
431
+ if (!isConsumedResumeState(currentState) || currentState.claimId !== claimId) {
432
+ return false;
433
+ }
434
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
435
+ return true;
436
+ },
437
+ });
197
438
  }
198
- export async function deleteStateJson({ env, key }) {
439
+ /**
440
+ * Retire an unconsumed capability at a safe terminal boundary. The snapshot is
441
+ * first replaced by a caller-owned marker under the state lock, so a
442
+ * cancellation can restore only the state this caller claimed. A stale resume
443
+ * that merely observed an earlier snapshot can never recreate a state another
444
+ * resume has already settled.
445
+ */
446
+ export async function deleteResumeStateWithRollback({ env, key, expectedState, signal = undefined, }) {
447
+ return withStateKeyLock({
448
+ env,
449
+ key,
450
+ signal,
451
+ task: async () => {
452
+ signal?.throwIfAborted();
453
+ const currentState = await readStateJson({ env, key });
454
+ if (stableStringify(currentState) !== stableStringify(expectedState))
455
+ return false;
456
+ const claimId = randomBytes(16).toString("hex");
457
+ let claimPublished = false;
458
+ let claimMayBePublished = false;
459
+ let deleted = false;
460
+ try {
461
+ let result;
462
+ try {
463
+ result = await writeStateJsonUnlocked({
464
+ env,
465
+ key,
466
+ value: {
467
+ type: CONSUMED_RESUME_STATE_TYPE,
468
+ consumedAt: new Date().toISOString(),
469
+ claimId,
470
+ },
471
+ signal,
472
+ });
473
+ }
474
+ catch (err) {
475
+ claimMayBePublished = atomicWriteWasPublished(err);
476
+ throw err;
477
+ }
478
+ claimPublished = true;
479
+ if (result?.signalAbortedAfterCommit)
480
+ signal?.throwIfAborted();
481
+ signal?.throwIfAborted();
482
+ await deleteStateJsonUnlocked({ env, key });
483
+ deleted = true;
484
+ signal?.throwIfAborted();
485
+ return true;
486
+ }
487
+ catch (err) {
488
+ if ((signal?.aborted && claimPublished) || claimMayBePublished) {
489
+ const latest = await readStateJson({ env, key });
490
+ if ((deleted && latest === null) ||
491
+ (isConsumedResumeState(latest) && latest.claimId === claimId)) {
492
+ await writeStateJsonUnlocked({ env, key, value: expectedState });
493
+ }
494
+ }
495
+ throw err;
496
+ }
497
+ },
498
+ });
499
+ }
500
+ /**
501
+ * Check physical state presence without parsing it. Cancellation uses this to
502
+ * retain the authoritative workflow spelling even if the state file is corrupt.
503
+ */
504
+ export async function stateJsonExists({ env, key, }) {
505
+ const filePath = keyToPath(defaultStateDir(env), key);
506
+ try {
507
+ await fsp.access(filePath);
508
+ return true;
509
+ }
510
+ catch (err) {
511
+ if (err?.code === "ENOENT")
512
+ return false;
513
+ throw err;
514
+ }
515
+ }
516
+ async function deleteStateJsonUnlocked({ env, key }) {
199
517
  const stateDir = defaultStateDir(env);
200
518
  const filePath = keyToPath(stateDir, key);
201
519
  try {
@@ -207,6 +525,62 @@ export async function deleteStateJson({ env, key }) {
207
525
  throw err;
208
526
  }
209
527
  }
528
+ export async function deleteStateJson({ env, key, signal = undefined, }) {
529
+ return withStateKeyLock({
530
+ env,
531
+ key,
532
+ signal,
533
+ task: () => deleteStateJsonUnlocked({ env, key }),
534
+ });
535
+ }
536
+ /**
537
+ * After an effect has started, its consumed marker already prevents replay.
538
+ * Give terminal cleanup a small, bounded opportunity to remove that marker,
539
+ * but never let a live state writer turn cancellation into an unbounded wait.
540
+ */
541
+ export async function deleteStateJsonWithBoundedResumeCleanup({ env, key, }) {
542
+ const controller = new AbortController();
543
+ const timeout = setTimeout(() => controller.abort(), TERMINAL_RESUME_CLEANUP_TIMEOUT_MS);
544
+ try {
545
+ await deleteStateJson({ env, key, signal: controller.signal });
546
+ }
547
+ finally {
548
+ clearTimeout(timeout);
549
+ }
550
+ }
551
+ /**
552
+ * Retire a resume capability only while it is still a live, unclaimed state.
553
+ * A consumed marker belongs to a resume that has already started its atomic
554
+ * predecessor-to-successor handoff, so cancellation must not delete it and
555
+ * report success while that successor remains executable.
556
+ */
557
+ export async function deleteUnconsumedResumeState({ env, key, signal = undefined, }) {
558
+ return withStateKeyLock({
559
+ env,
560
+ key,
561
+ signal,
562
+ task: async () => {
563
+ let currentState;
564
+ try {
565
+ currentState = await readStateJson({ env, key });
566
+ }
567
+ catch (err) {
568
+ // A corrupt state is not resumable, so explicit cancellation may still
569
+ // remove it. This keeps the legacy workflow-alias recovery behavior.
570
+ if (!isJsonSyntaxError(err))
571
+ throw err;
572
+ await deleteStateJsonUnlocked({ env, key });
573
+ return "deleted";
574
+ }
575
+ if (currentState === null)
576
+ return "missing";
577
+ if (isConsumedResumeState(currentState))
578
+ return "claimed";
579
+ await deleteStateJsonUnlocked({ env, key });
580
+ return "deleted";
581
+ },
582
+ });
583
+ }
210
584
  function sanitizeApprovalId(approvalId) {
211
585
  return approvalId.replace(/[^a-f0-9]/g, "");
212
586
  }
@@ -324,14 +698,54 @@ export async function cleanupApprovalIndexByStateKey({ env, stateKey, }) {
324
698
  }
325
699
  }
326
700
  }
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;
701
+ export async function diffAndStore({ env, key, value, signal = undefined, atomicWriteOptions = undefined, afterStore = undefined, }) {
702
+ return withStateKeyLock({
703
+ env,
704
+ key,
705
+ signal,
706
+ task: async () => {
707
+ const filePath = keyToPath(defaultStateDir(env), key);
708
+ let beforeExists = true;
709
+ try {
710
+ await fsp.access(filePath);
711
+ }
712
+ catch (err) {
713
+ if (err?.code !== "ENOENT")
714
+ throw err;
715
+ beforeExists = false;
716
+ }
717
+ const before = await readStateJson({ env, key }).catch((err) => {
718
+ if (isJsonSyntaxError(err))
719
+ return null;
720
+ throw err;
721
+ });
722
+ const changed = stableStringify(before) !== stableStringify(value);
723
+ const snapshot = { before, after: value, changed };
724
+ let stored = false;
725
+ try {
726
+ signal?.throwIfAborted();
727
+ await writeStateJsonUnlocked({ env, key, value, signal, atomicWriteOptions });
728
+ stored = true;
729
+ signal?.throwIfAborted();
730
+ await afterStore?.(snapshot);
731
+ }
732
+ catch (err) {
733
+ // A caller can publish another resource while this state lock is held.
734
+ // If that coordinated publication fails, restore the state snapshot before
735
+ // releasing the lock so readers never reuse a cancelled result.
736
+ const stateWasPublished = stored || atomicWriteWasPublished(err);
737
+ if (stateWasPublished && (signal?.aborted || afterStore || atomicWriteWasPublished(err))) {
738
+ if (!beforeExists) {
739
+ await deleteStateJsonUnlocked({ env, key });
740
+ }
741
+ else {
742
+ await writeStateJsonUnlocked({ env, key, value: before });
743
+ }
744
+ }
745
+ throw err;
746
+ }
747
+ return snapshot;
748
+ },
332
749
  });
333
- const changed = stableStringify(before) !== stableStringify(value);
334
- await writeStateJson({ env, key, value });
335
- return { before, after: value, changed };
336
750
  }
337
751
  //# sourceMappingURL=store.js.map