@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
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { readAgents } from "../../../core/parser.js";
|
|
4
|
+
import {
|
|
5
|
+
buildApfHint,
|
|
6
|
+
closeRuntimeSession,
|
|
7
|
+
createRuntimeSession,
|
|
8
|
+
extractApfResult,
|
|
9
|
+
} from "../../apc-runtime-context.js";
|
|
2
10
|
import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
|
|
3
11
|
import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
|
|
4
12
|
|
|
@@ -20,37 +28,70 @@ function resolveProjectForAgent(projects, project, slug) {
|
|
|
20
28
|
return resolveProject(projects, project);
|
|
21
29
|
}
|
|
22
30
|
|
|
31
|
+
function projectName(project) {
|
|
32
|
+
try {
|
|
33
|
+
const meta = JSON.parse(fs.readFileSync(path.join(project.path, ".apc", "project.json"), "utf8"));
|
|
34
|
+
if (meta.name) return meta.name;
|
|
35
|
+
} catch {}
|
|
36
|
+
return path.basename(project.path);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildRuntimeSystem(project, agent, runtime, sessionId, caller) {
|
|
40
|
+
const agentSlug = agent?.slug || "apx";
|
|
41
|
+
const hint = buildApfHint({
|
|
42
|
+
projectName: projectName(project),
|
|
43
|
+
projectPath: project.path,
|
|
44
|
+
agentSlug,
|
|
45
|
+
sessionId,
|
|
46
|
+
});
|
|
47
|
+
if (agent) {
|
|
48
|
+
return buildAgentSystem(project, agent, {
|
|
49
|
+
invocation: "runtime",
|
|
50
|
+
runtime,
|
|
51
|
+
caller,
|
|
52
|
+
extraParts: [hint],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
"You are APX running inside an external coding runtime.",
|
|
58
|
+
"No APC agent was explicitly selected for this run.",
|
|
59
|
+
"Use the project context and runtime tools directly. Do not impersonate a project agent.",
|
|
60
|
+
hint,
|
|
61
|
+
].join("\n\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
23
64
|
export default {
|
|
24
65
|
name: "call_runtime",
|
|
25
66
|
schema: {
|
|
26
67
|
type: "function",
|
|
27
68
|
function: {
|
|
28
69
|
name: "call_runtime",
|
|
29
|
-
description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider) impersonating an APC agent.",
|
|
70
|
+
description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider, Cursor Agent, Gemini CLI, Qwen Code), optionally impersonating an APC agent.",
|
|
30
71
|
parameters: {
|
|
31
72
|
type: "object",
|
|
32
73
|
properties: {
|
|
33
74
|
project: { type: "string" },
|
|
34
|
-
agent: { type: "string", description: "APC agent slug from AGENTS.md, not runtime name" },
|
|
75
|
+
agent: { type: "string", description: "Optional APC agent slug from AGENTS.md, not runtime name. Omit when the user did not name an agent." },
|
|
35
76
|
runtime: {
|
|
36
77
|
type: "string",
|
|
37
|
-
enum:
|
|
78
|
+
enum: RUNTIME_IDS,
|
|
38
79
|
description: "external CLI runtime",
|
|
39
80
|
},
|
|
40
81
|
prompt: { type: "string" },
|
|
41
82
|
timeout_s: { type: "integer", description: "seconds before SIGTERM; default 300" },
|
|
42
83
|
confirmed: confirmedProperty("true only after explicit user confirmation for this exact runtime command"),
|
|
43
84
|
},
|
|
44
|
-
required: ["
|
|
85
|
+
required: ["runtime", "prompt"],
|
|
45
86
|
},
|
|
46
87
|
},
|
|
47
88
|
},
|
|
48
89
|
makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, runtime, prompt, timeout_s = 300, confirmed = false }) => {
|
|
49
90
|
requirePermission("call_runtime", { dangerous: true, confirmed });
|
|
50
91
|
|
|
51
|
-
const p = resolveProjectForAgent(projects, project, slug);
|
|
52
|
-
const agent = readAgents(p.path).find((a) => a.slug === slug);
|
|
53
|
-
if (!agent) {
|
|
92
|
+
const p = slug ? resolveProjectForAgent(projects, project, slug) : resolveProject(projects, project);
|
|
93
|
+
const agent = slug ? readAgents(p.path).find((a) => a.slug === slug) : null;
|
|
94
|
+
if (slug && !agent) {
|
|
54
95
|
const directory = projects.list().map((entry) => ({
|
|
55
96
|
project: entry.name,
|
|
56
97
|
kind: entry.id === 0 ? "default" : "project",
|
|
@@ -67,45 +108,74 @@ export default {
|
|
|
67
108
|
return { error: `${e.message}. Available runtimes: ${RUNTIME_IDS.join(", ")}` };
|
|
68
109
|
}
|
|
69
110
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
cwd: p.path,
|
|
78
|
-
timeoutMs: timeout_s * 1000,
|
|
111
|
+
const actor = agent?.slug || "apx";
|
|
112
|
+
const session = createRuntimeSession({
|
|
113
|
+
projectRoot: p.path,
|
|
114
|
+
storageRoot: p.storagePath,
|
|
115
|
+
agentSlug: actor,
|
|
116
|
+
runtime,
|
|
117
|
+
title: `Runtime: ${runtime}${agent ? ` (${agent.slug})` : ""}`,
|
|
79
118
|
});
|
|
80
119
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
try {
|
|
121
|
+
const r = await rt.run({
|
|
122
|
+
system: buildRuntimeSystem(p, agent, runtime, session.id, "super_agent_tool"),
|
|
123
|
+
prompt,
|
|
124
|
+
cwd: p.path,
|
|
125
|
+
timeoutMs: timeout_s * 1000,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = extractApfResult(r.output) || (r.output || "").slice(0, 200);
|
|
129
|
+
closeRuntimeSession({
|
|
130
|
+
filePath: session.path,
|
|
131
|
+
externalSessionPath: r.externalSessionPath || null,
|
|
132
|
+
exitCode: r.exitCode,
|
|
133
|
+
result,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
p.logMessage({
|
|
137
|
+
agent_slug: actor,
|
|
138
|
+
channel: "runtime",
|
|
139
|
+
direction: "in",
|
|
140
|
+
author: "user",
|
|
141
|
+
body: prompt,
|
|
142
|
+
meta: { runtime, invoked_by: "super_agent_tool", apc_session: session.id },
|
|
143
|
+
});
|
|
144
|
+
p.logMessage({
|
|
145
|
+
agent_slug: actor,
|
|
146
|
+
channel: "runtime",
|
|
147
|
+
direction: "out",
|
|
148
|
+
author: actor,
|
|
149
|
+
body: r.output || "",
|
|
150
|
+
meta: {
|
|
151
|
+
runtime,
|
|
152
|
+
exit_code: r.exitCode,
|
|
153
|
+
external_session_path: r.externalSessionPath || null,
|
|
154
|
+
session_id: r.sessionId || null,
|
|
155
|
+
apc_session: session.id,
|
|
156
|
+
invoked_by: "super_agent_tool",
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
96
161
|
runtime,
|
|
162
|
+
agent: agent?.slug || null,
|
|
163
|
+
apc_session: session.id,
|
|
97
164
|
exit_code: r.exitCode,
|
|
165
|
+
output: (r.output || "").slice(0, 4000),
|
|
166
|
+
truncated: (r.output || "").length > 4000,
|
|
98
167
|
external_session_path: r.externalSessionPath || null,
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
168
|
+
session_id: r.sessionId || null,
|
|
169
|
+
};
|
|
170
|
+
} catch (e) {
|
|
171
|
+
try {
|
|
172
|
+
closeRuntimeSession({
|
|
173
|
+
filePath: session.path,
|
|
174
|
+
exitCode: -1,
|
|
175
|
+
result: `error: ${e.message.slice(0, 200)}`,
|
|
176
|
+
});
|
|
177
|
+
} catch {}
|
|
178
|
+
throw e;
|
|
179
|
+
}
|
|
110
180
|
},
|
|
111
181
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { resolveProject, safePathJoin } from "../helpers.js";
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: "search_files",
|
|
9
|
+
schema: {
|
|
10
|
+
type: "function",
|
|
11
|
+
function: {
|
|
12
|
+
name: "search_files",
|
|
13
|
+
description: "Search for text patterns inside project files using ripgrep or grep.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
query: { type: "string", description: "The text or regex pattern to search for." },
|
|
18
|
+
project: { type: "string" },
|
|
19
|
+
path: { type: "string", description: "relative path inside the project to restrict search; default '.'" },
|
|
20
|
+
},
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
makeHandler: ({ projects, requirePermission }) => async ({ query, project, path: sub = "." } = {}) => {
|
|
26
|
+
// Optional permission check if it's considered destructive, but search is safe read-only
|
|
27
|
+
await requirePermission("search_files", { query, project, path: sub }, "safe");
|
|
28
|
+
|
|
29
|
+
const p = resolveProject(projects, project);
|
|
30
|
+
const target = safePathJoin(p.path, sub);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execFileAsync("rg", ["-n", "--no-heading", "--color=never", query, target], {
|
|
34
|
+
cwd: p.path,
|
|
35
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
36
|
+
});
|
|
37
|
+
return formatResults(stdout);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (e.code === "ENOENT" || e.message.includes("ENOENT")) {
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await execFileAsync("grep", ["-rn", query, target], {
|
|
42
|
+
cwd: p.path,
|
|
43
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
44
|
+
});
|
|
45
|
+
return formatResults(stdout);
|
|
46
|
+
} catch (e2) {
|
|
47
|
+
if (e2.code === 1) return { result: "No matches found." };
|
|
48
|
+
throw new Error(`grep failed: ${e2.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (e.code === 1) return { result: "No matches found." };
|
|
52
|
+
return { error: `search failed: ${e.message}` };
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function formatResults(stdout) {
|
|
58
|
+
if (!stdout) return { result: "No matches found." };
|
|
59
|
+
const lines = stdout.split('\n').slice(0, 100);
|
|
60
|
+
const out = lines.join('\n');
|
|
61
|
+
if (lines.length >= 100) {
|
|
62
|
+
return { result: out + '\n...(truncated)' };
|
|
63
|
+
}
|
|
64
|
+
return { result: out };
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -34,6 +34,7 @@ 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
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?"
|
|
37
|
+
- Routine schedules: APX supports standard cron expressions (e.g. '*/5 * * * *'), OR 'every:<number><s|m|h|d>' (e.g. 'every:60s'), OR 'once:<iso-8601>'.
|
|
37
38
|
- Safe read-only shell checks such as apx --help, apx routine list, docker ps, find, ls, rg, grep can run in automatico without asking.
|
|
38
39
|
|
|
39
40
|
Channel context:
|
|
@@ -42,20 +43,6 @@ Channel context:
|
|
|
42
43
|
|
|
43
44
|
You HAVE tools. THE FIRST THING you do for any factual question is call a tool. Do not ask the user to specify a project unless the tool itself fails.
|
|
44
45
|
|
|
45
|
-
Available tools:
|
|
46
|
-
- list_projects, list_agents, list_mcps — discovery (call WITHOUT project to get all of them across every registered project; specify project only to filter)
|
|
47
|
-
- list_vault_agents, import_agent, add_project — inspect the agent vault, install a vault agent into a project, register an APC project
|
|
48
|
-
- read_agent_memory — what an agent knows
|
|
49
|
-
- list_files, read_file, write_file, edit_file — inspect/create/edit files in default or a project
|
|
50
|
-
- run_shell — execute shell commands in default or a project
|
|
51
|
-
- tail_messages, search_messages — see history
|
|
52
|
-
- call_agent — delegate to a project agent
|
|
53
|
-
- call_mcp — call an installed MCP tool when MCP is the right protocol
|
|
54
|
-
- call_runtime — spawn a separate claude-code/codex/opencode/aider session when the user wants an external runtime/chat
|
|
55
|
-
- send_telegram — send a message
|
|
56
|
-
- set_identity — update agent name, personality, owner, language (persists to disk)
|
|
57
|
-
- set_permission_mode — set total/automatico/permiso in ~/.apx/config.json
|
|
58
|
-
|
|
59
46
|
HARD RULES (do not deviate):
|
|
60
47
|
1. NEVER invent project names, agent slugs, model ids, MCP names or paths. ALWAYS look them up via list_* first.
|
|
61
48
|
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.
|
|
@@ -66,8 +53,8 @@ HARD RULES (do not deviate):
|
|
|
66
53
|
7. Stay brief: under 6 sentences unless asked for detail.
|
|
67
54
|
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.
|
|
68
55
|
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.
|
|
69
|
-
10.
|
|
70
|
-
11. DISPATCH RULE: when the user asks
|
|
56
|
+
10. DELEGATION RULE: when the user asks an agent to do a task, use call_agent (unless they specify opening it in a runtime, then see rule 11).
|
|
57
|
+
11. DISPATCH RULE: when the user asks to work inside Claude, Codex, OpenCode, Aider, Cursor, Gemini CLI, or Qwen Code, use call_runtime({runtime: 'claude-code'|'codex'|'opencode'|'aider'|'cursor-agent'|'gemini-cli'|'qwen-code', prompt: <user's request>}). If they explicitly name an agent to spawn, pass agent: <slug>. If they don't name an agent, DO NOT pass an agent argument. When an agent is passed, its memory + skills become the system prompt of the runtime.
|
|
71
58
|
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.
|
|
72
59
|
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.
|
|
73
60
|
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.
|
|
@@ -99,11 +86,13 @@ export async function runSuperAgent({
|
|
|
99
86
|
prompt,
|
|
100
87
|
contextNote = "",
|
|
101
88
|
previousMessages = [],
|
|
89
|
+
overrideModel = null,
|
|
102
90
|
}) {
|
|
103
91
|
if (!isSuperAgentEnabled(globalConfig)) {
|
|
104
92
|
throw new Error("super-agent not enabled (set super_agent.enabled and .model in ~/.apx/config.json)");
|
|
105
93
|
}
|
|
106
94
|
const sa = globalConfig.super_agent;
|
|
95
|
+
const activeModel = overrideModel || sa.model;
|
|
107
96
|
|
|
108
97
|
// Tiny project hint — JUST names + ids, no detail. The model is expected to
|
|
109
98
|
// call list_agents / list_mcps / read_agent_memory / etc. for everything
|
|
@@ -153,7 +142,7 @@ export async function runSuperAgent({
|
|
|
153
142
|
|
|
154
143
|
for (let iter = 0; iter < MAX_TOOL_ITERS; iter++) {
|
|
155
144
|
const result = await callEngine({
|
|
156
|
-
modelId:
|
|
145
|
+
modelId: activeModel,
|
|
157
146
|
system,
|
|
158
147
|
messages: conversation,
|
|
159
148
|
config: globalConfig,
|
package/src/mcp/index.js
CHANGED
|
@@ -95,7 +95,7 @@ server.tool(
|
|
|
95
95
|
{
|
|
96
96
|
slug: z.string().describe("Agent slug"),
|
|
97
97
|
prompt: z.string().describe("Prompt / task for the agent"),
|
|
98
|
-
runtime: z.string().optional().describe("Runtime: claude-code | codex (default: claude-code)"),
|
|
98
|
+
runtime: z.string().optional().describe("Runtime: claude-code | codex | opencode | aider | cursor-agent | gemini-cli | qwen-code (default: claude-code)"),
|
|
99
99
|
},
|
|
100
100
|
async ({ slug, prompt, runtime }) => {
|
|
101
101
|
const proj = await resolveProject();
|