@openchamber/web 1.4.4 → 1.4.6

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/dist/index.html CHANGED
@@ -160,10 +160,10 @@
160
160
  pointer-events: none;
161
161
  }
162
162
  </style>
163
- <script type="module" crossorigin src="/assets/index-CqQbtUxU.js"></script>
164
- <link rel="modulepreload" crossorigin href="/assets/vendor-.bun-BEzqubWg.js">
163
+ <script type="module" crossorigin src="/assets/index-_QJSNcFo.js"></script>
164
+ <link rel="modulepreload" crossorigin href="/assets/vendor-.bun-C07YQe9X.js">
165
165
  <link rel="stylesheet" crossorigin href="/assets/vendor--Jn2c0Clh.css">
166
- <link rel="stylesheet" crossorigin href="/assets/index-DR2OFuzB.css">
166
+ <link rel="stylesheet" crossorigin href="/assets/index-Cxzt1pIT.css">
167
167
  </head>
168
168
  <body class="h-full bg-background text-foreground">
169
169
  <div id="root" class="h-full">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchamber/web",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -26,7 +26,7 @@
26
26
  "@fontsource/ibm-plex-mono": "^5.2.7",
27
27
  "@fontsource/ibm-plex-sans": "^5.1.1",
28
28
  "@ibm/plex": "^6.4.1",
29
- "@opencode-ai/sdk": "^1.1.1",
29
+ "@opencode-ai/sdk": "^1.1.8",
30
30
  "@radix-ui/react-collapsible": "^1.1.12",
31
31
  "@radix-ui/react-dialog": "^1.1.15",
32
32
  "@radix-ui/react-dropdown-menu": "^2.1.16",
package/server/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import express from 'express';
2
2
  import { createProxyMiddleware } from 'http-proxy-middleware';
3
3
  import path from 'path';
4
- import { spawn, spawnSync } from 'child_process';
4
+ import { spawn } from 'child_process';
5
5
  import fs from 'fs';
6
6
  import http from 'http';
7
7
  import { fileURLToPath } from 'url';
@@ -9,12 +9,12 @@ import os from 'os';
9
9
  import crypto from 'crypto';
10
10
  import { createUiAuth } from './lib/ui-auth.js';
11
11
  import { startCloudflareTunnel, printTunnelWarning, checkCloudflaredAvailable } from './lib/cloudflare-tunnel.js';
12
+ import { createOpencodeServer } from '@opencode-ai/sdk/server';
12
13
 
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
15
16
 
16
17
  const DEFAULT_PORT = 3000;
17
- const DEFAULT_OPENCODE_PORT = 0;
18
18
  const HEALTH_CHECK_INTERVAL = 30000;
19
19
  const SHUTDOWN_TIMEOUT = 10000;
20
20
  const MODELS_DEV_API_URL = 'https://models.dev/api.json';
@@ -590,11 +590,17 @@ const sanitizeSettingsUpdate = (payload) => {
590
590
  result.typographySizes = typography;
591
591
  }
592
592
 
