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