@agentprojectcontext/apx 1.5.0 → 1.7.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/core/config.js +2 -0
- package/src/core/mascot.js +5 -7
- package/src/daemon/api.js +162 -0
- package/src/daemon/index.js +37 -2
- package/src/daemon/super-agent-tools/helpers.js +119 -0
- package/src/daemon/super-agent-tools/index.js +52 -0
- package/src/daemon/super-agent-tools/tools/add-project.js +36 -0
- package/src/daemon/super-agent-tools/tools/call-agent.js +45 -0
- package/src/daemon/super-agent-tools/tools/call-mcp.js +30 -0
- package/src/daemon/super-agent-tools/tools/call-runtime.js +107 -0
- package/src/daemon/super-agent-tools/tools/edit-file.js +44 -0
- package/src/daemon/super-agent-tools/tools/import-agent.js +48 -0
- package/src/daemon/super-agent-tools/tools/list-agents.js +36 -0
- package/src/daemon/super-agent-tools/tools/list-files.js +38 -0
- package/src/daemon/super-agent-tools/tools/list-mcps.js +48 -0
- package/src/daemon/super-agent-tools/tools/list-projects.js +20 -0
- package/src/daemon/super-agent-tools/tools/list-vault-agents.js +18 -0
- package/src/daemon/super-agent-tools/tools/read-agent-memory.js +28 -0
- package/src/daemon/super-agent-tools/tools/read-file.js +33 -0
- package/src/daemon/super-agent-tools/tools/run-shell.js +64 -0
- package/src/daemon/super-agent-tools/tools/search-messages.js +32 -0
- package/src/daemon/super-agent-tools/tools/send-telegram.js +30 -0
- package/src/daemon/super-agent-tools/tools/set-identity.js +35 -0
- package/src/daemon/super-agent-tools/tools/tail-messages.js +37 -0
- package/src/daemon/super-agent-tools/tools/write-file.js +33 -0
- package/src/daemon/super-agent-tools.js +1 -539
- package/src/daemon/super-agent.js +34 -7
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readAgents } from "../../../core/parser.js";
|
|
2
|
+
import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
|
|
3
|
+
import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
|
|
4
|
+
|
|
5
|
+
function resolveProjectForAgent(projects, project, slug) {
|
|
6
|
+
if (project) return resolveProject(projects, project);
|
|
7
|
+
|
|
8
|
+
const defaultProject = projects.get(0);
|
|
9
|
+
if (defaultProject && readAgents(defaultProject.path).find((a) => a.slug === slug)) {
|
|
10
|
+
return defaultProject;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const matches = [];
|
|
14
|
+
for (const entry of projects.list()) {
|
|
15
|
+
const p = projects.get(entry.id);
|
|
16
|
+
if (readAgents(p.path).find((a) => a.slug === slug)) matches.push(p);
|
|
17
|
+
}
|
|
18
|
+
if (matches.length === 1) return matches[0];
|
|
19
|
+
if (defaultProject) return defaultProject;
|
|
20
|
+
return resolveProject(projects, project);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: "call_runtime",
|
|
25
|
+
schema: {
|
|
26
|
+
type: "function",
|
|
27
|
+
function: {
|
|
28
|
+
name: "call_runtime",
|
|
29
|
+
description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider) impersonating an APC agent.",
|
|
30
|
+
parameters: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
project: { type: "string" },
|
|
34
|
+
agent: { type: "string", description: "APC agent slug from AGENTS.md, not runtime name" },
|
|
35
|
+
runtime: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["claude-code", "codex", "opencode", "aider"],
|
|
38
|
+
description: "external CLI runtime",
|
|
39
|
+
},
|
|
40
|
+
prompt: { type: "string" },
|
|
41
|
+
timeout_s: { type: "integer", description: "seconds before SIGTERM; default 300" },
|
|
42
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact runtime command"),
|
|
43
|
+
},
|
|
44
|
+
required: ["agent", "runtime", "prompt"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, runtime, prompt, timeout_s = 300, confirmed = false }) => {
|
|
49
|
+
requirePermission("call_runtime", { dangerous: true, confirmed });
|
|
50
|
+
|
|
51
|
+
const p = resolveProjectForAgent(projects, project, slug);
|
|
52
|
+
const agent = readAgents(p.path).find((a) => a.slug === slug);
|
|
53
|
+
if (!agent) {
|
|
54
|
+
const directory = projects.list().map((entry) => ({
|
|
55
|
+
project: entry.name,
|
|
56
|
+
kind: entry.id === 0 ? "default" : "project",
|
|
57
|
+
path: entry.path,
|
|
58
|
+
agents: readAgents(projects.get(entry.id).path).map((a) => a.slug),
|
|
59
|
+
}));
|
|
60
|
+
return { error: `agent "${slug}" not found in selected project`, directory };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let rt;
|
|
64
|
+
try {
|
|
65
|
+
rt = getRuntime(runtime);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return { error: `${e.message}. Available runtimes: ${RUNTIME_IDS.join(", ")}` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const r = await rt.run({
|
|
71
|
+
system: buildAgentSystem(p, agent),
|
|
72
|
+
prompt,
|
|
73
|
+
cwd: p.path,
|
|
74
|
+
timeoutMs: timeout_s * 1000,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
p.logMessage({
|
|
78
|
+
agent_slug: slug,
|
|
79
|
+
channel: "runtime",
|
|
80
|
+
direction: "in",
|
|
81
|
+
author: "user",
|
|
82
|
+
body: prompt,
|
|
83
|
+
meta: { runtime, invoked_by: "super_agent_tool" },
|
|
84
|
+
});
|
|
85
|
+
p.logMessage({
|
|
86
|
+
agent_slug: slug,
|
|
87
|
+
channel: "runtime",
|
|
88
|
+
direction: "out",
|
|
89
|
+
author: slug,
|
|
90
|
+
body: r.output || "",
|
|
91
|
+
meta: {
|
|
92
|
+
runtime,
|
|
93
|
+
exit_code: r.exitCode,
|
|
94
|
+
external_session_path: r.externalSessionPath || null,
|
|
95
|
+
invoked_by: "super_agent_tool",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
runtime,
|
|
101
|
+
exit_code: r.exitCode,
|
|
102
|
+
output: (r.output || "").slice(0, 4000),
|
|
103
|
+
truncated: (r.output || "").length > 4000,
|
|
104
|
+
external_session_path: r.externalSessionPath || null,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { confirmedProperty, resolveProject, safePathJoin } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "edit_file",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "edit_file",
|
|
10
|
+
description: "Edit a UTF-8 text file inside default or a project by replacing one exact string with another.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
project: { type: "string" },
|
|
15
|
+
path: { type: "string", description: "relative path inside the project" },
|
|
16
|
+
search: { type: "string", description: "exact text to replace" },
|
|
17
|
+
replace: { type: "string", description: "replacement text" },
|
|
18
|
+
all: { type: "boolean", description: "replace all matches; default false replaces one match" },
|
|
19
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact file edit"),
|
|
20
|
+
},
|
|
21
|
+
required: ["path", "search", "replace"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
makeHandler: ({ projects, requirePermission }) => ({ project, path, search, replace, all = false, confirmed = false }) => {
|
|
26
|
+
requirePermission("edit_file", { dangerous: true, confirmed });
|
|
27
|
+
if (!path) throw new Error("edit_file: path required");
|
|
28
|
+
if (!search) throw new Error("edit_file: search required");
|
|
29
|
+
|
|
30
|
+
const p = resolveProject(projects, project);
|
|
31
|
+
const target = safePathJoin(p.path, path);
|
|
32
|
+
if (!fs.existsSync(target)) throw new Error(`file not found: ${path}`);
|
|
33
|
+
const before = fs.readFileSync(target, "utf8");
|
|
34
|
+
const matches = before.split(search).length - 1;
|
|
35
|
+
if (matches === 0) throw new Error("search text not found");
|
|
36
|
+
if (!all && matches > 1) {
|
|
37
|
+
throw new Error(`search text appears ${matches} times; set all=true or use a more specific search`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const after = all ? before.split(search).join(replace) : before.replace(search, replace);
|
|
41
|
+
fs.writeFileSync(target, after, "utf8");
|
|
42
|
+
return { ok: true, path: target, replacements: all ? matches : 1 };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readVaultAgents, VAULT_DIR } from "../../../core/parser.js";
|
|
4
|
+
import { addImportedAgent, ensureAgentDir, regenerateAgentsMd } from "../../../core/scaffold.js";
|
|
5
|
+
import { confirmedProperty, projectMeta, resolveProject } from "../helpers.js";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: "import_agent",
|
|
9
|
+
schema: {
|
|
10
|
+
type: "function",
|
|
11
|
+
function: {
|
|
12
|
+
name: "import_agent",
|
|
13
|
+
description: "Import an agent template from the APX vault into default or a registered project.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
project: { type: "string", description: "project id/name/path; omit or use 'default' for ~/.apx/projects/default" },
|
|
18
|
+
agent: { type: "string", description: "agent slug from list_vault_agents" },
|
|
19
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact import"),
|
|
20
|
+
},
|
|
21
|
+
required: ["agent"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
makeHandler: ({ projects, requirePermission }) => ({ project, agent: slug, confirmed = false }) => {
|
|
26
|
+
requirePermission("import_agent", { dangerous: true, confirmed });
|
|
27
|
+
if (!slug) throw new Error("import_agent: agent required");
|
|
28
|
+
|
|
29
|
+
const vaultPath = path.join(VAULT_DIR, `${slug}.md`);
|
|
30
|
+
if (!fs.existsSync(vaultPath)) {
|
|
31
|
+
const available = readVaultAgents().map((a) => a.slug).join(", ") || "(none)";
|
|
32
|
+
throw new Error(`agent "${slug}" not found in vault. Available: ${available}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const p = resolveProject(projects, project || "default");
|
|
36
|
+
addImportedAgent(p.path, slug);
|
|
37
|
+
ensureAgentDir(p.path, slug);
|
|
38
|
+
regenerateAgentsMd(p.path);
|
|
39
|
+
projects.rebuild(p.id);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
agent: slug,
|
|
44
|
+
project: projectMeta(projects, p),
|
|
45
|
+
source: vaultPath,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readAgents } from "../../../core/parser.js";
|
|
2
|
+
import { agentRow, resolveProject } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "list_agents",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "list_agents",
|
|
10
|
+
description: "List agents. If project is omitted, returns all agents grouped by project, including default.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
project: { type: "string", description: "project id, name, path, or substring; omit to list all projects" },
|
|
15
|
+
},
|
|
16
|
+
required: [],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
makeHandler: ({ projects }) => ({ project } = {}) => {
|
|
21
|
+
const p = resolveProject(projects, project, { allowMulti: true });
|
|
22
|
+
if (p) return readAgents(p.path).map(agentRow);
|
|
23
|
+
return projects.list().map((entry) => {
|
|
24
|
+
const e = projects.get(entry.id);
|
|
25
|
+
return {
|
|
26
|
+
project: {
|
|
27
|
+
id: entry.id,
|
|
28
|
+
kind: entry.id === 0 ? "default" : "project",
|
|
29
|
+
name: entry.name,
|
|
30
|
+
path: entry.path,
|
|
31
|
+
},
|
|
32
|
+
agents: readAgents(e.path).map(agentRow),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveProject, safePathJoin } from "../helpers.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "list_files",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "list_files",
|
|
11
|
+
description: "List files and subdirectories inside default or a project.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
project: { type: "string" },
|
|
16
|
+
path: { type: "string", description: "relative path inside the project; default '.'" },
|
|
17
|
+
},
|
|
18
|
+
required: [],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
makeHandler: ({ projects }) => ({ project, path: sub = "." } = {}) => {
|
|
23
|
+
const p = resolveProject(projects, project);
|
|
24
|
+
const target = safePathJoin(p.path, sub);
|
|
25
|
+
if (!fs.existsSync(target)) return { error: `path not found: ${sub}` };
|
|
26
|
+
if (!fs.statSync(target).isDirectory()) return { error: `${sub} is not a directory` };
|
|
27
|
+
|
|
28
|
+
return fs.readdirSync(target).map((name) => {
|
|
29
|
+
const full = path.join(target, name);
|
|
30
|
+
const stat = fs.statSync(full);
|
|
31
|
+
return {
|
|
32
|
+
name,
|
|
33
|
+
type: stat.isDirectory() ? "dir" : "file",
|
|
34
|
+
size: stat.size,
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolveProject } from "../helpers.js";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: "list_mcps",
|
|
5
|
+
schema: {
|
|
6
|
+
type: "function",
|
|
7
|
+
function: {
|
|
8
|
+
name: "list_mcps",
|
|
9
|
+
description: "List MCPs. If project is omitted, returns all MCPs grouped by project, including default.",
|
|
10
|
+
parameters: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
project: { type: "string", description: "project id/name/path; omit to list all projects" },
|
|
14
|
+
},
|
|
15
|
+
required: [],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
makeHandler: ({ projects, registries }) => ({ project } = {}) => {
|
|
20
|
+
const row = (m) => ({
|
|
21
|
+
name: m.name,
|
|
22
|
+
source: m.source,
|
|
23
|
+
transport: m.transport,
|
|
24
|
+
enabled: !!m.enabled,
|
|
25
|
+
command: m.command,
|
|
26
|
+
url: m.url,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const p = resolveProject(projects, project, { allowMulti: true });
|
|
30
|
+
if (p) {
|
|
31
|
+
if (!registries) throw new Error("MCP registry unavailable");
|
|
32
|
+
return registries.for(p).list().map(row);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return projects.list().map((entry) => {
|
|
36
|
+
const e = projects.get(entry.id);
|
|
37
|
+
return {
|
|
38
|
+
project: {
|
|
39
|
+
id: entry.id,
|
|
40
|
+
kind: entry.id === 0 ? "default" : "project",
|
|
41
|
+
name: entry.name,
|
|
42
|
+
path: entry.path,
|
|
43
|
+
},
|
|
44
|
+
mcps: registries ? registries.for(e).list().map(row) : [],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: "list_projects",
|
|
3
|
+
schema: {
|
|
4
|
+
type: "function",
|
|
5
|
+
function: {
|
|
6
|
+
name: "list_projects",
|
|
7
|
+
description: "List the APX default project and every registered APC project. Returns id, kind, name, path, and agent count.",
|
|
8
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
makeHandler: ({ projects }) => () => {
|
|
12
|
+
return projects.list().map((p) => ({
|
|
13
|
+
id: p.id,
|
|
14
|
+
kind: p.id === 0 ? "default" : "project",
|
|
15
|
+
name: p.name,
|
|
16
|
+
path: p.path,
|
|
17
|
+
agents: p.agents,
|
|
18
|
+
}));
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readVaultAgents, VAULT_DIR } from "../../../core/parser.js";
|
|
2
|
+
import { agentRow } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "list_vault_agents",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "list_vault_agents",
|
|
10
|
+
description: "List reusable agent templates from the APX vault (~/.apx/agents). These can be imported into default or a project.",
|
|
11
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
makeHandler: () => () => ({
|
|
15
|
+
vault: VAULT_DIR,
|
|
16
|
+
agents: readVaultAgents().map(agentRow),
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveProject } from "../helpers.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "read_agent_memory",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "read_agent_memory",
|
|
11
|
+
description: "Read an agent memory.md file from default or a project.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
project: { type: "string" },
|
|
16
|
+
agent: { type: "string", description: "agent slug" },
|
|
17
|
+
},
|
|
18
|
+
required: ["agent"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
makeHandler: ({ projects }) => ({ project, agent }) => {
|
|
23
|
+
const p = resolveProject(projects, project);
|
|
24
|
+
const file = path.join(p.path, ".apc", "agents", agent, "memory.md");
|
|
25
|
+
if (!fs.existsSync(file)) return { error: `no memory.md for agent ${agent}` };
|
|
26
|
+
return { body: fs.readFileSync(file, "utf8") };
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { resolveProject, safePathJoin } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "read_file",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "read_file",
|
|
10
|
+
description: "Read a text file inside default or a project. Returns first 64KB.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
project: { type: "string" },
|
|
15
|
+
path: { type: "string", description: "relative path inside the project" },
|
|
16
|
+
},
|
|
17
|
+
required: ["path"],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
makeHandler: ({ projects }) => ({ project, path }) => {
|
|
22
|
+
if (!path) throw new Error("read_file: path required");
|
|
23
|
+
const p = resolveProject(projects, project);
|
|
24
|
+
const target = safePathJoin(p.path, path);
|
|
25
|
+
if (!fs.existsSync(target)) return { error: `file not found: ${path}` };
|
|
26
|
+
const stat = fs.statSync(target);
|
|
27
|
+
if (!stat.isFile()) return { error: `${path} is not a file` };
|
|
28
|
+
return {
|
|
29
|
+
content: fs.readFileSync(target, "utf8").slice(0, 64 * 1024),
|
|
30
|
+
truncated: stat.size > 64 * 1024,
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { confirmedProperty, resolveProject, safePathJoin } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
function run(command, { cwd, timeoutMs }) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const child = spawn("sh", ["-lc", command], { cwd, env: process.env });
|
|
7
|
+
let stdout = "";
|
|
8
|
+
let stderr = "";
|
|
9
|
+
let timedOut = false;
|
|
10
|
+
const timer = setTimeout(() => {
|
|
11
|
+
timedOut = true;
|
|
12
|
+
child.kill("SIGTERM");
|
|
13
|
+
}, timeoutMs);
|
|
14
|
+
|
|
15
|
+
child.stdout.on("data", (d) => { stdout += d.toString(); });
|
|
16
|
+
child.stderr.on("data", (d) => { stderr += d.toString(); });
|
|
17
|
+
child.on("close", (code, signal) => {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
resolve({ code, signal, timedOut, stdout, stderr });
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
name: "run_shell",
|
|
26
|
+
schema: {
|
|
27
|
+
type: "function",
|
|
28
|
+
function: {
|
|
29
|
+
name: "run_shell",
|
|
30
|
+
description: "Run a shell command in default or a project working directory. Direct command execution tool.",
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
project: { type: "string" },
|
|
35
|
+
cwd: { type: "string", description: "relative working directory inside the selected project; default '.'" },
|
|
36
|
+
command: { type: "string" },
|
|
37
|
+
timeout_s: { type: "integer", description: "seconds before SIGTERM; default 60" },
|
|
38
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact shell command"),
|
|
39
|
+
},
|
|
40
|
+
required: ["command"],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
makeHandler: ({ projects, requirePermission }) => async ({ project, cwd = ".", command, timeout_s = 60, confirmed = false }) => {
|
|
45
|
+
requirePermission("run_shell", { dangerous: true, confirmed });
|
|
46
|
+
if (!command) throw new Error("run_shell: command required");
|
|
47
|
+
|
|
48
|
+
const p = resolveProject(projects, project);
|
|
49
|
+
const workingDir = safePathJoin(p.path, cwd);
|
|
50
|
+
const result = await run(command, {
|
|
51
|
+
cwd: workingDir,
|
|
52
|
+
timeoutMs: Math.max(1, Math.min(timeout_s, 600)) * 1000,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
exit_code: result.code,
|
|
56
|
+
signal: result.signal,
|
|
57
|
+
timed_out: result.timedOut,
|
|
58
|
+
stdout: result.stdout.slice(0, 12000),
|
|
59
|
+
stderr: result.stderr.slice(0, 12000),
|
|
60
|
+
truncated: result.stdout.length > 12000 || result.stderr.length > 12000,
|
|
61
|
+
cwd: workingDir,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { searchProjectMessages } from "../../../core/messages-store.js";
|
|
2
|
+
import { resolveProject } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "search_messages",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "search_messages",
|
|
10
|
+
description: "Full-text search inside project messages.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
project: { type: "string" },
|
|
15
|
+
query: { type: "string" },
|
|
16
|
+
},
|
|
17
|
+
required: ["query"],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
makeHandler: ({ projects }) => ({ project, query }) => {
|
|
22
|
+
if (!query) throw new Error("search_messages: query required");
|
|
23
|
+
const p = resolveProject(projects, project);
|
|
24
|
+
return searchProjectMessages(p.path, query, 25).map((m) => ({
|
|
25
|
+
ts: m.ts,
|
|
26
|
+
channel: m.channel,
|
|
27
|
+
direction: m.direction,
|
|
28
|
+
author: m.author,
|
|
29
|
+
body: m.body,
|
|
30
|
+
}));
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { confirmedProperty } from "../helpers.js";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: "send_telegram",
|
|
5
|
+
schema: {
|
|
6
|
+
type: "function",
|
|
7
|
+
function: {
|
|
8
|
+
name: "send_telegram",
|
|
9
|
+
description: "Send a Telegram message via the daemon's Telegram plugin.",
|
|
10
|
+
parameters: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
channel: { type: "string", description: "telegram channel name; omit for default" },
|
|
14
|
+
chat_id: { type: "string", description: "destination chat id; omit to use channel default" },
|
|
15
|
+
text: { type: "string" },
|
|
16
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact outbound message"),
|
|
17
|
+
},
|
|
18
|
+
required: ["text"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
makeHandler: ({ plugins, requirePermission }) => async ({ channel, chat_id, text, confirmed = false }) => {
|
|
23
|
+
requirePermission("send_telegram", { dangerous: true, confirmed });
|
|
24
|
+
if (!plugins) throw new Error("plugins unavailable");
|
|
25
|
+
const telegram = plugins.get("telegram");
|
|
26
|
+
if (!telegram) throw new Error("telegram plugin not loaded");
|
|
27
|
+
const result = await telegram.send({ channel, chat_id, text, author: "apx" });
|
|
28
|
+
return { ok: true, message_id: result.message_id };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readIdentity, writeIdentity } from "../../../core/identity.js";
|
|
2
|
+
import { confirmedProperty } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "set_identity",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "set_identity",
|
|
10
|
+
description: "Update daemon identity fields. Persists to ~/.apx/identity.json.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
agent_name: { type: "string", description: "new agent name" },
|
|
15
|
+
owner_name: { type: "string", description: "owner name" },
|
|
16
|
+
personality: { type: "string", description: "comma-separated personality traits" },
|
|
17
|
+
language: { type: "string", description: "preferred language" },
|
|
18
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact identity update"),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
makeHandler: ({ requirePermission }) => ({ agent_name, owner_name, personality, language, confirmed = false } = {}) => {
|
|
24
|
+
requirePermission("set_identity", { dangerous: true, confirmed });
|
|
25
|
+
const fields = {};
|
|
26
|
+
if (agent_name) fields.agent_name = agent_name;
|
|
27
|
+
if (owner_name) fields.owner_name = owner_name;
|
|
28
|
+
if (personality) fields.personality = personality;
|
|
29
|
+
if (language) fields.language = language;
|
|
30
|
+
if (Object.keys(fields).length === 0) {
|
|
31
|
+
return { ok: true, identity: readIdentity() };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, identity: writeIdentity(fields) };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readProjectMessages } from "../../../core/messages-store.js";
|
|
2
|
+
import { resolveProject } from "../helpers.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: "tail_messages",
|
|
6
|
+
schema: {
|
|
7
|
+
type: "function",
|
|
8
|
+
function: {
|
|
9
|
+
name: "tail_messages",
|
|
10
|
+
description: "Tail project messages. Optional filter by channel and/or agent slug.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
project: { type: "string" },
|
|
15
|
+
channel: { type: "string", description: "e.g. telegram, engine, a2a, runtime, heartbeat" },
|
|
16
|
+
agent: { type: "string", description: "agent slug" },
|
|
17
|
+
limit: { type: "integer", description: "max rows; default 20" },
|
|
18
|
+
},
|
|
19
|
+
required: [],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
makeHandler: ({ projects }) => ({ project, channel, agent, limit = 20 } = {}) => {
|
|
24
|
+
const p = resolveProject(projects, project);
|
|
25
|
+
return readProjectMessages(p.path, {
|
|
26
|
+
channel,
|
|
27
|
+
agent_slug: agent,
|
|
28
|
+
limit: Math.min(limit, 100),
|
|
29
|
+
}).map((m) => ({
|
|
30
|
+
ts: m.ts,
|
|
31
|
+
channel: m.channel,
|
|
32
|
+
direction: m.direction,
|
|
33
|
+
author: m.author,
|
|
34
|
+
body: m.body,
|
|
35
|
+
}));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { confirmedProperty, resolveProject, safePathJoin } from "../helpers.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
name: "write_file",
|
|
7
|
+
schema: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "write_file",
|
|
11
|
+
description: "Create or overwrite a UTF-8 text file inside default or a project.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
project: { type: "string" },
|
|
16
|
+
path: { type: "string", description: "relative path inside the project" },
|
|
17
|
+
content: { type: "string" },
|
|
18
|
+
confirmed: confirmedProperty("true only after explicit user confirmation for this exact file write"),
|
|
19
|
+
},
|
|
20
|
+
required: ["path", "content"],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
makeHandler: ({ projects, requirePermission }) => ({ project, path: sub, content, confirmed = false }) => {
|
|
25
|
+
requirePermission("write_file", { dangerous: true, confirmed });
|
|
26
|
+
if (!sub) throw new Error("write_file: path required");
|
|
27
|
+
const p = resolveProject(projects, project);
|
|
28
|
+
const target = safePathJoin(p.path, sub);
|
|
29
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
30
|
+
fs.writeFileSync(target, content, "utf8");
|
|
31
|
+
return { ok: true, path: target, bytes: Buffer.byteLength(content, "utf8") };
|
|
32
|
+
},
|
|
33
|
+
};
|