indusagi 0.13.8 → 0.13.9

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/agent.js CHANGED
@@ -5,8 +5,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  if (typeof require !== "undefined") return require.apply(this, arguments);
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ var __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
10
15
  };
11
16
 
12
17
  // src/facade/ml/kit/streaming-state-manager.ts
@@ -4689,6 +4694,75 @@ var MODELS = {
4689
4694
  contextWindow: 1e6,
4690
4695
  maxTokens: 128e3
4691
4696
  },
4697
+ "gpt-5.6-sol": {
4698
+ id: "gpt-5.6-sol",
4699
+ name: "GPT-5.6 Sol",
4700
+ api: "openai-responses",
4701
+ provider: "openai",
4702
+ baseUrl: "https://api.openai.com/v1",
4703
+ reasoning: true,
4704
+ input: ["text", "image"],
4705
+ cost: {
4706
+ input: 5,
4707
+ output: 30,
4708
+ cacheRead: 0.5,
4709
+ cacheWrite: 0
4710
+ },
4711
+ contextWindow: 105e4,
4712
+ maxTokens: 128e3
4713
+ },
4714
+ "gpt-5.6-terra": {
4715
+ id: "gpt-5.6-terra",
4716
+ name: "GPT-5.6 Terra",
4717
+ api: "openai-responses",
4718
+ provider: "openai",
4719
+ baseUrl: "https://api.openai.com/v1",
4720
+ reasoning: true,
4721
+ input: ["text", "image"],
4722
+ cost: {
4723
+ input: 2,
4724
+ output: 12,
4725
+ // ponytail: 0 means unavailable cached-input pricing; allow null if the catalog must distinguish it from free.
4726
+ cacheRead: 0,
4727
+ cacheWrite: 0
4728
+ },
4729
+ contextWindow: 105e4,
4730
+ maxTokens: 128e3
4731
+ },
4732
+ "gpt-5.6-luna": {
4733
+ id: "gpt-5.6-luna",
4734
+ name: "GPT-5.6 Luna",
4735
+ api: "openai-responses",
4736
+ provider: "openai",
4737
+ baseUrl: "https://api.openai.com/v1",
4738
+ reasoning: true,
4739
+ input: ["text", "image"],
4740
+ cost: {
4741
+ input: 0.2,
4742
+ output: 1.2,
4743
+ cacheRead: 0,
4744
+ cacheWrite: 0
4745
+ },
4746
+ contextWindow: 105e4,
4747
+ maxTokens: 128e3
4748
+ },
4749
+ "gpt-5.6": {
4750
+ id: "gpt-5.6",
4751
+ name: "GPT-5.6",
4752
+ api: "openai-responses",
4753
+ provider: "openai",
4754
+ baseUrl: "https://api.openai.com/v1",
4755
+ reasoning: true,
4756
+ input: ["text", "image"],
4757
+ cost: {
4758
+ input: 5,
4759
+ output: 30,
4760
+ cacheRead: 0.5,
4761
+ cacheWrite: 0
4762
+ },
4763
+ contextWindow: 105e4,
4764
+ maxTokens: 128e3
4765
+ },
4692
4766
  "o1": {
4693
4767
  id: "o1",
4694
4768
  name: "o1",
@@ -5013,6 +5087,75 @@ var MODELS = {
5013
5087
  },
5014
5088
  contextWindow: 1e6,
5015
5089
  maxTokens: 128e3
5090
+ },
5091
+ "gpt-5.6-sol": {
5092
+ id: "gpt-5.6-sol",
5093
+ name: "GPT-5.6 Sol",
5094
+ api: "openai-codex-responses",
5095
+ provider: "openai-codex",
5096
+ baseUrl: "https://chatgpt.com/backend-api",
5097
+ reasoning: true,
5098
+ input: ["text", "image"],
5099
+ cost: {
5100
+ input: 5,
5101
+ output: 30,
5102
+ cacheRead: 0.5,
5103
+ cacheWrite: 0
5104
+ },
5105
+ contextWindow: 105e4,
5106
+ maxTokens: 128e3
5107
+ },
5108
+ "gpt-5.6-terra": {
5109
+ id: "gpt-5.6-terra",
5110
+ name: "GPT-5.6 Terra",
5111
+ api: "openai-codex-responses",
5112
+ provider: "openai-codex",
5113
+ baseUrl: "https://chatgpt.com/backend-api",
5114
+ reasoning: true,
5115
+ input: ["text", "image"],
5116
+ cost: {
5117
+ input: 2,
5118
+ output: 12,
5119
+ // ponytail: 0 means unavailable cached-input pricing; allow null if the catalog must distinguish it from free.
5120
+ cacheRead: 0,
5121
+ cacheWrite: 0
5122
+ },
5123
+ contextWindow: 105e4,
5124
+ maxTokens: 128e3
5125
+ },
5126
+ "gpt-5.6-luna": {
5127
+ id: "gpt-5.6-luna",
5128
+ name: "GPT-5.6 Luna",
5129
+ api: "openai-codex-responses",
5130
+ provider: "openai-codex",
5131
+ baseUrl: "https://chatgpt.com/backend-api",
5132
+ reasoning: true,
5133
+ input: ["text", "image"],
5134
+ cost: {
5135
+ input: 0.2,
5136
+ output: 1.2,
5137
+ cacheRead: 0,
5138
+ cacheWrite: 0
5139
+ },
5140
+ contextWindow: 105e4,
5141
+ maxTokens: 128e3
5142
+ },
5143
+ "gpt-5.6": {
5144
+ id: "gpt-5.6",
5145
+ name: "GPT-5.6",
5146
+ api: "openai-codex-responses",
5147
+ provider: "openai-codex",
5148
+ baseUrl: "https://chatgpt.com/backend-api",
5149
+ reasoning: true,
5150
+ input: ["text", "image"],
5151
+ cost: {
5152
+ input: 5,
5153
+ output: 30,
5154
+ cacheRead: 0.5,
5155
+ cacheWrite: 0
5156
+ },
5157
+ contextWindow: 105e4,
5158
+ maxTokens: 128e3
5016
5159
  }
