@ran-sh/dsh-crew 0.5.2 → 0.5.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/agents/ds-worker.md +5 -4
- package/codex/agents/ds-worker.toml +1 -1
- package/docs/job-contracts.md +12 -4
- package/docs/readiness-matrix.md +6 -2
- package/package.json +1 -1
- package/scripts/setup.mjs +62 -23
- package/src/config-readiness.mjs +50 -10
- package/src/hub/index.mjs +34 -9
- package/src/install/windows-startup.mjs +23 -6
- package/src/orchestrator.mjs +53 -0
- package/src/readiness-matrix.mjs +4 -3
- package/src/runtime-identity.mjs +1 -1
- package/src/server.mjs +38 -34
- package/src/workflow-runtime.mjs +27 -27
- package/src/workflow.mjs +63 -5
- package/windows/start-dsh-crew.cmd +51 -49
- package/windows/start-dsh-crew.ps1 +326 -0
- package/windows/start-dsh-crew.vbs +1 -1
- package/zcode/AGENTS.md +5 -0
- package/zcode/agents/ds-reviewer.md +21 -4
- package/zcode/agents/ds-worker.md +23 -4
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# DSH Crew managed Windows launcher
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param(
|
|
4
|
+
[ValidateSet('background', 'open', 'watch')]
|
|
5
|
+
[string] $Mode = 'open'
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
Set-StrictMode -Version Latest
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
|
|
11
|
+
$crewHome = Join-Path $env:USERPROFILE '.config\dsh-crew\harness'
|
|
12
|
+
$officialHome = Join-Path $env:USERPROFILE '.dsh'
|
|
13
|
+
$dshCli = Join-Path $crewHome 'runtime\node_modules\.bin\dsh.cmd'
|
|
14
|
+
$logRoot = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() }
|
|
15
|
+
$launcherLog = Join-Path $logRoot 'dsh-crew-launcher.log'
|
|
16
|
+
$startedAt = Get-Date
|
|
17
|
+
$services = @(
|
|
18
|
+
[pscustomobject]@{ Name = 'Crew backend'; Profile = 'dsh-crew'; Home = $crewHome; Port = 3210; Url = 'http://127.0.0.1:3210'; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null },
|
|
19
|
+
[pscustomobject]@{ Name = 'Official UI'; Profile = 'web'; Home = $officialHome; Port = 3080; Url = 'http://127.0.0.1:3080'; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null }
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function Write-LaunchLog {
|
|
23
|
+
param([string] $Message, [ValidateSet('INFO', 'WARN', 'ERROR')] [string] $Level = 'INFO')
|
|
24
|
+
$line = '[{0}] [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss.fffK'), $Level, $Message
|
|
25
|
+
Add-Content -LiteralPath $launcherLog -Value $line -Encoding UTF8
|
|
26
|
+
if ($Mode -eq 'open') {
|
|
27
|
+
if ($Level -eq 'ERROR') { Write-Host $line -ForegroundColor Red }
|
|
28
|
+
elseif ($Level -eq 'WARN') { Write-Host $line -ForegroundColor Yellow }
|
|
29
|
+
else { Write-Host $line }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Get-HealthState {
|
|
34
|
+
param([pscustomobject] $Service)
|
|
35
|
+
try {
|
|
36
|
+
$response = Invoke-RestMethod -Uri ($Service.Url + '/_dsh/dsh-crew/extension') -TimeoutSec 2
|
|
37
|
+
$version = $response.extension.runtime.runtime_version
|
|
38
|
+
if ($response.ok -eq $true -and $version) {
|
|
39
|
+
return [pscustomobject]@{ Ready = $true; Version = [string] $version; Error = $null }
|
|
40
|
+
}
|
|
41
|
+
return [pscustomobject]@{ Ready = $false; Version = $null; Error = 'Response did not contain a ready runtime contract.' }
|
|
42
|
+
} catch {
|
|
43
|
+
return [pscustomobject]@{ Ready = $false; Version = $null; Error = $_.Exception.Message }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Get-PortState {
|
|
48
|
+
param([int] $Port)
|
|
49
|
+
try {
|
|
50
|
+
$listeners = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().GetActiveTcpListeners()
|
|
51
|
+
$occupied = @($listeners | Where-Object Port -eq $Port).Count -gt 0
|
|
52
|
+
if (-not $occupied) {
|
|
53
|
+
return [pscustomobject]@{ State = 'free'; Error = $null; Pid = $null }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
$ownerPid = $null
|
|
57
|
+
try {
|
|
58
|
+
$ownerPid = (Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction Stop | Select-Object -First 1).OwningProcess
|
|
59
|
+
} catch { }
|
|
60
|
+
return [pscustomobject]@{ State = 'occupied'; Error = $null; Pid = $ownerPid }
|
|
61
|
+
} catch {
|
|
62
|
+
return [pscustomobject]@{ State = 'unknown'; Error = ('Listener enumeration failed: {0}' -f $_.Exception.Message); Pid = $null }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Test-TrackedProcessIdentity {
|
|
67
|
+
param([int] $ProcessId, [long] $ExpectedStartTicks, [object[]] $ProcessTable)
|
|
68
|
+
if ($ProcessId -lt 1 -or $ExpectedStartTicks -lt 1) { return $false }
|
|
69
|
+
$record = @($ProcessTable | Where-Object { [int] $_.ProcessId -eq $ProcessId } | Select-Object -First 1)
|
|
70
|
+
if ($record.Count -eq 0) { return $false }
|
|
71
|
+
$startTicks = $null
|
|
72
|
+
if ($record[0].PSObject.Properties.Name -contains 'StartTicks') {
|
|
73
|
+
$startTicks = [long] $record[0].StartTicks
|
|
74
|
+
} else {
|
|
75
|
+
try { $startTicks = (Get-Process -Id $ProcessId -ErrorAction Stop).StartTime.ToUniversalTime().Ticks } catch { return $false }
|
|
76
|
+
}
|
|
77
|
+
return $startTicks -eq $ExpectedStartTicks
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Get-TrackedProcessTree {
|
|
81
|
+
param([pscustomobject] $Service, [object[]] $ProcessTable = $null)
|
|
82
|
+
$processes = if ($null -ne $ProcessTable) { @($ProcessTable) } else { @(Get-CimInstance Win32_Process -ErrorAction Stop) }
|
|
83
|
+
$owned = [System.Collections.Generic.HashSet[int]]::new()
|
|
84
|
+
$rootMatches = Test-TrackedProcessIdentity -ProcessId $Service.RootPid -ExpectedStartTicks $Service.RootStartedAtUtcTicks -ProcessTable $processes
|
|
85
|
+
$hasTrackedListener = $Service.ListenerPid -and $Service.ListenerStartedAtUtcTicks
|
|
86
|
+
if ($hasTrackedListener) {
|
|
87
|
+
$listenerMatches = Test-TrackedProcessIdentity -ProcessId $Service.ListenerPid -ExpectedStartTicks $Service.ListenerStartedAtUtcTicks -ProcessTable $processes
|
|
88
|
+
if (-not $listenerMatches) { return @() }
|
|
89
|
+
[void] $owned.Add([int] $Service.ListenerPid)
|
|
90
|
+
} elseif (-not $rootMatches) {
|
|
91
|
+
return @()
|
|
92
|
+
}
|
|
93
|
+
if ($rootMatches) { [void] $owned.Add([int] $Service.RootPid) }
|
|
94
|
+
do {
|
|
95
|
+
$added = $false
|
|
96
|
+
foreach ($candidate in $processes) {
|
|
97
|
+
$candidateId = [int] $candidate.ProcessId
|
|
98
|
+
$parentId = [int] $candidate.ParentProcessId
|
|
99
|
+
if ($owned.Contains($parentId) -and $owned.Add($candidateId)) { $added = $true }
|
|
100
|
+
}
|
|
101
|
+
} while ($added)
|
|
102
|
+
return @($owned | ForEach-Object { [int] $_ })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function Set-TrackedListenerIdentity {
|
|
106
|
+
param([pscustomobject] $Service)
|
|
107
|
+
$port = Get-PortState $Service.Port
|
|
108
|
+
if ($port.State -ne 'occupied' -or -not $port.Pid) { return $false }
|
|
109
|
+
$tree = @(Get-TrackedProcessTree -Service $Service)
|
|
110
|
+
if ($port.Pid -notin $tree) { return $false }
|
|
111
|
+
try {
|
|
112
|
+
$listener = Get-Process -Id $port.Pid -ErrorAction Stop
|
|
113
|
+
$Service.ListenerPid = [int] $port.Pid
|
|
114
|
+
$Service.ListenerStartedAtUtcTicks = $listener.StartTime.ToUniversalTime().Ticks
|
|
115
|
+
return $true
|
|
116
|
+
} catch {
|
|
117
|
+
return $false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Stop-OwnedListener {
|
|
122
|
+
param([pscustomobject] $Service, [int] $ListenerPid)
|
|
123
|
+
if ($Mode -ne 'watch' -or -not $Service.ListenerPid -or $ListenerPid -ne $Service.ListenerPid) {
|
|
124
|
+
return $false
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
$ownedProcessIds = @(Get-TrackedProcessTree -Service $Service)
|
|
128
|
+
if ($ListenerPid -notin $ownedProcessIds) { return $false }
|
|
129
|
+
Write-LaunchLog ('Supervisor confirmed owned listener PID={0} under tracked root PID={1}; stopping that process tree after {2} consecutive failed health checks.' -f $ListenerPid, $Service.RootPid, $Service.ConsecutiveFailures) 'WARN'
|
|
130
|
+
foreach ($processId in $ownedProcessIds) {
|
|
131
|
+
Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue
|
|
132
|
+
}
|
|
133
|
+
$deadline = (Get-Date).AddSeconds(5)
|
|
134
|
+
do {
|
|
135
|
+
$port = Get-PortState $Service.Port
|
|
136
|
+
if ($port.State -eq 'free') { return $true }
|
|
137
|
+
Start-Sleep -Milliseconds 250
|
|
138
|
+
} while ((Get-Date) -lt $deadline)
|
|
139
|
+
} catch {
|
|
140
|
+
Write-LaunchLog ('Supervisor could not stop its owned listener safely: {0}' -f $_.Exception.Message) 'WARN'
|
|
141
|
+
}
|
|
142
|
+
return $false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function Start-CrewService {
|
|
146
|
+
param([pscustomobject] $Service)
|
|
147
|
+
$serviceRunStamp = (Get-Date).ToString('yyyyMMdd-HHmmssfff')
|
|
148
|
+
$stdout = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.out.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
|
|
149
|
+
$stderr = Join-Path $logRoot ('dsh-crew-{0}-{1}-{2}.err.log' -f $Service.Profile, $Service.Port, $serviceRunStamp)
|
|
150
|
+
$previousHome = $env:DSH_HOME
|
|
151
|
+
try {
|
|
152
|
+
$env:DSH_HOME = $Service.Home
|
|
153
|
+
$arguments = @('--profile', $Service.Profile, '--host', '127.0.0.1', '--port', [string] $Service.Port, '--no-open')
|
|
154
|
+
$process = Start-Process -FilePath $dshCli -ArgumentList $arguments -WindowStyle Hidden -PassThru `
|
|
155
|
+
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
156
|
+
$Service.Process = $process
|
|
157
|
+
$Service.RootPid = $process.Id
|
|
158
|
+
$Service.RootStartedAtUtcTicks = $process.StartTime.ToUniversalTime().Ticks
|
|
159
|
+
$Service.ListenerPid = $null
|
|
160
|
+
$Service.ListenerStartedAtUtcTicks = $null
|
|
161
|
+
$Service.ConsecutiveFailures = 0
|
|
162
|
+
$Service.State = 'starting'
|
|
163
|
+
Write-LaunchLog ('Started {0} on port {1}; PID={2}; stdout={3}; stderr={4}' -f $Service.Profile, $Service.Port, $process.Id, $stdout, $stderr)
|
|
164
|
+
} finally {
|
|
165
|
+
$env:DSH_HOME = $previousHome
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function Wait-CrewServices {
|
|
170
|
+
$deadline = (Get-Date).AddSeconds(90)
|
|
171
|
+
while (@($services | Where-Object State -eq 'starting').Count -gt 0 -and (Get-Date) -lt $deadline) {
|
|
172
|
+
foreach ($service in ($services | Where-Object State -eq 'starting')) {
|
|
173
|
+
$health = Get-HealthState $service
|
|
174
|
+
$service.LastError = $health.Error
|
|
175
|
+
if ($health.Ready) {
|
|
176
|
+
$service.State = 'ready'
|
|
177
|
+
$service.ConsecutiveFailures = 0
|
|
178
|
+
[void] (Set-TrackedListenerIdentity -Service $service)
|
|
179
|
+
Write-LaunchLog ('{0} ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
|
|
180
|
+
} elseif ($service.Process -and $service.Process.HasExited) {
|
|
181
|
+
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)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (@($services | Where-Object State -eq 'starting').Count -gt 0) { Start-Sleep -Milliseconds 500 }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
$notReady = @($services | Where-Object State -ne 'ready')
|
|
188
|
+
if ($notReady.Count -gt 0) {
|
|
189
|
+
$details = ($notReady | ForEach-Object { '{0}:{1} ({2})' -f $_.Profile, $_.Port, $_.LastError }) -join '; '
|
|
190
|
+
throw "Startup health deadline exceeded: $details"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function Ensure-CrewServices {
|
|
195
|
+
param([switch] $QuietHealthy)
|
|
196
|
+
|
|
197
|
+
foreach ($service in $services) {
|
|
198
|
+
$health = Get-HealthState $service
|
|
199
|
+
if ($health.Ready) {
|
|
200
|
+
$wasReady = $service.State -eq 'ready'
|
|
201
|
+
$service.State = 'ready'
|
|
202
|
+
$service.ConsecutiveFailures = 0
|
|
203
|
+
$service.LastError = $null
|
|
204
|
+
if (-not $service.ListenerPid) { [void] (Set-TrackedListenerIdentity -Service $service) }
|
|
205
|
+
if (-not $QuietHealthy -or -not $wasReady) {
|
|
206
|
+
Write-LaunchLog ('{0} already ready on {1}; runtime={2}' -f $service.Name, $service.Port, $health.Version)
|
|
207
|
+
}
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
$service.LastError = $health.Error
|
|
212
|
+
$service.ConsecutiveFailures += 1
|
|
213
|
+
if ($service.Process -and $service.Process.HasExited) {
|
|
214
|
+
Write-LaunchLog ('{0} process exited after startup; PID={1}; exit={2}' -f $service.Name, $service.Process.Id, $service.Process.ExitCode) 'WARN'
|
|
215
|
+
$service.Process = $null
|
|
216
|
+
}
|
|
217
|
+
$service.State = 'pending'
|
|
218
|
+
|
|
219
|
+
$port = Get-PortState $service.Port
|
|
220
|
+
if ($port.State -eq 'occupied') {
|
|
221
|
+
if ($service.ConsecutiveFailures -lt 3) {
|
|
222
|
+
throw ('Health check {0}/3 failed for {1}; owned process remains untouched until failure is confirmed. Health error: {2}' -f $service.ConsecutiveFailures, $service.Name, $health.Error)
|
|
223
|
+
}
|
|
224
|
+
if (Stop-OwnedListener -Service $service -ListenerPid $port.Pid) {
|
|
225
|
+
$service.Process = $null
|
|
226
|
+
$service.RootPid = $null
|
|
227
|
+
$service.RootStartedAtUtcTicks = $null
|
|
228
|
+
$service.ListenerPid = $null
|
|
229
|
+
$service.ListenerStartedAtUtcTicks = $null
|
|
230
|
+
$service.ConsecutiveFailures = 0
|
|
231
|
+
$port = Get-PortState $service.Port
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if ($port.State -eq 'occupied') {
|
|
235
|
+
$owner = if ($port.Pid) { "; listener PID=$($port.Pid)" } else { '' }
|
|
236
|
+
throw ('Port {0} is occupied, but {1} failed its health contract{2}. Health error: {3}' -f $service.Port, $service.Name, $owner, $health.Error)
|
|
237
|
+
}
|
|
238
|
+
if ($port.State -ne 'free') {
|
|
239
|
+
throw ('Could not determine whether port {0} is available: {1}' -f $service.Port, $port.Error)
|
|
240
|
+
}
|
|
241
|
+
if ($Mode -eq 'watch') {
|
|
242
|
+
Write-LaunchLog ('Supervisor detected {0} unavailable on {1}; restarting it.' -f $service.Name, $service.Port) 'WARN'
|
|
243
|
+
}
|
|
244
|
+
Start-CrewService $service
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
Wait-CrewServices
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function Start-ServiceSupervisor {
|
|
251
|
+
$mutex = [System.Threading.Mutex]::new($false, 'Local\DSHCrewServiceSupervisor')
|
|
252
|
+
$ownsMutex = $false
|
|
253
|
+
try {
|
|
254
|
+
try {
|
|
255
|
+
$ownsMutex = $mutex.WaitOne(0)
|
|
256
|
+
} catch [System.Threading.AbandonedMutexException] {
|
|
257
|
+
$ownsMutex = $true
|
|
258
|
+
}
|
|
259
|
+
if (-not $ownsMutex) {
|
|
260
|
+
Write-LaunchLog 'Supervisor already active; duplicate watcher exiting.'
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Write-LaunchLog 'Supervisor active; monitoring 3080 and 3210 every 10 seconds.'
|
|
265
|
+
$lastRecoveryError = $null
|
|
266
|
+
while ($true) {
|
|
267
|
+
try {
|
|
268
|
+
Ensure-CrewServices -QuietHealthy
|
|
269
|
+
if ($lastRecoveryError) {
|
|
270
|
+
Write-LaunchLog 'Supervisor recovery succeeded; both services are healthy.'
|
|
271
|
+
$lastRecoveryError = $null
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
$recoveryError = $_.Exception.Message
|
|
275
|
+
if ($recoveryError -ne $lastRecoveryError) {
|
|
276
|
+
Write-LaunchLog ('Supervisor recovery failed; will retry: {0}' -f $recoveryError) 'WARN'
|
|
277
|
+
$lastRecoveryError = $recoveryError
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
Start-Sleep -Seconds 10
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
if ($ownsMutex) { $mutex.ReleaseMutex() }
|
|
284
|
+
$mutex.Dispose()
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if ($env:DSH_CREW_LAUNCHER_TEST_IMPORT -eq '1') { return }
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
|
|
292
|
+
Write-LaunchLog ('Launcher started; mode={0}; user={1}' -f $Mode, $env:USERNAME)
|
|
293
|
+
|
|
294
|
+
if (-not (Test-Path -LiteralPath $dshCli -PathType Leaf)) {
|
|
295
|
+
throw "DSH CLI was not found at $dshCli. Run: npm install -g @ran-sh/dsh-crew@latest; dsh-crew update"
|
|
296
|
+
}
|
|
297
|
+
if (-not (Test-Path -LiteralPath (Join-Path $crewHome 'profiles\dsh-crew\package.json') -PathType Leaf)) {
|
|
298
|
+
throw "The isolated dsh-crew profile is missing under $crewHome. Run: dsh-crew update"
|
|
299
|
+
}
|
|
300
|
+
if (-not (Test-Path -LiteralPath (Join-Path $officialHome 'profiles\web\package.json') -PathType Leaf)) {
|
|
301
|
+
throw "The official web profile is missing under $officialHome. Run: dsh-crew integrate"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if ($Mode -eq 'watch') {
|
|
305
|
+
Start-ServiceSupervisor
|
|
306
|
+
Write-LaunchLog 'Supervisor stopped.' 'WARN'
|
|
307
|
+
exit 0
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
Ensure-CrewServices
|
|
311
|
+
|
|
312
|
+
if ($Mode -eq 'open') {
|
|
313
|
+
Start-Process 'http://127.0.0.1:3080/' | Out-Null
|
|
314
|
+
Write-LaunchLog 'Opened daily console at http://127.0.0.1:3080/'
|
|
315
|
+
}
|
|
316
|
+
Write-LaunchLog ('Launcher completed successfully in {0:n1}s.' -f ((Get-Date) - $startedAt).TotalSeconds)
|
|
317
|
+
exit 0
|
|
318
|
+
} catch {
|
|
319
|
+
Write-LaunchLog $_.Exception.Message 'ERROR'
|
|
320
|
+
if ($Mode -eq 'open') {
|
|
321
|
+
Write-Host ''
|
|
322
|
+
Write-Host "Diagnostic log: $launcherLog" -ForegroundColor Yellow
|
|
323
|
+
Write-Host 'Startup failed. Review the diagnostic log and the service stdout/stderr paths recorded above.' -ForegroundColor Yellow
|
|
324
|
+
}
|
|
325
|
+
exit 1
|
|
326
|
+
}
|
|
@@ -4,6 +4,6 @@ Dim shell, launcher, command
|
|
|
4
4
|
Set shell = CreateObject("WScript.Shell")
|
|
5
5
|
launcher = "__LAUNCHER__"
|
|
6
6
|
command = shell.ExpandEnvironmentStrings("%COMSPEC%") & " /d /c " & _
|
|
7
|
-
Chr(34) & Chr(34) & launcher & Chr(34) & Chr(34)
|
|
7
|
+
Chr(34) & Chr(34) & launcher & Chr(34) & " --watch" & Chr(34)
|
|
8
8
|
|
|
9
9
|
shell.Run command, 0, False
|
package/zcode/AGENTS.md
CHANGED
|
@@ -10,6 +10,11 @@ work only after its live capability and readiness surfaces have been checked.
|
|
|
10
10
|
repository/worktree and Result Contract boundaries. Keep planning, ambiguous
|
|
11
11
|
requirements, integration, external side effects and final communication in
|
|
12
12
|
the host agent.
|
|
13
|
+
- Dispatch selected work asynchronously with `dsh_spawn_worker`, save its
|
|
14
|
+
workflow ID, and poll that same workflow with `dsh_worker_result` using
|
|
15
|
+
`wait_seconds: 10`. A bounded wait that returns a nonterminal state is not a
|
|
16
|
+
failure when `dsh_worker_status` confirms that workflow is still running;
|
|
17
|
+
continue polling it and never start a duplicate workflow.
|
|
13
18
|
- If Crew is selected and any required capability is unavailable, non-callable,
|
|
14
19
|
or returns an unknown/runtime/configuration/credential/routing/timeout error,
|
|
15
20
|
pause. Report the evidence and wait for the operator to choose repair Crew or
|
|
@@ -5,6 +5,7 @@ mcpServers:
|
|
|
5
5
|
- dsh-crew
|
|
6
6
|
tools:
|
|
7
7
|
- mcp__dsh-crew__dsh_run_worker
|
|
8
|
+
- mcp__dsh-crew__dsh_spawn_worker
|
|
8
9
|
- mcp__dsh-crew__dsh_worker_status
|
|
9
10
|
- mcp__dsh-crew__dsh_worker_result
|
|
10
11
|
- mcp__dsh-crew__dsh_worker_cancel
|
|
@@ -13,7 +14,23 @@ tools:
|
|
|
13
14
|
|
|
14
15
|
You are a thin, read-only dispatcher. Never edit files or implement fixes.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Check `dsh_worker_config` before dispatch. Derive one bounded, read-only review
|
|
18
|
+
task that contains only the code, acceptance criteria and validation evidence
|
|
19
|
+
the Reviewer must inspect. Keep harness-reporting requirements in this
|
|
20
|
+
dispatcher, then call `dsh_spawn_worker` with role `reviewer` and the current
|
|
21
|
+
workspace as `cwd`; omit effort unless explicitly requested. Save the returned
|
|
22
|
+
workflow ID.
|
|
23
|
+
|
|
24
|
+
Poll that same workflow through `dsh_worker_result` with `wait_seconds: 10`.
|
|
25
|
+
If a bounded result wait expires or returns a nonterminal state, check
|
|
26
|
+
`dsh_worker_status` once. When it confirms the workflow is still running,
|
|
27
|
+
continue polling the same workflow; never start a duplicate. A genuine
|
|
28
|
+
transport, runtime, configuration, credential or routing error is a hard stop
|
|
29
|
+
and must be reported to the operator.
|
|
30
|
+
|
|
31
|
+
Treat the workflow ID as host structured-result metadata; never ask the
|
|
32
|
+
Reviewer to discover or report its workflow ID, provider, model or other
|
|
33
|
+
harness metadata. Do not forward workflow ID, provider, model or status-reporting
|
|
34
|
+
fields as Reviewer task requirements. Combine the host-owned metadata with
|
|
35
|
+
Review Findings, Evidence, Risks and Verdict. Treat failing tests or incomplete
|
|
36
|
+
evidence as not approved.
|
|
@@ -14,7 +14,26 @@ tools:
|
|
|
14
14
|
|
|
15
15
|
You are a thin dispatcher. You never do the task yourself.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Check `dsh_worker_config` before dispatch. Derive one bounded, domain-only task
|
|
18
|
+
for the Worker, preserving the requested implementation and validation scope.
|
|
19
|
+
Keep harness-reporting requirements in this dispatcher, then call
|
|
20
|
+
`dsh_spawn_worker` with role `worker` and the current workspace as `cwd`; omit
|
|
21
|
+
effort unless the task explicitly requests it. Save the returned workflow ID.
|
|
22
|
+
When the operator explicitly requests read-only search or analysis that should
|
|
23
|
+
make zero file changes, pass `constraints: { allow_no_changes: true }`.
|
|
24
|
+
Otherwise omit that constraint so implementation work still fails closed when
|
|
25
|
+
the reported diff and isolated workspace disagree.
|
|
26
|
+
|
|
27
|
+
Poll that same workflow through `dsh_worker_result` with `wait_seconds: 10`.
|
|
28
|
+
If a bounded result wait expires or returns a nonterminal state, check
|
|
29
|
+
`dsh_worker_status` once. When it confirms the workflow is still running,
|
|
30
|
+
continue polling the same workflow; never start a duplicate. A genuine
|
|
31
|
+
transport, runtime, configuration, credential or routing error is a hard stop
|
|
32
|
+
and must be reported to the operator.
|
|
33
|
+
|
|
34
|
+
Treat the workflow ID as host structured-result metadata; never ask the Worker
|
|
35
|
+
to discover or report its workflow ID, provider, model or other harness
|
|
36
|
+
metadata. Do not forward workflow ID, provider, model or status-reporting fields
|
|
37
|
+
as Worker task requirements. If the workflow is `done`, combine the host-owned
|
|
38
|
+
metadata with its compact result and evidence footer. Otherwise report the
|
|
39
|
+
terminal error and stop reason clearly.
|