@ran-sh/dsh-crew 1.1.0 → 1.2.0-rc.4
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/.claude-plugin/marketplace.json +16 -17
- package/.claude-plugin/plugin.json +7 -1
- package/README.md +44 -15
- package/README.zh.md +37 -12
- package/agents/ds-flash.md +16 -18
- package/agents/ds-pro.md +16 -24
- package/agents/ds-reviewer.md +15 -15
- package/agents/ds-worker.md +16 -16
- package/bin/dsh-crew.mjs +16 -12
- package/codex/AGENTS.md +37 -85
- package/codex/agents/ds-flash.toml +18 -14
- package/codex/agents/ds-pro.toml +18 -15
- package/codex/agents/ds-reviewer.toml +17 -13
- package/codex/agents/ds-worker.toml +17 -13
- package/codex/prompts/dsh-config.md +17 -1
- package/codex/prompts/dsh-status.md +5 -1
- package/commands/config.md +16 -4
- package/commands/status.md +6 -2
- package/docs/installation.md +54 -9
- package/docs/readiness-matrix.md +14 -6
- package/docs/ui-surfaces.md +32 -17
- package/lib/client.js +5434 -4844
- package/official-web-bridge/lib/client.js +567 -479
- package/official-web-bridge/overlay-entry.mjs +59 -0
- package/package.json +6 -3
- package/scripts/build-client.mjs +2 -0
- package/scripts/setup.mjs +8 -7
- package/scripts/verify-crew-ui-polish.mjs +145 -0
- package/scripts/verify-history-ui.mjs +97 -0
- package/scripts/verify-npm-install.mjs +1 -1
- package/src/client/history-panel.tsx +108 -0
- package/src/client/host-readiness.mjs +15 -6
- package/src/client/index.tsx +86 -64
- package/src/client/model-callability-view.mjs +17 -0
- package/src/client/panel-chrome.tsx +40 -0
- package/src/client/quick-panel.tsx +88 -73
- package/src/client/readiness-envelope.mjs +34 -0
- package/src/config-readiness.mjs +181 -115
- package/src/extension-contract.mjs +44 -26
- package/src/history/admission-gate.mjs +51 -0
- package/src/history/archive-store.mjs +261 -0
- package/src/history/cleanup-plan.mjs +64 -0
- package/src/history/http.mjs +32 -0
- package/src/history/operation.mjs +86 -0
- package/src/history/runner.mjs +51 -0
- package/src/history/runtime.mjs +28 -0
- package/src/history/service.mjs +137 -0
- package/src/history/state.mjs +27 -0
- package/src/hub/index.mjs +141 -24
- package/src/hub-client.mjs +17 -15
- package/src/image-output.mjs +71 -0
- package/src/install/install-legacy.mjs +126 -23
- package/src/install/npx-lifecycle.mjs +373 -64
- package/src/install/official-frontend-assets.mjs +78 -0
- package/src/install/payload-content.mjs +88 -0
- package/src/install/windows-startup.mjs +107 -19
- package/src/install/windows-supervisor-adapter.mjs +443 -0
- package/src/install/windows-supervisor-lifecycle.mjs +782 -0
- package/src/install/zcode.mjs +53 -14
- package/src/job-contracts.mjs +37 -31
- package/src/jobs.mjs +2 -2
- package/src/mcp-runtime.mjs +74 -9
- package/src/model-callability-contract.mjs +79 -0
- package/src/multimodal.mjs +0 -0
- package/src/official-web-bridge.mjs +0 -2
- package/src/provider-health.mjs +130 -124
- package/src/provider-profile-store.mjs +12 -0
- package/src/provider-settings-store.mjs +347 -5
- package/src/readiness-matrix.mjs +20 -15
- package/src/runtime-identity-contract.mjs +34 -0
- package/src/runtime-identity.mjs +14 -4
- package/src/runtime-readiness-snapshot.mjs +285 -87
- package/src/server.mjs +86 -76
- package/src/standalone-sdk.mjs +23 -0
- package/src/supervisor/restart-request.mjs +64 -10
- package/src/workflow-runtime.mjs +28 -13
- package/windows/start-dsh-crew.cmd +57 -57
- package/windows/start-dsh-crew.ps1 +327 -55
- package/windows/supervisor-control.ps1 +467 -0
- package/zcode/AGENTS.md +4 -1
- package/zcode/agents/ds-reviewer.md +31 -36
- package/zcode/agents/ds-worker.md +31 -39
- package/zcode/commands/dsh-config.md +17 -6
- package/zcode/commands/dsh-status.md +5 -4
- package/.mcp.json +0 -8
|
@@ -9,13 +9,22 @@ Set-StrictMode -Version Latest
|
|
|
9
9
|
$ErrorActionPreference = 'Stop'
|
|
10
10
|
|
|
11
11
|
$crewHome = Join-Path $env:USERPROFILE '.config\dsh-crew\harness'
|
|
12
|
-
$officialHome = Join-Path $env:USERPROFILE '.dsh'
|
|
13
12
|
$dshCli = Join-Path $crewHome 'runtime\node_modules\.bin\dsh.cmd'
|
|
14
13
|
$logRoot = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() }
|
|
15
14
|
$launcherLog = Join-Path $logRoot 'dsh-crew-launcher.log'
|
|
16
15
|
$startedAt = Get-Date
|
|
16
|
+
$launcherProcessStartedAtUtcTicks = try { (Get-Process -Id $PID -ErrorAction Stop).StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture) } catch { $null }
|
|
17
|
+
$supervisorInstanceId = [guid]::NewGuid().ToString()
|
|
18
|
+
$launcherHelperHash = $null
|
|
19
|
+
try {
|
|
20
|
+
$hashStream = [System.IO.File]::OpenRead($PSCommandPath)
|
|
21
|
+
try {
|
|
22
|
+
$sha256 = [System.Security.Cryptography.SHA256]::Create()
|
|
23
|
+
try { $launcherHelperHash = (($sha256.ComputeHash($hashStream) | ForEach-Object { $_.ToString('x2') }) -join '') }
|
|
24
|
+
finally { $sha256.Dispose() }
|
|
25
|
+
} finally { $hashStream.Dispose() }
|
|
26
|
+
} catch { $launcherHelperHash = $null }
|
|
17
27
|
$services = @(
|
|
18
|
-
[pscustomobject]@{ Name = 'Official UI'; Profile = 'web'; Home = $officialHome; Port = 3080; Url = 'http://127.0.0.1:3080'; CrewOwned = $false; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null },
|
|
19
28
|
[pscustomobject]@{ Name = 'Crew backend'; Profile = 'dsh-crew'; Home = $crewHome; Port = 3210; Url = 'http://127.0.0.1:3210'; CrewOwned = $true; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null }
|
|
20
29
|
)
|
|
21
30
|
|
|
@@ -30,11 +39,136 @@ function Write-LaunchLog {
|
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
function Resolve-OfficialHarnessCommand {
|
|
43
|
+
$shim = Get-Command dsh.cmd -CommandType Application -ErrorAction Stop | Select-Object -First 1
|
|
44
|
+
$root = Join-Path (Split-Path -Parent $shim.Source) 'node_modules\@deepseek-ai\dsh'
|
|
45
|
+
$manifest = Get-Content -LiteralPath (Join-Path $root 'package.json') -Raw | ConvertFrom-Json
|
|
46
|
+
if ($manifest.name -ne '@deepseek-ai/dsh') { throw 'The dsh command is not a verified official Harness installation.' }
|
|
47
|
+
$entrySpec = [string] $manifest.bin.dsh
|
|
48
|
+
if (-not $entrySpec -or [IO.Path]::IsPathRooted($entrySpec) -or $entrySpec.Contains(':') -or ($entrySpec.Replace('\', '/').Split('/') -contains '..')) {
|
|
49
|
+
throw 'The official Harness CLI entry is invalid.'
|
|
50
|
+
}
|
|
51
|
+
$entry = [IO.Path]::GetFullPath((Join-Path $root $entrySpec))
|
|
52
|
+
$rootPrefix = [IO.Path]::GetFullPath($root).TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
|
53
|
+
if (-not $entry.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $entry -PathType Leaf)) {
|
|
54
|
+
throw 'The official Harness CLI entry was not found.'
|
|
55
|
+
}
|
|
56
|
+
$node = Get-Command node.exe -CommandType Application -ErrorAction Stop | Select-Object -First 1
|
|
57
|
+
return [pscustomobject]@{ NodePath = $node.Source; Entry = $entry }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Test-OfficialHarnessListener {
|
|
61
|
+
param([int] $OwnerPid, [pscustomobject] $Official)
|
|
62
|
+
if ($OwnerPid -le 0) { return $false }
|
|
63
|
+
try {
|
|
64
|
+
$owner = Get-CimInstance Win32_Process -Filter ('ProcessId={0}' -f $OwnerPid) -ErrorAction Stop
|
|
65
|
+
if (-not $owner -or [IO.Path]::GetFileName([string] $owner.ExecutablePath) -ine 'node.exe') { return $false }
|
|
66
|
+
$line = ([string] $owner.CommandLine).Replace('/', '\')
|
|
67
|
+
$entry = [regex]::Escape($Official.Entry.Replace('/', '\'))
|
|
68
|
+
$node = [regex]::Escape($Official.NodePath.Replace('/', '\'))
|
|
69
|
+
$pattern = '^\s*(?:"?' + $node + '"?|"?node(?:\.exe)?"?)\s+"?' + $entry + '"?\s+(?:"?web"?|--profile\s+"?web"?)(?:\s|$)'
|
|
70
|
+
return $line -match $pattern
|
|
71
|
+
} catch { return $false }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Test-OfficialWebReady {
|
|
75
|
+
try {
|
|
76
|
+
$response = Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:3080/' -TimeoutSec 2
|
|
77
|
+
return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300
|
|
78
|
+
} catch {
|
|
79
|
+
# An authenticated official UI may reject this cookie-free health request.
|
|
80
|
+
if ($_.Exception.Response) { return [int] $_.Exception.Response.StatusCode -in @(401, 403) }
|
|
81
|
+
return $false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Get-OfficialFrontendOverlay {
|
|
86
|
+
$frontendRoot = Join-Path $env:USERPROFILE '.config\dsh-crew\frontend'
|
|
87
|
+
$path = Join-Path $frontendRoot 'official-web.patch.json'
|
|
88
|
+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw 'Crew frontend overlay is missing. Run dsh-crew update with the current GitHub candidate.' }
|
|
89
|
+
$patch = @(Get-Content -LiteralPath $path -Raw | ConvertFrom-Json)
|
|
90
|
+
if ($patch.Count -ne 1 -or @($patch[0].insert).Count -ne 1 -or $patch[0].insert[0].id -ne 'dsh-crew-official-web-bridge') {
|
|
91
|
+
throw 'Crew frontend overlay has an unexpected structure.'
|
|
92
|
+
}
|
|
93
|
+
$uri = [Uri] ([string] $patch[0].insert[0].name)
|
|
94
|
+
if (-not $uri.IsFile) { throw 'Crew frontend must load from its local snapshot.' }
|
|
95
|
+
$entry = [IO.Path]::GetFullPath($uri.LocalPath)
|
|
96
|
+
$prefix = [IO.Path]::GetFullPath((Join-Path $frontendRoot 'revisions')) + [IO.Path]::DirectorySeparatorChar
|
|
97
|
+
if (-not $entry.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { throw 'Crew frontend snapshot is outside its owned directory.' }
|
|
98
|
+
$metadata = Get-Content -LiteralPath (Join-Path (Split-Path -Parent $entry) 'package.json') -Raw | ConvertFrom-Json
|
|
99
|
+
$revision = [string] $metadata.dshCrewFrontendRevision
|
|
100
|
+
if ($metadata.dshCrewManagedFrontend -ne $true -or $revision -notmatch '^[a-f0-9]{64}$' -or -not (Test-Path -LiteralPath $entry -PathType Leaf)) {
|
|
101
|
+
throw 'Crew frontend snapshot is incomplete.'
|
|
102
|
+
}
|
|
103
|
+
return [pscustomobject]@{ Path = $path; Revision = $revision }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function Test-OfficialFrontendAttached {
|
|
107
|
+
param([string] $Revision)
|
|
108
|
+
try {
|
|
109
|
+
$response = Invoke-RestMethod -Uri 'http://127.0.0.1:3080/_dsh/dsh-crew/bridge-status' -TimeoutSec 2
|
|
110
|
+
return $response.ok -eq $true -and $response.surface -eq 'official-bridge' -and $response.frontend_revision -eq $Revision
|
|
111
|
+
} catch { return $false }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Open-OfficialFrontend {
|
|
115
|
+
param([int] $TimeoutSeconds = 90)
|
|
116
|
+
$official = Resolve-OfficialHarnessCommand
|
|
117
|
+
$frontend = Get-OfficialFrontendOverlay
|
|
118
|
+
$mutex = New-Object System.Threading.Mutex($false, 'Local\DSHCrewOfficialFrontendLauncher')
|
|
119
|
+
$locked = $false
|
|
120
|
+
try {
|
|
121
|
+
try { $locked = $mutex.WaitOne(30000) } catch [System.Threading.AbandonedMutexException] { $locked = $true }
|
|
122
|
+
if (-not $locked) { throw 'Another desktop launch is still starting the official frontend.' }
|
|
123
|
+
$port = Get-PortState -Port 3080
|
|
124
|
+
if ($port.State -eq 'unknown') { throw $port.Error }
|
|
125
|
+
if ($port.State -eq 'occupied') {
|
|
126
|
+
if (-not (Test-OfficialHarnessListener -OwnerPid $port.Pid -Official $official)) { throw 'Port 3080 is occupied by an unverified process; it was left untouched.' }
|
|
127
|
+
if (-not (Test-OfficialWebReady)) { throw 'The official 3080 frontend is not ready; its process was left running.' }
|
|
128
|
+
if (-not (Test-OfficialFrontendAttached -Revision $frontend.Revision)) { throw 'The running official frontend has not loaded the current Crew panel. Stop that official instance when idle, then launch from the desktop again. It was left untouched.' }
|
|
129
|
+
Start-Process 'http://127.0.0.1:3080/' | Out-Null
|
|
130
|
+
Write-LaunchLog 'Opened the existing official Harness frontend on 3080.'
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$stamp = '{0}-{1}' -f (Get-Date -Format 'yyyyMMdd-HHmmssfff'), $PID
|
|
135
|
+
$stdout = Join-Path $logRoot ('dsh-official-web-{0}.out.log' -f $stamp)
|
|
136
|
+
$stderr = Join-Path $logRoot ('dsh-official-web-{0}.err.log' -f $stamp)
|
|
137
|
+
$previousHome = $env:DSH_HOME
|
|
138
|
+
try {
|
|
139
|
+
# The official application owns its normal state; no Crew plugin/profile
|
|
140
|
+
# registration or repair is performed in this home by the launcher.
|
|
141
|
+
$env:DSH_HOME = Join-Path $env:USERPROFILE '.dsh'
|
|
142
|
+
$arguments = @(('"{0}"' -f $official.Entry), 'web', '--patch', ('"{0}"' -f $frontend.Path), '--host', '127.0.0.1', '--port', '3080')
|
|
143
|
+
$process = Start-Process -FilePath $official.NodePath -ArgumentList $arguments -WindowStyle Hidden -PassThru `
|
|
144
|
+
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
145
|
+
} finally { $env:DSH_HOME = $previousHome }
|
|
146
|
+
Write-LaunchLog ('Started official Harness on 3080; PID={0}. The official CLI opens the browser. Logs: {1}, {2}' -f $process.Id, $stdout, $stderr)
|
|
147
|
+
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
148
|
+
$nextProgress = (Get-Date).AddSeconds(5)
|
|
149
|
+
do {
|
|
150
|
+
$port = Get-PortState -Port 3080
|
|
151
|
+
if ($port.State -eq 'occupied') {
|
|
152
|
+
if (-not (Test-OfficialHarnessListener -OwnerPid $port.Pid -Official $official)) { throw 'Port 3080 became occupied by an unverified process; it was left untouched.' }
|
|
153
|
+
if ((Test-OfficialWebReady) -and (Test-OfficialFrontendAttached -Revision $frontend.Revision)) { return }
|
|
154
|
+
}
|
|
155
|
+
if ($process.HasExited) { throw ('Official Harness exited before readiness. Diagnostic log: {0}' -f $stderr) }
|
|
156
|
+
if ((Get-Date) -ge $nextProgress) {
|
|
157
|
+
Write-LaunchLog 'Waiting for the official Harness frontend on 3080...'
|
|
158
|
+
$nextProgress = (Get-Date).AddSeconds(5)
|
|
159
|
+
}
|
|
160
|
+
Start-Sleep -Milliseconds 500
|
|
161
|
+
} while ((Get-Date) -lt $deadline)
|
|
162
|
+
throw ('Official Harness did not become ready within {0}s; inspect {1}. No official process was stopped.' -f $TimeoutSeconds, $stderr)
|
|
163
|
+
} finally {
|
|
164
|
+
if ($locked) { $mutex.ReleaseMutex() }
|
|
165
|
+
$mutex.Dispose()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
33
169
|
function Get-HealthState {
|
|
34
170
|
param([pscustomObject] $Service)
|
|
35
|
-
#
|
|
36
|
-
# is an external dependency: probe only its native web root, never the
|
|
37
|
-
# Crew bridge endpoint, so a missing legacy bridge cannot fail 3080 health.
|
|
171
|
+
# Preserve the installed extension health contract used by maintenance.
|
|
38
172
|
if ($Service.CrewOwned) {
|
|
39
173
|
try {
|
|
40
174
|
$response = Invoke-RestMethod -Uri ($Service.Url + '/_dsh/dsh-crew/extension') -TimeoutSec 2
|
|
@@ -71,28 +205,10 @@ function Get-HealthState {
|
|
|
71
205
|
return [pscustomobject]@{ Ready = $false; Version = $null; Error = $_.Exception.Message }
|
|
72
206
|
}
|
|
73
207
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# is NOT healthy.
|
|
79
|
-
$response = Invoke-WebRequest -UseBasicParsing -Uri $Service.Url -TimeoutSec 2
|
|
80
|
-
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) {
|
|
81
|
-
return [pscustomobject]@{ Ready = $true; Version = $null; Error = $null }
|
|
82
|
-
}
|
|
83
|
-
return [pscustomobject]@{ Ready = $false; Version = $null; Error = ('Official UI returned HTTP {0}.' -f $response.StatusCode) }
|
|
84
|
-
} catch {
|
|
85
|
-
# Invoke-WebRequest surfaces 401/403 as exceptions; those prove the web
|
|
86
|
-
# service is alive and answering (token gate present).
|
|
87
|
-
$status = $null
|
|
88
|
-
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
|
|
89
|
-
$status = [int] $_.Exception.Response.StatusCode
|
|
90
|
-
}
|
|
91
|
-
if ($status -eq 401 -or $status -eq 403) {
|
|
92
|
-
return [pscustomobject]@{ Ready = $true; Version = $null; Error = $null }
|
|
93
|
-
}
|
|
94
|
-
return [pscustomobject]@{ Ready = $false; Version = $null; Error = $_.Exception.Message }
|
|
95
|
-
}
|
|
208
|
+
# The launcher supervises only the Crew-owned 3210 service. Any non-Crew
|
|
209
|
+
# service is never started or health-gated here; the legacy 3080 bridge
|
|
210
|
+
# is probed only diagnostically after 3210 readiness (see Write-LegacyBridgeDiagnostic).
|
|
211
|
+
return [pscustomobject]@{ Ready = $false; Version = $null; Error = 'Unknown service: only the Crew-owned 3210 service is supervised.' }
|
|
96
212
|
}
|
|
97
213
|
|
|
98
214
|
# Optional legacy compatibility probe: returns true only when a 3080 that
|
|
@@ -117,17 +233,114 @@ $crewHeartbeatFile = Join-Path $crewSupervisorRoot 'heartbeat.json'
|
|
|
117
233
|
$crewRequestsDir = Join-Path $crewSupervisorRoot 'restart-requests'
|
|
118
234
|
$crewResultsDir = Join-Path $crewSupervisorRoot 'restart-results'
|
|
119
235
|
|
|
236
|
+
# UTF-8 WITHOUT BOM: Windows PowerShell 5.1's Set-Content -Encoding UTF8
|
|
237
|
+
# writes a BOM, and the Node hub's JSON.parse rejects it (heartbeat/request
|
|
238
|
+
# files would be unreadable -> supervisor judged unavailable).
|
|
239
|
+
function Write-Utf8NoBom {
|
|
240
|
+
param([string] $Path, [string] $Content)
|
|
241
|
+
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
242
|
+
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
|
|
243
|
+
}
|
|
244
|
+
|
|
120
245
|
function Write-SupervisorHeartbeat {
|
|
246
|
+
param([bool] $OwnershipReady = $false)
|
|
121
247
|
$now = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
122
|
-
$record = @{ schema_version = 1; pid = $PID; last_seen = $now; protocol_version = 1 } | ConvertTo-Json -Compress
|
|
248
|
+
$record = @{ schema_version = 1; supervisor_instance_id = $supervisorInstanceId; pid = $PID; process_started_at_utc_ticks = $launcherProcessStartedAtUtcTicks; helper_hash = $launcherHelperHash; ownership_ready = $OwnershipReady; last_seen = $now; protocol_version = 1 } | ConvertTo-Json -Compress
|
|
123
249
|
try {
|
|
124
250
|
if (-not (Test-Path -LiteralPath $crewSupervisorRoot -PathType Container)) { New-Item -ItemType Directory -Path $crewSupervisorRoot -Force | Out-Null }
|
|
125
251
|
$temp = Join-Path $crewSupervisorRoot ("heartbeat.{0}.tmp" -f $PID)
|
|
126
|
-
|
|
252
|
+
Write-Utf8NoBom -Path $temp -Content $record
|
|
127
253
|
Move-Item -LiteralPath $temp -Destination $crewHeartbeatFile -Force
|
|
128
254
|
} catch { /* heartbeat is best-effort */ }
|
|
129
255
|
}
|
|
130
256
|
|
|
257
|
+
function Get-SupervisorLaunchArguments {
|
|
258
|
+
param([string] $ScriptPath = $PSCommandPath)
|
|
259
|
+
if ([string]::IsNullOrWhiteSpace($ScriptPath) -or $ScriptPath.Contains('"')) {
|
|
260
|
+
throw 'A valid launcher script path is required.'
|
|
261
|
+
}
|
|
262
|
+
return @(
|
|
263
|
+
'-NoLogo',
|
|
264
|
+
'-NoProfile',
|
|
265
|
+
'-NonInteractive',
|
|
266
|
+
'-ExecutionPolicy', 'Bypass',
|
|
267
|
+
'-File', ('"{0}"' -f $ScriptPath),
|
|
268
|
+
'-Mode', 'watch'
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function Get-SupervisorHeartbeatRecord {
|
|
273
|
+
param([int] $MaxAgeSeconds = 30)
|
|
274
|
+
try {
|
|
275
|
+
if (-not (Test-Path -LiteralPath $crewHeartbeatFile -PathType Leaf)) { return $null }
|
|
276
|
+
$record = Get-Content -LiteralPath $crewHeartbeatFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
277
|
+
$now = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
278
|
+
$lastSeen = [long] $record.last_seen
|
|
279
|
+
$age = $now - $lastSeen
|
|
280
|
+
if ($record.schema_version -ne 1 -or $record.protocol_version -ne 1 -or [int] $record.pid -le 0) { return $null }
|
|
281
|
+
if ($age -lt -5000 -or $age -gt ([long] $MaxAgeSeconds * 1000)) { return $null }
|
|
282
|
+
$process = Get-Process -Id ([int] $record.pid) -ErrorAction Stop
|
|
283
|
+
$processStartProperty = $record.PSObject.Properties['process_started_at_utc_ticks']
|
|
284
|
+
if ($null -ne $processStartProperty -and $processStartProperty.Value) {
|
|
285
|
+
$expectedTicks = [long] $processStartProperty.Value
|
|
286
|
+
if ($process.StartTime.ToUniversalTime().Ticks -ne $expectedTicks) { return $null }
|
|
287
|
+
}
|
|
288
|
+
$ownershipProperty = $record.PSObject.Properties['ownership_ready']
|
|
289
|
+
$helperHashProperty = $record.PSObject.Properties['helper_hash']
|
|
290
|
+
$instanceProperty = $record.PSObject.Properties['supervisor_instance_id']
|
|
291
|
+
$identityReady = $null -ne $processStartProperty -and $processStartProperty.Value `
|
|
292
|
+
-and $null -ne $helperHashProperty -and $helperHashProperty.Value -match '^[a-f0-9]{64}$' `
|
|
293
|
+
-and $null -ne $instanceProperty -and -not [string]::IsNullOrWhiteSpace([string] $instanceProperty.Value)
|
|
294
|
+
$state = if ($null -eq $ownershipProperty) { 'legacy-v1' } elseif ($ownershipProperty.Value -eq $true -and $identityReady) { 'ready' } else { 'starting' }
|
|
295
|
+
return [pscustomobject]@{ State = $state; Record = $record }
|
|
296
|
+
} catch {
|
|
297
|
+
return $null
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function Get-FreshSupervisorHeartbeat {
|
|
302
|
+
param([int] $MaxAgeSeconds = 30)
|
|
303
|
+
$observed = Get-SupervisorHeartbeatRecord -MaxAgeSeconds $MaxAgeSeconds
|
|
304
|
+
if ($observed -and $observed.State -eq 'ready') { return $observed.Record }
|
|
305
|
+
return $null
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function Ensure-CrewSupervisorRunning {
|
|
309
|
+
param([int] $TimeoutSeconds = 90)
|
|
310
|
+
$watcher = $null
|
|
311
|
+
$observed = Get-SupervisorHeartbeatRecord
|
|
312
|
+
if ($observed -and $observed.State -eq 'legacy-v1') {
|
|
313
|
+
throw 'CREW_SUPERVISOR_UPGRADE_REQUIRED: a legacy watcher is active and must be handed off before interactive launch.'
|
|
314
|
+
}
|
|
315
|
+
if (-not $observed) {
|
|
316
|
+
$arguments = @(Get-SupervisorLaunchArguments -ScriptPath $PSCommandPath)
|
|
317
|
+
$watcher = Start-Process -FilePath 'powershell.exe' -ArgumentList $arguments -WindowStyle Hidden -PassThru
|
|
318
|
+
Write-LaunchLog ('Started persistent Crew supervisor; PID={0}.' -f $watcher.Id)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
$crew = $services | Where-Object { $_.CrewOwned } | Select-Object -First 1
|
|
322
|
+
if (-not $crew) { throw 'Crew-owned 3210 service definition is missing.' }
|
|
323
|
+
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
324
|
+
$nextProgress = (Get-Date).AddSeconds(5)
|
|
325
|
+
$lastHealthError = 'not checked'
|
|
326
|
+
do {
|
|
327
|
+
$heartbeat = Get-FreshSupervisorHeartbeat
|
|
328
|
+
$health = Get-HealthState $crew
|
|
329
|
+
$lastHealthError = $health.Error
|
|
330
|
+
if ($heartbeat -and $health.Ready) {
|
|
331
|
+
Write-LaunchLog ('Persistent supervisor ready; PID={0}; 3210 runtime={1}.' -f $heartbeat.pid, $health.Version)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
if ((Get-Date) -ge $nextProgress) {
|
|
335
|
+
Write-LaunchLog ('Waiting for Crew on 3210; {0:n0}s remaining. Health: {1}' -f [Math]::Max(0, ($deadline - (Get-Date)).TotalSeconds), $lastHealthError)
|
|
336
|
+
$nextProgress = (Get-Date).AddSeconds(5)
|
|
337
|
+
}
|
|
338
|
+
Start-Sleep -Milliseconds 500
|
|
339
|
+
} while ((Get-Date) -lt $deadline)
|
|
340
|
+
|
|
341
|
+
throw ('Persistent supervisor did not make 3210 ready within {0}s. Last health error: {1}' -f $TimeoutSeconds, $lastHealthError)
|
|
342
|
+
}
|
|
343
|
+
|
|
131
344
|
function Read-RestartRequests {
|
|
132
345
|
if (-not (Test-Path -LiteralPath $crewRequestsDir -PathType Container)) { return @() }
|
|
133
346
|
$requests = @()
|
|
@@ -157,7 +370,7 @@ function Write-RestartResult {
|
|
|
157
370
|
} | ConvertTo-Json -Depth 5
|
|
158
371
|
$file = Join-Path $crewResultsDir ("{0}.json" -f $Request.request_id)
|
|
159
372
|
$temp = Join-Path $crewResultsDir ("{0}.{1}.tmp" -f $Request.request_id, $PID)
|
|
160
|
-
|
|
373
|
+
Write-Utf8NoBom -Path $temp -Content $result
|
|
161
374
|
Move-Item -LiteralPath $temp -Destination $file -Force
|
|
162
375
|
} catch { /* best effort */ }
|
|
163
376
|
Remove-Item -LiteralPath (Join-Path $crewRequestsDir ("{0}.json" -f $Request.request_id)) -Force -ErrorAction SilentlyContinue
|
|
@@ -253,7 +466,7 @@ function Set-MaintenanceSession {
|
|
|
253
466
|
request_id = $Request.request_id
|
|
254
467
|
} | ConvertTo-Json -Compress
|
|
255
468
|
$temp = Join-Path $crewSupervisorRoot ("maintenance-session.{0}.tmp" -f $PID)
|
|
256
|
-
|
|
469
|
+
Write-Utf8NoBom -Path $temp -Content $session
|
|
257
470
|
Move-Item -LiteralPath $temp -Destination $crewMaintenanceSessionFile -Force
|
|
258
471
|
$back = Read-MaintenanceSession
|
|
259
472
|
if ($back -is [string]) { return $false }
|
|
@@ -304,7 +517,7 @@ function Write-MaintenanceResult {
|
|
|
304
517
|
} | ConvertTo-Json -Depth 5
|
|
305
518
|
$file = Join-Path $crewMaintenanceResultsDir ("{0}.json" -f $Request.request_id)
|
|
306
519
|
$temp = Join-Path $crewMaintenanceResultsDir ("{0}.{1}.tmp" -f $Request.request_id, $PID)
|
|
307
|
-
|
|
520
|
+
Write-Utf8NoBom -Path $temp -Content $result
|
|
308
521
|
Move-Item -LiteralPath $temp -Destination $file -Force
|
|
309
522
|
} catch { /* best effort */ }
|
|
310
523
|
Remove-Item -LiteralPath (Join-Path $crewMaintenanceRequestsDir ("{0}.json" -f $Request.request_id)) -Force -ErrorAction SilentlyContinue
|
|
@@ -342,7 +555,14 @@ function Invoke-CrewMaintenanceRequests {
|
|
|
342
555
|
$liveRuntimeId = $null
|
|
343
556
|
try {
|
|
344
557
|
$resp = Invoke-RestMethod -Uri 'http://127.0.0.1:3210/_dsh/dsh-crew/extension' -TimeoutSec 3
|
|
345
|
-
|
|
558
|
+
$runtime = $resp.extension.runtime
|
|
559
|
+
$canonical = $resp.ok -eq $true `
|
|
560
|
+
-and $runtime.service -eq 'dsh-crew-hub' `
|
|
561
|
+
-and $runtime.execution_plane -eq 'hub-3210' `
|
|
562
|
+
-and $runtime.profile -eq 'dsh-crew' `
|
|
563
|
+
-and ($runtime.listen_port -as [int]) -eq 3210 `
|
|
564
|
+
-and ($runtime.protocol_version -as [int]) -eq 1
|
|
565
|
+
if ($canonical) { $liveRuntimeId = $runtime.runtime_id }
|
|
346
566
|
} catch { $liveRuntimeId = $null }
|
|
347
567
|
if (-not $liveRuntimeId -or $liveRuntimeId -ne $request.runtime_id) {
|
|
348
568
|
Write-MaintenanceResult $request 'SUPERVISOR_OWNERSHIP_CONFLICT'
|
|
@@ -408,7 +628,14 @@ function Invoke-CrewMaintenanceRequests {
|
|
|
408
628
|
$resp = Invoke-RestMethod -Uri 'http://127.0.0.1:3210/_dsh/dsh-crew/extension' -TimeoutSec 5
|
|
409
629
|
if ($resp.ok -eq $true) { $newRuntime = $resp.extension.runtime }
|
|
410
630
|
} catch { $newRuntime = $null }
|
|
411
|
-
$ok = $newRuntime -and $newRuntime.runtime_id
|
|
631
|
+
$ok = $newRuntime -and $newRuntime.runtime_id `
|
|
632
|
+
-and $newRuntime.service -eq 'dsh-crew-hub' `
|
|
633
|
+
-and $newRuntime.execution_plane -eq 'hub-3210' `
|
|
634
|
+
-and $newRuntime.profile -eq 'dsh-crew' `
|
|
635
|
+
-and ($newRuntime.listen_port -as [int]) -eq 3210 `
|
|
636
|
+
-and ($newRuntime.protocol_version -as [int]) -eq 1 `
|
|
637
|
+
-and ($expectedCrew -eq $null -or $newRuntime.runtime_version -eq $expectedCrew) `
|
|
638
|
+
-and ($expectedDsh -eq $null -or $newRuntime.dsh_version -eq $expectedDsh)
|
|
412
639
|
if ($ok) {
|
|
413
640
|
# Verifiable one-shot consumption: only a provably-cleared session
|
|
414
641
|
# completes the transaction; otherwise the lease stays live and any
|
|
@@ -509,6 +736,27 @@ function Set-TrackedListenerIdentity {
|
|
|
509
736
|
}
|
|
510
737
|
}
|
|
511
738
|
|
|
739
|
+
function Test-CrewServiceOwnership {
|
|
740
|
+
param(
|
|
741
|
+
[pscustomObject] $Service,
|
|
742
|
+
[pscustomObject] $PortState = $null,
|
|
743
|
+
[object[]] $ProcessTable = $null
|
|
744
|
+
)
|
|
745
|
+
if (-not $Service.ListenerPid -or -not $Service.ListenerStartedAtUtcTicks) { return $false }
|
|
746
|
+
$port = if ($null -ne $PortState) { $PortState } else { Get-PortState $Service.Port }
|
|
747
|
+
if ($port.State -ne 'occupied' -or -not $port.Pid -or [int] $port.Pid -ne [int] $Service.ListenerPid) { return $false }
|
|
748
|
+
try {
|
|
749
|
+
$tree = if ($null -ne $ProcessTable) {
|
|
750
|
+
@(Get-TrackedProcessTree -Service $Service -ProcessTable $ProcessTable)
|
|
751
|
+
} else {
|
|
752
|
+
@(Get-TrackedProcessTree -Service $Service)
|
|
753
|
+
}
|
|
754
|
+
return ([int] $port.Pid -in $tree)
|
|
755
|
+
} catch {
|
|
756
|
+
return $false
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
512
760
|
function Stop-OwnedListener {
|
|
513
761
|
param([pscustomobject] $Service, [int] $ListenerPid)
|
|
514
762
|
if ($Mode -ne 'watch' -or -not $Service.ListenerPid -or $ListenerPid -ne $Service.ListenerPid) {
|
|
@@ -533,8 +781,19 @@ function Stop-OwnedListener {
|
|
|
533
781
|
return $false
|
|
534
782
|
}
|
|
535
783
|
|
|
784
|
+
function Assert-HistoryStartAllowed {
|
|
785
|
+
param([string] $StatePath = (Join-Path $crewHome '..\history\active.json'))
|
|
786
|
+
if (-not (Test-Path -LiteralPath $StatePath)) { return }
|
|
787
|
+
try {
|
|
788
|
+
$state = Get-Content -LiteralPath $StatePath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
789
|
+
if ($state.schemaVersion -eq 1 -and $state.phase -in @('STARTING', 'VERIFYING', 'DONE', 'FAILED', 'ROLLED_BACK')) { return }
|
|
790
|
+
} catch { }
|
|
791
|
+
throw 'HISTORY_RECOVERY_REQUIRED: Crew history maintenance is unfinished. Run dsh-crew history recover. Official 3080 is unchanged.'
|
|
792
|
+
}
|
|
793
|
+
|
|
536
794
|
function Start-CrewService {
|
|
537
795
|
param([pscustomobject] $Service)
|
|
796
|
+
Assert-HistoryStartAllowed
|
|
538
797
|
$serviceRunStamp = (Get-Date).ToString('yyyyMMdd-HHmmssfff')
|
|
539
798
|
$stdout = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.out.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
|
|
540
799
|
$stderr = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.err.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
|
|
@@ -564,9 +823,14 @@ function Wait-CrewServices {
|
|
|
564
823
|
$health = Get-HealthState $service
|
|
565
824
|
$service.LastError = $health.Error
|
|
566
825
|
if ($health.Ready) {
|
|
826
|
+
if ($service.CrewOwned -and -not $service.ListenerPid -and -not (Set-TrackedListenerIdentity -Service $service)) {
|
|
827
|
+
throw ('{0} is healthy on {1}, but this supervisor cannot prove process ownership.' -f $service.Name, $service.Port)
|
|
828
|
+
}
|
|
829
|
+
if ($service.CrewOwned -and -not (Test-CrewServiceOwnership -Service $service)) {
|
|
830
|
+
throw ('{0} is healthy on {1}, but its tracked listener identity is not owned.' -f $service.Name, $service.Port)
|
|
831
|
+
}
|
|
567
832
|
$service.State = 'ready'
|
|
568
833
|
$service.ConsecutiveFailures = 0
|
|
569
|
-
if ($service.CrewOwned) { [void] (Set-TrackedListenerIdentity -Service $service) }
|
|
570
834
|
Write-LaunchLog ('{0} ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
|
|
571
835
|
} elseif ($service.Process -and $service.Process.HasExited) {
|
|
572
836
|
throw ('{0} exited before becoming ready; PID={1}; exit={2}; last health error: {3}' -f $service.Name, $service.Process.Id, $service.Process.ExitCode, $health.Error)
|
|
@@ -584,9 +848,7 @@ function Wait-CrewServices {
|
|
|
584
848
|
}
|
|
585
849
|
}
|
|
586
850
|
|
|
587
|
-
#
|
|
588
|
-
# logs legacy bridge presence for operator awareness. Never gates, delays,
|
|
589
|
-
# or claims 3210; never runs on the boot path.
|
|
851
|
+
# Explicit diagnostic helper; not called by normal startup or supervision.
|
|
590
852
|
function Write-LegacyBridgeDiagnostic {
|
|
591
853
|
if (Test-LegacyBridgeAvailable) {
|
|
592
854
|
Write-LaunchLog 'Diagnostic: legacy 3080 bridge still answers ping; Crew launcher owns 3210 directly, bridge ignored.'
|
|
@@ -600,8 +862,7 @@ function Ensure-CrewServices {
|
|
|
600
862
|
# Maintenance fence: while a STOPPED maintenance session is active for
|
|
601
863
|
# the Crew backend, ordinary supervision must NOT auto-start 3210 —
|
|
602
864
|
# the stopped window belongs to the npx lifecycle's tree swap. Only a
|
|
603
|
-
# matching maintenance-start owns the launch right.
|
|
604
|
-
# continues normally during the window.
|
|
865
|
+
# matching maintenance-start owns the launch right.
|
|
605
866
|
if ($service.CrewOwned -and (Test-MaintenanceSessionActive)) {
|
|
606
867
|
$service.State = 'maintenance'
|
|
607
868
|
$service.LastError = $null
|
|
@@ -610,10 +871,15 @@ function Ensure-CrewServices {
|
|
|
610
871
|
$health = Get-HealthState $service
|
|
611
872
|
if ($health.Ready) {
|
|
612
873
|
$wasReady = $service.State -eq 'ready'
|
|
874
|
+
if ($service.CrewOwned -and -not $service.ListenerPid -and -not (Set-TrackedListenerIdentity -Service $service)) {
|
|
875
|
+
throw ('{0} is healthy on {1}, but this supervisor cannot prove process ownership.' -f $service.Name, $service.Port)
|
|
876
|
+
}
|
|
877
|
+
if ($service.CrewOwned -and -not (Test-CrewServiceOwnership -Service $service)) {
|
|
878
|
+
throw ('{0} is healthy on {1}, but its tracked listener identity is not owned.' -f $service.Name, $service.Port)
|
|
879
|
+
}
|
|
613
880
|
$service.State = 'ready'
|
|
614
881
|
$service.ConsecutiveFailures = 0
|
|
615
882
|
$service.LastError = $null
|
|
616
|
-
if ($service.CrewOwned -and -not $service.ListenerPid) { [void] (Set-TrackedListenerIdentity -Service $service) }
|
|
617
883
|
if (-not $QuietHealthy -or -not $wasReady) {
|
|
618
884
|
Write-LaunchLog ('{0} already ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
|
|
619
885
|
}
|
|
@@ -677,7 +943,11 @@ function Start-ServiceSupervisor {
|
|
|
677
943
|
return
|
|
678
944
|
}
|
|
679
945
|
|
|
680
|
-
|
|
946
|
+
# Publish process identity before any 3210 startup/health wait. Handoff
|
|
947
|
+
# callers can now distinguish a legitimate cold-starting target watcher
|
|
948
|
+
# from the stale heartbeat left by the watcher it replaced.
|
|
949
|
+
Write-SupervisorHeartbeat -OwnershipReady $false
|
|
950
|
+
Write-LaunchLog 'Supervisor active; monitoring Crew-owned 3210 every 10 seconds.'
|
|
681
951
|
$lastRecoveryError = $null
|
|
682
952
|
$updateLockFile = Join-Path $crewHome '..\app\update-in-progress.lock'
|
|
683
953
|
$staleLockNotified = $false
|
|
@@ -709,25 +979,30 @@ function Start-ServiceSupervisor {
|
|
|
709
979
|
} else {
|
|
710
980
|
$staleLockNotified = $false
|
|
711
981
|
}
|
|
982
|
+
# Maintenance stop/start is the updater's owned process handoff and
|
|
983
|
+
# must remain live while the update lock fences ordinary recovery.
|
|
984
|
+
Invoke-CrewMaintenanceRequests
|
|
712
985
|
if ($updateHeld) {
|
|
713
|
-
Write-LaunchLog 'Update in progress;
|
|
986
|
+
Write-LaunchLog 'Update in progress; maintenance only, ordinary recovery suspended.'
|
|
714
987
|
$lastRecoveryError = $null
|
|
715
988
|
} else {
|
|
716
989
|
# Control protocol: publish this watcher's heartbeat and consume
|
|
717
990
|
# any durable restart requests the hub wrote (3210 never spawns
|
|
718
|
-
# itself).
|
|
719
|
-
#
|
|
720
|
-
Write-SupervisorHeartbeat
|
|
991
|
+
# itself). Maintenance was consumed above; now run the ordinary
|
|
992
|
+
# health pass.
|
|
721
993
|
Invoke-CrewRestartRequests
|
|
722
|
-
Invoke-CrewMaintenanceRequests
|
|
723
994
|
Ensure-CrewServices -QuietHealthy
|
|
724
995
|
if ($lastRecoveryError) {
|
|
725
|
-
Write-LaunchLog 'Supervisor recovery succeeded;
|
|
996
|
+
Write-LaunchLog 'Supervisor recovery succeeded; Crew-owned 3210 is healthy.'
|
|
726
997
|
$lastRecoveryError = $null
|
|
727
998
|
}
|
|
728
999
|
}
|
|
1000
|
+
$ownedServices = @($services | Where-Object { $_.CrewOwned -and (Test-CrewServiceOwnership -Service $_) })
|
|
1001
|
+
$expectedServices = @($services | Where-Object { $_.CrewOwned })
|
|
1002
|
+
Write-SupervisorHeartbeat -OwnershipReady ($expectedServices.Count -gt 0 -and $ownedServices.Count -eq $expectedServices.Count)
|
|
729
1003
|
} catch {
|
|
730
1004
|
$recoveryError = $_.Exception.Message
|
|
1005
|
+
Write-SupervisorHeartbeat -OwnershipReady $false
|
|
731
1006
|
if ($recoveryError -ne $lastRecoveryError) {
|
|
732
1007
|
Write-LaunchLog ('Supervisor recovery failed; will retry: {0}' -f $recoveryError) 'WARN'
|
|
733
1008
|
$lastRecoveryError = $recoveryError
|
|
@@ -746,6 +1021,7 @@ if ($env:DSH_CREW_LAUNCHER_TEST_IMPORT -eq '1') { return }
|
|
|
746
1021
|
try {
|
|
747
1022
|
New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
|
|
748
1023
|
Write-LaunchLog ('Launcher started; mode={0}; user={1}' -f $Mode, $env:USERNAME)
|
|
1024
|
+
if ($Mode -eq 'open') { Open-OfficialFrontend }
|
|
749
1025
|
|
|
750
1026
|
if (-not (Test-Path -LiteralPath $dshCli -PathType Leaf)) {
|
|
751
1027
|
throw "DSH CLI was not found at $dshCli. Run: npm install -g @ran-sh/dsh-crew@latest; dsh-crew update"
|
|
@@ -753,9 +1029,7 @@ try {
|
|
|
753
1029
|
if (-not (Test-Path -LiteralPath (Join-Path $crewHome 'profiles\dsh-crew\package.json') -PathType Leaf)) {
|
|
754
1030
|
throw "The isolated dsh-crew profile is missing under $crewHome. Run: dsh-crew update"
|
|
755
1031
|
}
|
|
756
|
-
|
|
757
|
-
throw "The official web profile is missing under $officialHome. Repair or install the official DeepSeek Harness web profile outside dsh-crew; dsh-crew never mutates the official profile."
|
|
758
|
-
}
|
|
1032
|
+
# Background/watch modes remain independent of the official frontend.
|
|
759
1033
|
|
|
760
1034
|
if ($Mode -eq 'watch') {
|
|
761
1035
|
Start-ServiceSupervisor
|
|
@@ -763,12 +1037,10 @@ try {
|
|
|
763
1037
|
exit 0
|
|
764
1038
|
}
|
|
765
1039
|
|
|
766
|
-
Ensure-
|
|
767
|
-
Write-LegacyBridgeDiagnostic
|
|
1040
|
+
Ensure-CrewSupervisorRunning
|
|
768
1041
|
|
|
769
1042
|
if ($Mode -eq 'open') {
|
|
770
|
-
|
|
771
|
-
Write-LaunchLog 'Opened daily console at http://127.0.0.1:3080/'
|
|
1043
|
+
Write-LaunchLog 'Official frontend is on 3080; Crew is running silently on 3210.'
|
|
772
1044
|
}
|
|
773
1045
|
Write-LaunchLog ('Launcher completed successfully in {0:n1}s.' -f ((Get-Date) - $startedAt).TotalSeconds)
|
|
774
1046
|
exit 0
|