5017
5160
  },
5018
5161
  "opencode": {
@@ -16864,8 +17007,8 @@ function buildHeaders(initHeaders, additionalHeaders, accountId, token, sessionI
16864
17007
  headers.set("Authorization", `Bearer ${token}`);
16865
17008
  headers.set("chatgpt-account-id", accountId);
16866
17009
  headers.set("OpenAI-Beta", "responses=experimental");
16867
- headers.set("originator", "indusagi");
16868
- const userAgent = _os ? `indusagi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "indusagi (browser)";
17010
+ headers.set("originator", "pi");
17011
+ const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
16869
17012
  headers.set("User-Agent", userAgent);
16870
17013
  headers.set("accept", "text/event-stream");
16871
17014
  headers.set("content-type", "application/json");
@@ -18394,7 +18537,12 @@ function ensureNodeBuiltins() {
18394
18537
  });
18395
18538
  }
18396
18539
  ensureNodeBuiltins();
18397
- var CLIENT_ID2 = process.env.INDUSAGI_OPENAI_CODEX_CLIENT_ID ?? "<UNREGISTERED-OPENAI-CODEX-APP>";
18540
+ var DEFAULT_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
18541
+ var UNREGISTERED_CLIENT_ID = "<UNREGISTERED-OPENAI-CODEX-APP>";
18542
+ function resolveOpenAICodexClientId(configured = process.env.INDUSAGI_OPENAI_CODEX_CLIENT_ID) {
18543
+ return configured && configured !== UNREGISTERED_CLIENT_ID ? configured : DEFAULT_CLIENT_ID;
18544
+ }
18545
+ var CLIENT_ID2 = resolveOpenAICodexClientId();
18398
18546
 
18399
18547
  // src/facade/ml/kit/overflow.ts
18400
18548
  var OVERFLOW_PATTERN_CONFIG = [
package/dist/ai.js CHANGED
@@ -8,8 +8,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
  if (typeof require !== "undefined") return require.apply(this, arguments);
9
9
  throw Error('Dynamic require of "' + x + '" is not supported');
10
10
  });
11
- var __esm = (fn, res) => function __init() {
12
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ var __esm = (fn, res, err) => function __init() {
12
+ if (err) throw err[0];
13
+ try {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ } catch (e) {
16
+ throw err = [e], e;
17
+ }
13
18
  };
14
19
  var __export = (target, all) => {
15
20
  for (var name in all)
@@ -4813,6 +4818,75 @@ var MODELS = {
4813
4818
  contextWindow: 1e6,
4814
4819
  maxTokens: 128e3
4815
4820
  },
4821
+ "gpt-5.6-sol": {
4822
+ id: "gpt-5.6-sol",
4823
+ name: "GPT-5.6 Sol",
4824
+ api: "openai-responses",
4825
+ provider: "openai",
4826
+ baseUrl: "https://api.openai.com/v1",
4827
+ reasoning: true,
4828
+ input: ["text", "image"],
4829
+ cost: {
4830
+ input: 5,
4831
+ output: 30,
4832
+ cacheRead: 0.5,
4833
+ cacheWrite: 0
4834
+ },
4835
+ contextWindow: 105e4,
4836
+ maxTokens: 128e3
4837
+ },
4838
+ "gpt-5.6-terra": {
4839
+ id: "gpt-5.6-terra",
4840
+ name: "GPT-5.6 Terra",
4841
+ api: "openai-responses",
4842
+ provider: "openai",
4843
+ baseUrl: "https://api.openai.com/v1",
4844
+ reasoning: true,
4845
+ input: ["text", "image"],
4846
+ cost: {
4847
+ input: 2,
4848
+ output: 12,
4849
+ // ponytail: 0 means unavailable cached-input pricing; allow null if the catalog must distinguish it from free.
4850
+ cacheRead: 0,
4851
+ cacheWrite: 0
4852
+ },
4853
+ contextWindow: 105e4,
4854
+ maxTokens: 128e3
4855
+ },
4856
+ "gpt-5.6-luna": {
4857
+ id: "gpt-5.6-luna",
4858
+ name: "GPT-5.6 Luna",
4859
+ api: "openai-responses",
4860
+ provider: "openai",
4861
+ baseUrl: "https://api.openai.com/v1",
4862
+ reasoning: true,
4863
+ input: ["text", "image"],
4864
+ cost: {
4865
+ input: 0.2,
4866
+ output: 1.2,
4867
+ cacheRead: 0,
4868
+ cacheWrite: 0
4869
+ },
4870
+ contextWindow: 105e4,
4871
+ maxTokens: 128e3
4872
+ },
4873
+ "gpt-5.6": {
4874
+ id: "gpt-5.6",
4875
+ name: "GPT-5.6",
4876
+ api: "openai-responses",
4877
+ provider: "openai",
4878
+ baseUrl: "https://api.openai.com/v1",
4879
+ reasoning: true,
4880
+ input: ["text", "image"],
4881
+ cost: {
4882
+ input: 5,
4883
+ output: 30,
4884
+ cacheRead: 0.5,
4885
+ cacheWrite: 0
4886
+ },
4887
+ contextWindow: 105e4,
4888
+ maxTokens: 128e3
4889
+ },
4816
4890
  "o1": {
4817
4891
  id: "o1",
4818
4892
  name: "o1",
@@ -5137,6 +5211,75 @@ var MODELS = {
5137
5211
  },
5138
5212
  contextWindow: 1e6,
5139
5213
  maxTokens: 128e3
5214
+ },
5215
+ "gpt-5.6-sol": {
5216
+ id: "gpt-5.6-sol",
5217
+ name: "GPT-5.6 Sol",
5218
+ api: "openai-codex-responses",
5219
+ provider: "openai-codex",
5220
+ baseUrl: "https://chatgpt.com/backend-api",
5221
+ reasoning: true,
5222
+ input: ["text", "image"],
5223
+ cost: {
5224
+ input: 5,
5225
+ output: 30,
5226
+ cacheRead: 0.5,
5227
+ cacheWrite: 0
5228
+ },
5229
+ contextWindow: 105e4,
5230
+ maxTokens: 128e3
5231
+ },
5232
+ "gpt-5.6-terra": {
5233
+ id: "gpt-5.6-terra",
5234
+ name: "GPT-5.6 Terra",
5235
+ api: "openai-codex-responses",
5236
+ provider: "openai-codex",
5237
+ baseUrl: "https://chatgpt.com/backend-api",
5238
+ reasoning: true,
5239
+ input: ["text", "image"],
5240
+ cost: {
5241
+ input: 2,
5242
+ output: 12,
5243
+ // ponytail: 0 means unavailable cached-input pricing; allow null if the catalog must distinguish it from free.
5244
+ cacheRead: 0,
5245
+ cacheWrite: 0
5246
+ },
5247
+ contextWindow: 105e4,
5248
+ maxTokens: 128e3
5249
+ },
5250
+ "gpt-5.6-luna": {
5251
+ id: "gpt-5.6-luna",
5252
+ name: "GPT-5.6 Luna",
5253
+ api: "openai-codex-responses",
5254
+ provider: "openai-codex",
5255
+ baseUrl: "https://chatgpt.com/backend-api",
5256
+ reasoning: true,
5257
+ input: ["text", "image"],
5258
+ cost: {
5259
+ input: 0.2,
5260
+ output: 1.2,
5261
+ cacheRead: 0,
5262
+ cacheWrite: 0
5263
+ },
5264
+ contextWindow: 105e4,
5265
+ maxTokens: 128e3
5266
+ },
5267
+ "gpt-5.6": {
5268
+ id: "gpt-5.6",
5269
+ name: "GPT-5.6",
5270
+ api: "openai-codex-responses",
5271
+ provider: "openai-codex",
5272
+ baseUrl: "https://chatgpt.com/backend-api",
5273
+ reasoning: true,
5274
+ input: ["text", "image"],
5275
+ cost: {
5276
+ input: 5,
5277
+ output: 30,
5278
+ cacheRead: 0.5,
5279
+ cacheWrite: 0
5280
+ },
5281
+ contextWindow: 105e4,
5282
+ maxTokens: 128e3
5140
5283
  }
5141
5284
  },
5142
5285
  "opencode": {
@@ -17077,8 +17220,8 @@ function buildHeaders(initHeaders, additionalHeaders, accountId, token, sessionI
17077
17220
  headers.set("Authorization", `Bearer ${token}`);
17078
17221
  headers.set("chatgpt-account-id", accountId);
17079
17222
  headers.set("OpenAI-Beta", "responses=experimental");
17080
- headers.set("originator", "indusagi");
17081
- const userAgent = _os ? `indusagi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "indusagi (browser)";
17223
+ headers.set("originator", "pi");
17224
+ const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
17082
17225
  headers.set("User-Agent", userAgent);
17083
17226
  headers.set("accept", "text/event-stream");
17084
17227
  headers.set("content-type", "application/json");
@@ -19132,18 +19275,23 @@ function ensureNodeBuiltins() {
19132
19275
  });
19133
19276
  }
