@agentprojectcontext/apx 1.4.0 → 1.6.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/package.json +1 -1
- package/src/cli/commands/setup.js +20 -14
- package/src/daemon/api.js +162 -0
package/package.json
CHANGED
|
@@ -281,14 +281,15 @@ export async function cmdSetup() {
|
|
|
281
281
|
// The prompt is in English so the model knows to reply in the user's language.
|
|
282
282
|
async function sendTelegramWakeup({ botToken, chatId, language, model }) {
|
|
283
283
|
const prompt =
|
|
284
|
-
`You are APX, an AI agent assistant that just came online. ` +
|
|
285
|
-
`
|
|
286
|
-
`
|
|
287
|
-
`
|
|
288
|
-
`
|
|
289
|
-
`
|
|
290
|
-
|
|
291
|
-
|
|
284
|
+
`You are APX, an AI agent assistant that just came online for the first time. ` +
|
|
285
|
+
`Write a short, enthusiastic wake-up message in ${language}. ` +
|
|
286
|
+
`Structure it in exactly 3 short lines: ` +
|
|
287
|
+
`1) An energetic line announcing you are online (use ⚡ emoji). ` +
|
|
288
|
+
`2) Say you don't have a name yet and ask the user what they'd like to call you. ` +
|
|
289
|
+
`3) Ask the user for their own name or what you should call them. ` +
|
|
290
|
+
`Be warm and playful. Do NOT mention configuration or setup.`;
|
|
291
|
+
|
|
292
|
+
// Ask the daemon's super-agent
|
|
292
293
|
let text;
|
|
293
294
|
try {
|
|
294
295
|
const res = await fetchJson("http://127.0.0.1:7430/super-agent/ask", 8000);
|
|
@@ -323,10 +324,15 @@ async function sendTelegramWakeup({ botToken, chatId, language, model }) {
|
|
|
323
324
|
// Minimal fallback messages per common language (used only if daemon can't respond)
|
|
324
325
|
function languageFallback(lang) {
|
|
325
326
|
const l = lang.toLowerCase();
|
|
326
|
-
if (/espa[ñn]|spanish|arg|lat/i.test(l))
|
|
327
|
-
|
|
328
|
-
if (/
|
|
329
|
-
|
|
330
|
-
if (/
|
|
331
|
-
|
|
327
|
+
if (/espa[ñn]|spanish|arg|lat/i.test(l))
|
|
328
|
+
return "⚡ ¡Despierto y listo para trabajar! APX online.\nAún no tengo nombre, ¿cómo te gustaría llamarme?\nY vos, ¿cómo te llamas o cómo puedo llamarte?";
|
|
329
|
+
if (/portugu|brasil/i.test(l))
|
|
330
|
+
return "⚡ Acordei e pronto para trabalhar! APX online.\nAinda não tenho nome, como você gostaria de me chamar?\nE você, como posso te chamar?";
|
|
331
|
+
if (/franc|french/i.test(l))
|
|
332
|
+
return "⚡ Réveillé et prêt à travailler ! APX en ligne.\nJe n'ai pas encore de nom, comment souhaitez-vous m'appeler ?\nEt vous, comment puis-je vous appeler ?";
|
|
333
|
+
if (/deutsch|german/i.test(l))
|
|
334
|
+
return "⚡ Aufgewacht und bereit! APX ist online.\nIch habe noch keinen Namen — wie möchtest du mich nennen?\nUnd du, wie kann ich dich nennen?";
|
|
335
|
+
if (/ital/i.test(l))
|
|
336
|
+
return "⚡ Sveglio e pronto a lavorare! APX online.\nNon ho ancora un nome, come vorresti chiamarmi?\nE tu, come posso chiamarti?";
|
|
337
|
+
return "⚡ I'm awake and ready to go! APX is online.\nI don't have a name yet — what would you like to call me?\nAnd you, what's your name or what should I call you?";
|
|
332
338
|
}
|
package/src/daemon/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Express REST API for APX. See APC docs reference/apx-daemon.
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
4
5
|
import express from "express";
|
|
5
6
|
import { readApfMcps, writeApfMcps, SOURCES } from "./mcp-sources.js";
|
|
6
7
|
import { callEngine, ENGINE_IDS } from "./engines/index.js";
|
|
@@ -887,6 +888,167 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
887
888
|
res.json({ ok: true, project_only: cfg });
|
|
888
889
|
});
|
|
889
890
|
|
|
891
|
+
// ---- Run (bash execution) -----------------------------------------
|
|
892
|
+
// POST /run { cmd, cwd?, project?, timeout_ms? }
|
|
893
|
+
// Executes a shell command and returns stdout + stderr.
|
|
894
|
+
// `cwd` defaults to the project path (by id or first registered), or process.cwd().
|
|
895
|
+
app.post("/run", (req, res) => {
|
|
896
|
+
const { cmd, cwd: cwdOverride, project: projectRef, timeout_ms = 30000 } = req.body || {};
|
|
897
|
+
if (!cmd) return res.status(400).json({ error: "cmd required" });
|
|
898
|
+
|
|
899
|
+
// Resolve working directory
|
|
900
|
+
let cwd = cwdOverride || null;
|
|
901
|
+
if (!cwd) {
|
|
902
|
+
let entry = null;
|
|
903
|
+
if (projectRef !== undefined && projectRef !== null) {
|
|
904
|
+
const all = projects.list();
|
|
905
|
+
const ref = String(projectRef);
|
|
906
|
+
entry = all.find((p) => String(p.id) === ref || p.path === path.resolve(ref));
|
|
907
|
+
}
|
|
908
|
+
if (!entry) {
|
|
909
|
+
const all = projects.list().filter((p) => p.id !== 0);
|
|
910
|
+
entry = all[0] || projects.get(0);
|
|
911
|
+
}
|
|
912
|
+
cwd = entry ? entry.path : process.cwd();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const timeout = Math.min(Math.max(parseInt(timeout_ms, 10) || 30000, 1000), 300000);
|
|
916
|
+
|
|
917
|
+
execFile("bash", ["-c", cmd], { cwd, timeout, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
918
|
+
const exit_code = err?.code ?? (err ? 1 : 0);
|
|
919
|
+
res.json({
|
|
920
|
+
ok: !err || exit_code === 0,
|
|
921
|
+
exit_code,
|
|
922
|
+
stdout: stdout || "",
|
|
923
|
+
stderr: stderr || "",
|
|
924
|
+
cwd,
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// ---- Top-level memory shortcuts -----------------------------------
|
|
930
|
+
// GET /memory?project=<id> → reads default agent memory.md
|
|
931
|
+
// POST /memory?project=<id> { body } → writes it
|
|
932
|
+
//
|
|
933
|
+
// Targets the *first non-default agent* of the resolved project,
|
|
934
|
+
// or falls back to a bare memory.md in .apc/ root.
|
|
935
|
+
|
|
936
|
+
function resolveTopProject(query) {
|
|
937
|
+
const ref = query?.project;
|
|
938
|
+
if (ref !== undefined && ref !== null) {
|
|
939
|
+
const all = projects.list();
|
|
940
|
+
const r = String(ref);
|
|
941
|
+
return projects.get(all.find((p) => String(p.id) === r || p.path === path.resolve(r))?.id);
|
|
942
|
+
}
|
|
943
|
+
const all = projects.list().filter((p) => p.id !== 0);
|
|
944
|
+
return all.length ? projects.get(all[0].id) : projects.get(0);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function resolveMemoryPath(p) {
|
|
948
|
+
const agentsDir = path.join(p.path, ".apc", "agents");
|
|
949
|
+
if (fs.existsSync(agentsDir)) {
|
|
950
|
+
const slugs = fs.readdirSync(agentsDir).filter((s) => {
|
|
951
|
+
const mp = path.join(agentsDir, s, "memory.md");
|
|
952
|
+
return fs.statSync(path.join(agentsDir, s)).isDirectory();
|
|
953
|
+
});
|
|
954
|
+
if (slugs.length) return path.join(agentsDir, slugs[0], "memory.md");
|
|
955
|
+
}
|
|
956
|
+
return path.join(p.path, ".apc", "memory.md");
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
app.get("/memory", (req, res) => {
|
|
960
|
+
const p = resolveTopProject(req.query);
|
|
961
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
962
|
+
const memPath = resolveMemoryPath(p);
|
|
963
|
+
const body = fs.existsSync(memPath) ? fs.readFileSync(memPath, "utf8") : "";
|
|
964
|
+
res.json({ project_id: p.id, path: memPath, body });
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
app.post("/memory", (req, res) => {
|
|
968
|
+
const p = resolveTopProject(req.query);
|
|
969
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
970
|
+
const { body } = req.body || {};
|
|
971
|
+
if (typeof body !== "string") return res.status(400).json({ error: "body must be string" });
|
|
972
|
+
const memPath = resolveMemoryPath(p);
|
|
973
|
+
fs.mkdirSync(path.dirname(memPath), { recursive: true });
|
|
974
|
+
fs.writeFileSync(memPath, body);
|
|
975
|
+
try { projects.rebuild(p.id); } catch {}
|
|
976
|
+
res.json({ ok: true, path: memPath, bytes: Buffer.byteLength(body, "utf8") });
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// ---- Top-level file shortcuts -------------------------------------
|
|
980
|
+
// GET /files?path=<rel>&project=<id> → read file contents
|
|
981
|
+
// POST /files?project=<id> { path, content } → write file
|
|
982
|
+
|
|
983
|
+
app.get("/files", (req, res) => {
|
|
984
|
+
const p = resolveTopProject(req.query);
|
|
985
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
986
|
+
const rel = req.query.path;
|
|
987
|
+
if (!rel) {
|
|
988
|
+
// List top-level files of the project
|
|
989
|
+
try {
|
|
990
|
+
const entries = fs.readdirSync(p.path).map((name) => {
|
|
991
|
+
const full = path.join(p.path, name);
|
|
992
|
+
const stat = fs.statSync(full);
|
|
993
|
+
return { name, type: stat.isDirectory() ? "dir" : "file", size: stat.isDirectory() ? null : stat.size };
|
|
994
|
+
});
|
|
995
|
+
return res.json({ project_id: p.id, cwd: p.path, entries });
|
|
996
|
+
} catch (e) {
|
|
997
|
+
return res.status(500).json({ error: e.message });
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const abs = path.resolve(p.path, rel);
|
|
1001
|
+
if (!abs.startsWith(path.resolve(p.path))) return res.status(403).json({ error: "path escapes project root" });
|
|
1002
|
+
if (!fs.existsSync(abs)) return res.status(404).json({ error: "not found" });
|
|
1003
|
+
const stat = fs.statSync(abs);
|
|
1004
|
+
if (stat.isDirectory()) {
|
|
1005
|
+
const entries = fs.readdirSync(abs).map((name) => {
|
|
1006
|
+
const s = fs.statSync(path.join(abs, name));
|
|
1007
|
+
return { name, type: s.isDirectory() ? "dir" : "file", size: s.isDirectory() ? null : s.size };
|
|
1008
|
+
});
|
|
1009
|
+
return res.json({ project_id: p.id, path: rel, type: "dir", entries });
|
|
1010
|
+
}
|
|
1011
|
+
const content = fs.readFileSync(abs, "utf8");
|
|
1012
|
+
res.json({ project_id: p.id, path: rel, type: "file", size: stat.size, content });
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
app.post("/files", (req, res) => {
|
|
1016
|
+
const p = resolveTopProject(req.query);
|
|
1017
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
1018
|
+
const { path: rel, content } = req.body || {};
|
|
1019
|
+
if (!rel) return res.status(400).json({ error: "path required" });
|
|
1020
|
+
if (typeof content !== "string") return res.status(400).json({ error: "content must be string" });
|
|
1021
|
+
const abs = path.resolve(p.path, rel);
|
|
1022
|
+
if (!abs.startsWith(path.resolve(p.path))) return res.status(403).json({ error: "path escapes project root" });
|
|
1023
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
1024
|
+
fs.writeFileSync(abs, content);
|
|
1025
|
+
res.json({ ok: true, path: rel, bytes: Buffer.byteLength(content, "utf8") });
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
// ---- Top-level MCP shortcuts --------------------------------------
|
|
1029
|
+
// GET /mcp?project=<id> → list MCPs
|
|
1030
|
+
// POST /mcp/run { project?, name, tool, params } → call MCP tool
|
|
1031
|
+
|
|
1032
|
+
app.get("/mcp", (req, res) => {
|
|
1033
|
+
const p = resolveTopProject(req.query);
|
|
1034
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
1035
|
+
res.json(registries.for(p).list());
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
app.post("/mcp/run", async (req, res) => {
|
|
1039
|
+
const { project: projectRef, name, tool, params } = req.body || {};
|
|
1040
|
+
if (!name) return res.status(400).json({ error: "name required" });
|
|
1041
|
+
if (!tool) return res.status(400).json({ error: "tool required" });
|
|
1042
|
+
const p = resolveTopProject({ project: projectRef });
|
|
1043
|
+
if (!p) return res.status(404).json({ error: "no project registered" });
|
|
1044
|
+
try {
|
|
1045
|
+
const result = await registries.for(p).call(name, tool, params);
|
|
1046
|
+
res.json({ ok: true, result });
|
|
1047
|
+
} catch (e) {
|
|
1048
|
+
res.status(500).json({ error: e.message });
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
|
|
890
1052
|
// ---- Admin --------------------------------------------------------
|
|
891
1053
|
app.post("/admin/shutdown", (_req, res) => {
|
|
892
1054
|
res.json({ ok: true });
|