@unerr-ai/unerr 0.2.1 → 0.2.3
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 +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* System health probes — power & load awareness for warm-start scheduling.
|
|
3
|
-
*
|
|
4
|
-
* Cross-platform:
|
|
5
|
-
* macOS: `pmset -g batt` for battery, os.loadavg() for load
|
|
6
|
-
* Linux: /sys/class/power_supply/ for battery, os.loadavg() for load
|
|
7
|
-
* Windows: wmic for battery, CPU idle delta sampling for load
|
|
8
|
-
*
|
|
9
|
-
* Results are cached for 10 seconds to avoid repeated system calls.
|
|
10
|
-
*/
|
|
11
|
-
import { execSync } from "node:child_process";
|
|
12
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
13
|
-
import { cpus, loadavg, platform } from "node:os";
|
|
14
|
-
const CACHE_TTL_MS = 10_000;
|
|
15
|
-
let batteryCache = null;
|
|
16
|
-
let loadCache = null;
|
|
17
|
-
export function onBattery() {
|
|
18
|
-
const now = Date.now();
|
|
19
|
-
if (batteryCache && now - batteryCache.ts < CACHE_TTL_MS) {
|
|
20
|
-
return batteryCache.value;
|
|
21
|
-
}
|
|
22
|
-
const result = detectBattery();
|
|
23
|
-
batteryCache = { value: result, ts: now };
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
function detectBattery() {
|
|
27
|
-
const plat = platform();
|
|
28
|
-
if (plat === "darwin")
|
|
29
|
-
return detectBatteryMacOS();
|
|
30
|
-
if (plat === "linux")
|
|
31
|
-
return detectBatteryLinux();
|
|
32
|
-
if (plat === "win32")
|
|
33
|
-
return detectBatteryWindows();
|
|
34
|
-
return false; // Unknown platform — assume plugged in
|
|
35
|
-
}
|
|
36
|
-
function detectBatteryMacOS() {
|
|
37
|
-
try {
|
|
38
|
-
const out = execSync("pmset -g batt", {
|
|
39
|
-
encoding: "utf-8",
|
|
40
|
-
timeout: 3000,
|
|
41
|
-
});
|
|
42
|
-
// "Now drawing from 'Battery Power'" means on battery
|
|
43
|
-
// "Now drawing from 'AC Power'" means plugged in
|
|
44
|
-
// No battery line at all means desktop Mac
|
|
45
|
-
if (out.includes("'Battery Power'"))
|
|
46
|
-
return true;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function detectBatteryLinux() {
|
|
54
|
-
try {
|
|
55
|
-
const psDir = "/sys/class/power_supply";
|
|
56
|
-
if (!existsSync(psDir))
|
|
57
|
-
return false;
|
|
58
|
-
const supplies = readdirSync(psDir);
|
|
59
|
-
for (const name of supplies) {
|
|
60
|
-
// AC adapters are named AC*, ADP*, etc.
|
|
61
|
-
if (name.startsWith("AC") || name.startsWith("ADP")) {
|
|
62
|
-
const onlinePath = `${psDir}/${name}/online`;
|
|
63
|
-
if (existsSync(onlinePath)) {
|
|
64
|
-
const online = readFileSync(onlinePath, "utf-8").trim();
|
|
65
|
-
if (online === "0")
|
|
66
|
-
return true; // AC disconnected
|
|
67
|
-
if (online === "1")
|
|
68
|
-
return false; // AC connected
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function detectBatteryWindows() {
|
|
79
|
-
try {
|
|
80
|
-
const out = execSync("wmic path Win32_Battery get BatteryStatus /value", {
|
|
81
|
-
encoding: "utf-8",
|
|
82
|
-
timeout: 5000,
|
|
83
|
-
});
|
|
84
|
-
// BatteryStatus=1 means discharging (on battery)
|
|
85
|
-
// BatteryStatus=2 means AC connected
|
|
86
|
-
const match = out.match(/BatteryStatus=(\d+)/);
|
|
87
|
-
if (match)
|
|
88
|
-
return match[1] === "1";
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// ── Load average ──────────────────────────────────────────────
|
|
96
|
-
export function loadAverage1() {
|
|
97
|
-
const now = Date.now();
|
|
98
|
-
if (loadCache && now - loadCache.ts < CACHE_TTL_MS) {
|
|
99
|
-
return loadCache.value;
|
|
100
|
-
}
|
|
101
|
-
const result = detectLoad();
|
|
102
|
-
loadCache = { value: result, ts: now };
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
function detectLoad() {
|
|
106
|
-
if (platform() === "win32")
|
|
107
|
-
return windowsLoadEstimate();
|
|
108
|
-
return loadavg()[0] ?? 0;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Estimate 1-minute load average on Windows using a 1s CPU idle delta.
|
|
112
|
-
* This is a rough approximation — Windows doesn't expose loadavg natively.
|
|
113
|
-
*/
|
|
114
|
-
function windowsLoadEstimate() {
|
|
115
|
-
const cores = cpus();
|
|
116
|
-
const total = cores.length;
|
|
117
|
-
if (total === 0)
|
|
118
|
-
return 0;
|
|
119
|
-
let totalIdle = 0;
|
|
120
|
-
let totalTick = 0;
|
|
121
|
-
for (const cpu of cores) {
|
|
122
|
-
const { user, nice, sys, idle, irq } = cpu.times;
|
|
123
|
-
totalTick += user + nice + sys + idle + irq;
|
|
124
|
-
totalIdle += idle;
|
|
125
|
-
}
|
|
126
|
-
// Rough estimate: (busy fraction) * core count ≈ load
|
|
127
|
-
const busy = 1 - totalIdle / totalTick;
|
|
128
|
-
return Math.max(0, busy * total);
|
|
129
|
-
}
|
|
130
|
-
/** Reset caches (for testing). */
|
|
131
|
-
export function resetHealthCaches() {
|
|
132
|
-
batteryCache = null;
|
|
133
|
-
loadCache = null;
|
|
134
|
-
}
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Version checker — daily npm registry check + local cache.
|
|
3
|
-
*
|
|
4
|
-
* Flow:
|
|
5
|
-
* 1. On daemon boot (and every 24h thereafter), HEAD the npm registry
|
|
6
|
-
* 2. Compare latest vs installed version
|
|
7
|
-
* 3. Cache result to ~/.unerr/version.json
|
|
8
|
-
* 4. Expose shouldNotify() for surfaces (CLI banner, dashboard, MCP _meta)
|
|
9
|
-
*
|
|
10
|
-
* Design: notify, never auto-apply. Users run `unerr daemon update` explicitly.
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { get as httpsGet } from "node:https";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
// ── Constants ─────────────────────────────────────────────────
|
|
17
|
-
const PACKAGE_NAME = "unerr";
|
|
18
|
-
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
19
|
-
const DEFAULT_CHECK_INTERVAL_S = 86_400; // 24 hours
|
|
20
|
-
const REQUEST_TIMEOUT_MS = 10_000;
|
|
21
|
-
function versionCachePath() {
|
|
22
|
-
return join(homedir(), ".unerr", "version.json");
|
|
23
|
-
}
|
|
24
|
-
// ── Installed version ─────────────────────────────────────────
|
|
25
|
-
let cachedInstalledVersion = null;
|
|
26
|
-
export function getInstalledVersion() {
|
|
27
|
-
if (cachedInstalledVersion)
|
|
28
|
-
return cachedInstalledVersion;
|
|
29
|
-
try {
|
|
30
|
-
const pkgPath = join(__dirname, "../../package.json");
|
|
31
|
-
if (existsSync(pkgPath)) {
|
|
32
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
33
|
-
cachedInstalledVersion = pkg.version;
|
|
34
|
-
return pkg.version;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Fall through
|
|
39
|
-
}
|
|
40
|
-
// Fallback: try to resolve from process.argv
|
|
41
|
-
try {
|
|
42
|
-
const binDir = join(process.argv[1] || "", "..");
|
|
43
|
-
const pkgPath = join(binDir, "../package.json");
|
|
44
|
-
if (existsSync(pkgPath)) {
|
|
45
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
46
|
-
cachedInstalledVersion = pkg.version;
|
|
47
|
-
return pkg.version;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Fall through
|
|
52
|
-
}
|
|
53
|
-
cachedInstalledVersion = "0.0.1";
|
|
54
|
-
return "0.0.1";
|
|
55
|
-
}
|
|
56
|
-
// ── Cache read/write ──────────────────────────────────────────
|
|
57
|
-
export function readVersionCache() {
|
|
58
|
-
const defaults = {
|
|
59
|
-
lastChecked: "",
|
|
60
|
-
latestVersion: "",
|
|
61
|
-
installedVersion: getInstalledVersion(),
|
|
62
|
-
dismissed: [],
|
|
63
|
-
checkInterval: DEFAULT_CHECK_INTERVAL_S,
|
|
64
|
-
};
|
|
65
|
-
try {
|
|
66
|
-
const path = versionCachePath();
|
|
67
|
-
if (!existsSync(path))
|
|
68
|
-
return defaults;
|
|
69
|
-
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
70
|
-
return {
|
|
71
|
-
lastChecked: raw.lastChecked ?? defaults.lastChecked,
|
|
72
|
-
latestVersion: raw.latestVersion ?? defaults.latestVersion,
|
|
73
|
-
installedVersion: getInstalledVersion(),
|
|
74
|
-
dismissed: Array.isArray(raw.dismissed) ? raw.dismissed : [],
|
|
75
|
-
checkInterval: typeof raw.checkInterval === "number"
|
|
76
|
-
? raw.checkInterval
|
|
77
|
-
: defaults.checkInterval,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return defaults;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export function writeVersionCache(cache) {
|
|
85
|
-
const dir = join(homedir(), ".unerr");
|
|
86
|
-
mkdirSync(dir, { recursive: true });
|
|
87
|
-
writeFileSync(versionCachePath(), JSON.stringify(cache, null, 2), "utf-8");
|
|
88
|
-
}
|
|
89
|
-
function parseSemVer(version) {
|
|
90
|
-
const clean = version.replace(/^v/, "");
|
|
91
|
-
const parts = clean.split(".");
|
|
92
|
-
if (parts.length < 3)
|
|
93
|
-
return null;
|
|
94
|
-
const major = Number.parseInt(parts[0], 10);
|
|
95
|
-
const minor = Number.parseInt(parts[1], 10);
|
|
96
|
-
const patch = Number.parseInt(parts[2]?.split("-")[0], 10);
|
|
97
|
-
if (!Number.isFinite(major) ||
|
|
98
|
-
!Number.isFinite(minor) ||
|
|
99
|
-
!Number.isFinite(patch))
|
|
100
|
-
return null;
|
|
101
|
-
return { major, minor, patch };
|
|
102
|
-
}
|
|
103
|
-
export function isNewer(latest, current) {
|
|
104
|
-
const l = parseSemVer(latest);
|
|
105
|
-
const c = parseSemVer(current);
|
|
106
|
-
if (!l || !c)
|
|
107
|
-
return false;
|
|
108
|
-
if (l.major !== c.major)
|
|
109
|
-
return l.major > c.major;
|
|
110
|
-
if (l.minor !== c.minor)
|
|
111
|
-
return l.minor > c.minor;
|
|
112
|
-
return l.patch > c.patch;
|
|
113
|
-
}
|
|
114
|
-
export function minorsBehind(latest, current) {
|
|
115
|
-
const l = parseSemVer(latest);
|
|
116
|
-
const c = parseSemVer(current);
|
|
117
|
-
if (!l || !c)
|
|
118
|
-
return 0;
|
|
119
|
-
if (l.major > c.major)
|
|
120
|
-
return (l.major - c.major) * 10 + l.minor;
|
|
121
|
-
if (l.major < c.major)
|
|
122
|
-
return 0;
|
|
123
|
-
return Math.max(0, l.minor - c.minor);
|
|
124
|
-
}
|
|
125
|
-
// ── Registry fetch ────────────────────────────────────────────
|
|
126
|
-
export function fetchLatestVersion() {
|
|
127
|
-
return new Promise((resolve) => {
|
|
128
|
-
const req = httpsGet(REGISTRY_URL, { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS }, (res) => {
|
|
129
|
-
if (res.statusCode !== 200) {
|
|
130
|
-
res.resume();
|
|
131
|
-
resolve(null);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
let body = "";
|
|
135
|
-
res.on("data", (chunk) => {
|
|
136
|
-
body += chunk.toString();
|
|
137
|
-
});
|
|
138
|
-
res.on("end", () => {
|
|
139
|
-
try {
|
|
140
|
-
const data = JSON.parse(body);
|
|
141
|
-
resolve(data.version ?? null);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
resolve(null);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
req.on("error", () => resolve(null));
|
|
149
|
-
req.on("timeout", () => {
|
|
150
|
-
req.destroy();
|
|
151
|
-
resolve(null);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// ── Check logic ───────────────────────────────────────────────
|
|
156
|
-
function shouldCheck(cache) {
|
|
157
|
-
if (!cache.lastChecked)
|
|
158
|
-
return true;
|
|
159
|
-
const lastMs = new Date(cache.lastChecked).getTime();
|
|
160
|
-
if (Number.isNaN(lastMs))
|
|
161
|
-
return true;
|
|
162
|
-
const elapsed = (Date.now() - lastMs) / 1000;
|
|
163
|
-
return elapsed >= cache.checkInterval;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Perform a version check if due. Returns the current update info.
|
|
167
|
-
* Safe to call frequently — respects the check interval.
|
|
168
|
-
*/
|
|
169
|
-
export async function checkForUpdate() {
|
|
170
|
-
const cache = readVersionCache();
|
|
171
|
-
const current = getInstalledVersion();
|
|
172
|
-
if (shouldCheck(cache)) {
|
|
173
|
-
const latest = await fetchLatestVersion();
|
|
174
|
-
if (latest) {
|
|
175
|
-
cache.latestVersion = latest;
|
|
176
|
-
cache.lastChecked = new Date().toISOString();
|
|
177
|
-
cache.installedVersion = current;
|
|
178
|
-
writeVersionCache(cache);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const latest = cache.latestVersion || current;
|
|
182
|
-
const available = isNewer(latest, current);
|
|
183
|
-
const dismissed = cache.dismissed.includes(latest);
|
|
184
|
-
return {
|
|
185
|
-
available,
|
|
186
|
-
current,
|
|
187
|
-
latest,
|
|
188
|
-
behindMinor: minorsBehind(latest, current),
|
|
189
|
-
dismissed,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Get update info from cache only (no network request).
|
|
194
|
-
* Use this for synchronous surfaces like CLI banners.
|
|
195
|
-
*/
|
|
196
|
-
export function getCachedUpdateInfo() {
|
|
197
|
-
const cache = readVersionCache();
|
|
198
|
-
const current = getInstalledVersion();
|
|
199
|
-
const latest = cache.latestVersion || current;
|
|
200
|
-
const available = isNewer(latest, current);
|
|
201
|
-
const dismissed = cache.dismissed.includes(latest);
|
|
202
|
-
return {
|
|
203
|
-
available,
|
|
204
|
-
current,
|
|
205
|
-
latest,
|
|
206
|
-
behindMinor: minorsBehind(latest, current),
|
|
207
|
-
dismissed,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Whether to show update notification — respects dismissal.
|
|
212
|
-
*/
|
|
213
|
-
export function shouldNotify() {
|
|
214
|
-
const info = getCachedUpdateInfo();
|
|
215
|
-
return info.available && !info.dismissed;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Dismiss notifications for a specific version.
|
|
219
|
-
*/
|
|
220
|
-
export function dismissVersion(version) {
|
|
221
|
-
const cache = readVersionCache();
|
|
222
|
-
const clean = version.replace(/^v/, "");
|
|
223
|
-
if (!cache.dismissed.includes(clean)) {
|
|
224
|
-
cache.dismissed.push(clean);
|
|
225
|
-
writeVersionCache(cache);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Set check interval (in seconds). 0 disables checks.
|
|
230
|
-
*/
|
|
231
|
-
export function setCheckInterval(seconds) {
|
|
232
|
-
const cache = readVersionCache();
|
|
233
|
-
cache.checkInterval = seconds;
|
|
234
|
-
writeVersionCache(cache);
|
|
235
|
-
}
|
|
236
|
-
// ── Daemon integration ────────────────────────────────────────
|
|
237
|
-
let checkTimer = null;
|
|
238
|
-
/**
|
|
239
|
-
* Start periodic version checking (called by unerrd on boot).
|
|
240
|
-
* Returns a cancel function.
|
|
241
|
-
*/
|
|
242
|
-
export function startPeriodicCheck() {
|
|
243
|
-
// Initial check (deferred 5s to not block boot)
|
|
244
|
-
const initialTimer = setTimeout(() => {
|
|
245
|
-
checkForUpdate().catch(() => { });
|
|
246
|
-
}, 5000);
|
|
247
|
-
initialTimer.unref();
|
|
248
|
-
// Periodic check every checkInterval
|
|
249
|
-
const cache = readVersionCache();
|
|
250
|
-
const intervalMs = (cache.checkInterval || DEFAULT_CHECK_INTERVAL_S) * 1000;
|
|
251
|
-
checkTimer = setInterval(() => {
|
|
252
|
-
checkForUpdate().catch(() => { });
|
|
253
|
-
}, intervalMs);
|
|
254
|
-
checkTimer.unref();
|
|
255
|
-
return () => {
|
|
256
|
-
clearTimeout(initialTimer);
|
|
257
|
-
if (checkTimer) {
|
|
258
|
-
clearInterval(checkTimer);
|
|
259
|
-
checkTimer = null;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Warm-start scheduler — pre-spawn MRU repos after daemon boot.
|
|
3
|
-
*
|
|
4
|
-
* Reads repos from the registry, sorts by lastActivity (MRU first),
|
|
5
|
-
* respects per-repo autostart policy and global budget. Spawns
|
|
6
|
-
* sequentially at low priority. Aborts if load jumps too high or
|
|
7
|
-
* system is on battery.
|
|
8
|
-
*
|
|
9
|
-
* Events emitted for dashboard timeline:
|
|
10
|
-
* { type: "warm_start", repo, status: "started"|"skipped"|"failed"|"aborted", ms, reason? }
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { isCI } from "./detect-ci.js";
|
|
16
|
-
import { readRegistry } from "./registry.js";
|
|
17
|
-
import { loadAverage1, onBattery } from "./system-health.js";
|
|
18
|
-
const DEFAULT_CONFIG = {
|
|
19
|
-
warmStartBudget: 3,
|
|
20
|
-
warmStartIdleDays: 14,
|
|
21
|
-
warmStartDelayMs: 30_000,
|
|
22
|
-
};
|
|
23
|
-
export function loadWarmStartConfig() {
|
|
24
|
-
const configPath = join(homedir(), ".unerr", "config.json");
|
|
25
|
-
try {
|
|
26
|
-
if (!existsSync(configPath))
|
|
27
|
-
return { ...DEFAULT_CONFIG };
|
|
28
|
-
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
29
|
-
return {
|
|
30
|
-
warmStartBudget: typeof raw.warmStartBudget === "number"
|
|
31
|
-
? raw.warmStartBudget
|
|
32
|
-
: DEFAULT_CONFIG.warmStartBudget,
|
|
33
|
-
warmStartIdleDays: typeof raw.warmStartIdleDays === "number"
|
|
34
|
-
? raw.warmStartIdleDays
|
|
35
|
-
: DEFAULT_CONFIG.warmStartIdleDays,
|
|
36
|
-
warmStartDelayMs: typeof raw.warmStartDelayMs === "number"
|
|
37
|
-
? raw.warmStartDelayMs
|
|
38
|
-
: DEFAULT_CONFIG.warmStartDelayMs,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return { ...DEFAULT_CONFIG };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
export function saveWarmStartConfig(partial) {
|
|
46
|
-
const configPath = join(homedir(), ".unerr", "config.json");
|
|
47
|
-
const dir = join(homedir(), ".unerr");
|
|
48
|
-
mkdirSync(dir, { recursive: true });
|
|
49
|
-
let existing = {};
|
|
50
|
-
try {
|
|
51
|
-
if (existsSync(configPath)) {
|
|
52
|
-
existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// Corrupted — reset
|
|
57
|
-
}
|
|
58
|
-
const merged = { ...existing, ...partial };
|
|
59
|
-
writeFileSync(configPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Select repos eligible for warm-start, sorted by MRU.
|
|
63
|
-
* Respects per-repo `autostart` policy and `warmStartIdleDays` cutoff.
|
|
64
|
-
*/
|
|
65
|
-
export function selectCandidates(repos, config) {
|
|
66
|
-
const now = Date.now();
|
|
67
|
-
const idleCutoff = now - config.warmStartIdleDays * 24 * 60 * 60 * 1000;
|
|
68
|
-
const candidates = [];
|
|
69
|
-
const skipped = [];
|
|
70
|
-
for (const entry of repos) {
|
|
71
|
-
const autostart = entry.settings?.autostart ?? "auto";
|
|
72
|
-
if (autostart === "never") {
|
|
73
|
-
skipped.push({ entry, reason: "autostart=never" });
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (!existsSync(entry.path)) {
|
|
77
|
-
skipped.push({ entry, reason: "directory not found" });
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const lastTs = entry.lastActivity
|
|
81
|
-
? new Date(entry.lastActivity).getTime()
|
|
82
|
-
: 0;
|
|
83
|
-
if (autostart === "auto" && lastTs < idleCutoff && lastTs > 0) {
|
|
84
|
-
skipped.push({
|
|
85
|
-
entry,
|
|
86
|
-
reason: `inactive >${config.warmStartIdleDays} days`,
|
|
87
|
-
});
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
candidates.push({ entry, lastActivityTs: lastTs });
|
|
91
|
-
}
|
|
92
|
-
// Eager repos first, then sort by MRU within each tier
|
|
93
|
-
candidates.sort((a, b) => {
|
|
94
|
-
const aEager = a.entry.settings?.autostart === "eager" ? 0 : 1;
|
|
95
|
-
const bEager = b.entry.settings?.autostart === "eager" ? 0 : 1;
|
|
96
|
-
if (aEager !== bEager)
|
|
97
|
-
return aEager - bEager;
|
|
98
|
-
return b.lastActivityTs - a.lastActivityTs;
|
|
99
|
-
});
|
|
100
|
-
return { candidates, skipped };
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Run the warm-start sweep. Called by daemon entrypoint after boot delay.
|
|
104
|
-
*
|
|
105
|
-
* Spawns repos sequentially through ProcessManager.ensure().
|
|
106
|
-
* Aborts if load average jumps >1.5× baseline.
|
|
107
|
-
*/
|
|
108
|
-
export async function runWarmStart(pm, onEvent) {
|
|
109
|
-
const config = loadWarmStartConfig();
|
|
110
|
-
const result = {
|
|
111
|
-
started: [],
|
|
112
|
-
skipped: [],
|
|
113
|
-
aborted: false,
|
|
114
|
-
totalMs: 0,
|
|
115
|
-
};
|
|
116
|
-
const overallStart = Date.now();
|
|
117
|
-
if (config.warmStartBudget <= 0)
|
|
118
|
-
return result;
|
|
119
|
-
if (isCI())
|
|
120
|
-
return result;
|
|
121
|
-
const registry = readRegistry();
|
|
122
|
-
const { candidates, skipped } = selectCandidates(registry.repos, config);
|
|
123
|
-
for (const s of skipped) {
|
|
124
|
-
result.skipped.push({ label: s.entry.label, reason: s.reason });
|
|
125
|
-
onEvent?.({
|
|
126
|
-
type: "warm_start",
|
|
127
|
-
repo: s.entry.path,
|
|
128
|
-
label: s.entry.label,
|
|
129
|
-
status: "skipped",
|
|
130
|
-
ms: 0,
|
|
131
|
-
reason: s.reason,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
const baselineLoad = loadAverage1();
|
|
135
|
-
const toWarm = candidates.slice(0, config.warmStartBudget);
|
|
136
|
-
for (const c of toWarm) {
|
|
137
|
-
// Check system health before each spawn
|
|
138
|
-
if (onBattery()) {
|
|
139
|
-
result.aborted = true;
|
|
140
|
-
onEvent?.({
|
|
141
|
-
type: "warm_start",
|
|
142
|
-
repo: c.entry.path,
|
|
143
|
-
label: c.entry.label,
|
|
144
|
-
status: "aborted",
|
|
145
|
-
ms: 0,
|
|
146
|
-
reason: "on battery",
|
|
147
|
-
});
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
const currentLoad = loadAverage1();
|
|
151
|
-
if (baselineLoad > 0 && currentLoad > baselineLoad * 1.5) {
|
|
152
|
-
result.aborted = true;
|
|
153
|
-
onEvent?.({
|
|
154
|
-
type: "warm_start",
|
|
155
|
-
repo: c.entry.path,
|
|
156
|
-
label: c.entry.label,
|
|
157
|
-
status: "aborted",
|
|
158
|
-
ms: 0,
|
|
159
|
-
reason: `load ${currentLoad.toFixed(1)} > 1.5× baseline ${baselineLoad.toFixed(1)}`,
|
|
160
|
-
});
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
const start = Date.now();
|
|
164
|
-
try {
|
|
165
|
-
await pm.ensure(c.entry.path);
|
|
166
|
-
const ms = Date.now() - start;
|
|
167
|
-
result.started.push(c.entry.label);
|
|
168
|
-
onEvent?.({
|
|
169
|
-
type: "warm_start",
|
|
170
|
-
repo: c.entry.path,
|
|
171
|
-
label: c.entry.label,
|
|
172
|
-
status: "started",
|
|
173
|
-
ms,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
const ms = Date.now() - start;
|
|
178
|
-
onEvent?.({
|
|
179
|
-
type: "warm_start",
|
|
180
|
-
repo: c.entry.path,
|
|
181
|
-
label: c.entry.label,
|
|
182
|
-
status: "failed",
|
|
183
|
-
ms,
|
|
184
|
-
reason: err.message,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// Mark remaining candidates beyond budget as skipped
|
|
189
|
-
for (const c of candidates.slice(config.warmStartBudget)) {
|
|
190
|
-
result.skipped.push({ label: c.entry.label, reason: "beyond budget" });
|
|
191
|
-
onEvent?.({
|
|
192
|
-
type: "warm_start",
|
|
193
|
-
repo: c.entry.path,
|
|
194
|
-
label: c.entry.label,
|
|
195
|
-
status: "skipped",
|
|
196
|
-
ms: 0,
|
|
197
|
-
reason: `beyond budget (${config.warmStartBudget})`,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
result.totalMs = Date.now() - overallStart;
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Schedule the warm-start sweep after the configured delay.
|
|
205
|
-
* Returns a cancel function.
|
|
206
|
-
*/
|
|
207
|
-
export function scheduleWarmStart(pm, onEvent) {
|
|
208
|
-
if (isCI())
|
|
209
|
-
return () => { };
|
|
210
|
-
const config = loadWarmStartConfig();
|
|
211
|
-
if (config.warmStartBudget <= 0)
|
|
212
|
-
return () => { };
|
|
213
|
-
const timer = setTimeout(async () => {
|
|
214
|
-
try {
|
|
215
|
-
await runWarmStart(pm, onEvent);
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
// Warm-start failure is non-fatal
|
|
219
|
-
}
|
|
220
|
-
}, config.warmStartDelayMs);
|
|
221
|
-
timer.unref();
|
|
222
|
-
return () => clearTimeout(timer);
|
|
223
|
-
}
|