@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.
- package/package.json +1 -1
- package/src/adapters/base.js +55 -26
- package/src/adapters/codex.js +33 -2
- package/src/adapters/failure-streak.js +89 -0
- package/src/adapters/kimi.js +11 -0
- package/src/adapters/pi.js +2 -1
- package/src/config.js +107 -28
- package/src/daemon.js +30 -35
- package/src/installer.js +26 -4
- package/src/log-writer.js +351 -0
- package/src/node-pty-perms.js +111 -0
- package/src/workspace-client.js +44 -14
|
@@ -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 };
|
package/src/workspace-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1208
|
+
req.on('error', rejectWithDeadline(reject, 15000));
|
|
1209
|
+
req.on('timeout', () => { req.destroy(); reject(requestTimeoutError(15000)); });
|
|
1180
1210
|
req.end();
|
|
1181
1211
|
});
|
|
1182
1212
|
}
|