@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.
Files changed (85) hide show
  1. package/.claude-plugin/marketplace.json +16 -17
  2. package/.claude-plugin/plugin.json +7 -1
  3. package/README.md +44 -15
  4. package/README.zh.md +37 -12
  5. package/agents/ds-flash.md +16 -18
  6. package/agents/ds-pro.md +16 -24
  7. package/agents/ds-reviewer.md +15 -15
  8. package/agents/ds-worker.md +16 -16
  9. package/bin/dsh-crew.mjs +16 -12
  10. package/codex/AGENTS.md +37 -85
  11. package/codex/agents/ds-flash.toml +18 -14
  12. package/codex/agents/ds-pro.toml +18 -15
  13. package/codex/agents/ds-reviewer.toml +17 -13
  14. package/codex/agents/ds-worker.toml +17 -13
  15. package/codex/prompts/dsh-config.md +17 -1
  16. package/codex/prompts/dsh-status.md +5 -1
  17. package/commands/config.md +16 -4
  18. package/commands/status.md +6 -2
  19. package/docs/installation.md +54 -9
  20. package/docs/readiness-matrix.md +14 -6
  21. package/docs/ui-surfaces.md +32 -17
  22. package/lib/client.js +5434 -4844
  23. package/official-web-bridge/lib/client.js +567 -479
  24. package/official-web-bridge/overlay-entry.mjs +59 -0
  25. package/package.json +6 -3
  26. package/scripts/build-client.mjs +2 -0
  27. package/scripts/setup.mjs +8 -7
  28. package/scripts/verify-crew-ui-polish.mjs +145 -0
  29. package/scripts/verify-history-ui.mjs +97 -0
  30. package/scripts/verify-npm-install.mjs +1 -1
  31. package/src/client/history-panel.tsx +108 -0
  32. package/src/client/host-readiness.mjs +15 -6
  33. package/src/client/index.tsx +86 -64
  34. package/src/client/model-callability-view.mjs +17 -0
  35. package/src/client/panel-chrome.tsx +40 -0
  36. package/src/client/quick-panel.tsx +88 -73
  37. package/src/client/readiness-envelope.mjs +34 -0
  38. package/src/config-readiness.mjs +181 -115
  39. package/src/extension-contract.mjs +44 -26
  40. package/src/history/admission-gate.mjs +51 -0
  41. package/src/history/archive-store.mjs +261 -0
  42. package/src/history/cleanup-plan.mjs +64 -0
  43. package/src/history/http.mjs +32 -0
  44. package/src/history/operation.mjs +86 -0
  45. package/src/history/runner.mjs +51 -0
  46. package/src/history/runtime.mjs +28 -0
  47. package/src/history/service.mjs +137 -0
  48. package/src/history/state.mjs +27 -0
  49. package/src/hub/index.mjs +141 -24
  50. package/src/hub-client.mjs +17 -15
  51. package/src/image-output.mjs +71 -0
  52. package/src/install/install-legacy.mjs +126 -23
  53. package/src/install/npx-lifecycle.mjs +373 -64
  54. package/src/install/official-frontend-assets.mjs +78 -0
  55. package/src/install/payload-content.mjs +88 -0
  56. package/src/install/windows-startup.mjs +107 -19
  57. package/src/install/windows-supervisor-adapter.mjs +443 -0
  58. package/src/install/windows-supervisor-lifecycle.mjs +782 -0
  59. package/src/install/zcode.mjs +53 -14
  60. package/src/job-contracts.mjs +37 -31
  61. package/src/jobs.mjs +2 -2
  62. package/src/mcp-runtime.mjs +74 -9
  63. package/src/model-callability-contract.mjs +79 -0
  64. package/src/multimodal.mjs +0 -0
  65. package/src/official-web-bridge.mjs +0 -2
  66. package/src/provider-health.mjs +130 -124
  67. package/src/provider-profile-store.mjs +12 -0
  68. package/src/provider-settings-store.mjs +347 -5
  69. package/src/readiness-matrix.mjs +20 -15
  70. package/src/runtime-identity-contract.mjs +34 -0
  71. package/src/runtime-identity.mjs +14 -4
  72. package/src/runtime-readiness-snapshot.mjs +285 -87
  73. package/src/server.mjs +86 -76
  74. package/src/standalone-sdk.mjs +23 -0
  75. package/src/supervisor/restart-request.mjs +64 -10
  76. package/src/workflow-runtime.mjs +28 -13
  77. package/windows/start-dsh-crew.cmd +57 -57
  78. package/windows/start-dsh-crew.ps1 +327 -55
  79. package/windows/supervisor-control.ps1 +467 -0
  80. package/zcode/AGENTS.md +4 -1
  81. package/zcode/agents/ds-reviewer.md +31 -36
  82. package/zcode/agents/ds-worker.md +31 -39
  83. package/zcode/commands/dsh-config.md +17 -6
  84. package/zcode/commands/dsh-status.md +5 -4
  85. package/.mcp.json +0 -8
