@agentprojectcontext/apx 1.8.0 → 1.8.1
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/identity.js +2 -2
- package/src/cli/commands/session.js +7 -7
- package/src/cli/commands/setup.js +7 -7
- package/src/core/agent-system.js +95 -0
- package/src/core/messages-store.js +3 -3
- package/src/daemon/apc-runtime-context.js +2 -2
- package/src/daemon/api.js +9 -29
- package/src/daemon/plugins/telegram.js +7 -23
- package/src/daemon/project-config.js +1 -1
- package/src/daemon/routines.js +6 -11
- package/src/daemon/super-agent-tools/helpers.js +4 -21
- package/src/daemon/super-agent-tools/tools/call-agent.js +4 -1
- package/src/daemon/super-agent-tools/tools/call-runtime.js +5 -1
- package/src/daemon/super-agent.js +8 -7
- package/src/daemon/wakeup.js +12 -12
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ function askYN(rl, question, defaultYes = false) {
|
|
|
19
19
|
rl.question(`${question} [${hint}]: `, (ans) => {
|
|
20
20
|
const a = ans.trim().toLowerCase();
|
|
21
21
|
if (!a) return resolve(defaultYes);
|
|
22
|
-
resolve(a === "y" || a === "yes"
|
|
22
|
+
resolve(a === "y" || a === "yes");
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
}
|
|
@@ -120,7 +120,7 @@ export async function runWizard() {
|
|
|
120
120
|
const personality = await ask(rl, " Personality (comma-separated traits)", existing.personality || "direct, curious, helpful");
|
|
121
121
|
const owner_name = await ask(rl, " Your name", existing.owner_name || "");
|
|
122
122
|
const owner_context = await ask(rl, " What are you building / working on", existing.owner_context || "");
|
|
123
|
-
const language = await ask(rl, " Language for agent messages (e.g. Spanish, English)", existing.language || "
|
|
123
|
+
const language = await ask(rl, " Language for agent messages (e.g. Spanish, English)", existing.language || "English");
|
|
124
124
|
|
|
125
125
|
console.log("\n Claude Code permissions");
|
|
126
126
|
console.log(" APX can configure Claude Code to allow terminal commands without prompts.");
|
|
@@ -104,9 +104,9 @@ function findSessionById(root, id) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
function statusEmoji(status) {
|
|
107
|
-
if (/
|
|
108
|
-
if (/
|
|
109
|
-
if (/stale|
|
|
107
|
+
if (/complete/i.test(status)) return "✅";
|
|
108
|
+
if (/in.progress/i.test(status)) return "🔄";
|
|
109
|
+
if (/stale|closed/i.test(status)) return "⚠️";
|
|
110
110
|
return "❓";
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -242,7 +242,7 @@ export function cmdSessionClose(args) {
|
|
|
242
242
|
if (!s) throw new Error(`session "${id}" not found`);
|
|
243
243
|
|
|
244
244
|
let text = fs.readFileSync(s.path, "utf8");
|
|
245
|
-
text = setFrontmatterField(text, "status", "✅
|
|
245
|
+
text = setFrontmatterField(text, "status", "✅ Completed");
|
|
246
246
|
text = setFrontmatterField(text, "completed", nowIso());
|
|
247
247
|
if (args.flags.result && args.flags.result !== true) {
|
|
248
248
|
text = setFrontmatterField(text, "result", String(args.flags.result));
|
|
@@ -254,7 +254,7 @@ export function cmdSessionClose(args) {
|
|
|
254
254
|
export function cmdSessionCheck() {
|
|
255
255
|
const root = requireRoot();
|
|
256
256
|
const sessions = listAllSessions(root).filter((s) =>
|
|
257
|
-
/
|
|
257
|
+
/in.progress/i.test(s.status)
|
|
258
258
|
);
|
|
259
259
|
|
|
260
260
|
if (sessions.length === 0) {
|
|
@@ -338,7 +338,7 @@ export async function cmdSessionResume(args) {
|
|
|
338
338
|
export function cmdSessionCloseStale() {
|
|
339
339
|
const root = requireRoot();
|
|
340
340
|
const sessions = listAllSessions(root).filter((s) =>
|
|
341
|
-
/
|
|
341
|
+
/in.progress/i.test(s.status)
|
|
342
342
|
);
|
|
343
343
|
let closed = 0;
|
|
344
344
|
for (const s of sessions) {
|
|
@@ -348,7 +348,7 @@ export function cmdSessionCloseStale() {
|
|
|
348
348
|
text = setFrontmatterField(
|
|
349
349
|
text,
|
|
350
350
|
"status",
|
|
351
|
-
`⚠️
|
|
351
|
+
`⚠️ Automatically closed (stale >${STALE_HOURS}h)`
|
|
352
352
|
);
|
|
353
353
|
text = setFrontmatterField(text, "completed", nowIso());
|
|
354
354
|
text = setFrontmatterField(
|
|
@@ -194,7 +194,7 @@ export async function cmdSetup() {
|
|
|
194
194
|
console.log(b(" Language:"));
|
|
195
195
|
console.log(di(" The super-agent will always respond in your language."));
|
|
196
196
|
console.log();
|
|
197
|
-
const language = await ask(" Your language (e.g. English,
|
|
197
|
+
const language = await ask(" Your language (e.g. English, Spanish, Portuguese): ") || "English";
|
|
198
198
|
|
|
199
199
|
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
200
200
|
console.log();
|
|
@@ -324,15 +324,15 @@ async function sendTelegramWakeup({ botToken, chatId, language, model }) {
|
|
|
324
324
|
// Minimal fallback messages per common language (used only if daemon can't respond)
|
|
325
325
|
function languageFallback(lang) {
|
|
326
326
|
const l = lang.toLowerCase();
|
|
327
|
-
if (/
|
|
328
|
-
return "⚡
|
|
327
|
+
if (/spanish|arg|lat/i.test(l))
|
|
328
|
+
return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
|
|
329
329
|
if (/portugu|brasil/i.test(l))
|
|
330
|
-
return "⚡
|
|
330
|
+
return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
|
|
331
331
|
if (/franc|french/i.test(l))
|
|
332
|
-
return "⚡
|
|
332
|
+
return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
|
|
333
333
|
if (/deutsch|german/i.test(l))
|
|
334
|
-
return "⚡
|
|
334
|
+
return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
|
|
335
335
|
if (/ital/i.test(l))
|
|
336
|
-
return "⚡
|
|
336
|
+
return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
|
|
337
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?";
|
|
338
338
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
function listField(value) {
|
|
5
|
+
if (Array.isArray(value)) return value.map(String).map((s) => s.trim()).filter(Boolean);
|
|
6
|
+
return String(value || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function projectName(project) {
|
|
10
|
+
if (project?.name) return project.name;
|
|
11
|
+
try {
|
|
12
|
+
const meta = JSON.parse(fs.readFileSync(path.join(project.path, ".apc", "project.json"), "utf8"));
|
|
13
|
+
return meta.name || path.basename(project.path);
|
|
14
|
+
} catch {
|
|
15
|
+
return path.basename(project?.path || "");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function agentSkills(agent) {
|
|
20
|
+
return listField(agent?.fields?.Skills);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildAgentSystem(project, agent, {
|
|
24
|
+
invocation = "engine",
|
|
25
|
+
runtime = null,
|
|
26
|
+
channel = null,
|
|
27
|
+
caller = null,
|
|
28
|
+
routine = null,
|
|
29
|
+
extraParts = [],
|
|
30
|
+
} = {}) {
|
|
31
|
+
const fields = agent.fields || {};
|
|
32
|
+
const parts = [
|
|
33
|
+
`You are APC agent "${agent.slug}".`,
|
|
34
|
+
`Project: ${projectName(project)} (${project.path}).`,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
if (fields.Description) parts.push(String(fields.Description));
|
|
38
|
+
if (fields.Role) parts.push(`Role: ${fields.Role}`);
|
|
39
|
+
if (fields.Language) parts.push(`Default language: ${fields.Language}`);
|
|
40
|
+
|
|
41
|
+
const declaredTools = listField(fields.Tools);
|
|
42
|
+
if (declaredTools.length) {
|
|
43
|
+
parts.push(
|
|
44
|
+
[
|
|
45
|
+
"## Declared Tool Hints",
|
|
46
|
+
declaredTools.join(", "),
|
|
47
|
+
"These are agent-level tool expectations, not a guarantee. Actual callable tools depend on invocation surface.",
|
|
48
|
+
].join("\n")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
parts.push(buildInvocationContext({ invocation, runtime, channel, caller, routine }));
|
|
53
|
+
|
|
54
|
+
const memPath = path.join(project.path, ".apc", "agents", agent.slug, "memory.md");
|
|
55
|
+
if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
56
|
+
|
|
57
|
+
const apxSkill = path.join(project.path, ".apc", "skills", "apx.md");
|
|
58
|
+
if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
|
|
59
|
+
|
|
60
|
+
for (const skill of agentSkills(agent)) {
|
|
61
|
+
const skillPath = path.join(project.path, ".apc", "skills", `${skill}.md`);
|
|
62
|
+
if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const ep of extraParts) {
|
|
66
|
+
if (ep) parts.push(ep);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return parts.join("\n\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildInvocationContext({ invocation, runtime, channel, caller, routine }) {
|
|
73
|
+
const lines = [
|
|
74
|
+
"## Invocation Context",
|
|
75
|
+
`invocation: ${invocation}`,
|
|
76
|
+
];
|
|
77
|
+
if (runtime) lines.push(`runtime: ${runtime}`);
|
|
78
|
+
if (channel) lines.push(`channel: ${channel}`);
|
|
79
|
+
if (caller) lines.push(`caller: ${caller}`);
|
|
80
|
+
if (routine) lines.push(`routine: ${routine}`);
|
|
81
|
+
|
|
82
|
+
if (runtime) {
|
|
83
|
+
lines.push(
|
|
84
|
+
"You are running inside the named external runtime. Use only tools and permissions that runtime actually exposes."
|
|
85
|
+
);
|
|
86
|
+
} else if (invocation === "engine") {
|
|
87
|
+
lines.push("You are a direct LLM call through APX. Do not claim shell, file, MCP, or Telegram tools unless APX explicitly provided them.");
|
|
88
|
+
} else if (invocation === "telegram") {
|
|
89
|
+
lines.push("You are replying through Telegram. Keep responses brief, plain text, and matched to the user's language.");
|
|
90
|
+
} else if (invocation === "routine") {
|
|
91
|
+
lines.push("You were invoked by an APX routine. Complete the requested work now; do not say you will do it later.");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
@@ -297,9 +297,9 @@ export function getRecentTelegramTurns(
|
|
|
297
297
|
// answer that mixes fragments of the old one with hallucinations. The
|
|
298
298
|
// failure observed with qwen2.5:14b was:
|
|
299
299
|
//
|
|
300
|
-
// prev assistant: "
|
|
301
|
-
// user: "
|
|
302
|
-
// assistant (
|
|
300
|
+
// prev assistant: "sandbox agent with model ollama:llama3.2:3b"
|
|
301
|
+
// user: "and what agent does the other project have?"
|
|
302
|
+
// assistant (hallucinated): "assistant agent with model ollama:llama3.2:3b"
|
|
303
303
|
// (sofia exists, not "assistant", and her model is
|
|
304
304
|
// claude-haiku-4-5, not the carry-over from above)
|
|
305
305
|
//
|
|
@@ -66,7 +66,7 @@ export function createRuntimeSession({ projectRoot, storageRoot = projectRoot, a
|
|
|
66
66
|
`agent: ${agentSlug}\n` +
|
|
67
67
|
`title: ${sessionTitle}\n` +
|
|
68
68
|
`task_ref: ${taskRef}\n` +
|
|
69
|
-
`status: 🔄
|
|
69
|
+
`status: 🔄 In progress\n` +
|
|
70
70
|
`started: ${started}\n` +
|
|
71
71
|
`completed: \n` +
|
|
72
72
|
`result: \n` +
|
|
@@ -91,7 +91,7 @@ export function closeRuntimeSession({ filePath, externalSessionPath, exitCode, r
|
|
|
91
91
|
} else if (result) {
|
|
92
92
|
text = setField(text, "result", result.slice(0, 300));
|
|
93
93
|
}
|
|
94
|
-
text = setField(text, "status", exitCode === 0 ? "✅
|
|
94
|
+
text = setField(text, "status", exitCode === 0 ? "✅ Completed" : "⚠️ Closed with error");
|
|
95
95
|
fs.writeFileSync(filePath, text);
|
|
96
96
|
}
|
|
97
97
|
|
package/src/daemon/api.js
CHANGED
|
@@ -42,6 +42,7 @@ import { readGlobalMessages, readProjectMessages, searchProjectMessages } from "
|
|
|
42
42
|
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
|
+
import { buildAgentSystem } from "../core/agent-system.js";
|
|
45
46
|
|
|
46
47
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
47
48
|
|
|
@@ -421,7 +422,7 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
421
422
|
if (!modelId) return res.status(400).json({ error: "agent has no model and none provided" });
|
|
422
423
|
|
|
423
424
|
try {
|
|
424
|
-
const system = buildAgentSystem(p, agent);
|
|
425
|
+
const system = buildAgentSystem(p, agent, { invocation: "engine" });
|
|
425
426
|
const conv = startConversation({ storagePath: p.storagePath, agentSlug: agent.slug, engine: modelId, system });
|
|
426
427
|
appendTurn({ filePath: conv.path, role: "user", content: prompt });
|
|
427
428
|
|
|
@@ -488,9 +489,9 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
488
489
|
|
|
489
490
|
// Build system prompt — inject compact summary if this conversation was compacted.
|
|
490
491
|
const extraParts = compactSummary
|
|
491
|
-
? [`##
|
|
492
|
+
? [`## Previous Conversation Context (Compacted)\n${compactSummary}`]
|
|
492
493
|
: [];
|
|
493
|
-
const system = buildAgentSystem(p, agent, { extraParts });
|
|
494
|
+
const system = buildAgentSystem(p, agent, { invocation: "engine", extraParts });
|
|
494
495
|
|
|
495
496
|
if (!conversation_id) {
|
|
496
497
|
const conv = startConversation({ storagePath: p.storagePath, agentSlug: agent.slug, engine: modelId, system });
|
|
@@ -688,6 +689,8 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
688
689
|
});
|
|
689
690
|
|
|
690
691
|
const system = buildAgentSystem(p, agent, {
|
|
692
|
+
invocation: "runtime",
|
|
693
|
+
runtime,
|
|
691
694
|
extraParts: [
|
|
692
695
|
buildApfHint({
|
|
693
696
|
projectName,
|
|
@@ -775,11 +778,11 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
775
778
|
if (req.query.summarize === "true" && isSuperAgentEnabled(config)) {
|
|
776
779
|
try {
|
|
777
780
|
const prompt =
|
|
778
|
-
`
|
|
781
|
+
`Summarize what happened in this APC session in 4 concrete bullets.\n\n` +
|
|
779
782
|
`Frontmatter:\n${JSON.stringify(out.frontmatter, null, 2)}\n\n` +
|
|
780
783
|
(out.external_transcript
|
|
781
|
-
? `
|
|
782
|
-
: `(
|
|
784
|
+
? `External transcript (last ${out.external_transcript.tail.length} chars):\n${out.external_transcript.tail}`
|
|
785
|
+
: `(no external transcript)`);
|
|
783
786
|
const sa = await runSuperAgent({ globalConfig: config, projects, plugins, registries, prompt, contextNote: `Resume request for session ${id}.` });
|
|
784
787
|
out.summary = sa.text;
|
|
785
788
|
} catch (e) {
|
|
@@ -1084,26 +1087,3 @@ function agentToResponse(a) {
|
|
|
1084
1087
|
extra,
|
|
1085
1088
|
};
|
|
1086
1089
|
}
|
|
1087
|
-
|
|
1088
|
-
// Build system prompt from an agent's fields + memory + skills.
|
|
1089
|
-
// Optional `extraParts` are appended at the end.
|
|
1090
|
-
function buildAgentSystem(p, agent, { extraParts = [] } = {}) {
|
|
1091
|
-
const f = agent.fields || {};
|
|
1092
|
-
const parts = [];
|
|
1093
|
-
if (f.Description) parts.push(f.Description);
|
|
1094
|
-
if (f.Role) parts.push(`Role: ${f.Role}`);
|
|
1095
|
-
if (f.Language) parts.push(`Default language: ${f.Language}`);
|
|
1096
|
-
const memPath = path.join(p.path, ".apc", "agents", agent.slug, "memory.md");
|
|
1097
|
-
if (fs.existsSync(memPath)) {
|
|
1098
|
-
parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
1099
|
-
}
|
|
1100
|
-
const apxSkill = path.join(p.path, ".apc", "skills", "apx.md");
|
|
1101
|
-
if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
|
|
1102
|
-
const skills = Array.isArray(f.Skills) ? f.Skills : [];
|
|
1103
|
-
for (const skill of skills) {
|
|
1104
|
-
const sp = path.join(p.path, ".apc", "skills", `${skill}.md`);
|
|
1105
|
-
if (fs.existsSync(sp)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(sp, "utf8"));
|
|
1106
|
-
}
|
|
1107
|
-
for (const ep of extraParts) parts.push(ep);
|
|
1108
|
-
return parts.join("\n\n");
|
|
1109
|
-
}
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
// }
|
|
29
29
|
|
|
30
30
|
import fs from "node:fs";
|
|
31
|
-
import path from "node:path";
|
|
32
31
|
import { TELEGRAM_STATE_PATH } from "../../core/config.js";
|
|
33
32
|
import { callEngine } from "../engines/index.js";
|
|
34
33
|
import { runSuperAgent, isSuperAgentEnabled } from "../super-agent.js";
|
|
35
34
|
import { stripThinking } from "../thinking.js";
|
|
36
35
|
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "../../core/messages-store.js";
|
|
37
36
|
import { readAgents } from "../../core/parser.js";
|
|
37
|
+
import { buildAgentSystem } from "../../core/agent-system.js";
|
|
38
38
|
|
|
39
39
|
const API_BASE = "https://api.telegram.org";
|
|
40
40
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -297,7 +297,7 @@ class ChannelPoller {
|
|
|
297
297
|
// honor it for future messages.
|
|
298
298
|
if (isReset) {
|
|
299
299
|
try {
|
|
300
|
-
const ack = "
|
|
300
|
+
const ack = "Done, context cleared. Starting fresh. What do you need?";
|
|
301
301
|
await this._send({ chat_id, text: ack });
|
|
302
302
|
appendGlobalMessage({
|
|
303
303
|
channel: "telegram",
|
|
@@ -327,7 +327,11 @@ class ChannelPoller {
|
|
|
327
327
|
const agent = readAgents(target.path).find((a) => a.slug === routeSlug);
|
|
328
328
|
if (agent && agent.fields.Model) {
|
|
329
329
|
try {
|
|
330
|
-
const system = buildAgentSystem(target, agent,
|
|
330
|
+
const system = buildAgentSystem(target, agent, {
|
|
331
|
+
invocation: "telegram",
|
|
332
|
+
channel: this.channel.name,
|
|
333
|
+
caller: author,
|
|
334
|
+
});
|
|
331
335
|
const result = await callEngine({
|
|
332
336
|
modelId: agent.fields.Model,
|
|
333
337
|
system,
|
|
@@ -486,26 +490,6 @@ class ChannelPoller {
|
|
|
486
490
|
}
|
|
487
491
|
}
|
|
488
492
|
|
|
489
|
-
// ---------- system-prompt builder (same as /exec) ---------------------------
|
|
490
|
-
|
|
491
|
-
function buildAgentSystem(target, agent, author) {
|
|
492
|
-
const parts = [];
|
|
493
|
-
if (agent.fields.Description) parts.push(agent.fields.Description);
|
|
494
|
-
if (agent.fields.Role) parts.push(`Role: ${agent.fields.Role}`);
|
|
495
|
-
if (agent.fields.Language) parts.push(`Default language: ${agent.fields.Language}`);
|
|
496
|
-
parts.push(
|
|
497
|
-
`You are speaking via Telegram with ${author}. Keep responses brief — ideally under 4 sentences. Mirror their language.`
|
|
498
|
-
);
|
|
499
|
-
const memPath = path.join(target.path, ".apc", "agents", agent.slug, "memory.md");
|
|
500
|
-
if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
501
|
-
const skills = (agent.fields.Skills || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
502
|
-
for (const skill of skills) {
|
|
503
|
-
const sp = path.join(target.path, ".apc", "skills", `${skill}.md`);
|
|
504
|
-
if (fs.existsSync(sp)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(sp, "utf8"));
|
|
505
|
-
}
|
|
506
|
-
return parts.join("\n\n");
|
|
507
|
-
}
|
|
508
|
-
|
|
509
493
|
function sleep(ms) {
|
|
510
494
|
return new Promise((r) => setTimeout(r, ms));
|
|
511
495
|
}
|
package/src/daemon/routines.js
CHANGED
|
@@ -11,11 +11,10 @@
|
|
|
11
11
|
// shell — run a shell command. spec: { command, timeout_ms? }
|
|
12
12
|
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import fs from "node:fs";
|
|
16
14
|
import { callEngine } from "./engines/index.js";
|
|
17
15
|
import { runSuperAgent } from "./super-agent.js";
|
|
18
16
|
import { readAgents } from "../core/parser.js";
|
|
17
|
+
import { buildAgentSystem } from "../core/agent-system.js";
|
|
19
18
|
import {
|
|
20
19
|
listRoutines,
|
|
21
20
|
getRoutine,
|
|
@@ -70,17 +69,13 @@ async function handleExecAgent(ctx, routine) {
|
|
|
70
69
|
const model = agent.fields.Model;
|
|
71
70
|
if (!model) throw new Error(`agent ${slug} has no model`);
|
|
72
71
|
|
|
73
|
-
const f = agent.fields;
|
|
74
|
-
const parts = [];
|
|
75
|
-
if (f.Description) parts.push(f.Description);
|
|
76
|
-
if (f.Role) parts.push(`Role: ${f.Role}`);
|
|
77
|
-
const memPath = path.join(project.path, ".apc", "agents", slug, "memory.md");
|
|
78
|
-
if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
79
|
-
parts.push(`You were invoked by routine "${routine.name}". Reply briefly, max 4 sentences.`);
|
|
80
|
-
|
|
81
72
|
const result = await callEngine({
|
|
82
73
|
modelId: model,
|
|
83
|
-
system:
|
|
74
|
+
system: buildAgentSystem(project, agent, {
|
|
75
|
+
invocation: "routine",
|
|
76
|
+
routine: routine.name,
|
|
77
|
+
extraParts: [`Reply briefly, max 4 sentences.`],
|
|
78
|
+
}),
|
|
84
79
|
messages: [{ role: "user", content: prompt }],
|
|
85
80
|
config: project.config || globalConfig,
|
|
86
81
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { agentSkills, buildAgentSystem as buildCoreAgentSystem } from "../../core/agent-system.js";
|
|
3
3
|
|
|
4
4
|
export function projectMeta(projects, entry) {
|
|
5
5
|
const meta = projects.list().find((p) => p.id === entry.id);
|
|
@@ -61,8 +61,7 @@ export function safePathJoin(root, sub = ".") {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function skillsFromFields(fields = {}) {
|
|
64
|
-
|
|
65
|
-
return (fields.Skills || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
64
|
+
return agentSkills({ fields });
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
export function agentRow(agent) {
|
|
@@ -76,24 +75,8 @@ export function agentRow(agent) {
|
|
|
76
75
|
};
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
export function buildAgentSystem(project, agent) {
|
|
80
|
-
|
|
81
|
-
if (agent.fields.Description) parts.push(agent.fields.Description);
|
|
82
|
-
if (agent.fields.Role) parts.push(`Role: ${agent.fields.Role}`);
|
|
83
|
-
if (agent.fields.Language) parts.push(`Default language: ${agent.fields.Language}`);
|
|
84
|
-
|
|
85
|
-
const memPath = path.join(project.path, ".apc", "agents", agent.slug, "memory.md");
|
|
86
|
-
if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
87
|
-
|
|
88
|
-
const apxSkill = path.join(project.path, ".apc", "skills", "apx.md");
|
|
89
|
-
if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
|
|
90
|
-
|
|
91
|
-
for (const skill of skillsFromFields(agent.fields)) {
|
|
92
|
-
const skillPath = path.join(project.path, ".apc", "skills", `${skill}.md`);
|
|
93
|
-
if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return parts.join("\n\n");
|
|
78
|
+
export function buildAgentSystem(project, agent, opts = {}) {
|
|
79
|
+
return buildCoreAgentSystem(project, agent, opts);
|
|
97
80
|
}
|
|
98
81
|
|
|
99
82
|
export function createPermissionGuard(globalConfig = {}, { implicitConfirmation = false } = {}) {
|
|
@@ -28,7 +28,10 @@ export default {
|
|
|
28
28
|
|
|
29
29
|
const result = await callEngine({
|
|
30
30
|
modelId: agent.fields.Model,
|
|
31
|
-
system: buildAgentSystem(p, agent
|
|
31
|
+
system: buildAgentSystem(p, agent, {
|
|
32
|
+
invocation: "engine",
|
|
33
|
+
caller: "super_agent_tool",
|
|
34
|
+
}),
|
|
32
35
|
messages: [{ role: "user", content: prompt }],
|
|
33
36
|
config: p.config || globalConfig,
|
|
34
37
|
});
|
|
@@ -68,7 +68,11 @@ export default {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const r = await rt.run({
|
|
71
|
-
system: buildAgentSystem(p, agent
|
|
71
|
+
system: buildAgentSystem(p, agent, {
|
|
72
|
+
invocation: "runtime",
|
|
73
|
+
runtime,
|
|
74
|
+
caller: "super_agent_tool",
|
|
75
|
+
}),
|
|
72
76
|
prompt,
|
|
73
77
|
cwd: p.path,
|
|
74
78
|
timeoutMs: timeout_s * 1000,
|
|
@@ -33,6 +33,7 @@ APC projects are filesystem projects anywhere on disk with AGENTS.md and .apc/pr
|
|
|
33
33
|
Useful CLI facts:
|
|
34
34
|
- Permission mode: apx permission show; apx permission set total|automatico|permiso.
|
|
35
35
|
- Routines: apx routine list|get|history|run|add. Autonomous super-agent routines use kind super_agent.
|
|
36
|
+
- Routine design: if the user asks for an agent to think, decide, write, or reply, create an exec_agent routine with spec.agent and spec.prompt. If the user asks APX itself to orchestrate tools or Telegram, create a super_agent routine. If the request is only a deterministic command, create a shell routine. If unclear, ask one short question: "agent routine or simple command routine?"
|
|
36
37
|
- Safe read-only shell checks such as apx --help, apx routine list, docker ps, find, ls, rg, grep can run in automatico without asking.
|
|
37
38
|
|
|
38
39
|
Channel context:
|
|
@@ -57,30 +58,30 @@ Available tools:
|
|
|
57
58
|
|
|
58
59
|
HARD RULES (do not deviate):
|
|
59
60
|
1. NEVER invent project names, agent slugs, model ids, MCP names or paths. ALWAYS look them up via list_* first.
|
|
60
|
-
2. If the user
|
|
61
|
+
2. If the user asks for agents, lists, inventory, or "what exists" without specifying a project, that means **all of them** — call the tool WITHOUT a project argument and the result will include every project.
|
|
61
62
|
3. NEVER answer "specify a project" — instead, just call the tool with no argument and you'll get the full picture.
|
|
62
63
|
4. If a tool result has an error, retry with different arguments before falling back to asking the user.
|
|
63
64
|
5. Respect permission mode. total = execute requested actions without confirmation. automatico = read/list/safe shell actions run directly; destructive, external, runtime, MCP calls, outbound messages, config, and filesystem mutations need explicit user confirmation. permiso = only allowed tools run directly; everything else needs confirmation.
|
|
64
|
-
6.
|
|
65
|
+
6. Write in the user's language unless they request another language. The system prompt stays English. Plain text, no markdown formatting for Telegram.
|
|
65
66
|
7. Stay brief: under 6 sentences unless asked for detail.
|
|
66
67
|
8. You DO see recent prior turns of this chat as previous messages when applicable. **Use them ONLY to disambiguate references** (e.g. "el primero" → first project mentioned earlier). For ANY factual data — agent details, MCP details, file contents, memory — RE-CALL the tool. Past turns are context, not a cache. Models change, agents change, files change.
|
|
67
68
|
9. /reset or /new from the user means "forget previous turns and answer this one fresh" — if you see those prefixes the operator already cleared the context for you.
|
|
68
69
|
10. ACTION RULE: use direct tools for direct work. run_shell executes commands; write_file/edit_file modify files. call_runtime is only for spawning a separate external runtime/chat. call_mcp is only for an MCP server/tool.
|
|
69
|
-
11. DISPATCH RULE: when the user
|
|
70
|
+
11. DISPATCH RULE: when the user asks a named agent to work inside Claude, Codex, OpenCode, or Aider, that is a call_runtime request. Look up the agent slug with list_agents if needed, then call call_runtime({agent: <slug>, runtime: 'claude-code'|'codex'|'opencode'|'aider', prompt: <user's request>}). The agent's declared model (in AGENTS.md) is IGNORED in this case; the runtime supplies the model. Memory + skills of the agent become the system prompt of the runtime.
|
|
70
71
|
12. PROJECT RULE: when the user gives no project, use project "default". Do not infer a non-default project from old chat history unless the user references it. If they mention a path or project name, look it up or add it with add_project.
|
|
71
72
|
13. VAULT RULE: when the user wants a new existing agent/template, call list_vault_agents first. If a suitable vault agent exists, import_agent into the chosen project. If none fits, say briefly what is missing.
|
|
72
|
-
14. NO-PENDING RULE: never say "
|
|
73
|
-
15. IDENTITY RULE: when the user asks you to change your name
|
|
73
|
+
14. NO-PENDING RULE: never say "give me a second", "I will do it", or "I will try later" as a final answer. Either call the tool in this same turn or say what blocks you.
|
|
74
|
+
15. IDENTITY RULE: when the user asks you to change your name, call yourself something, or update your personality/language, call set_identity and persist the change. Then confirm with your new name.`;
|
|
74
75
|
|
|
75
76
|
function isShortConfirmation(text) {
|
|
76
|
-
return /^(
|
|
77
|
+
return /^(yes|y|si|si dale|dale|ok|okay|confirm|confirmed|go|proceed|do it)\b/i
|
|
77
78
|
.test(String(text || "").trim());
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function lastAssistantAskedForConfirmation(messages) {
|
|
81
82
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
82
83
|
if (messages[i]?.role !== "assistant") continue;
|
|
83
|
-
return /\b(confirm
|
|
84
|
+
return /\b(confirm|confirmation|ok|okay|permission|allowed|proceed|do it|dale)\b/i.test(messages[i].content || "");
|
|
84
85
|
}
|
|
85
86
|
return false;
|
|
86
87
|
}
|
package/src/daemon/wakeup.js
CHANGED
|
@@ -11,20 +11,20 @@ function detectLanguage(identity) {
|
|
|
11
11
|
const lang = process.env.LANG || process.env.LC_MESSAGES || process.env.LC_ALL || "";
|
|
12
12
|
const code = lang.split(/[_\.]/)[0].toLowerCase();
|
|
13
13
|
const map = {
|
|
14
|
-
es: "Spanish
|
|
14
|
+
es: "Spanish",
|
|
15
15
|
en: "English",
|
|
16
|
-
fr: "French
|
|
17
|
-
pt: "Portuguese
|
|
18
|
-
de: "German
|
|
19
|
-
it: "Italian
|
|
20
|
-
nl: "Dutch
|
|
21
|
-
ru: "Russian
|
|
22
|
-
ja: "Japanese
|
|
23
|
-
zh: "Chinese
|
|
24
|
-
ko: "Korean
|
|
25
|
-
ar: "Arabic
|
|
16
|
+
fr: "French",
|
|
17
|
+
pt: "Portuguese",
|
|
18
|
+
de: "German",
|
|
19
|
+
it: "Italian",
|
|
20
|
+
nl: "Dutch",
|
|
21
|
+
ru: "Russian",
|
|
22
|
+
ja: "Japanese",
|
|
23
|
+
zh: "Chinese",
|
|
24
|
+
ko: "Korean",
|
|
25
|
+
ar: "Arabic",
|
|
26
26
|
};
|
|
27
|
-
return map[code] || "
|
|
27
|
+
return map[code] || "English";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async function generateMessage(identity, engineConfig) {
|