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,106 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmdirSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { hostname } from "node:os";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { processStartToken } from "../agents/state/proc-start.ts";
|
|
15
|
+
|
|
16
|
+
interface LockOwner {
|
|
17
|
+
version: 1;
|
|
18
|
+
host: string;
|
|
19
|
+
pid: number;
|
|
20
|
+
process_start: string | null;
|
|
21
|
+
acquired_at: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Age is never authority to evict an owner. Unknown owners fail closed. */
|
|
25
|
+
function reclaimDeadOwner(lock: string): boolean {
|
|
26
|
+
try {
|
|
27
|
+
const stat = lstatSync(lock);
|
|
28
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
|
|
29
|
+
const names = readdirSync(lock);
|
|
30
|
+
if (names.length !== 1 || !/^owner-[0-9a-f-]{36}\.json$/.test(names[0]!)) return false;
|
|
31
|
+
const ownerPath = join(lock, names[0]!);
|
|
32
|
+
const ownerStat = lstatSync(ownerPath);
|
|
33
|
+
if (!ownerStat.isFile() || ownerStat.isSymbolicLink() || ownerStat.size > 4096) return false;
|
|
34
|
+
const owner = JSON.parse(readFileSync(ownerPath, "utf8")) as LockOwner;
|
|
35
|
+
if (
|
|
36
|
+
owner.version !== 1 ||
|
|
37
|
+
owner.host !== hostname() ||
|
|
38
|
+
!Number.isSafeInteger(owner.pid) ||
|
|
39
|
+
owner.pid <= 0 ||
|
|
40
|
+
!(owner.process_start === null || typeof owner.process_start === "string")
|
|
41
|
+
)
|
|
42
|
+
return false;
|
|
43
|
+
let dead = false;
|
|
44
|
+
try {
|
|
45
|
+
process.kill(owner.pid, 0);
|
|
46
|
+
const current = processStartToken(owner.pid);
|
|
47
|
+
// Wall-clock ps lstart output can drift even for a live process. Only
|
|
48
|
+
// boot-scoped procfs ticks establish PID reuse; other probes need ESRCH.
|
|
49
|
+
const bootScoped = /^l[0-9a-f]{8}\.\d+$/;
|
|
50
|
+
dead =
|
|
51
|
+
typeof owner.process_start === "string" &&
|
|
52
|
+
bootScoped.test(owner.process_start) &&
|
|
53
|
+
typeof current === "string" &&
|
|
54
|
+
bootScoped.test(current) &&
|
|
55
|
+
current !== owner.process_start;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
dead = (error as NodeJS.ErrnoException).code === "ESRCH";
|
|
58
|
+
}
|
|
59
|
+
if (!dead) return false;
|
|
60
|
+
// Only the contender that removes this unique owner's file may remove the
|
|
61
|
+
// directory. A losing reaper must not unlink a replacement owner's lock.
|
|
62
|
+
unlinkSync(ownerPath);
|
|
63
|
+
rmdirSync(lock);
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function withArtifactLock<T>(repoRoot: string, action: () => T): T {
|
|
71
|
+
// Probe before mkdir so a slow platform process probe cannot hold an
|
|
72
|
+
// ownerless lock open while other writers are trying to acquire it.
|
|
73
|
+
const owner: LockOwner = {
|
|
74
|
+
version: 1,
|
|
75
|
+
host: hostname(),
|
|
76
|
+
pid: process.pid,
|
|
77
|
+
process_start: processStartToken(process.pid),
|
|
78
|
+
acquired_at: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
const lock = join(resolve(repoRoot), ".harnery/artifacts-mutation.lock");
|
|
81
|
+
mkdirSync(dirname(lock), { recursive: true });
|
|
82
|
+
try {
|
|
83
|
+
mkdirSync(lock);
|
|
84
|
+
} catch {
|
|
85
|
+
if (!reclaimDeadOwner(lock)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"artifact store mutation lock unavailable; a live or unverifiable owner holds it. " +
|
|
88
|
+
"Dead local owners recover automatically; an empty or invalid lock requires explicit inspection.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
// Another caller may win after recovery. Never retry by removing its lock.
|
|
92
|
+
mkdirSync(lock);
|
|
93
|
+
}
|
|
94
|
+
const ownerPath = join(lock, `owner-${randomUUID()}.json`);
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(ownerPath, JSON.stringify(owner), { flag: "wx" });
|
|
97
|
+
return action();
|
|
98
|
+
} finally {
|
|
99
|
+
// A crash before publication or between these two calls leaves an empty
|
|
100
|
+
// directory. It cannot be distinguished safely from an initializing owner.
|
|
101
|
+
// Publication may have failed before creating a file. Remove only our
|
|
102
|
+
// unique file, then the empty directory; never recursively delete contents.
|
|
103
|
+
rmSync(ownerPath, { force: true });
|
|
104
|
+
rmdirSync(lock);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freshness helpers shared by `harn backup snapshot` and the session-start
|
|
3
|
+
* scheduler. The host cache is a local, best-effort record of this machine's
|
|
4
|
+
* newest known snapshot: the scheduler reads it to skip a remote restic round
|
|
5
|
+
* trip when the host is plainly current, and restic stays the authority
|
|
6
|
+
* whenever the cache is missing, stale, or written by another host.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { hostname } from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
export function parseBackupDuration(value: string): number | null {
|
|
14
|
+
const match = /^([1-9][0-9]*)(ms|s|m|h|d)$/.exec(value.trim());
|
|
15
|
+
if (!match) return null;
|
|
16
|
+
const scalar = Number(match[1]);
|
|
17
|
+
const factor = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[
|
|
18
|
+
match[2] as "ms" | "s" | "m" | "h" | "d"
|
|
19
|
+
];
|
|
20
|
+
const duration = scalar * factor;
|
|
21
|
+
return Number.isSafeInteger(duration) ? duration : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Newest snapshot time (epoch ms) in `restic snapshots --json` output, or null. */
|
|
25
|
+
export function newestSnapshotTime(output: string): number | null {
|
|
26
|
+
try {
|
|
27
|
+
const rows = JSON.parse(output) as Array<{ time?: string }>;
|
|
28
|
+
const newest = rows.reduce((latest, row) => {
|
|
29
|
+
const time = typeof row.time === "string" ? Date.parse(row.time) : Number.NaN;
|
|
30
|
+
return Number.isFinite(time) ? Math.max(latest, time) : latest;
|
|
31
|
+
}, Number.NEGATIVE_INFINITY);
|
|
32
|
+
return Number.isFinite(newest) ? newest : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface HostSnapshotCache {
|
|
39
|
+
host: string;
|
|
40
|
+
snapshotAt: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hostSnapshotCachePath(harneryDir: string): string {
|
|
44
|
+
return path.join(harneryDir, "logs", "backup-host-snapshot.json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readHostSnapshotCache(harneryDir: string): HostSnapshotCache | null {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(readFileSync(hostSnapshotCachePath(harneryDir), "utf8")) as {
|
|
50
|
+
host?: unknown;
|
|
51
|
+
snapshot_at?: unknown;
|
|
52
|
+
};
|
|
53
|
+
const snapshotAt =
|
|
54
|
+
typeof parsed.snapshot_at === "string" ? Date.parse(parsed.snapshot_at) : Number.NaN;
|
|
55
|
+
if (typeof parsed.host !== "string" || !Number.isFinite(snapshotAt)) return null;
|
|
56
|
+
return { host: parsed.host, snapshotAt };
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function recordHostSnapshot(harneryDir: string, snapshotAt: number): void {
|
|
63
|
+
try {
|
|
64
|
+
const file = hostSnapshotCachePath(harneryDir);
|
|
65
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
66
|
+
const body = `${JSON.stringify({
|
|
67
|
+
host: hostname(),
|
|
68
|
+
snapshot_at: new Date(snapshotAt).toISOString(),
|
|
69
|
+
recorded_at: new Date().toISOString(),
|
|
70
|
+
})}\n`;
|
|
71
|
+
writeFileSync(`${file}.tmp`, body, "utf8");
|
|
72
|
+
renameSync(`${file}.tmp`, file);
|
|
73
|
+
} catch {
|
|
74
|
+
// The cache is an optimisation; a failed write only costs a restic query later.
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. `<project-root>/.harnery/config.jsonc` — project override (authoritative)
|
|
8
8
|
*
|
|
9
9
|
* Fields owned here: `binName` (host CLI name for agent-facing strings),
|
|
10
|
-
* `hooksSetupHint`, `agents`, `instructions`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
|
|
10
|
+
* `hooksSetupHint`, `hooks`, `agents`, `instructions`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
|
|
11
11
|
* `coord` (heartbeat freshness), `logs` (structured-log storage budgets),
|
|
12
12
|
* `artifacts` (working-file retention),
|
|
13
13
|
* `backup` (restic repo/password/prune policy), `sync` (rclone
|
|
@@ -21,10 +21,14 @@
|
|
|
21
21
|
|
|
22
22
|
import { readFileSync, statSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
24
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
25
25
|
import { coordEnv } from "../lib/env.ts";
|
|
26
26
|
import { DEFAULT_WEB_PORT } from "../lib/local-file-url.ts";
|
|
27
27
|
import { resolveEventLedgerRotateActiveBytesV3 } from "./events/v3/rotation-config.ts";
|
|
28
|
+
import {
|
|
29
|
+
type PromptContextConfig,
|
|
30
|
+
parsePromptContextConfig,
|
|
31
|
+
} from "./hooks/prompt-context/contract.ts";
|
|
28
32
|
import { findCoordRoot } from "./hooks/resolve/coord-root.ts";
|
|
29
33
|
|
|
30
34
|
export { DEFAULT_WEB_PORT } from "../lib/local-file-url.ts";
|
|
@@ -73,6 +77,10 @@ interface HarneryConfig {
|
|
|
73
77
|
* declares it here (e.g. "scripts/setup-hooks.sh"). Unset → a generic hint.
|
|
74
78
|
*/
|
|
75
79
|
hooksSetupHint?: string;
|
|
80
|
+
/** Optional host prompt-context extension. Project config only. */
|
|
81
|
+
hooks?: {
|
|
82
|
+
promptContext?: unknown;
|
|
83
|
+
};
|
|
76
84
|
/**
|
|
77
85
|
* Agent-ritual policy owned by the host project. Git finalization is opt-in:
|
|
78
86
|
* standalone Harnery and embedding hosts keep the ordinary status ritual
|
|
@@ -163,12 +171,18 @@ interface HarneryConfig {
|
|
|
163
171
|
auto_clean?: boolean;
|
|
164
172
|
};
|
|
165
173
|
/**
|
|
166
|
-
* `harn backup` (restic) defaults
|
|
167
|
-
* `keep_daily`/`keep_weekly`/`keep_monthly` prune policy. Read via `backupConfig()`.
|
|
174
|
+
* `harn backup` (restic) defaults. Read via `backupConfig()`.
|
|
168
175
|
*/
|
|
169
176
|
backup?: {
|
|
170
177
|
repo?: string;
|
|
171
178
|
password_file?: string;
|
|
179
|
+
include?: string[];
|
|
180
|
+
exclude?: string[];
|
|
181
|
+
max_bytes?: number;
|
|
182
|
+
schedule?: {
|
|
183
|
+
if_stale?: string;
|
|
184
|
+
tags?: string[];
|
|
185
|
+
};
|
|
172
186
|
keep_daily?: number;
|
|
173
187
|
keep_weekly?: number;
|
|
174
188
|
keep_monthly?: number;
|
|
@@ -482,6 +496,18 @@ export function hostPromptReminder(coordRoot?: string | null): string | null {
|
|
|
482
496
|
return reminder;
|
|
483
497
|
}
|
|
484
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Project-owned prompt-context provider settings. The user-global config is
|
|
501
|
+
* ignored because one project's executable and data policy must not become a
|
|
502
|
+
* default for another project. Invalid values fail closed to `null`; callers
|
|
503
|
+
* treat that the same as a disabled provider and keep the prompt hook usable.
|
|
504
|
+
*/
|
|
505
|
+
export function hostPromptContextConfig(coordRoot?: string | null): PromptContextConfig | null {
|
|
506
|
+
const root = coordRoot ?? findCoordRoot();
|
|
507
|
+
if (!root) return null;
|
|
508
|
+
return parsePromptContextConfig(readProjectConfig(root).hooks?.promptContext);
|
|
509
|
+
}
|
|
510
|
+
|
|
485
511
|
/**
|
|
486
512
|
* Whether the host requires the guarded Git check at the end of tool-using
|
|
487
513
|
* turns. Default false: Harnery exposes `agents status --end-turn` as a capability
|
|
@@ -795,7 +821,7 @@ export function artifactMaxUnitBytes(coordRoot?: string | null): number {
|
|
|
795
821
|
}
|
|
796
822
|
|
|
797
823
|
/**
|
|
798
|
-
* Whether
|
|
824
|
+
* Whether opportunistic cleanup of expired artifact workspaces runs.
|
|
799
825
|
* Precedence: `HARNERY_ARTIFACT_AUTO_CLEAN` (0/false disables) ->
|
|
800
826
|
* `artifacts.auto_clean` -> enabled. The sweep only ever deletes
|
|
801
827
|
* `managed-expired` entries via the same guarded classifier as
|
|
@@ -826,9 +852,8 @@ export function reviewPackAutoCleanEnabled(coordRoot?: string | null): boolean {
|
|
|
826
852
|
|
|
827
853
|
/**
|
|
828
854
|
* Sweep cadence in hours. Env-only (`HARNERY_ARTIFACT_AUTO_CLEAN_INTERVAL_HOURS`,
|
|
829
|
-
* mainly for tests)
|
|
830
|
-
*
|
|
831
|
-
* config surface.
|
|
855
|
+
* mainly for tests). Session starts and new artifact work share the hourly
|
|
856
|
+
* throttle so minute-scale retention is useful within long sessions.
|
|
832
857
|
*/
|
|
833
858
|
export function artifactAutoCleanIntervalHours(): number {
|
|
834
859
|
const env = coordEnv("ARTIFACT_AUTO_CLEAN_INTERVAL_HOURS");
|
|
@@ -836,7 +861,7 @@ export function artifactAutoCleanIntervalHours(): number {
|
|
|
836
861
|
const n = Number(env);
|
|
837
862
|
if (Number.isFinite(n) && n > 0 && n <= 24 * 365) return n;
|
|
838
863
|
}
|
|
839
|
-
return
|
|
864
|
+
return 1;
|
|
840
865
|
}
|
|
841
866
|
|
|
842
867
|
function integerSetting(
|
|
@@ -869,6 +894,10 @@ function booleanSetting(
|
|
|
869
894
|
export interface BackupConfig {
|
|
870
895
|
repo: string;
|
|
871
896
|
passwordFile: string;
|
|
897
|
+
include: readonly string[];
|
|
898
|
+
exclude: readonly string[];
|
|
899
|
+
maxBytes: number;
|
|
900
|
+
schedule: { ifStale: string; tags: readonly string[] } | null;
|
|
872
901
|
keepDaily: number;
|
|
873
902
|
keepWeekly: number;
|
|
874
903
|
keepMonthly: number;
|
|
@@ -878,6 +907,7 @@ export interface BackupConfig {
|
|
|
878
907
|
* `harn backup` (restic) defaults. Per field, precedence is env → config → built-in:
|
|
879
908
|
* repo: `HARNERY_RESTIC_REPO` → `backup.repo` → `~/.cache/harnery/restic-repo`
|
|
880
909
|
* passwordFile: `HARNERY_RESTIC_PASSWORD_FILE` → `backup.password_file` → `~/.config/harnery/restic-password`
|
|
910
|
+
* include/exclude/maxBytes/schedule: `backup.*` → catalog defaults / 50 MiB / disabled
|
|
881
911
|
* keepDaily/Weekly/Monthly: `backup.keep_*` → 7 / 4 / 6
|
|
882
912
|
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
883
913
|
*/
|
|
@@ -889,13 +919,36 @@ export function backupConfig(coordRoot?: string | null): BackupConfig {
|
|
|
889
919
|
typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
890
920
|
const repo =
|
|
891
921
|
coordEnv("RESTIC_REPO") ?? cfgStr(b.repo) ?? join(home, ".cache", "harnery", "restic-repo");
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
922
|
+
const envPasswordFile = coordEnv("RESTIC_PASSWORD_FILE");
|
|
923
|
+
const configuredPasswordFile = cfgStr(b.password_file);
|
|
924
|
+
const passwordFile = envPasswordFile
|
|
925
|
+
? envPasswordFile
|
|
926
|
+
: configuredPasswordFile
|
|
927
|
+
? root && !isAbsolute(configuredPasswordFile)
|
|
928
|
+
? resolve(root, configuredPasswordFile)
|
|
929
|
+
: configuredPasswordFile
|
|
930
|
+
: join(home, ".config", "harnery", "restic-password");
|
|
931
|
+
const stringList = (value: unknown): readonly string[] =>
|
|
932
|
+
Array.isArray(value)
|
|
933
|
+
? value.filter(
|
|
934
|
+
(entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
|
|
935
|
+
)
|
|
936
|
+
: [];
|
|
937
|
+
const schedule = isPlainObject(b.schedule)
|
|
938
|
+
? (() => {
|
|
939
|
+
const ifStale = cfgStr(b.schedule?.if_stale);
|
|
940
|
+
return ifStale
|
|
941
|
+
? { ifStale, tags: stringList(b.schedule?.tags).map((tag) => tag.trim()) }
|
|
942
|
+
: null;
|
|
943
|
+
})()
|
|
944
|
+
: null;
|
|
896
945
|
return {
|
|
897
946
|
repo,
|
|
898
947
|
passwordFile,
|
|
948
|
+
include: stringList(b.include).map((entry) => entry.trim()),
|
|
949
|
+
exclude: stringList(b.exclude).map((entry) => entry.trim()),
|
|
950
|
+
maxBytes: posIntOr(b.max_bytes, 50 * 1_024 * 1_024),
|
|
951
|
+
schedule,
|
|
899
952
|
keepDaily: posIntOr(b.keep_daily, 7),
|
|
900
953
|
keepWeekly: posIntOr(b.keep_weekly, 4),
|
|
901
954
|
keepMonthly: posIntOr(b.keep_monthly, 6),
|
|
@@ -1,147 +1,175 @@
|
|
|
1
|
-
import type { SupervisorCapability,
|
|
1
|
+
import type { SupervisorCapability, SupervisorHistoryPoint } from "../supervisor/contract.ts";
|
|
2
2
|
import {
|
|
3
|
-
DIAGNOSTIC_ADVICE_LIMITS,
|
|
4
3
|
DIAGNOSTIC_ADVICE_SCHEMA_VERSION,
|
|
5
4
|
type DiagnosticAdvice,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
PRESSURE_ASSESSMENT_SCHEMA_VERSION,
|
|
6
|
+
PRESSURE_POLICY,
|
|
7
|
+
type PressureAssessment,
|
|
8
|
+
type PressureDimension,
|
|
9
|
+
type PressureEvidenceDimension,
|
|
10
|
+
type PressureHysteresisState,
|
|
11
|
+
type PressureReason,
|
|
12
|
+
type PressureReasonCode,
|
|
13
|
+
type PressureScope,
|
|
14
|
+
type PressureWorkloadGuidance,
|
|
8
15
|
} from "./contract.ts";
|
|
16
|
+
import type { PressureHistorySample } from "./pressure-contract.ts";
|
|
9
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Advice is an envelope around one already-computed assessment. It maps
|
|
20
|
+
* nothing: severity does not become pressure here, and no numeric threshold is
|
|
21
|
+
* read. `assessPressure` owns the judgement, the observer publishes it, and
|
|
22
|
+
* every surface renders this envelope so all of them agree.
|
|
23
|
+
*/
|
|
10
24
|
export interface BuildDiagnosticAdviceInput {
|
|
11
|
-
|
|
25
|
+
assessment: PressureAssessment;
|
|
26
|
+
/** The state the previous sample carried forward, so a replay can reproduce a transition. */
|
|
27
|
+
priorHysteresis: PressureHysteresisState | null;
|
|
28
|
+
/** Where the assessment came from, and why it is missing when it is. */
|
|
12
29
|
sourceCapability: SupervisorCapability;
|
|
30
|
+
activeFindingCount: number;
|
|
13
31
|
evaluatedAt: string;
|
|
14
32
|
}
|
|
15
33
|
|
|
16
|
-
const SEVERITY_RANK: Readonly<Record<SupervisorFinding["severity"], number>> = {
|
|
17
|
-
info: 0,
|
|
18
|
-
warning: 1,
|
|
19
|
-
critical: 2,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
34
|
export function buildDiagnosticAdvice(input: BuildDiagnosticAdviceInput): DiagnosticAdvice {
|
|
23
|
-
const sourceAvailable = input.sourceCapability.state === "supported";
|
|
24
|
-
const active = sourceAvailable
|
|
25
|
-
? input.findings.filter((finding) => finding.state === "opened").sort(compareFindings)
|
|
26
|
-
: [];
|
|
27
|
-
const pressureFindings = active.filter(
|
|
28
|
-
(finding): finding is SupervisorFinding & { severity: "warning" | "critical" } =>
|
|
29
|
-
finding.severity === "warning" || finding.severity === "critical",
|
|
30
|
-
);
|
|
31
|
-
const critical = pressureFindings.filter((finding) => finding.severity === "critical");
|
|
32
|
-
const warning = pressureFindings.filter((finding) => finding.severity === "warning");
|
|
33
|
-
const pressure = critical.length
|
|
34
|
-
? "critical"
|
|
35
|
-
: warning.length
|
|
36
|
-
? "elevated"
|
|
37
|
-
: sourceAvailable
|
|
38
|
-
? "normal"
|
|
39
|
-
: "unknown";
|
|
40
|
-
const fanOutRecommendation =
|
|
41
|
-
pressure === "critical"
|
|
42
|
-
? "avoid-new-fan-out"
|
|
43
|
-
: pressure === "elevated"
|
|
44
|
-
? "use-caution"
|
|
45
|
-
: pressure === "normal"
|
|
46
|
-
? "proceed"
|
|
47
|
-
: "unknown";
|
|
48
|
-
const contributing = pressureFindings.slice(
|
|
49
|
-
0,
|
|
50
|
-
DIAGNOSTIC_ADVICE_LIMITS.max_contributing_findings,
|
|
51
|
-
);
|
|
52
35
|
return {
|
|
53
36
|
schema_version: DIAGNOSTIC_ADVICE_SCHEMA_VERSION,
|
|
54
37
|
evaluated_at: input.evaluatedAt,
|
|
55
|
-
pressure,
|
|
56
|
-
fan_out_recommendation: fanOutRecommendation,
|
|
57
38
|
observer_only: true,
|
|
58
|
-
|
|
39
|
+
assessment: input.assessment,
|
|
40
|
+
prior_hysteresis: input.priorHysteresis,
|
|
59
41
|
source_capability: input.sourceCapability,
|
|
60
|
-
active_finding_count:
|
|
61
|
-
|
|
62
|
-
omitted_contributing_finding_count: Math.max(0, pressureFindings.length - contributing.length),
|
|
63
|
-
contributing_findings: contributing.map(adviceFinding),
|
|
64
|
-
reasons: reasonsFor(critical, warning, input.sourceCapability),
|
|
42
|
+
active_finding_count: Math.max(0, Math.trunc(input.activeFindingCount)),
|
|
43
|
+
summary: input.assessment.summary,
|
|
65
44
|
};
|
|
66
45
|
}
|
|
67
46
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
owner_kind: finding.attribution?.owner_kind,
|
|
80
|
-
owner_id: finding.attribution?.owner_id,
|
|
81
|
-
workload_relationship: finding.workload_context?.relationship,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
47
|
+
export const PRESSURE_DIMENSIONS: readonly PressureDimension[] = [
|
|
48
|
+
"memory_stall",
|
|
49
|
+
"io_stall",
|
|
50
|
+
"cpu_stall",
|
|
51
|
+
"oom_kills",
|
|
52
|
+
"swap_activity",
|
|
53
|
+
"direct_reclaim",
|
|
54
|
+
"memory_available",
|
|
55
|
+
"disk_available",
|
|
56
|
+
"host_memory",
|
|
57
|
+
];
|
|
84
58
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
finding_ids: boundedIds(critical),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (warning.length) {
|
|
99
|
-
reasons.push({
|
|
100
|
-
code: "warning_findings_active",
|
|
101
|
-
summary: `${warning.length} active warning finding${warning.length === 1 ? "" : "s"} ${warning.length === 1 ? "indicates" : "indicate"} elevated pressure.`,
|
|
102
|
-
finding_ids: boundedIds(warning),
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
if (capability.state !== "supported") {
|
|
106
|
-
reasons.push({
|
|
107
|
-
code: "findings_source_unavailable",
|
|
108
|
-
summary: `The findings source is ${capability.state}${capability.reason_code ? ` (${capability.reason_code})` : ""}.`,
|
|
109
|
-
finding_ids: [],
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (!critical.length && !warning.length && capability.state === "supported") {
|
|
113
|
-
reasons.push({
|
|
114
|
-
code: "no_active_pressure_findings",
|
|
115
|
-
summary: "No active warning or critical findings were observed.",
|
|
116
|
-
finding_ids: [],
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return reasons.slice(0, DIAGNOSTIC_ADVICE_LIMITS.max_reasons);
|
|
59
|
+
export interface UnknownPressureAssessmentInput {
|
|
60
|
+
observedAt: string;
|
|
61
|
+
/** A stable code from the frozen union, normally `evidence_unavailable` or `snapshot_stale`. */
|
|
62
|
+
reasonCode: PressureReasonCode;
|
|
63
|
+
/** One plain sentence naming what is missing. Becomes the assessment summary. */
|
|
64
|
+
summary: string;
|
|
65
|
+
scope?: PressureScope;
|
|
66
|
+
sampleAgeMs?: number | null;
|
|
67
|
+
prior?: PressureHysteresisState | null;
|
|
68
|
+
observerGeneration?: string | null;
|
|
120
69
|
}
|
|
121
70
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The assessment to publish when the source is absent, stale, or malformed.
|
|
73
|
+
* Every dimension reads `unavailable` and no owner is named, because a gap in
|
|
74
|
+
* the evidence is not a measurement of health.
|
|
75
|
+
*/
|
|
76
|
+
export function unknownPressureAssessment(
|
|
77
|
+
input: UnknownPressureAssessmentInput,
|
|
78
|
+
): PressureAssessment {
|
|
79
|
+
const reason: PressureReason = {
|
|
80
|
+
code: input.reasonCode,
|
|
81
|
+
dimension: null,
|
|
82
|
+
summary: input.summary,
|
|
83
|
+
observed_value: null,
|
|
84
|
+
threshold_value: null,
|
|
85
|
+
unit: null,
|
|
86
|
+
sample_count: 0,
|
|
87
|
+
contributes_to: "unknown",
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
schema_version: PRESSURE_ASSESSMENT_SCHEMA_VERSION,
|
|
91
|
+
observer_only: true,
|
|
92
|
+
state: "unknown",
|
|
93
|
+
scope: input.scope ?? "native",
|
|
94
|
+
limiting_resource: "unknown",
|
|
95
|
+
trend: "unknown",
|
|
96
|
+
observed_at: input.observedAt,
|
|
97
|
+
sample_age_ms: input.sampleAgeMs ?? null,
|
|
98
|
+
evidence_state: "unavailable",
|
|
99
|
+
evidence: PRESSURE_DIMENSIONS.map(
|
|
100
|
+
(dimension): PressureEvidenceDimension => ({
|
|
101
|
+
dimension,
|
|
102
|
+
state: "unavailable",
|
|
103
|
+
observed_value: null,
|
|
104
|
+
unit: null,
|
|
105
|
+
sample_count: 0,
|
|
106
|
+
reason_code: input.reasonCode,
|
|
107
|
+
}),
|
|
108
|
+
).slice(0, PRESSURE_POLICY.limits.max_evidence),
|
|
109
|
+
reasons: [reason],
|
|
110
|
+
contributors: [],
|
|
111
|
+
omitted_contributor_count: 0,
|
|
112
|
+
unattributed_memory_percent: null,
|
|
113
|
+
recommended_action: "unknown",
|
|
114
|
+
summary: input.summary,
|
|
115
|
+
guidance: unknownGuidance(),
|
|
116
|
+
hysteresis: {
|
|
117
|
+
state: "unknown",
|
|
118
|
+
state_since: input.observedAt,
|
|
119
|
+
consecutive_clear_samples: 0,
|
|
120
|
+
dimension_streaks: {},
|
|
121
|
+
oom_baseline_total_kills: input.prior?.oom_baseline_total_kills ?? null,
|
|
122
|
+
oom_hold_until: null,
|
|
123
|
+
observer_generation: input.observerGeneration ?? input.prior?.observer_generation ?? null,
|
|
124
|
+
},
|
|
125
|
+
policy_version: PRESSURE_POLICY.policy_version,
|
|
126
|
+
};
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
function unknownGuidance(): PressureWorkloadGuidance[] {
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
workload_class: "lightweight",
|
|
133
|
+
recommendation: "unknown",
|
|
134
|
+
summary:
|
|
135
|
+
"Reads and small edits cost almost nothing, so continue them while the measurements are missing.",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
workload_class: "cpu-heavy",
|
|
139
|
+
recommendation: "unknown",
|
|
140
|
+
summary:
|
|
141
|
+
"Builds and test runs cannot be judged from here. Start one only if you already know the machine is idle.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
workload_class: "memory-heavy",
|
|
145
|
+
recommendation: "unknown",
|
|
146
|
+
summary:
|
|
147
|
+
"Browser captures and page QA cannot be judged from here. Run one at a time until the measurements return.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
workload_class: "storage-heavy",
|
|
151
|
+
recommendation: "unknown",
|
|
152
|
+
summary:
|
|
153
|
+
"Large writes and exports cannot be judged from here. Check free disk space yourself before starting one.",
|
|
154
|
+
},
|
|
155
|
+
];
|
|
139
156
|
}
|
|
140
157
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Project bounded supervisor history into the trend input the assessment reads.
|
|
160
|
+
* The observer and a bundle replay both call this, so the trend they compute
|
|
161
|
+
* from the same stored history is the same trend.
|
|
162
|
+
*/
|
|
163
|
+
export function pressureHistoryFromSupervisor(
|
|
164
|
+
points: readonly SupervisorHistoryPoint[],
|
|
165
|
+
): PressureHistorySample[] {
|
|
166
|
+
return points.slice(-PRESSURE_POLICY.max_history_samples).map((point) => ({
|
|
167
|
+
sampled_at: point.sampled_at,
|
|
168
|
+
memory_full_avg10: point.pressure.memory_full_avg10,
|
|
169
|
+
io_full_avg10: point.pressure.io_full_avg10,
|
|
170
|
+
cpu_some_avg60: point.pressure.cpu_some_avg60,
|
|
171
|
+
memory_available_percent:
|
|
172
|
+
point.machine.memory_percent === null ? null : 100 - point.machine.memory_percent,
|
|
173
|
+
swap_out_bytes_per_second: point.pressure.swap_out_bytes_per_second,
|
|
174
|
+
}));
|
|
147
175
|
}
|