19134
19277
  ensureNodeBuiltins();
19135
- var CLIENT_ID2 = process.env.INDUSAGI_OPENAI_CODEX_CLIENT_ID ?? "<UNREGISTERED-OPENAI-CODEX-APP>";
19278
+ var DEFAULT_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
19279
+ var UNREGISTERED_CLIENT_ID = "<UNREGISTERED-OPENAI-CODEX-APP>";
19280
+ function resolveOpenAICodexClientId(configured = process.env.INDUSAGI_OPENAI_CODEX_CLIENT_ID) {
19281
+ return configured && configured !== UNREGISTERED_CLIENT_ID ? configured : DEFAULT_CLIENT_ID;
19282
+ }
19283
+ var CLIENT_ID2 = resolveOpenAICodexClientId();
19136
19284
  var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
19137
19285
  var TOKEN_URL2 = "https://auth.openai.com/oauth/token";
19138
19286
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
19139
- var SCOPE = "openid profile email offline_access";
19287
+ var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
19140
19288
  var JWT_CLAIM_PATH2 = "https://api.openai.com/auth";
19141
19289
  function createState() {
19142
19290
  const { randomBytes } = nodeBuiltins;
19143
19291
  if (!randomBytes) {
19144
19292
  throw new Error("Codex sign-in needs a Node or Bun runtime and cannot run inside a browser.");
19145
19293
  }
19146
- return randomBytes(16).toString("hex");
19294
+ return randomBytes(32).toString("hex");
19147
19295
  }
