@openclaw/lobster 2026.9.3 → 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 -0
  322. package/package.json +10 -7
@@ -7,13 +7,15 @@ import os from "node:os";
7
7
  import { createDefaultRegistry } from "../src/commands/registry.js";
8
8
  import { runWorkflowFile } from "../src/workflows/file.js";
9
9
  import { decodeResumeToken } from "../src/resume.js";
10
- import { readStateJson } from "../src/state/store.js";
10
+ import { keyToPath, readStateJsonWithLock as readStateJson } from "../src/state/store.js";
11
11
  function streamOf(items) {
12
12
  return (async function* () {
13
13
  for (const item of items)
14
14
  yield item;
15
15
  })();
16
16
  }
17
+ const APPEND_EFFECT_AND_WAIT = "node -e \"require('fs').appendFileSync(process.env.LOBSTER_TEST_EFFECT_PATH, 'run\\n'); setInterval(() => {}, 1000)\"";
18
+ const COUNT_ATTEMPTS_AND_RETRY = "node -e \"const fs=require('fs'); const file=process.env.LOBSTER_TEST_ATTEMPTS_PATH; const attempt=fs.existsSync(file) ? Number(fs.readFileSync(file, 'utf8')) : 0; fs.writeFileSync(file, String(attempt + 1)); if (attempt === 0) setInterval(() => {}, 1000); else process.stdout.write('retried');\"";
17
19
  test("workflow file runs with approval and resume", async () => {
18
20
  const workflow = {
19
21
  name: "sample",
@@ -78,6 +80,53 @@ test("workflow file runs with approval and resume", async () => {
78
80
  const resumeStateFiles = stateFiles.filter((name) => name.startsWith("workflow_resume_"));
79
81
  assert.deepEqual(resumeStateFiles, []);
80
82
  });
83
+ test("sequential workflow approvals reclaim superseded resume markers", async () => {
84
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-sequential-approval-"));
85
+ try {
86
+ const stateDir = path.join(tmpDir, "state");
87
+ const filePath = path.join(tmpDir, "workflow.lobster");
88
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
89
+ await fsp.writeFile(filePath, JSON.stringify({
90
+ steps: [
91
+ { id: "first", approval: "First?" },
92
+ { id: "second", approval: "Second?" },
93
+ { id: "finish", command: "node -e \"process.stdout.write('{}')\"" },
94
+ ],
95
+ }), "utf8");
96
+ const ctx = {
97
+ stdin: process.stdin,
98
+ stdout: process.stdout,
99
+ stderr: process.stderr,
100
+ env,
101
+ mode: "tool",
102
+ };
103
+ const first = await runWorkflowFile({ filePath, ctx });
104
+ assert.equal(first.status, "needs_approval");
105
+ const firstPayload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
106
+ assert.equal(firstPayload.kind, "workflow-file");
107
+ const second = await runWorkflowFile({
108
+ filePath,
109
+ ctx,
110
+ resume: firstPayload,
111
+ approved: true,
112
+ });
113
+ assert.equal(second.status, "needs_approval");
114
+ const secondPayload = decodeResumeToken(second.requiresApproval?.resumeToken ?? "");
115
+ assert.equal(secondPayload.kind, "workflow-file");
116
+ const completed = await runWorkflowFile({
117
+ filePath,
118
+ ctx,
119
+ resume: secondPayload,
120
+ approved: true,
121
+ });
122
+ assert.equal(completed.status, "ok");
123
+ const stateFiles = await fsp.readdir(stateDir);
124
+ assert.deepEqual(stateFiles.filter((name) => name.startsWith("workflow_resume_")), []);
125
+ }
126
+ finally {
127
+ await fsp.rm(tmpDir, { recursive: true, force: true });
128
+ }
129
+ });
81
130
  test("workflow resume cancellation cleans up resume state", async () => {
82
131
  const workflow = {
83
132
  steps: [
@@ -131,6 +180,490 @@ test("workflow resume cancellation cleans up resume state", async () => {
131
180
  const resumeStateFiles = files.filter((name) => name.startsWith("workflow_resume_"));
132
181
  assert.deepEqual(resumeStateFiles, []);
133
182
  });
183
+ test("direct workflow resume consumes its capability after cancellation starts an effect", async () => {
184
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-direct-cancel-"));
185
+ const stateDir = path.join(tmpDir, "state");
186
+ const effectPath = path.join(tmpDir, "effects.log");
187
+ const filePath = path.join(tmpDir, "workflow.lobster");
188
+ await fsp.writeFile(filePath, JSON.stringify({
189
+ steps: [
190
+ {
191
+ id: "approve",
192
+ command: "node -e \"process.stdout.write(JSON.stringify({requiresApproval:{prompt:'Proceed?',items:[{id:1}]}}))\"",
193
+ approval: "required",
194
+ },
195
+ {
196
+ id: "effect",
197
+ run: APPEND_EFFECT_AND_WAIT,
198
+ env: { LOBSTER_TEST_EFFECT_PATH: effectPath },
199
+ condition: "$approve.approved",
200
+ },
201
+ ],
202
+ }), "utf8");
203
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
204
+ const first = await runWorkflowFile({
205
+ filePath,
206
+ ctx: {
207
+ stdin: process.stdin,
208
+ stdout: process.stdout,
209
+ stderr: process.stderr,
210
+ env,
211
+ mode: "tool",
212
+ },
213
+ });
214
+ assert.equal(first.status, "needs_approval");
215
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
216
+ assert.equal(payload.kind, "workflow-file");
217
+ assert.ok(payload.stateKey);
218
+ const controller = new AbortController();
219
+ const resumed = runWorkflowFile({
220
+ filePath,
221
+ ctx: {
222
+ stdin: process.stdin,
223
+ stdout: process.stdout,
224
+ stderr: process.stderr,
225
+ env,
226
+ mode: "tool",
227
+ signal: controller.signal,
228
+ },
229
+ resume: payload,
230
+ approved: true,
231
+ });
232
+ try {
233
+ for (let attempt = 0; attempt < 1000; attempt++) {
234
+ try {
235
+ await fsp.access(effectPath);
236
+ break;
237
+ }
238
+ catch {
239
+ if (attempt === 999)
240
+ throw new Error("workflow effect did not start");
241
+ await new Promise((resolve) => setTimeout(resolve, 10));
242
+ }
243
+ }
244
+ }
245
+ finally {
246
+ // A startup failure must still cancel and reap the hanging fixture.
247
+ controller.abort(new Error("cancel after dispatch"));
248
+ await assert.rejects(() => resumed, /cancel after dispatch/);
249
+ }
250
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
251
+ const stateFiles = await fsp.readdir(stateDir);
252
+ assert.equal(stateFiles.some((file) => file.startsWith("approval_")), false);
253
+ await assert.rejects(() => runWorkflowFile({
254
+ filePath,
255
+ ctx: {
256
+ stdin: process.stdin,
257
+ stdout: process.stdout,
258
+ stderr: process.stderr,
259
+ env,
260
+ mode: "tool",
261
+ },
262
+ resume: payload,
263
+ approved: true,
264
+ }), /Workflow resume state not found/);
265
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
266
+ });
267
+ test("workflow template setup failure preserves an unstarted approval capability", async () => {
268
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-template-resume-"));
269
+ try {
270
+ const stateDir = path.join(tmpDir, "state");
271
+ const filePath = path.join(tmpDir, "workflow.lobster");
272
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
273
+ const broken = {
274
+ steps: [
275
+ { id: "approve", approval: "Continue?" },
276
+ {
277
+ id: "effect",
278
+ run: "node -e \"process.stdout.write('should not run')\"",
279
+ stdin: "$missing.stdout",
280
+ },
281
+ ],
282
+ };
283
+ await fsp.writeFile(filePath, JSON.stringify(broken), "utf8");
284
+ const first = await runWorkflowFile({
285
+ filePath,
286
+ ctx: {
287
+ stdin: process.stdin,
288
+ stdout: process.stdout,
289
+ stderr: process.stderr,
290
+ env,
291
+ mode: "tool",
292
+ },
293
+ });
294
+ assert.equal(first.status, "needs_approval");
295
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
296
+ assert.equal(payload.kind, "workflow-file");
297
+ assert.ok(payload.stateKey);
298
+ await assert.rejects(() => runWorkflowFile({
299
+ filePath,
300
+ ctx: {
301
+ stdin: process.stdin,
302
+ stdout: process.stdout,
303
+ stderr: process.stderr,
304
+ env,
305
+ mode: "tool",
306
+ },
307
+ resume: payload,
308
+ approved: true,
309
+ }), /Unknown step reference: missing\.stdout/);
310
+ assert.notEqual(await readStateJson({ env, key: payload.stateKey }), null);
311
+ await fsp.writeFile(filePath, JSON.stringify({
312
+ steps: [
313
+ { id: "approve", approval: "Continue?" },
314
+ { id: "effect", run: "node -e \"process.stdout.write('ok')\"" },
315
+ ],
316
+ }), "utf8");
317
+ const retried = await runWorkflowFile({
318
+ filePath,
319
+ ctx: {
320
+ stdin: process.stdin,
321
+ stdout: process.stdout,
322
+ stderr: process.stderr,
323
+ env,
324
+ mode: "tool",
325
+ },
326
+ resume: payload,
327
+ approved: true,
328
+ });
329
+ assert.equal(retried.status, "ok");
330
+ assert.deepEqual(retried.output, ["ok"]);
331
+ }
332
+ finally {
333
+ await fsp.rm(tmpDir, { recursive: true, force: true });
334
+ }
335
+ });
336
+ test("workflow resume consumes its capability after a step timeout starts an effect", async () => {
337
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-step-timeout-"));
338
+ const stateDir = path.join(tmpDir, "state");
339
+ const effectPath = path.join(tmpDir, "effects.log");
340
+ const filePath = path.join(tmpDir, "workflow.lobster");
341
+ await fsp.writeFile(filePath, JSON.stringify({
342
+ steps: [
343
+ {
344
+ id: "approve",
345
+ command: "node -e \"process.stdout.write(JSON.stringify({requiresApproval:{prompt:'Proceed?',items:[{id:1}]}}))\"",
346
+ approval: "required",
347
+ },
348
+ {
349
+ id: "effect",
350
+ run: APPEND_EFFECT_AND_WAIT,
351
+ env: { LOBSTER_TEST_EFFECT_PATH: effectPath },
352
+ condition: "$approve.approved",
353
+ timeout_ms: 1500,
354
+ },
355
+ ],
356
+ }), "utf8");
357
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
358
+ const first = await runWorkflowFile({
359
+ filePath,
360
+ ctx: {
361
+ stdin: process.stdin,
362
+ stdout: process.stdout,
363
+ stderr: process.stderr,
364
+ env,
365
+ mode: "tool",
366
+ },
367
+ });
368
+ assert.equal(first.status, "needs_approval");
369
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
370
+ assert.equal(payload.kind, "workflow-file");
371
+ assert.ok(payload.stateKey);
372
+ await assert.rejects(() => runWorkflowFile({
373
+ filePath,
374
+ ctx: {
375
+ stdin: process.stdin,
376
+ stdout: process.stdout,
377
+ stderr: process.stderr,
378
+ env,
379
+ mode: "tool",
380
+ },
381
+ resume: payload,
382
+ approved: true,
383
+ }), /timed out|timeout|abort|cancel/i);
384
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
385
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
386
+ await assert.rejects(() => runWorkflowFile({
387
+ filePath,
388
+ ctx: {
389
+ stdin: process.stdin,
390
+ stdout: process.stdout,
391
+ stderr: process.stderr,
392
+ env,
393
+ mode: "tool",
394
+ },
395
+ resume: payload,
396
+ approved: true,
397
+ }), /Workflow resume state not found/);
398
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
399
+ });
400
+ test("workflow resume applies on_error after a timed-out effect", async () => {
401
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-timeout-on-error-"));
402
+ const stateDir = path.join(tmpDir, "state");
403
+ const effectPath = path.join(tmpDir, "effects.log");
404
+ const filePath = path.join(tmpDir, "workflow.lobster");
405
+ await fsp.writeFile(filePath, JSON.stringify({
406
+ steps: [
407
+ {
408
+ id: "approve",
409
+ command: "node -e \"process.stdout.write(JSON.stringify({requiresApproval:{prompt:'Proceed?',items:[{id:1}]}}))\"",
410
+ approval: "required",
411
+ },
412
+ {
413
+ id: "effect",
414
+ run: APPEND_EFFECT_AND_WAIT,
415
+ env: { LOBSTER_TEST_EFFECT_PATH: effectPath },
416
+ condition: "$approve.approved",
417
+ timeout_ms: 1500,
418
+ on_error: "continue",
419
+ },
420
+ { id: "after", run: "echo continued" },
421
+ ],
422
+ }), "utf8");
423
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
424
+ const first = await runWorkflowFile({
425
+ filePath,
426
+ ctx: {
427
+ stdin: process.stdin,
428
+ stdout: process.stdout,
429
+ stderr: process.stderr,
430
+ env,
431
+ mode: "tool",
432
+ },
433
+ });
434
+ assert.equal(first.status, "needs_approval");
435
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
436
+ assert.equal(payload.kind, "workflow-file");
437
+ assert.ok(payload.stateKey);
438
+ const resumed = await runWorkflowFile({
439
+ filePath,
440
+ ctx: {
441
+ stdin: process.stdin,
442
+ stdout: process.stdout,
443
+ stderr: process.stderr,
444
+ env,
445
+ mode: "tool",
446
+ },
447
+ resume: payload,
448
+ approved: true,
449
+ });
450
+ assert.equal(resumed.status, "ok");
451
+ assert.deepEqual(resumed.output, ["continued\n"]);
452
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
453
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
454
+ });
455
+ test("workflow resume retries a timed-out effect before consuming its capability", async () => {
456
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-timeout-retry-"));
457
+ const stateDir = path.join(tmpDir, "state");
458
+ const attemptsPath = path.join(tmpDir, "attempts");
459
+ const filePath = path.join(tmpDir, "workflow.lobster");
460
+ await fsp.writeFile(filePath, JSON.stringify({
461
+ steps: [
462
+ {
463
+ id: "approve",
464
+ command: "node -e \"process.stdout.write(JSON.stringify({requiresApproval:{prompt:'Proceed?',items:[{id:1}]}}))\"",
465
+ approval: "required",
466
+ },
467
+ {
468
+ id: "effect",
469
+ run: COUNT_ATTEMPTS_AND_RETRY,
470
+ env: { LOBSTER_TEST_ATTEMPTS_PATH: attemptsPath },
471
+ condition: "$approve.approved",
472
+ timeout_ms: 1500,
473
+ retry: { max: 2, delay_ms: 10 },
474
+ },
475
+ ],
476
+ }), "utf8");
477
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
478
+ const first = await runWorkflowFile({
479
+ filePath,
480
+ ctx: {
481
+ stdin: process.stdin,
482
+ stdout: process.stdout,
483
+ stderr: process.stderr,
484
+ env,
485
+ mode: "tool",
486
+ },
487
+ });
488
+ assert.equal(first.status, "needs_approval");
489
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
490
+ assert.equal(payload.kind, "workflow-file");
491
+ assert.ok(payload.stateKey);
492
+ const resumed = await runWorkflowFile({
493
+ filePath,
494
+ ctx: {
495
+ stdin: process.stdin,
496
+ stdout: process.stdout,
497
+ stderr: process.stderr,
498
+ env,
499
+ mode: "tool",
500
+ },
501
+ resume: payload,
502
+ approved: true,
503
+ });
504
+ assert.equal(resumed.status, "ok");
505
+ assert.deepEqual(resumed.output, ["retried"]);
506
+ assert.equal(await fsp.readFile(attemptsPath, "utf8"), "2");
507
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
508
+ });
509
+ test("workflow resume retries a claim blocked by a state lock before dispatch", async () => {
510
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-claim-retry-"));
511
+ try {
512
+ const stateDir = path.join(tmpDir, "state");
513
+ const filePath = path.join(tmpDir, "workflow.lobster");
514
+ await fsp.writeFile(filePath, JSON.stringify({
515
+ steps: [
516
+ { id: "approve", approval: "Proceed?" },
517
+ {
518
+ id: "effect",
519
+ run: "printf ran",
520
+ condition: "$approve.approved",
521
+ timeout_ms: 1000,
522
+ retry: { max: 2, delay_ms: 250 },
523
+ },
524
+ ],
525
+ }), "utf8");
526
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
527
+ const first = await runWorkflowFile({
528
+ filePath,
529
+ ctx: {
530
+ stdin: process.stdin,
531
+ stdout: process.stdout,
532
+ stderr: process.stderr,
533
+ env,
534
+ mode: "tool",
535
+ },
536
+ });
537
+ assert.equal(first.status, "needs_approval");
538
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
539
+ assert.equal(payload.kind, "workflow-file");
540
+ assert.ok(payload.stateKey);
541
+ const lockPath = `${keyToPath(stateDir, payload.stateKey)}.lock`;
542
+ const originalRename = fsp.rename;
543
+ const originalRm = fsp.rm;
544
+ let releasedInitialReadLocks = 0;
545
+ let lockReplaced;
546
+ const replacedInitialReadLocks = new Promise((resolve) => {
547
+ lockReplaced = resolve;
548
+ });
549
+ Object.defineProperty(fsp, "rename", {
550
+ configurable: true,
551
+ writable: true,
552
+ async value(oldPath, newPath) {
553
+ const result = await originalRename(oldPath, newPath);
554
+ if (String(oldPath) === lockPath && ++releasedInitialReadLocks === 2) {
555
+ await fsp.mkdir(lockPath);
556
+ await fsp.writeFile(path.join(lockPath, "owner"), `${process.pid}::live-writer\n`, "utf8");
557
+ lockReplaced();
558
+ }
559
+ return result;
560
+ },
561
+ });
562
+ try {
563
+ const resumedRun = runWorkflowFile({
564
+ filePath,
565
+ ctx: {
566
+ stdin: process.stdin,
567
+ stdout: process.stdout,
568
+ stderr: process.stderr,
569
+ env,
570
+ mode: "tool",
571
+ },
572
+ resume: payload,
573
+ approved: true,
574
+ });
575
+ await replacedInitialReadLocks;
576
+ const release = setTimeout(() => void originalRm(lockPath, { recursive: true, force: true }), 1200);
577
+ const resumed = await resumedRun;
578
+ clearTimeout(release);
579
+ assert.equal(resumed.status, "ok");
580
+ assert.deepEqual(resumed.output, ["ran"]);
581
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
582
+ }
583
+ finally {
584
+ Object.defineProperty(fsp, "rename", {
585
+ configurable: true,
586
+ writable: true,
587
+ value: originalRename,
588
+ });
589
+ await fsp.rm(lockPath, { recursive: true, force: true });
590
+ }
591
+ }
592
+ finally {
593
+ await fsp.rm(tmpDir, { recursive: true, force: true });
594
+ }
595
+ });
596
+ test("workflow resume consumes its capability after a parallel timeout starts an effect", async () => {
597
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-parallel-timeout-"));
598
+ const stateDir = path.join(tmpDir, "state");
599
+ const effectPath = path.join(tmpDir, "effects.log");
600
+ const filePath = path.join(tmpDir, "workflow.lobster");
601
+ await fsp.writeFile(filePath, JSON.stringify({
602
+ steps: [
603
+ {
604
+ id: "approve",
605
+ command: "node -e \"process.stdout.write(JSON.stringify({requiresApproval:{prompt:'Proceed?',items:[{id:1}]}}))\"",
606
+ approval: "required",
607
+ },
608
+ {
609
+ id: "effect",
610
+ condition: "$approve.approved",
611
+ parallel: {
612
+ timeout_ms: 5000,
613
+ branches: [
614
+ {
615
+ id: "side-effect",
616
+ run: APPEND_EFFECT_AND_WAIT,
617
+ env: { LOBSTER_TEST_EFFECT_PATH: effectPath },
618
+ },
619
+ ],
620
+ },
621
+ },
622
+ ],
623
+ }), "utf8");
624
+ const env = { ...process.env, LOBSTER_STATE_DIR: stateDir };
625
+ const first = await runWorkflowFile({
626
+ filePath,
627
+ ctx: {
628
+ stdin: process.stdin,
629
+ stdout: process.stdout,
630
+ stderr: process.stderr,
631
+ env,
632
+ mode: "tool",
633
+ },
634
+ });
635
+ assert.equal(first.status, "needs_approval");
636
+ const payload = decodeResumeToken(first.requiresApproval?.resumeToken ?? "");
637
+ assert.equal(payload.kind, "workflow-file");
638
+ assert.ok(payload.stateKey);
639
+ await assert.rejects(() => runWorkflowFile({
640
+ filePath,
641
+ ctx: {
642
+ stdin: process.stdin,
643
+ stdout: process.stdout,
644
+ stderr: process.stderr,
645
+ env,
646
+ mode: "tool",
647
+ },
648
+ resume: payload,
649
+ approved: true,
650
+ }), /timed out|timeout|abort|cancel/i);
651
+ assert.equal(await readStateJson({ env, key: payload.stateKey }), null);
652
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
653
+ await assert.rejects(() => runWorkflowFile({
654
+ filePath,
655
+ ctx: {
656
+ stdin: process.stdin,
657
+ stdout: process.stdout,
658
+ stderr: process.stderr,
659
+ env,
660
+ mode: "tool",
661
+ },
662
+ resume: payload,
663
+ approved: true,
664
+ }), /Workflow resume state not found/);
665
+ assert.equal((await fsp.readFile(effectPath, "utf8")).trim().split(/\r?\n/).length, 1);
666
+ });
134
667
  test("workflow resume accepts workflow-resume_ state key aliases and cleans up state", async () => {
135
668
  const workflow = {
136
669
  steps: [
@@ -253,6 +786,58 @@ test("workflow file input steps pause and resume with structured responses", asy
253
786
  assert.equal(resumed.status, "ok");
254
787
  assert.deepEqual(resumed.output, [{ decision: "approve", subject: "hello" }]);
255
788
  });
789
+ test("workflow input resume contains prototype-named step ids", async () => {
790
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-workflow-prototype-step-"));
791
+ const stateDir = path.join(tmpDir, "state");
792
+ const filePath = path.join(tmpDir, "workflow.lobster");
793
+ const objectPrototype = Object.prototype;
794
+ try {
795
+ await fsp.writeFile(filePath, JSON.stringify({
796
+ steps: [
797
+ {
798
+ id: "__proto__",
799
+ input: {
800
+ prompt: "Continue?",
801
+ responseSchema: {
802
+ type: "object",
803
+ properties: { decision: { type: "string" } },
804
+ required: ["decision"],
805
+ },
806
+ },
807
+ },
808
+ {
809
+ id: "finish",
810
+ run: 'node -e "process.stdout.write(JSON.stringify({decision:process.env.DECISION}))"',
811
+ env: { DECISION: "$__proto__.response.decision" },
812
+ },
813
+ ],
814
+ }), "utf8");
815
+ const ctx = {
816
+ stdin: process.stdin,
817
+ stdout: process.stdout,
818
+ stderr: process.stderr,
819
+ env: { ...process.env, LOBSTER_STATE_DIR: stateDir },
820
+ mode: "tool",
821
+ };
822
+ const first = await runWorkflowFile({ filePath, ctx });
823
+ const payload = decodeResumeToken(first.requiresInput?.resumeToken ?? "");
824
+ assert.equal(payload.kind, "workflow-file");
825
+ const resumed = await runWorkflowFile({
826
+ filePath,
827
+ ctx,
828
+ resume: payload,
829
+ response: { decision: "approve" },
830
+ });
831
+ assert.equal(Object.hasOwn(objectPrototype, "response"), false);
832
+ assert.equal(Object.hasOwn(objectPrototype, "subject"), false);
833
+ assert.deepEqual(resumed.output, [{ decision: "approve" }]);
834
+ }
835
+ finally {
836
+ delete objectPrototype.response;
837
+ delete objectPrototype.subject;
838
+ await fsp.rm(tmpDir, { recursive: true, force: true });
839
+ }
840
+ });
256
841
  test("workflow pipeline command input pauses and resumes the same pipeline step", async () => {
257
842
  const schema = JSON.stringify({
258
843
  type: "object",
@@ -335,6 +920,7 @@ test("workflow pipeline requestInput resume invariant bypasses on_error", async
335
920
  let sideEffects = 0;
336
921
  const choose = {
337
922
  name: "choose",
923
+ meta: { resumeSafeBeforeInput: true },
338
924
  async run({ ctx }) {
339
925
  calls += 1;
340
926
  if (calls > 1)
@@ -1098,6 +1684,66 @@ test("workflow conditions reject standalone bare identifiers", async () => {
1098
1684
  },
1099
1685
  }), /Unsupported condition: approve/);
1100
1686
  });
1687
+ test("parallel wait:any fails promptly when a registered loser ignores cancellation", async () => {
1688
+ const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "lobster-parallel-unsettled-"));
1689
+ const filePath = path.join(tmpDir, "workflow.lobster");
1690
+ const registry = {
1691
+ get(name) {
1692
+ if (name === "winner") {
1693
+ return {
1694
+ name,
1695
+ help: () => "winner",
1696
+ async run() {
1697
+ return { output: [{ winner: true }] };
1698
+ },
1699
+ };
1700
+ }
1701
+ if (name === "never") {
1702
+ return {
1703
+ name,
1704
+ help: () => "never",
1705
+ async run() {
1706
+ await new Promise(() => { });
1707
+ },
1708
+ };
1709
+ }
1710
+ return undefined;
1711
+ },
1712
+ };
1713
+ try {
1714
+ await fsp.writeFile(filePath, JSON.stringify({
1715
+ steps: [
1716
+ {
1717
+ id: "parallel",
1718
+ parallel: {
1719
+ wait: "any",
1720
+ branches: [
1721
+ { id: "winner", pipeline: "winner" },
1722
+ { id: "never", pipeline: "never" },
1723
+ ],
1724
+ },
1725
+ },
1726
+ ],
1727
+ }), "utf8");
1728
+ await assert.rejects(Promise.race([
1729
+ runWorkflowFile({
1730
+ filePath,
1731
+ ctx: {
1732
+ stdin: process.stdin,
1733
+ stdout: process.stdout,
1734
+ stderr: process.stderr,
1735
+ env: { ...process.env },
1736
+ mode: "tool",
1737
+ registry,
1738
+ },
1739
+ }),
1740
+ new Promise((_resolve, reject) => setTimeout(() => reject(new Error("workflow did not finish")), 1_000)),
1741
+ ]), /Parallel branches did not settle after cancellation/);
1742
+ }
1743
+ finally {
1744
+ await fsp.rm(tmpDir, { recursive: true, force: true });
1745
+ }
1746
+ });
1101
1747
  test("workflow conditions reject unknown step refs even under negation", async () => {
1102
1748
  const workflow = {
1103
1749
  steps: [