gsd-pi 2.57.0 → 2.58.0-dev.d63175c

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 (194) hide show
  1. package/dist/resources/extensions/gsd/auto/infra-errors.js +4 -0
  2. package/dist/resources/extensions/gsd/auto-dispatch.js +3 -3
  3. package/dist/resources/extensions/gsd/auto-worktree.js +7 -2
  4. package/dist/resources/extensions/gsd/auto.js +4 -0
  5. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -1
  6. package/dist/resources/extensions/gsd/dispatch-guard.js +11 -1
  7. package/dist/resources/extensions/gsd/gsd-db.js +8 -1
  8. package/dist/resources/extensions/gsd/parallel-orchestrator.js +23 -6
  9. package/dist/resources/extensions/gsd/preferences.js +29 -15
  10. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  11. package/dist/resources/extensions/gsd/tools/validate-milestone.js +4 -0
  12. package/dist/web/standalone/.next/BUILD_ID +1 -1
  13. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  14. package/dist/web/standalone/.next/build-manifest.json +4 -4
  15. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  16. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  17. package/dist/web/standalone/.next/required-server-files.json +4 -4
  18. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  21. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  29. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  57. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  85. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  91. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  107. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  109. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/index.html +1 -1
  121. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  122. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  123. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  124. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  126. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/page.js +2 -2
  128. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  130. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  131. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  132. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/middleware.js +2 -2
  135. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  137. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  138. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  139. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  140. package/dist/web/standalone/.next/static/chunks/6502.8b732f67a11b11b4.js +9 -0
  141. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  142. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  143. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  144. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  145. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/{webpack-4332cbd5dd1be584.js → webpack-61d3afac6d0f0ce7.js} +1 -1
  147. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  148. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  149. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  150. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  152. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  153. package/dist/web/standalone/server.js +1 -1
  154. package/package.json +1 -1
  155. package/packages/daemon/src/cli.ts +49 -0
  156. package/packages/daemon/src/daemon.test.ts +104 -1
  157. package/packages/daemon/src/daemon.ts +24 -1
  158. package/packages/daemon/src/discord-bot.ts +73 -3
  159. package/packages/daemon/src/event-bridge.ts +15 -9
  160. package/packages/daemon/src/event-formatter.ts +30 -2
  161. package/packages/daemon/src/index.ts +9 -0
  162. package/packages/daemon/src/launchd.test.ts +356 -0
  163. package/packages/daemon/src/launchd.ts +242 -0
  164. package/packages/daemon/src/message-batcher.test.ts +2 -2
  165. package/packages/daemon/src/message-batcher.ts +9 -3
  166. package/packages/daemon/src/orchestrator.test.ts +1 -0
  167. package/packages/daemon/src/orchestrator.ts +106 -2
  168. package/packages/pi-coding-agent/package.json +1 -1
  169. package/pkg/package.json +1 -1
  170. package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
  171. package/src/resources/extensions/gsd/auto-dispatch.ts +3 -3
  172. package/src/resources/extensions/gsd/auto-worktree.ts +7 -2
  173. package/src/resources/extensions/gsd/auto.ts +5 -0
  174. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -1
  175. package/src/resources/extensions/gsd/dispatch-guard.ts +12 -1
  176. package/src/resources/extensions/gsd/gsd-db.ts +6 -1
  177. package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -6
  178. package/src/resources/extensions/gsd/preferences.ts +32 -14
  179. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  180. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +18 -0
  181. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +47 -0
  182. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +9 -8
  183. package/src/resources/extensions/gsd/tests/preferences.test.ts +34 -0
  184. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -0
  185. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +23 -1
  186. package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +44 -2
  187. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +175 -0
  188. package/src/resources/extensions/gsd/tools/validate-milestone.ts +5 -0
  189. package/dist/web/standalone/.next/static/chunks/6502.2305d0afd2385711.js +0 -9
  190. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  191. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  192. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  193. /package/dist/web/standalone/.next/static/{yowc5qPtuKxjOr22KmOAy → 5DLsjFHdSB6_a1EDQVjr7}/_buildManifest.js +0 -0
  194. /package/dist/web/standalone/.next/static/{yowc5qPtuKxjOr22KmOAy → 5DLsjFHdSB6_a1EDQVjr7}/_ssgManifest.js +0 -0
