harnery 0.38.0 → 0.40.0
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/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +32 -8
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +97 -6
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +239 -33
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +466 -231
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +6 -3
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +20 -4
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +26 -3
- package/dist/commands/qa-status.d.ts.map +1 -1
- package/dist/commands/qa-status.js +7 -2
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +23 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +98 -23
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -2
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -0
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +5 -3
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.d.ts +7 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +63 -8
- package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +2 -1
- package/dist/core/agents/state/names.d.ts +35 -2
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +80 -5
- package/dist/core/agents/state/stale-sweep.d.ts +2 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -6
- package/dist/core/agents/turn-elapsed.d.ts +51 -0
- package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
- package/dist/core/agents/turn-elapsed.js +91 -0
- package/dist/core/artifacts/activity.d.ts +14 -0
- package/dist/core/artifacts/activity.d.ts.map +1 -0
- package/dist/core/artifacts/activity.js +52 -0
- package/dist/core/artifacts/delivery-card.d.ts +39 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
- package/dist/core/artifacts/delivery-card.js +282 -0
- package/dist/core/artifacts/index.d.ts +42 -7
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +288 -76
- package/dist/core/artifacts/mutation-lock.d.ts +2 -0
- package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
- package/dist/core/artifacts/mutation-lock.js +91 -0
- package/dist/core/backup/host-snapshot.d.ts +18 -0
- package/dist/core/backup/host-snapshot.d.ts.map +1 -0
- package/dist/core/backup/host-snapshot.js +64 -0
- package/dist/core/config.d.ts +20 -5
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +44 -10
- package/dist/core/diagnostics/advice.d.ts +38 -3
- package/dist/core/diagnostics/advice.d.ts.map +1 -1
- package/dist/core/diagnostics/advice.js +103 -99
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +111 -47
- package/dist/core/diagnostics/comparison.d.ts.map +1 -1
- package/dist/core/diagnostics/comparison.js +3 -2
- package/dist/core/diagnostics/contract.d.ts +15 -31
- package/dist/core/diagnostics/contract.d.ts.map +1 -1
- package/dist/core/diagnostics/contract.js +2 -5
- package/dist/core/diagnostics/index.d.ts +2 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -1
- package/dist/core/diagnostics/index.js +2 -0
- package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
- package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure-contract.js +70 -0
- package/dist/core/diagnostics/pressure.d.ts +25 -0
- package/dist/core/diagnostics/pressure.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure.js +1138 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -1
- package/dist/core/diagnostics/replay.js +90 -10
- package/dist/core/events/v3/adapter-id.d.ts +6 -0
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
- package/dist/core/events/v3/adapter-id.js +7 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
- package/dist/core/events/v3/archive-retention.js +3 -2
- package/dist/core/events/v3/bootstrap.d.ts +41 -4
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +120 -17
- package/dist/core/events/v3/capabilities.d.ts +16 -9
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +37 -2
- package/dist/core/events/v3/capability-drift.d.ts +2 -2
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.d.ts +48 -5
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +91 -36
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +22 -2
- package/dist/core/events/v3/index.d.ts +2 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +2 -1
- package/dist/core/events/v3/live-feed.d.ts +7 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +21 -3
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +10 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +2 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
- package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +15 -5
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +17 -4
- package/dist/core/events/v3/producers/intake.d.ts +4 -4
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +3 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
- package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
- package/dist/core/events/v3/producers/recorder.d.ts +16 -5
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +31 -9
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +10 -13
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +3 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.d.ts +16 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +36 -38
- package/dist/core/hooks/backup-schedule.d.ts +34 -0
- package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
- package/dist/core/hooks/backup-schedule.js +114 -0
- package/dist/core/hooks/cli.js +61 -18
- package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
- package/dist/core/hooks/codex-wsl-bridge.js +18 -4
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/contract.js +102 -0
- package/dist/core/hooks/prompt-context/index.d.ts +5 -0
- package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/index.js +2 -0
- package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
- package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/runner.js +249 -0
- package/dist/core/qa-artifacts.d.ts +3 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -1
- package/dist/core/qa-artifacts.js +25 -3
- package/dist/core/resources/contract.d.ts +69 -3
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/contract.js +1 -1
- package/dist/core/resources/index.d.ts +1 -0
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +1 -0
- package/dist/core/resources/native.d.ts +36 -0
- package/dist/core/resources/native.d.ts.map +1 -0
- package/dist/core/resources/native.js +418 -0
- package/dist/core/resources/sampler.d.ts +2 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +31 -1
- package/dist/core/resources/status.d.ts +61 -0
- package/dist/core/resources/status.d.ts.map +1 -0
- package/dist/core/resources/status.js +536 -0
- package/dist/core/resources/system.d.ts +27 -0
- package/dist/core/resources/system.d.ts.map +1 -0
- package/dist/core/resources/system.js +439 -0
- package/dist/core/semantic/contract.d.ts +13 -13
- package/dist/core/semantic/service.d.ts +2 -0
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +27 -4
- package/dist/core/storage/builtins.js +41 -0
- package/dist/core/supervisor/contract.d.ts +41 -3
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/contract.js +3 -2
- package/dist/core/supervisor/findings.d.ts +3 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +88 -0
- package/dist/core/supervisor/history.d.ts.map +1 -1
- package/dist/core/supervisor/history.js +14 -0
- package/dist/core/supervisor/ledger-state.d.ts +55 -0
- package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
- package/dist/core/supervisor/ledger-state.js +0 -0
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +120 -7
- package/dist/core/supervisor/storage.d.ts +3 -1
- package/dist/core/supervisor/storage.d.ts.map +1 -1
- package/dist/core/supervisor/storage.js +4 -0
- package/dist/core/workflow/admission.d.ts +3 -0
- package/dist/core/workflow/admission.d.ts.map +1 -1
- package/dist/core/workflow/admission.js +28 -16
- package/dist/core/workflow/engine.js +2 -2
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +6 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -3
- package/dist/lib/browser/capture-fidelity.d.ts +7 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
- package/dist/lib/browser/capture-fidelity.js +17 -0
- package/dist/lib/browser/client.d.ts +9 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +85 -37
- package/dist/lib/browser/index.d.ts +3 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/page-review-budget.d.ts +13 -0
- package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
- package/dist/lib/browser/page-review-budget.js +251 -0
- package/dist/lib/browser/page-review-contracts.d.ts +127 -0
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
- package/dist/lib/browser/page-review-contracts.js +5 -0
- package/dist/lib/browser/page-review-evidence.d.ts +48 -0
- package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
- package/dist/lib/browser/page-review-evidence.js +340 -0
- package/dist/lib/browser/page-review-judge.d.ts +1 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +46 -38
- package/dist/lib/browser/page-review-pack.d.ts +44 -22
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +116 -36
- package/dist/lib/browser/page-review-source.d.ts +12 -0
- package/dist/lib/browser/page-review-source.d.ts.map +1 -0
- package/dist/lib/browser/page-review-source.js +22 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +48 -14
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +215 -68
- package/dist/lib/browser/qa-snapshot.d.ts +6 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
- package/dist/lib/browser/qa-snapshot.js +259 -48
- package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
- package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
- package/dist/lib/browser/review-capture-transaction.js +79 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.js +4 -2
- package/dist/lib/browser/thumbnail-association.d.ts +34 -0
- package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
- package/dist/lib/browser/thumbnail-association.js +158 -0
- package/dist/lib/browser/tiling.d.ts +30 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +38 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +4 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +7 -1
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +1 -0
- package/dist/lib/local-file-url.d.ts +12 -0
- package/dist/lib/local-file-url.d.ts.map +1 -1
- package/dist/lib/local-file-url.js +16 -2
- package/dist/lib/tunnel/state.d.ts +19 -4
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +53 -13
- package/package.json +7 -4
- package/schemas/config.schema.json +69 -2
- package/src/commands/agents.ts +42 -9
- package/src/commands/artifacts.ts +130 -6
- package/src/commands/backup.ts +287 -34
- package/src/commands/browse.ts +564 -267
- package/src/commands/diagnostics.ts +6 -3
- package/src/commands/ledger-v3.ts +20 -3
- package/src/commands/qa-run.ts +37 -3
- package/src/commands/qa-status.ts +11 -2
- package/src/commands/resources.ts +23 -3
- package/src/commands/review-pack.ts +165 -62
- package/src/commands/tunnel.ts +2 -1
- package/src/core/agents/canonical-emit.ts +4 -2
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/live-authority-v3.ts +7 -3
- package/src/core/agents/live-lifecycle-v3.ts +2 -2
- package/src/core/agents/render/prompt-context.ts +68 -14
- package/src/core/agents/session-finalizer-v3.ts +2 -2
- package/src/core/agents/state/live-coordination-view.ts +4 -4
- package/src/core/agents/state/names.ts +92 -7
- package/src/core/agents/state/stale-sweep.ts +22 -8
- package/src/core/agents/turn-elapsed.ts +135 -0
- package/src/core/artifacts/activity.ts +71 -0
- package/src/core/artifacts/delivery-card.ts +375 -0
- package/src/core/artifacts/index.ts +385 -91
- package/src/core/artifacts/mutation-lock.ts +106 -0
- package/src/core/backup/host-snapshot.ts +76 -0
- package/src/core/config.ts +66 -13
- package/src/core/diagnostics/advice.ts +148 -120
- package/src/core/diagnostics/bundle.ts +138 -52
- package/src/core/diagnostics/comparison.ts +6 -2
- package/src/core/diagnostics/contract.ts +39 -42
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/pressure-contract.ts +294 -0
- package/src/core/diagnostics/pressure.ts +1361 -0
- package/src/core/diagnostics/replay.ts +116 -11
- package/src/core/events/v3/adapter-id.ts +12 -0
- package/src/core/events/v3/archive-retention.ts +3 -6
- package/src/core/events/v3/bootstrap.ts +163 -16
- package/src/core/events/v3/capabilities.ts +75 -25
- package/src/core/events/v3/capability-drift.ts +2 -2
- package/src/core/events/v3/control-witness.ts +172 -41
- package/src/core/events/v3/control.ts +34 -2
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/live-feed.ts +23 -5
- package/src/core/events/v3/live-routing.ts +14 -2
- package/src/core/events/v3/producers/command-recorder.ts +4 -4
- package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
- package/src/core/events/v3/producers/hook-base.ts +21 -16
- package/src/core/events/v3/producers/hook.ts +21 -4
- package/src/core/events/v3/producers/intake.ts +6 -6
- package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +50 -21
- package/src/core/events/v3/reader.ts +14 -13
- package/src/core/events/v3/runtime-build.ts +3 -2
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
- package/src/core/events/v3/turn-telemetry.ts +4 -4
- package/src/core/hooks/adapter/output.ts +50 -36
- package/src/core/hooks/backup-schedule.ts +147 -0
- package/src/core/hooks/cli.ts +65 -14
- package/src/core/hooks/codex-wsl-bridge.ts +21 -5
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/prompt-context/contract.ts +177 -0
- package/src/core/hooks/prompt-context/index.ts +22 -0
- package/src/core/hooks/prompt-context/runner.ts +303 -0
- package/src/core/qa-artifacts.ts +29 -3
- package/src/core/resources/contract.ts +76 -3
- package/src/core/resources/index.ts +1 -0
- package/src/core/resources/native.ts +481 -0
- package/src/core/resources/sampler.ts +46 -1
- package/src/core/resources/status.ts +717 -0
- package/src/core/resources/system.ts +530 -0
- package/src/core/semantic/service.ts +37 -3
- package/src/core/storage/builtins.ts +41 -0
- package/src/core/supervisor/contract.ts +45 -3
- package/src/core/supervisor/findings.ts +159 -0
- package/src/core/supervisor/history.ts +15 -0
- package/src/core/supervisor/ledger-state.ts +0 -0
- package/src/core/supervisor/service.ts +133 -5
- package/src/core/supervisor/storage.ts +7 -0
- package/src/core/workflow/admission.ts +33 -17
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/live-session-v3.ts +6 -8
- package/src/core/workflow/proof.ts +4 -3
- package/src/lib/browser/capture-fidelity.ts +23 -0
- package/src/lib/browser/client.ts +96 -44
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/page-review-budget.ts +309 -0
- package/src/lib/browser/page-review-contracts.ts +129 -0
- package/src/lib/browser/page-review-evidence.ts +465 -0
- package/src/lib/browser/page-review-judge.ts +56 -40
- package/src/lib/browser/page-review-pack.ts +181 -43
- package/src/lib/browser/page-review-source.ts +30 -0
- package/src/lib/browser/qa-run-contracts.ts +62 -18
- package/src/lib/browser/qa-run.ts +259 -71
- package/src/lib/browser/qa-snapshot.ts +289 -48
- package/src/lib/browser/review-capture-transaction.ts +106 -0
- package/src/lib/browser/standalone-html.ts +4 -2
- package/src/lib/browser/thumbnail-association.ts +190 -0
- package/src/lib/browser/tiling.ts +63 -0
- package/src/lib/docs-metadata-managed.ts +4 -1
- package/src/lib/identities/assume.ts +12 -4
- package/src/lib/instructions/templates.ts +1 -0
- package/src/lib/local-file-url.ts +17 -2
- package/src/lib/tunnel/state.ts +70 -13
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
constants,
|
|
5
|
+
fstatSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
statSync,
|
|
11
|
+
unlinkSync,
|
|
12
|
+
writeSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
const MAX_HTML = 2 * 1024 * 1024;
|
|
17
|
+
const MAX_PNG = 32 * 1024 * 1024;
|
|
18
|
+
export interface CaptureFingerprint {
|
|
19
|
+
version: string;
|
|
20
|
+
sha256: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CaptureThumbnailAssociation {
|
|
23
|
+
schema_version: 2;
|
|
24
|
+
producer: "browser-standalone-capture";
|
|
25
|
+
source: CaptureFingerprint & { path: string };
|
|
26
|
+
preview: CaptureFingerprint & { path: string };
|
|
27
|
+
}
|
|
28
|
+
function version(fd: number): string {
|
|
29
|
+
const s = fstatSync(fd);
|
|
30
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}:${s.ctimeMs}`;
|
|
31
|
+
}
|
|
32
|
+
function fingerprint(filename: string, maxBytes: number): CaptureFingerprint | null {
|
|
33
|
+
const canonical = realpathSync(filename);
|
|
34
|
+
const before = statSync(canonical);
|
|
35
|
+
if (!before.isFile() || before.size > maxBytes) return null;
|
|
36
|
+
const fd = openSync(canonical, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
37
|
+
try {
|
|
38
|
+
const opened = fstatSync(fd);
|
|
39
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) return null;
|
|
40
|
+
const initial = version(fd);
|
|
41
|
+
const hash = createHash("sha256");
|
|
42
|
+
const bytes = Buffer.alloc(64 * 1024);
|
|
43
|
+
let offset = 0;
|
|
44
|
+
while (offset < before.size) {
|
|
45
|
+
const count = readSync(fd, bytes, 0, Math.min(bytes.length, before.size - offset), offset);
|
|
46
|
+
if (!count) return null;
|
|
47
|
+
hash.update(bytes.subarray(0, count));
|
|
48
|
+
offset += count;
|
|
49
|
+
}
|
|
50
|
+
if (version(fd) !== initial || realpathSync(filename) !== canonical) return null;
|
|
51
|
+
return { version: initial, sha256: hash.digest("hex") };
|
|
52
|
+
} finally {
|
|
53
|
+
closeSync(fd);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Take immediately after the screenshot writer completes, before asynchronous
|
|
58
|
+
* HTML serialization. A later writer cannot silently substitute its PNG. */
|
|
59
|
+
export function captureThumbnailFingerprint(pngPath: string): CaptureFingerprint | null {
|
|
60
|
+
try {
|
|
61
|
+
return fingerprint(pngPath, MAX_PNG);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function workspaceFor(filename: string): { root: string; workspace: string } | null {
|
|
67
|
+
for (
|
|
68
|
+
let current = path.dirname(filename);
|
|
69
|
+
path.dirname(current) !== current;
|
|
70
|
+
current = path.dirname(current)
|
|
71
|
+
) {
|
|
72
|
+
const artifacts = path.dirname(current);
|
|
73
|
+
const state = path.dirname(artifacts);
|
|
74
|
+
if (path.basename(artifacts) === "artifacts" && path.basename(state) === ".harnery")
|
|
75
|
+
return { root: path.dirname(state), workspace: current };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function inside(root: string, filename: string): boolean {
|
|
80
|
+
const relative = path.relative(root, filename);
|
|
81
|
+
return (
|
|
82
|
+
relative !== "" &&
|
|
83
|
+
relative !== ".." &&
|
|
84
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
85
|
+
!path.isAbsolute(relative)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Associate only the full-document, standalone HTML and exact PNG produced by
|
|
90
|
+
* this capture operation. No basename inference and no reads of source pages.
|
|
91
|
+
* A linked asset, selector fragment, changed output, oversize file or output
|
|
92
|
+
* outside a managed artifact workspace safely skips registration.
|
|
93
|
+
* The hidden sidecar binds canonical repository paths, checked file versions
|
|
94
|
+
* and SHA256 receipts. Web readers also require an empty local asset graph;
|
|
95
|
+
* manual registrations bind their own complete dependency graph instead. */
|
|
96
|
+
export function registerCaptureThumbnail(input: {
|
|
97
|
+
htmlPath: string;
|
|
98
|
+
pngPath: string;
|
|
99
|
+
html: string;
|
|
100
|
+
preview: CaptureFingerprint;
|
|
101
|
+
fullDocument: boolean;
|
|
102
|
+
stylesheetsLinked: number;
|
|
103
|
+
resourcesLinked: number;
|
|
104
|
+
}): string | null {
|
|
105
|
+
if (!input.fullDocument || input.stylesheetsLinked !== 0 || input.resourcesLinked !== 0)
|
|
106
|
+
return null;
|
|
107
|
+
try {
|
|
108
|
+
const htmlPath = realpathSync(input.htmlPath);
|
|
109
|
+
const pngPath = realpathSync(input.pngPath);
|
|
110
|
+
if (!/\.html?$/i.test(htmlPath) || !/\.png$/i.test(pngPath)) return null;
|
|
111
|
+
const owner = workspaceFor(htmlPath);
|
|
112
|
+
if (!owner || !inside(owner.workspace, pngPath)) return null;
|
|
113
|
+
const source = fingerprint(htmlPath, MAX_HTML);
|
|
114
|
+
const preview = fingerprint(pngPath, MAX_PNG);
|
|
115
|
+
if (
|
|
116
|
+
!source ||
|
|
117
|
+
!preview ||
|
|
118
|
+
source.sha256 !== createHash("sha256").update(input.html).digest("hex") ||
|
|
119
|
+
preview.version !== input.preview.version ||
|
|
120
|
+
preview.sha256 !== input.preview.sha256
|
|
121
|
+
)
|
|
122
|
+
return null;
|
|
123
|
+
const sourceRelative = path.relative(owner.root, htmlPath).split(path.sep).join("/");
|
|
124
|
+
const previewRelative = path.relative(owner.root, pngPath).split(path.sep).join("/");
|
|
125
|
+
const association: CaptureThumbnailAssociation = {
|
|
126
|
+
schema_version: 2,
|
|
127
|
+
producer: "browser-standalone-capture",
|
|
128
|
+
source: { path: sourceRelative, ...source },
|
|
129
|
+
preview: { path: previewRelative, ...preview },
|
|
130
|
+
};
|
|
131
|
+
const name = `.thumbnail-preview-${createHash("sha256").update(sourceRelative).digest("hex").slice(0, 24)}.json`;
|
|
132
|
+
const directoryFd = openSync(
|
|
133
|
+
owner.workspace,
|
|
134
|
+
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
|
|
135
|
+
);
|
|
136
|
+
try {
|
|
137
|
+
let anchor: string | undefined;
|
|
138
|
+
for (const prefix of ["/proc/self/fd", "/dev/fd"]) {
|
|
139
|
+
const candidate = `${prefix}/${directoryFd}`;
|
|
140
|
+
try {
|
|
141
|
+
if (realpathSync(candidate) === owner.workspace) {
|
|
142
|
+
anchor = candidate;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
/* unsupported descriptor namespace */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!anchor) return null;
|
|
150
|
+
const target = path.join(anchor, name);
|
|
151
|
+
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
152
|
+
const fd = openSync(
|
|
153
|
+
temporary,
|
|
154
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
|
|
155
|
+
0o600,
|
|
156
|
+
);
|
|
157
|
+
try {
|
|
158
|
+
const bytes = Buffer.from(`${JSON.stringify(association)}\n`);
|
|
159
|
+
if (bytes.length > 16 * 1024) return null;
|
|
160
|
+
let offset = 0;
|
|
161
|
+
while (offset < bytes.length) offset += writeSync(fd, bytes, offset, bytes.length - offset);
|
|
162
|
+
if (
|
|
163
|
+
realpathSync(anchor) !== owner.workspace ||
|
|
164
|
+
realpathSync(owner.workspace) !== owner.workspace
|
|
165
|
+
)
|
|
166
|
+
return null;
|
|
167
|
+
// Stat again after the write, without reading another potentially large
|
|
168
|
+
// image. ctime detects same-size edits with a restored mtime.
|
|
169
|
+
const matches = (filename: string, expected: string) => {
|
|
170
|
+
const s = statSync(filename);
|
|
171
|
+
return `${s.dev}:${s.ino}:${s.size}:${s.mtimeMs}:${s.ctimeMs}` === expected;
|
|
172
|
+
};
|
|
173
|
+
if (!matches(htmlPath, source.version) || !matches(pngPath, preview.version)) return null;
|
|
174
|
+
renameSync(temporary, target);
|
|
175
|
+
} finally {
|
|
176
|
+
closeSync(fd);
|
|
177
|
+
try {
|
|
178
|
+
unlinkSync(temporary);
|
|
179
|
+
} catch {
|
|
180
|
+
/* renamed */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
closeSync(directoryFd);
|
|
185
|
+
}
|
|
186
|
+
return path.relative(owner.root, path.join(owner.workspace, name)).split(path.sep).join("/");
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -194,3 +194,66 @@ export function bandsCoveringRects(
|
|
|
194
194
|
}
|
|
195
195
|
return out;
|
|
196
196
|
}
|
|
197
|
+
|
|
198
|
+
/** All native-pixel candidates, before any per-pack allocation or cropping. */
|
|
199
|
+
export function reviewCandidateRects(args: {
|
|
200
|
+
width: number;
|
|
201
|
+
height: number;
|
|
202
|
+
bandHeight: number;
|
|
203
|
+
overlap: number;
|
|
204
|
+
atoms?: VisualAtom[];
|
|
205
|
+
scopes?: Array<{
|
|
206
|
+
selector: string;
|
|
207
|
+
rects: Array<{ label: string; x: number; y: number; width: number; height: number }>;
|
|
208
|
+
}>;
|
|
209
|
+
}): Array<{
|
|
210
|
+
id: string;
|
|
211
|
+
index: number;
|
|
212
|
+
label: string;
|
|
213
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
214
|
+
scope?: string;
|
|
215
|
+
gate_hits: [];
|
|
216
|
+
}> {
|
|
217
|
+
const opts = { bandHeight: args.bandHeight, overlap: args.overlap };
|
|
218
|
+
const bands = args.atoms?.length
|
|
219
|
+
? snappedBandRects(0, args.height, 0, args.width, args.atoms, opts).rects
|
|
220
|
+
: Array.from(
|
|
221
|
+
{
|
|
222
|
+
length:
|
|
223
|
+
Math.ceil(
|
|
224
|
+
Math.max(0, args.height - args.bandHeight) /
|
|
225
|
+
Math.max(1, args.bandHeight - args.overlap),
|
|
226
|
+
) + 1,
|
|
227
|
+
},
|
|
228
|
+
(_, i) => {
|
|
229
|
+
const y = i * Math.max(1, args.bandHeight - args.overlap);
|
|
230
|
+
return { x: 0, y, width: args.width, height: Math.min(args.bandHeight, args.height - y) };
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
const out: ReturnType<typeof reviewCandidateRects> = bands.map((r, i) => ({
|
|
234
|
+
id: `band-${i + 1}`,
|
|
235
|
+
index: i,
|
|
236
|
+
label: `band ${i + 1}`,
|
|
237
|
+
rect: { x: r.x, y: r.y, width: r.width, height: r.height },
|
|
238
|
+
gate_hits: [],
|
|
239
|
+
}));
|
|
240
|
+
for (const [scopeIndex, scope] of (args.scopes ?? []).entries()) {
|
|
241
|
+
const expanded = scope.rects.flatMap((r) => bandOversizedRect(r, args.atoms ?? [], opts));
|
|
242
|
+
for (const [i, r] of expanded.entries()) {
|
|
243
|
+
const x = Math.max(0, Math.round(r.x)),
|
|
244
|
+
y = Math.max(0, Math.round(r.y));
|
|
245
|
+
const width = Math.min(Math.round(r.width), args.width - x),
|
|
246
|
+
height = Math.min(Math.round(r.height), args.height - y);
|
|
247
|
+
if (width <= 0 || height <= 0) continue;
|
|
248
|
+
out.push({
|
|
249
|
+
id: `scope-${scopeIndex + 1}-${i + 1}`,
|
|
250
|
+
index: out.length,
|
|
251
|
+
label: r.label,
|
|
252
|
+
scope: scope.selector,
|
|
253
|
+
rect: { x, y, width, height },
|
|
254
|
+
gate_hits: [],
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
@@ -18,7 +18,10 @@ export function isExcludedDocsMetadataPath(path: string): boolean {
|
|
|
18
18
|
// Dashboard surfaces render from copies of other repositories' documents.
|
|
19
19
|
/(?:^|\/)content\/dashboards\//.test(rel) ||
|
|
20
20
|
/(?:^|\/)docs\/shopify\/admin-api\//.test(rel) ||
|
|
21
|
-
|
|
21
|
+
// Template skeletons carry placeholder frontmatter by design. Hosts keep
|
|
22
|
+
// them under docs/templates/ or another documentation root such as
|
|
23
|
+
// meta-docs/templates/, so match the directory name rather than one root.
|
|
24
|
+
/(?:^|\/)templates\//.test(rel)
|
|
22
25
|
);
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
-
|
|
20
19
|
import { recordLiveIdentityChangeV3 } from "../../core/agents/live-authority-v3.ts";
|
|
21
20
|
import { recordLiveSweepObservationV3 } from "../../core/agents/live-lifecycle-v3.ts";
|
|
22
21
|
import type { Heartbeat } from "../../core/agents/state/heartbeat-writer.ts";
|
|
@@ -27,6 +26,7 @@ import {
|
|
|
27
26
|
import { resolveForkAncestry } from "../../core/agents/state/names.ts";
|
|
28
27
|
import { instanceHasLivePid, removePidmapRowsForInstance } from "../../core/agents/state/pidmap.ts";
|
|
29
28
|
import { coordFreshnessSeconds } from "../../core/config.ts";
|
|
29
|
+
import type { EventAdapterIdV3 } from "../../core/events/v3/adapter-id.ts";
|
|
30
30
|
import { readRemoteMachines } from "../../core/presence/index.ts";
|
|
31
31
|
import { type AgentIdentity, bareName, ensureIdentity, lookupById, lookupByName } from "./index.ts";
|
|
32
32
|
|
|
@@ -133,7 +133,7 @@ export function reclaimAbandonedLocalConflict(
|
|
|
133
133
|
if (instanceHasLivePid(coordRoot, conflict.instance_id)) return false;
|
|
134
134
|
|
|
135
135
|
const hbPath = join(coordRoot, ".harnery", "active", `${conflict.instance_id}.json`);
|
|
136
|
-
let adapter:
|
|
136
|
+
let adapter: EventAdapterIdV3 = "claude-code";
|
|
137
137
|
let sessionId = conflict.instance_id;
|
|
138
138
|
let ageSecs: number | undefined;
|
|
139
139
|
const liveRow = readLiveCoordinationRow(coordRoot, conflict.instance_id);
|
|
@@ -236,9 +236,10 @@ function acquireLock(coordRoot: string): () => void {
|
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
function adapterOf(platform: string | undefined):
|
|
239
|
+
function adapterOf(platform: string | undefined): EventAdapterIdV3 {
|
|
240
240
|
if (platform === "cursor") return "cursor";
|
|
241
241
|
if (platform === "codex") return "codex";
|
|
242
|
+
if (platform === "openclaw") return "openclaw";
|
|
242
243
|
return "claude-code";
|
|
243
244
|
}
|
|
244
245
|
|
|
@@ -328,11 +329,18 @@ export function assumeIdentity(
|
|
|
328
329
|
};
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
const adapter = adapterOf(hb.platform);
|
|
333
|
+
if (adapter === "openclaw") {
|
|
334
|
+
throw new IdentityAssumeError(
|
|
335
|
+
"not_session",
|
|
336
|
+
"identity assumption is unavailable for observer-only OpenClaw generations",
|
|
337
|
+
);
|
|
338
|
+
}
|
|
331
339
|
const emitted = recordLiveIdentityChangeV3({
|
|
332
340
|
coordRoot,
|
|
333
341
|
owner: instanceId,
|
|
334
342
|
nativeSessionId: hb.native_session_id ?? hb.session_id,
|
|
335
|
-
adapter
|
|
343
|
+
adapter,
|
|
336
344
|
name: identity.name,
|
|
337
345
|
identityId: identity.agent_id,
|
|
338
346
|
});
|
|
@@ -150,6 +150,7 @@ Use it for anything future-you or a peer will need to pick up your thread.
|
|
|
150
150
|
and other untracked files that must survive a session, create a managed workspace
|
|
151
151
|
with \`${b} artifacts create <slug> --purpose "<why>"\`. Write files under the
|
|
152
152
|
returned path, then run \`${b} artifacts release <id>\` when active work is done.
|
|
153
|
+
After review, use \`${b} artifacts discard <id> --reason "<why no longer needed>"\` for disposable or superseded evidence: it releases ownership and shortens retention to at most 60 minutes (\`--minutes\` overrides). Keep unresolved failures, handoffs, final evidence, and files awaiting review; use a hold when needed. A passing check cannot establish whether another reader needs the files, so the reviewing agent decides. For known short-lived work, \`${b} artifacts create --minutes <n>\` sets the initial window.
|
|
153
154
|
Do not create a repo-root temp directory; \`${b} artifacts clean\` previews
|
|
154
155
|
expired cleanup and requires \`--yes\` to delete anything.
|
|
155
156
|
|
|
@@ -4,13 +4,28 @@ export const DEFAULT_WEB_PORT = 4276;
|
|
|
4
4
|
/** Cookie-isolated host for navigable repo files. */
|
|
5
5
|
export const FILES_ORIGIN_HOST = "harnery-files.localhost";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Percent-encode one URL component so the finished URL is safe as a bare
|
|
9
|
+
* Markdown destination.
|
|
10
|
+
*
|
|
11
|
+
* encodeURIComponent leaves parentheses raw, and a raw ")" ends a `[text](url)`
|
|
12
|
+
* destination early. The alternative is the angle-bracket destination form,
|
|
13
|
+
* which is valid CommonMark but is not implemented by every renderer, and one
|
|
14
|
+
* that skips it can style the text as a link while dropping the href, leaving
|
|
15
|
+
* something that looks clickable and does nothing. These URLs are written into
|
|
16
|
+
* Markdown for humans to click, so encode the parentheses instead.
|
|
17
|
+
*/
|
|
18
|
+
export function encodeLinkSafeComponent(value: string): string {
|
|
19
|
+
return encodeURIComponent(value).replace(/[()]/g, (char) => (char === "(" ? "%28" : "%29"));
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
/** Encode each repo-path segment while preserving slash-based URL resolution. */
|
|
8
23
|
export function encodedRepoPath(relPath: string): string {
|
|
9
24
|
return relPath
|
|
10
25
|
.replace(/^\/+/, "")
|
|
11
26
|
.split("/")
|
|
12
27
|
.filter(Boolean)
|
|
13
|
-
.map((segment) =>
|
|
28
|
+
.map((segment) => encodeLinkSafeComponent(segment))
|
|
14
29
|
.join("/");
|
|
15
30
|
}
|
|
16
31
|
|
|
@@ -27,5 +42,5 @@ export function localFileViewerUrl(
|
|
|
27
42
|
relPath: string,
|
|
28
43
|
port: number | string = DEFAULT_WEB_PORT,
|
|
29
44
|
): string {
|
|
30
|
-
return `http://localhost:${port}/files?path=${
|
|
45
|
+
return `http://localhost:${port}/files?path=${encodeLinkSafeComponent(relPath)}`;
|
|
31
46
|
}
|
package/src/lib/tunnel/state.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Tunnel config + state persistence + provider helpers.
|
|
1
|
+
// Tunnel config + state persistence + provider helpers. Commands default to
|
|
2
2
|
// <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
|
|
3
3
|
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { resolve } from "node:path";
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Tunnel state lives under <root>/.cache/tunnel/. Root defaults to cwd for the
|
|
16
|
+
// command surface and can be supplied by callers that already resolved a repo.
|
|
17
|
+
function cachePath(tool: string, filename: string, root: string = process.cwd()): string {
|
|
18
|
+
const dir = resolve(root, ".cache", tool);
|
|
18
19
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
19
20
|
return resolve(dir, filename);
|
|
20
21
|
}
|
|
@@ -107,8 +108,11 @@ export function writeConfig(cfg: TunnelConfig): void {
|
|
|
107
108
|
writeFileSync(cachePath("tunnel", CONFIG_FILE), JSON.stringify(cfg, null, 2));
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
export function readState(
|
|
111
|
-
|
|
111
|
+
export function readState(
|
|
112
|
+
name: string = DEFAULT_INSTANCE,
|
|
113
|
+
root: string = process.cwd(),
|
|
114
|
+
): TunnelState | null {
|
|
115
|
+
const p = cachePath("tunnel", stateFile(name), root);
|
|
112
116
|
if (!existsSync(p)) return null;
|
|
113
117
|
try {
|
|
114
118
|
return normalizeState(JSON.parse(readFileSync(p, "utf-8")) as TunnelState, name);
|
|
@@ -117,12 +121,12 @@ export function readState(name: string = DEFAULT_INSTANCE): TunnelState | null {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
export function writeState(state: TunnelState): void {
|
|
121
|
-
writeFileSync(cachePath("tunnel", stateFile(state.name)), JSON.stringify(state, null, 2));
|
|
124
|
+
export function writeState(state: TunnelState, root: string = process.cwd()): void {
|
|
125
|
+
writeFileSync(cachePath("tunnel", stateFile(state.name), root), JSON.stringify(state, null, 2));
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
export function clearState(name: string = DEFAULT_INSTANCE): void {
|
|
125
|
-
const p = cachePath("tunnel", stateFile(name));
|
|
128
|
+
export function clearState(name: string = DEFAULT_INSTANCE, root: string = process.cwd()): void {
|
|
129
|
+
const p = cachePath("tunnel", stateFile(name), root);
|
|
126
130
|
if (existsSync(p)) unlinkSync(p);
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -130,14 +134,14 @@ export function clearState(name: string = DEFAULT_INSTANCE): void {
|
|
|
130
134
|
* Every persisted tunnel instance, newest-started first. Reads every
|
|
131
135
|
* `state*.json` under `.cache/tunnel/`; tolerates missing/corrupt files.
|
|
132
136
|
*/
|
|
133
|
-
export function listStates(): TunnelState[] {
|
|
134
|
-
const dir = resolve(
|
|
137
|
+
export function listStates(root: string = process.cwd()): TunnelState[] {
|
|
138
|
+
const dir = resolve(root, ".cache", "tunnel");
|
|
135
139
|
if (!existsSync(dir)) return [];
|
|
136
140
|
const out: TunnelState[] = [];
|
|
137
141
|
for (const file of readdirSync(dir)) {
|
|
138
142
|
const name = nameFromStateFile(file);
|
|
139
143
|
if (name === null) continue;
|
|
140
|
-
const state = readState(name);
|
|
144
|
+
const state = readState(name, root);
|
|
141
145
|
if (state) out.push(state);
|
|
142
146
|
}
|
|
143
147
|
return out.sort((a, b) => (b.started_at ?? "").localeCompare(a.started_at ?? ""));
|
|
@@ -152,6 +156,59 @@ export function isProcessAlive(pid: number): boolean {
|
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
export type ProcessAliveCheck = (pid: number) => boolean;
|
|
160
|
+
|
|
161
|
+
/** True when both the local gate and its provider-side process are live. */
|
|
162
|
+
export function isTunnelStateLive(
|
|
163
|
+
state: TunnelState,
|
|
164
|
+
processAlive: ProcessAliveCheck = isProcessAlive,
|
|
165
|
+
): boolean {
|
|
166
|
+
if (!processAlive(state.gate_pid)) return false;
|
|
167
|
+
if (state.provider === "tailscale") return true;
|
|
168
|
+
const providerPid = state.cloudflared_pid ?? state.provider_pid;
|
|
169
|
+
return typeof providerPid === "number" && processAlive(providerPid);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function tunnelTargetPort(state: TunnelState): number | null {
|
|
173
|
+
const value = /^[a-z][a-z\d+.-]*:\/\//i.test(state.target)
|
|
174
|
+
? state.target
|
|
175
|
+
: `http://${state.target}`;
|
|
176
|
+
try {
|
|
177
|
+
const url = new URL(value);
|
|
178
|
+
if (url.port) return Number.parseInt(url.port, 10);
|
|
179
|
+
return url.protocol === "https:" ? 443 : 80;
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Newest live tunnel that serves `vhost` on the requested local web port.
|
|
187
|
+
*
|
|
188
|
+
* The port alone does not identify a tunnel. Several tunnels can forward to one
|
|
189
|
+
* upstream port and be told apart only by the Host header they send, and a
|
|
190
|
+
* server that routes on Host then serves each of them a different site. Picking
|
|
191
|
+
* by port alone returns whichever such tunnel started last, so a caller that
|
|
192
|
+
* builds a URL for one site can hand back a host that serves another and every
|
|
193
|
+
* link 400s. Matching the vhost too keeps the returned tunnel the one that
|
|
194
|
+
* actually serves the caller's origin.
|
|
195
|
+
*/
|
|
196
|
+
export function findLiveTunnelForOrigin(
|
|
197
|
+
port: number,
|
|
198
|
+
vhost: string,
|
|
199
|
+
root: string = process.cwd(),
|
|
200
|
+
processAlive: ProcessAliveCheck = isProcessAlive,
|
|
201
|
+
): TunnelState | null {
|
|
202
|
+
return (
|
|
203
|
+
listStates(root).find(
|
|
204
|
+
(state) =>
|
|
205
|
+
tunnelTargetPort(state) === port &&
|
|
206
|
+
state.vhost === vhost &&
|
|
207
|
+
isTunnelStateLive(state, processAlive),
|
|
208
|
+
) ?? null
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
155
212
|
/**
|
|
156
213
|
* Ensure cloudflared is on PATH or installed at ~/.local/bin/cloudflared.
|
|
157
214
|
* Auto-downloads on Linux; throws on other platforms with brew hint.
|