@agentprojectcontext/apx 1.8.2 → 1.10.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/README.md +2 -1
- package/package.json +4 -1
- package/skills/apx/SKILL.md +5 -0
- package/src/cli/commands/artifact.js +45 -0
- package/src/cli/commands/routine.js +15 -1
- package/src/cli/commands/runtime.js +1 -1
- package/src/cli/commands/sys.js +330 -0
- package/src/cli/index.js +100 -6
- package/src/cli/terminal-chat/renderer.js +412 -0
- package/src/core/apc-context-skill.md +2 -2
- package/src/core/apx-skill.md +4 -0
- package/src/core/artifacts-store.js +59 -0
- package/src/core/routines-store.js +40 -7
- package/src/daemon/apc-runtime-context.js +3 -2
- package/src/daemon/api.js +80 -2
- package/src/daemon/env-detect.js +1 -0
- package/src/daemon/routines.js +141 -13
- package/src/daemon/runtimes/claude-code.js +24 -6
- package/src/daemon/runtimes/cursor-agent.js +34 -0
- package/src/daemon/runtimes/gemini-cli.js +32 -0
- package/src/daemon/runtimes/index.js +8 -1
- package/src/daemon/runtimes/qwen-code.js +36 -0
- package/src/daemon/super-agent-tools/index.js +2 -0
- package/src/daemon/super-agent-tools/tools/call-runtime.js +112 -42
- package/src/daemon/super-agent-tools/tools/search-files.js +66 -0
- package/src/daemon/super-agent.js +6 -17
- package/src/mcp/index.js +1 -1
package/src/daemon/api.js
CHANGED
|
@@ -43,6 +43,12 @@ import { readAgents } from "../core/parser.js";
|
|
|
43
43
|
import { parseSessionFrontmatter } from "../core/parser.js";
|
|
44
44
|
import { writeAgentFile, ensureAgentDir, regenerateAgentsMd } from "../core/scaffold.js";
|
|
45
45
|
import { buildAgentSystem } from "../core/agent-system.js";
|
|
46
|
+
import {
|
|
47
|
+
createArtifact,
|
|
48
|
+
listArtifacts,
|
|
49
|
+
readArtifact,
|
|
50
|
+
removeArtifact,
|
|
51
|
+
} from "../core/artifacts-store.js";
|
|
46
52
|
|
|
47
53
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
48
54
|
|
|
@@ -518,6 +524,35 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
518
524
|
}
|
|
519
525
|
});
|
|
520
526
|
|
|
527
|
+
// POST /projects/:pid/super-agent/chat
|
|
528
|
+
app.post("/projects/:pid/super-agent/chat", async (req, res) => {
|
|
529
|
+
const p = project(req, res);
|
|
530
|
+
if (!p) return;
|
|
531
|
+
const { prompt, contextNote, previousMessages, model } = req.body || {};
|
|
532
|
+
if (!prompt) return res.status(400).json({ error: "prompt required" });
|
|
533
|
+
try {
|
|
534
|
+
const saResult = await runSuperAgent({
|
|
535
|
+
globalConfig: config,
|
|
536
|
+
projects,
|
|
537
|
+
plugins,
|
|
538
|
+
registries,
|
|
539
|
+
prompt,
|
|
540
|
+
contextNote: contextNote || `Context: Project ${p.id} (${p.name}) at ${p.path}`,
|
|
541
|
+
previousMessages: previousMessages || [],
|
|
542
|
+
overrideModel: model,
|
|
543
|
+
});
|
|
544
|
+
projects.rebuild(p.id);
|
|
545
|
+
res.json({
|
|
546
|
+
text: saResult.text,
|
|
547
|
+
usage: saResult.usage,
|
|
548
|
+
name: saResult.name,
|
|
549
|
+
trace: saResult.trace,
|
|
550
|
+
});
|
|
551
|
+
} catch (e) {
|
|
552
|
+
res.status(500).json({ error: e.message });
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
521
556
|
// GET /projects/:pid/agents/:slug/conversations
|
|
522
557
|
app.get("/projects/:pid/agents/:slug/conversations", (req, res) => {
|
|
523
558
|
const p = project(req, res);
|
|
@@ -739,10 +774,14 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
739
774
|
if (!p) return;
|
|
740
775
|
const { id } = req.params;
|
|
741
776
|
|
|
742
|
-
const
|
|
777
|
+
const sessionRoots = [
|
|
778
|
+
path.join(p.storagePath || p.path, "agents"),
|
|
779
|
+
path.join(p.path, ".apc", "agents"),
|
|
780
|
+
];
|
|
743
781
|
let sessionFile = null;
|
|
744
782
|
let agentSlug = null;
|
|
745
|
-
|
|
783
|
+
for (const agentsDir of sessionRoots) {
|
|
784
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
746
785
|
for (const slug of fs.readdirSync(agentsDir)) {
|
|
747
786
|
const f = path.join(agentsDir, slug, "sessions", `${id}.md`);
|
|
748
787
|
if (fs.existsSync(f)) {
|
|
@@ -751,6 +790,7 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
751
790
|
break;
|
|
752
791
|
}
|
|
753
792
|
}
|
|
793
|
+
if (sessionFile) break;
|
|
754
794
|
}
|
|
755
795
|
if (!sessionFile) return res.status(404).json({ error: `session ${id} not found` });
|
|
756
796
|
|
|
@@ -812,6 +852,7 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
812
852
|
const p = project(req, res);
|
|
813
853
|
if (!p) return;
|
|
814
854
|
try {
|
|
855
|
+
// Pass all fields including pipeline extensions.
|
|
815
856
|
const r = upsertRoutine(p.storagePath, req.body || {});
|
|
816
857
|
res.status(201).json(r);
|
|
817
858
|
} catch (e) {
|
|
@@ -819,6 +860,43 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
819
860
|
}
|
|
820
861
|
});
|
|
821
862
|
|
|
863
|
+
// ---- Artifacts (managed files in storagePath/artifacts/) ---------
|
|
864
|
+
app.get("/projects/:pid/artifacts", (req, res) => {
|
|
865
|
+
const p = project(req, res);
|
|
866
|
+
if (!p) return;
|
|
867
|
+
res.json(listArtifacts(p.storagePath));
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
app.post("/projects/:pid/artifacts", (req, res) => {
|
|
871
|
+
const p = project(req, res);
|
|
872
|
+
if (!p) return;
|
|
873
|
+
const { name, content = "" } = req.body || {};
|
|
874
|
+
if (!name) return res.status(400).json({ error: "name required" });
|
|
875
|
+
try {
|
|
876
|
+
const filePath = createArtifact(p.storagePath, name, content);
|
|
877
|
+
res.status(201).json({ name, path: filePath });
|
|
878
|
+
} catch (e) {
|
|
879
|
+
res.status(400).json({ error: e.message });
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
app.get("/projects/:pid/artifacts/:name", (req, res) => {
|
|
884
|
+
const p = project(req, res);
|
|
885
|
+
if (!p) return;
|
|
886
|
+
try {
|
|
887
|
+
res.json(readArtifact(p.storagePath, decodeURIComponent(req.params.name)));
|
|
888
|
+
} catch (e) {
|
|
889
|
+
res.status(404).json({ error: e.message });
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
app.delete("/projects/:pid/artifacts/:name", (req, res) => {
|
|
894
|
+
const p = project(req, res);
|
|
895
|
+
if (!p) return;
|
|
896
|
+
const ok = removeArtifact(p.storagePath, decodeURIComponent(req.params.name));
|
|
897
|
+
res.status(ok ? 204 : 404).end();
|
|
898
|
+
});
|
|
899
|
+
|
|
822
900
|
app.delete("/projects/:pid/routines/:name", (req, res) => {
|
|
823
901
|
const p = project(req, res);
|
|
824
902
|
if (!p) return;
|
package/src/daemon/env-detect.js
CHANGED
|
@@ -11,6 +11,7 @@ const PROBES = [
|
|
|
11
11
|
{ id: "aider", binary: "aider", args: ["--version"], category: "runtime" },
|
|
12
12
|
{ id: "gemini-cli", binary: "gemini", args: ["--version"], category: "runtime" },
|
|
13
13
|
{ id: "cursor-agent",binary: "cursor-agent", args: ["--version"], category: "runtime" },
|
|
14
|
+
{ id: "qwen-code", binary: "qwen", args: ["--version"], category: "runtime" },
|
|
14
15
|
|
|
15
16
|
// Local LLM runners (engines/)
|
|
16
17
|
{ id: "ollama", binary: "ollama", args: ["--version"], category: "engine" },
|
package/src/daemon/routines.js
CHANGED
|
@@ -11,10 +11,15 @@
|
|
|
11
11
|
// shell — run a shell command. spec: { command, timeout_ms? }
|
|
12
12
|
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
+
import { execFile } from "node:child_process";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
14
18
|
import { callEngine } from "./engines/index.js";
|
|
15
19
|
import { runSuperAgent } from "./super-agent.js";
|
|
16
20
|
import { readAgents } from "../core/parser.js";
|
|
17
21
|
import { buildAgentSystem } from "../core/agent-system.js";
|
|
22
|
+
import { resolveArtifactRef, ARTIFACTS_SKIP_SIGNAL } from "../core/artifacts-store.js";
|
|
18
23
|
import {
|
|
19
24
|
listRoutines,
|
|
20
25
|
getRoutine,
|
|
@@ -169,25 +174,148 @@ const HANDLERS = {
|
|
|
169
174
|
shell: handleShell,
|
|
170
175
|
};
|
|
171
176
|
|
|
177
|
+
// --------------------- pipeline: pre/post shell commands --------------------
|
|
178
|
+
|
|
179
|
+
// Run a single shell command. Returns { exitCode, stdout, stderr }.
|
|
180
|
+
function runShellCmd(cmd, env = {}, cwd = os.homedir()) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
const child = spawn("sh", ["-c", cmd], {
|
|
183
|
+
cwd,
|
|
184
|
+
env: { ...process.env, ...env },
|
|
185
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
186
|
+
});
|
|
187
|
+
let stdout = "";
|
|
188
|
+
let stderr = "";
|
|
189
|
+
child.stdout.on("data", (c) => (stdout += c.toString()));
|
|
190
|
+
child.stderr.on("data", (c) => (stderr += c.toString()));
|
|
191
|
+
child.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
|
|
192
|
+
child.on("error", (e) => resolve({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Inject {{pre_output}} into a prompt string.
|
|
197
|
+
function injectPreOutput(prompt, preOutput) {
|
|
198
|
+
if (!prompt || typeof prompt !== "string") return prompt;
|
|
199
|
+
return prompt.replace(/\{\{pre_output\}\}/g, preOutput || "");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Determine whether to skip the LLM call based on skip_prompt_on + pre results.
|
|
203
|
+
function shouldSkipPrompt(routine, preExitCode, preStdout) {
|
|
204
|
+
const mode = routine.skip_prompt_on || "signal";
|
|
205
|
+
if (mode === "always") return true;
|
|
206
|
+
if (mode === "never") return false;
|
|
207
|
+
if (mode === "signal") return preStdout.includes(ARTIFACTS_SKIP_SIGNAL);
|
|
208
|
+
if (mode === "pre_failure") return preExitCode !== 0;
|
|
209
|
+
if (mode === "pre_success") return preExitCode === 0;
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
172
213
|
// --------------------- runtime: run one + loop ------------------------------
|
|
173
214
|
|
|
174
215
|
export async function runRoutineNow(ctx, routine) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
216
|
+
// Determine the working directory for shell commands.
|
|
217
|
+
const cwd = ctx.project?.path || os.homedir();
|
|
218
|
+
const storagePath = ctx.project?.storagePath || os.homedir();
|
|
219
|
+
|
|
220
|
+
const hasPreCmds = Array.isArray(routine.pre_commands) && routine.pre_commands.length > 0;
|
|
221
|
+
const hasPostCmds = Array.isArray(routine.post_commands) && routine.post_commands.length > 0;
|
|
222
|
+
|
|
223
|
+
let preStdout = "";
|
|
224
|
+
let preExitCode = 0;
|
|
225
|
+
let preOutputFile = null;
|
|
226
|
+
|
|
227
|
+
// ── Phase 1: pre_commands ──────────────────────────────────────────────────
|
|
228
|
+
if (hasPreCmds) {
|
|
229
|
+
const combinedOut = [];
|
|
230
|
+
for (const rawCmd of routine.pre_commands) {
|
|
231
|
+
// Resolve "artifact:<name>" shorthand to its absolute path.
|
|
232
|
+
const cmd = resolveArtifactRef(rawCmd, storagePath);
|
|
233
|
+
const { exitCode, stdout, stderr } = await runShellCmd(cmd, {}, cwd);
|
|
234
|
+
combinedOut.push(stdout);
|
|
235
|
+
if (stderr) combinedOut.push(stderr);
|
|
236
|
+
preExitCode = exitCode;
|
|
237
|
+
if (exitCode !== 0 && (routine.skip_prompt_on === "pre_failure" || routine.skip_prompt_on === "signal")) {
|
|
238
|
+
// Stop running further pre_commands on failure when mode cares about exit code.
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
preStdout = combinedOut.join("");
|
|
243
|
+
|
|
244
|
+
// Write pre output to a temp file so post_commands can reference it via
|
|
245
|
+
// $APX_PRE_OUTPUT_FILE even if the output is large.
|
|
246
|
+
try {
|
|
247
|
+
preOutputFile = path.join(os.tmpdir(), `apx-pre-${routine.name}-${Date.now()}.txt`);
|
|
248
|
+
fs.writeFileSync(preOutputFile, preStdout);
|
|
249
|
+
} catch { preOutputFile = null; }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Env vars injected into post_commands and available in shell pre_commands output.
|
|
253
|
+
const pipelineEnv = {
|
|
254
|
+
APX_PRE_EXIT: String(preExitCode),
|
|
255
|
+
APX_PRE_OUTPUT: preStdout.slice(0, 32_000), // guard against huge outputs
|
|
256
|
+
APX_PRE_OUTPUT_FILE: preOutputFile || "",
|
|
257
|
+
APX_ROUTINE: routine.name,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// ── Phase 2: LLM / handler ────────────────────────────────────────────────
|
|
261
|
+
const skip = hasPreCmds && shouldSkipPrompt(routine, preExitCode, preStdout);
|
|
262
|
+
|
|
263
|
+
let result = { status: "ok" };
|
|
178
264
|
let status = "ok";
|
|
179
265
|
let errMsg = null;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
266
|
+
|
|
267
|
+
if (!skip) {
|
|
268
|
+
// Inject {{pre_output}} into exec_agent and super_agent prompts.
|
|
269
|
+
const enrichedRoutine = (hasPreCmds && preStdout)
|
|
270
|
+
? {
|
|
271
|
+
...routine,
|
|
272
|
+
spec: {
|
|
273
|
+
...routine.spec,
|
|
274
|
+
prompt: injectPreOutput(routine.spec?.prompt, preStdout),
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
: routine;
|
|
278
|
+
|
|
279
|
+
const handler = HANDLERS[enrichedRoutine.kind];
|
|
280
|
+
if (!handler) {
|
|
183
281
|
status = "error";
|
|
184
|
-
errMsg =
|
|
282
|
+
errMsg = `unknown routine kind: ${enrichedRoutine.kind}`;
|
|
283
|
+
} else {
|
|
284
|
+
try {
|
|
285
|
+
result = await handler(ctx, enrichedRoutine);
|
|
286
|
+
if (result?.status === "error") {
|
|
287
|
+
status = "error";
|
|
288
|
+
errMsg = result.error || result.stderr || `routine ${routine.name} returned error status`;
|
|
289
|
+
}
|
|
290
|
+
} catch (e) {
|
|
291
|
+
status = "error";
|
|
292
|
+
errMsg = e.message;
|
|
293
|
+
result = { status: "error", error: e.message };
|
|
294
|
+
}
|
|
185
295
|
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
errMsg = e.message;
|
|
189
|
-
result = { status: "error", error: e.message };
|
|
296
|
+
} else {
|
|
297
|
+
result = { status: "ok", skipped: true, note: "pre_commands signalled skip" };
|
|
190
298
|
}
|
|
299
|
+
|
|
300
|
+
// ── Phase 3: post_commands ────────────────────────────────────────────────
|
|
301
|
+
if (hasPostCmds) {
|
|
302
|
+
const llmOutput = result?.reply || result?.text || "";
|
|
303
|
+
const postEnv = {
|
|
304
|
+
...pipelineEnv,
|
|
305
|
+
APX_LLM_OUTPUT: llmOutput.slice(0, 32_000),
|
|
306
|
+
APX_STATUS: status,
|
|
307
|
+
APX_SKIPPED: skip ? "1" : "0",
|
|
308
|
+
};
|
|
309
|
+
for (const rawCmd of routine.post_commands) {
|
|
310
|
+
const cmd = resolveArtifactRef(rawCmd, storagePath);
|
|
311
|
+
await runShellCmd(cmd, postEnv, cwd);
|
|
312
|
+
// Post-command failures are logged but don't change routine status.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Cleanup temp file.
|
|
317
|
+
if (preOutputFile) try { fs.unlinkSync(preOutputFile); } catch {}
|
|
318
|
+
|
|
191
319
|
const lastRun = nowIso();
|
|
192
320
|
const next = computeNextRun({ schedule: routine.schedule, last_run_at: lastRun });
|
|
193
321
|
const isOnce = parseSchedule(routine.schedule).kind === "once";
|
|
@@ -205,9 +333,9 @@ export async function runRoutineNow(ctx, routine) {
|
|
|
205
333
|
actor_id: "apx:routine",
|
|
206
334
|
author: "apx",
|
|
207
335
|
body: status === "ok"
|
|
208
|
-
? `routine ${routine.name} ok`
|
|
336
|
+
? `routine ${routine.name} ok${skip ? " (skipped LLM)" : ""}`
|
|
209
337
|
: `routine ${routine.name} error: ${errMsg}`,
|
|
210
|
-
meta: { routine: routine.name, status, result },
|
|
338
|
+
meta: { routine: routine.name, status, skipped: skip, result },
|
|
211
339
|
});
|
|
212
340
|
return { ...result, last_run_at: lastRun, next_run_at: next };
|
|
213
341
|
}
|
|
@@ -3,8 +3,31 @@
|
|
|
3
3
|
// Returns one JSON line with the result and session_id.
|
|
4
4
|
// Reference: https://docs.claude.com/en/docs/claude-code/headless
|
|
5
5
|
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
6
8
|
import { runProcess } from "./_spawn.js";
|
|
7
9
|
|
|
10
|
+
export function encodeClaudeProjectPath(cwd) {
|
|
11
|
+
return String(cwd || process.cwd()).replace(/[^A-Za-z0-9]/g, "-");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveClaudeSessionPath({ cwd, sessionId, home = process.env.HOME || process.env.USERPROFILE || "" }) {
|
|
15
|
+
if (!sessionId || !home) return null;
|
|
16
|
+
const projectsDir = path.join(home, ".claude", "projects");
|
|
17
|
+
const encodedCwd = encodeClaudeProjectPath(cwd);
|
|
18
|
+
const expected = path.join(projectsDir, encodedCwd, `${sessionId}.jsonl`);
|
|
19
|
+
if (fs.existsSync(expected)) return expected;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
for (const dir of fs.readdirSync(projectsDir)) {
|
|
23
|
+
const candidate = path.join(projectsDir, dir, `${sessionId}.jsonl`);
|
|
24
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
25
|
+
}
|
|
26
|
+
} catch {}
|
|
27
|
+
|
|
28
|
+
return expected;
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
export default {
|
|
9
32
|
id: "claude-code",
|
|
10
33
|
binary: "claude",
|
|
@@ -40,12 +63,7 @@ export default {
|
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
if (sessionId) {
|
|
43
|
-
|
|
44
|
-
// "-" (verified empirically against ~/.claude/projects/). The trailing
|
|
45
|
-
// file is `<sessionId>.jsonl`.
|
|
46
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
47
|
-
const encodedCwd = (cwd || process.cwd()).replace(/[/_]/g, "-");
|
|
48
|
-
externalSessionPath = `${home}/.claude/projects/${encodedCwd}/${sessionId}.jsonl`;
|
|
66
|
+
externalSessionPath = resolveClaudeSessionPath({ cwd, sessionId });
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
return {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Cursor Agent runtime adapter. Uses print mode for non-interactive runs:
|
|
2
|
+
// cursor-agent --print --output-format text --trust --force "<prompt>"
|
|
3
|
+
// Reference: https://docs.cursor.com/en/cli/headless
|
|
4
|
+
|
|
5
|
+
import { runProcess } from "./_spawn.js";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
id: "cursor-agent",
|
|
9
|
+
binary: "cursor-agent",
|
|
10
|
+
versionFlag: "--version",
|
|
11
|
+
|
|
12
|
+
async run({ system, prompt, cwd, env, timeoutMs }) {
|
|
13
|
+
const fullPrompt = system ? `${system}\n\n---\n\n${prompt}` : prompt;
|
|
14
|
+
const r = await runProcess({
|
|
15
|
+
command: "cursor-agent",
|
|
16
|
+
args: [
|
|
17
|
+
"--print",
|
|
18
|
+
"--output-format", "text",
|
|
19
|
+
"--trust",
|
|
20
|
+
"--force",
|
|
21
|
+
fullPrompt,
|
|
22
|
+
],
|
|
23
|
+
cwd,
|
|
24
|
+
env,
|
|
25
|
+
timeoutMs,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
exitCode: r.exitCode,
|
|
29
|
+
output: r.stdout.trim(),
|
|
30
|
+
stderr: r.stderr,
|
|
31
|
+
externalSessionPath: null,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Gemini CLI runtime adapter. Uses headless prompt mode:
|
|
2
|
+
// gemini --prompt "<prompt>" --output-format text --approval-mode yolo
|
|
3
|
+
// Reference: https://google-gemini.github.io/gemini-cli/docs/cli/headless.html
|
|
4
|
+
|
|
5
|
+
import { runProcess } from "./_spawn.js";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
id: "gemini-cli",
|
|
9
|
+
binary: "gemini",
|
|
10
|
+
versionFlag: "--version",
|
|
11
|
+
|
|
12
|
+
async run({ system, prompt, cwd, env, timeoutMs }) {
|
|
13
|
+
const fullPrompt = system ? `${system}\n\n---\n\n${prompt}` : prompt;
|
|
14
|
+
const r = await runProcess({
|
|
15
|
+
command: "gemini",
|
|
16
|
+
args: [
|
|
17
|
+
"--prompt", fullPrompt,
|
|
18
|
+
"--output-format", "text",
|
|
19
|
+
"--approval-mode", "yolo",
|
|
20
|
+
],
|
|
21
|
+
cwd,
|
|
22
|
+
env,
|
|
23
|
+
timeoutMs,
|
|
24
|
+
});
|
|
25
|
+
return {
|
|
26
|
+
exitCode: r.exitCode,
|
|
27
|
+
output: r.stdout.trim(),
|
|
28
|
+
stderr: r.stderr,
|
|
29
|
+
externalSessionPath: null,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Runtime adapters: spawn external agent CLIs (Claude Code, Codex, OpenCode,
|
|
2
|
-
// Aider, ...) with the agent's system
|
|
2
|
+
// Aider, Cursor Agent, Gemini CLI, Qwen Code, ...) with the agent's system
|
|
3
|
+
// prompt + the prompt we want to run, and
|
|
3
4
|
// capture their output. Unlike engines/ — which talk directly to model APIs —
|
|
4
5
|
// runtimes/ delegate the whole conversation to the external tool. APX only
|
|
5
6
|
// records the invocation, the prompt, the captured output, and where the tool
|
|
@@ -18,12 +19,18 @@ import claudeCode from "./claude-code.js";
|
|
|
18
19
|
import codex from "./codex.js";
|
|
19
20
|
import opencode from "./opencode.js";
|
|
20
21
|
import aider from "./aider.js";
|
|
22
|
+
import cursorAgent from "./cursor-agent.js";
|
|
23
|
+
import geminiCli from "./gemini-cli.js";
|
|
24
|
+
import qwenCode from "./qwen-code.js";
|
|
21
25
|
|
|
22
26
|
const REGISTRY = {
|
|
23
27
|
"claude-code": claudeCode,
|
|
24
28
|
codex,
|
|
25
29
|
opencode,
|
|
26
30
|
aider,
|
|
31
|
+
"cursor-agent": cursorAgent,
|
|
32
|
+
"gemini-cli": geminiCli,
|
|
33
|
+
"qwen-code": qwenCode,
|
|
27
34
|
};
|
|
28
35
|
|
|
29
36
|
export const RUNTIME_IDS = Object.keys(REGISTRY);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Qwen Code runtime adapter. Uses non-interactive mode:
|
|
2
|
+
// qwen --output-format text --approval-mode yolo "<prompt>"
|
|
3
|
+
// Reference: https://qwenlm.github.io/qwen-code-docs/en/cli/index
|
|
4
|
+
|
|
5
|
+
import { runProcess } from "./_spawn.js";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
id: "qwen-code",
|
|
9
|
+
binary: "qwen",
|
|
10
|
+
versionFlag: "--version",
|
|
11
|
+
|
|
12
|
+
async run({ system, prompt, cwd, env, timeoutMs }) {
|
|
13
|
+
const args = [
|
|
14
|
+
"--output-format", "text",
|
|
15
|
+
"--approval-mode", "yolo",
|
|
16
|
+
];
|
|
17
|
+
if (system) {
|
|
18
|
+
args.push("--append-system-prompt", system);
|
|
19
|
+
}
|
|
20
|
+
args.push(prompt);
|
|
21
|
+
|
|
22
|
+
const r = await runProcess({
|
|
23
|
+
command: "qwen",
|
|
24
|
+
args,
|
|
25
|
+
cwd,
|
|
26
|
+
env,
|
|
27
|
+
timeoutMs,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
exitCode: r.exitCode,
|
|
31
|
+
output: r.stdout.trim(),
|
|
32
|
+
stderr: r.stderr,
|
|
33
|
+
externalSessionPath: null,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -18,6 +18,7 @@ import callRuntime from "./tools/call-runtime.js";
|
|
|
18
18
|
import sendTelegram from "./tools/send-telegram.js";
|
|
19
19
|
import setIdentity from "./tools/set-identity.js";
|
|
20
20
|
import setPermissionMode from "./tools/set-permission-mode.js";
|
|
21
|
+
import searchFiles from "./tools/search-files.js";
|
|
21
22
|
import { createPermissionGuard } from "./helpers.js";
|
|
22
23
|
|
|
23
24
|
const TOOLS = [
|
|
@@ -41,6 +42,7 @@ const TOOLS = [
|
|
|
41
42
|
sendTelegram,
|
|
42
43
|
setIdentity,
|
|
43
44
|
setPermissionMode,
|
|
45
|
+
searchFiles,
|
|
44
46
|
];
|
|
45
47
|
|
|
46
48
|
export const TOOL_SCHEMAS = TOOLS.map((tool) => tool.schema);
|