@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.1
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/README.md +398 -45
- package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
- package/THIRD_PARTY_NOTICES.md +273 -12
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/bin/ztc-capture-darwin-arm64 +0 -0
- package/bin/ztc-computer-darwin-arm64 +0 -0
- package/bin/ztc-llm-darwin-arm64 +0 -0
- package/bin/ztc-tts-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +1206 -209
- package/dist/agent/agent.d.ts +74 -8
- package/dist/agent/agent.js +1073 -184
- package/dist/agent/backends/account_rotation.d.ts +4 -0
- package/dist/agent/backends/account_rotation.js +8 -2
- package/dist/agent/backends/anthropic.d.ts +13 -0
- package/dist/agent/backends/anthropic.js +169 -28
- package/dist/agent/backends/claude_cli.js +18 -8
- package/dist/agent/backends/codex_app_server.d.ts +1 -0
- package/dist/agent/backends/codex_app_server.js +55 -3
- package/dist/agent/backends/codex_auth.d.ts +1 -0
- package/dist/agent/backends/codex_auth.js +67 -13
- package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
- package/dist/agent/backends/codex_buffered_recovery.js +293 -0
- package/dist/agent/backends/gemini.js +3 -1
- package/dist/agent/backends/http_error.d.ts +26 -0
- package/dist/agent/backends/http_error.js +221 -0
- package/dist/agent/backends/index.d.ts +9 -2
- package/dist/agent/backends/index.js +22 -2
- package/dist/agent/backends/openai.d.ts +0 -6
- package/dist/agent/backends/openai.js +5 -2
- package/dist/agent/backends/openai_compatible.d.ts +5 -4
- package/dist/agent/backends/openai_compatible.js +26 -19
- package/dist/agent/backends/openai_responses.d.ts +8 -3
- package/dist/agent/backends/openai_responses.js +77 -33
- package/dist/agent/backends/provider_ids.d.ts +1 -0
- package/dist/agent/backends/provider_ids.js +4 -0
- package/dist/agent/backends/request_metrics.js +12 -2
- package/dist/agent/backends/types.d.ts +109 -6
- package/dist/agent/backends/types.js +5 -1
- package/dist/agent/backends/zrouter_stream.d.ts +6 -1
- package/dist/agent/backends/zrouter_stream.js +160 -42
- package/dist/agent/background_registry.d.ts +2 -0
- package/dist/agent/background_registry.js +1 -0
- package/dist/agent/claim_observation.d.ts +6 -0
- package/dist/agent/claim_observation.js +54 -0
- package/dist/agent/commands/accounts.js +46 -177
- package/dist/agent/commands/attention.d.ts +2 -0
- package/dist/agent/commands/attention.js +67 -0
- package/dist/agent/commands/auth.js +18 -2
- package/dist/agent/commands/board.d.ts +2 -0
- package/dist/agent/commands/board.js +169 -0
- package/dist/agent/commands/clipboard.js +50 -1
- package/dist/agent/commands/compact.js +21 -13
- package/dist/agent/commands/config.js +4 -1
- package/dist/agent/commands/context.js +6 -1
- package/dist/agent/commands/desktop.d.ts +2 -0
- package/dist/agent/commands/desktop.js +29 -0
- package/dist/agent/commands/effort.js +14 -6
- package/dist/agent/commands/environment.d.ts +3 -0
- package/dist/agent/commands/environment.js +109 -0
- package/dist/agent/commands/evidence.d.ts +2 -0
- package/dist/agent/commands/evidence.js +9 -0
- package/dist/agent/commands/fork.js +4 -3
- package/dist/agent/commands/goal.d.ts +33 -1
- package/dist/agent/commands/goal.js +174 -14
- package/dist/agent/commands/goal_exit.d.ts +36 -0
- package/dist/agent/commands/goal_exit.js +120 -0
- package/dist/agent/commands/help.js +3 -1
- package/dist/agent/commands/history.js +3 -1
- package/dist/agent/commands/inbox.d.ts +2 -0
- package/dist/agent/commands/inbox.js +148 -0
- package/dist/agent/commands/index.js +16 -1
- package/dist/agent/commands/input_mode.js +8 -6
- package/dist/agent/commands/job.d.ts +2 -0
- package/dist/agent/commands/job.js +93 -0
- package/dist/agent/commands/keybindings.js +4 -1
- package/dist/agent/commands/label.d.ts +2 -0
- package/dist/agent/commands/label.js +77 -0
- package/dist/agent/commands/layout.d.ts +2 -0
- package/dist/agent/commands/layout.js +20 -0
- package/dist/agent/commands/mirror.js +16 -1
- package/dist/agent/commands/model.js +12 -2
- package/dist/agent/commands/permissions.d.ts +3 -1
- package/dist/agent/commands/permissions.js +79 -89
- package/dist/agent/commands/plan.js +5 -15
- package/dist/agent/commands/profile.js +21 -2
- package/dist/agent/commands/provider_selection.js +20 -1
- package/dist/agent/commands/rename.js +1 -1
- package/dist/agent/commands/resources.d.ts +2 -0
- package/dist/agent/commands/resources.js +67 -0
- package/dist/agent/commands/rewind.d.ts +3 -0
- package/dist/agent/commands/rewind.js +16 -0
- package/dist/agent/commands/runbooks.d.ts +2 -0
- package/dist/agent/commands/runbooks.js +73 -0
- package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
- package/dist/agent/commands/runbooks_lazy.js +8 -0
- package/dist/agent/commands/sessions.d.ts +1 -1
- package/dist/agent/commands/sessions.js +33 -5
- package/dist/agent/commands/settings.js +185 -39
- package/dist/agent/commands/setup.d.ts +2 -0
- package/dist/agent/commands/setup.js +78 -0
- package/dist/agent/commands/share.d.ts +2 -0
- package/dist/agent/commands/share.js +64 -0
- package/dist/agent/commands/status.js +14 -2
- package/dist/agent/commands/types.d.ts +99 -6
- package/dist/agent/commands/update.js +8 -0
- package/dist/agent/commands/usage.d.ts +2 -0
- package/dist/agent/commands/usage.js +8 -0
- package/dist/agent/factory.d.ts +11 -0
- package/dist/agent/factory.js +113 -88
- package/dist/agent/loop_policy.d.ts +6 -3
- package/dist/agent/loop_policy.js +12 -3
- package/dist/agent/measurement_guidance.d.ts +2 -0
- package/dist/agent/measurement_guidance.js +5 -0
- package/dist/agent/request_encoder.d.ts +8 -1
- package/dist/agent/request_encoder.js +95 -23
- package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
- package/dist/agent/runtime/benchmark_comparison.js +94 -0
- package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
- package/dist/agent/runtime/benchmark_contract.js +90 -0
- package/dist/agent/runtime/checked_commands.d.ts +32 -0
- package/dist/agent/runtime/checked_commands.js +119 -0
- package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
- package/dist/agent/runtime/command_execution_policy.js +91 -0
- package/dist/agent/runtime/command_jobs.d.ts +36 -18
- package/dist/agent/runtime/command_jobs.js +151 -13
- package/dist/agent/runtime/command_panes.d.ts +59 -0
- package/dist/agent/runtime/command_panes.js +398 -0
- package/dist/agent/runtime/command_wait.d.ts +19 -0
- package/dist/agent/runtime/command_wait.js +167 -0
- package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
- package/dist/agent/runtime/cpu_benchmark.js +126 -0
- package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
- package/dist/agent/runtime/delegated_evidence.js +80 -0
- package/dist/agent/runtime/evidence.d.ts +127 -0
- package/dist/agent/runtime/evidence.js +468 -0
- package/dist/agent/runtime/execution_context.d.ts +7 -0
- package/dist/agent/runtime/execution_context.js +39 -3
- package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
- package/dist/agent/runtime/json_verifier_recipe.js +66 -0
- package/dist/agent/runtime/measurements.d.ts +110 -0
- package/dist/agent/runtime/measurements.js +317 -0
- package/dist/agent/runtime/policy.d.ts +4 -0
- package/dist/agent/runtime/policy.js +31 -20
- package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
- package/dist/agent/runtime/provider_native_evidence.js +86 -0
- package/dist/agent/runtime/recovery.d.ts +2 -0
- package/dist/agent/runtime/recovery.js +45 -2
- package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
- package/dist/agent/runtime/remote_broker_transport.js +84 -0
- package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
- package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
- package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
- package/dist/agent/runtime/remote_journal_replay.js +233 -0
- package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
- package/dist/agent/runtime/remote_mutation_journal.js +208 -0
- package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
- package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
- package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
- package/dist/agent/runtime/remote_resource_errors.js +9 -0
- package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
- package/dist/agent/runtime/remote_resource_script.js +1009 -0
- package/dist/agent/runtime/remote_resources.d.ts +29 -0
- package/dist/agent/runtime/remote_resources.js +160 -0
- package/dist/agent/runtime/source_verification.d.ts +12 -0
- package/dist/agent/runtime/source_verification.js +210 -0
- package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
- package/dist/agent/runtime/source_verification_contract.js +203 -0
- package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
- package/dist/agent/runtime/tool_checkpoints.js +26 -0
- package/dist/agent/runtime/tool_effects.d.ts +5 -0
- package/dist/agent/runtime/tool_progress.d.ts +12 -0
- package/dist/agent/runtime/tool_progress.js +76 -0
- package/dist/agent/runtime/tool_runtime.d.ts +16 -1
- package/dist/agent/runtime/tool_runtime.js +204 -10
- package/dist/agent/runtime/tracing.d.ts +1 -1
- package/dist/agent/runtime/verifier_capture.d.ts +9 -0
- package/dist/agent/runtime/verifier_capture.js +100 -0
- package/dist/agent/runtime/verifier_contract.d.ts +24 -0
- package/dist/agent/runtime/verifier_contract.js +41 -0
- package/dist/agent/subagent.d.ts +20 -10
- package/dist/agent/subagent.js +266 -29
- package/dist/agent/tool_response_directory.d.ts +24 -0
- package/dist/agent/tool_response_directory.js +61 -0
- package/dist/agent/tool_response_source.d.ts +8 -0
- package/dist/agent/tool_response_source.js +49 -0
- package/dist/agent/tool_response_store.d.ts +75 -4
- package/dist/agent/tool_response_store.js +583 -64
- package/dist/agent/tools/ask_user.d.ts +3 -1
- package/dist/agent/tools/ask_user.js +30 -11
- package/dist/agent/tools/background.js +1 -0
- package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
- package/dist/agent/tools/benchmark_cpu.js +102 -0
- package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
- package/dist/agent/tools/compare_benchmarks.js +53 -0
- package/dist/agent/tools/deferred.js +1 -0
- package/dist/agent/tools/delegate.js +18 -2
- package/dist/agent/tools/helper_requirements.d.ts +7 -0
- package/dist/agent/tools/helper_requirements.js +19 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/measurement_report.d.ts +22 -0
- package/dist/agent/tools/measurement_report.js +190 -0
- package/dist/agent/tools/pane.d.ts +0 -7
- package/dist/agent/tools/pane.js +5 -4
- package/dist/agent/tools/project_context.d.ts +3 -0
- package/dist/agent/tools/project_context.js +45 -0
- package/dist/agent/tools/recall_tool_response.d.ts +3 -5
- package/dist/agent/tools/recall_tool_response.js +19 -29
- package/dist/agent/tools/registry.d.ts +1 -0
- package/dist/agent/tools/registry.js +23 -0
- package/dist/agent/tools/related_sessions.d.ts +2 -0
- package/dist/agent/tools/related_sessions.js +156 -0
- package/dist/agent/tools/routine.js +13 -3
- package/dist/agent/tools/session_evidence.d.ts +3 -0
- package/dist/agent/tools/session_evidence.js +72 -0
- package/dist/agent/tools/shell.d.ts +4 -3
- package/dist/agent/tools/shell.js +174 -47
- package/dist/agent/tools/tool_search.js +3 -2
- package/dist/agent/tools/types.d.ts +28 -0
- package/dist/agent/tools/verify_artifact.d.ts +2 -0
- package/dist/agent/tools/verify_artifact.js +60 -0
- package/dist/agent/tools/workflow.js +1 -0
- package/dist/app/activity.d.ts +6 -1
- package/dist/app/activity.js +81 -12
- package/dist/app/agent_runner.d.ts +6 -1
- package/dist/app/agent_runner.js +34 -19
- package/dist/app/command_context.js +106 -10
- package/dist/app/controller.d.ts +15 -4
- package/dist/app/controller.js +29 -3
- package/dist/app/escape_stop.d.ts +11 -0
- package/dist/app/escape_stop.js +15 -0
- package/dist/app/liveness.d.ts +6 -4
- package/dist/app/liveness.js +13 -3
- package/dist/app/model_catalog_cache.d.ts +5 -0
- package/dist/app/model_catalog_cache.js +138 -0
- package/dist/app/ordered_transcript.js +43 -4
- package/dist/app/prompt_turn.d.ts +14 -0
- package/dist/app/prompt_turn.js +53 -0
- package/dist/app/recap.d.ts +3 -0
- package/dist/app/recap.js +9 -3
- package/dist/app/restart.d.ts +3 -2
- package/dist/app/restart.js +22 -7
- package/dist/app/rewind.d.ts +3 -3
- package/dist/app/rewind.js +3 -3
- package/dist/app/selection_completion.d.ts +17 -2
- package/dist/app/selection_completion.js +84 -9
- package/dist/app/session_manager.d.ts +38 -6
- package/dist/app/session_manager.js +263 -60
- package/dist/app/session_messages.d.ts +3 -0
- package/dist/app/session_messages.js +60 -0
- package/dist/app/task_scope_router.d.ts +20 -0
- package/dist/app/task_scope_router.js +173 -0
- package/dist/app/transcript_window.d.ts +2 -0
- package/dist/app/transcript_window.js +4 -0
- package/dist/app/workspace_admission_details.d.ts +3 -0
- package/dist/app/workspace_admission_details.js +26 -0
- package/dist/app/workspace_layout_controller.d.ts +11 -0
- package/dist/app/workspace_layout_controller.js +88 -0
- package/dist/app/workspace_monitor.d.ts +1 -0
- package/dist/app/workspace_monitor.js +216 -0
- package/dist/app/workspace_presets.d.ts +29 -0
- package/dist/app/workspace_presets.js +192 -0
- package/dist/attention/queue.d.ts +37 -0
- package/dist/attention/queue.js +183 -0
- package/dist/bench/rf_shared.d.ts +2 -0
- package/dist/board/binding.d.ts +42 -0
- package/dist/board/binding.js +38 -0
- package/dist/board/client.d.ts +42 -0
- package/dist/board/client.js +60 -0
- package/dist/cli.js +1 -1
- package/dist/cli_args.js +19 -17
- package/dist/cli_main.js +110 -13
- package/dist/cli_ratatui.d.ts +183 -16
- package/dist/cli_ratatui.js +3989 -1255
- package/dist/components/ActivityLine.d.ts +3 -2
- package/dist/components/ActivityLine.js +2 -2
- package/dist/components/InputArea.d.ts +19 -1
- package/dist/components/InputArea.js +264 -190
- package/dist/components/MessageList.d.ts +2 -0
- package/dist/components/MessageList.js +3 -1
- package/dist/components/SingleMessage.d.ts +2 -0
- package/dist/components/SingleMessage.js +4 -2
- package/dist/components/StatusBar.d.ts +4 -2
- package/dist/components/StatusBar.js +3 -1
- package/dist/conductor_branch.js +3 -0
- package/dist/config/api_retry.d.ts +6 -1
- package/dist/config/api_retry.js +19 -1
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.js +9 -2
- package/dist/config/message_display.d.ts +1 -0
- package/dist/config/message_display.js +17 -2
- package/dist/config/permissions.d.ts +21 -0
- package/dist/config/permissions.js +89 -0
- package/dist/config/resolved.d.ts +2 -2
- package/dist/config/types.d.ts +32 -5
- package/dist/config.d.ts +38 -4
- package/dist/config.js +290 -42
- package/dist/context/project_intelligence.d.ts +44 -0
- package/dist/context/project_intelligence.js +246 -0
- package/dist/desktop/cli_launch.d.ts +8 -0
- package/dist/desktop/cli_launch.js +6 -0
- package/dist/desktop/cli_shim.d.ts +8 -0
- package/dist/desktop/cli_shim.js +120 -0
- package/dist/desktop/runtime.d.ts +16 -0
- package/dist/desktop/runtime.js +161 -0
- package/dist/desktop/worker_launch.d.ts +7 -0
- package/dist/desktop/worker_launch.js +7 -0
- package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
- package/dist/diagnostics/cargo_mutants_selection.js +48 -0
- package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
- package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
- package/dist/diagnostics/session_evidence_package.d.ts +40 -0
- package/dist/diagnostics/session_evidence_package.js +234 -0
- package/dist/diagnostics/session_export.d.ts +93 -0
- package/dist/diagnostics/session_export.js +634 -0
- package/dist/emulation/tool_adapters.js +48 -18
- package/dist/emulation/ztc_addendum.d.ts +1 -0
- package/dist/emulation/ztc_addendum.js +13 -12
- package/dist/environment/service_runner.d.ts +1 -0
- package/dist/environment/service_runner.js +68 -0
- package/dist/environment/store.d.ts +120 -0
- package/dist/environment/store.js +387 -0
- package/dist/events.d.ts +40 -1
- package/dist/events.js +10 -17
- package/dist/goal_contract.d.ts +25 -0
- package/dist/goal_contract.js +44 -0
- package/dist/headless.d.ts +31 -0
- package/dist/headless.js +578 -70
- package/dist/headless_cli.js +8 -0
- package/dist/headless_cli_args.d.ts +1 -0
- package/dist/headless_cli_args.js +1 -0
- package/dist/intake/cli.d.ts +1 -0
- package/dist/intake/cli.js +33 -0
- package/dist/intake/format.d.ts +2 -0
- package/dist/intake/format.js +14 -0
- package/dist/intake/notifications.d.ts +16 -0
- package/dist/intake/notifications.js +132 -0
- package/dist/intake/store.d.ts +56 -0
- package/dist/intake/store.js +238 -0
- package/dist/library.d.ts +8 -0
- package/dist/library.js +19 -10
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +20 -0
- package/dist/modes/builtin/plan.yaml +2 -2
- package/dist/modes/handlers.js +1 -1
- package/dist/onboarding/first_run.d.ts +3 -0
- package/dist/onboarding/first_run.js +33 -25
- package/dist/onboarding/setup.d.ts +32 -0
- package/dist/onboarding/setup.js +133 -0
- package/dist/onboarding/setup_agent_health.d.ts +17 -0
- package/dist/onboarding/setup_agent_health.js +79 -0
- package/dist/onboarding/setup_environment.d.ts +66 -0
- package/dist/onboarding/setup_environment.js +219 -0
- package/dist/onboarding/setup_workspace.d.ts +17 -0
- package/dist/onboarding/setup_workspace.js +129 -0
- package/dist/optimize/adapters/index.d.ts +4 -1
- package/dist/optimize/adapters/index.js +7 -0
- package/dist/optimize/adapters/kernelbench.js +14 -5
- package/dist/optimize/adapters/popcorn.js +3 -2
- package/dist/optimize/adapters/profile_benchmark.js +13 -7
- package/dist/optimize/adapters/rfbench.js +25 -14
- package/dist/optimize/adapters/types.d.ts +3 -0
- package/dist/optimize/adapters/zergpod.js +5 -2
- package/dist/optimize/baseline.js +10 -21
- package/dist/optimize/branch.js +9 -12
- package/dist/optimize/combine.js +2 -3
- package/dist/optimize/executor.js +34 -48
- package/dist/optimize/measurements.d.ts +55 -0
- package/dist/optimize/measurements.js +173 -0
- package/dist/optimize/results.d.ts +1 -0
- package/dist/optimize/results.js +11 -33
- package/dist/optimize/tge.js +2 -1
- package/dist/panes/initial_input.d.ts +19 -0
- package/dist/panes/initial_input.js +206 -0
- package/dist/panes/labels.d.ts +16 -0
- package/dist/panes/labels.js +42 -0
- package/dist/panes/model_catalog.d.ts +3 -3
- package/dist/panes/model_catalog.js +28 -4
- package/dist/panes/pane_registry.js +6 -2
- package/dist/panes/types.d.ts +3 -0
- package/dist/panes/ztc_launch.js +19 -0
- package/dist/plugin/optimization.d.ts +2 -0
- package/dist/plugin/optimization.js +1 -0
- package/dist/plugin/runtime.d.ts +2 -0
- package/dist/plugin/simulation.d.ts +6 -1
- package/dist/plugin/simulation_contract.d.ts +836 -1
- package/dist/plugin/simulation_documents.d.ts +22 -0
- package/dist/plugin/simulation_documents.js +1 -0
- package/dist/plugin/simulation_interventions.d.ts +103 -0
- package/dist/plugin/simulation_interventions.js +1 -0
- package/dist/plugin/simulation_living.d.ts +1 -0
- package/dist/plugin/simulation_living.js +3 -0
- package/dist/plugin/simulation_meetings.d.ts +121 -0
- package/dist/plugin/simulation_meetings.js +1 -0
- package/dist/plugin/simulation_story_review.d.ts +111 -0
- package/dist/plugin/simulation_story_review.js +1 -0
- package/dist/plugin/simulation_storyteller.d.ts +91 -0
- package/dist/plugin/simulation_storyteller.js +1 -0
- package/dist/plugin/simulation_structured.d.ts +2 -0
- package/dist/plugin/simulation_structured.js +2 -0
- package/dist/plugin/simulation_toolkit.d.ts +21 -0
- package/dist/plugin/simulation_toolkit.js +24 -0
- package/dist/prompts/tool_delegate_task.j2 +1 -1
- package/dist/remote-broker/Dockerfile +10 -0
- package/dist/remote-broker/archive.py +1227 -0
- package/dist/remote-broker/cutover.py +761 -0
- package/dist/remote-broker/requirements.txt +5 -0
- package/dist/remote-broker/resources.json +6 -0
- package/dist/remote-broker/resources.write-staging.example.json +18 -0
- package/dist/remote-broker/retained.py +686 -0
- package/dist/remote-broker/retained_cleanup.py +550 -0
- package/dist/remote-broker/server.py +648 -0
- package/dist/remote-broker/worker.py +1007 -0
- package/dist/router_auth.d.ts +31 -0
- package/dist/router_auth.js +155 -9
- package/dist/runbooks/catalog.d.ts +3 -0
- package/dist/runbooks/catalog.js +26 -0
- package/dist/runbooks/library.d.ts +11 -0
- package/dist/runbooks/library.js +95 -0
- package/dist/runbooks/package.d.ts +43 -0
- package/dist/runbooks/package.js +102 -0
- package/dist/session.d.ts +65 -13
- package/dist/session.js +548 -43
- package/dist/session_handoff.d.ts +25 -0
- package/dist/session_handoff.js +322 -0
- package/dist/session_handoff_contract.d.ts +89 -0
- package/dist/session_handoff_contract.js +179 -0
- package/dist/session_media.d.ts +41 -0
- package/dist/session_media.js +357 -0
- package/dist/session_ownership.d.ts +16 -0
- package/dist/session_ownership.js +221 -0
- package/dist/session_public.d.ts +10 -2
- package/dist/session_public.js +6 -1
- package/dist/simulation/dynamic_history.d.ts +42 -0
- package/dist/simulation/dynamic_history.js +173 -0
- package/dist/simulation/engines/swarm/pane_manager.js +7 -6
- package/dist/simulation/inference.d.ts +3 -1
- package/dist/simulation/inference.js +19 -1
- package/dist/simulation/intervention_generation.d.ts +27 -0
- package/dist/simulation/intervention_generation.js +246 -0
- package/dist/simulation/intervention_material.d.ts +4 -0
- package/dist/simulation/intervention_material.js +81 -0
- package/dist/simulation/living/context.d.ts +3130 -0
- package/dist/simulation/living/context.js +944 -0
- package/dist/simulation/living/definer.d.ts +12 -0
- package/dist/simulation/living/definer.js +223 -0
- package/dist/simulation/living/definition.d.ts +257 -0
- package/dist/simulation/living/definition.js +341 -0
- package/dist/simulation/living/frame.d.ts +259 -0
- package/dist/simulation/living/frame.js +223 -0
- package/dist/simulation/living/identifiers.d.ts +2 -0
- package/dist/simulation/living/identifiers.js +12 -0
- package/dist/simulation/living/index.d.ts +15 -0
- package/dist/simulation/living/index.js +15 -0
- package/dist/simulation/living/inference.d.ts +219 -0
- package/dist/simulation/living/inference.js +576 -0
- package/dist/simulation/living/mechanics.d.ts +54 -0
- package/dist/simulation/living/mechanics.js +588 -0
- package/dist/simulation/living/model-input.d.ts +25 -0
- package/dist/simulation/living/model-input.js +215 -0
- package/dist/simulation/living/open-world.d.ts +257 -0
- package/dist/simulation/living/open-world.js +102 -0
- package/dist/simulation/living/projection.d.ts +7 -0
- package/dist/simulation/living/projection.js +31 -0
- package/dist/simulation/living/recall.d.ts +17 -0
- package/dist/simulation/living/recall.js +25 -0
- package/dist/simulation/living/reduce.d.ts +15 -0
- package/dist/simulation/living/reduce.js +1853 -0
- package/dist/simulation/living/replay.d.ts +6 -0
- package/dist/simulation/living/replay.js +22 -0
- package/dist/simulation/living/runtime.d.ts +140 -0
- package/dist/simulation/living/runtime.js +3020 -0
- package/dist/simulation/living/shared-kinds.d.ts +24 -0
- package/dist/simulation/living/shared-kinds.js +96 -0
- package/dist/simulation/living/types.d.ts +2785 -0
- package/dist/simulation/living/types.js +669 -0
- package/dist/simulation/llm.d.ts +3 -1
- package/dist/simulation/llm.js +90 -30
- package/dist/simulation/memory/embedding.d.ts +1 -1
- package/dist/simulation/memory/embedding.js +9 -3
- package/dist/simulation/memory/filesystem.d.ts +2 -0
- package/dist/simulation/memory/filesystem.js +5 -1
- package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
- package/dist/simulation/memory/filesystem_file_tree.js +41 -10
- package/dist/simulation/memory/filesystem_invariants.js +1 -2
- package/dist/simulation/memory/filesystem_types.d.ts +1 -0
- package/dist/simulation/memory/memory.d.ts +2 -0
- package/dist/simulation/memory/memory.js +5 -1
- package/dist/simulation/orchestration/intent.js +0 -2
- package/dist/simulation/orchestration/lifecycle.js +0 -2
- package/dist/simulation/plugin_host.d.ts +25 -1
- package/dist/simulation/plugin_host.js +334 -17
- package/dist/simulation/session_documents.d.ts +20 -0
- package/dist/simulation/session_documents.js +62 -0
- package/dist/simulation/session_input_validation.d.ts +43 -0
- package/dist/simulation/session_input_validation.js +364 -0
- package/dist/simulation/session_inputs.d.ts +32 -0
- package/dist/simulation/session_inputs.js +154 -0
- package/dist/simulation/telemetry.d.ts +11 -14
- package/dist/simulation/telemetry.js +177 -54
- package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
- package/dist/simulation/toolkit/bounded_turns.js +80 -0
- package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
- package/dist/simulation/toolkit/dynamic_agents.js +218 -0
- package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
- package/dist/simulation/toolkit/dynamic_digest.js +8 -0
- package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
- package/dist/simulation/toolkit/dynamic_documents.js +71 -0
- package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
- package/dist/simulation/toolkit/dynamic_driver.js +269 -0
- package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
- package/dist/simulation/toolkit/dynamic_game.js +805 -0
- package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
- package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
- package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
- package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
- package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
- package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
- package/dist/simulation/toolkit/dynamic_progress.js +214 -0
- package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
- package/dist/simulation/toolkit/dynamic_projection.js +219 -0
- package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
- package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
- package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
- package/dist/simulation/toolkit/dynamic_state.js +1967 -0
- package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
- package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
- package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
- package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
- package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_world.js +377 -0
- package/dist/simulation/toolkit/structured_output.d.ts +5 -0
- package/dist/simulation/toolkit/structured_output.js +321 -0
- package/dist/simulation/types/world.d.ts +2 -0
- package/dist/simulation/world/artifact_store.js +1 -1
- package/dist/simulation/world/memory_manager.d.ts +5 -0
- package/dist/simulation/world/memory_manager.js +17 -2
- package/dist/simulation/world/state.d.ts +3 -2
- package/dist/simulation/world/state.js +15 -5
- package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
- package/dist/terminal_attach.d.ts +1 -0
- package/dist/terminal_attach.js +113 -0
- package/dist/tool_progress.d.ts +3 -0
- package/dist/tool_progress.js +27 -0
- package/dist/types.d.ts +145 -10
- package/dist/types.js +3 -0
- package/dist/ui/core/input.d.ts +3 -0
- package/dist/ui/core/input_segments.d.ts +18 -1
- package/dist/ui/core/input_segments.js +131 -47
- package/dist/ui/core/input_state.d.ts +12 -0
- package/dist/ui/core/layout_yoga.d.ts +1 -1
- package/dist/ui/core/turn_metadata.d.ts +5 -0
- package/dist/ui/core/turn_metadata.js +22 -0
- package/dist/ui/ink/render.d.ts +2 -1
- package/dist/ui/ink/render.js +6 -5
- package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
- package/dist/ui/ratatui/headless_pane_manager.js +293 -104
- package/dist/ui/ratatui/index.d.ts +1 -0
- package/dist/ui/ratatui/input.d.ts +4 -0
- package/dist/ui/ratatui/input.js +15 -0
- package/dist/ui/ratatui/ipc_path.d.ts +2 -0
- package/dist/ui/ratatui/ipc_path.js +29 -1
- package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
- package/dist/ui/ratatui/mux_persistence.js +139 -1
- package/dist/ui/ratatui/pane_relay.d.ts +1 -1
- package/dist/ui/ratatui/pane_relay.js +55 -27
- package/dist/ui/ratatui/process.d.ts +17 -1
- package/dist/ui/ratatui/process.js +80 -7
- package/dist/ui/ratatui/protocol.d.ts +236 -1
- package/dist/ui/ratatui/protocol.js +90 -0
- package/dist/ui/scrollback.d.ts +2 -1
- package/dist/ui/scrollback.js +9 -4
- package/dist/ui/stream_typewriter.d.ts +8 -3
- package/dist/ui/stream_typewriter.js +30 -1
- package/dist/ui/streaming_scrollback.d.ts +1 -0
- package/dist/ui/streaming_scrollback.js +1 -1
- package/dist/ui/transcript_render_cache.js +10 -0
- package/dist/ui/views/activity_line.d.ts +5 -3
- package/dist/ui/views/activity_line.js +65 -7
- package/dist/ui/views/app.d.ts +6 -3
- package/dist/ui/views/app.js +17 -6
- package/dist/ui/views/completion_menu.js +10 -5
- package/dist/ui/views/input_area.d.ts +7 -3
- package/dist/ui/views/input_area.js +15 -9
- package/dist/ui/views/message_list.d.ts +4 -1
- package/dist/ui/views/message_list.js +89 -19
- package/dist/ui/views/queue_panel.d.ts +3 -1
- package/dist/ui/views/queue_panel.js +9 -2
- package/dist/ui/views/status_bar.d.ts +4 -2
- package/dist/ui/views/status_bar.js +28 -13
- package/dist/ui/views/theme.d.ts +2 -0
- package/dist/ui/views/theme.js +2 -0
- package/dist/ui/widgets/color_panel.js +18 -0
- package/dist/ui/widgets/form.js +8 -0
- package/dist/ui/widgets/log_view.js +7 -0
- package/dist/ui/widgets/navigator_panel.js +8 -0
- package/dist/ui/widgets/prompt_viewer.js +2 -0
- package/dist/ui/widgets/questionnaire.d.ts +2 -0
- package/dist/ui/widgets/questionnaire.js +79 -16
- package/dist/ui/widgets/resume_panel.d.ts +2 -0
- package/dist/ui/widgets/resume_panel.js +26 -15
- package/dist/ui/widgets/rewind_panel.d.ts +2 -2
- package/dist/ui/widgets/rewind_panel.js +4 -2
- package/dist/ui/widgets/select.js +16 -27
- package/dist/ui/widgets/settings_panel.js +16 -0
- package/dist/ui/widgets/status_line_panel.d.ts +1 -0
- package/dist/ui/widgets/status_line_panel.js +30 -4
- package/dist/ui/widgets/text_input.d.ts +2 -0
- package/dist/ui/widgets/text_input.js +15 -1
- package/dist/ui/widgets/types.d.ts +2 -0
- package/dist/usage_projection.d.ts +24 -0
- package/dist/usage_projection.js +130 -0
- package/dist/usage_snapshot.d.ts +36 -0
- package/dist/usage_snapshot.js +131 -0
- package/dist/utils/assistant_message.d.ts +2 -0
- package/dist/utils/assistant_message.js +5 -0
- package/dist/utils/atomic_write.d.ts +6 -2
- package/dist/utils/atomic_write.js +29 -4
- package/dist/utils/checked_shell.d.ts +25 -0
- package/dist/utils/checked_shell.js +107 -0
- package/dist/utils/compaction.d.ts +4 -1
- package/dist/utils/compaction.js +31 -7
- package/dist/utils/context.d.ts +4 -4
- package/dist/utils/context.js +14 -12
- package/dist/utils/deepseek.d.ts +2 -2
- package/dist/utils/deepseek.js +13 -5
- package/dist/utils/ephemeral.js +4 -0
- package/dist/utils/ephemeral_tools.d.ts +18 -0
- package/dist/utils/ephemeral_tools.js +97 -0
- package/dist/utils/jev_client.d.ts +4 -0
- package/dist/utils/jev_client.js +15 -0
- package/dist/utils/model_cache.d.ts +15 -0
- package/dist/utils/model_cache.js +143 -0
- package/dist/utils/models.d.ts +4 -2
- package/dist/utils/models.js +12 -16
- package/dist/utils/reasoning.d.ts +8 -1
- package/dist/utils/reasoning.js +31 -11
- package/dist/utils/router_catalog.d.ts +49 -0
- package/dist/utils/router_catalog.js +271 -0
- package/dist/utils/sandbox.d.ts +9 -0
- package/dist/utils/sandbox.js +118 -15
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.js +98 -34
- package/dist/utils/subprocess_env.d.ts +3 -0
- package/dist/utils/subprocess_env.js +27 -1
- package/dist/web/accounts_mirror.d.ts +3 -1
- package/dist/web/accounts_mirror.js +13 -9
- package/dist/web/child_publication.d.ts +17 -0
- package/dist/web/child_publication.js +45 -0
- package/dist/web/mirror_authenticator.d.ts +31 -11
- package/dist/web/mirror_authenticator.js +67 -17
- package/dist/web/mirror_client.d.ts +31 -4
- package/dist/web/mirror_client.js +189 -23
- package/dist/web/mirror_stream.d.ts +84 -22
- package/dist/web/mirror_stream.js +676 -95
- package/dist/web/observer_query_redaction.d.ts +6 -0
- package/dist/web/observer_query_redaction.js +58 -0
- package/dist/web/pane_registry.d.ts +2 -0
- package/dist/web/pane_registry.js +86 -22
- package/dist/web/tool_image.d.ts +6 -0
- package/dist/web/tool_image.js +17 -0
- package/dist/web/view_plugin.d.ts +14 -0
- package/dist/web/view_plugin.js +23 -1
- package/dist/worker/executor.js +31 -12
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/toolsets.js +3 -1
- package/dist/worker/types.d.ts +12 -1
- package/dist/workflows/registry.js +1 -0
- package/dist/workflows/registry_handle.d.ts +2 -0
- package/dist/workflows/runtime.d.ts +2 -0
- package/dist/workflows/runtime.js +1 -0
- package/dist/workspace_agent.d.ts +7 -0
- package/dist/workspace_agent.js +36 -0
- package/dist/workspace_collision.d.ts +76 -0
- package/dist/workspace_collision.js +688 -0
- package/dist/workspace_layout.d.ts +18 -0
- package/dist/workspace_layout.js +121 -0
- package/dist/workspace_runtime.d.ts +44 -0
- package/dist/workspace_runtime.js +82 -0
- package/dist/workspace_session_context.d.ts +62 -0
- package/dist/workspace_session_context.js +160 -0
- package/dist/zlm-embed.mjs +32518 -9615
- package/dist/zlm.js +1 -0
- package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
- package/node_modules/@zerg/model-contracts/README.md +90 -0
- package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
- package/node_modules/@zerg/model-contracts/index.js +223 -0
- package/node_modules/@zerg/model-contracts/package.json +8 -0
- package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
- package/package.json +45 -24
- package/packages/model-contracts/LICENSE.md +375 -0
- package/packages/model-contracts/README.md +90 -0
- package/packages/model-contracts/index.d.ts +20 -0
- package/packages/model-contracts/index.js +223 -0
- package/packages/model-contracts/package.json +8 -0
- package/remote-broker/Dockerfile +10 -0
- package/remote-broker/README.md +308 -0
- package/remote-broker/archive.py +1227 -0
- package/remote-broker/cutover.py +761 -0
- package/remote-broker/requirements.txt +5 -0
- package/remote-broker/resources.example.json +6 -0
- package/remote-broker/resources.write-staging.example.json +18 -0
- package/remote-broker/retained.py +686 -0
- package/remote-broker/retained_cleanup.py +550 -0
- package/remote-broker/server.py +648 -0
- package/scripts/build-remote-broker.mjs +13 -0
- package/scripts/ensure-local-packages.mjs +6 -0
- package/scripts/node-pty-helpers.mjs +103 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
/** Local, observational coordination. These records are advisory, never leases or locks. */
|
|
2
|
+
import { execFile, spawn } from 'node:child_process';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { constants as fsConstants, closeSync, fstatSync, linkSync, lstatSync, openSync, readFileSync, renameSync, unlinkSync } from 'node:fs';
|
|
5
|
+
import { chmod, copyFile, link, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, symlink, unlink, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { homedir, tmpdir } from 'node:os';
|
|
7
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { deriveWorkspaceSessionWork, parseWorkspaceSessionWork, formatWorkspaceSessionWork } from './workspace_session_context.js';
|
|
10
|
+
export class WorkspaceIsolationError extends Error {
|
|
11
|
+
retainedTarget;
|
|
12
|
+
constructor(message, retainedTarget = null) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.retainedTarget = retainedTarget;
|
|
15
|
+
this.name = 'WorkspaceIsolationError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const MAX_RECORDS = 128;
|
|
19
|
+
const MAX_OWNER_SCAN = 4096;
|
|
20
|
+
const MAX_PEERS = 32;
|
|
21
|
+
const MAX_RECORD_BYTES = 64 * 1024;
|
|
22
|
+
const WORK_CONTEXT_WARNING = 'Uncertain local work context: an owner summary was unavailable or invalid; presence evidence remains advisory.';
|
|
23
|
+
const MAX_PATHS = 64;
|
|
24
|
+
const STALE_MS = 90_000;
|
|
25
|
+
const GIT_TIMEOUT_MS = 2_000;
|
|
26
|
+
const PATCH_LIMIT = 16 * 1024 * 1024;
|
|
27
|
+
const FILE_LIMIT = 64 * 1024 * 1024;
|
|
28
|
+
const COPY_PATH_LIMIT = 512;
|
|
29
|
+
const execFileAsync = promisify(execFile);
|
|
30
|
+
function ownersDirectory() {
|
|
31
|
+
// All profiles share local coordination; selecting a profile cannot hide a sibling.
|
|
32
|
+
return join(process.env.ZTC_CONFIG_DIR || join(homedir(), '.ztc'), 'workspace-owners');
|
|
33
|
+
}
|
|
34
|
+
function gitEnvironment() {
|
|
35
|
+
const env = { ...process.env };
|
|
36
|
+
for (const key of ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE'])
|
|
37
|
+
delete env[key];
|
|
38
|
+
return { ...env, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0' };
|
|
39
|
+
}
|
|
40
|
+
async function git(cwd, args, limit = 1024 * 1024, timeout = GIT_TIMEOUT_MS) {
|
|
41
|
+
const result = await execFileAsync('git', ['-C', cwd, ...args], {
|
|
42
|
+
env: gitEnvironment(), encoding: 'buffer', timeout, maxBuffer: limit,
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
});
|
|
45
|
+
return result.stdout;
|
|
46
|
+
}
|
|
47
|
+
async function canonical(path) {
|
|
48
|
+
try {
|
|
49
|
+
return await realpath(path);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
const absolute = resolve(path);
|
|
53
|
+
const parent = dirname(absolute);
|
|
54
|
+
return parent === absolute ? absolute : join(await canonical(parent), relative(parent, absolute));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function gitIdentity(cwd) {
|
|
58
|
+
try {
|
|
59
|
+
const paths = (await git(cwd, ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'])).toString('utf8').trim().split('\n');
|
|
60
|
+
if (paths.length !== 2)
|
|
61
|
+
throw new Error('Invalid Git identity');
|
|
62
|
+
const [repositoryRoot, gitCommonDir] = await Promise.all(paths.map(canonical));
|
|
63
|
+
const [branch, head] = await Promise.all([
|
|
64
|
+
git(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD']).then(value => value.toString('utf8').trim()).catch(() => null),
|
|
65
|
+
git(cwd, ['rev-parse', '--verify', 'HEAD']).then(value => value.toString('utf8').trim()).catch(() => null),
|
|
66
|
+
]);
|
|
67
|
+
return { repositoryRoot, gitCommonDir, branch, head };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { repositoryRoot: null, gitCommonDir: null, branch: null, head: null };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function liveness(pid) {
|
|
74
|
+
try {
|
|
75
|
+
process.kill(pid, 0);
|
|
76
|
+
return 'live';
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return error.code === 'ESRCH' ? 'dead' : 'unknown';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isKind(value) {
|
|
83
|
+
return value === 'interactive' || value === 'headless' || value === 'colony_child';
|
|
84
|
+
}
|
|
85
|
+
function isActivity(value) {
|
|
86
|
+
return value === 'idle' || value === 'running' || value === 'paused' || value === 'unknown';
|
|
87
|
+
}
|
|
88
|
+
function parsePresence(value, source) {
|
|
89
|
+
if (typeof value !== 'object' || value === null)
|
|
90
|
+
return null;
|
|
91
|
+
const item = value;
|
|
92
|
+
if (!Number.isSafeInteger(item.pid) || item.pid <= 0
|
|
93
|
+
|| typeof item.sessionId !== 'string' || !item.sessionId || item.sessionId.length > 256
|
|
94
|
+
|| typeof item.cwd !== 'string' || !isAbsolute(item.cwd) || item.cwd.length > 4096
|
|
95
|
+
|| typeof item.startedAt !== 'string' || !isKind(item.kind)
|
|
96
|
+
|| (source === 'owner' && item.schemaVersion !== 1))
|
|
97
|
+
return null;
|
|
98
|
+
const observedPaths = Array.isArray(item.observedPaths)
|
|
99
|
+
? item.observedPaths.filter((path) => typeof path === 'string' && isAbsolute(path) && path.length <= 4096).slice(0, MAX_PATHS)
|
|
100
|
+
: undefined;
|
|
101
|
+
return { sessionId: item.sessionId, pid: item.pid, cwd: item.cwd,
|
|
102
|
+
startedAt: item.startedAt, kind: item.kind, source, processIdentity: 'unknown',
|
|
103
|
+
heartbeatAt: typeof item.heartbeatAt === 'string' ? item.heartbeatAt : undefined,
|
|
104
|
+
activity: source === 'owner' && isActivity(item.activity) ? item.activity : 'unknown',
|
|
105
|
+
observedPaths: source === 'owner' ? observedPaths : undefined,
|
|
106
|
+
work: source === 'owner' ? parseWorkspaceSessionWork(item.work) : null };
|
|
107
|
+
}
|
|
108
|
+
async function readBounded(path) {
|
|
109
|
+
const metadata = await lstat(path);
|
|
110
|
+
if (!metadata.isFile() || metadata.size > MAX_RECORD_BYTES)
|
|
111
|
+
throw new Error('Invalid or oversized presence file');
|
|
112
|
+
const handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
|
|
113
|
+
try {
|
|
114
|
+
const opened = await handle.stat();
|
|
115
|
+
if (!opened.isFile() || opened.dev !== metadata.dev || opened.ino !== metadata.ino)
|
|
116
|
+
throw new Error('Presence file changed during admission');
|
|
117
|
+
const buffer = Buffer.alloc(MAX_RECORD_BYTES + 1);
|
|
118
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
119
|
+
if (bytesRead > MAX_RECORD_BYTES)
|
|
120
|
+
throw new Error('Presence output limit exceeded');
|
|
121
|
+
return JSON.parse(buffer.subarray(0, bytesRead).toString('utf8'));
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await handle.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Kernel start time distinguishes a reused PID from the captured process.
|
|
128
|
+
* Missing/unsupported observations remain uncertain; presence is advisory. */
|
|
129
|
+
async function observedProcessStart(pid, timeout) {
|
|
130
|
+
try {
|
|
131
|
+
const result = await execFileAsync('ps', ['-p', String(pid), '-o', 'lstart='], {
|
|
132
|
+
timeout, maxBuffer: 1024, env: { ...process.env, LC_ALL: 'C', TZ: 'UTC' },
|
|
133
|
+
});
|
|
134
|
+
const value = Date.parse(`${result.stdout.trim()} UTC`);
|
|
135
|
+
return Number.isFinite(value) ? value : null;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Startup maintenance touches only canonical publications whose PID is gone.
|
|
142
|
+
* Capture before rechecking; preserve replacements, malformed data, and uncertainty. */
|
|
143
|
+
async function reapExitedOwnerRecords() {
|
|
144
|
+
let names;
|
|
145
|
+
try {
|
|
146
|
+
names = (await readdir(ownersDirectory())).sort().slice(0, MAX_OWNER_SCAN);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error.code === 'ENOENT')
|
|
150
|
+
return;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
for (const name of names) {
|
|
154
|
+
if (!/^\d+-[a-f0-9]{16}-[a-f0-9-]{36}\.json$/.test(name))
|
|
155
|
+
continue;
|
|
156
|
+
const path = join(ownersDirectory(), name);
|
|
157
|
+
let record;
|
|
158
|
+
try {
|
|
159
|
+
record = parsePresence(await readBounded(path), 'owner');
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!record || !name.startsWith(`${record.pid}-${createHash('sha256').update(record.sessionId).digest('hex').slice(0, 16)}-`)
|
|
165
|
+
|| liveness(record.pid) !== 'dead')
|
|
166
|
+
continue;
|
|
167
|
+
const captured = `${path}.${randomUUID()}.retired.json`;
|
|
168
|
+
let removed = false;
|
|
169
|
+
try {
|
|
170
|
+
await rename(path, captured);
|
|
171
|
+
const current = parsePresence(await readBounded(captured), 'owner');
|
|
172
|
+
if (current && current.pid === record.pid && current.sessionId === record.sessionId
|
|
173
|
+
&& current.startedAt === record.startedAt && liveness(current.pid) === 'dead') {
|
|
174
|
+
await unlink(captured);
|
|
175
|
+
removed = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch { /* An unreadable/replaced record remains conservative evidence. */ }
|
|
179
|
+
finally {
|
|
180
|
+
if (!removed) {
|
|
181
|
+
try {
|
|
182
|
+
await link(captured, path);
|
|
183
|
+
await unlink(captured);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!['ENOENT', 'EEXIST'].includes(error.code ?? ''))
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function readPresence() {
|
|
194
|
+
const records = [];
|
|
195
|
+
const warnings = [];
|
|
196
|
+
let names = [];
|
|
197
|
+
try {
|
|
198
|
+
names = (await readdir(ownersDirectory())).filter(name => name.endsWith('.json')).sort();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error.code !== 'ENOENT')
|
|
202
|
+
warnings.push('Uncertain local presence: workspace owner records are unreadable.');
|
|
203
|
+
}
|
|
204
|
+
if (names.length > MAX_OWNER_SCAN)
|
|
205
|
+
warnings.push('Uncertain local presence: the bounded owner scan is incomplete.');
|
|
206
|
+
const starts = new Map();
|
|
207
|
+
const identityDeadline = performance.now() + GIT_TIMEOUT_MS;
|
|
208
|
+
const active = async (record) => {
|
|
209
|
+
const state = liveness(record.pid);
|
|
210
|
+
if (state === 'dead')
|
|
211
|
+
return false;
|
|
212
|
+
if (state === 'unknown')
|
|
213
|
+
return true;
|
|
214
|
+
let start = starts.get(record.pid);
|
|
215
|
+
if (!start) {
|
|
216
|
+
const remaining = Math.floor(identityDeadline - performance.now());
|
|
217
|
+
start = remaining <= 0 ? Promise.resolve(null) : observedProcessStart(record.pid, remaining);
|
|
218
|
+
starts.set(record.pid, start);
|
|
219
|
+
}
|
|
220
|
+
const observed = await start, captured = Date.parse(record.startedAt);
|
|
221
|
+
// Owner records capture process birth from Node uptime. Legacy instance
|
|
222
|
+
// records capture registration, which may happen well after startup.
|
|
223
|
+
// A process born after that registration is a reused PID; an earlier birth
|
|
224
|
+
// remains valid legacy evidence. ps has a two-second rounding allowance.
|
|
225
|
+
record.processIdentity = observed === null || !Number.isFinite(captured) ? 'unknown' : 'matched';
|
|
226
|
+
return record.processIdentity === 'unknown' || (record.source === 'legacy'
|
|
227
|
+
? observed <= captured + 2000 : Math.abs(observed - captured) <= 2000);
|
|
228
|
+
};
|
|
229
|
+
for (const name of names.slice(0, MAX_OWNER_SCAN)) {
|
|
230
|
+
try {
|
|
231
|
+
const raw = await readBounded(join(ownersDirectory(), name));
|
|
232
|
+
const record = parsePresence(raw, 'owner');
|
|
233
|
+
if (record && await active(record)) {
|
|
234
|
+
records.push(record);
|
|
235
|
+
if (raw !== null && typeof raw === 'object' && Object.prototype.hasOwnProperty.call(raw, 'work') && record.work === null)
|
|
236
|
+
warnings.push(WORK_CONTEXT_WARNING);
|
|
237
|
+
}
|
|
238
|
+
else if (!record)
|
|
239
|
+
warnings.push('Uncertain local presence: an invalid workspace owner record was retained.');
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error.code !== 'ENOENT')
|
|
243
|
+
warnings.push('Uncertain local presence: an unreadable workspace owner record was retained.');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const legacy = await readBounded(join(homedir(), '.ztc', 'instances.json'));
|
|
248
|
+
if (!Array.isArray(legacy))
|
|
249
|
+
throw new Error('Invalid legacy registry');
|
|
250
|
+
for (const value of legacy) {
|
|
251
|
+
const record = parsePresence(value, 'legacy');
|
|
252
|
+
if (record && await active(record))
|
|
253
|
+
records.push(record);
|
|
254
|
+
else if (!record)
|
|
255
|
+
warnings.push('Uncertain local presence: an invalid legacy instance was retained.');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (error.code !== 'ENOENT')
|
|
260
|
+
warnings.push('Uncertain local presence: the legacy registry is unreadable.');
|
|
261
|
+
}
|
|
262
|
+
// Prefer independent owner records, while leaving both source files untouched.
|
|
263
|
+
const unique = new Map();
|
|
264
|
+
for (const record of records) {
|
|
265
|
+
const key = `${record.pid}:${record.sessionId}`;
|
|
266
|
+
if (!unique.has(key) || record.source === 'owner')
|
|
267
|
+
unique.set(key, record);
|
|
268
|
+
}
|
|
269
|
+
if (unique.size > MAX_RECORDS)
|
|
270
|
+
warnings.push('Uncertain local presence: the bounded live owner inventory is incomplete.');
|
|
271
|
+
return { records: [...unique.values()].slice(0, MAX_RECORDS), warnings: [...new Set(warnings)] };
|
|
272
|
+
}
|
|
273
|
+
function evidenceQuality(record, now) {
|
|
274
|
+
const heartbeat = Date.parse(record.heartbeatAt || '');
|
|
275
|
+
const evidenceAgeMs = Number.isFinite(heartbeat) ? Math.max(0, now - heartbeat) : null;
|
|
276
|
+
if (record.processIdentity === 'unknown' || liveness(record.pid) === 'unknown' || evidenceAgeMs === null)
|
|
277
|
+
return { evidenceQuality: 'unknown', evidenceAgeMs };
|
|
278
|
+
return { evidenceQuality: evidenceAgeMs > STALE_MS ? 'stale' : record.source === 'owner' ? 'observed' : 'live', evidenceAgeMs };
|
|
279
|
+
}
|
|
280
|
+
async function sessionEvidence(record, now) {
|
|
281
|
+
const cwd = await canonical(record.cwd);
|
|
282
|
+
return { sessionId: record.sessionId, pid: record.pid, startedAt: record.startedAt, cwd,
|
|
283
|
+
...(await gitIdentity(cwd)), kind: record.kind, activity: record.activity || 'unknown',
|
|
284
|
+
observedPaths: [...new Set(record.observedPaths || [])].sort(),
|
|
285
|
+
fileEvidence: record.source === 'owner' && Array.isArray(record.observedPaths) ? 'observed' : 'unknown',
|
|
286
|
+
...evidenceQuality(record, now), work: record.source === 'owner' ? record.work ?? null : null };
|
|
287
|
+
}
|
|
288
|
+
function sameRepository(left, right) {
|
|
289
|
+
return left.gitCommonDir !== null && right.gitCommonDir !== null
|
|
290
|
+
? left.gitCommonDir === right.gitCommonDir
|
|
291
|
+
: left.repositoryRoot === null && right.repositoryRoot === null && left.cwd === right.cwd;
|
|
292
|
+
}
|
|
293
|
+
function collisionReasons(snapshot) {
|
|
294
|
+
return snapshot.reasons.filter(reason => reason !== WORK_CONTEXT_WARNING);
|
|
295
|
+
}
|
|
296
|
+
/** Missing work context is advisory; actual presence, branch and path risks still require admission. */
|
|
297
|
+
export function hasWorkspaceCollisionRisk(snapshot) {
|
|
298
|
+
return collisionReasons(snapshot).length > 0;
|
|
299
|
+
}
|
|
300
|
+
function fingerprint(snapshot) {
|
|
301
|
+
const stable = (owner) => ({ sessionId: owner.sessionId, pid: owner.pid,
|
|
302
|
+
startedAt: owner.startedAt, cwd: owner.cwd, repositoryRoot: owner.repositoryRoot, gitCommonDir: owner.gitCommonDir,
|
|
303
|
+
branch: owner.branch, head: owner.head, activity: owner.activity, observedPaths: owner.observedPaths,
|
|
304
|
+
fileEvidence: owner.fileEvidence, evidenceQuality: owner.evidenceQuality });
|
|
305
|
+
const { current } = snapshot;
|
|
306
|
+
// A caller's own idle/running/paused transitions do not create a new risk.
|
|
307
|
+
// Own paths affect the decision only when they add an overlap reason.
|
|
308
|
+
const identity = { sessionId: current.sessionId, pid: current.pid, startedAt: current.startedAt,
|
|
309
|
+
cwd: current.cwd, repositoryRoot: current.repositoryRoot, gitCommonDir: current.gitCommonDir,
|
|
310
|
+
branch: current.branch, head: current.head };
|
|
311
|
+
// Summary availability/content never creates a new workspace-collision decision.
|
|
312
|
+
return createHash('sha256').update(JSON.stringify({ current: identity, peers: snapshot.peers.map(stable), reasons: collisionReasons(snapshot) })).digest('hex');
|
|
313
|
+
}
|
|
314
|
+
/** Read-only: never call the legacy registry's pruning/reaping readers. */
|
|
315
|
+
export async function inspectWorkspaceSessions(cwd, sessionId) {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
const presence = await readPresence();
|
|
318
|
+
const currentRecord = presence.records.find(record => record.pid === process.pid && (!sessionId || record.sessionId === sessionId));
|
|
319
|
+
const current = await sessionEvidence(currentRecord ? { ...currentRecord, cwd } : {
|
|
320
|
+
sessionId: sessionId || 'unknown', pid: process.pid, startedAt: new Date(now - process.uptime() * 1000).toISOString(),
|
|
321
|
+
cwd: resolve(cwd), kind: 'interactive', source: 'legacy', activity: 'unknown', processIdentity: 'unknown',
|
|
322
|
+
}, now);
|
|
323
|
+
const candidates = presence.records.filter(record => !(record.pid === process.pid && record.sessionId === current.sessionId)
|
|
324
|
+
&& liveness(record.pid) !== 'dead');
|
|
325
|
+
const reasons = [...presence.warnings];
|
|
326
|
+
if (candidates.length > MAX_PEERS)
|
|
327
|
+
reasons.push('Uncertain local presence: the bounded peer discovery is incomplete.');
|
|
328
|
+
const peers = [];
|
|
329
|
+
// Four independent Git probes at a time; all commands have fixed argv and bounded output/time.
|
|
330
|
+
const selected = candidates.slice(0, MAX_PEERS);
|
|
331
|
+
for (let offset = 0; offset < selected.length; offset += 4) {
|
|
332
|
+
const batch = await Promise.all(selected.slice(offset, offset + 4).map(record => sessionEvidence(record, now)));
|
|
333
|
+
peers.push(...batch.filter(peer => sameRepository(current, peer)));
|
|
334
|
+
}
|
|
335
|
+
peers.sort((a, b) => a.sessionId.localeCompare(b.sessionId) || a.pid - b.pid || a.startedAt.localeCompare(b.startedAt));
|
|
336
|
+
for (const peer of peers) {
|
|
337
|
+
if (current.repositoryRoot !== null && current.repositoryRoot === peer.repositoryRoot)
|
|
338
|
+
reasons.push(`Same worktree: ${peer.sessionId} (${peer.activity}; ${peer.evidenceQuality} evidence).`);
|
|
339
|
+
if (current.branch !== null && current.branch === peer.branch)
|
|
340
|
+
reasons.push(`Same branch: ${peer.sessionId} on ${peer.branch}.`);
|
|
341
|
+
const overlap = current.observedPaths.filter(path => peer.observedPaths.includes(path));
|
|
342
|
+
if (overlap.length)
|
|
343
|
+
reasons.push(`Observed file activity overlaps ${peer.sessionId}: ${overlap.join(', ')}.`);
|
|
344
|
+
if (peer.evidenceQuality === 'stale' || peer.evidenceQuality === 'unknown')
|
|
345
|
+
reasons.push(`Uncertain activity: ${peer.sessionId} remains a live or unverifiable process; stale evidence is not an ownership grant.`);
|
|
346
|
+
}
|
|
347
|
+
const snapshot = { current, peers, reasons: [...new Set(reasons)].sort(), observedAt: new Date(now).toISOString() };
|
|
348
|
+
return { ...snapshot, fingerprint: fingerprint(snapshot) };
|
|
349
|
+
}
|
|
350
|
+
export function formatWorkspaceSnapshot(snapshot) {
|
|
351
|
+
const { current } = snapshot;
|
|
352
|
+
const lines = ['Active local sessions (advisory; observed activity is not an enforced lock):',
|
|
353
|
+
`Current: ${current.sessionId} PID ${current.pid} ${current.activity}`,
|
|
354
|
+
`Repository: ${current.repositoryRoot || 'unknown (not a Git repository)'}`,
|
|
355
|
+
`Branch: ${current.branch || 'unknown / detached'}`];
|
|
356
|
+
lines.push(...formatWorkspaceSessionWork(current.work, current.evidenceQuality));
|
|
357
|
+
if (!snapshot.peers.length)
|
|
358
|
+
lines.push('No sibling sessions observed in this repository.');
|
|
359
|
+
for (const peer of snapshot.peers) {
|
|
360
|
+
lines.push(`${peer.sessionId} PID ${peer.pid} ${peer.activity} ${peer.evidenceQuality} evidence`, ` Repository: ${peer.repositoryRoot || 'unknown'} Branch: ${peer.branch || 'unknown / detached'}`, ` ${peer.fileEvidence === 'unknown' ? 'File ownership unknown; no attributed write observations' : `Observed file activity: ${peer.observedPaths.length ? peer.observedPaths.join(', ') : 'no writes observed; other file activity remains unknown'}`}`);
|
|
361
|
+
lines.push(...formatWorkspaceSessionWork(peer.work, peer.evidenceQuality));
|
|
362
|
+
}
|
|
363
|
+
lines.push(...snapshot.reasons);
|
|
364
|
+
lines.push('Continue knowingly, isolate into a new worktree, or pause. This does not grant remote ownership.');
|
|
365
|
+
return lines.join('\n');
|
|
366
|
+
}
|
|
367
|
+
export class LocalWorkspaceCoordinator {
|
|
368
|
+
options;
|
|
369
|
+
startedAt = new Date(Date.now() - process.uptime() * 1000).toISOString();
|
|
370
|
+
recordPath;
|
|
371
|
+
activity = 'idle';
|
|
372
|
+
paths = [];
|
|
373
|
+
acknowledgement = null;
|
|
374
|
+
timer = null;
|
|
375
|
+
pending = Promise.resolve();
|
|
376
|
+
closed = false;
|
|
377
|
+
publication = null;
|
|
378
|
+
onProcessExit = () => { this.closeSync(); };
|
|
379
|
+
listeningForExit = false;
|
|
380
|
+
constructor(options) {
|
|
381
|
+
this.options = options;
|
|
382
|
+
if (!options.sessionId || options.sessionId.length > 256)
|
|
383
|
+
throw new Error('A canonical session ID is required for local workspace coordination.');
|
|
384
|
+
this.recordPath = join(ownersDirectory(), `${process.pid}-${createHash('sha256').update(options.sessionId).digest('hex').slice(0, 16)}-${randomUUID()}.json`);
|
|
385
|
+
}
|
|
386
|
+
async start() {
|
|
387
|
+
if (!this.listeningForExit && !this.closed) {
|
|
388
|
+
process.on('exit', this.onProcessExit);
|
|
389
|
+
this.listeningForExit = true;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
await reapExitedOwnerRecords();
|
|
393
|
+
}
|
|
394
|
+
catch { /* The following read-only inspection surfaces inaccessible records. */ }
|
|
395
|
+
const snapshot = await this.refresh();
|
|
396
|
+
if (!this.timer && !this.closed) {
|
|
397
|
+
this.timer = setInterval(() => { void this.refresh().catch(() => { }); }, 15_000);
|
|
398
|
+
this.timer.unref();
|
|
399
|
+
}
|
|
400
|
+
return snapshot;
|
|
401
|
+
}
|
|
402
|
+
refresh() {
|
|
403
|
+
const operation = this.pending.then(async () => {
|
|
404
|
+
if (this.closed)
|
|
405
|
+
throw new Error('Local workspace coordinator is closed.');
|
|
406
|
+
const cwd = await canonical(this.options.getCwd());
|
|
407
|
+
let warning = null;
|
|
408
|
+
let work = null;
|
|
409
|
+
let workWarning = null;
|
|
410
|
+
if (this.options.getWork) {
|
|
411
|
+
try {
|
|
412
|
+
work = deriveWorkspaceSessionWork({ ...this.options.getWork(), cwd }, this.options.sessionId);
|
|
413
|
+
}
|
|
414
|
+
catch { /* Context failure leaves the independently observed peer identity available. */ }
|
|
415
|
+
if (work === null)
|
|
416
|
+
workWarning = WORK_CONTEXT_WARNING;
|
|
417
|
+
}
|
|
418
|
+
const record = { schemaVersion: 1, sessionId: this.options.sessionId, pid: process.pid,
|
|
419
|
+
startedAt: this.startedAt, cwd, kind: this.options.kind, heartbeatAt: new Date().toISOString(),
|
|
420
|
+
activity: this.activity, observedPaths: this.paths, ...(this.options.getWork ? { work } : {}) };
|
|
421
|
+
let publication = JSON.stringify(record);
|
|
422
|
+
// New summaries must not make previously admitted identity/path evidence unreadable.
|
|
423
|
+
if (this.options.getWork && Buffer.byteLength(publication) > MAX_RECORD_BYTES) {
|
|
424
|
+
work = null;
|
|
425
|
+
record.work = null;
|
|
426
|
+
workWarning = WORK_CONTEXT_WARNING;
|
|
427
|
+
publication = JSON.stringify(record);
|
|
428
|
+
if (Buffer.byteLength(publication) > MAX_RECORD_BYTES) {
|
|
429
|
+
delete record.work;
|
|
430
|
+
publication = JSON.stringify(record);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const temporary = `${this.recordPath}.${randomUUID()}.tmp`;
|
|
434
|
+
try {
|
|
435
|
+
await mkdir(dirname(this.recordPath), { recursive: true, mode: 0o700 });
|
|
436
|
+
await writeFile(temporary, publication, { flag: 'wx', mode: 0o600 });
|
|
437
|
+
const identity = await lstat(temporary);
|
|
438
|
+
await rename(temporary, this.recordPath);
|
|
439
|
+
this.publication = { device: identity.dev, inode: identity.ino, sha256: createHash('sha256').update(publication).digest('hex') };
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
warning = 'Uncertain local presence: this session could not publish its advisory activity.';
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
try {
|
|
446
|
+
await unlink(temporary);
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
if (error.code !== 'ENOENT')
|
|
450
|
+
warning = 'Uncertain local presence: an incomplete publication could not be removed.';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const inspected = await inspectWorkspaceSessions(cwd, this.options.sessionId);
|
|
454
|
+
// The owning process knows its own state even if publication is unavailable.
|
|
455
|
+
inspected.current = { ...inspected.current, startedAt: this.startedAt, kind: this.options.kind,
|
|
456
|
+
activity: this.activity, observedPaths: [...this.paths], fileEvidence: 'observed', work };
|
|
457
|
+
if (warning)
|
|
458
|
+
inspected.reasons = [...new Set([...inspected.reasons, warning])].sort();
|
|
459
|
+
if (workWarning)
|
|
460
|
+
inspected.reasons = [...new Set([...inspected.reasons, workWarning])].sort();
|
|
461
|
+
inspected.fingerprint = fingerprint(inspected);
|
|
462
|
+
this.options.onSnapshot?.(inspected);
|
|
463
|
+
return inspected;
|
|
464
|
+
});
|
|
465
|
+
this.pending = operation.catch(() => { });
|
|
466
|
+
return operation;
|
|
467
|
+
}
|
|
468
|
+
setActivity(activity) {
|
|
469
|
+
this.activity = activity;
|
|
470
|
+
return this.refresh();
|
|
471
|
+
}
|
|
472
|
+
async recordObservedPaths(paths, cwd = this.options.getCwd()) {
|
|
473
|
+
const normalized = await Promise.all(paths.slice(0, MAX_PATHS).filter(path => typeof path === 'string' && path.length <= 4096)
|
|
474
|
+
.map(path => canonical(resolve(cwd, path))));
|
|
475
|
+
this.paths = [...new Set([...this.paths, ...normalized])].sort().slice(0, MAX_PATHS);
|
|
476
|
+
return this.refresh();
|
|
477
|
+
}
|
|
478
|
+
acknowledge(snapshot) { this.acknowledgement = snapshot.fingerprint; }
|
|
479
|
+
isAcknowledged(snapshot) { return this.acknowledgement === snapshot.fingerprint; }
|
|
480
|
+
async close() {
|
|
481
|
+
this.closed = true;
|
|
482
|
+
if (this.timer)
|
|
483
|
+
clearInterval(this.timer);
|
|
484
|
+
this.timer = null;
|
|
485
|
+
await this.pending;
|
|
486
|
+
this.closeSync();
|
|
487
|
+
}
|
|
488
|
+
/** Only the exact directly owned publication may be retired during process exit. */
|
|
489
|
+
closeSync() {
|
|
490
|
+
this.closed = true;
|
|
491
|
+
if (this.timer)
|
|
492
|
+
clearInterval(this.timer);
|
|
493
|
+
this.timer = null;
|
|
494
|
+
process.removeListener('exit', this.onProcessExit);
|
|
495
|
+
this.listeningForExit = false;
|
|
496
|
+
const expected = this.publication;
|
|
497
|
+
if (!expected)
|
|
498
|
+
return;
|
|
499
|
+
const captured = `${this.recordPath}.${randomUUID()}.retired.json`;
|
|
500
|
+
let owned = false;
|
|
501
|
+
try {
|
|
502
|
+
renameSync(this.recordPath, captured);
|
|
503
|
+
const fd = openSync(captured, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
504
|
+
try {
|
|
505
|
+
const identity = fstatSync(fd);
|
|
506
|
+
owned = identity.isFile() && identity.size <= MAX_RECORD_BYTES && identity.dev === expected.device
|
|
507
|
+
&& identity.ino === expected.inode && createHash('sha256').update(readFileSync(fd)).digest('hex') === expected.sha256;
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
closeSync(fd);
|
|
511
|
+
}
|
|
512
|
+
if (owned) {
|
|
513
|
+
unlinkSync(captured);
|
|
514
|
+
this.publication = null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (error.code !== 'ENOENT')
|
|
519
|
+
console.error('Could not retire the owned workspace presence record:', error.message);
|
|
520
|
+
}
|
|
521
|
+
finally {
|
|
522
|
+
if (!owned) {
|
|
523
|
+
// Hard-link restoration never replaces a newer publication. A replaced
|
|
524
|
+
// file remains discoverable under the capture name if the original is busy.
|
|
525
|
+
try {
|
|
526
|
+
lstatSync(captured);
|
|
527
|
+
linkSync(captured, this.recordPath);
|
|
528
|
+
unlinkSync(captured);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (!['ENOENT', 'EEXIST'].includes(error.code ?? ''))
|
|
532
|
+
console.error('Could not restore a replaced workspace presence record:', error.message);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function hash(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
539
|
+
async function captureSource(root) {
|
|
540
|
+
const [staged, unstaged, stagedPaths, unstagedPaths, untrackedRaw, indexRaw] = await Promise.all([
|
|
541
|
+
git(root, ['diff', '--cached', '--binary', '--no-ext-diff', 'HEAD', '--'], PATCH_LIMIT),
|
|
542
|
+
git(root, ['diff', '--binary', '--no-ext-diff', '--'], PATCH_LIMIT),
|
|
543
|
+
git(root, ['diff', '--cached', '--no-renames', '--name-only', '-z', 'HEAD', '--']),
|
|
544
|
+
git(root, ['diff', '--no-renames', '--name-only', '-z', '--']),
|
|
545
|
+
git(root, ['ls-files', '--others', '--exclude-standard', '-z']),
|
|
546
|
+
git(root, ['rev-parse', '--path-format=absolute', '--git-path', 'index']),
|
|
547
|
+
]);
|
|
548
|
+
const split = (value) => value.toString('utf8').split('\0').filter(Boolean);
|
|
549
|
+
const untracked = split(untrackedRaw);
|
|
550
|
+
const paths = [...new Set([...split(stagedPaths), ...split(unstagedPaths), ...untracked])].sort();
|
|
551
|
+
if (paths.length > COPY_PATH_LIMIT)
|
|
552
|
+
throw new Error('Workspace isolation path limit exceeded; reduce the working set and retry.');
|
|
553
|
+
let size = 0;
|
|
554
|
+
const entries = [];
|
|
555
|
+
for (const path of paths) {
|
|
556
|
+
if (isAbsolute(path) || path.split(/[\\/]/).includes('..'))
|
|
557
|
+
throw new Error('Unsupported path in working tree.');
|
|
558
|
+
const absolute = join(root, path);
|
|
559
|
+
let metadata;
|
|
560
|
+
try {
|
|
561
|
+
metadata = await lstat(absolute);
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
if (error.code === 'ENOENT') {
|
|
565
|
+
entries.push(`${path}:deleted`);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
if (metadata.isSymbolicLink()) {
|
|
571
|
+
entries.push(`${path}:link:${await readlink(absolute)}`);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (!metadata.isFile())
|
|
575
|
+
throw new Error('Workspace isolation cannot copy submodule or special-file edits; preserve them manually.');
|
|
576
|
+
size += metadata.size;
|
|
577
|
+
if (size > FILE_LIMIT)
|
|
578
|
+
throw new Error('Workspace isolation content limit exceeded; preserve large files manually.');
|
|
579
|
+
entries.push(`${path}:${metadata.mode & 0o777}:${hash(await readFile(absolute))}`);
|
|
580
|
+
}
|
|
581
|
+
let index;
|
|
582
|
+
try {
|
|
583
|
+
index = hash(await readFile(indexRaw.toString('utf8').trim()));
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
if (error.code === 'ENOENT')
|
|
587
|
+
index = 'absent';
|
|
588
|
+
else
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
return { staged, unstaged, paths, untracked, index, bytes: hash(JSON.stringify(entries)) };
|
|
592
|
+
}
|
|
593
|
+
async function applyPatch(cwd, patch, staged) {
|
|
594
|
+
if (!patch.length)
|
|
595
|
+
return;
|
|
596
|
+
await new Promise((resolvePromise, reject) => {
|
|
597
|
+
const child = spawn('git', ['-C', cwd, 'apply', '--binary', ...(staged ? ['--index'] : []), '-'], {
|
|
598
|
+
env: gitEnvironment(), stdio: ['pipe', 'ignore', 'pipe'], windowsHide: true,
|
|
599
|
+
});
|
|
600
|
+
let stderr = '';
|
|
601
|
+
let timeout = false;
|
|
602
|
+
const timer = setTimeout(() => { timeout = true; child.kill('SIGKILL'); }, 10_000);
|
|
603
|
+
child.stderr.on('data', chunk => { if (stderr.length < 4096)
|
|
604
|
+
stderr += chunk.toString().slice(0, 4096 - stderr.length); });
|
|
605
|
+
child.stdin.on('error', () => { });
|
|
606
|
+
child.once('error', error => { clearTimeout(timer); reject(error); });
|
|
607
|
+
child.once('close', code => { clearTimeout(timer); code === 0 ? resolvePromise() : reject(new Error(timeout ? 'Git patch application timed out.' : `Git patch application failed: ${stderr.trim()}`)); });
|
|
608
|
+
child.stdin.end(patch);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function inside(parent, child) {
|
|
612
|
+
const path = relative(parent, child);
|
|
613
|
+
return path === '' || (path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path));
|
|
614
|
+
}
|
|
615
|
+
/** Create an isolated copy without stashing, resetting, checking out, or editing the source index. */
|
|
616
|
+
export async function prepareWorkspaceIsolation(snapshot, target) {
|
|
617
|
+
const source = snapshot.current.repositoryRoot;
|
|
618
|
+
if (!source || !snapshot.current.head)
|
|
619
|
+
throw new WorkspaceIsolationError('A committed Git repository is required for workspace isolation.');
|
|
620
|
+
let destination = null;
|
|
621
|
+
let created = false;
|
|
622
|
+
try {
|
|
623
|
+
const current = await gitIdentity(source);
|
|
624
|
+
if (current.head !== snapshot.current.head || current.gitCommonDir !== snapshot.current.gitCommonDir)
|
|
625
|
+
throw new Error('Source HEAD changed since the workspace snapshot; refresh and retry isolation.');
|
|
626
|
+
const before = await captureSource(source);
|
|
627
|
+
if (target) {
|
|
628
|
+
destination = resolve(await canonical(dirname(resolve(target))), resolve(target).split(sep).at(-1));
|
|
629
|
+
if (inside(source, destination))
|
|
630
|
+
throw new Error('The isolation target must be outside the source repository.');
|
|
631
|
+
try {
|
|
632
|
+
await lstat(destination);
|
|
633
|
+
throw new Error('The isolation target already exists; choose a new target.');
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
if (error.code !== 'ENOENT')
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
destination = await mkdtemp(join(tmpdir(), 'ztc-workspace-'));
|
|
642
|
+
// Git permits an empty precreated directory; no existing user files are removed.
|
|
643
|
+
}
|
|
644
|
+
const branch = `ztc-isolate-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
645
|
+
// No --force: branch/path collisions remain recoverable errors.
|
|
646
|
+
created = true;
|
|
647
|
+
await git(source, ['worktree', 'add', '-b', branch, '--', destination, snapshot.current.head], 1024 * 1024, 10_000);
|
|
648
|
+
await applyPatch(destination, before.staged, true);
|
|
649
|
+
await applyPatch(destination, before.unstaged, false);
|
|
650
|
+
for (const path of before.untracked) {
|
|
651
|
+
const from = join(source, path);
|
|
652
|
+
const to = join(destination, path);
|
|
653
|
+
await mkdir(dirname(to), { recursive: true });
|
|
654
|
+
const metadata = await lstat(from);
|
|
655
|
+
if (metadata.isSymbolicLink())
|
|
656
|
+
await symlink(await readlink(from), to);
|
|
657
|
+
else {
|
|
658
|
+
await copyFile(from, to);
|
|
659
|
+
await chmod(to, metadata.mode & 0o777);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const [after, isolated, latest] = await Promise.all([captureSource(source), captureSource(destination), gitIdentity(source)]);
|
|
663
|
+
if (before.index !== after.index || before.bytes !== after.bytes || !before.staged.equals(after.staged)
|
|
664
|
+
|| !before.unstaged.equals(after.unstaged) || latest.head !== snapshot.current.head)
|
|
665
|
+
throw new Error('Source changed while preparing isolation; both workspaces were retained for recovery.');
|
|
666
|
+
if (before.bytes !== isolated.bytes || !before.staged.equals(isolated.staged) || !before.unstaged.equals(isolated.unstaged))
|
|
667
|
+
throw new Error('The isolated copy did not preserve all captured edits; both workspaces were retained for recovery.');
|
|
668
|
+
const subdirectory = relative(source, snapshot.current.cwd);
|
|
669
|
+
const cwd = inside(source, snapshot.current.cwd) ? join(destination, subdirectory) : destination;
|
|
670
|
+
await mkdir(cwd, { recursive: true });
|
|
671
|
+
return { cwd, sourceCwd: snapshot.current.cwd, copiedPaths: [...before.paths], preservedSource: true };
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
// A partial worktree is never force-removed. The caller receives its recovery path.
|
|
675
|
+
let retainedTarget = null;
|
|
676
|
+
if (created && destination) {
|
|
677
|
+
try {
|
|
678
|
+
await lstat(destination);
|
|
679
|
+
retainedTarget = destination;
|
|
680
|
+
}
|
|
681
|
+
catch (probeError) {
|
|
682
|
+
if (probeError.code !== 'ENOENT')
|
|
683
|
+
retainedTarget = destination;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
throw new WorkspaceIsolationError(error.message, retainedTarget);
|
|
687
|
+
}
|
|
688
|
+
}
|