593
- if (typeof candidate.defaultModel === 'string' && candidate.defaultModel.length > 0) {
594
- result.defaultModel = candidate.defaultModel;
593
+ if (typeof candidate.defaultModel === 'string') {
594
+ const trimmed = candidate.defaultModel.trim();
595
+ result.defaultModel = trimmed.length > 0 ? trimmed : undefined;
595
596
  }
596
- if (typeof candidate.defaultAgent === 'string' && candidate.defaultAgent.length > 0) {
597
- result.defaultAgent = candidate.defaultAgent;
597
+ if (typeof candidate.defaultVariant === 'string') {
598
+ const trimmed = candidate.defaultVariant.trim();
599
+ result.defaultVariant = trimmed.length > 0 ? trimmed : undefined;
600
+ }
601
+ if (typeof candidate.defaultAgent === 'string') {
602
+ const trimmed = candidate.defaultAgent.trim();
603
+ result.defaultAgent = trimmed.length > 0 ? trimmed : undefined;
598
604
  }
599
605
  if (typeof candidate.queueModeEnabled === 'boolean') {
600
606
  result.queueModeEnabled = candidate.queueModeEnabled;
@@ -842,7 +848,6 @@ let openCodeApiDetectionTimer = null;
842
848
  let isDetectingApiPrefix = false;
843
849
  let openCodeApiDetectionPromise = null;
844
850
  let lastOpenCodeError = null;
845
- let openCodePortWaiters = [];
846
851
  let isOpenCodeReady = false;
847
852
  let openCodeNotReadySince = 0;
848
853
  let exitOnShutdown = true;
@@ -884,12 +889,7 @@ async function isOpenCodeProcessHealthy() {
884
889
  return false;
885
890
  }
886
891
 
887
- // Check if process is still running
888
- if (openCodeProcess.exitCode !== null || openCodeProcess.signalCode !== null) {
889
- return false;
890
- }
891
-
892
- // Health check via HTTP
892
+ // Health check via HTTP since SDK object doesn't expose exitCode
893
893
  try {
894
894
  const response = await fetch(`http://127.0.0.1:${openCodePort}/session`, {
895
895
  method: 'GET',
@@ -901,102 +901,6 @@ async function isOpenCodeProcessHealthy() {
901
901
  }
902
902
  }
903
903
 
904
- const OPENCODE_BINARY_ENV =
905
- process.env.OPENCODE_BINARY ||
906
- process.env.OPENCHAMBER_BINARY ||
907
- process.env.OPENCODE_PATH ||
908
- process.env.OPENCHAMBER_OPENCODE_PATH ||
909
- null;
910
-
911
- function buildAugmentedPath() {
912
- const augmented = new Set();
913
-
914
- const loginShellPath = getLoginShellPath();
915
- if (loginShellPath) {
916
- for (const segment of loginShellPath.split(path.delimiter)) {
917
- if (segment) {
918
- augmented.add(segment);
919
- }
920
- }
921
- }
922
-
923
- const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
924
- for (const segment of current) {
925
- augmented.add(segment);
926
- }
927
-
928
- return Array.from(augmented).join(path.delimiter);
929
- }
930
-
931
- function getLoginShellPath() {
932
- if (process.platform === 'win32') {
933
- return null;
934
- }
935
-
936
- const shell = process.env.SHELL || '/bin/zsh';
937
- const shellName = path.basename(shell);
938
-
939
- // Nushell requires different flag syntax and PATH access
940
- const isNushell = shellName === 'nu' || shellName === 'nushell';
941
- const args = isNushell
942
- ? ['-l', '-i', '-c', '$env.PATH | str join (char esep)']
943
- : ['-lic', 'echo -n "$PATH"'];
944
-
945
- try {
946
- const result = spawnSync(shell, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
947
- if (result.status === 0 && typeof result.stdout === 'string') {
948
- const value = result.stdout.trim();
949
- if (value) {
950
- return value;
951
- }
952
- } else if (result.stderr) {
953
- console.warn(`Failed to read PATH from login shell (${shell}): ${result.stderr}`);
954
- }
955
- } catch (error) {
956
- console.warn(`Error executing login shell (${shell}) for PATH detection: ${error.message}`);
957
- }
958
- return null;
959
- }
960
-
961
- function resolveBinaryFromPath(binaryName, searchPath) {
962
- if (!binaryName) {
963
- return null;
964
- }
965
- if (path.isAbsolute(binaryName)) {
966
- return fs.existsSync(binaryName) ? binaryName : null;
967
- }
968
- const directories = searchPath.split(path.delimiter).filter(Boolean);
969
- for (const directory of directories) {
970
- try {
971
- const candidate = path.join(directory, binaryName);
972
- if (fs.existsSync(candidate)) {
973
- const stats = fs.statSync(candidate);
974
- if (stats.isFile()) {
975
- return candidate;
976
- }
977
- }
978
- } catch {
979
- // Ignore resolution errors, continue searching
980
- }
981
- }
982
- return null;
983
- }
984
-
985
- function getOpencodeSpawnConfig() {
986
- if (OPENCODE_BINARY_ENV) {
987
- const explicit = resolveBinaryFromPath(OPENCODE_BINARY_ENV, process.env.PATH);
988
- if (explicit) {
989
- console.log(`Using OpenCode binary from OPENCODE_BINARY: ${explicit}`);
990
- return { command: explicit, env: undefined };
991
- }
992
- console.warn(
993
- `OPENCODE_BINARY path "${OPENCODE_BINARY_ENV}" not found. Falling back to search.`
994
- );
995
- }
996
-
997
- return { command: 'opencode', env: undefined };
998
- }
999
-
1000
904
  const ENV_CONFIGURED_OPENCODE_PORT = (() => {
1001
905
  const raw =
1002
906
  process.env.OPENCODE_PORT ||
@@ -1039,42 +943,31 @@ function setOpenCodePort(port) {
1039
943
  }
1040
944
 
1041
945
  lastOpenCodeError = null;
1042
-
1043
- if (openCodePortWaiters.length > 0) {
1044
- const waiters = openCodePortWaiters;
1045
- openCodePortWaiters = [];
1046
- for (const notify of waiters) {
1047
- try {
1048
- notify(numericPort);
1049
- } catch (error) {
1050
- console.warn('Failed to notify OpenCode port waiter:', error);
1051
- }
1052
- }
1053
- }
1054
946
  }
1055
947
 
1056
- async function waitForOpenCodePort(timeoutMs = 15000) {
1057
- if (openCodePort !== null) {
1058
- return openCodePort;
1059
- }
948
+ const API_PREFIX_CANDIDATES = ['', '/api']; // Simplified - only check root and /api
1060
949
 
1061
- return new Promise((resolve, reject) => {
1062
- const onPortDetected = (port) => {
950
+ async function waitForReady(url, timeoutMs = 10000) {
951
+ const start = Date.now();
952
+ while (Date.now() - start < timeoutMs) {
953
+ try {
954
+ const controller = new AbortController();
955
+ const timeout = setTimeout(() => controller.abort(), 3000);
956
+ const res = await fetch(`${url.replace(/\/+$/, '')}/config`, {
957
+ method: 'GET',
958
+ headers: { Accept: 'application/json' },
959
+ signal: controller.signal
960
+ });
1063
961
  clearTimeout(timeout);
1064
- resolve(port);
1065
- };
1066
-
1067
- const timeout = setTimeout(() => {
1068
- openCodePortWaiters = openCodePortWaiters.filter((cb) => cb !== onPortDetected);
1069
- reject(new Error('Timed out waiting for OpenCode port'));
1070
- }, timeoutMs);
1071
-
1072
- openCodePortWaiters.push(onPortDetected);
1073
- });
962
+ if (res.ok) return true;
963
+ } catch {
964
+ // ignore
965
+ }
966
+ await new Promise(r => setTimeout(r, 100));
967
+ }
968
+ return false;
1074
969
  }
1075
970
 
1076
- const API_PREFIX_CANDIDATES = ['', '/api']; // Simplified - only check root and /api
1077
-
1078
971
  function normalizeApiPrefix(prefix) {
1079
972
  if (!prefix) {
1080
973
  return '';
@@ -1110,51 +1003,6 @@ function setDetectedOpenCodeApiPrefix(prefix) {
1110
1003
  }
1111
1004
  }
1112
1005
 
1113
- function detectPortFromLogMessage(message) {
1114
- if (openCodePort && ENV_CONFIGURED_OPENCODE_PORT) {
1115
- return;
1116
- }
1117
-
1118
- const regex = /https?:\/\/[^:\s]+:(\d+)/gi;
1119
- let match;
1120
- while ((match = regex.exec(message)) !== null) {
1121
- const port = parseInt(match[1], 10);
1122
- if (Number.isFinite(port) && port > 0) {
1123
- setOpenCodePort(port);
1124
- return;
1125
- }
1126
- }
1127
-
1128
- const fallbackMatch = /(?:^|\s)(?:127\.0\.0\.1|localhost):(\d+)/i.exec(message);
1129
- if (fallbackMatch) {
1130
- const port = parseInt(fallbackMatch[1], 10);
1131
- if (Number.isFinite(port) && port > 0) {
1132
- setOpenCodePort(port);
1133
- }
1134
- }
1135
- }
1136
-
1137
- function detectPrefixFromLogMessage(message) {
1138
- if (!openCodePort) {
1139
- return;
1140
- }
1141
-
1142
- const urlRegex = /https?:\/\/[^:\s]+:(\d+)(\/[^\s"']*)?/gi;
1143
- let match;
1144
-
1145
- while ((match = urlRegex.exec(message)) !== null) {
1146
- const portMatch = parseInt(match[1], 10);
1147
- if (portMatch !== openCodePort) {
1148
- continue;
1149
- }
1150
-
1151
- const path = match[2] || '';
1152
- const normalized = normalizeApiPrefix(path);
1153
- setDetectedOpenCodeApiPrefix(normalized);
1154
- return;
1155
- }
1156
- }
1157
-
1158
1006
  function getCandidateApiPrefixes() {
1159
1007
  if (openCodeApiPrefixDetected) {
1160
1008
  return [openCodeApiPrefix];
@@ -1486,136 +1334,57 @@ function parseArgs(argv = process.argv.slice(2)) {
1486
1334
  }
1487
1335
 
1488
1336
  async function startOpenCode() {
1489
- const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ?? DEFAULT_OPENCODE_PORT;
1337
+ const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ?? 0;
1490
1338
  console.log(
1491
- desiredPort
1339
+ desiredPort > 0
1492
1340
  ? `Starting OpenCode on requested port ${desiredPort}...`
1493
1341
  : 'Starting OpenCode with dynamic port assignment...'
1494
1342
  );
1495
- console.log(`Starting OpenCode in working directory: ${openCodeWorkingDirectory}`);
1496
-
1497
- const { command, env } = getOpencodeSpawnConfig();
1498
- const args = ['serve', '--port', desiredPort.toString()];
1499
- console.log(`Launching OpenCode via "${command}" with args ${args.join(' ')}`);
1343
+ // Note: SDK starts in current process CWD. openCodeWorkingDirectory is tracked but not used for spawn in SDK.
1500
1344
 
1501
- const child = spawn(command, args, {
1502
- stdio: 'pipe',
1503
- env,
1504
- cwd: openCodeWorkingDirectory
1505
- });
1506
- isOpenCodeReady = false;
1507
- openCodeNotReadySince = Date.now();
1345
+ try {
1346
+ const serverInstance = await createOpencodeServer({
1347
+ hostname: '127.0.0.1',
1348
+ port: desiredPort,
1349
+ timeout: 30000,
1350
+ env: {
1351
+ ...process.env,
1352
+ // Pass minimal config to avoid pollution, but inherit PATH etc
1353
+ }
1354
+ });
1508
1355
 
1509
- let firstSignalResolver;
1510
- const firstSignalPromise = new Promise((resolve) => {
1511
- firstSignalResolver = resolve;
1512
- });
1513
- let firstSignalSettled = false;
1514
- const settleFirstSignal = () => {
1515
- if (firstSignalSettled) {
1516
- return;
1517
- }
1518
- firstSignalSettled = true;
1519
- clearTimeout(firstSignalTimer);
1520
- child.stdout.off('data', settleFirstSignal);
1521
- child.stderr.off('data', settleFirstSignal);
1522
- child.off('exit', settleFirstSignal);
1523
- if (firstSignalResolver) {
1524
- firstSignalResolver();
1356
+ if (!serverInstance || !serverInstance.url) {
1357
+ throw new Error('OpenCode server started but URL is missing');
1525
1358
  }
1526
- };
1527
- const firstSignalTimer = setTimeout(settleFirstSignal, 750);
1528
-
1529
- child.stdout.once('data', settleFirstSignal);
1530
- child.stderr.once('data', settleFirstSignal);
1531
- child.once('exit', settleFirstSignal);
1532
-
1533
- child.stdout.on('data', (data) => {
1534
- const text = data.toString();
1535
- console.log(`OpenCode: ${text.trim()}`);
1536
- detectPortFromLogMessage(text);
1537
- detectPrefixFromLogMessage(text);
1538
- settleFirstSignal();
1539
- });
1540
1359
 
1541
- child.stderr.on('data', (data) => {
1542
- const text = data.toString();
1543
- lastOpenCodeError = text.trim();
1544
- console.error(`OpenCode Error: ${lastOpenCodeError}`);
1545
- detectPortFromLogMessage(text);
1546
- detectPrefixFromLogMessage(text);
1547
- settleFirstSignal();
1548
- });
1360
+ const url = new URL(serverInstance.url);
1361
+ const port = parseInt(url.port, 10);
1362
+ const prefix = normalizeApiPrefix(url.pathname);
1549
1363
 
1550
- let startupError = await new Promise((resolve, reject) => {
1551
- const onSpawn = () => {
1552
- setOpenCodePort(desiredPort);
1553
- child.off('error', onError);
1554
- resolve(null);
1555
- };
1556
- const onError = (error) => {
1557
- child.off('spawn', onSpawn);
1558
- reject(error);
1559
- };
1364
+ if (await waitForReady(serverInstance.url, 10000)) {
1365
+ setOpenCodePort(port);
1366
+ setDetectedOpenCodeApiPrefix(prefix); // SDK URL typically includes the prefix if any
1367
+
1368
+ isOpenCodeReady = true;
1369
+ lastOpenCodeError = null;
1370
+ openCodeNotReadySince = 0;
1560
1371
 
1561
- child.once('spawn', onSpawn);
1562
- child.once('error', onError);
1563
- }).catch((error) => {
1372
+ return serverInstance;
1373
+ } else {
1374
+ try {
1375
+ serverInstance.close();
1376
+ } catch {
1377
+ // ignore
1378
+ }
1379
+ throw new Error('Server started but health check failed (timeout)');
1380
+ }
1381
+ } catch (error) {
1564
1382
  lastOpenCodeError = error.message;
1565
1383
  openCodePort = null;
1566
1384
  syncToHmrState();
1567
- settleFirstSignal();
1568
- return error;
1569
- });
1570
-
1571
- if (startupError) {
1572
- if (startupError.code === 'ENOENT') {
1573
- const enhanced = new Error(
1574
- `Failed to start OpenCode – executable "${command}" not found. ` +
1575
- 'Set OPENCODE_BINARY to the full path of the opencode CLI or ensure it is on PATH.'
1576
- );
1577
- enhanced.code = startupError.code;
1578
- startupError = enhanced;
1579
- }
1580
- throw startupError;
1385
+ console.error(`Failed to start OpenCode: ${error.message}`);
1386
+ throw error;
1581
1387
  }
1582
-
1583
- child.on('exit', (code, signal) => {
1584
- lastOpenCodeError = `OpenCode exited with code ${code}, signal ${signal ?? 'null'}`;
1585
- isOpenCodeReady = false;
1586
- openCodeNotReadySince = Date.now();
1587
-
1588
- if (!isShuttingDown && !isRestartingOpenCode) {
1589
- console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
1590
-
1591
- setTimeout(() => {
1592
- restartOpenCode().catch((err) => {
1593
- console.error('Failed to restart OpenCode after exit:', err);
1594
- });
1595
- }, 5000);
1596
- } else if (isRestartingOpenCode) {
1597
- console.log('OpenCode exit during controlled restart, not triggering auto-restart');
1598
- }
1599
- });
1600
-
1601
- child.on('error', (error) => {
1602
- lastOpenCodeError = error.message;
1603
- isOpenCodeReady = false;
1604
- openCodeNotReadySince = Date.now();
1605
- console.error(`OpenCode process error: ${error.message}`);
1606
- if (!isShuttingDown) {
1607
-
1608
- setTimeout(() => {
1609
- restartOpenCode().catch((err) => {
1610
- console.error('Failed to restart OpenCode after error:', err);
1611
- });
1612
- }, 5000);
1613
- }
1614
- });
1615
-
1616
- await firstSignalPromise;
1617
-
1618
- return child;
1619
1388
  }
1620
1389
 
1621
1390
  async function restartOpenCode() {
@@ -1632,60 +1401,16 @@ async function restartOpenCode() {
1632
1401
  console.log('Restarting OpenCode process...');
1633
1402
 
1634
1403
  if (openCodeProcess) {
1635
- console.log('Waiting for OpenCode process to terminate...');
1636
- const processToTerminate = openCodeProcess;
1637
- let forcedTermination = false;
1638
-
1639
- if (processToTerminate.exitCode === null && processToTerminate.signalCode === null) {
1640
- processToTerminate.kill('SIGTERM');
1641
-
1642
- await new Promise((resolve) => {
1643
- let resolved = false;
1644
-
1645
- const cleanup = () => {
1646
- processToTerminate.off('exit', onExit);
1647
- clearTimeout(forceKillTimer);
1648
- clearTimeout(hardStopTimer);
1649
- if (!resolved) {
1650
- resolved = true;
1651
- resolve();
1652
- }
1653
- };
1654
-
1655
- const onExit = () => {
1656
- cleanup();
1657
- };
1658
-
1659
- const forceKillTimer = setTimeout(() => {
1660
- if (resolved) {
1661
- return;
1662
- }
1663
- forcedTermination = true;
1664
- console.warn('OpenCode process did not exit after SIGTERM, sending SIGKILL');
1665
- processToTerminate.kill('SIGKILL');
1666
- }, 3000);
1667
-
1668
- const hardStopTimer = setTimeout(() => {
1669
- if (resolved) {
1670
- return;
1671
- }
1672
- console.warn('OpenCode process unresponsive after SIGKILL, continuing restart');
1673
- cleanup();
1674
- }, 5000);
1675
-
1676
- processToTerminate.once('exit', onExit);
1677
- });
1678
-
1679
- if (forcedTermination) {
1680
- console.log('OpenCode process terminated forcefully during restart');
1681
- }
1682
- } else {
1683
- console.log('OpenCode process already stopped before restart command');
1404
+ console.log('Stopping existing OpenCode process...');
1405
+ try {
1406
+ openCodeProcess.close();
1407
+ } catch (error) {
1408
+ console.warn('Error closing OpenCode process:', error);
1684
1409
  }
1685
-
1686
1410
  openCodeProcess = null;
1687
1411
  syncToHmrState();
1688
-
1412
+
1413
+ // Brief delay to allow port release
1689
1414
  await new Promise((resolve) => setTimeout(resolve, 250));
1690
1415
  }
1691
1416
 
@@ -1696,6 +1421,8 @@ async function restartOpenCode() {
1696
1421
  openCodePort = null;
1697
1422
  syncToHmrState();
1698
1423
  }
1424
+
1425
+ // Reset detection state
1699
1426
  openCodeApiPrefixDetected = false;
1700
1427
  if (openCodeApiDetectionTimer) {
1701
1428
  clearTimeout(openCodeApiDetectionTimer);
@@ -1707,13 +1434,10 @@ async function restartOpenCode() {
1707
1434
  openCodeProcess = await startOpenCode();
1708
1435
  syncToHmrState();
1709
1436
 
1710
- if (!ENV_CONFIGURED_OPENCODE_PORT) {
1711
- await waitForOpenCodePort();
1712
- }
1713
-
1714
1437
  if (expressApp) {
1715
1438
  setupProxy(expressApp);
1716
- scheduleOpenCodeApiDetection();
1439
+ // Ensure prefix is set correctly (SDK usually handles this, but just in case)
1440
+ ensureOpenCodeApiPrefix();
1717
1441
  }
1718
1442
  })();
1719
1443
 
@@ -4121,7 +3845,12 @@ async function main(options = {}) {
4121
3845
  // NOTE: This route supports background execution to avoid tying up browser connections.
4122
3846
  const execJobs = new Map();
4123
3847
  const EXEC_JOB_TTL_MS = 30 * 60 * 1000;
4124
- const COMMAND_TIMEOUT_MS = 60000;
3848
+ const COMMAND_TIMEOUT_MS = (() => {
3849
+ const raw = Number(process.env.OPENCHAMBER_FS_EXEC_TIMEOUT_MS);
3850
+ if (Number.isFinite(raw) && raw > 0) return raw;
3851
+ // `bun install` (common worktree setup cmd) often takes >60s.
3852
+ return 5 * 60 * 1000;
3853
+ })();
4125
3854
 
4126
3855
  const pruneExecJobs = () => {
4127
3856
  const now = Date.now();
@@ -4143,9 +3872,12 @@ async function main(options = {}) {
4143
3872
  let stderr = '';
4144
3873
  let timedOut = false;
4145
3874
 
3875
+ const envPath = buildAugmentedPath();
3876
+ const execEnv = { ...process.env, PATH: envPath };
3877
+
4146
3878
  const child = spawn(shell, [shellFlag, command], {
4147
3879
  cwd: resolvedCwd,
4148
- env: process.env,
3880
+ env: execEnv,
4149
3881
  stdio: ['ignore', 'pipe', 'pipe'],
4150
3882
  });
4151
3883