@engineeros/connector 0.8.7 → 0.8.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/README.md +20 -7
- package/bin/engineeros-connector.mjs +188 -83
- package/package.json +2 -2
- package/src/acp-client.mjs +348 -84
- package/src/agent-registry.mjs +643 -0
- package/src/config.mjs +37 -0
- package/src/runner.mjs +59 -22
package/README.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# EngineerOS Connector
|
|
2
2
|
|
|
3
|
-
The connector uses an authenticated EngineerOS WebSocket for pairing, workspace identity, run lifecycle, and evidence. Coding work can run through any ACP v1-compatible
|
|
3
|
+
The connector uses an authenticated EngineerOS WebSocket for pairing, workspace identity, run lifecycle, and evidence. Coding work can run through any agent in the official ACP registry or a custom ACP v1-compatible command over stdio. Direct Codex CLI remains available as the compatibility path.
|
|
4
|
+
|
|
5
|
+
## Connect an official ACP agent
|
|
6
|
+
|
|
7
|
+
EngineerOS reads the curated [ACP agent registry](https://agentclientprotocol.com/registry), caches it for 24 hours, and uses the registry's pinned distribution for the current platform. The catalog includes Codex, Claude, Gemini, GitHub Copilot, Goose, OpenCode, Qwen Code, Cursor, and other compatible agents as they are published. List the current catalog, prepare the selected agent, then pair the workspace:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx --yes @engineeros/connector@latest agents
|
|
11
|
+
npx --yes @engineeros/connector@latest agent gemini install
|
|
12
|
+
npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engineeros.example --workspace . --onboard --agent gemini
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Registry `npx` and `uvx` packages are prepared with their native package runner. Registry binaries are downloaded into `~/.engineeros/agents`, checked against the published SHA-256 digest when present, and launched from that managed location. The connector checks the selected distribution before pairing. Agent processes stay alive while the connector is active, project conversations reuse their ACP sessions, and response chunks reach Copilot as the agent produces them.
|
|
4
16
|
|
|
5
17
|
## Pair an ACP coding agent
|
|
6
18
|
|
|
@@ -10,7 +22,7 @@ Run the command from the repository the agent should work in:
|
|
|
10
22
|
npx --yes @engineeros/connector@latest pair PAIRING-CODE --url http://localhost:8000 --workspace . --onboard --agent-name "My ACP agent" --agent-command my-agent --agent-args '["--acp"]'
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
`--agent-command` must start an ACP v1 agent over NDJSON stdio. `--agent-args` is a JSON array so arguments are passed without invoking a shell. Omit
|
|
25
|
+
`--agent-command` must start an ACP v1 agent over NDJSON stdio. `--agent-args` is a JSON array so arguments are passed without invoking a shell. Omit `--agent` and the custom command options to use the installed Codex CLI directly.
|
|
14
26
|
|
|
15
27
|
Connect a local Codex CLI workspace to EngineerOS through an outbound WebSocket.
|
|
16
28
|
|
|
@@ -29,9 +41,9 @@ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engi
|
|
|
29
41
|
|
|
30
42
|
The connector uploads a bounded ZIP snapshot for a safe file inventory, then stays online for deep assessments, rescans, and Goal Runs. Inventory never executes repository code. It excludes known secrets, dependency directories, build output, compiled binaries, files larger than 5 MB, agent-tool caches, Git metadata, and connector state before upload.
|
|
31
43
|
|
|
32
|
-
From **Project steering -> Workspace**,
|
|
44
|
+
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. EngineerOS stores the source-cited findings in System State and promotes the highest-return corrective action in Steering. Assessment cannot modify the workspace.
|
|
33
45
|
|
|
34
|
-
After onboarding, every project prompt is routed to this connection. Copilot, shaping, planning, architecture, and experience generation use the
|
|
46
|
+
After onboarding, every project prompt is routed to this connection. Copilot, shaping, planning, architecture, and experience generation use the connected agent subscription and workspace context. Interactive prompts run independently from assessments and Goal scheduling. Prompt runs are read-only; only an explicitly registered Goal Run receives workspace-write access. If the connector is offline, EngineerOS asks the user to reconnect instead of silently switching models.
|
|
35
47
|
|
|
36
48
|
Project prompts use resumable, purpose-specific coding-agent sessions. The connector keeps the external session identifiers in its local configuration, so Copilot and artifact conversations survive connector restarts. Changing the purpose, model, or reasoning effort starts a separate session. Goal implementation and verification remain isolated runs.
|
|
37
49
|
|
|
@@ -40,18 +52,19 @@ An empty or document-only folder establishes a greenfield baseline. A code-beari
|
|
|
40
52
|
## Reconnect
|
|
41
53
|
|
|
42
54
|
```sh
|
|
43
|
-
npx @engineeros/connector start --workspace .
|
|
55
|
+
npx --yes @engineeros/connector@latest start --workspace .
|
|
44
56
|
```
|
|
45
57
|
|
|
46
58
|
Credentials are stored per workspace under `~/.engineeros/connectors` with owner-only permissions where supported.
|
|
59
|
+
If a new pairing command is accidentally run from the same folder against the same EngineerOS server, the connector reuses this saved identity instead of creating another baseline assessment.
|
|
47
60
|
|
|
48
61
|
## Run Goals
|
|
49
62
|
|
|
50
|
-
Keep the connector online to receive Goals assigned from EngineerOS. Each Goal runs in an isolated worktree below `~/.engineeros/runs`. Cancellation stops
|
|
63
|
+
Keep the connector online to receive Goals assigned from EngineerOS. Each Goal runs in an isolated worktree below `~/.engineeros/runs`. Cancellation stops the agent. The connector returns changed paths, a bounded diff, and the exact repository ZIP; a human still performs independent attestation.
|
|
51
64
|
|
|
52
65
|
During the writable implementation phase, Codex receives an authenticated EngineerOS MCP server automatically. It can list, read, create, update, reclassify, soft-delete, and materialize project artifacts into canonical Product records through the same repository boundary used by Copilot. The backend accepts those calls only while the assigned Goal is running. Read-only project prompts and the independent verification phase do not receive mutation tools.
|
|
53
66
|
|
|
54
|
-
Requirements: Node.js 22 or newer, Git, and an authenticated
|
|
67
|
+
Requirements: Node.js 22 or newer, Git, and an authenticated agent. `npx` distributions use npm, `uvx` distributions require uv, and binary archives require `tar` (`unzip` for ZIP files on Linux). Registry agents report their own authentication prerequisites when they start.
|
|
55
68
|
|
|
56
69
|
## Execution profiles
|
|
57
70
|
|
|
@@ -3,7 +3,9 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
assessmentResultUrl,
|
|
6
|
+
connectorResumeCredentials,
|
|
6
7
|
loadConfig,
|
|
8
|
+
mergePairedConfig,
|
|
7
9
|
resultUrl,
|
|
8
10
|
saveConfig,
|
|
9
11
|
socketUrl,
|
|
@@ -16,11 +18,17 @@ import {
|
|
|
16
18
|
executeConnectedPrompt,
|
|
17
19
|
executeWorkspaceAssessment,
|
|
18
20
|
inspectCodingAgent,
|
|
19
|
-
|
|
20
|
-
promptQueueMessage,
|
|
21
|
+
promptStreamEvent,
|
|
21
22
|
stopProcess,
|
|
22
23
|
workspaceSnapshot,
|
|
23
24
|
} from "../src/runner.mjs";
|
|
25
|
+
import { disposeAcpRuntimes } from "../src/acp-client.mjs";
|
|
26
|
+
import {
|
|
27
|
+
installRegisteredAgent,
|
|
28
|
+
inspectRegisteredAgent,
|
|
29
|
+
registeredAgentConfig,
|
|
30
|
+
registeredAgents,
|
|
31
|
+
} from "../src/agent-registry.mjs";
|
|
24
32
|
import {
|
|
25
33
|
describeWebSocketError,
|
|
26
34
|
startConnectionWatchdog,
|
|
@@ -30,13 +38,60 @@ import { parseConnectorArgs } from "../src/cli-args.mjs";
|
|
|
30
38
|
import { runMcpServer } from "../src/mcp-server.mjs";
|
|
31
39
|
import packageJson from "../package.json" with { type: "json" };
|
|
32
40
|
|
|
33
|
-
const { command, positional, flags } = parseConnectorArgs(
|
|
41
|
+
const { command, positional, flags } = parseConnectorArgs(
|
|
42
|
+
process.argv.slice(2),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (command === "agents") {
|
|
46
|
+
let agents;
|
|
47
|
+
try {
|
|
48
|
+
agents = await registeredAgents();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
52
|
+
const statuses = await Promise.all(
|
|
53
|
+
agents.map(async (agent) => {
|
|
54
|
+
try {
|
|
55
|
+
await inspectRegisteredAgent(agent.id);
|
|
56
|
+
return `${agent.id}: ready (${agent.name} ${agent.version}, ${agent.distribution_type})`;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return `${agent.id}: setup needed (${agent.name} ${agent.version}, ${agent.distribution_type}) - ${detail}`;
|
|
60
|
+
}
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
for (const status of statuses) {
|
|
64
|
+
console.log(status);
|
|
65
|
+
}
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (command === "agent") {
|
|
70
|
+
const agentId = positional[0];
|
|
71
|
+
const action = positional[1] || "check";
|
|
72
|
+
if (!agentId || !["check", "install"].includes(action)) {
|
|
73
|
+
fail("Usage: engineeros-connector agent AGENT_ID [check|install]");
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const installed =
|
|
77
|
+
action === "install"
|
|
78
|
+
? await installRegisteredAgent(agentId)
|
|
79
|
+
: await inspectRegisteredAgent(agentId);
|
|
80
|
+
console.log(
|
|
81
|
+
`${installed.name} is ready (${installed.version}, ${installed.distribution}).`,
|
|
82
|
+
);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
85
|
+
}
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
34
88
|
|
|
35
89
|
if (command === "mcp") {
|
|
36
90
|
const config = await loadConfig(flags.workspace || process.cwd());
|
|
37
91
|
if (!config) fail("This workspace is not paired with EngineerOS.");
|
|
38
92
|
const runId = flags["run-id"] || positional[0];
|
|
39
|
-
if (!runId)
|
|
93
|
+
if (!runId)
|
|
94
|
+
fail("Usage: engineeros-connector mcp --run-id RUN_ID [--workspace PATH]");
|
|
40
95
|
await runMcpServer({
|
|
41
96
|
config,
|
|
42
97
|
runId,
|
|
@@ -59,14 +114,27 @@ if (command === "status") {
|
|
|
59
114
|
|
|
60
115
|
let config;
|
|
61
116
|
let firstMessage;
|
|
117
|
+
let existingConfig;
|
|
62
118
|
if (command === "pair") {
|
|
63
119
|
const pairingCode = positional[0];
|
|
64
120
|
if (!pairingCode)
|
|
65
121
|
fail(
|
|
66
|
-
"Usage: engineeros-connector pair CODE --url URL [--name NAME] [--workspace PATH] [--skip-git-repo-check]",
|
|
122
|
+
"Usage: engineeros-connector pair CODE --url URL [--agent AGENT_ID] [--name NAME] [--workspace PATH] [--skip-git-repo-check]",
|
|
67
123
|
);
|
|
68
124
|
const url = flags.url;
|
|
69
125
|
if (!url) fail("Pairing requires --url with the EngineerOS backend address.");
|
|
126
|
+
if (flags.agent && flags["agent-command"]) {
|
|
127
|
+
fail("Use either --agent or --agent-command, not both.");
|
|
128
|
+
}
|
|
129
|
+
let agentConfig = {};
|
|
130
|
+
try {
|
|
131
|
+
if (flags.agent) {
|
|
132
|
+
await inspectRegisteredAgent(flags.agent);
|
|
133
|
+
agentConfig = await registeredAgentConfig(flags.agent);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
137
|
+
}
|
|
70
138
|
config = {
|
|
71
139
|
server_url: socketUrl(url),
|
|
72
140
|
workspace: path.resolve(flags.workspace || process.cwd()),
|
|
@@ -76,16 +144,19 @@ if (command === "pair") {
|
|
|
76
144
|
agent_command: flags["agent-command"] || null,
|
|
77
145
|
agent_args: parseAgentArgs(flags["agent-args"]),
|
|
78
146
|
agent_name: flags["agent-name"] || null,
|
|
147
|
+
...agentConfig,
|
|
79
148
|
skip_git_repo_check: flags["skip-git-repo-check"] === true,
|
|
80
149
|
name:
|
|
81
150
|
flags.name ||
|
|
82
151
|
`${os.hostname()} - ${path.basename(path.resolve(flags.workspace || process.cwd()))}`,
|
|
83
152
|
};
|
|
153
|
+
existingConfig = await loadConfig(config.workspace);
|
|
84
154
|
firstMessage = {
|
|
85
155
|
type: "pair",
|
|
86
156
|
pairing_code: pairingCode,
|
|
87
157
|
name: config.name,
|
|
88
158
|
capabilities: {},
|
|
159
|
+
...connectorResumeCredentials(existingConfig, config.server_url),
|
|
89
160
|
};
|
|
90
161
|
} else if (command === "start") {
|
|
91
162
|
config = await loadConfig(flags.workspace || process.cwd());
|
|
@@ -99,7 +170,9 @@ if (command === "pair") {
|
|
|
99
170
|
token: config.token,
|
|
100
171
|
};
|
|
101
172
|
} else {
|
|
102
|
-
fail(
|
|
173
|
+
fail(
|
|
174
|
+
"Use `engineeros-connector pair`, `start`, `status`, `agents`, `agent`, or `mcp`.",
|
|
175
|
+
);
|
|
103
176
|
}
|
|
104
177
|
|
|
105
178
|
let codingAgent;
|
|
@@ -108,7 +181,9 @@ try {
|
|
|
108
181
|
} catch (error) {
|
|
109
182
|
fail(error instanceof Error ? error.message : String(error));
|
|
110
183
|
}
|
|
111
|
-
console.log(
|
|
184
|
+
console.log(
|
|
185
|
+
`Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`,
|
|
186
|
+
);
|
|
112
187
|
const capabilities = advertisedCapabilities(config, codingAgent);
|
|
113
188
|
firstMessage.capabilities = capabilities;
|
|
114
189
|
|
|
@@ -116,7 +191,7 @@ let stopped = false;
|
|
|
116
191
|
let active = null;
|
|
117
192
|
const available = [];
|
|
118
193
|
const assessments = [];
|
|
119
|
-
const
|
|
194
|
+
const activePrompts = new Map();
|
|
120
195
|
let socket;
|
|
121
196
|
let pingTimer;
|
|
122
197
|
let reconnectTimer;
|
|
@@ -132,6 +207,8 @@ process.on("SIGINT", async () => {
|
|
|
132
207
|
clearTimeout(reconnectTimer);
|
|
133
208
|
clearInterval(pingTimer);
|
|
134
209
|
await stopProcess(active?.child);
|
|
210
|
+
await Promise.all([...activePrompts.values()].map(cancelPrompt));
|
|
211
|
+
await disposeAcpRuntimes();
|
|
135
212
|
socket?.close();
|
|
136
213
|
process.exit(0);
|
|
137
214
|
});
|
|
@@ -157,11 +234,14 @@ async function connect() {
|
|
|
157
234
|
const message = JSON.parse(String(event.data));
|
|
158
235
|
if (message.type === "paired") {
|
|
159
236
|
clearConnectionWatchdog?.();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
237
|
+
const reusedConnector =
|
|
238
|
+
existingConfig?.connector_id === message.connector.id;
|
|
239
|
+
config = mergePairedConfig(
|
|
240
|
+
config,
|
|
241
|
+
existingConfig,
|
|
242
|
+
message.connector.id,
|
|
243
|
+
message.token,
|
|
244
|
+
);
|
|
165
245
|
await saveConfig(config);
|
|
166
246
|
firstMessage = {
|
|
167
247
|
type: "authenticate",
|
|
@@ -169,7 +249,11 @@ async function connect() {
|
|
|
169
249
|
token: config.token,
|
|
170
250
|
capabilities,
|
|
171
251
|
};
|
|
172
|
-
console.log(
|
|
252
|
+
console.log(
|
|
253
|
+
reusedConnector
|
|
254
|
+
? `Reconnected existing workspace connector ${config.connector_id}; assessment history is preserved.`
|
|
255
|
+
: `Paired. Connector ${config.connector_id} is online.`,
|
|
256
|
+
);
|
|
173
257
|
reconnectDelay = 1_000;
|
|
174
258
|
startPings();
|
|
175
259
|
if (config.onboarding_pending) void submitWorkspaceSnapshot();
|
|
@@ -200,29 +284,12 @@ async function connect() {
|
|
|
200
284
|
return;
|
|
201
285
|
}
|
|
202
286
|
if (message.type === "prompt.execute") {
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
active?.runId !== message.prompt_id &&
|
|
206
|
-
!prompts.some((candidate) => candidate.prompt_id === message.prompt_id)
|
|
207
|
-
) {
|
|
208
|
-
prompts.push(message);
|
|
209
|
-
added = true;
|
|
210
|
-
}
|
|
211
|
-
if (added && active) {
|
|
212
|
-
sendPromptProgress(message.prompt_id, promptQueueMessage(active.kind));
|
|
213
|
-
}
|
|
214
|
-
pump();
|
|
287
|
+
if (!activePrompts.has(message.prompt_id)) void executePrompt(message);
|
|
215
288
|
return;
|
|
216
289
|
}
|
|
217
290
|
if (message.type === "prompt.cancel") {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
if (queued >= 0) prompts.splice(queued, 1);
|
|
222
|
-
if (active?.kind === "prompt" && active.runId === message.prompt_id) {
|
|
223
|
-
active.cancelled = true;
|
|
224
|
-
await stopProcess(active.child);
|
|
225
|
-
}
|
|
291
|
+
const promptState = activePrompts.get(message.prompt_id);
|
|
292
|
+
if (promptState) await cancelPrompt(promptState);
|
|
226
293
|
return;
|
|
227
294
|
}
|
|
228
295
|
if (message.type === "run.available") {
|
|
@@ -259,11 +326,12 @@ async function connect() {
|
|
|
259
326
|
console.error(`EngineerOS: ${message.message}`);
|
|
260
327
|
}
|
|
261
328
|
});
|
|
262
|
-
socket.addEventListener("close", () => {
|
|
329
|
+
socket.addEventListener("close", (event) => {
|
|
263
330
|
clearConnectionWatchdog?.();
|
|
264
331
|
clearInterval(pingTimer);
|
|
265
|
-
|
|
266
|
-
|
|
332
|
+
for (const promptState of activePrompts.values())
|
|
333
|
+
void cancelPrompt(promptState);
|
|
334
|
+
if (event.code === 4001 && active?.kind === "assessment") {
|
|
267
335
|
void stopProcess(active.child);
|
|
268
336
|
}
|
|
269
337
|
if (connectionRejected) {
|
|
@@ -286,7 +354,9 @@ async function connect() {
|
|
|
286
354
|
|
|
287
355
|
function scheduleReconnect() {
|
|
288
356
|
if (stopped || connectionRejected || reconnectTimer) return;
|
|
289
|
-
const detail = lastConnectionError
|
|
357
|
+
const detail = lastConnectionError
|
|
358
|
+
? ` Last error: ${lastConnectionError}`
|
|
359
|
+
: "";
|
|
290
360
|
console.error(
|
|
291
361
|
`Connection unavailable.${detail} Retrying in ${Math.round(reconnectDelay / 1_000)}s.`,
|
|
292
362
|
);
|
|
@@ -351,30 +421,21 @@ function startPings() {
|
|
|
351
421
|
}, 10_000);
|
|
352
422
|
}
|
|
353
423
|
|
|
354
|
-
function
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
424
|
+
function sendPromptEvent(promptId, event) {
|
|
425
|
+
if (!event || socket.readyState !== WebSocket.OPEN) return;
|
|
426
|
+
const payload = {
|
|
427
|
+
type: "prompt.event",
|
|
428
|
+
prompt_id: promptId,
|
|
429
|
+
kind: event.kind,
|
|
430
|
+
};
|
|
431
|
+
if (event.message) payload.message = String(event.message).slice(0, 500);
|
|
432
|
+
if (event.delta) payload.delta = String(event.delta).slice(0, 50_000);
|
|
433
|
+
if (event.status) payload.status = String(event.status).slice(0, 100);
|
|
434
|
+
socket.send(JSON.stringify(payload));
|
|
363
435
|
}
|
|
364
436
|
|
|
365
437
|
function pump() {
|
|
366
438
|
if (active || socket.readyState !== WebSocket.OPEN) return;
|
|
367
|
-
const prompt = prompts.shift();
|
|
368
|
-
if (prompt) {
|
|
369
|
-
active = {
|
|
370
|
-
kind: "prompt",
|
|
371
|
-
runId: prompt.prompt_id,
|
|
372
|
-
child: null,
|
|
373
|
-
cancelled: false,
|
|
374
|
-
};
|
|
375
|
-
void executePrompt(prompt);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
439
|
const assessment = assessments.shift();
|
|
379
440
|
if (assessment) {
|
|
380
441
|
active = {
|
|
@@ -403,36 +464,56 @@ function pump() {
|
|
|
403
464
|
|
|
404
465
|
async function executePrompt(assignment) {
|
|
405
466
|
const promptId = assignment.prompt_id;
|
|
406
|
-
|
|
407
|
-
|
|
467
|
+
const promptState = {
|
|
468
|
+
runId: promptId,
|
|
469
|
+
child: null,
|
|
470
|
+
controller: null,
|
|
471
|
+
cancelled: false,
|
|
472
|
+
};
|
|
473
|
+
activePrompts.set(promptId, promptState);
|
|
474
|
+
let lastEvent;
|
|
475
|
+
const reportEvent = (event) => {
|
|
476
|
+
const serialized = event ? JSON.stringify(event) : null;
|
|
408
477
|
if (
|
|
409
|
-
!
|
|
410
|
-
|
|
478
|
+
!serialized ||
|
|
479
|
+
serialized === lastEvent ||
|
|
411
480
|
socket.readyState !== WebSocket.OPEN ||
|
|
412
|
-
|
|
481
|
+
activePrompts.get(promptId) !== promptState
|
|
413
482
|
) {
|
|
414
483
|
return;
|
|
415
484
|
}
|
|
416
|
-
|
|
417
|
-
|
|
485
|
+
lastEvent = serialized;
|
|
486
|
+
sendPromptEvent(promptId, event);
|
|
418
487
|
};
|
|
419
|
-
|
|
488
|
+
sendPromptEvent(promptId, {
|
|
489
|
+
kind: "status",
|
|
490
|
+
message: "Agent started this request",
|
|
491
|
+
});
|
|
420
492
|
console.log(
|
|
421
493
|
`Answering ${assignment.purpose || "project"} prompt with ${codingAgent.name}.`,
|
|
422
494
|
);
|
|
423
495
|
try {
|
|
424
496
|
const result = await executeConnectedPrompt(assignment, config, {
|
|
425
497
|
onProcess: (child) => {
|
|
426
|
-
if (
|
|
498
|
+
if (activePrompts.get(promptId) === promptState)
|
|
499
|
+
promptState.child = child;
|
|
427
500
|
},
|
|
428
|
-
|
|
501
|
+
onController: (controller) => {
|
|
502
|
+
if (activePrompts.get(promptId) === promptState) {
|
|
503
|
+
promptState.controller = controller;
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
onEvent: (event) => reportEvent(promptStreamEvent(event)),
|
|
429
507
|
});
|
|
430
508
|
config = {
|
|
431
509
|
...config,
|
|
432
|
-
sessions: {
|
|
510
|
+
sessions: {
|
|
511
|
+
...(config.sessions || {}),
|
|
512
|
+
[result.sessionKey]: result.sessionId,
|
|
513
|
+
},
|
|
433
514
|
};
|
|
434
515
|
await saveConfig(config);
|
|
435
|
-
if (
|
|
516
|
+
if (promptState.cancelled || socket.readyState !== WebSocket.OPEN) return;
|
|
436
517
|
socket.send(
|
|
437
518
|
JSON.stringify({
|
|
438
519
|
type: "prompt.completed",
|
|
@@ -443,7 +524,7 @@ async function executePrompt(assignment) {
|
|
|
443
524
|
}),
|
|
444
525
|
);
|
|
445
526
|
} catch (error) {
|
|
446
|
-
if (!
|
|
527
|
+
if (!promptState.cancelled && socket.readyState === WebSocket.OPEN) {
|
|
447
528
|
socket.send(
|
|
448
529
|
JSON.stringify({
|
|
449
530
|
type: "prompt.failed",
|
|
@@ -452,18 +533,29 @@ async function executePrompt(assignment) {
|
|
|
452
533
|
}),
|
|
453
534
|
);
|
|
454
535
|
}
|
|
455
|
-
if (!
|
|
536
|
+
if (!promptState.cancelled) {
|
|
456
537
|
console.error(error instanceof Error ? error.message : String(error));
|
|
457
538
|
}
|
|
458
539
|
} finally {
|
|
459
|
-
|
|
460
|
-
|
|
540
|
+
if (activePrompts.get(promptId) === promptState)
|
|
541
|
+
activePrompts.delete(promptId);
|
|
461
542
|
}
|
|
462
543
|
}
|
|
463
544
|
|
|
545
|
+
async function cancelPrompt(promptState) {
|
|
546
|
+
promptState.cancelled = true;
|
|
547
|
+
if (typeof promptState.controller?.cancel === "function") {
|
|
548
|
+
await promptState.controller.cancel();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
await stopProcess(promptState.child);
|
|
552
|
+
}
|
|
553
|
+
|
|
464
554
|
async function executeAssessment(assignment) {
|
|
465
555
|
const assessmentId = assignment.assessment_id;
|
|
466
|
-
console.log(
|
|
556
|
+
console.log(
|
|
557
|
+
`Assessing workspace with ${codingAgent.name} (${assessmentId}).`,
|
|
558
|
+
);
|
|
467
559
|
let progress = 10;
|
|
468
560
|
const reportedMilestones = new Set();
|
|
469
561
|
const reportProgress = (message, { milestone = true } = {}) => {
|
|
@@ -515,7 +607,9 @@ async function executeAssessment(assignment) {
|
|
|
515
607
|
`EngineerOS rejected the assessment (${response.status}): ${await response.text()}`,
|
|
516
608
|
);
|
|
517
609
|
}
|
|
518
|
-
console.log(
|
|
610
|
+
console.log(
|
|
611
|
+
`${codingAgent.name} workspace assessment is current in EngineerOS.`,
|
|
612
|
+
);
|
|
519
613
|
} catch (error) {
|
|
520
614
|
if (socket.readyState === WebSocket.OPEN) {
|
|
521
615
|
socket.send(
|
|
@@ -546,7 +640,7 @@ async function execute(assignment) {
|
|
|
546
640
|
type: "run.progress",
|
|
547
641
|
run_id: runId,
|
|
548
642
|
progress_percent: progress,
|
|
549
|
-
message:
|
|
643
|
+
message: "Agent is working",
|
|
550
644
|
}),
|
|
551
645
|
);
|
|
552
646
|
}
|
|
@@ -558,7 +652,7 @@ async function execute(assignment) {
|
|
|
558
652
|
},
|
|
559
653
|
onEvent: (event) => {
|
|
560
654
|
const message =
|
|
561
|
-
event.message || event.item?.text || event.type ||
|
|
655
|
+
event.message || event.item?.text || event.type || "Agent is working";
|
|
562
656
|
if (socket.readyState === WebSocket.OPEN) {
|
|
563
657
|
socket.send(
|
|
564
658
|
JSON.stringify({
|
|
@@ -593,10 +687,16 @@ async function execute(assignment) {
|
|
|
593
687
|
result.head_revision,
|
|
594
688
|
);
|
|
595
689
|
if (integration.applied) {
|
|
596
|
-
console.log(
|
|
690
|
+
console.log(
|
|
691
|
+
`Run accepted and applied to the connected repository at ${integration.revision}.`,
|
|
692
|
+
);
|
|
597
693
|
} else {
|
|
598
|
-
console.warn(
|
|
599
|
-
|
|
694
|
+
console.warn(
|
|
695
|
+
`Run accepted but not applied locally. ${integration.reason}`,
|
|
696
|
+
);
|
|
697
|
+
console.warn(
|
|
698
|
+
`The verified run workspace remains at ${result.run_workspace}.`,
|
|
699
|
+
);
|
|
600
700
|
}
|
|
601
701
|
} catch (error) {
|
|
602
702
|
if (!active?.cancelled && socket.readyState === WebSocket.OPEN) {
|
|
@@ -621,12 +721,17 @@ function parseAgentArgs(value) {
|
|
|
621
721
|
if (!value) return [];
|
|
622
722
|
try {
|
|
623
723
|
const parsed = JSON.parse(value);
|
|
624
|
-
if (
|
|
724
|
+
if (
|
|
725
|
+
!Array.isArray(parsed) ||
|
|
726
|
+
parsed.some((item) => typeof item !== "string")
|
|
727
|
+
) {
|
|
625
728
|
throw new Error();
|
|
626
729
|
}
|
|
627
730
|
return parsed;
|
|
628
731
|
} catch {
|
|
629
|
-
fail(
|
|
732
|
+
fail(
|
|
733
|
+
'--agent-args must be a JSON array, for example: --agent-args "[\\"acp\\"]"',
|
|
734
|
+
);
|
|
630
735
|
}
|
|
631
736
|
}
|
|
632
737
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engineeros/connector",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9",
|
|
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/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
|
|
19
|
+
"type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/acp-client.mjs && node --check ./src/agent-registry.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/config.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22"
|