@@ -0,0 +1,467 @@
1
+ # DSH Crew Windows supervisor process control
2
+ #
3
+ # Reads one JSON request from stdin and writes exactly one JSON response to
4
+ # stdout. This helper deliberately controls one verified watcher PID at a
5
+ # time; it never uses process-tree termination.
6
+ [CmdletBinding()]
7
+ param()
8
+
9
+ Set-StrictMode -Version Latest
10
+ $ErrorActionPreference = 'Stop'
11
+
12
+ Add-Type -TypeDefinition @'
13
+ using System;
14
+ using System.Runtime.InteropServices;
15
+
16
+ namespace DshCrewSupervisorControl {
17
+ public static class NativeProcess {
18
+ private const int ProcessCommandLineInformation = 60;
19
+
20
+ [StructLayout(LayoutKind.Sequential)]
21
+ private struct UNICODE_STRING {
22
+ public ushort Length;
23
+ public ushort MaximumLength;
24
+ public IntPtr Buffer;
25
+ }
26
+
27
+ [DllImport("ntdll.dll")]
28
+ private static extern int NtQueryInformationProcess(
29
+ IntPtr process,
30
+ int informationClass,
31
+ IntPtr information,
32
+ int informationLength,
33
+ out int returnLength);
34
+
35
+ public static string ReadCommandLine(IntPtr process) {
36
+ int length;
37
+ NtQueryInformationProcess(process, ProcessCommandLineInformation, IntPtr.Zero, 0, out length);
38
+ if (length <= 0 || length > 1024 * 1024) {
39
+ throw new InvalidOperationException("Process command line length is unavailable or unsafe.");
40
+ }
41
+ IntPtr buffer = Marshal.AllocHGlobal(length);
42
+ try {
43
+ int returned;
44
+ int status = NtQueryInformationProcess(process, ProcessCommandLineInformation, buffer, length, out returned);
45
+ if (status != 0) throw new InvalidOperationException("Process command line query failed.");
46
+ UNICODE_STRING value = (UNICODE_STRING)Marshal.PtrToStructure(buffer, typeof(UNICODE_STRING));
47
+ if (value.Buffer == IntPtr.Zero || value.Length == 0) return String.Empty;
48
+ return Marshal.PtrToStringUni(value.Buffer, value.Length / 2);
49
+ } finally {
50
+ Marshal.FreeHGlobal(buffer);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ '@ | Out-Null
56
+
57
+ function Throw-ControlFailure {
58
+ param(
59
+ [Parameter(Mandatory = $true)] [string] $Code,
60
+ [Parameter(Mandatory = $true)] [string] $Message
61
+ )
62
+ $failure = New-Object System.InvalidOperationException($Message)
63
+ $failure.Data['dsh_crew_control_code'] = $Code
64
+ throw $failure
65
+ }
66
+
67
+ function Get-RequestProperty {
68
+ param(
69
+ [Parameter(Mandatory = $true)] [psobject] $Request,
70
+ [Parameter(Mandatory = $true)] [string] $Name,
71
+ [bool] $Required = $false
72
+ )
73
+ $property = $Request.PSObject.Properties[$Name]
74
+ if ($null -eq $property) {
75
+ if ($Required) { Throw-ControlFailure 'REQUEST_INVALID' ("Missing required field: {0}." -f $Name) }
76
+ return $null
77
+ }
78
+ return $property.Value
79
+ }
80
+
81
+ function Get-IdentitySource {
82
+ param([Parameter(Mandatory = $true)] [psobject] $Request)
83
+ $expected = Get-RequestProperty -Request $Request -Name 'expected'
84
+ if ($null -ne $expected) {
85
+ if ($expected -isnot [psobject] -or $expected -is [string]) {
86
+ Throw-ControlFailure 'REQUEST_INVALID' 'The expected field must be an object.'
87
+ }
88
+ return $expected
89
+ }
90
+ return $Request
91
+ }
92
+
93
+ function Get-CanonicalHelperPath {
94
+ param([object] $Value)
95
+ if ($Value -isnot [string] -or [string]::IsNullOrWhiteSpace($Value) -or $Value.Length -gt 32768) {
96
+ Throw-ControlFailure 'HELPER_PATH_INVALID' 'helper_path must be a non-empty absolute path.'
97
+ }
98
+ if (-not [System.IO.Path]::IsPathRooted($Value) -or $Value.Contains('"')) {
99
+ Throw-ControlFailure 'HELPER_PATH_INVALID' 'helper_path must be a safe absolute path.'
100
+ }
101
+ try {
102
+ $fullPath = [System.IO.Path]::GetFullPath($Value)
103
+ } catch {
104
+ Throw-ControlFailure 'HELPER_PATH_INVALID' 'helper_path could not be normalized.'
105
+ }
106
+ if (-not [string]::Equals([System.IO.Path]::GetFileName($fullPath), 'start-dsh-crew.ps1', [System.StringComparison]::OrdinalIgnoreCase)) {
107
+ Throw-ControlFailure 'HELPER_PATH_INVALID' 'The managed helper must be named start-dsh-crew.ps1.'
108
+ }
109
+ if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) {
110
+ Throw-ControlFailure 'HELPER_PATH_NOT_FOUND' 'The expected managed helper does not exist.'
111
+ }
112
+ try {
113
+ return (Get-Item -LiteralPath $fullPath -Force -ErrorAction Stop).FullName
114
+ } catch {
115
+ Throw-ControlFailure 'HELPER_PATH_INVALID' 'The expected managed helper cannot be inspected.'
116
+ }
117
+ }
118
+
119
+ function Get-HelperHash {
120
+ param([Parameter(Mandatory = $true)] [string] $HelperPath)
121
+ $stream = $null
122
+ $sha256 = $null
123
+ try {
124
+ $stream = [System.IO.File]::Open($HelperPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
125
+ $sha256 = [System.Security.Cryptography.SHA256]::Create()
126
+ return ([System.BitConverter]::ToString($sha256.ComputeHash($stream))).Replace('-', '').ToLowerInvariant()
127
+ } catch {
128
+ Throw-ControlFailure 'HELPER_HASH_UNAVAILABLE' ('The expected managed helper could not be hashed: {0}' -f $_.Exception.Message)
129
+ } finally {
130
+ if ($null -ne $sha256) { $sha256.Dispose() }
131
+ if ($null -ne $stream) { $stream.Dispose() }
132
+ }
133
+ }
134
+
135
+ function Get-ExpectedIdentity {
136
+ param(
137
+ [Parameter(Mandatory = $true)] [psobject] $Request,
138
+ [bool] $RequireExact = $false
139
+ )
140
+ $source = Get-IdentitySource -Request $Request
141
+ $pidValue = Get-RequestProperty -Request $source -Name 'pid' -Required $true
142
+ try {
143
+ $pid64 = [long] $pidValue
144
+ } catch {
145
+ Throw-ControlFailure 'REQUEST_INVALID' 'pid must be a positive integer.'
146
+ }
147
+ if ($pid64 -le 0 -or $pid64 -gt [int]::MaxValue -or $pidValue.ToString() -notmatch '^\d+$') {
148
+ Throw-ControlFailure 'REQUEST_INVALID' 'pid must be a positive integer.'
149
+ }
150
+
151
+ $ticksValue = Get-RequestProperty -Request $source -Name 'process_started_at_utc_ticks' -Required $RequireExact
152
+ if ($null -ne $ticksValue -and ($ticksValue -isnot [string] -or $ticksValue -notmatch '^\d+$' -or $ticksValue -eq '0')) {
153
+ Throw-ControlFailure 'REQUEST_INVALID' 'process_started_at_utc_ticks must be a positive decimal string.'
154
+ }
155
+
156
+ $hashValue = Get-RequestProperty -Request $source -Name 'helper_hash' -Required $RequireExact
157
+ if ($null -ne $hashValue -and ($hashValue -isnot [string] -or $hashValue -notmatch '^[a-fA-F0-9]{64}$')) {
158
+ Throw-ControlFailure 'REQUEST_INVALID' 'helper_hash must be a SHA-256 hex digest when supplied.'
159
+ }
160
+ return [pscustomobject]@{
161
+ pid = [int] $pid64
162
+ process_started_at_utc_ticks = $ticksValue
163
+ helper_hash = if ($null -eq $hashValue) { $null } else { $hashValue.ToLowerInvariant() }
164
+ }
165
+ }
166
+
167
+ function Test-SamePath {
168
+ param([string] $Left, [string] $Right)
169
+ try {
170
+ $leftFull = [System.IO.Path]::GetFullPath($Left)
171
+ $rightFull = [System.IO.Path]::GetFullPath($Right)
172
+ return [string]::Equals($leftFull, $rightFull, [System.StringComparison]::OrdinalIgnoreCase)
173
+ } catch {
174
+ return $false
175
+ }
176
+ }
177
+
178
+ function ConvertFrom-WindowsCommandLine {
179
+ param([Parameter(Mandatory = $true)] [string] $CommandLine)
180
+ $tokens = New-Object System.Collections.Generic.List[string]
181
+ $length = $CommandLine.Length
182
+ $index = 0
183
+ while ($index -lt $length) {
184
+ while ($index -lt $length -and [char]::IsWhiteSpace($CommandLine[$index])) { $index++ }
185
+ if ($index -ge $length) { break }
186
+
187
+ $value = New-Object System.Text.StringBuilder
188
+ $inQuotes = $false
189
+ while ($index -lt $length) {
190
+ $character = $CommandLine[$index]
191
+ if ($character -eq '\') {
192
+ $slashStart = $index
193
+ while ($index -lt $length -and $CommandLine[$index] -eq '\') { $index++ }
194
+ $slashCount = $index - $slashStart
195
+ if ($index -lt $length -and $CommandLine[$index] -eq '"') {
196
+ for ($slash = 0; $slash -lt [Math]::Floor($slashCount / 2); $slash++) { $value.Append('\') | Out-Null }
197
+ if ($slashCount % 2 -eq 0) { $inQuotes = -not $inQuotes } else { $value.Append('"') | Out-Null }
198
+ $index++
199
+ } else {
200
+ for ($slash = 0; $slash -lt $slashCount; $slash++) { $value.Append('\') | Out-Null }
201
+ }
202
+ continue
203
+ }
204
+ if ($character -eq '"') {
205
+ if ($inQuotes -and $index + 1 -lt $length -and $CommandLine[$index + 1] -eq '"') {
206
+ $value.Append('"') | Out-Null
207
+ $index += 2
208
+ } else {
209
+ $inQuotes = -not $inQuotes
210
+ $index++
211
+ }
212
+ continue
213
+ }
214
+ if (-not $inQuotes -and [char]::IsWhiteSpace($character)) { break }
215
+ $value.Append($character) | Out-Null
216
+ $index++
217
+ }
218
+ $tokens.Add($value.ToString())
219
+ while ($index -lt $length -and [char]::IsWhiteSpace($CommandLine[$index])) { $index++ }
220
+ }
221
+ return $tokens.ToArray()
222
+ }
223
+
224
+ function Test-ExactManagedCommand {
225
+ param(
226
+ [Parameter(Mandatory = $true)] [string] $CommandLine,
227
+ [Parameter(Mandatory = $true)] [string] $HelperPath
228
+ )
229
+ $tokens = @(ConvertFrom-WindowsCommandLine -CommandLine $CommandLine)
230
+ # MainModule already proves token 0 is the canonical powershell.exe. Require
231
+ # the complete remaining argv so additional script arguments or alternate
232
+ # execution modes cannot hide behind one valid -File/-Mode pair.
233
+ if ($tokens.Count -ne 10) { return $false }
234
+ $expected = @('-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File')
235
+ for ($index = 0; $index -lt $expected.Count; $index++) {
236
+ if (-not [string]::Equals($tokens[$index + 1], $expected[$index], [System.StringComparison]::OrdinalIgnoreCase)) { return $false }
237
+ }
238
+ if (-not (Test-SamePath -Left $tokens[7] -Right $HelperPath)) { return $false }
239
+ if (-not [string]::Equals($tokens[8], '-Mode', [System.StringComparison]::OrdinalIgnoreCase)) { return $false }
240
+ return [string]::Equals($tokens[9], 'watch', [System.StringComparison]::OrdinalIgnoreCase)
241
+ }
242
+
243
+ function Open-VerifiedManagedWatcher {
244
+ param(
245
+ [Parameter(Mandatory = $true)] [string] $HelperPath,
246
+ [Parameter(Mandatory = $true)] [pscustomobject] $Expected,
247
+ [bool] $AllowHelperDrift = $false
248
+ )
249
+ $helperHash = Get-HelperHash -HelperPath $HelperPath
250
+ if (-not $AllowHelperDrift -and $null -ne $Expected.helper_hash -and -not [string]::Equals($Expected.helper_hash, $helperHash, [System.StringComparison]::OrdinalIgnoreCase)) {
251
+ Throw-ControlFailure 'HELPER_HASH_MISMATCH' 'The managed helper hash no longer matches the expected watcher identity.'
252
+ }
253
+
254
+ $process = $null
255
+ try {
256
+ $process = Get-Process -Id $Expected.pid -ErrorAction Stop
257
+ } catch {
258
+ Throw-ControlFailure 'PROCESS_NOT_FOUND' 'The expected watcher PID is not live.'
259
+ }
260
+ try {
261
+ # Force one native process handle open and keep it live through every
262
+ # identity check (and through Kill for stop). Windows does not recycle the
263
+ # PID while this process object holds the underlying kernel object.
264
+ $openedHandle = $process.Handle
265
+ if ($openedHandle -eq [IntPtr]::Zero) { throw 'process handle is unavailable' }
266
+ $actualTicks = $process.StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)
267
+ } catch {
268
+ if ($null -ne $process) { $process.Dispose() }
269
+ Throw-ControlFailure 'PROCESS_METADATA_UNAVAILABLE' 'The expected watcher process identity metadata is unavailable.'
270
+ }
271
+ if ($null -ne $Expected.process_started_at_utc_ticks -and -not [string]::Equals($actualTicks, $Expected.process_started_at_utc_ticks, [System.StringComparison]::Ordinal)) {
272
+ $process.Dispose()
273
+ Throw-ControlFailure 'PROCESS_START_TIME_MISMATCH' 'The watcher PID belongs to a different process lifetime.'
274
+ }
275
+
276
+ try {
277
+ $executablePath = $process.MainModule.FileName
278
+ $commandLine = [DshCrewSupervisorControl.NativeProcess]::ReadCommandLine($openedHandle)
279
+ } catch {
280
+ $process.Dispose()
281
+ Throw-ControlFailure 'PROCESS_METADATA_UNAVAILABLE' 'The watcher process metadata could not be read.'
282
+ }
283
+ if ([string]::IsNullOrWhiteSpace($executablePath)) {
284
+ $process.Dispose()
285
+ Throw-ControlFailure 'PROCESS_METADATA_UNAVAILABLE' 'The watcher executable path is unavailable.'
286
+ }
287
+ $expectedPowerShellPath = Join-Path $PSHOME 'powershell.exe'
288
+ if (-not [string]::Equals(
289
+ [System.IO.Path]::GetFullPath($executablePath),
290
+ [System.IO.Path]::GetFullPath($expectedPowerShellPath),
291
+ [System.StringComparison]::OrdinalIgnoreCase
292
+ )) {
293
+ $process.Dispose()
294
+ Throw-ControlFailure 'PROCESS_EXECUTABLE_MISMATCH' 'The watcher PID is not powershell.exe.'
295
+ }
296
+ if ([string]::IsNullOrWhiteSpace($commandLine) -or -not (Test-ExactManagedCommand -CommandLine $commandLine -HelperPath $HelperPath)) {
297
+ $process.Dispose()
298
+ Throw-ControlFailure 'PROCESS_COMMAND_MISMATCH' 'The watcher command line does not name the exact managed helper in watch mode.'
299
+ }
300
+
301
+ return [pscustomobject]@{
302
+ Process = $process
303
+ Watcher = [pscustomobject]@{
304
+ pid = $Expected.pid
305
+ process_started_at_utc_ticks = $actualTicks
306
+ # A running PowerShell process has already loaded its script. During an
307
+ # upgrade the stable helper file is replaced before handoff, so its
308
+ # current disk hash is the target hash, not the old process's attested
309
+ # hash. Drift is allowed only with the full PID/start/hash identity and
310
+ # the exact managed command line; preserve that attested old hash here.
311
+ helper_hash = if ($AllowHelperDrift) { $Expected.helper_hash } else { $helperHash }
312
+ }
313
+ }
314
+ }
315
+
316
+ function Get-ExactManagedWatcher {
317
+ param(
318
+ [Parameter(Mandatory = $true)] [string] $HelperPath,
319
+ [Parameter(Mandatory = $true)] [pscustomobject] $Expected,
320
+ [bool] $AllowHelperDrift = $false
321
+ )
322
+ $opened = Open-VerifiedManagedWatcher -HelperPath $HelperPath -Expected $Expected -AllowHelperDrift $AllowHelperDrift
323
+ try {
324
+ return $opened.Watcher
325
+ } finally {
326
+ $opened.Process.Dispose()
327
+ }
328
+ }
329
+
330
+ function Start-ExactManagedWatcher {
331
+ param([Parameter(Mandatory = $true)] [string] $HelperPath)
332
+ $powerShellPath = Join-Path $PSHOME 'powershell.exe'
333
+ if (-not (Test-Path -LiteralPath $powerShellPath -PathType Leaf)) {
334
+ Throw-ControlFailure 'POWERSHELL_NOT_FOUND' 'powershell.exe is unavailable.'
335
+ }
336
+ $startInfo = New-Object System.Diagnostics.ProcessStartInfo
337
+ $startInfo.FileName = $powerShellPath
338
+ $startInfo.Arguments = ('-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}" -Mode watch' -f $HelperPath)
339
+ # Shell execution prevents the long-lived watcher from inheriting this
340
+ # controller's JSON stdout/stderr pipe handles.
341
+ $startInfo.UseShellExecute = $true
342
+ $startInfo.CreateNoWindow = $true
343
+ $startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
344
+ try {
345
+ $started = [System.Diagnostics.Process]::Start($startInfo)
346
+ if ($null -eq $started) { throw 'Process.Start returned no process.' }
347
+ $startedTicks = $started.StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)
348
+ } catch {
349
+ Throw-ControlFailure 'PROCESS_START_FAILED' 'The managed watcher could not be started.'
350
+ }
351
+
352
+ $expected = [pscustomobject]@{
353
+ pid = [int] $started.Id
354
+ process_started_at_utc_ticks = $startedTicks
355
+ helper_hash = (Get-HelperHash -HelperPath $HelperPath)
356
+ }
357
+ $lastFailure = $null
358
+ Start-Sleep -Milliseconds 100
359
+ try {
360
+ return Get-ExactManagedWatcher -HelperPath $HelperPath -Expected $expected
361
+ } catch {
362
+ $lastFailure = $_.Exception
363
+ }
364
+
365
+ # The Process object identifies the exact child started above. Cleanup is
366
+ # single-process only; no child tree or unrelated PID is touched.
367
+ try {
368
+ if (-not $started.HasExited) { $started.Kill() }
369
+ } catch { }
370
+ if ($null -ne $lastFailure -and $lastFailure.Data.Contains('dsh_crew_control_code')) {
371
+ Throw-ControlFailure ([string] $lastFailure.Data['dsh_crew_control_code']) $lastFailure.Message
372
+ }
373
+ Throw-ControlFailure 'PROCESS_START_VERIFY_FAILED' 'The newly started managed watcher could not be verified.'
374
+ }
375
+
376
+ function Stop-ExactManagedWatcher {
377
+ param(
378
+ [Parameter(Mandatory = $true)] [string] $HelperPath,
379
+ [Parameter(Mandatory = $true)] [pscustomobject] $Expected,
380
+ [bool] $AllowHelperDrift = $false
381
+ )
382
+ $opened = Open-VerifiedManagedWatcher -HelperPath $HelperPath -Expected $Expected -AllowHelperDrift $AllowHelperDrift
383
+ $watcher = $opened.Watcher
384
+ try {
385
+ $opened.Process.Kill()
386
+ if (-not $opened.Process.WaitForExit(5000)) {
387
+ Throw-ControlFailure 'PROCESS_STOP_TIMEOUT' 'The exact managed watcher did not exit before the timeout.'
388
+ }
389
+ } catch {
390
+ if ($_.Exception.Data.Contains('dsh_crew_control_code')) { throw }
391
+ Throw-ControlFailure 'PROCESS_STOP_FAILED' 'The exact managed watcher could not be stopped.'
392
+ } finally {
393
+ $opened.Process.Dispose()
394
+ }
395
+ return $watcher
396
+ }
397
+
398
+ $response = $null
399
+ $exitCode = 0
400
+ try {
401
+ $rawRequest = [Console]::In.ReadToEnd()
402
+ if ([string]::IsNullOrWhiteSpace($rawRequest)) {
403
+ Throw-ControlFailure 'REQUEST_INVALID' 'A JSON request is required on stdin.'
404
+ }
405
+ try {
406
+ $request = $rawRequest | ConvertFrom-Json -ErrorAction Stop
407
+ } catch {
408
+ Throw-ControlFailure 'REQUEST_JSON_INVALID' 'The stdin request is not valid JSON.'
409
+ }
410
+ if ($null -eq $request -or $request -is [System.Array] -or $request -is [string]) {
411
+ Throw-ControlFailure 'REQUEST_INVALID' 'The JSON request must be an object.'
412
+ }
413
+ $operationValue = Get-RequestProperty -Request $request -Name 'operation' -Required $true
414
+ if ($operationValue -isnot [string]) {
415
+ Throw-ControlFailure 'REQUEST_INVALID' 'operation must be a string.'
416
+ }
417
+ $operation = $operationValue.ToLowerInvariant()
418
+ if ($operation -notin @('inspect', 'start', 'stop')) {
419
+ Throw-ControlFailure 'OPERATION_UNSUPPORTED' 'operation must be inspect, start, or stop.'
420
+ }
421
+ $helperPath = Get-CanonicalHelperPath (Get-RequestProperty -Request $request -Name 'helper_path' -Required $true)
422
+ $allowHelperDriftValue = Get-RequestProperty -Request $request -Name 'allow_helper_drift'
423
+ if ($null -ne $allowHelperDriftValue -and $allowHelperDriftValue -isnot [bool]) {
424
+ Throw-ControlFailure 'REQUEST_INVALID' 'allow_helper_drift must be a boolean when supplied.'
425
+ }
426
+ $allowHelperDrift = $allowHelperDriftValue -eq $true
427
+ if ($operation -eq 'start' -and $allowHelperDrift) {
428
+ Throw-ControlFailure 'REQUEST_INVALID' 'allow_helper_drift is not valid for start.'
429
+ }
430
+
431
+ if ($operation -eq 'start') {
432
+ $requestedHash = Get-RequestProperty -Request $request -Name 'helper_hash' -Required $true
433
+ if ($requestedHash -isnot [string] -or $requestedHash -notmatch '^[a-fA-F0-9]{64}$') {
434
+ Throw-ControlFailure 'REQUEST_INVALID' 'helper_hash must be the expected SHA-256 digest for start.'
435
+ }
436
+ $actualHash = Get-HelperHash -HelperPath $helperPath
437
+ if (-not [string]::Equals($requestedHash, $actualHash, [System.StringComparison]::OrdinalIgnoreCase)) {
438
+ Throw-ControlFailure 'HELPER_HASH_MISMATCH' 'Refusing to start a managed helper whose hash differs from the target.'
439
+ }
440
+ $watcher = Start-ExactManagedWatcher -HelperPath $helperPath
441
+ $response = [pscustomobject]@{ ok = $true; operation = 'start'; watcher = $watcher }
442
+ } else {
443
+ $expected = Get-ExpectedIdentity -Request $request -RequireExact ($operation -eq 'stop' -or $allowHelperDrift)
444
+ if ($operation -eq 'inspect') {
445
+ $watcher = Get-ExactManagedWatcher -HelperPath $helperPath -Expected $expected -AllowHelperDrift $allowHelperDrift
446
+ $response = [pscustomobject]@{ ok = $true; operation = 'inspect'; watcher = $watcher }
447
+ } else {
448
+ $watcher = Stop-ExactManagedWatcher -HelperPath $helperPath -Expected $expected -AllowHelperDrift $allowHelperDrift
449
+ $response = [pscustomobject]@{ ok = $true; operation = 'stop'; stopped = $true; watcher = $watcher }
450
+ }
451
+ }
452
+ } catch {
453
+ $exitCode = 1
454
+ $code = if ($_.Exception.Data.Contains('dsh_crew_control_code')) {
455
+ [string] $_.Exception.Data['dsh_crew_control_code']
456
+ } else {
457
+ 'SUPERVISOR_CONTROL_FAILED'
458
+ }
459
+ $response = [pscustomobject]@{
460
+ ok = $false
461
+ code = $code
462
+ error = $_.Exception.Message
463
+ }
464
+ }
465
+
466
+ [Console]::Out.WriteLine(($response | ConvertTo-Json -Compress -Depth 6))
467
+ exit $exitCode
package/zcode/AGENTS.md CHANGED
@@ -18,9 +18,12 @@ work only after its live capability and readiness surfaces have been checked.
18
18
  - If Crew is selected and any required capability is unavailable, non-callable,
19
19
  or returns an unknown/runtime/configuration/credential/routing/timeout error,
20
20
  pause. Report the evidence and wait for the operator to choose repair Crew or
21
- continue locally; never silently fall back or retry blindly.
21
+ continue locally; do not repair/reconfigure, silently fall back or retry blindly
22
+ before that decision. A nonterminal bounded wait is not a transport timeout.
22
23
  - Validate returned evidence, changed scope, tests and completion state before
23
24
  accepting delegated work. Do not expose credentials or raw provider payloads.
25
+ - Continue the authorized task after a successful subtask; do not stop at its
26
+ checkpoint. Missing evidence or requested review changes are not approval.
24
27
 
25
28
  ## Harness upgrade redlines
26
29
 
@@ -1,36 +1,31 @@
1
- ---
2
- name: ds-reviewer
3
- description: Independent DSH Crew reviewer dispatcher. Inspect completed changes read-only and return a structured verdict.
4
- mcpServers:
5
- - dsh-crew
6
- tools:
7
- - mcp__dsh-crew__dsh_run_worker
8
- - mcp__dsh-crew__dsh_spawn_worker
9
- - mcp__dsh-crew__dsh_worker_status
10
- - mcp__dsh-crew__dsh_worker_result
11
- - mcp__dsh-crew__dsh_worker_cancel
12
- - mcp__dsh-crew__dsh_worker_config
13
- ---
14
-
15
- You are a thin, read-only dispatcher. Never edit files or implement fixes.
16
-
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.
1
+ ---
2
+ name: ds-reviewer
3
+ description: Independent DSH Crew reviewer dispatcher. Inspect completed changes read-only and return a structured verdict.
4
+ mcpServers:
5
+ - dsh-crew
6
+ tools:
7
+ - mcp__dsh-crew__dsh_run_worker
8
+ - mcp__dsh-crew__dsh_spawn_worker
9
+ - mcp__dsh-crew__dsh_worker_status
10
+ - mcp__dsh-crew__dsh_worker_result
11
+ - mcp__dsh-crew__dsh_worker_cancel
12
+ - mcp__dsh-crew__dsh_worker_config
13
+ ---
14
+
15
+ Thin dispatcher only: do not edit files or perform the delegated task locally.
16
+
17
+ 1. Read dsh_worker_config if policy is unknown/changed; honor Auto, Manual and
18
+ disabled capabilities. Use role "reviewer".
19
+ 2. Call dsh_spawn_worker with the complete bounded objective, owned scope, cwd,
20
+ constraints and acceptance evidence; exclude unrelated chat history.
21
+ Backend policy selects models. Omit effort unless explicitly requested.
22
+ 3. Save the workflow ID. Follow the same job_id via dsh_worker_result with
23
+ compact detail and wait_seconds: 10. Running is not failure; never redispatch a duplicate.
24
+ 4. Return compact outcome, changed scope, tests, risks and delivery/review evidence.
25
+ Done alone is not success; failing tests or incomplete evidence are not approval.
26
+ Keep workflow ID and model metadata host-owned; do not forward workflow ID,
27
+ provider or model metadata as task requirements. Never invent missing counters.
28
+
29
+ If required Crew capability fails, report bounded evidence and await the operator's
30
+ repair-or-local decision. Do not repair, fall back or switch tiers yourself.
31
+ Review is read-only; report requested fixes, do not implement them.
@@ -1,39 +1,31 @@
1
- ---
2
- name: ds-worker
3
- description: DSH Crew worker dispatcher for implementation, fixes, tests, search and analysis. Never implement locally; return the auditable Crew result.
4
- mcpServers:
5
- - dsh-crew
6
- tools:
7
- - mcp__dsh-crew__dsh_run_worker
8
- - mcp__dsh-crew__dsh_spawn_worker
9
- - mcp__dsh-crew__dsh_worker_status
10
- - mcp__dsh-crew__dsh_worker_result
11
- - mcp__dsh-crew__dsh_worker_cancel
12
- - mcp__dsh-crew__dsh_worker_config
13
- ---
14
-
15
- You are a thin dispatcher. You never do the task yourself.
16
-
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.
1
+ ---
2
+ name: ds-worker
3
+ description: DSH Crew worker dispatcher for implementation, fixes, tests, search and analysis. Never implement locally; return the auditable Crew result.
4
+ mcpServers:
5
+ - dsh-crew
6
+ tools:
7
+ - mcp__dsh-crew__dsh_run_worker
8
+ - mcp__dsh-crew__dsh_spawn_worker
9
+ - mcp__dsh-crew__dsh_worker_status
10
+ - mcp__dsh-crew__dsh_worker_result
11
+ - mcp__dsh-crew__dsh_worker_cancel
12
+ - mcp__dsh-crew__dsh_worker_config
13
+ ---
14
+
15
+ Thin dispatcher only: do not edit files or perform the delegated task locally.
16
+
17
+ 1. Read dsh_worker_config if policy is unknown/changed; honor Auto, Manual and
18
+ disabled capabilities. Use role "worker".
19
+ 2. Call dsh_spawn_worker with the complete bounded objective, owned scope, cwd,
20
+ constraints and acceptance evidence; exclude unrelated chat history.
21
+ Backend policy selects models. Omit effort unless explicitly requested.
22
+ For explicitly read-only work only, use constraints: { allow_no_changes: true }.
23
+ 3. Save the workflow ID. Follow the same job_id via dsh_worker_result with
24
+ compact detail and wait_seconds: 10. Running is not failure; never redispatch a duplicate.
25
+ 4. Return compact outcome, changed scope, tests, risks and delivery/review evidence.
26
+ Done alone is not success; failing tests or incomplete evidence are not approval.
27
+ Keep workflow ID and model metadata host-owned; do not forward workflow ID,
28
+ provider or model metadata as task requirements. Never invent missing counters.
29
+
30
+ If required Crew capability fails, report bounded evidence and await the operator's
31
+ repair-or-local decision. Do not repair, fall back or switch tiers yourself.
@@ -1,6 +1,17 @@
1
- Parse arguments after this command as key=value pairs and call the
2
- `dsh_worker_config` tool. Supported keys include enabled, tier, effort, mode,
3
- timeout, policy, escalate, collab, main, flash, pro, review and reset. With no
4
- arguments read the current configuration. Show one compact table including
5
- hub_reachable, effective flash/pro state, effective policy and routing guidance.
6
- Name changed fields. Reply in the user's language and do nothing else.
1
+ Read or update this session with dsh_worker_config. With no arguments, pass {}.
2
+ For changes, parse only the user's requested key=value pairs and validate against
3
+ the current tool schema; do not guess unsupported fields or change global defaults.
4
+
5
+ Aliases → tool fields:
6
+ - enabled → enabled; tier → default_tier; effort → default_effort
7
+ - mode → mode; timeout → default_timeout_seconds; policy → tier_policy
8
+ - escalate → escalate_on_failure; collab → collaboration_mode; main → main_agent_mode
9
+ - flash → flash_state; pro → pro_state; review → pro_reviews_flash
10
+ - reset → reset: true
11
+
12
+ Current writable modes are auto|hub; standalone is legacy read/migration only.
13
+ Crew effort is off|high|max, not the host model's reasoning setting.
14
+ Other values must match the live schema. Report the returned effective policy,
15
+ activation boundaries and changed fields; do not promise that a reset or policy
16
+ alias restores a particular global state without returned evidence.
17
+ Use a compact response in the user's language. No unrelated actions.
@@ -1,4 +1,5 @@
1
- Call the `dsh_worker_status` tool and show one compact table with job id,
2
- tier/effort, status and current tool, progress, tokens and task summary. If
3
- there are no jobs, say so in one line. Reply in the user's language and do
4
- nothing else.
1
+ Call dsh_worker_status. Summarize the returned workflow IDs, role, phase, task
2
+ and available progress/evidence in a compact list or table. Show tokens/model
3
+ metadata only when returned; missing values are unknown, not zero. If no jobs
4
+ exist, say so. This is read-only: do not dispatch, cancel or alter configuration.
5
+ Reply in the user's language.