@engineeros/connector 0.10.3 → 0.11.0
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 +790 -779
- package/package.json +39 -39
- package/src/codex-app-server.mjs +139 -139
- package/src/runner.mjs +1626 -1355
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@engineeros/connector",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
|
|
5
|
-
"private": false,
|
|
6
|
-
"type": "module",
|
|
7
|
-
"license": "UNLICENSED",
|
|
8
|
-
"files": [
|
|
9
|
-
"bin",
|
|
10
|
-
"src",
|
|
11
|
-
"README.md"
|
|
12
|
-
],
|
|
13
|
-
"bin": {
|
|
14
|
-
"engineeros-connector": "bin/engineeros-connector.mjs"
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"start": "node ./bin/engineeros-connector.mjs",
|
|
18
|
-
"prepublishOnly": "node ./scripts/release.mjs check",
|
|
19
|
-
"release:patch": "node ./scripts/release.mjs patch",
|
|
20
|
-
"test": "node --test --test-concurrency=1",
|
|
1
|
+
{
|
|
2
|
+
"name": "@engineeros/connector",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"src",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"engineeros-connector": "bin/engineeros-connector.mjs"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node ./bin/engineeros-connector.mjs",
|
|
18
|
+
"prepublishOnly": "node ./scripts/release.mjs check",
|
|
19
|
+
"release:patch": "node ./scripts/release.mjs patch",
|
|
20
|
+
"test": "node --test --test-concurrency=1",
|
|
21
21
|
"type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./scripts/release.mjs && node --check ./src/acp-client.mjs && node --check ./src/agent-harness.mjs && node --check ./src/agent-registry.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/codex-app-server.mjs && node --check ./src/config.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
|
|
22
|
-
},
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=22"
|
|
25
|
-
},
|
|
26
|
-
"repository": {
|
|
27
|
-
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/vinpuli/engineerosv2.git",
|
|
29
|
-
"directory": "packages/engineeros-connector"
|
|
30
|
-
},
|
|
31
|
-
"bugs": {
|
|
32
|
-
"url": "https://github.com/vinpuli/engineerosv2/issues"
|
|
33
|
-
},
|
|
34
|
-
"publishConfig": {
|
|
35
|
-
"access": "public"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@agentclientprotocol/sdk": "1.3.0"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/vinpuli/engineerosv2.git",
|
|
29
|
+
"directory": "packages/engineeros-connector"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/vinpuli/engineerosv2/issues"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@agentclientprotocol/sdk": "1.3.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/codex-app-server.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import readline from "node:readline";
|
|
3
3
|
|
|
4
4
|
export async function inspectCodexExecutionProfiles({
|
|
@@ -51,7 +51,7 @@ export async function inspectCodexExecutionProfiles({
|
|
|
51
51
|
}, timeoutMs);
|
|
52
52
|
try {
|
|
53
53
|
await request("initialize", {
|
|
54
|
-
clientInfo: { name: "engineeros-connector", title: "EngineerOS Connector", version: "0.
|
|
54
|
+
clientInfo: { name: "engineeros-connector", title: "EngineerOS Connector", version: "0.11.0" },
|
|
55
55
|
});
|
|
56
56
|
child.stdin.write(`${JSON.stringify({ method: "initialized" })}\n`);
|
|
57
57
|
const models = [];
|
|
@@ -79,140 +79,140 @@ export async function inspectCodexExecutionProfiles({
|
|
|
79
79
|
child.kill();
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
export function launchCodexAppServer({
|
|
84
|
-
workspace,
|
|
85
|
-
prompt,
|
|
86
|
-
sandbox,
|
|
87
|
-
profile = {},
|
|
88
|
-
previousSessionId,
|
|
89
|
-
callbacks = {},
|
|
90
|
-
command = process.env.CODEX_BIN || (process.platform === "win32" ? "codex.cmd" : "codex"),
|
|
91
|
-
spawnProcess = spawn,
|
|
92
|
-
}) {
|
|
93
|
-
const child = spawnProcess(command, ["app-server", "--stdio"], {
|
|
94
|
-
cwd: workspace,
|
|
95
|
-
env: process.env,
|
|
96
|
-
shell: process.platform === "win32",
|
|
97
|
-
windowsHide: true,
|
|
98
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
99
|
-
});
|
|
100
|
-
const pending = new Map();
|
|
101
|
-
let requestId = 0;
|
|
102
|
-
let threadId = previousSessionId || "";
|
|
103
|
-
let turnId = "";
|
|
104
|
-
let finalMessage = "";
|
|
105
|
-
let stderr = "";
|
|
106
|
-
let settled = false;
|
|
107
|
-
|
|
108
|
-
const request = (method, params) =>
|
|
109
|
-
new Promise((resolve, reject) => {
|
|
110
|
-
const id = ++requestId;
|
|
111
|
-
pending.set(id, { resolve, reject });
|
|
112
|
-
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const completed = new Promise((resolve, reject) => {
|
|
116
|
-
const finish = (error) => {
|
|
117
|
-
if (settled) return;
|
|
118
|
-
settled = true;
|
|
119
|
-
for (const waiter of pending.values()) waiter.reject(error || new Error("Codex app-server stopped."));
|
|
120
|
-
pending.clear();
|
|
121
|
-
if (error) reject(error);
|
|
122
|
-
else resolve({ finalMessage, output: stderr.slice(-20_000), sessionId: threadId });
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
child.once("error", finish);
|
|
126
|
-
child.once("close", (code) => {
|
|
127
|
-
if (!settled) finish(new Error(`Codex app-server exited before completing the turn (code ${code ?? 1}). ${stderr}`));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const lines = readline.createInterface({ input: child.stdout });
|
|
131
|
-
lines.on("line", (line) => {
|
|
132
|
-
let message;
|
|
133
|
-
try {
|
|
134
|
-
message = JSON.parse(line);
|
|
135
|
-
} catch {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (message.id !== undefined) {
|
|
139
|
-
const waiter = pending.get(message.id);
|
|
140
|
-
if (!waiter) return;
|
|
141
|
-
pending.delete(message.id);
|
|
142
|
-
if (message.error) waiter.reject(new Error(message.error.message || "Codex app-server request failed."));
|
|
143
|
-
else waiter.resolve(message.result);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const params = message.params || {};
|
|
147
|
-
if (message.method === "item/agentMessage/delta" && typeof params.delta === "string") {
|
|
148
|
-
finalMessage += params.delta;
|
|
149
|
-
callbacks.onEvent?.({ type: "codex.agent_message_delta", delta: params.delta });
|
|
150
|
-
} else if (message.method === "item/started" || message.method === "item/completed") {
|
|
151
|
-
callbacks.onEvent?.({ type: `codex.${message.method}`, item: params.item });
|
|
152
|
-
} else if (message.method === "thread/tokenUsage/updated") {
|
|
153
|
-
callbacks.onEvent?.({ type: "codex.usage", update: params });
|
|
154
|
-
} else if (message.method === "turn/completed" && params.threadId === threadId) {
|
|
155
|
-
const status = params.turn?.status;
|
|
156
|
-
if (status === "failed") finish(new Error(params.turn?.error?.message || "Codex turn failed."));
|
|
157
|
-
else if (!finalMessage.trim()) finish(new Error("Codex completed without returning a response."));
|
|
158
|
-
else finish();
|
|
159
|
-
child.kill();
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
child.stderr.setEncoding("utf8");
|
|
164
|
-
child.stderr.on("data", (chunk) => {
|
|
165
|
-
stderr = `${stderr}${chunk}`.slice(-20_000);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
void (async () => {
|
|
169
|
-
try {
|
|
170
|
-
await request("initialize", {
|
|
171
|
-
clientInfo: { name: "engineeros-connector", title: "EngineerOS Connector", version: "0.
|
|
172
|
-
});
|
|
173
|
-
child.stdin.write(`${JSON.stringify({ method: "initialized" })}\n`);
|
|
174
|
-
const threadResult = previousSessionId
|
|
175
|
-
? await request("thread/resume", {
|
|
176
|
-
threadId: previousSessionId,
|
|
177
|
-
cwd: workspace,
|
|
178
|
-
sandbox,
|
|
179
|
-
approvalPolicy: "never",
|
|
180
|
-
model: profile.model || null,
|
|
181
|
-
})
|
|
182
|
-
: await request("thread/start", {
|
|
183
|
-
cwd: workspace,
|
|
184
|
-
sandbox,
|
|
185
|
-
approvalPolicy: "never",
|
|
186
|
-
model: profile.model || null,
|
|
187
|
-
ephemeral: false,
|
|
188
|
-
});
|
|
189
|
-
threadId = threadResult.thread.id;
|
|
190
|
-
callbacks.onEvent?.({ type: "thread.started", thread_id: threadId });
|
|
191
|
-
const turnResult = await request("turn/start", {
|
|
192
|
-
threadId,
|
|
193
|
-
input: [{ type: "text", text: prompt }],
|
|
194
|
-
effort: profile.reasoning_effort || null,
|
|
195
|
-
});
|
|
196
|
-
turnId = turnResult.turn.id;
|
|
197
|
-
} catch (error) {
|
|
198
|
-
child.kill();
|
|
199
|
-
finish(error instanceof Error ? error : new Error(String(error)));
|
|
200
|
-
}
|
|
201
|
-
})();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
child,
|
|
206
|
-
completed,
|
|
207
|
-
cancel: async () => {
|
|
208
|
-
if (threadId && turnId && !settled) {
|
|
209
|
-
try {
|
|
210
|
-
await request("turn/interrupt", { threadId, turnId });
|
|
211
|
-
} catch {
|
|
212
|
-
// Process termination below is the final cancellation boundary.
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
child.kill();
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
}
|
|
82
|
+
|
|
83
|
+
export function launchCodexAppServer({
|
|
84
|
+
workspace,
|
|
85
|
+
prompt,
|
|
86
|
+
sandbox,
|
|
87
|
+
profile = {},
|
|
88
|
+
previousSessionId,
|
|
89
|
+
callbacks = {},
|
|
90
|
+
command = process.env.CODEX_BIN || (process.platform === "win32" ? "codex.cmd" : "codex"),
|
|
91
|
+
spawnProcess = spawn,
|
|
92
|
+
}) {
|
|
93
|
+
const child = spawnProcess(command, ["app-server", "--stdio"], {
|
|
94
|
+
cwd: workspace,
|
|
95
|
+
env: process.env,
|
|
96
|
+
shell: process.platform === "win32",
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
99
|
+
});
|
|
100
|
+
const pending = new Map();
|
|
101
|
+
let requestId = 0;
|
|
102
|
+
let threadId = previousSessionId || "";
|
|
103
|
+
let turnId = "";
|
|
104
|
+
let finalMessage = "";
|
|
105
|
+
let stderr = "";
|
|
106
|
+
let settled = false;
|
|
107
|
+
|
|
108
|
+
const request = (method, params) =>
|
|
109
|
+
new Promise((resolve, reject) => {
|
|
110
|
+
const id = ++requestId;
|
|
111
|
+
pending.set(id, { resolve, reject });
|
|
112
|
+
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const completed = new Promise((resolve, reject) => {
|
|
116
|
+
const finish = (error) => {
|
|
117
|
+
if (settled) return;
|
|
118
|
+
settled = true;
|
|
119
|
+
for (const waiter of pending.values()) waiter.reject(error || new Error("Codex app-server stopped."));
|
|
120
|
+
pending.clear();
|
|
121
|
+
if (error) reject(error);
|
|
122
|
+
else resolve({ finalMessage, output: stderr.slice(-20_000), sessionId: threadId });
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
child.once("error", finish);
|
|
126
|
+
child.once("close", (code) => {
|
|
127
|
+
if (!settled) finish(new Error(`Codex app-server exited before completing the turn (code ${code ?? 1}). ${stderr}`));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const lines = readline.createInterface({ input: child.stdout });
|
|
131
|
+
lines.on("line", (line) => {
|
|
132
|
+
let message;
|
|
133
|
+
try {
|
|
134
|
+
message = JSON.parse(line);
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (message.id !== undefined) {
|
|
139
|
+
const waiter = pending.get(message.id);
|
|
140
|
+
if (!waiter) return;
|
|
141
|
+
pending.delete(message.id);
|
|
142
|
+
if (message.error) waiter.reject(new Error(message.error.message || "Codex app-server request failed."));
|
|
143
|
+
else waiter.resolve(message.result);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const params = message.params || {};
|
|
147
|
+
if (message.method === "item/agentMessage/delta" && typeof params.delta === "string") {
|
|
148
|
+
finalMessage += params.delta;
|
|
149
|
+
callbacks.onEvent?.({ type: "codex.agent_message_delta", delta: params.delta });
|
|
150
|
+
} else if (message.method === "item/started" || message.method === "item/completed") {
|
|
151
|
+
callbacks.onEvent?.({ type: `codex.${message.method}`, item: params.item });
|
|
152
|
+
} else if (message.method === "thread/tokenUsage/updated") {
|
|
153
|
+
callbacks.onEvent?.({ type: "codex.usage", update: params });
|
|
154
|
+
} else if (message.method === "turn/completed" && params.threadId === threadId) {
|
|
155
|
+
const status = params.turn?.status;
|
|
156
|
+
if (status === "failed") finish(new Error(params.turn?.error?.message || "Codex turn failed."));
|
|
157
|
+
else if (!finalMessage.trim()) finish(new Error("Codex completed without returning a response."));
|
|
158
|
+
else finish();
|
|
159
|
+
child.kill();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
child.stderr.setEncoding("utf8");
|
|
164
|
+
child.stderr.on("data", (chunk) => {
|
|
165
|
+
stderr = `${stderr}${chunk}`.slice(-20_000);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
void (async () => {
|
|
169
|
+
try {
|
|
170
|
+
await request("initialize", {
|
|
171
|
+
clientInfo: { name: "engineeros-connector", title: "EngineerOS Connector", version: "0.11.0" },
|
|
172
|
+
});
|
|
173
|
+
child.stdin.write(`${JSON.stringify({ method: "initialized" })}\n`);
|
|
174
|
+
const threadResult = previousSessionId
|
|
175
|
+
? await request("thread/resume", {
|
|
176
|
+
threadId: previousSessionId,
|
|
177
|
+
cwd: workspace,
|
|
178
|
+
sandbox,
|
|
179
|
+
approvalPolicy: "never",
|
|
180
|
+
model: profile.model || null,
|
|
181
|
+
})
|
|
182
|
+
: await request("thread/start", {
|
|
183
|
+
cwd: workspace,
|
|
184
|
+
sandbox,
|
|
185
|
+
approvalPolicy: "never",
|
|
186
|
+
model: profile.model || null,
|
|
187
|
+
ephemeral: false,
|
|
188
|
+
});
|
|
189
|
+
threadId = threadResult.thread.id;
|
|
190
|
+
callbacks.onEvent?.({ type: "thread.started", thread_id: threadId });
|
|
191
|
+
const turnResult = await request("turn/start", {
|
|
192
|
+
threadId,
|
|
193
|
+
input: [{ type: "text", text: prompt }],
|
|
194
|
+
effort: profile.reasoning_effort || null,
|
|
195
|
+
});
|
|
196
|
+
turnId = turnResult.turn.id;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
child.kill();
|
|
199
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
200
|
+
}
|
|
201
|
+
})();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
child,
|
|
206
|
+
completed,
|
|
207
|
+
cancel: async () => {
|
|
208
|
+
if (threadId && turnId && !settled) {
|
|
209
|
+
try {
|
|
210
|
+
await request("turn/interrupt", { threadId, turnId });
|
|
211
|
+
} catch {
|
|
212
|
+
// Process termination below is the final cancellation boundary.
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
child.kill();
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|