@agentprojectcontext/apx 1.7.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/README.md +4 -0
- package/package.json +1 -1
- package/src/cli/commands/config.js +23 -0
- package/src/cli/commands/identity.js +2 -2
- package/src/cli/commands/messages.js +45 -0
- package/src/cli/commands/routine.js +27 -2
- package/src/cli/commands/session.js +7 -7
- package/src/cli/commands/setup.js +9 -9
- package/src/cli/index.js +969 -3
- package/src/core/agent-system.js +95 -0
- package/src/core/apc-context-skill.md +3 -0
- package/src/core/apx-skill.md +30 -0
- package/src/core/messages-store.js +97 -23
- package/src/core/routines-store.js +3 -1
- package/src/daemon/apc-runtime-context.js +2 -2
- package/src/daemon/api.js +12 -32
- package/src/daemon/index.js +1 -0
- package/src/daemon/plugins/telegram.js +39 -25
- package/src/daemon/project-config.js +1 -1
- package/src/daemon/routines.js +70 -13
- package/src/daemon/super-agent-tools/helpers.js +8 -24
- package/src/daemon/super-agent-tools/index.js +5 -1
- 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-tools/tools/run-shell.js +23 -1
- package/src/daemon/super-agent-tools/tools/search-messages.js +2 -0
- package/src/daemon/super-agent-tools/tools/set-permission-mode.js +32 -0
- package/src/daemon/super-agent-tools/tools/tail-messages.js +2 -0
- package/src/daemon/super-agent.js +28 -5
- package/src/daemon/wakeup.js +12 -12
|
@@ -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
|
+
}
|
|
@@ -95,6 +95,9 @@ APX can provide a local daemon, MCP management, Telegram bridge, routines, and r
|
|
|
95
95
|
across Codex, Claude Code, OpenCode, Aider, or direct LLM engines. Those are APX runtime features,
|
|
96
96
|
not APC portable-core requirements.
|
|
97
97
|
|
|
98
|
+
The APX super-agent uses `~/.apx/projects/default` for system-level work when no project is named.
|
|
99
|
+
APX routines can run heartbeat, shell, Telegram, project agent, or super-agent tasks on a schedule.
|
|
100
|
+
|
|
98
101
|
APX runtime state belongs outside the repository:
|
|
99
102
|
|
|
100
103
|
```text
|
package/src/core/apx-skill.md
CHANGED
|
@@ -5,6 +5,9 @@ The daemon runs on `127.0.0.1:7430` and auto-starts on first `apx` call.
|
|
|
5
5
|
APX reads APC project context from `.apc/`, but APX runtime state belongs outside the repository
|
|
6
6
|
under `~/.apx/projects/<project-id>/`.
|
|
7
7
|
|
|
8
|
+
The APX super-agent has an always-available default workspace at `~/.apx/projects/default`.
|
|
9
|
+
When no project is named, system-level work belongs there.
|
|
10
|
+
|
|
8
11
|
---
|
|
9
12
|
|
|
10
13
|
## Coordinate with other agents
|
|
@@ -73,10 +76,37 @@ apx session check # exits 1 if session already ac
|
|
|
73
76
|
|
|
74
77
|
```bash
|
|
75
78
|
apx messages tail # last 50 messages, all channels
|
|
79
|
+
apx messages chat --channel telegram -n 20 # chat view with user/agent/system type
|
|
76
80
|
apx messages tail --channel runtime # only agent invocations
|
|
77
81
|
apx messages tail --agent <slug> -n 20
|
|
78
82
|
```
|
|
79
83
|
|
|
84
|
+
Message rows expose `type` (`user`, `agent`, `tool`, `system`) and `actor_id`; use `messages chat`
|
|
85
|
+
when you need a readable transcript.
|
|
86
|
+
|
|
87
|
+
## Super-agent permissions
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
apx permission show
|
|
91
|
+
apx permission set automatico # total | automatico | permiso
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`automatico` runs read/list/safe shell checks directly and asks before destructive shell, MCP,
|
|
95
|
+
runtime, outbound, config, or filesystem mutation actions.
|
|
96
|
+
|
|
97
|
+
## Routines
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
apx routine list
|
|
101
|
+
apx routine get <name>
|
|
102
|
+
apx routine history <name>
|
|
103
|
+
apx routine add clima --kind super_agent --schedule every:5m \
|
|
104
|
+
--permission-mode total \
|
|
105
|
+
--spec '{"prompt":"Check weather and send Telegram update."}'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Routine kinds: `heartbeat`, `exec_agent`, `super_agent`, `telegram`, `shell`.
|
|
109
|
+
|
|
80
110
|
## APC_RESULT
|
|
81
111
|
|
|
82
112
|
Print on the last meaningful line of your output so the invoker captures it:
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// ~/.apx/messages/<channel>/YYYY-MM-DD.jsonl
|
|
8
8
|
//
|
|
9
9
|
// Each line:
|
|
10
|
-
// {"ts":"...","channel":"...","direction":"in|out","author":"...","body":"...","meta":{...}}
|
|
10
|
+
// {"ts":"...","channel":"...","direction":"in|out","type":"user|agent|tool|system","author":"...","actor_id":"...","body":"...","meta":{...}}
|
|
11
11
|
//
|
|
12
12
|
// Why JSONL: same shape as Claude Code's ~/.claude/projects/<id>.jsonl.
|
|
13
13
|
// Streamable, structured, no markdown parsing fragility.
|
|
@@ -32,24 +32,61 @@ function dayPathMd(projectRoot, ts) {
|
|
|
32
32
|
return path.join(projectRoot, "messages", `${day}.md`);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
const VALID_MESSAGE_TYPES = new Set(["user", "agent", "tool", "system"]);
|
|
36
|
+
|
|
37
|
+
function normalizeMessageType(type) {
|
|
38
|
+
return typeof type === "string" && VALID_MESSAGE_TYPES.has(type) ? type : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function inferMessageType({ type, channel, direction, author, agent_slug, meta = {} } = {}) {
|
|
42
|
+
const explicit = normalizeMessageType(type) || normalizeMessageType(meta.type) || normalizeMessageType(meta.actor_type);
|
|
43
|
+
if (explicit) return explicit;
|
|
44
|
+
if (channel === "a2a") return "agent";
|
|
45
|
+
if (meta.tool || meta.tool_name) return "tool";
|
|
46
|
+
if (author === "system") return "system";
|
|
47
|
+
if (agent_slug && author && author !== "user" && !String(author).startsWith("@")) return "agent";
|
|
48
|
+
if (direction === "in" && (author === "user" || String(author || "").startsWith("@"))) return "user";
|
|
49
|
+
if (direction === "out") return "agent";
|
|
50
|
+
return direction === "in" ? "user" : "agent";
|
|
51
|
+
}
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
function inferActorId({ type, actor_id, author, agent_slug, meta = {} } = {}) {
|
|
54
|
+
if (actor_id) return actor_id;
|
|
55
|
+
if (meta.actor_id) return meta.actor_id;
|
|
56
|
+
if (type === "user") return meta.user_id ? String(meta.user_id) : (author || "user");
|
|
57
|
+
if (type === "agent") return agent_slug || author || "agent";
|
|
58
|
+
if (type === "tool") return meta.tool || meta.tool_name || author || "tool";
|
|
59
|
+
if (type === "system") return author || "system";
|
|
60
|
+
return author || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function messageMeta({ type, actor_id, agent_slug, session_id, external_id, meta = {} }) {
|
|
64
|
+
return {
|
|
65
|
+
...meta,
|
|
66
|
+
type,
|
|
67
|
+
...(actor_id ? { actor_id } : {}),
|
|
42
68
|
...(agent_slug ? { agent: agent_slug } : {}),
|
|
43
69
|
...(session_id ? { session_id } : {}),
|
|
44
70
|
...(external_id ? { external_id } : {}),
|
|
45
|
-
...meta,
|
|
46
71
|
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function appendMessageToFs({ projectRoot, channel, direction, type, actor_id, author, body, meta = {}, ts, agent_slug, session_id, external_id }) {
|
|
75
|
+
ts = ts || nowIso();
|
|
76
|
+
const file = dayPathJsonl(projectRoot, ts);
|
|
77
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
78
|
+
|
|
79
|
+
const msgType = inferMessageType({ type, channel, direction, author, agent_slug, meta });
|
|
80
|
+
const msgActorId = inferActorId({ type: msgType, actor_id, author, agent_slug, meta });
|
|
81
|
+
const fullMeta = messageMeta({ type: msgType, actor_id: msgActorId, agent_slug, session_id, external_id, meta });
|
|
47
82
|
|
|
48
83
|
const record = {
|
|
49
84
|
ts,
|
|
50
85
|
channel,
|
|
51
86
|
direction,
|
|
87
|
+
type: msgType,
|
|
52
88
|
author: author || null,
|
|
89
|
+
...(msgActorId ? { actor_id: msgActorId } : {}),
|
|
53
90
|
body: body || "",
|
|
54
91
|
...(Object.keys(fullMeta).length ? { meta: fullMeta } : {}),
|
|
55
92
|
};
|
|
@@ -65,6 +102,16 @@ export function insertMessageRow(db, m) {
|
|
|
65
102
|
const a = db.prepare("SELECT id FROM agents WHERE slug = ?").get(m.agent_slug);
|
|
66
103
|
if (a) agent_id = a.id;
|
|
67
104
|
}
|
|
105
|
+
const type = inferMessageType(m);
|
|
106
|
+
const actor_id = inferActorId({ ...m, type });
|
|
107
|
+
const meta = messageMeta({
|
|
108
|
+
type,
|
|
109
|
+
actor_id,
|
|
110
|
+
agent_slug: m.agent_slug,
|
|
111
|
+
session_id: m.session_id,
|
|
112
|
+
external_id: m.external_id,
|
|
113
|
+
meta: m.meta || {},
|
|
114
|
+
});
|
|
68
115
|
return db
|
|
69
116
|
.prepare(
|
|
70
117
|
`INSERT INTO messages (agent_id, session_id, channel, direction, external_id, author, body, meta_json, ts)
|
|
@@ -78,17 +125,19 @@ export function insertMessageRow(db, m) {
|
|
|
78
125
|
m.external_id || null,
|
|
79
126
|
m.author || null,
|
|
80
127
|
m.body || "",
|
|
81
|
-
JSON.stringify(
|
|
128
|
+
JSON.stringify(meta),
|
|
82
129
|
m.ts
|
|
83
130
|
);
|
|
84
131
|
}
|
|
85
132
|
|
|
86
133
|
// Single entry point used by everywhere the daemon writes a message.
|
|
87
|
-
export function appendMessage({ projectRoot, db, channel, direction, author, body, meta = {}, ts, agent_slug, session_id, external_id }) {
|
|
134
|
+
export function appendMessage({ projectRoot, db, channel, direction, type, actor_id, author, body, meta = {}, ts, agent_slug, session_id, external_id }) {
|
|
88
135
|
const written = appendMessageToFs({
|
|
89
136
|
projectRoot,
|
|
90
137
|
channel,
|
|
91
138
|
direction,
|
|
139
|
+
type,
|
|
140
|
+
actor_id,
|
|
92
141
|
author,
|
|
93
142
|
body,
|
|
94
143
|
meta,
|
|
@@ -100,6 +149,8 @@ export function appendMessage({ projectRoot, db, channel, direction, author, bod
|
|
|
100
149
|
insertMessageRow(db, {
|
|
101
150
|
channel,
|
|
102
151
|
direction,
|
|
152
|
+
type,
|
|
153
|
+
actor_id,
|
|
103
154
|
author,
|
|
104
155
|
body,
|
|
105
156
|
meta,
|
|
@@ -121,15 +172,33 @@ export function parseDayJsonl(text) {
|
|
|
121
172
|
try { obj = JSON.parse(trimmed); } catch { continue; }
|
|
122
173
|
if (!obj || typeof obj !== "object") continue;
|
|
123
174
|
const meta = obj.meta || {};
|
|
175
|
+
const agent_slug = obj.agent_slug || meta.agent;
|
|
176
|
+
const type = inferMessageType({
|
|
177
|
+
type: obj.type,
|
|
178
|
+
channel: obj.channel,
|
|
179
|
+
direction: obj.direction,
|
|
180
|
+
author: obj.author,
|
|
181
|
+
agent_slug,
|
|
182
|
+
meta,
|
|
183
|
+
});
|
|
184
|
+
const actor_id = inferActorId({
|
|
185
|
+
type,
|
|
186
|
+
actor_id: obj.actor_id,
|
|
187
|
+
author: obj.author,
|
|
188
|
+
agent_slug,
|
|
189
|
+
meta,
|
|
190
|
+
});
|
|
124
191
|
out.push({
|
|
125
192
|
ts: obj.ts,
|
|
126
193
|
channel: obj.channel,
|
|
127
194
|
direction: obj.direction,
|
|
195
|
+
type,
|
|
128
196
|
author: obj.author,
|
|
197
|
+
actor_id,
|
|
129
198
|
body: obj.body || "",
|
|
130
199
|
meta,
|
|
131
|
-
agent_slug
|
|
132
|
-
session_id: typeof meta.apc_session_id === "number" ? meta.apc_session_id : null,
|
|
200
|
+
agent_slug,
|
|
201
|
+
session_id: meta.session_id ?? (typeof meta.apc_session_id === "number" ? meta.apc_session_id : null),
|
|
133
202
|
external_id: meta.external_id,
|
|
134
203
|
});
|
|
135
204
|
}
|
|
@@ -156,10 +225,15 @@ export function parseDayFile(text) {
|
|
|
156
225
|
body = body.replace(metaMatch[0], "");
|
|
157
226
|
}
|
|
158
227
|
body = body.trim();
|
|
228
|
+
const agent_slug = meta.agent;
|
|
229
|
+
const type = inferMessageType({ channel, direction, author, agent_slug, meta });
|
|
230
|
+
const actor_id = inferActorId({ type, author, agent_slug, meta });
|
|
159
231
|
out.push({
|
|
160
232
|
ts, channel, direction, author, body, meta,
|
|
161
|
-
|
|
162
|
-
|
|
233
|
+
type,
|
|
234
|
+
actor_id,
|
|
235
|
+
agent_slug,
|
|
236
|
+
session_id: meta.session_id ?? (typeof meta.apc_session_id === "number" ? meta.apc_session_id : null),
|
|
163
237
|
external_id: meta.external_id,
|
|
164
238
|
});
|
|
165
239
|
}
|
|
@@ -223,9 +297,9 @@ export function getRecentTelegramTurns(
|
|
|
223
297
|
// answer that mixes fragments of the old one with hallucinations. The
|
|
224
298
|
// failure observed with qwen2.5:14b was:
|
|
225
299
|
//
|
|
226
|
-
// prev assistant: "
|
|
227
|
-
// user: "
|
|
228
|
-
// 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"
|
|
229
303
|
// (sofia exists, not "assistant", and her model is
|
|
230
304
|
// claude-haiku-4-5, not the carry-over from above)
|
|
231
305
|
//
|
|
@@ -343,21 +417,21 @@ export function getRecentTelegramTurnsFromFs({
|
|
|
343
417
|
// ---------------------------------------------------------------------------
|
|
344
418
|
|
|
345
419
|
// Write a message to the global channel store. No SQL cache — JSONL only.
|
|
346
|
-
export function appendGlobalMessage({ channel, direction, author, body, meta = {}, ts, agent_slug, external_id }) {
|
|
420
|
+
export function appendGlobalMessage({ channel, direction, type, actor_id, author, body, meta = {}, ts, agent_slug, external_id }) {
|
|
347
421
|
ts = ts || nowIso();
|
|
348
422
|
const dir = path.join(GLOBAL_MESSAGES_DIR, channel);
|
|
349
423
|
fs.mkdirSync(dir, { recursive: true });
|
|
350
424
|
const file = path.join(dir, `${ts.slice(0, 10)}.jsonl`);
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
...meta,
|
|
355
|
-
};
|
|
425
|
+
const msgType = inferMessageType({ type, channel, direction, author, agent_slug, meta });
|
|
426
|
+
const msgActorId = inferActorId({ type: msgType, actor_id, author, agent_slug, meta });
|
|
427
|
+
const fullMeta = messageMeta({ type: msgType, actor_id: msgActorId, agent_slug, external_id, meta });
|
|
356
428
|
const record = {
|
|
357
429
|
ts,
|
|
358
430
|
channel,
|
|
359
431
|
direction,
|
|
432
|
+
type: msgType,
|
|
360
433
|
author: author || null,
|
|
434
|
+
...(msgActorId ? { actor_id: msgActorId } : {}),
|
|
361
435
|
body: body || "",
|
|
362
436
|
...(Object.keys(fullMeta).length ? { meta: fullMeta } : {}),
|
|
363
437
|
};
|
|
@@ -75,7 +75,7 @@ export function getRoutine(projectPath, name) {
|
|
|
75
75
|
return readFile(projectPath).find((r) => r.name === name) || null;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export function upsertRoutine(projectPath, { name, kind, schedule, spec, enabled = true }) {
|
|
78
|
+
export function upsertRoutine(projectPath, { name, kind, schedule, spec, enabled = true, permission_mode, allowed_tools }) {
|
|
79
79
|
if (!name || !kind || !schedule) throw new Error("routine requires name, kind, schedule");
|
|
80
80
|
const now = nowIso();
|
|
81
81
|
const routines = readFile(projectPath);
|
|
@@ -87,6 +87,8 @@ export function upsertRoutine(projectPath, { name, kind, schedule, spec, enabled
|
|
|
87
87
|
kind,
|
|
88
88
|
schedule,
|
|
89
89
|
spec: spec || {},
|
|
90
|
+
permission_mode: permission_mode || prev?.permission_mode || null,
|
|
91
|
+
allowed_tools: Array.isArray(allowed_tools) ? allowed_tools : (prev?.allowed_tools || []),
|
|
90
92
|
enabled: enabled !== false,
|
|
91
93
|
last_run_at: prev?.last_run_at ?? null,
|
|
92
94
|
last_status: prev?.last_status ?? null,
|
|
@@ -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
|
|
|
@@ -348,13 +349,13 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
348
349
|
app.post("/projects/:pid/messages", (req, res) => {
|
|
349
350
|
const p = project(req, res);
|
|
350
351
|
if (!p) return;
|
|
351
|
-
const { channel, direction, agent_slug, body, meta = {}, author = null } =
|
|
352
|
+
const { channel, direction, type, actor_id, agent_slug, body, meta = {}, author = null } =
|
|
352
353
|
req.body || {};
|
|
353
354
|
if (!channel || !direction || !body)
|
|
354
355
|
return res.status(400).json({ error: "channel, direction, body required" });
|
|
355
356
|
if (!["in", "out"].includes(direction))
|
|
356
357
|
return res.status(400).json({ error: "direction must be in|out" });
|
|
357
|
-
const r = p.logMessage({ agent_slug: agent_slug || null, channel, direction, author, body, meta });
|
|
358
|
+
const r = p.logMessage({ agent_slug: agent_slug || null, channel, direction, type, actor_id, author, body, meta });
|
|
358
359
|
res.status(201).json({ ok: true, ts: r.ts });
|
|
359
360
|
});
|
|
360
361
|
|
|
@@ -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) {
|
|
@@ -843,7 +846,7 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
|
|
|
843
846
|
const r = getRoutine(p.path, req.params.name);
|
|
844
847
|
if (!r) return res.status(404).json({ error: "routine not found" });
|
|
845
848
|
try {
|
|
846
|
-
const result = await runRoutineNow({ project: p, plugins, globalConfig: config }, r);
|
|
849
|
+
const result = await runRoutineNow({ project: p, projects, plugins, registries, globalConfig: config }, r);
|
|
847
850
|
res.json(result);
|
|
848
851
|
} catch (e) {
|
|
849
852
|
res.status(500).json({ error: e.message });
|
|
@@ -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
|
-
}
|
package/src/daemon/index.js
CHANGED
|
@@ -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");
|
|
@@ -252,7 +252,7 @@ class ChannelPoller {
|
|
|
252
252
|
if (chat_id && !isReset) {
|
|
253
253
|
previousMessages = getRecentTelegramTurnsFromFs({
|
|
254
254
|
chat_id,
|
|
255
|
-
limit:
|
|
255
|
+
limit: 20,
|
|
256
256
|
max_age_hours: 24,
|
|
257
257
|
});
|
|
258
258
|
// Honour a /reset marker: drop everything up to and including it.
|
|
@@ -276,11 +276,14 @@ class ChannelPoller {
|
|
|
276
276
|
appendGlobalMessage({
|
|
277
277
|
channel: "telegram",
|
|
278
278
|
direction: "in",
|
|
279
|
+
type: "user",
|
|
280
|
+
actor_id: msg.from?.id ? String(msg.from.id) : author,
|
|
279
281
|
external_id: String(u.update_id),
|
|
280
282
|
author,
|
|
281
283
|
body: text,
|
|
282
284
|
meta: {
|
|
283
285
|
chat_id,
|
|
286
|
+
user_id: msg.from?.id || null,
|
|
284
287
|
message_id: msg.message_id,
|
|
285
288
|
tg_channel: this.channel.name,
|
|
286
289
|
},
|
|
@@ -294,11 +297,13 @@ class ChannelPoller {
|
|
|
294
297
|
// honor it for future messages.
|
|
295
298
|
if (isReset) {
|
|
296
299
|
try {
|
|
297
|
-
const ack = "
|
|
300
|
+
const ack = "Done, context cleared. Starting fresh. What do you need?";
|
|
298
301
|
await this._send({ chat_id, text: ack });
|
|
299
302
|
appendGlobalMessage({
|
|
300
303
|
channel: "telegram",
|
|
301
304
|
direction: "out",
|
|
305
|
+
type: "agent",
|
|
306
|
+
actor_id: "apx",
|
|
302
307
|
author: "apx",
|
|
303
308
|
body: ack,
|
|
304
309
|
meta: { chat_id, tg_channel: this.channel.name, in_reply_to: u.update_id, reset: true },
|
|
@@ -322,7 +327,11 @@ class ChannelPoller {
|
|
|
322
327
|
const agent = readAgents(target.path).find((a) => a.slug === routeSlug);
|
|
323
328
|
if (agent && agent.fields.Model) {
|
|
324
329
|
try {
|
|
325
|
-
const system = buildAgentSystem(target, agent,
|
|
330
|
+
const system = buildAgentSystem(target, agent, {
|
|
331
|
+
invocation: "telegram",
|
|
332
|
+
channel: this.channel.name,
|
|
333
|
+
caller: author,
|
|
334
|
+
});
|
|
326
335
|
const result = await callEngine({
|
|
327
336
|
modelId: agent.fields.Model,
|
|
328
337
|
system,
|
|
@@ -355,7 +364,7 @@ class ChannelPoller {
|
|
|
355
364
|
registries: this.registries,
|
|
356
365
|
prompt: text,
|
|
357
366
|
previousMessages,
|
|
358
|
-
contextNote: `
|
|
367
|
+
contextNote: `You are replying inside Telegram right now. Telegram channel="${this.channel.name}", author=${author}, chat_id=${chat_id}. Keep the reply plain-text and concise. Previous turns of this chat are included only for local conversational context; re-call tools for facts.`,
|
|
359
368
|
});
|
|
360
369
|
replyText = sa.text;
|
|
361
370
|
replyAuthor = sa.name;
|
|
@@ -401,12 +410,34 @@ class ChannelPoller {
|
|
|
401
410
|
appendGlobalMessage({
|
|
402
411
|
channel: "telegram",
|
|
403
412
|
direction: "out",
|
|
413
|
+
type: "agent",
|
|
414
|
+
actor_id: replyAuthor || "apx",
|
|
415
|
+
agent_slug: replyAuthor || "apx",
|
|
404
416
|
author: replyAuthor || "apx",
|
|
405
417
|
body: clean || replyText,
|
|
406
418
|
meta,
|
|
407
419
|
});
|
|
408
420
|
} catch (e) {
|
|
409
421
|
this.log(`telegram[${this.channel.name}] send-back error: ${e.message}`);
|
|
422
|
+
appendGlobalMessage({
|
|
423
|
+
channel: "telegram",
|
|
424
|
+
direction: "out",
|
|
425
|
+
type: "agent",
|
|
426
|
+
actor_id: replyAuthor || "apx",
|
|
427
|
+
agent_slug: replyAuthor || "apx",
|
|
428
|
+
author: replyAuthor || "apx",
|
|
429
|
+
body: `[send_failed] ${clean || replyText}`,
|
|
430
|
+
meta: {
|
|
431
|
+
chat_id,
|
|
432
|
+
tg_channel: this.channel.name,
|
|
433
|
+
in_reply_to: u.update_id,
|
|
434
|
+
send_error: e.message,
|
|
435
|
+
...(saTrace && saTrace.length > 0
|
|
436
|
+
? { tools_called: saTrace.map((t) => ({ tool: t.tool, args: t.args })) }
|
|
437
|
+
: {}),
|
|
438
|
+
...(saUsage ? { usage: saUsage } : {}),
|
|
439
|
+
},
|
|
440
|
+
});
|
|
410
441
|
}
|
|
411
442
|
}
|
|
412
443
|
|
|
@@ -459,26 +490,6 @@ class ChannelPoller {
|
|
|
459
490
|
}
|
|
460
491
|
}
|
|
461
492
|
|
|
462
|
-
// ---------- system-prompt builder (same as /exec) ---------------------------
|
|
463
|
-
|
|
464
|
-
function buildAgentSystem(target, agent, author) {
|
|
465
|
-
const parts = [];
|
|
466
|
-
if (agent.fields.Description) parts.push(agent.fields.Description);
|
|
467
|
-
if (agent.fields.Role) parts.push(`Role: ${agent.fields.Role}`);
|
|
468
|
-
if (agent.fields.Language) parts.push(`Default language: ${agent.fields.Language}`);
|
|
469
|
-
parts.push(
|
|
470
|
-
`You are speaking via Telegram with ${author}. Keep responses brief — ideally under 4 sentences. Mirror their language.`
|
|
471
|
-
);
|
|
472
|
-
const memPath = path.join(target.path, ".apc", "agents", agent.slug, "memory.md");
|
|
473
|
-
if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
|
|
474
|
-
const skills = (agent.fields.Skills || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
475
|
-
for (const skill of skills) {
|
|
476
|
-
const sp = path.join(target.path, ".apc", "skills", `${skill}.md`);
|
|
477
|
-
if (fs.existsSync(sp)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(sp, "utf8"));
|
|
478
|
-
}
|
|
479
|
-
return parts.join("\n\n");
|
|
480
|
-
}
|
|
481
|
-
|
|
482
493
|
function sleep(ms) {
|
|
483
494
|
return new Promise((r) => setTimeout(r, ms));
|
|
484
495
|
}
|
|
@@ -533,6 +544,9 @@ export default {
|
|
|
533
544
|
appendGlobalMessage({
|
|
534
545
|
channel: "telegram",
|
|
535
546
|
direction: "out",
|
|
547
|
+
type: "agent",
|
|
548
|
+
actor_id: author,
|
|
549
|
+
agent_slug: author,
|
|
536
550
|
author,
|
|
537
551
|
body: text,
|
|
538
552
|
meta: {
|