@trillboards/edge-sdk 0.2.156 → 0.2.157
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.
|
@@ -499,7 +499,7 @@ function Invoke-Commit {
|
|
|
499
499
|
# failure and $ErrorActionPreference = 'Stop' turns that into ours.
|
|
500
500
|
& $installUpdate -InstallDir $InstallDir -StagingDir $PayloadDir
|
|
501
501
|
} catch {
|
|
502
|
-
Fail 'failed' "the
|
|
502
|
+
Fail 'failed' "the update transaction failed: $($_.Exception.Message)"
|
|
503
503
|
}
|
|
504
504
|
|
|
505
505
|
# The measured half. The probe predicts; this observes.
|
|
@@ -10,9 +10,17 @@ param(
|
|
|
10
10
|
|
|
11
11
|
$ErrorActionPreference = "Stop"
|
|
12
12
|
$previousDir = Join-Path $InstallDir ".previous"
|
|
13
|
+
$runtimeRepairMarker = Join-Path $previousDir 'model-runtime-rollback-failed.json'
|
|
13
14
|
$parts = @("bin", "lib")
|
|
14
15
|
$movedPrevious = New-Object System.Collections.Generic.List[string]
|
|
15
16
|
$movedStaged = New-Object System.Collections.Generic.List[string]
|
|
17
|
+
$script:modelRuntimeUpdate = $null
|
|
18
|
+
|
|
19
|
+
# A failed external-runtime rollback owns this recovery tree. A later update
|
|
20
|
+
# must not delete it in the normal .previous cleanup or restart sensing over it.
|
|
21
|
+
if (Test-Path -LiteralPath $runtimeRepairMarker) {
|
|
22
|
+
throw 'The previous local AI rollback needs repair; saved connector/runtime backups were left untouched'
|
|
23
|
+
}
|
|
16
24
|
|
|
17
25
|
function Assert-ConnectorVersion([string]$Root) {
|
|
18
26
|
$node = Join-Path $Root "bin\node.exe"
|
|
@@ -43,6 +51,109 @@ function Register-Connector([string]$Root) {
|
|
|
43
51
|
if ($LASTEXITCODE -ne 0) { throw "Scheduled-task registration failed." }
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
function Get-OwnedLocalAiProcesses {
|
|
55
|
+
# Task Scheduler can stop the uv launcher while leaving its Python child
|
|
56
|
+
# alive. Match only this user's model server on the verified task's port;
|
|
57
|
+
# never kill all Python processes or a different user's listener.
|
|
58
|
+
$portPattern = '(?:^|\s)--port(?:\s+|=)' + $script:modelRuntimeUpdate.Port + '(?=\s|$)'
|
|
59
|
+
$processes = Get-CimInstance Win32_Process -Filter "Name = 'python.exe' OR Name = 'pythonw.exe' OR Name = 'litert-lm.exe'" -OperationTimeoutSec 5 -ErrorAction Stop
|
|
60
|
+
foreach ($process in $processes) {
|
|
61
|
+
if (-not $process.CommandLine -or $process.CommandLine -notmatch 'litert[-_]lm' -or
|
|
62
|
+
$process.CommandLine -notmatch '(?:^|\s)serve(?=\s|$)' -or $process.CommandLine -notmatch $portPattern) { continue }
|
|
63
|
+
$owner = Invoke-CimMethod -InputObject $process -MethodName GetOwnerSid -OperationTimeoutSec 5 -ErrorAction Stop
|
|
64
|
+
if ($owner.ReturnValue -ne 0 -or -not $owner.Sid) { throw 'Cannot verify the model process owner; no unverified process was stopped' }
|
|
65
|
+
if ($owner.Sid -eq $script:modelRuntimeUpdate.OwnerSid) { $process }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Stop-LocalAiForUpdate {
|
|
70
|
+
Disable-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop | Out-Null
|
|
71
|
+
Stop-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop
|
|
72
|
+
for ($attempt = 0; $attempt -lt 15; $attempt++) {
|
|
73
|
+
$task = Get-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop
|
|
74
|
+
$remaining = @(Get-OwnedLocalAiProcesses)
|
|
75
|
+
foreach ($process in $remaining) { Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop }
|
|
76
|
+
$listenerErrors = @()
|
|
77
|
+
$listeners = @(Get-NetTCPConnection -LocalPort $script:modelRuntimeUpdate.Port -State Listen -ErrorAction SilentlyContinue -ErrorVariable listenerErrors)
|
|
78
|
+
if (@($listenerErrors | Where-Object { $_.CategoryInfo.Category -ne 'ObjectNotFound' }).Count) { throw 'Cannot verify the local AI port is free' }
|
|
79
|
+
if ($task.State -notin @('Running', 'Queued') -and $remaining.Count -eq 0 -and $listeners.Count -eq 0) { return }
|
|
80
|
+
Start-Sleep -Seconds 1
|
|
81
|
+
}
|
|
82
|
+
throw 'The local AI task, server process or listening port did not stop; runtime files were not replaced'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Restore-LocalAiTaskState {
|
|
86
|
+
if ($null -eq $script:modelRuntimeUpdate) { return }
|
|
87
|
+
if ($script:modelRuntimeUpdate.Enabled) {
|
|
88
|
+
Enable-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop | Out-Null
|
|
89
|
+
if ($script:modelRuntimeUpdate.Running) { Start-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop }
|
|
90
|
+
} else {
|
|
91
|
+
Disable-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction Stop | Out-Null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Update-LocalAiRuntime([string]$Root) {
|
|
96
|
+
$modelTaskErrors = @()
|
|
97
|
+
$task = Get-ScheduledTask -TaskName 'TrillboardsLiteRtLm' -ErrorAction SilentlyContinue -ErrorVariable modelTaskErrors
|
|
98
|
+
if (-not $task) {
|
|
99
|
+
# Only a genuine absence belongs to first-time setup. An inaccessible task
|
|
100
|
+
# must not be mistaken for "no model service" and silently keep old code.
|
|
101
|
+
if (@($modelTaskErrors | Where-Object { $_.CategoryInfo.Category -ne 'ObjectNotFound' }).Count) {
|
|
102
|
+
throw 'The local AI task is inaccessible; runtime refresh was not skipped'
|
|
103
|
+
}
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
$taskUser = [string]$task.Principal.UserId
|
|
107
|
+
$taskSid = if ($taskUser -match '^S-1-') { $taskUser } else {
|
|
108
|
+
(New-Object Security.Principal.NTAccount($taskUser)).Translate([Security.Principal.SecurityIdentifier]).Value
|
|
109
|
+
}
|
|
110
|
+
$currentSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
111
|
+
$action = @($task.Actions)
|
|
112
|
+
$ownTool = Join-Path $env:USERPROFILE '.local\bin\litert-lm.exe'
|
|
113
|
+
if ($taskSid -ne $currentSid -or $action.Count -ne 1 -or
|
|
114
|
+
[IO.Path]::GetFullPath([string]$action[0].Execute) -ine [IO.Path]::GetFullPath($ownTool)) {
|
|
115
|
+
throw 'Cannot verify ownership of the local AI task; no runtime was changed'
|
|
116
|
+
}
|
|
117
|
+
$serverArguments = [string]$action[0].Arguments
|
|
118
|
+
$ports = [regex]::Matches($serverArguments, '(?:^|\s)--port(?:\s+|=)([0-9]+)(?=\s|$)')
|
|
119
|
+
if ($serverArguments -notmatch '^serve\s' -or $ports.Count -ne 1 -or
|
|
120
|
+
[long]$ports[0].Groups[1].Value -lt 1 -or [long]$ports[0].Groups[1].Value -gt 65535) {
|
|
121
|
+
throw 'Cannot verify the local AI task port; no runtime was changed'
|
|
122
|
+
}
|
|
123
|
+
$setup = Join-Path $Root 'bin\setup-litertlm.ps1'
|
|
124
|
+
if (-not (Test-Path -LiteralPath $setup -PathType Leaf)) { throw 'The released local AI setup is missing' }
|
|
125
|
+
# Inspect first: an unchanged shim does NOT restart a healthy model server on
|
|
126
|
+
# every SDK update. /v1/models and the CLI version cannot detect stale code.
|
|
127
|
+
$state = (& $setup -RuntimeAction Inspect | Out-String).Trim() | ConvertFrom-Json
|
|
128
|
+
if ($LASTEXITCODE -ne 0 -or $state.version -ne 1) { throw 'Could not inspect the installed local AI runtime' }
|
|
129
|
+
if (-not $state.needsRefresh) { return }
|
|
130
|
+
$script:modelRuntimeUpdate = [pscustomobject]@{
|
|
131
|
+
Enabled = [bool]$task.Settings.Enabled
|
|
132
|
+
Running = ($task.State -eq 'Running')
|
|
133
|
+
OwnerSid = $currentSid
|
|
134
|
+
Port = [int]$ports[0].Groups[1].Value
|
|
135
|
+
Setup = $setup
|
|
136
|
+
Backup = (Join-Path $previousDir 'litertlm-runtime')
|
|
137
|
+
}
|
|
138
|
+
Stop-LocalAiForUpdate
|
|
139
|
+
# This mode is local file replacement only. It never downloads a CLI/model,
|
|
140
|
+
# imports weights, resets tuning, re-registers tasks or runs image inference.
|
|
141
|
+
& $setup -RuntimeAction Refresh -RuntimeBackupDir $script:modelRuntimeUpdate.Backup
|
|
142
|
+
if ($LASTEXITCODE -ne 0) { throw 'The local AI runtime refresh failed' }
|
|
143
|
+
Restore-LocalAiTaskState
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function Restore-LocalAiRuntime {
|
|
147
|
+
if ($null -eq $script:modelRuntimeUpdate) { return }
|
|
148
|
+
Stop-LocalAiForUpdate
|
|
149
|
+
$receipt = Join-Path $script:modelRuntimeUpdate.Backup 'runtime-backup.json'
|
|
150
|
+
if (Test-Path -LiteralPath $receipt -PathType Leaf) {
|
|
151
|
+
& $script:modelRuntimeUpdate.Setup -RuntimeAction Restore -RuntimeBackupDir $script:modelRuntimeUpdate.Backup
|
|
152
|
+
if ($LASTEXITCODE -ne 0) { throw 'The previous local AI runtime could not be verified; rollback backup was preserved' }
|
|
153
|
+
}
|
|
154
|
+
Restore-LocalAiTaskState
|
|
155
|
+
}
|
|
156
|
+
|
|
46
157
|
<#
|
|
47
158
|
Carry the detector's RAM ceiling onto an EXISTING install.
|
|
48
159
|
|
|
@@ -192,6 +303,9 @@ try {
|
|
|
192
303
|
}
|
|
193
304
|
|
|
194
305
|
Assert-ConnectorVersion $InstallDir
|
|
306
|
+
# The Python venv is OUTSIDE bin/lib. Refresh it transactionally before the
|
|
307
|
+
# connector can review a saved frame with the new native-image contract.
|
|
308
|
+
Update-LocalAiRuntime $InstallDir
|
|
195
309
|
Register-Connector $InstallDir
|
|
196
310
|
Assert-ConnectorScheduled
|
|
197
311
|
if (Test-Path (Join-Path $InstallDir "trillboards.config.json")) {
|
|
@@ -206,11 +320,21 @@ try {
|
|
|
206
320
|
Write-Host "Trillboards Edge update installed successfully."
|
|
207
321
|
} catch {
|
|
208
322
|
$failure = $_
|
|
323
|
+
$runtimeRollbackFailure = $null
|
|
209
324
|
try {
|
|
210
325
|
$stopScript = Join-Path $InstallDir "bin\stop-existing.ps1"
|
|
211
326
|
if (Test-Path $stopScript) { & $stopScript -InstallDir $InstallDir }
|
|
212
327
|
} catch {}
|
|
213
328
|
|
|
329
|
+
# Use the NEW setup while its bin tree still exists. Restore external runtime
|
|
330
|
+
# bytes before rolling bin/lib back; neither path touches pairings or spools.
|
|
331
|
+
try { Restore-LocalAiRuntime } catch { $runtimeRollbackFailure = $_.Exception.Message }
|
|
332
|
+
if ($runtimeRollbackFailure) {
|
|
333
|
+
[IO.File]::WriteAllText($runtimeRepairMarker,
|
|
334
|
+
(@{ version = 1; failedAtUtc = [DateTime]::UtcNow.ToString('o'); detail = $runtimeRollbackFailure } | ConvertTo-Json),
|
|
335
|
+
[Text.UTF8Encoding]::new($false))
|
|
336
|
+
throw "Update failed: $($failure.Exception.Message). Local AI rollback needs repair: $runtimeRollbackFailure. Preserved both connector trees, runtime backups, pairings and saved evidence."
|
|
337
|
+
}
|
|
214
338
|
foreach ($part in $movedStaged) {
|
|
215
339
|
Remove-Item -Recurse -Force (Join-Path $InstallDir $part) -ErrorAction SilentlyContinue
|
|
216
340
|
}
|
|
@@ -24,6 +24,10 @@ param(
|
|
|
24
24
|
[switch]$RequireBundledModel,
|
|
25
25
|
[switch]$AllowModelDownload,
|
|
26
26
|
[string]$ProgressPath = "",
|
|
27
|
+
# Upgrades refresh only our shipped Python adapter, never the CLI, weights,
|
|
28
|
+
# model config or task registration. The caller owns stopping/restarting it.
|
|
29
|
+
[ValidateSet('Setup', 'Inspect', 'Refresh', 'Restore')][string]$RuntimeAction = 'Setup',
|
|
30
|
+
[string]$RuntimeBackupDir = "",
|
|
27
31
|
[switch]$SmokeTest
|
|
28
32
|
)
|
|
29
33
|
|
|
@@ -333,6 +337,128 @@ function Import-VerifiedModel([string]$SourcePath, [string]$ExecutablePath) {
|
|
|
333
337
|
return (($LASTEXITCODE -eq 0) -and (Test-LitertLmModel $ExecutablePath))
|
|
334
338
|
}
|
|
335
339
|
|
|
340
|
+
# One owner for the shim on fresh installs AND upgrades. None of these helpers
|
|
341
|
+
# import a model, rewrite model config, install dependencies or register tasks.
|
|
342
|
+
function Get-LitertlmShimPaths([string]$UvPath) {
|
|
343
|
+
$shim = $null
|
|
344
|
+
foreach ($candidate in @(
|
|
345
|
+
(Join-Path $PSScriptRoot 'trillboards_litertlm_shim.py'),
|
|
346
|
+
(Join-Path $PSScriptRoot '..\litertlm\trillboards_litertlm_shim.py')
|
|
347
|
+
)) { if (Test-Path -LiteralPath $candidate -PathType Leaf) { $shim = $candidate; break } }
|
|
348
|
+
if (-not $shim) { throw 'The released litert-lm shim is missing' }
|
|
349
|
+
$toolDir = (& $UvPath tool dir 2>$null | Select-Object -First 1)
|
|
350
|
+
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($toolDir)) { throw 'Cannot resolve uv tool dir' }
|
|
351
|
+
$venv = [IO.Path]::GetFullPath((Join-Path $toolDir.Trim() 'litert-lm'))
|
|
352
|
+
$py = Join-Path $venv 'Scripts\python.exe'
|
|
353
|
+
if (-not (Test-Path -LiteralPath $py -PathType Leaf)) { throw 'The installed litert-lm Python is missing' }
|
|
354
|
+
$site = (& $py -c "import sysconfig;print(sysconfig.get_paths()['purelib'])" 2>$null | Select-Object -First 1)
|
|
355
|
+
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($site)) { throw 'Cannot resolve litert-lm site-packages' }
|
|
356
|
+
$site = [IO.Path]::GetFullPath($site.Trim())
|
|
357
|
+
if (-not $site.StartsWith(($venv.TrimEnd('\') + '\'), [StringComparison]::OrdinalIgnoreCase) -or
|
|
358
|
+
-not (Test-Path -LiteralPath $site -PathType Container)) { throw 'The runtime site is outside its managed venv' }
|
|
359
|
+
return [pscustomobject]@{ Source = $shim; Python = $py; Site = $site }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function Get-LitertlmShimFiles($Paths) {
|
|
363
|
+
return @(
|
|
364
|
+
[pscustomobject]@{ Name = 'trillboards_litertlm_shim.py'; Bytes = [IO.File]::ReadAllBytes($Paths.Source) },
|
|
365
|
+
[pscustomobject]@{ Name = 'trillboards-litertlm-shim.pth'; Bytes = [Text.Encoding]::ASCII.GetBytes("import trillboards_litertlm_shim`r`n") }
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function Get-ShimBytesHash([byte[]]$Bytes) {
|
|
370
|
+
$hasher = [Security.Cryptography.SHA256]::Create()
|
|
371
|
+
try { return ([BitConverter]::ToString($hasher.ComputeHash($Bytes))).Replace('-', '').ToLowerInvariant() }
|
|
372
|
+
finally { $hasher.Dispose() }
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function Get-LitertlmShimState([string]$UvPath) {
|
|
376
|
+
$paths = Get-LitertlmShimPaths $UvPath
|
|
377
|
+
$needsRefresh = $false
|
|
378
|
+
foreach ($file in (Get-LitertlmShimFiles $paths)) {
|
|
379
|
+
$target = Join-Path $paths.Site $file.Name
|
|
380
|
+
if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { $needsRefresh = $true; continue }
|
|
381
|
+
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() -ne (Get-ShimBytesHash $file.Bytes)) { $needsRefresh = $true }
|
|
382
|
+
}
|
|
383
|
+
return [pscustomobject]@{ version = 1; needsRefresh = $needsRefresh }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function Install-LitertlmShim {
|
|
387
|
+
param([string]$UvPath, [string]$BackupDirectory = '', [switch]$Restore)
|
|
388
|
+
$ErrorActionPreference = 'Stop'
|
|
389
|
+
try {
|
|
390
|
+
$paths = Get-LitertlmShimPaths $UvPath
|
|
391
|
+
$files = @(Get-LitertlmShimFiles $paths)
|
|
392
|
+
$receiptPath = if ($BackupDirectory) { Join-Path $BackupDirectory 'runtime-backup.json' } else { '' }
|
|
393
|
+
if ($Restore) {
|
|
394
|
+
if (-not $receiptPath) { throw 'Runtime rollback requires its backup directory' }
|
|
395
|
+
$receipt = Get-Content -LiteralPath $receiptPath -Raw | ConvertFrom-Json
|
|
396
|
+
if ($receipt.version -ne 1 -or $receipt.site -ine $paths.Site -or @($receipt.files).Count -ne $files.Count) { throw 'Runtime rollback scope mismatch' }
|
|
397
|
+
# Validate the COMPLETE restore before changing either file. Refuse an
|
|
398
|
+
# unexpected concurrent write rather than overwriting another repair.
|
|
399
|
+
foreach ($file in $files) {
|
|
400
|
+
$entry = @($receipt.files | Where-Object { $_.name -eq $file.Name })
|
|
401
|
+
if ($entry.Count -ne 1) { throw 'Runtime rollback file set mismatch' }
|
|
402
|
+
$target = Join-Path $paths.Site $file.Name
|
|
403
|
+
if (Test-Path -LiteralPath $target) {
|
|
404
|
+
$current = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
405
|
+
if ($current -ne $entry[0].installedSha256 -and $current -ne $entry[0].previousSha256) { throw 'Runtime changed after the update; rollback refused' }
|
|
406
|
+
}
|
|
407
|
+
if ($entry[0].existed -and (Get-FileHash -LiteralPath (Join-Path $BackupDirectory $file.Name) -Algorithm SHA256).Hash.ToLowerInvariant() -ne $entry[0].previousSha256) { throw 'Runtime rollback backup hash mismatch' }
|
|
408
|
+
}
|
|
409
|
+
foreach ($entry in $receipt.files) {
|
|
410
|
+
$target = Join-Path $paths.Site $entry.name
|
|
411
|
+
if ($entry.existed) { Copy-Item -LiteralPath (Join-Path $BackupDirectory $entry.name) -Destination $target -Force }
|
|
412
|
+
elseif (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Force }
|
|
413
|
+
if ($entry.existed) {
|
|
414
|
+
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() -ne $entry.previousSha256) { throw 'Restored runtime hash mismatch' }
|
|
415
|
+
} elseif (Test-Path -LiteralPath $target) { throw 'Runtime rollback left a newly introduced file' }
|
|
416
|
+
}
|
|
417
|
+
Log 'restored the previous litert-lm shim bytes'
|
|
418
|
+
return $true
|
|
419
|
+
}
|
|
420
|
+
if ($BackupDirectory) {
|
|
421
|
+
if (Test-Path -LiteralPath $receiptPath) { throw 'Preserve the existing runtime rollback receipt' }
|
|
422
|
+
New-Item -ItemType Directory -Force -Path $BackupDirectory | Out-Null
|
|
423
|
+
$backups = @(foreach ($file in $files) {
|
|
424
|
+
$target = Join-Path $paths.Site $file.Name
|
|
425
|
+
$existed = Test-Path -LiteralPath $target -PathType Leaf
|
|
426
|
+
$previousSha = $null
|
|
427
|
+
if ($existed) {
|
|
428
|
+
$previousSha = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
429
|
+
$backup = Join-Path $BackupDirectory $file.Name
|
|
430
|
+
Copy-Item -LiteralPath $target -Destination $backup
|
|
431
|
+
if ((Get-FileHash -LiteralPath $backup -Algorithm SHA256).Hash.ToLowerInvariant() -ne $previousSha) { throw 'Runtime rollback copy did not verify' }
|
|
432
|
+
}
|
|
433
|
+
[pscustomobject]@{ name = $file.Name; existed = [bool]$existed; previousSha256 = $previousSha; installedSha256 = (Get-ShimBytesHash $file.Bytes) }
|
|
434
|
+
})
|
|
435
|
+
[IO.File]::WriteAllText($receiptPath, (@{ version = 1; site = $paths.Site; files = $backups } | ConvertTo-Json -Depth 5), [Text.UTF8Encoding]::new($false))
|
|
436
|
+
}
|
|
437
|
+
foreach ($file in $files) {
|
|
438
|
+
$target = Join-Path $paths.Site $file.Name
|
|
439
|
+
$temporary = "$target.$PID.update"
|
|
440
|
+
try {
|
|
441
|
+
[IO.File]::WriteAllBytes($temporary, $file.Bytes)
|
|
442
|
+
if ((Get-FileHash -LiteralPath $temporary -Algorithm SHA256).Hash.ToLowerInvariant() -ne (Get-ShimBytesHash $file.Bytes)) { throw 'Runtime refresh hash mismatch' }
|
|
443
|
+
# PS 5.1 binds an ordinary $null to an empty String here, which .NET
|
|
444
|
+
# treats as an invalid backup path. Pass an actual null String.
|
|
445
|
+
if (Test-Path -LiteralPath $target) { [IO.File]::Replace($temporary, $target, [NullString]::Value) }
|
|
446
|
+
else { [IO.File]::Move($temporary, $target) }
|
|
447
|
+
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() -ne (Get-ShimBytesHash $file.Bytes)) { throw 'Installed runtime refresh hash mismatch' }
|
|
448
|
+
} finally { if (Test-Path -LiteralPath $temporary) { Remove-Item -LiteralPath $temporary -Force } }
|
|
449
|
+
}
|
|
450
|
+
# A fresh interpreter proves the installed wrapper imports. No engine,
|
|
451
|
+
# weights, image inference, model config or camera credentials are loaded.
|
|
452
|
+
& $paths.Python -c "import litert_lm,sys; sys.exit(0 if getattr(litert_lm.Engine.create_conversation,'_trillboards_litertlm_shim',False) else 1)" *>> $LogPath
|
|
453
|
+
if ($LASTEXITCODE -ne 0) { throw 'The refreshed model-service shim did not import' }
|
|
454
|
+
Log "litert-lm fast-conversation shim active in $($paths.Site)"
|
|
455
|
+
return $true
|
|
456
|
+
} catch {
|
|
457
|
+
Log "WARN: litert-lm shim operation failed: $_"
|
|
458
|
+
return $false
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
336
462
|
if ($SmokeTest) {
|
|
337
463
|
if ([string]::IsNullOrWhiteSpace($ProgressPath)) { throw "SmokeTest requires ProgressPath" }
|
|
338
464
|
Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Smoke test" -Phase "installing_model" -Percent 62 -TotalBytes $ExpectedModelBytes
|
|
@@ -345,7 +471,10 @@ if ($SmokeTest) {
|
|
|
345
471
|
exit 0
|
|
346
472
|
}
|
|
347
473
|
|
|
348
|
-
|
|
474
|
+
# S4U/boot tasks and the interactive installer run in different sessions. A
|
|
475
|
+
# Local mutex cannot serialize them; scope the Global name to this user's SID.
|
|
476
|
+
$setupSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
477
|
+
$setupMutex = New-Object System.Threading.Mutex($false, ("Global\TrillboardsLiteRtLmSetup-" + $setupSid))
|
|
349
478
|
$haveSetupMutex = $false
|
|
350
479
|
try {
|
|
351
480
|
$haveSetupMutex = $setupMutex.WaitOne(0)
|
|
@@ -354,6 +483,10 @@ try {
|
|
|
354
483
|
}
|
|
355
484
|
|
|
356
485
|
if (-not $haveSetupMutex) {
|
|
486
|
+
if ($RuntimeAction -ne 'Setup') {
|
|
487
|
+
$setupMutex.Dispose()
|
|
488
|
+
throw 'Local AI setup is busy; no concurrent runtime refresh was attempted'
|
|
489
|
+
}
|
|
357
490
|
Log "LiteRT-LM provisioning is already running; attaching setup progress"
|
|
358
491
|
if ([string]::IsNullOrWhiteSpace($ProgressPath) -and -not $RequireBundledModel) {
|
|
359
492
|
$setupMutex.Dispose()
|
|
@@ -389,6 +522,24 @@ function Stop-SetupLock {
|
|
|
389
522
|
$setupMutex.Dispose()
|
|
390
523
|
}
|
|
391
524
|
|
|
525
|
+
if ($RuntimeAction -ne 'Setup') {
|
|
526
|
+
try {
|
|
527
|
+
$refreshUvPath = Join-Path $LocalBin 'uv.exe'
|
|
528
|
+
if (-not (Test-Path -LiteralPath $refreshUvPath -PathType Leaf)) {
|
|
529
|
+
$refreshUv = Get-Command uv -ErrorAction Stop
|
|
530
|
+
$refreshUvPath = $refreshUv.Source
|
|
531
|
+
}
|
|
532
|
+
if ($RuntimeAction -eq 'Inspect') {
|
|
533
|
+
Get-LitertlmShimState $refreshUvPath | ConvertTo-Json -Compress
|
|
534
|
+
} else {
|
|
535
|
+
if ([string]::IsNullOrWhiteSpace($RuntimeBackupDir)) { throw 'A runtime rollback directory is required' }
|
|
536
|
+
$refreshArgs = @{ UvPath = $refreshUvPath; BackupDirectory = $RuntimeBackupDir; Restore = ($RuntimeAction -eq 'Restore') }
|
|
537
|
+
if (-not (Install-LitertlmShim @refreshArgs)) { throw 'The local AI runtime refresh/rollback did not verify' }
|
|
538
|
+
}
|
|
539
|
+
} finally { Stop-SetupLock }
|
|
540
|
+
exit 0
|
|
541
|
+
}
|
|
542
|
+
|
|
392
543
|
Log "starting LiteRT-LM provisioning (Windows x64, CPU backend)"
|
|
393
544
|
Write-SetupProgress -Stage "local_ai" -Title "Installing local AI" -Message "Preparing the private on-device AI runtime. Cloud inference remains off." -Phase "installing_runtime" -Percent 0
|
|
394
545
|
|
|
@@ -585,41 +736,6 @@ if ((Test-LitertLmCli) -and ($installedLitertLmVersion -eq $LitertLmVersion)) {
|
|
|
585
736
|
# short-circuits that branch on a healthy CLI -- gating this behind it is exactly
|
|
586
737
|
# how a repaired-and-healthy machine would keep the slow serve and report fine.
|
|
587
738
|
# Never throws: this is a speed fix, and it must not be able to fail an install.
|
|
588
|
-
function Install-LitertlmShim {
|
|
589
|
-
param([string]$UvPath)
|
|
590
|
-
try {
|
|
591
|
-
$shim = $null
|
|
592
|
-
foreach ($c in @(
|
|
593
|
-
(Join-Path $PSScriptRoot "trillboards_litertlm_shim.py"),
|
|
594
|
-
(Join-Path $PSScriptRoot "..\litertlm\trillboards_litertlm_shim.py")
|
|
595
|
-
)) { if (Test-Path -LiteralPath $c -PathType Leaf) { $shim = $c; break } }
|
|
596
|
-
if (-not $shim) { Log "WARN: litert-lm shim source not found; serve keeps stock (slower) conversation setup"; return $false }
|
|
597
|
-
|
|
598
|
-
$toolDir = (& $UvPath tool dir 2>$null | Select-Object -First 1)
|
|
599
|
-
if ([string]::IsNullOrWhiteSpace($toolDir)) { Log "WARN: could not resolve 'uv tool dir'; skipping litert-lm shim"; return $false }
|
|
600
|
-
$py = Join-Path $toolDir.Trim() "litert-lm\Scripts\python.exe"
|
|
601
|
-
if (-not (Test-Path -LiteralPath $py -PathType Leaf)) { Log "WARN: litert-lm venv python missing at $py; skipping shim"; return $false }
|
|
602
|
-
|
|
603
|
-
$site = (& $py -c "import sysconfig;print(sysconfig.get_paths()['purelib'])" 2>$null | Select-Object -First 1)
|
|
604
|
-
if ([string]::IsNullOrWhiteSpace($site) -or -not (Test-Path -LiteralPath $site.Trim())) {
|
|
605
|
-
Log "WARN: could not resolve litert-lm site-packages; skipping shim"; return $false
|
|
606
|
-
}
|
|
607
|
-
$site = $site.Trim()
|
|
608
|
-
Copy-Item -LiteralPath $shim -Destination (Join-Path $site "trillboards_litertlm_shim.py") -Force
|
|
609
|
-
Set-Content -LiteralPath (Join-Path $site "trillboards-litertlm-shim.pth") -Value "import trillboards_litertlm_shim" -Encoding ASCII
|
|
610
|
-
|
|
611
|
-
# Capability is established by ATTEMPTING THE OPERATION. Importing litert_lm
|
|
612
|
-
# here is the only thing that proves the wrap landed on the class the serve
|
|
613
|
-
# will use; a file that merely exists proves nothing.
|
|
614
|
-
& $py -c "import litert_lm,sys; sys.exit(0 if getattr(litert_lm.Engine.create_conversation,'_trillboards_litertlm_shim',False) else 1)" *>> $LogPath
|
|
615
|
-
if ($LASTEXITCODE -eq 0) { Log "litert-lm fast-conversation shim active in $site"; return $true }
|
|
616
|
-
Log "WARN: litert-lm shim did not take effect; serve keeps stock (slower) conversation setup"
|
|
617
|
-
return $false
|
|
618
|
-
} catch {
|
|
619
|
-
Log "WARN: litert-lm shim install failed: $_"
|
|
620
|
-
return $false
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
739
|
Install-LitertlmShim -UvPath $uvCmd.Source | Out-Null
|
|
624
740
|
|
|
625
741
|
$bundledModelVerified = $false
|
package/dist/EdgeAgent.js
CHANGED
|
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
|
|
|
44
44
|
const CommandRouter_1 = require("./CommandRouter");
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const dataDir_1 = require("./dataDir");
|
|
47
|
-
const SDK_VERSION = '0.2.
|
|
47
|
+
const SDK_VERSION = '0.2.157';
|
|
48
48
|
class EdgeAgent extends events_1.EventEmitter {
|
|
49
49
|
constructor(config, deps) {
|
|
50
50
|
super();
|
package/dist/diagnose.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface DiagnosticReport {
|
|
|
31
31
|
skipped: number;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
declare const SDK_VERSION = "0.2.
|
|
34
|
+
declare const SDK_VERSION = "0.2.157";
|
|
35
35
|
declare function checkNodeVersion(): CheckResult;
|
|
36
36
|
declare function checkPlatform(): CheckResult;
|
|
37
37
|
declare function checkMemory(): CheckResult;
|
package/dist/diagnose.js
CHANGED
|
@@ -63,7 +63,7 @@ const localNetworkAccess_1 = require("./localNetworkAccess");
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
// SDK version — mirrors package.json; kept in sync by CI
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
|
-
const SDK_VERSION = '0.2.
|
|
66
|
+
const SDK_VERSION = '0.2.157';
|
|
67
67
|
exports._SDK_VERSION = SDK_VERSION;
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
// ANSI helpers (no external deps)
|
package/package.json
CHANGED