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