19148
19296
  function readCodeAndState(params) {
19149
19297
  return {
@@ -19204,13 +19352,12 @@ async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_
19204
19352
  })
19205
19353
  });
19206
19354
  if (!response.ok) {
19207
- const text = await response.text().catch(() => "");
19208
- console.error("[openai-codex] exchanging the code for a token failed:", response.status, text);
19355
+ console.error("[openai-codex] exchanging the code for a token failed:", response.status);
19209
19356
  return { type: "failed" };
19210
19357
  }
19211
19358
  const json = await response.json();
19212
19359
  if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
19213
- console.error("[openai-codex] token response was missing required fields:", json);
19360
+ console.error("[openai-codex] token response was missing required fields");
19214
19361
  return { type: "failed" };
19215
19362
  }
19216
19363
  return {
@@ -19232,13 +19379,12 @@ async function refreshAccessToken(refreshToken) {
19232
19379
  })
19233
19380
  });
19234
19381
  if (!response.ok) {
19235
- const text = await response.text().catch(() => "");
19236
- console.error("[openai-codex] refreshing the token failed:", response.status, text);
19382
+ console.error("[openai-codex] refreshing the token failed:", response.status);
19237
19383
  return { type: "failed" };
19238
19384
  }
19239
19385
  const json = await response.json();
