replicas-engine 0.1.1042 → 0.1.1044
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/dist/src/{asp-host-UAEBYHX2.js → asp-host-CIG6J64Z.js} +5 -5
- package/dist/src/chunk-3RXHH2OK.js +232 -0
- package/dist/src/{chunk-CMXKQPDV.js → chunk-A6M7VE2Y.js} +1 -1
- package/dist/src/{chunk-QDIKROO7.js → chunk-EPG6XMVB.js} +3 -3
- package/dist/src/{chunk-TR7NUUT3.js → chunk-GEYFS65S.js} +2 -2
- package/dist/src/{chunk-KXBTOL3R.js → chunk-PZEZIF5C.js} +75 -61
- package/dist/src/{chunk-4VDF2C2U.js → chunk-UMVWJYQQ.js} +6 -6
- package/dist/src/{chunk-XS3QUSHQ.js → chunk-VSDRLCZY.js} +1 -1
- package/dist/src/{chunk-SMBKCCEE.js → chunk-ZCIRBGGV.js} +2 -2
- package/dist/src/command-protection-hook.js +4 -4
- package/dist/src/headless-agent.js +159 -19
- package/dist/src/index.js +1493 -1608
- package/dist/src/post-tool-pr-hook.js +2 -2
- package/dist/src/subagent-mcp.js +1 -1
- package/package.json +1 -1
- package/dist/src/chunk-P73LGPQO.js +0 -65
|
@@ -2,28 +2,47 @@
|
|
|
2
2
|
import { createRequire as __createRequire } from 'node:module';
|
|
3
3
|
const require = __createRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
THREAD_TOKEN_USAGE_UPDATED_METHOD,
|
|
6
|
+
fetchOpencodeModelVariants,
|
|
7
|
+
getOpenCodeGoModel,
|
|
8
|
+
getOpencodeProvider,
|
|
9
|
+
initializeMuse,
|
|
10
|
+
museTurnStartParams,
|
|
11
|
+
opencodeConfig,
|
|
12
|
+
opencodeThinkingVariant,
|
|
13
|
+
recoverCompletedTurn,
|
|
14
|
+
spawnMuseMsp,
|
|
15
|
+
startMuseSession
|
|
16
|
+
} from "./chunk-3RXHH2OK.js";
|
|
7
17
|
import {
|
|
8
18
|
AppServerProcess,
|
|
9
19
|
buildCodexAgentEnv
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-EPG6XMVB.js";
|
|
21
|
+
import "./chunk-VSDRLCZY.js";
|
|
12
22
|
import {
|
|
13
23
|
AGENT,
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
OPENCODE_GO_PROVIDER,
|
|
25
|
+
codexReasoningEffortForThinkingLevel,
|
|
26
|
+
extractJsonObject,
|
|
27
|
+
headlessAgentRequestSchema,
|
|
28
|
+
headlessAgentUsagePath,
|
|
29
|
+
isRecord,
|
|
30
|
+
withTimeout
|
|
31
|
+
} from "./chunk-PZEZIF5C.js";
|
|
16
32
|
import "./chunk-VEQXQN22.js";
|
|
17
33
|
|
|
18
34
|
// src/headless-agent.ts
|
|
19
35
|
import { readFile, writeFile } from "fs/promises";
|
|
20
36
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
37
|
+
import { createOpencodeClient, createOpencodeServer } from "@opencode-ai/sdk/v2";
|
|
38
|
+
import { Agent as CursorAgent } from "@cursor/sdk";
|
|
21
39
|
async function runClaude(request) {
|
|
22
40
|
const response = query({
|
|
23
41
|
prompt: request.prompt,
|
|
24
42
|
options: {
|
|
25
43
|
cwd: request.workingDirectory,
|
|
26
44
|
model: request.model,
|
|
45
|
+
...request.thinkingLevel ? { effort: request.thinkingLevel } : {},
|
|
27
46
|
tools: [],
|
|
28
47
|
permissionMode: "dontAsk",
|
|
29
48
|
settingSources: [],
|
|
@@ -32,21 +51,31 @@ async function runClaude(request) {
|
|
|
32
51
|
env: { ...process.env, CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" }
|
|
33
52
|
}
|
|
34
53
|
});
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
const lastResult = async () => {
|
|
55
|
+
let last = null;
|
|
37
56
|
for await (const message of response) {
|
|
38
|
-
if (message.type === "result")
|
|
57
|
+
if (message.type === "result") last = message;
|
|
39
58
|
}
|
|
59
|
+
return last;
|
|
60
|
+
};
|
|
61
|
+
let result;
|
|
62
|
+
try {
|
|
63
|
+
result = await withTimeout(lastResult(), request.timeoutSeconds * 1e3, "Claude run timed out");
|
|
40
64
|
} finally {
|
|
41
65
|
response.close();
|
|
42
66
|
}
|
|
43
67
|
if (!result) throw new Error("Claude Agent SDK returned no result");
|
|
44
68
|
if (result.subtype !== "success") throw new Error(result.errors.join("\n") || result.subtype);
|
|
45
|
-
|
|
69
|
+
const { input_tokens, cache_creation_input_tokens, cache_read_input_tokens, output_tokens } = result.usage;
|
|
70
|
+
return {
|
|
71
|
+
output: result.structured_output ?? JSON.parse(result.result),
|
|
72
|
+
usage: { inputTokens: input_tokens + cache_creation_input_tokens + cache_read_input_tokens, outputTokens: output_tokens }
|
|
73
|
+
};
|
|
46
74
|
}
|
|
47
75
|
function runCodexTurn(client, params, timeoutMs) {
|
|
48
76
|
return new Promise((resolve, reject) => {
|
|
49
77
|
const completedItems = [];
|
|
78
|
+
let usage = null;
|
|
50
79
|
const messageDeltas = /* @__PURE__ */ new Map();
|
|
51
80
|
const cleanup = () => {
|
|
52
81
|
clearTimeout(timeout);
|
|
@@ -54,6 +83,12 @@ function runCodexTurn(client, params, timeoutMs) {
|
|
|
54
83
|
client.off("dispose", onDispose);
|
|
55
84
|
};
|
|
56
85
|
const onNotification = (notification) => {
|
|
86
|
+
if (notification.method === THREAD_TOKEN_USAGE_UPDATED_METHOD) {
|
|
87
|
+
if (notification.params.threadId !== params.threadId) return;
|
|
88
|
+
const { inputTokens, outputTokens } = notification.params.tokenUsage.total;
|
|
89
|
+
usage = { inputTokens, outputTokens };
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
57
92
|
if (notification.method === "item/completed") {
|
|
58
93
|
if (notification.params.threadId !== params.threadId || notification.params.item.type !== "agentMessage") return;
|
|
59
94
|
completedItems.push(notification.params.item);
|
|
@@ -71,7 +106,7 @@ function runCodexTurn(client, params, timeoutMs) {
|
|
|
71
106
|
const turn = recoverCompletedTurn(notification.params.turn, completedItems, messageDeltas);
|
|
72
107
|
const message = turn.items.findLast((item) => item.type === "agentMessage")?.text ?? null;
|
|
73
108
|
cleanup();
|
|
74
|
-
resolve({ turn, message });
|
|
109
|
+
resolve({ turn, message, usage });
|
|
75
110
|
};
|
|
76
111
|
const onDispose = (error) => {
|
|
77
112
|
cleanup();
|
|
@@ -122,20 +157,112 @@ async function runCodex(request) {
|
|
|
122
157
|
sandbox: "read-only",
|
|
123
158
|
ephemeral: true
|
|
124
159
|
});
|
|
125
|
-
const { turn, message } = await runCodexTurn(client, {
|
|
160
|
+
const { turn, message, usage } = await runCodexTurn(client, {
|
|
126
161
|
threadId: thread.thread.id,
|
|
127
162
|
input: [{ type: "text", text: request.prompt, text_elements: [] }],
|
|
128
163
|
outputSchema: request.outputSchema,
|
|
164
|
+
...request.thinkingLevel ? { effort: codexReasoningEffortForThinkingLevel(request.thinkingLevel) } : {},
|
|
129
165
|
approvalPolicy: "never",
|
|
130
166
|
approvalsReviewer: "user"
|
|
131
167
|
}, request.timeoutSeconds * 1e3);
|
|
132
168
|
if (turn.status === "failed") throw new Error(turn.error?.message ?? "Codex ASP turn failed");
|
|
133
169
|
if (!message) throw new Error("Codex ASP returned no final message");
|
|
134
|
-
return JSON.parse(message);
|
|
170
|
+
return { output: JSON.parse(message), usage };
|
|
135
171
|
} finally {
|
|
136
172
|
await appServer.stop();
|
|
137
173
|
}
|
|
138
174
|
}
|
|
175
|
+
async function runOpencode(request) {
|
|
176
|
+
const provider = await getOpencodeProvider();
|
|
177
|
+
const model = provider === OPENCODE_GO_PROVIDER ? getOpenCodeGoModel(request.model) : request.model;
|
|
178
|
+
const server = await createOpencodeServer({ port: 0, timeout: 3e4, config: opencodeConfig(provider, model) });
|
|
179
|
+
try {
|
|
180
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
181
|
+
const client = createOpencodeClient({
|
|
182
|
+
baseUrl: server.url,
|
|
183
|
+
...password ? { headers: { Authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` } } : {}
|
|
184
|
+
});
|
|
185
|
+
const variant = opencodeThinkingVariant((await fetchOpencodeModelVariants(client, request.workingDirectory, provider)).get(model), request.thinkingLevel);
|
|
186
|
+
const session = await client.session.create({ directory: request.workingDirectory, model: { providerID: provider, id: model } }, { throwOnError: true });
|
|
187
|
+
const result = await client.session.prompt({
|
|
188
|
+
sessionID: session.data.id,
|
|
189
|
+
directory: request.workingDirectory,
|
|
190
|
+
model: { providerID: provider, modelID: model },
|
|
191
|
+
...variant ? { variant } : {},
|
|
192
|
+
format: { type: "json_schema", schema: request.outputSchema, retryCount: 2 },
|
|
193
|
+
parts: [{ type: "text", text: request.prompt }]
|
|
194
|
+
}, { throwOnError: true, signal: AbortSignal.timeout(request.timeoutSeconds * 1e3) });
|
|
195
|
+
const { info } = result.data;
|
|
196
|
+
if (info.error) throw new Error(`${info.error.name}: ${JSON.stringify(info.error.data)}`);
|
|
197
|
+
if (info.structured === void 0) throw new Error("Opencode returned no structured output");
|
|
198
|
+
return {
|
|
199
|
+
output: info.structured,
|
|
200
|
+
usage: { inputTokens: info.tokens.input + info.tokens.cache.read + info.tokens.cache.write, outputTokens: info.tokens.output + info.tokens.reasoning }
|
|
201
|
+
};
|
|
202
|
+
} finally {
|
|
203
|
+
server.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
var jsonReplyPrompt = (request) => `${request.prompt}
|
|
207
|
+
|
|
208
|
+
Reply with only JSON, no prose or code fences, matching this JSON Schema:
|
|
209
|
+
${JSON.stringify(request.outputSchema)}`;
|
|
210
|
+
function parseJsonReply(reply) {
|
|
211
|
+
const output = extractJsonObject(reply);
|
|
212
|
+
if (output === null) throw new Error(`Reply was not JSON: ${reply.slice(0, 200)}`);
|
|
213
|
+
return output;
|
|
214
|
+
}
|
|
215
|
+
async function runCursor(request) {
|
|
216
|
+
const apiKey = process.env.CURSOR_API_KEY;
|
|
217
|
+
if (!apiKey) throw new Error("Cursor API key is not configured");
|
|
218
|
+
const agent = await CursorAgent.create({ apiKey, model: { id: request.model }, local: { cwd: request.workingDirectory, settingSources: [] } });
|
|
219
|
+
const run = await agent.send(jsonReplyPrompt(request));
|
|
220
|
+
const result = await withTimeout(run.wait(), request.timeoutSeconds * 1e3, "Cursor run timed out");
|
|
221
|
+
if (result.status !== "finished" || !result.result) throw new Error(result.error?.message ?? `Cursor run ${result.status}`);
|
|
222
|
+
return {
|
|
223
|
+
output: parseJsonReply(result.result),
|
|
224
|
+
usage: result.usage ? { inputTokens: result.usage.inputTokens + result.usage.cacheReadTokens + result.usage.cacheWriteTokens, outputTokens: result.usage.outputTokens } : null
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function runMuse(request) {
|
|
228
|
+
const { child, client } = spawnMuseMsp(request.workingDirectory);
|
|
229
|
+
let stderr = "";
|
|
230
|
+
child.stderr.on("data", (chunk) => {
|
|
231
|
+
stderr = `${stderr}${chunk}`.slice(-2e3);
|
|
232
|
+
});
|
|
233
|
+
child.on("exit", (code, signal) => client.dispose(new Error(`Muse exited with ${code ?? signal}: ${stderr.trim()}`)));
|
|
234
|
+
client.on("serverRequest", (serverRequest) => client.reject(serverRequest.id, -32601, "Headless agents do not accept server requests"));
|
|
235
|
+
try {
|
|
236
|
+
await initializeMuse(client, "replicas_headless_agent");
|
|
237
|
+
const sessionId = await startMuseSession(client, { approvalMode: "denyUnmatched", modelId: request.model, workspaceRoot: request.workingDirectory });
|
|
238
|
+
let message = null;
|
|
239
|
+
let usage = null;
|
|
240
|
+
const completed = new Promise((resolve, reject) => {
|
|
241
|
+
client.on("dispose", reject);
|
|
242
|
+
client.on("notification", (frame) => {
|
|
243
|
+
if (!isRecord(frame) || !isRecord(frame.params) || frame.params.sessionId !== sessionId) return;
|
|
244
|
+
const { method, params } = frame;
|
|
245
|
+
if (method === "item/completed" && isRecord(params.item) && params.item.kind === "agentMessage" && typeof params.item.text === "string") {
|
|
246
|
+
message = params.item.text;
|
|
247
|
+
} else if (method === "session/tokenUsage" && isRecord(params.cumulative) && typeof params.cumulative.promptTokens === "number" && typeof params.cumulative.outputTokens === "number") {
|
|
248
|
+
usage = { inputTokens: params.cumulative.promptTokens, outputTokens: params.cumulative.outputTokens };
|
|
249
|
+
} else if (method === "turn/completed") {
|
|
250
|
+
if (isRecord(params.error)) reject(new Error(typeof params.error.message === "string" ? params.error.message : "Muse turn failed"));
|
|
251
|
+
else resolve();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
await Promise.all([
|
|
256
|
+
client.request("turn/start", museTurnStartParams(sessionId, [{ type: "text", text: jsonReplyPrompt(request) }], request.thinkingLevel)),
|
|
257
|
+
withTimeout(completed, request.timeoutSeconds * 1e3, "Muse turn timed out")
|
|
258
|
+
]);
|
|
259
|
+
if (!message) throw new Error("Muse returned no final message");
|
|
260
|
+
return { output: parseJsonReply(message), usage };
|
|
261
|
+
} finally {
|
|
262
|
+
client.dispose();
|
|
263
|
+
child.kill("SIGTERM");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
139
266
|
async function main() {
|
|
140
267
|
const [requestPath, outputPath] = process.argv.slice(2);
|
|
141
268
|
if (!requestPath || !outputPath) throw new Error("Usage: replicas-headless-agent <request.json> <output.json>");
|
|
@@ -148,13 +275,26 @@ async function main() {
|
|
|
148
275
|
case AGENT.CODEX:
|
|
149
276
|
result = await runCodex(request);
|
|
150
277
|
break;
|
|
151
|
-
|
|
152
|
-
|
|
278
|
+
case AGENT.OPENCODE:
|
|
279
|
+
result = await runOpencode(request);
|
|
280
|
+
break;
|
|
281
|
+
case AGENT.CURSOR:
|
|
282
|
+
result = await runCursor(request);
|
|
283
|
+
break;
|
|
284
|
+
case AGENT.MUSE:
|
|
285
|
+
result = await runMuse(request);
|
|
286
|
+
break;
|
|
287
|
+
case AGENT.PI:
|
|
288
|
+
throw new Error("Send Pi credentials as an Opencode request, which reads the same stored secrets");
|
|
289
|
+
default: {
|
|
290
|
+
const unsupported = request.agent;
|
|
291
|
+
throw new Error(`Unsupported headless agent: ${String(unsupported)}`);
|
|
292
|
+
}
|
|
153
293
|
}
|
|
154
|
-
await writeFile(outputPath, JSON.stringify(result), { mode: 384 });
|
|
294
|
+
await writeFile(outputPath, JSON.stringify(result.output), { mode: 384 });
|
|
295
|
+
if (result.usage) await writeFile(headlessAgentUsagePath(outputPath), JSON.stringify(result.usage), { mode: 384 });
|
|
155
296
|
}
|
|
156
|
-
main().
|
|
157
|
-
process.exitCode = 1;
|
|
297
|
+
main().then(() => process.exit(0), (error) => {
|
|
158
298
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
159
|
-
|
|
299
|
+
`, () => process.exit(1));
|
|
160
300
|
});
|