@engineeros/connector 0.6.0 → 0.6.2
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/bin/engineeros-connector.mjs +44 -15
- package/package.json +2 -2
- package/src/capabilities.mjs +13 -0
- package/src/connection.mjs +26 -0
|
@@ -19,6 +19,11 @@ import {
|
|
|
19
19
|
stopProcess,
|
|
20
20
|
workspaceSnapshot,
|
|
21
21
|
} from "../src/runner.mjs";
|
|
22
|
+
import {
|
|
23
|
+
describeWebSocketError,
|
|
24
|
+
startConnectionWatchdog,
|
|
25
|
+
} from "../src/connection.mjs";
|
|
26
|
+
import { advertisedCapabilities } from "../src/capabilities.mjs";
|
|
22
27
|
|
|
23
28
|
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
24
29
|
|
|
@@ -59,13 +64,7 @@ if (command === "pair") {
|
|
|
59
64
|
type: "pair",
|
|
60
65
|
pairing_code: pairingCode,
|
|
61
66
|
name: config.name,
|
|
62
|
-
capabilities: {
|
|
63
|
-
agent_protocols: flags["agent-command"] ? ["acp"] : ["codex"],
|
|
64
|
-
coding_agent: true,
|
|
65
|
-
codex_cli: !flags["agent-command"],
|
|
66
|
-
platform: process.platform,
|
|
67
|
-
workspace_name: path.basename(config.workspace),
|
|
68
|
-
},
|
|
67
|
+
capabilities: {},
|
|
69
68
|
};
|
|
70
69
|
} else if (command === "start") {
|
|
71
70
|
config = await loadConfig(flags.workspace || process.cwd());
|
|
@@ -89,10 +88,8 @@ try {
|
|
|
89
88
|
fail(error instanceof Error ? error.message : String(error));
|
|
90
89
|
}
|
|
91
90
|
console.log(`Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
firstMessage.capabilities.agent_version = codingAgent.version;
|
|
95
|
-
}
|
|
91
|
+
const capabilities = advertisedCapabilities(config, codingAgent);
|
|
92
|
+
firstMessage.capabilities = capabilities;
|
|
96
93
|
|
|
97
94
|
let stopped = false;
|
|
98
95
|
let active = null;
|
|
@@ -101,12 +98,18 @@ const assessments = [];
|
|
|
101
98
|
const prompts = [];
|
|
102
99
|
let socket;
|
|
103
100
|
let pingTimer;
|
|
101
|
+
let reconnectTimer;
|
|
102
|
+
let clearConnectionWatchdog;
|
|
104
103
|
let reconnectDelay = 1_000;
|
|
105
104
|
let connectionRejected = false;
|
|
105
|
+
let lastConnectionError;
|
|
106
106
|
let snapshotInFlight = false;
|
|
107
107
|
|
|
108
108
|
process.on("SIGINT", async () => {
|
|
109
109
|
stopped = true;
|
|
110
|
+
clearConnectionWatchdog?.();
|
|
111
|
+
clearTimeout(reconnectTimer);
|
|
112
|
+
clearInterval(pingTimer);
|
|
110
113
|
await stopProcess(active?.child);
|
|
111
114
|
socket?.close();
|
|
112
115
|
process.exit(0);
|
|
@@ -117,13 +120,22 @@ await connect();
|
|
|
117
120
|
async function connect() {
|
|
118
121
|
console.log(`Connecting ${config.name} to ${config.server_url}`);
|
|
119
122
|
connectionRejected = false;
|
|
123
|
+
lastConnectionError = undefined;
|
|
120
124
|
socket = new WebSocket(config.server_url);
|
|
125
|
+
clearConnectionWatchdog = startConnectionWatchdog(socket, config.server_url, {
|
|
126
|
+
onTimeout: (message) => {
|
|
127
|
+
lastConnectionError = message;
|
|
128
|
+
console.error(message);
|
|
129
|
+
scheduleReconnect();
|
|
130
|
+
},
|
|
131
|
+
});
|
|
121
132
|
socket.addEventListener("open", () =>
|
|
122
133
|
socket.send(JSON.stringify(firstMessage)),
|
|
123
134
|
);
|
|
124
135
|
socket.addEventListener("message", async (event) => {
|
|
125
136
|
const message = JSON.parse(String(event.data));
|
|
126
137
|
if (message.type === "paired") {
|
|
138
|
+
clearConnectionWatchdog?.();
|
|
127
139
|
config = {
|
|
128
140
|
...config,
|
|
129
141
|
connector_id: message.connector.id,
|
|
@@ -134,6 +146,7 @@ async function connect() {
|
|
|
134
146
|
type: "authenticate",
|
|
135
147
|
connector_id: config.connector_id,
|
|
136
148
|
token: config.token,
|
|
149
|
+
capabilities,
|
|
137
150
|
};
|
|
138
151
|
console.log(`Paired. Connector ${config.connector_id} is online.`);
|
|
139
152
|
reconnectDelay = 1_000;
|
|
@@ -142,6 +155,7 @@ async function connect() {
|
|
|
142
155
|
return;
|
|
143
156
|
}
|
|
144
157
|
if (message.type === "authenticated") {
|
|
158
|
+
clearConnectionWatchdog?.();
|
|
145
159
|
console.log("Connected and waiting for EngineerOS runs.");
|
|
146
160
|
reconnectDelay = 1_000;
|
|
147
161
|
startPings();
|
|
@@ -220,6 +234,7 @@ async function connect() {
|
|
|
220
234
|
}
|
|
221
235
|
});
|
|
222
236
|
socket.addEventListener("close", () => {
|
|
237
|
+
clearConnectionWatchdog?.();
|
|
223
238
|
clearInterval(pingTimer);
|
|
224
239
|
if (active?.kind === "prompt") {
|
|
225
240
|
active.cancelled = true;
|
|
@@ -233,13 +248,27 @@ async function connect() {
|
|
|
233
248
|
return;
|
|
234
249
|
}
|
|
235
250
|
if (stopped) return;
|
|
251
|
+
scheduleReconnect();
|
|
252
|
+
});
|
|
253
|
+
socket.addEventListener("error", (event) => {
|
|
254
|
+
lastConnectionError = describeWebSocketError(event);
|
|
236
255
|
console.error(
|
|
237
|
-
`
|
|
256
|
+
`WebSocket connection to ${config.server_url} failed: ${lastConnectionError}`,
|
|
238
257
|
);
|
|
239
|
-
setTimeout(connect, reconnectDelay);
|
|
240
|
-
reconnectDelay = Math.min(30_000, reconnectDelay * 2);
|
|
241
258
|
});
|
|
242
|
-
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function scheduleReconnect() {
|
|
262
|
+
if (stopped || connectionRejected || reconnectTimer) return;
|
|
263
|
+
const detail = lastConnectionError ? ` Last error: ${lastConnectionError}` : "";
|
|
264
|
+
console.error(
|
|
265
|
+
`Connection unavailable.${detail} Retrying in ${Math.round(reconnectDelay / 1_000)}s.`,
|
|
266
|
+
);
|
|
267
|
+
reconnectTimer = setTimeout(() => {
|
|
268
|
+
reconnectTimer = undefined;
|
|
269
|
+
void connect();
|
|
270
|
+
}, reconnectDelay);
|
|
271
|
+
reconnectDelay = Math.min(30_000, reconnectDelay * 2);
|
|
243
272
|
}
|
|
244
273
|
|
|
245
274
|
async function submitWorkspaceSnapshot() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engineeros/connector",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"start": "node ./bin/engineeros-connector.mjs",
|
|
18
18
|
"test": "node --test --test-concurrency=1",
|
|
19
|
-
"type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/runner.mjs"
|
|
19
|
+
"type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/capabilities.mjs && node --check ./src/connection.mjs && node --check ./src/runner.mjs"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
export function advertisedCapabilities(config, codingAgent) {
|
|
4
|
+
return {
|
|
5
|
+
agent_protocols: [codingAgent.protocol],
|
|
6
|
+
coding_agent: true,
|
|
7
|
+
codex_cli: codingAgent.protocol === "codex",
|
|
8
|
+
platform: process.platform,
|
|
9
|
+
workspace_name: path.basename(config.workspace),
|
|
10
|
+
agent_name: codingAgent.name,
|
|
11
|
+
agent_version: codingAgent.version,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function describeWebSocketError(event) {
|
|
2
|
+
return (
|
|
3
|
+
event?.error?.message ||
|
|
4
|
+
event?.message ||
|
|
5
|
+
"The WebSocket connection failed without providing an error detail."
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function startConnectionWatchdog(
|
|
10
|
+
socket,
|
|
11
|
+
url,
|
|
12
|
+
{ timeoutMs = 15_000, onTimeout = console.error } = {},
|
|
13
|
+
) {
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
onTimeout(
|
|
16
|
+
`EngineerOS did not complete the WebSocket handshake at ${url} within ${Math.round(timeoutMs / 1_000)} seconds. Check that the backend is running and the URL is reachable from this machine.`,
|
|
17
|
+
);
|
|
18
|
+
try {
|
|
19
|
+
socket.close();
|
|
20
|
+
} catch {
|
|
21
|
+
// Reconnect scheduling is owned by the caller.
|
|
22
|
+
}
|
|
23
|
+
}, timeoutMs);
|
|
24
|
+
|
|
25
|
+
return () => clearTimeout(timer);
|
|
26
|
+
}
|