@openagents-org/agent-launcher 0.2.191 → 0.2.193

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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Restore the execute bit on node-pty's `spawn-helper`.
3
+ *
4
+ * node-pty 1.1.0 publishes its macOS/Linux prebuilds with
5
+ * `prebuilds/<platform>/spawn-helper` at mode 0644, and npm does not fix it
6
+ * (the package has no install script to). pty.spawn() execs that helper, so
7
+ * every terminal the CLI opens dies with "posix_spawnp failed." — seen with
8
+ * Kimi Code, whose optional `node-pty` dependency resolves to 1.1.0. Every
9
+ * node-pty fork that ships a spawn-helper is treated the same, so the next CLI
10
+ * that pulls one in is covered too.
11
+ *
12
+ * Nothing to do on Windows: there is no spawn-helper and no mode bits.
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const IS_WINDOWS = process.platform === 'win32';
21
+
22
+ /** Nested node_modules deeper than this are not worth walking. */
23
+ const MAX_DEPTH = 6;
24
+
25
+ /** Packages whose spawn-helper this module restores. */
26
+ const PTY_PACKAGES = [
27
+ 'node-pty',
28
+ // @lydell keeps its prebuilds in a per-platform package.
29
+ `@lydell/node-pty-${process.platform}-${process.arch}`,
30
+ ];
31
+
32
+ function readDir(dir) {
33
+ try { return fs.readdirSync(dir); } catch { return []; }
34
+ }
35
+
36
+ /**
37
+ * chmod +x every spawn-helper inside one package directory.
38
+ * @returns {string[]} the helpers that were fixed
39
+ */
40
+ function fixPackage(pkgDir) {
41
+ const helpers = [path.join(pkgDir, 'build', 'Release', 'spawn-helper')];
42
+ const prebuilds = path.join(pkgDir, 'prebuilds');
43
+ for (const plat of readDir(prebuilds)) {
44
+ helpers.push(path.join(prebuilds, plat, 'spawn-helper'));
45
+ }
46
+ const fixed = [];
47
+ for (const helper of helpers) {
48
+ try {
49
+ const { mode } = fs.statSync(helper);
50
+ if ((mode & 0o111) === 0o111) continue;
51
+ fs.chmodSync(helper, mode | 0o755);
52
+ fixed.push(helper);
53
+ } catch {}
54
+ }
55
+ return fixed;
56
+ }
57
+
58
+ /**
59
+ * Walk an npm prefix's node_modules (nested ones included) and fix every
60
+ * package that carries a spawn-helper. Run after an npm install.
61
+ * @param {string} prefixDir - the `--prefix` the agent was installed into
62
+ * @returns {string[]} the helpers that were fixed
63
+ */
64
+ function fixSpawnHelpers(prefixDir) {
65
+ if (IS_WINDOWS) return [];
66
+ const fixed = [];
67
+ const visit = (modulesDir, depth) => {
68
+ if (depth > MAX_DEPTH) return;
69
+ for (const entry of readDir(modulesDir)) {
70
+ if (entry.startsWith('.')) continue;
71
+ const dirs = entry.startsWith('@')
72
+ ? readDir(path.join(modulesDir, entry)).map((s) => path.join(modulesDir, entry, s))
73
+ : [path.join(modulesDir, entry)];
74
+ for (const pkgDir of dirs) {
75
+ fixed.push(...fixPackage(pkgDir));
76
+ visit(path.join(pkgDir, 'node_modules'), depth + 1);
77
+ }
78
+ }
79
+ };
80
+ visit(path.join(prefixDir, 'node_modules'), 0);
81
+ return fixed;
82
+ }
83
+
84
+ /**
85
+ * Fix the node-pty a CLI would load, resolved the way Node resolves it from
86
+ * the CLI's own location — for an install that predates fixSpawnHelpers, or
87
+ * one the user made themselves.
88
+ * @param {string} fromPath - the CLI binary or entry script
89
+ * @returns {string[]} the helpers that were fixed
90
+ */
91
+ function fixSpawnHelpersFor(fromPath) {
92
+ if (IS_WINDOWS || !fromPath) return [];
93
+ let start;
94
+ try { start = path.dirname(fs.realpathSync(fromPath)); } catch { return []; }
95
+ // Node's lookup by hand: the nearest node_modules/<name> going up. Not
96
+ // require.resolve — a package with an `exports` map refuses package.json.
97
+ const fixed = [];
98
+ for (const name of PTY_PACKAGES) {
99
+ for (let dir = start; ; dir = path.dirname(dir)) {
100
+ const pkgDir = path.join(dir, 'node_modules', name);
101
+ if (fs.existsSync(path.join(pkgDir, 'package.json'))) {
102
+ fixed.push(...fixPackage(pkgDir));
103
+ break;
104
+ }
105
+ if (path.dirname(dir) === dir) break;
106
+ }
107
+ }
108
+ return fixed;
109
+ }
110
+
111
+ module.exports = { fixSpawnHelpers, fixSpawnHelpersFor };
@@ -5,6 +5,29 @@ const http = require('http');
5
5
 