@@ -0,0 +1,242 @@
1
+ import { writeFileSync, unlinkSync, existsSync, chmodSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { execSync } from 'node:child_process';
5
+ import { dirname } from 'node:path';
6
+
7
+ // --------------- types ---------------
8
+
9
+ export interface PlistOptions {
10
+ /** Absolute path to the Node.js binary */
11
+ nodePath: string;
12
+ /** Absolute path to the daemon script (cli.js) */
13
+ scriptPath: string;
14
+ /** Absolute path to the config file */
15
+ configPath: string;
16
+ /** Directory to use as WorkingDirectory in the plist (defaults to homedir) */
17
+ workingDirectory?: string;
18
+ /** Override stdout log path */
19
+ stdoutPath?: string;
20
+ /** Override stderr log path */
21
+ stderrPath?: string;
22
+ }
23
+
24
+ export interface LaunchdStatus {
25
+ /** Whether the daemon is registered with launchd */
26
+ registered: boolean;
27
+ /** PID if currently running, null otherwise */
28
+ pid: number | null;
29
+ /** Last exit status code, null if never exited or not available */
30
+ lastExitStatus: number | null;
31
+ }
32
+
33
+ export type RunCommandFn = (cmd: string) => string;
34
+
35
+ // --------------- constants ---------------
36
+
37
+ const LABEL = 'com.gsd.daemon';
38
+ const PLIST_FILENAME = `${LABEL}.plist`;
39
+
40
+ // --------------- helpers ---------------
41
+
42
+ /** Escape special XML characters in a string. */
43
+ export function escapeXml(str: string): string {
44
+ return str
45
+ .replace(/&/g, '&')
46
+ .replace(/</g, '&lt;')
47
+ .replace(/>/g, '&gt;')
48
+ .replace(/"/g, '&quot;')
49
+ .replace(/'/g, '&apos;');
50
+ }
51
+
52
+ /** Return the canonical plist path under ~/Library/LaunchAgents/. */
53
+ export function getPlistPath(): string {
54
+ return resolve(homedir(), 'Library', 'LaunchAgents', PLIST_FILENAME);
55
+ }
56
+
57
+ /**
58
+ * Build the NVM-aware PATH string.
59
+ * Includes the directory containing the Node binary so that launchd can find node
60
+ * even when launched outside a shell session (where NVM isn't sourced).
61
+ */
62
+ function buildEnvPath(nodePath: string): string {
63
+ const nodeBinDir = dirname(nodePath);
64
+ // Keep system essentials and prepend the node binary's directory
65
+ return `${nodeBinDir}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
66
+ }
67
+
68
+ // --------------- plist generation ---------------
69
+
70
+ /** Generate valid launchd plist XML for the GSD daemon. */
71
+ export function generatePlist(opts: PlistOptions): string {
72
+ const home = homedir();
73
+ const workDir = opts.workingDirectory ?? home;
74
+ const stdoutPath = opts.stdoutPath ?? resolve(home, '.gsd', 'daemon-stdout.log');
75
+ const stderrPath = opts.stderrPath ?? resolve(home, '.gsd', 'daemon-stderr.log');
76
+ const envPath = buildEnvPath(opts.nodePath);
77
+
78
+ // Forward ANTHROPIC_API_KEY so the orchestrator LLM can authenticate.
79
+ // Captured at install time from the current process environment.
80
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
81
+ const anthropicKeyXml = anthropicKey
82
+ ? `\n\t\t<key>ANTHROPIC_API_KEY</key>\n\t\t<string>${escapeXml(anthropicKey)}</string>`
83
+ : '';
84
+
85
+ return `<?xml version="1.0" encoding="UTF-8"?>
86
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
87
+ <plist version="1.0">
88
+ <dict>
89
+ \t<key>Label</key>
90
+ \t<string>${escapeXml(LABEL)}</string>
91
+
92
+ \t<key>ProgramArguments</key>
93
+ \t<array>
94
+ \t\t<string>${escapeXml(opts.nodePath)}</string>
95
+ \t\t<string>${escapeXml(opts.scriptPath)}</string>
96
+ \t\t<string>--config</string>
97
+ \t\t<string>${escapeXml(opts.configPath)}</string>
98
+ \t</array>
99
+
100
+ \t<key>KeepAlive</key>
101
+ \t<dict>
102
+ \t\t<key>SuccessfulExit</key>
103
+ \t\t<false/>
104
+ \t</dict>
105
+
106
+ \t<key>RunAtLoad</key>
107
+ \t<true/>
108
+
109
+ \t<key>EnvironmentVariables</key>
110
+ \t<dict>
111
+ \t\t<key>PATH</key>
112
+ \t\t<string>${escapeXml(envPath)}</string>
113
+ \t\t<key>HOME</key>
114
+ \t\t<string>${escapeXml(home)}</string>${anthropicKeyXml}
115
+ \t</dict>
116
+
117
+ \t<key>WorkingDirectory</key>
118
+ \t<string>${escapeXml(workDir)}</string>
119
+
120
+ \t<key>StandardOutPath</key>
121
+ \t<string>${escapeXml(stdoutPath)}</string>
122
+
123
+ \t<key>StandardErrorPath</key>
124
+ \t<string>${escapeXml(stderrPath)}</string>
125
+ </dict>
126
+ </plist>
127
+ `;
128
+ }
129
+
130
+ // --------------- install / uninstall / status ---------------
131
+
132
+ /** Default runCommand using execSync. */
133
+ function defaultRunCommand(cmd: string): string {
134
+ return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
135
+ }
136
+
137
+ /**
138
+ * Install the launchd agent: write plist and load it.
139
+ * Idempotent — unloads first if already loaded.
140
+ */
141
+ export function install(
142
+ opts: PlistOptions,
143
+ runCommand: RunCommandFn = defaultRunCommand,
144
+ ): void {
145
+ const plistPath = getPlistPath();
146
+ const xml = generatePlist(opts);
147
+
148
+ // Unload first if already present (ignore errors)
149
+ if (existsSync(plistPath)) {
150
+ try {
151
+ runCommand(`launchctl unload ${plistPath}`);
152
+ } catch {
153
+ // already unloaded — fine
154
+ }
155
+ }
156
+
157
+ writeFileSync(plistPath, xml, 'utf-8');
158
+ chmodSync(plistPath, 0o644);
159
+
160
+ runCommand(`launchctl load ${plistPath}`);
161
+
162
+ // Verify it loaded
163
+ try {
164
+ runCommand(`launchctl list ${LABEL}`);
165
+ } catch {
166
+ throw new Error(
167
+ `Plist was written to ${plistPath} and launchctl load succeeded, but launchctl list ${LABEL} failed. The agent may not have started.`,
168
+ );
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Uninstall the launchd agent: unload and remove plist.
174
+ * Graceful — does not throw if already uninstalled.
175
+ */
176
+ export function uninstall(runCommand: RunCommandFn = defaultRunCommand): void {
177
+ const plistPath = getPlistPath();
178
+
179
+ if (existsSync(plistPath)) {
180
+ try {
181
+ runCommand(`launchctl unload ${plistPath}`);
182
+ } catch {
183
+ // already unloaded — that's fine
184
+ }
185
+ unlinkSync(plistPath);
186
+ }
187
+ // If plist doesn't exist, nothing to do — already uninstalled
188
+ }
189
+
190
+ /**
191
+ * Query launchd for the daemon's status.
192
+ * Returns structured information about registration, PID, and last exit code.
193
+ *
194
+ * Handles two launchctl output formats:
195
+ * 1. Tabular: "PID\tStatus\tLabel" (older macOS)
196
+ * 2. JSON-style dict: `"PID" = 1234;` / `"LastExitStatus" = 0;` (newer macOS)
197
+ */
198
+ export function status(runCommand: RunCommandFn = defaultRunCommand): LaunchdStatus {
199
+ try {
200
+ const output = runCommand(`launchctl list ${LABEL}`);
201
+
202
+ // --- Try tabular format first ---
203
+ const lines = output.trim().split('\n');
204
+ for (const line of lines) {
205
+ const parts = line.trim().split(/\t+/);
206
+ if (parts.length >= 3 && parts[2] === LABEL) {
207
+ const pidStr = parts[0];
208
+ const statusStr = parts[1];
209
+
210
+ const pid = pidStr === '-' ? null : parseInt(pidStr, 10);
211
+ const lastExitStatus = statusStr != null ? parseInt(statusStr, 10) : null;
212
+
213
+ return {
214
+ registered: true,
215
+ pid: Number.isNaN(pid!) ? null : pid,
216
+ lastExitStatus: Number.isNaN(lastExitStatus!) ? null : lastExitStatus,
217
+ };
218
+ }
219
+ }
220
+
221
+ // --- Try JSON-style dict format ---
222
+ // Matches: "PID" = 1234; or "LastExitStatus" = 0;
223
+ const pidMatch = output.match(/"PID"\s*=\s*(\d+)\s*;/);
224
+ const exitMatch = output.match(/"LastExitStatus"\s*=\s*(\d+)\s*;/);
225
+
226
+ if (pidMatch || exitMatch) {
227
+ const pid = pidMatch ? parseInt(pidMatch[1], 10) : null;
228
+ const lastExitStatus = exitMatch ? parseInt(exitMatch[1], 10) : null;
229
+ return {
230
+ registered: true,
231
+ pid: Number.isNaN(pid!) ? null : pid,
232
+ lastExitStatus: Number.isNaN(lastExitStatus!) ? null : lastExitStatus,
233
+ };
234
+ }
235
+
236
+ // Label resolved (no error) but no parseable output — still registered
237
+ return { registered: true, pid: null, lastExitStatus: null };
238
+ } catch {
239
+ // launchctl list exits non-zero when the label isn't found
240
+ return { registered: false, pid: null, lastExitStatus: null };
241
+ }
242
+ }
@@ -65,7 +65,7 @@ describe('MessageBatcher', () => {
65
65
  await batcher.destroy();
66
66
  });
67
67
 
68
- it('combines embeds into a single send call', async () => {
68
+ it('skips embeds for batched messages (only content)', async () => {
69
69
  const { fn, calls } = createSend();
70
70
  const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 2, flushIntervalMs: 60_000 });
71
71
 
@@ -74,7 +74,7 @@ describe('MessageBatcher', () => {
74
74
  await new Promise((r) => setTimeout(r, 10));
75
75
 
76
76
  assert.equal(calls.length, 1);
77
- assert.equal(calls[0].embeds.length, 2);
77
+ assert.equal(calls[0].embeds.length, 0, 'batched sends skip embeds to avoid duplication');
78
78
  assert.equal(calls[0].content, 'a\nb');
79
79
 
80
80
  await batcher.destroy();
@@ -162,6 +162,10 @@ export class MessageBatcher {
162
162
  /**
163
163
  * Build a SendPayload from a batch of FormattedEvents and invoke the send callback.
164
164
  * Catches and logs errors — never throws.
165
+ *
166
+ * For batched messages (2+ events), we send content-only to avoid duplication
167
+ * between content text and embed descriptions, and to stay under Discord's
168
+ * 10-embed limit. Single-event sends include the embed for rich formatting.
165
169
  */
166
170
  private async doSend(batch: FormattedEvent[]): Promise<void> {
167
171
  if (batch.length === 0) return;
@@ -169,10 +173,12 @@ export class MessageBatcher {
169
173
  // Combine content lines
170
174
  const content = batch.map((e) => e.content).join('\n');
171
175
 
172
- // Collect all embeds (Discord allows up to 10 per message)
176
+ // For single events, include the embed for rich formatting.
177
+ // For batches, skip embeds — the content lines are self-descriptive and
178
+ // embeds would duplicate the information + risk hitting Discord's 10-embed cap.
173
179
  const embeds: unknown[] = [];
174
- for (const e of batch) {
175
- if (e.embed) embeds.push(e.embed);
180
+ if (batch.length === 1 && batch[0].embed) {
181
+ embeds.push(batch[0].embed);
176
182
  }
177
183
 
178
184
  // Collect all component rows (only from the last event with components —
@@ -201,6 +201,7 @@ function makeMessage(overrides: Partial<{
201
201
  send: async (content: string) => {
202
202
  sentMessages.push(content);
203
203
  },
204
+ sendTyping: async () => {},
204
205
  },
205
206
  sentMessages,
206
207
  };
@@ -12,6 +12,9 @@
12
12
  */
13
13
 
14
14
  import { z } from 'zod';
15
+ import { readFileSync, writeFileSync, chmodSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
15
18
  import type Anthropic from '@anthropic-ai/sdk';
16
19
  import type {
17
20
  MessageParam,
@@ -26,6 +29,93 @@ import type { ChannelManager } from './channel-manager.js';
26
29
  import type { ProjectInfo, ManagedSession } from './types.js';
27
30
  import type { Logger } from './logger.js';
28
31
 
32
+ // ---------------------------------------------------------------------------
33
+ // OAuth token resolution — reads GSD's auth.json, refreshes if expired
34
+ // ---------------------------------------------------------------------------
35
+
36
+ interface OAuthCredentials {
37
+ type: 'oauth';
38
+ refresh: string;
39
+ access: string;
40
+ expires: number;
41
+ }
42
+
43
+ const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
44
+ const CLIENT_ID = atob('OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl');
45
+
46
+ /**
47
+ * Read the Anthropic OAuth access token from GSD's auth.json.
48
+ * If expired, refresh it and write the new credentials back.
49
+ * Falls back to ANTHROPIC_API_KEY env var if no OAuth credential exists.
50
+ */
51
+ async function resolveAnthropicApiKey(logger?: Logger): Promise<string> {
52
+ // Try env var first (explicit override)
53
+ if (process.env.ANTHROPIC_API_KEY) {
54
+ return process.env.ANTHROPIC_API_KEY;
55
+ }
56
+
57
+ const authPath = join(homedir(), '.gsd', 'agent', 'auth.json');
58
+ let authData: Record<string, unknown>;
59
+ try {
60
+ authData = JSON.parse(readFileSync(authPath, 'utf-8'));
61
+ } catch {
62
+ throw new Error(
63
+ 'No Anthropic auth found. Run `gsd login` to authenticate, or set ANTHROPIC_API_KEY.',
64
+ );
65
+ }
66
+
67
+ const cred = authData.anthropic as OAuthCredentials | undefined;
68
+ if (!cred || cred.type !== 'oauth' || !cred.access) {
69
+ throw new Error(
70
+ 'No Anthropic OAuth credential in auth.json. Run `gsd login` to authenticate.',
71
+ );
72
+ }
73
+
74
+ // If token is still valid, use it
75
+ if (Date.now() < cred.expires) {
76
+ return cred.access;
77
+ }
78
+
79
+ // Token expired — refresh it
80
+ logger?.info('orchestrator: refreshing Anthropic OAuth token');
81
+ const response = await fetch(TOKEN_URL, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({
85
+ grant_type: 'refresh_token',
86
+ client_id: CLIENT_ID,
87
+ refresh_token: cred.refresh,
88
+ }),
89
+ signal: AbortSignal.timeout(30_000),
90
+ });
91
+
92
+ if (!response.ok) {
93
+ const error = await response.text();
94
+ throw new Error(`Anthropic token refresh failed: ${error}`);
95
+ }
96
+
97
+ const data = (await response.json()) as {
98
+ access_token: string;
99
+ refresh_token: string;
100
+ expires_in: number;
101
+ };
102
+
103
+ const newCred: OAuthCredentials = {
104
+ type: 'oauth',
105
+ refresh: data.refresh_token,
106
+ access: data.access_token,
107
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
108
+ };
109
+
110
+ // Write back to auth.json
111
+ authData.anthropic = newCred;
112
+ writeFileSync(authPath, JSON.stringify(authData, null, 2), 'utf-8');
113
+ chmodSync(authPath, 0o600);
114
+ logger?.info('orchestrator: Anthropic OAuth token refreshed');
115
+
116
+ return newCred.access;
117
+ }
118
+
29
119
  // ---------------------------------------------------------------------------
30
120
  // Configuration
31
121
  // ---------------------------------------------------------------------------
@@ -164,11 +254,13 @@ export class Orchestrator {
164
254
 
165
255
  /**
166
256
  * Lazily initialise the Anthropic client. Dynamic import handles K007 module resolution.
257
+ * Resolves auth from GSD's OAuth credentials (auth.json), refreshing if needed.
167
258
  */
168
259
  private async getClient(): Promise<Anthropic> {
169
260
  if (this.client) return this.client;
261
+ const apiKey = await resolveAnthropicApiKey(this.deps.logger);
170
262
  const { default: AnthropicSDK } = await import('@anthropic-ai/sdk');
171
- this.client = new AnthropicSDK();
263
+ this.client = new AnthropicSDK({ apiKey });
172
264
  return this.client;
173
265
  }
174
266
 
@@ -204,6 +296,9 @@ export class Orchestrator {
204
296
  this.history.push({ role: 'user', content });
205
297
 
206
298
  try {
299
+ // Show typing indicator while processing
300
+ await message.channel.sendTyping().catch(() => {});
301
+
207
302
  const responseText = await this.runAgentLoop();
208
303
 
209
304
  // Send response to Discord
@@ -215,6 +310,12 @@ export class Orchestrator {
215
310
  });
216
311
  } catch (err) {
217
312
  const errorMsg = err instanceof Error ? err.message : String(err);
313
+
314
+ // Invalidate cached client on auth errors so next call re-resolves OAuth token
315
+ if (errorMsg.includes('authentication') || errorMsg.includes('apiKey') || errorMsg.includes('authToken') || errorMsg.includes('401')) {
316
+ this.client = null;
317
+ }
318
+
218
319
  this.deps.logger.error('orchestrator error', {
219
320
  error: errorMsg,
220
321
  userId: message.author.id,
@@ -436,5 +537,8 @@ export interface DiscordMessageLike {
436
537
  author: { id: string; bot: boolean };
437
538
  channelId: string;
438
539
  content: string;
439
- channel: { send: (content: string) => Promise<unknown> };
540
+ channel: {
541
+ send: (content: string) => Promise<unknown>;
542
+ sendTyping: () => Promise<unknown>;
543
+ };
440
544
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-coding-agent",
3
- "version": "2.57.0",
3
+ "version": "2.58.0",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "piConfig": {
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.57.0",
3
+ "version": "2.58.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -41,5 +41,8 @@ export function isInfrastructureError(err: unknown): string | null {
41
41
  for (const code of INFRA_ERROR_CODES) {
42
42
  if (msg.includes(code)) return code;
43
43
  }
44
+ // SQLite WAL corruption is not transient — retrying burns LLM budget
45
+ // for guaranteed failures (#2823).
46
+ if (msg.includes("database disk image is malformed")) return "SQLITE_CORRUPT";
44
47
  return null;
45
48
  }
@@ -677,13 +677,13 @@ export const DISPATCH_RULES: DispatchRule[] = [
677
677
  if (validationPath) {
678
678
  const validationContent = await loadFile(validationPath);
679
679
  if (validationContent) {
680
- // Accept either the structured template format (table with MET/N/A)
680
+ // Accept either the structured template format (table with MET/N/A/SATISFIED)
681
681
  // or prose evidence patterns the validation agent may emit.
682
682
  const structuredMatch =
683
683
  validationContent.includes("Operational") &&
684
- (validationContent.includes("MET") || validationContent.includes("N/A"));
684
+ (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
685
685
  const proseMatch =
686
- /[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(validationContent);
686
+ /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent);
687
687
  const hasOperationalCheck = structuredMatch || proseMatch;
688
688
  if (!hasOperationalCheck) {
689
689
  return {
@@ -1264,12 +1264,17 @@ export function mergeMilestoneToMain(
1264
1264
  // 1. Auto-commit dirty state in worktree before leaving
1265
1265
  autoCommitDirtyState(worktreeCwd);
1266
1266
 
1267
- // Reconcile worktree DB into main DB before leaving worktree context
1267
+ // Reconcile worktree DB into main DB before leaving worktree context.
1268
+ // Skip when both paths resolve to the same physical file (shared WAL /
1269
+ // symlink layout) — ATTACHing a WAL-mode file to itself corrupts the
1270
+ // database (#2823).
1268
1271
  if (isDbAvailable()) {
1269
1272
  try {
1270
1273
  const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
1271
1274
  const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
1272
- reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1275
+ if (!isSamePath(worktreeDbPath, mainDbPath)) {
1276
+ reconcileWorktreeDb(mainDbPath, worktreeDbPath);
1277
+ }
1273
1278
  } catch {
1274
1279
  /* non-fatal */
1275
1280
  }
@@ -1061,6 +1061,11 @@ export async function startAuto(
1061
1061
  verboseMode: boolean,
1062
1062
  options?: { step?: boolean },
1063
1063
  ): Promise<void> {
1064
+ if (s.active) {
1065
+ debugLog("startAuto", { phase: "already-active", skipping: true });
1066
+ return;
1067
+ }
1068
+
1064
1069
  const requestedStepMode = options?.step ?? false;
1065
1070
 
1066
1071
  // Escape stale worktree cwd from a previous milestone (#608).
@@ -924,7 +924,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
924
924
  promptSnippet: "Validate a GSD milestone (DB write + VALIDATION.md render)",
925
925
  promptGuidelines: [
926
926
  "Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
927
- "Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verdictRationale, remediationPlan (optional).",
927
+ "Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), verdictRationale, remediationPlan (optional).",
928
928
  "If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
929
929
  "On success, returns validationPath where VALIDATION.md was written.",
930
930
  ],
@@ -936,6 +936,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
936
936
  sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
937
937
  crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
938
938
  requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
939
+ verificationClasses: Type.Optional(Type.String({ description: "Markdown describing verification class compliance and gaps" })),
939
940
  verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
940
941
  remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
941
942
  }),
@@ -26,9 +26,20 @@ export function getPriorSliceCompletionBlocker(
26
26
  const { milestone: targetMid, slice: targetSid } = parseUnitId(unitId);
27
27
  if (!targetMid || !targetSid) return null;
28
28
 
29
+ // Parallel worker isolation: when GSD_MILESTONE_LOCK is set, this worker
30
+ // is scoped to a single milestone. Skip the cross-milestone dependency
31
+ // check — other milestones are being handled by their own workers.
32
+ // Without this, the dispatch guard sees incomplete slices in M010/M011
33
+ // (cloned into the worktree DB) and blocks M012 from ever starting. #2797
34
+ const milestoneLock = process.env.GSD_MILESTONE_LOCK;
35
+
29
36
  // Use findMilestoneIds to respect custom queue order.
30
37
  // Only check milestones that come BEFORE the target in queue order.
31
- const allIds = findMilestoneIds(base);
38
+ // When locked to a specific milestone, only check that milestone's
39
+ // intra-slice dependencies — skip all cross-milestone checks.
40
+ const allIds = milestoneLock && targetMid === milestoneLock
41
+ ? [targetMid]
42
+ : findMilestoneIds(base);
32
43
  const targetIdx = allIds.indexOf(targetMid);
33
44
  if (targetIdx < 0) return null;
34
45
  const milestoneIds = allIds.slice(0, targetIdx + 1);
@@ -6,7 +6,7 @@
6
6
  // Schema is initialized on first open with WAL mode for file-backed DBs.
7
7
 
8
8
  import { createRequire } from "node:module";
9
- import { existsSync, copyFileSync, mkdirSync } from "node:fs";
9
+ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
10
10
  import { dirname } from "node:path";
11
11
  import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
12
12
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
@@ -1761,6 +1761,11 @@ export function reconcileWorktreeDb(
1761
1761
  ): ReconcileResult {
1762
1762
  const zero: ReconcileResult = { decisions: 0, requirements: 0, artifacts: 0, milestones: 0, slices: 0, tasks: 0, memories: 0, verification_evidence: 0, conflicts: [] };
1763
1763
  if (!existsSync(worktreeDbPath)) return zero;
1764
+ // Guard: bail when both paths resolve to the same physical file.
1765
+ // ATTACHing a WAL-mode DB to itself corrupts the WAL (#2823).
1766
+ try {
1767
+ if (realpathSync(mainDbPath) === realpathSync(worktreeDbPath)) return zero;
1768
+ } catch { /* path resolution failed — fall through to existing checks */ }
1764
1769
  // Sanitize path: reject any characters that could break ATTACH syntax.
1765
1770
  // ATTACH DATABASE doesn't support parameterized paths in all providers,
1766
1771
  // so we use strict allowlist validation instead.
@@ -519,8 +519,19 @@ function createMilestoneWorktree(basePath: string, milestoneId: string): string
519
519
 
520
520
  /**
521
521
  * Spawn a worker process for a milestone.
522
- * The worker runs `gsd --print "/gsd auto"` in the milestone's worktree
522
+ * The worker runs `gsd headless --json auto` in the milestone's worktree
523
523
  * with GSD_MILESTONE_LOCK set to isolate state derivation.
524
+ *
525
+ * IMPORTANT: We use `headless --json auto` instead of `--print "/gsd auto"`.
526
+ * --print mode calls session.prompt() which returns immediately after the
527
+ * extension command handler fires, because auto-mode's ctx.newSession()
528
+ * resets the session and unblocks the outer prompt() await. This causes
529
+ * process.exit(0) to fire before any LLM work happens. See #2792.
530
+ *
531
+ * The headless subcommand uses an RPC client that keeps the process alive
532
+ * until auto-mode emits a terminal notification or the idle timer fires.
533
+ * It outputs NDJSON events to stdout (with --json), which our
534
+ * processWorkerLine() parser already understands.
524
535
  */
525
536
  export function spawnWorker(
526
537
  basePath: string,
@@ -537,7 +548,7 @@ export function spawnWorker(
537
548
 
538
549
  let child: ChildProcess;
539
550
  try {
540
- child = spawn(process.execPath, [binPath, "--mode", "json", "--print", "/gsd auto"], {
551
+ child = spawn(process.execPath, [binPath, "headless", "--json", "auto"], {
541
552
  cwd: worker.worktreePath,
542
553
  env: {
543
554
  ...process.env,
@@ -577,9 +588,10 @@ export function spawnWorker(
577
588
  }
578
589
 
579
590
  // ── NDJSON stdout monitoring ────────────────────────────────────────
580
- // Workers run with --mode json, emitting one JSON event per line.
581
- // We parse message_end events to extract cost/token usage, keeping
582
- // the coordinator's cost tracking in sync with actual API spend.
591
+ // Workers run via `headless --json`, which forwards all RPC events
592
+ // as NDJSON to stdout. We parse message_end events to extract
593
+ // cost/token usage, keeping the coordinator's cost tracking in sync
594
+ // with actual API spend.
583
595
  if (child.stdout) {
584
596
  let stdoutBuffer = "";
585
597
  child.stdout.on("data", (data: Buffer) => {
@@ -808,7 +820,12 @@ export async function stopParallel(
808
820
  } catch { /* process may already be dead */ }
809
821
  }
810
822
 
811
- const exitedAfterTerm = await waitForWorkerExit(worker, 750);
823
+ // Wait for the headless process to cascade SIGTERM to its RPC child.
824
+ // The headless signal handler calls client.stop() which sends SIGTERM
825
+ // to the RPC child and waits up to 1000ms. The previous 750ms window
826
+ // was insufficient — the parent got SIGKILL before the child died,
827
+ // leaving orphaned RPC processes holding auto.lock. See #2798.
828
+ const exitedAfterTerm = await waitForWorkerExit(worker, 3000);
812
829
  if (!exitedAfterTerm && worker.pid > 0) {
813
830
  try {
814
831
  if (worker.process) {