@bli-cockpit/cli 0.2.281 → 0.2.283
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/autostart-contract.js +10 -0
- package/dist/autostart-memory-guard.js +275 -0
- package/dist/autostart-self-heal.js +15 -1
- package/dist/autostart.js +9 -4
- package/dist/commands/autostart-command.js +15 -0
- package/dist/commands/autostart-heal.js +36 -1
- package/dist/commands/autostart-reading.js +17 -3
- package/dist/commands/local-args-tower-notes.js +25 -1
- package/dist/commands/local-help-notes.js +2 -1
- package/dist/commands/notes-review.js +204 -0
- package/dist/commands/notes.js +4 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/sync-followups-memory-guard.js +13 -0
- package/dist/commands/sync.js +3 -0
- package/dist/memory-guard-contract.js +121 -0
- package/dist/memory-guard-forward.js +82 -0
- package/dist/memory-guard-script-darwin.js +128 -0
- package/dist/memory-guard-script-windows.js +144 -0
- package/package.json +2 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { MEMORY_GUARD_KIND_PATTERNS, MEMORY_GUARD_LOG_KEEP_LINES, MEMORY_GUARD_LOG_LIMIT_BYTES, MEMORY_GUARD_ORPHAN_MIN_AGE_SECONDS, } from "./memory-guard-contract.js";
|
|
2
|
+
import { powershellLiteral } from "./autostart-windows-scripts.js";
|
|
3
|
+
/**
|
|
4
|
+
* The Windows memory guard (BLI-23571): a resident PowerShell loop behind the same hidden wscript launcher
|
|
5
|
+
* the sync job uses. Windows has no `top -o mem` footprint, so the analogue is PrivateMemorySize64 (the
|
|
6
|
+
* commit charge, which counts pages swapped to the pagefile). Pressure comes from free physical memory:
|
|
7
|
+
* under 10% free is a warning, under 5% is critical, read at most every 10 s. Only `node` processes are
|
|
8
|
+
* touched, never the guard's own PID. Orphaned vitest workers (parent PID no longer alive, command line
|
|
9
|
+
* contains `vitest`, older than 5 minutes) are reaped once a minute through Win32_Process.
|
|
10
|
+
*
|
|
11
|
+
* Same log contract as the macOS guard: one JSON line per decision, a kind label and a sha256 prefix of
|
|
12
|
+
* the command line, never the command line. NOT proven on a real Windows host from this repo's CI: the
|
|
13
|
+
* rendering is tested, the live behaviour is not.
|
|
14
|
+
*/
|
|
15
|
+
export function renderWindowsMemoryGuardScript(options) {
|
|
16
|
+
const kindChecks = MEMORY_GUARD_KIND_PATTERNS.map(([kind, pattern]) => ` if ($Command -match ${powershellLiteral(pattern)}) { return '${kind}' }`);
|
|
17
|
+
return [
|
|
18
|
+
"$ErrorActionPreference = 'Continue'",
|
|
19
|
+
`$limitMb = ${options.limits.hard}`,
|
|
20
|
+
`$warnLimitMb = ${options.limits.warning}`,
|
|
21
|
+
`$criticalLimitMb = ${options.limits.critical}`,
|
|
22
|
+
`$orphanMinAgeSeconds = ${MEMORY_GUARD_ORPHAN_MIN_AGE_SECONDS}`,
|
|
23
|
+
`$logPath = ${powershellLiteral(options.logPath)}`,
|
|
24
|
+
`$logLimitBytes = ${MEMORY_GUARD_LOG_LIMIT_BYTES}`,
|
|
25
|
+
`$logKeepLines = ${MEMORY_GUARD_LOG_KEEP_LINES}`,
|
|
26
|
+
"# One resident copy per user session; a second instance leaves quietly.",
|
|
27
|
+
"$guardMutex = New-Object System.Threading.Mutex($false, 'Local\\BLICockpitMemoryGuard')",
|
|
28
|
+
"if (-not $guardMutex.WaitOne(0)) { exit 0 }",
|
|
29
|
+
"",
|
|
30
|
+
"function Write-GuardLog([string]$EventName, [int]$ProcessId, [int]$FootprintMb, [int]$LimitMb, [string]$Reason, [string]$Kind, [string]$Sha) {",
|
|
31
|
+
" $ts = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')",
|
|
32
|
+
' $line = \'{"ts":"\' + $ts + \'","event":"\' + $EventName + \'","pid":\' + $ProcessId + \',"footprint_mb":\' + $FootprintMb + \',"limit_mb":\' + $LimitMb + \',"reason":"\' + $Reason + \'","kind":"\' + $Kind + \'","cmd_sha256":"\' + $Sha + \'"}\'',
|
|
33
|
+
" Add-Content -LiteralPath $logPath -Encoding UTF8 -Value $line",
|
|
34
|
+
"}",
|
|
35
|
+
"",
|
|
36
|
+
"# The coarse label for a command line. The same table as the macOS guard and the CLI's tests.",
|
|
37
|
+
"function Get-GuardKind([string]$Command) {",
|
|
38
|
+
...kindChecks,
|
|
39
|
+
" return 'other'",
|
|
40
|
+
"}",
|
|
41
|
+
"",
|
|
42
|
+
"function Get-GuardSha([string]$Command) {",
|
|
43
|
+
" $sha = [System.Security.Cryptography.SHA256]::Create()",
|
|
44
|
+
" $bytes = $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Command))",
|
|
45
|
+
" return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '').Substring(0, 12)",
|
|
46
|
+
"}",
|
|
47
|
+
"",
|
|
48
|
+
"function Stop-Runaway($Process, [int]$FootprintMb, [int]$LimitMb, [string]$Reason) {",
|
|
49
|
+
" if ($Process.Id -eq $PID) { return }",
|
|
50
|
+
" $command = ''",
|
|
51
|
+
" try { $command = [string](Get-CimInstance Win32_Process -Filter \"ProcessId=$($Process.Id)\" -ErrorAction Stop).CommandLine } catch { $command = '' } # an unreadable command line only loses the kind label (other)",
|
|
52
|
+
" $kind = Get-GuardKind $command",
|
|
53
|
+
" $sha = Get-GuardSha $command",
|
|
54
|
+
" try {",
|
|
55
|
+
" Stop-Process -Id $Process.Id -Force -ErrorAction Stop",
|
|
56
|
+
" Write-GuardLog 'killed' $Process.Id $FootprintMb $LimitMb $Reason $kind $sha",
|
|
57
|
+
" } catch {",
|
|
58
|
+
" Write-GuardLog 'kill_failed' $Process.Id $FootprintMb $LimitMb $Reason $kind $sha",
|
|
59
|
+
" }",
|
|
60
|
+
"}",
|
|
61
|
+
"",
|
|
62
|
+
"# 1 normal, 2 warning (<10% free physical memory), 4 critical (<5%).",
|
|
63
|
+
"function Get-PressureLevel {",
|
|
64
|
+
" try {",
|
|
65
|
+
" $os = Get-CimInstance Win32_OperatingSystem -ErrorAction Stop",
|
|
66
|
+
" $freePercent = ($os.FreePhysicalMemory / $os.TotalVisibleMemorySize) * 100",
|
|
67
|
+
" if ($freePercent -lt 5) { return 4 }",
|
|
68
|
+
" if ($freePercent -lt 10) { return 2 }",
|
|
69
|
+
" } catch { return 1 } # pressure unreadable: judge by the normal limit",
|
|
70
|
+
" return 1",
|
|
71
|
+
"}",
|
|
72
|
+
"",
|
|
73
|
+
"function Remove-OrphanVitest {",
|
|
74
|
+
" $alive = @{}",
|
|
75
|
+
" Get-Process -ErrorAction SilentlyContinue | ForEach-Object { $alive[[int]$_.Id] = $true }",
|
|
76
|
+
" $now = Get-Date",
|
|
77
|
+
" Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" -ErrorAction SilentlyContinue | ForEach-Object {",
|
|
78
|
+
" if ($_.ProcessId -eq $PID) { return }",
|
|
79
|
+
" if (-not $_.CommandLine -or $_.CommandLine -notmatch 'vitest') { return }",
|
|
80
|
+
" if ($alive.ContainsKey([int]$_.ParentProcessId)) { return }",
|
|
81
|
+
" if (-not $_.CreationDate -or ($now - $_.CreationDate).TotalSeconds -lt $orphanMinAgeSeconds) { return }",
|
|
82
|
+
" try {",
|
|
83
|
+
" Stop-Process -Id $_.ProcessId -Force -ErrorAction Stop",
|
|
84
|
+
" Write-GuardLog 'orphan_reaped' $_.ProcessId 0 0 'orphan_vitest_worker' (Get-GuardKind $_.CommandLine) (Get-GuardSha $_.CommandLine)",
|
|
85
|
+
" } catch {",
|
|
86
|
+
" Write-GuardLog 'kill_failed' $_.ProcessId 0 0 'orphan_vitest_worker' (Get-GuardKind $_.CommandLine) (Get-GuardSha $_.CommandLine)",
|
|
87
|
+
" }",
|
|
88
|
+
" }",
|
|
89
|
+
"}",
|
|
90
|
+
"",
|
|
91
|
+
"function Limit-GuardLog {",
|
|
92
|
+
" $file = Get-Item -LiteralPath $logPath -ErrorAction SilentlyContinue",
|
|
93
|
+
" if ($file -and $file.Length -gt $logLimitBytes) {",
|
|
94
|
+
" $keep = Get-Content -LiteralPath $logPath -Tail $logKeepLines",
|
|
95
|
+
" Set-Content -LiteralPath $logPath -Value $keep -Encoding UTF8",
|
|
96
|
+
" }",
|
|
97
|
+
"}",
|
|
98
|
+
"",
|
|
99
|
+
"New-Item -ItemType Directory -Force -Path (Split-Path -Parent $logPath) | Out-Null",
|
|
100
|
+
"Write-GuardLog 'started' $PID 0 $limitMb 'started' 'guard' ''",
|
|
101
|
+
"$level = 1",
|
|
102
|
+
"$lastPressure = [DateTime]::MinValue",
|
|
103
|
+
"$lastOrphan = Get-Date",
|
|
104
|
+
"while ($true) {",
|
|
105
|
+
" $now = Get-Date",
|
|
106
|
+
" if (($now - $lastPressure).TotalSeconds -ge 10) { $level = Get-PressureLevel; $lastPressure = $now }",
|
|
107
|
+
" $limit = $limitMb; $footprintReason = 'footprint_over_limit'",
|
|
108
|
+
" if ($level -ge 4) { $limit = $criticalLimitMb; $footprintReason = 'pressure_critical' }",
|
|
109
|
+
" elseif ($level -ge 2) { $limit = $warnLimitMb; $footprintReason = 'pressure_warning' }",
|
|
110
|
+
" foreach ($p in @(Get-Process -Name node -ErrorAction SilentlyContinue)) {",
|
|
111
|
+
" $workingMb = [int]($p.WorkingSet64 / 1MB)",
|
|
112
|
+
" $privateMb = [int]($p.PrivateMemorySize64 / 1MB)",
|
|
113
|
+
" if ($workingMb -gt $limit) { Stop-Runaway $p $workingMb $limit 'rss_over_limit' }",
|
|
114
|
+
" elseif ($privateMb -gt $limit) { Stop-Runaway $p $privateMb $limit $footprintReason }",
|
|
115
|
+
" }",
|
|
116
|
+
" if (($now - $lastOrphan).TotalSeconds -ge 60) { Remove-OrphanVitest; Limit-GuardLog; $lastOrphan = $now }",
|
|
117
|
+
" Start-Sleep -Seconds 5",
|
|
118
|
+
"}",
|
|
119
|
+
"",
|
|
120
|
+
].join("\r\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Registers the guard as a logon task that restarts on failure, has no execution time limit, and starts
|
|
124
|
+
* immediately. Any older resident copy is stopped first so a re-install runs the new script, not the old.
|
|
125
|
+
*/
|
|
126
|
+
export function renderWindowsMemoryGuardRegistration(options) {
|
|
127
|
+
return [
|
|
128
|
+
"$ErrorActionPreference = 'Stop'",
|
|
129
|
+
`$taskName = ${powershellLiteral(options.taskName)}`,
|
|
130
|
+
`$launcherPath = ${powershellLiteral(options.launcherPath)}`,
|
|
131
|
+
"$wscriptPath = Join-Path (Join-Path $env:SystemRoot 'System32') 'wscript.exe'",
|
|
132
|
+
"$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
|
|
133
|
+
"$action = New-ScheduledTaskAction -Execute $wscriptPath -Argument ('//B \"' + $launcherPath + '\"')",
|
|
134
|
+
"$trigger = New-ScheduledTaskTrigger -AtLogOn -User $identity",
|
|
135
|
+
"$principal = New-ScheduledTaskPrincipal -UserId $identity -LogonType Interactive -RunLevel Limited",
|
|
136
|
+
"$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Seconds 0)",
|
|
137
|
+
"Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null",
|
|
138
|
+
`# A running older copy keeps the old limits and script: stop it, then start the registered one.`,
|
|
139
|
+
"Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue",
|
|
140
|
+
`Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*${options.scriptName}*' -and $_.ProcessId -ne $PID } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`,
|
|
141
|
+
"Start-ScheduledTask -TaskName $taskName",
|
|
142
|
+
"",
|
|
143
|
+
].join("\r\n");
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.283",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@bli-cockpit/memory-mcp": "0.1.88",
|
|
31
|
-
"@bli-cockpit/mcp": "0.1.
|
|
31
|
+
"@bli-cockpit/mcp": "0.1.172",
|
|
32
32
|
"@bli-cockpit/telemetry-core": "0.1.94",
|
|
33
33
|
"unpdf": "1.8.1"
|
|
34
34
|
},
|