@shogo-ai/worker 1.8.14 → 1.8.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/worker",
3
- "version": "1.8.14",
3
+ "version": "1.8.16",
4
4
  "description": "Shogo Cloud Agent Worker — run Shogo agents on your own machine (laptop, devbox, CI).",
5
5
  "license": "MIT",
6
6
  "author": "Shogo Technologies, Inc.",
@@ -31,6 +31,7 @@
31
31
  ],
32
32
  "scripts": {
33
33
  "start": "bun src/cli.ts",
34
+ "test": "bun --no-env-file --conditions=development test src/",
34
35
  "typecheck": "tsc --noEmit",
35
36
  "build": "bun run build:js",
36
37
  "build:js": "bun build --target=node --format=esm --minify --external bun --external @shogo-ai/sdk --external commander --external picocolors --outdir dist src/cli.ts && mv dist/cli.js dist/cli.mjs",
@@ -31,7 +31,7 @@
31
31
  * - No Prisma reads. All policy comes from the spawn config.
32
32
  * - No security policy build. Cloud signs the policy and sends it.
33
33
  */
34
- import { type ChildProcess, spawn } from 'node:child_process';
34
+ import { type ChildProcess, spawn, spawnSync } from 'node:child_process';
35
35
  import { createHmac, randomBytes } from 'node:crypto';
36
36
  import { existsSync, mkdirSync, readdirSync } from 'node:fs';
37
37
  import { createConnection } from 'node:net';
@@ -1097,9 +1097,9 @@ export class WorkerRuntimeManager implements RuntimeResolver {
1097
1097
  }
1098
1098
 
1099
1099
  /**
1100
- * Kill every process in `slot.pid`'s process group. Best-effort:
1101
- * if the group is already gone (everyone exited cleanly), or the
1102
- * platform doesn't support PGID kills (Windows), this is a no-op.
1100
+ * Kill every process in `slot.pid`'s process group / job tree.
1101
+ * Best-effort: if the group is already gone (everyone exited cleanly)
1102
+ * this is a no-op.
1103
1103
  *
1104
1104
  * Why we use the recorded PID and not `slot.proc.pid`: by the time
1105
1105
  * {@link handleExit} runs, `proc` has already fired its `'exit'`
@@ -1107,10 +1107,31 @@ export class WorkerRuntimeManager implements RuntimeResolver {
1107
1107
  * intact until the *last* member of the group exits, so the PGID
1108
1108
  * we captured at spawn is still valid for reaping the orphans even
1109
1109
  * after the group leader is gone.
1110
+ *
1111
+ * Windows: `process.kill(-pid, ...)` is unsupported and Node's
1112
+ * `child.kill('SIGTERM')` is just `TerminateProcess` on the parent,
1113
+ * which does NOT cascade to grandchildren — vite, the inner
1114
+ * preview-manager API server, tsserver, pyright, and `server.tsx`
1115
+ * all survive as orphans (each holding chokidar watcher handles
1116
+ * that wedge the next spawn's event loop). We use `taskkill /F /T`
1117
+ * to walk the process tree by parent PID instead.
1110
1118
  */
1111
1119
  private killProcessGroup(slot: InternalRuntime, signal: NodeJS.Signals): void {
1112
- if (process.platform === 'win32') return;
1113
1120
  if (!slot.pid) return;
1121
+ if (process.platform === 'win32') {
1122
+ try {
1123
+ spawnSync('taskkill', ['/F', '/T', '/PID', String(slot.pid)], {
1124
+ stdio: 'ignore',
1125
+ windowsHide: true,
1126
+ });
1127
+ } catch {
1128
+ // taskkill missing or the tree already collapsed — either way
1129
+ // we have no recourse and the parent .kill() above (or the
1130
+ // belt-and-suspenders SIGKILL in waitForExit) is the best we
1131
+ // can still do.
1132
+ }
1133
+ return;
1134
+ }
1114
1135
  try {
1115
1136
  process.kill(-slot.pid, signal);
1116
1137
  } catch {