6
6
  const DEFAULT_ENDPOINT = 'https://workspace-endpoint.openagents.org';
7
7
 
8
+ /**
9
+ * The error a request deadline produces. Every request carries a hard
10
+ * AbortSignal.timeout deadline (see _get), and when it fires Node rejects with
11
+ * a bare "AbortError: The operation was aborted" — which reads like somebody
12
+ * cancelled the call and names no cause. The socket-inactivity timeout said
13
+ * "Request timed out". Both now say the same thing, with the deadline, and
14
+ * carry ETIMEDOUT so callers can tell a slow network from a bug.
15
+ */
16
+ function requestTimeoutError(timeout) {
17
+ const after = timeout >= 1000 ? `${Math.round(timeout / 1000)}s` : `${timeout}ms`;
18
+ const err = new Error(`Request timed out after ${after}`);
19
+ err.code = 'ETIMEDOUT';
20
+ return err;
21
+ }
22
+
23
+ /** A request 'error' handler that reports a fired deadline as a timeout. */
24
+ function rejectWithDeadline(reject, timeout) {
25
+ return (err) => {
26
+ const aborted = err && (err.name === 'AbortError' || err.code === 'ABORT_ERR');
27
+ reject(aborted ? requestTimeoutError(timeout) : err);
28
+ };
29
+ }
30
+
8
31
  /**
9
32
  * Thrown when the workspace rejects a request because our session_id has
10
33
  * been revoked by a newer /v1/join as the same agent. Callers should
@@ -967,18 +990,25 @@ class WorkspaceClient {
967
990
  try {
968
991
  const parsed = JSON.parse(data);
969
992
  if (res.statusCode >= 400) {
970
- reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
993
+ const err = new Error(parsed.message || `HTTP ${res.statusCode}`);
994
+ // Lets the poll loop tell a rejected credential (401/403) and an
995
+ // overloaded server (5xx) apart from a bug.
996
+ err.status = res.statusCode;
997
+ reject(err);
971
998
  } else {
972
999
  resolve(parsed);
973
1000
  }
974
1001
  } catch {
975
- reject(new Error(`Invalid response: ${data.slice(0, 200)}`));
1002
+ // A proxy's HTML error page (502/504) lands here.
1003
+ const err = new Error(`Invalid response: ${data.slice(0, 200)}`);
1004
+ if (res.statusCode >= 400) err.status = res.statusCode;
1005
+ reject(err);
976
1006
  }
977
1007
  });
978
1008
  });
979
1009
 
980
- req.on('error', reject);
981
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1010
+ req.on('error', rejectWithDeadline(reject, timeout));
1011
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
982
1012
  req.end();
983
1013
  });
984
1014
  }
@@ -1008,8 +1038,8 @@ class WorkspaceClient {
1008
1038
  });
1009
1039
  });
1010
1040
 
1011
- req.on('error', reject);
1012
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1041
+ req.on('error', rejectWithDeadline(reject, timeout));
1042
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
1013
1043
  req.end();
1014
1044
  });
1015
1045
  }
@@ -1058,8 +1088,8 @@ class WorkspaceClient {
1058
1088
  });
1059
1089
  });
1060
1090
 
1061
- req.on('error', reject);
1062
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1091
+ req.on('error', rejectWithDeadline(reject, timeout));
1092
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
1063
1093
  req.write(jsonBody);
1064
1094
  req.end();
1065
1095
  });
@@ -1101,8 +1131,8 @@ class WorkspaceClient {
1101
1131
  });
1102
1132
  });
1103
1133
 
1104
- req.on('error', reject);
1105
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1134
+ req.on('error', rejectWithDeadline(reject, timeout));
1135
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
1106
1136
  req.write(jsonBody);
1107
1137
  req.end();
1108
1138
  });
@@ -1139,8 +1169,8 @@ class WorkspaceClient {
1139
1169
  });
1140
1170
  });
1141
1171
 
1142
- req.on('error', reject);
1143
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1172
+ req.on('error', rejectWithDeadline(reject, timeout));
1173
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(timeout)); });
1144
1174
  req.write(jsonBody);
1145
1175
  req.end();
1146
1176
  });
@@ -1175,8 +1205,8 @@ class WorkspaceClient {
1175
1205
  });
1176
1206
  });
1177
1207
 
1178
- req.on('error', reject);
1179
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
1208
+ req.on('error', rejectWithDeadline(reject, 15000));
1209
+ req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(15000)); });
1180
1210
  req.end();
1181
1211
  });
1182
1212
  }