19240
19386
  if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
19241
- console.error("[openai-codex] refresh response was missing required fields:", json);
19387
+ console.error("[openai-codex] refresh response was missing required fields");
19242
19388
  return { type: "failed" };
19243
19389
  }
19244
19390
  return {
@@ -19247,27 +19393,27 @@ async function refreshAccessToken(refreshToken) {
19247
19393
  refresh: json.refresh_token,
19248
19394
  expires: Date.now() + json.expires_in * 1e3
19249
19395
  };
19250
- } catch (error) {
19251
- console.error("[openai-codex] token refresh threw an error:", error);
19396
+ } catch {
19397
+ console.error("[openai-codex] token refresh failed");
19252
19398
  return { type: "failed" };
19253
19399
  }
19254
19400
  }
19255
- async function createAuthorizationFlow(originator = process.env.INDUSAGI_OPENAI_CODEX_ORIGINATOR ?? "indusagi") {
19401
+ async function createAuthorizationFlow(originator = process.env.INDUSAGI_OPENAI_CODEX_ORIGINATOR ?? "codex_cli_rs") {
19256
19402
  const { verifier, challenge } = await generatePKCE();
19257
19403
  const state = createState();
19258
- const query = new URLSearchParams({
19259
- client_id: CLIENT_ID2,
19260
- response_type: "code",
19261
- redirect_uri: REDIRECT_URI2,
19262
- scope: SCOPE,
19263
- state,
19264
- code_challenge: challenge,
19265
- code_challenge_method: "S256",
19266
- id_token_add_organizations: "true",
19267
- codex_cli_simplified_flow: "true",
19268
- originator
19269
- });
19270
- const url = `${AUTHORIZE_URL2}?${query.toString()}`;
19404
+ const query = new URLSearchParams([
19405
+ ["response_type", "code"],
19406
+ ["client_id", CLIENT_ID2],
19407
+ ["redirect_uri", REDIRECT_URI2],
19408
+ ["scope", SCOPE],
19409
+ ["code_challenge", challenge],
19410
+ ["code_challenge_method", "S256"],
19411
+ ["id_token_add_organizations", "true"],
19412
+ ["codex_cli_simplified_flow", "true"],
19413
+ ["state", state],
19414
+ ["originator", originator]
19415
+ ]);
19416
+ const url = `${AUTHORIZE_URL2}?${query}`;
19271
19417
  return { verifier, state, url };
19272
19418
  }
19273
19419
  function createSingleShotGate() {
@@ -263,7 +263,7 @@ var ToolRegistry = class {
263
263
 
264
264
  // src/capabilities/backends/node-backends.ts
265
265
  import { promises as fsp } from "node:fs";
266
- import { spawn as childSpawn } from "node:child_process";
266
+ import { spawn as childSpawn, spawnSync } from "node:child_process";
267
267
  var NodeFs = class {
268
268
  async readFile(path, encoding) {
269
269
  if (encoding === void 0) {
@@ -323,6 +323,21 @@ function joinChunks(chunks) {
323
323
  function asSignal(signal) {
324
324
  return signal;
325
325
  }
326
+ function killProcessTree(child, signal = "SIGTERM") {
327
+ if (child.pid === void 0) return;
328
+ if (process.platform === "win32") {
329
+ spawnSync("taskkill", ["/F", "/T", "/PID", String(child.pid)], {
330
+ stdio: "ignore",
331
+ windowsHide: true
332
+ });
333
+ return;
334
+ }
335
+ try {
336
+ process.kill(-child.pid, signal);
337
+ } catch {
338
+ child.kill(signal);
339
+ }
340
+ }
326
341
  var NodeShell = class {
327
342
  exec(command, options) {
328
343
  if (typeof command !== "string" || command.length === 0) {
@@ -333,21 +348,30 @@ var NodeShell = class {
333
348
  return new Promise((resolve, reject) => {
334
349
  const child = childSpawn(command, {
335
350
  shell: true,
351
+ detached: process.platform !== "win32",
336
352
  cwd: options?.cwd,
337
- env: options?.env ? { ...process.env, ...options.env } : process.env,
338
- signal: options?.signal
353
+ env: options?.env ? { ...process.env, ...options.env } : process.env
339
354
  });
340
355
  const out = [];
341
356
  const err = [];
342
357
  let settled = false;
343
358
  let timedOut = false;
359
+ let aborted = false;
344
360
  let timer;
361
+ const abortSignal = options?.signal;
362
+ const onAbort = () => {
363
+ aborted = true;
364
+ killProcessTree(child);
365
+ };
345
366
  const finish = (fn) => {
346
367
  if (settled) return;
347
368
  settled = true;
348
369
  if (timer !== void 0) clearTimeout(timer);
370
+ abortSignal?.removeEventListener("abort", onAbort);
349
371
  fn();
350
372
  };
373
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
374
+ if (abortSignal?.aborted) onAbort();
351
375
  child.stdout?.on("data", (chunk) => out.push(chunk));
352
376
  child.stderr?.on("data", (chunk) => err.push(chunk));
353
377
  if (options?.input !== void 0 && child.stdin) {
@@ -360,7 +384,7 @@ var NodeShell = class {
360
384
  if (limit !== void 0 && limit > 0) {
361
385
  timer = setTimeout(() => {
362
386
  timedOut = true;
363
- child.kill("SIGTERM");
387
+ killProcessTree(child);
364
388
  }, limit);
365
389
  }
366
390
  child.on("error", (cause) => {
@@ -376,6 +400,10 @@ var NodeShell = class {
376
400
  );
377
401
  return;
378
402
  }
403
+ if (aborted) {
404
+ reject(new Error("The command was aborted and stopped."));
405
+ return;
406
+ }
379
407
  resolve({
380
408
  stdout: joinChunks(out),
381
409
  stderr: joinChunks(err),
@@ -393,11 +421,11 @@ var NodeShell = class {
393
421
  }
394
422
  const child = childSpawn(command, {
395
423
  shell: true,
424
+ detached: process.platform !== "win32",
396
425
  cwd: options?.cwd,
397
- env: options?.env ? { ...process.env, ...options.env } : process.env,
398
- signal: options?.signal
426
+ env: options?.env ? { ...process.env, ...options.env } : process.env
399
427
  });
400
- return new NodeShellHandle(child);
428
+ return new NodeShellHandle(child, options?.signal);
401
429
  }
402
430
  };
403
431
  var NodeShellHandle = class {
@@ -406,27 +434,33 @@ var NodeShellHandle = class {
406
434
  exited = false;
407
435
  exitCode = null;
408
436
  exitSignal = null;
409
- constructor(child) {
437
+ abortSignal;
438
+ onAbort = () => this.kill();
439
+ constructor(child, abortSignal) {
410
440
  this.child = child;
441
+ this.abortSignal = abortSignal;
411
442
  child.once(
412
443
  "close",
413
444
  (code, signal) => {
414
445
  this.exited = true;
415
446
  this.exitCode = code;
416
447
  this.exitSignal = asSignal(signal);
448
+ this.abortSignal?.removeEventListener("abort", this.onAbort);
417
449
  for (const listener of [...this.listeners]) {
418
450
  listener(this.exitCode, this.exitSignal);
419
451
  }
420
452
  this.listeners.clear();
421
453
  }
422
454
  );
455
+ this.abortSignal?.addEventListener("abort", this.onAbort, { once: true });
456
+ if (this.abortSignal?.aborted) this.onAbort();
423
457
  }
424
458
  get pid() {
425
459
  return this.child.pid;
426
460
  }
427
461
  kill(signal) {
428
462
  if (this.exited) return;
429
- this.child.kill(signal ?? "SIGTERM");
463
+ killProcessTree(this.child, signal);
430
464
  }
431
465
  onExit(listener) {
432
466
  if (this.exited) {
@@ -1860,9 +1894,6 @@ function readInput2(raw) {
1860
1894
  }
1861
1895
  return { action, command, id };
1862
1896
  }
1863
- function shellQuote(value) {
1864
- return `'${value.replace(/'/g, `'\\''`)}'`;
1865
- }
1866
1897
  async function drainCapture(ctx, path, alreadyRead) {
1867
1898
  let full;
1868
1899
  try {
@@ -1898,7 +1929,7 @@ async function startJob(ctx, command) {
1898
1929
  await ctx.fs.mkdir(scratchDir, { recursive: true });
1899
1930
  await ctx.fs.writeFile(outPath, "", "utf8");
1900
1931
  await ctx.fs.writeFile(errPath, "", "utf8");
1901
- const wrapped = `( ${command} ) >${shellQuote(outPath)} 2>${shellQuote(errPath)}`;
1932
+ const wrapped = `( ${command} ) >${SCRATCH_DIR}/${id}.out 2>${SCRATCH_DIR}/${id}.err`;
1902
1933
  const handle = ctx.shell.spawn(wrapped, { cwd: ctx.cwd });
1903
1934
  const entry = {
1904
1935
  id,
@@ -1978,7 +2009,7 @@ async function pollJob(ctx, id) {
1978
2009
  hadNewOutput: out.fresh.length > 0 || err.fresh.length > 0
1979
2010
  });
1980
2011
  }
1981
- function stopJob(id) {
2012
+ async function stopJob(id) {
1982
2013
  const entry = table.remove(id);
1983
2014
  if (entry === void 0) {
1984
2015
  return asText2(
@@ -1986,14 +2017,24 @@ function stopJob(id) {
1986
2017
  true
1987
2018
  );
1988
2019
  }
1989
- entry.unsubscribe();
1990
2020
  const wasRunning = entry.state === "running";
1991
2021
  if (wasRunning) {
1992
- try {
1993
- entry.handle.kill();
1994
- } catch {
1995
- }
2022
+ await new Promise((resolve) => {
2023
+ let unsubscribeExit = () => {
2024
+ };
2025
+ unsubscribeExit = entry.handle.onExit(() => {
2026
+ unsubscribeExit();
2027
+ resolve();
2028
+ });
2029
+ try {
2030
+ entry.handle.kill();
2031
+ } catch {
2032
+ unsubscribeExit();
2033
+ resolve();
2034
+ }
2035
+ });
1996
2036
  }
2037
+ entry.unsubscribe();
1997
2038
  return asJson({
1998
2039
  action: "stop",
1999
2040
  id: entry.id,