@agentprojectcontext/apx 1.30.2 → 1.31.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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/skills/apx-agency-agents/SKILL.md +1 -1
  3. package/skills/apx-agent/SKILL.md +6 -6
  4. package/skills/apx-project/SKILL.md +1 -2
  5. package/src/core/agent/prompt-builder.js +6 -0
  6. package/src/core/agent/run-agent.js +21 -0
  7. package/src/core/agent/self-memory.js +1 -1
  8. package/src/core/agent-memory.js +64 -0
  9. package/src/core/agent-system.js +3 -2
  10. package/src/core/scaffold.js +43 -18
  11. package/src/core/tools/browser.js +169 -75
  12. package/src/core/tools/registry.js +13 -8
  13. package/src/core/tools/search.js +35 -7
  14. package/src/host/daemon/api/agents.js +19 -21
  15. package/src/host/daemon/api/sessions-search.js +1 -1
  16. package/src/host/daemon/api/shared.js +5 -8
  17. package/src/host/daemon/super-agent-tools/index.js +232 -43
  18. package/src/host/daemon/super-agent-tools/registry-bridge.js +30 -1
  19. package/src/host/daemon/super-agent-tools/tools/discover-tools.js +67 -0
  20. package/src/host/daemon/super-agent-tools/tools/import-agent.js +2 -0
  21. package/src/host/daemon/super-agent-tools/tools/read-agent-memory.js +5 -4
  22. package/src/host/daemon/super-agent.js +15 -17
  23. package/src/interfaces/cli/commands/agent.js +4 -1
  24. package/src/interfaces/cli/commands/memory.js +9 -10
  25. package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js → index-BV615I9p.js} +5 -5
  26. package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js.map → index-BV615I9p.js.map} +1 -1
  27. package/src/interfaces/web/dist/index.html +1 -1
  28. package/src/interfaces/web/package-lock.json +100 -211
  29. package/src/interfaces/web/src/i18n/en.ts +6 -6
  30. package/src/interfaces/web/src/i18n/es.ts +6 -6
  31. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
@@ -0,0 +1,67 @@
1
+ // discover_tools — lazy tool discovery + activation.
2
+ //
3
+ // Roby (and any super-agent surface) only carries a small "base" set of tool
4
+ // schemas on lightweight channels (Telegram/desktop/deck) to stay under
5
+ // cheap-tier TPM caps. The rest (browser/Puppeteer, fetch, web_search, runtime,
6
+ // voice, …) exist but are NOT sent to the model by default. This tool is how
7
+ // the model reveals and activates them on demand:
8
+ //
9
+ // discover_tools() → catalog of NOT-loaded tools
10
+ // discover_tools({ category: "browser" }) → activate a whole category
11
+ // discover_tools({ names: ["browser_navigate"] })→ activate specific tools
12
+ //
13
+ // Activation pushes the requested schemas into the per-turn tool session; the
14
+ // agent loop (run-agent.js) merges them into the live schema set so the NEXT
15
+ // model call can actually invoke them. Handlers for every tool already exist —
16
+ // gating is purely about which schemas the model sees.
17
+
18
+ export default {
19
+ name: "discover_tools",
20
+ schema: {
21
+ type: "function",
22
+ function: {
23
+ name: "discover_tools",
24
+ description:
25
+ "Discover and activate additional tools that are not loaded by default. " +
26
+ "Call with NO arguments to get the catalog of available-but-not-loaded tools " +
27
+ "(name + 1-line description, grouped by category). Call with `category` (e.g. " +
28
+ "\"browser\", \"fetch\") or `names` (exact tool names) to ACTIVATE those tools — " +
29
+ "they become callable starting on your next step. Use this whenever the tool you " +
30
+ "need (browser automation, HTTP fetch, web search, runtime delegation, voice, …) " +
31
+ "isn't in your current tool list.",
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ category: {
36
+ type: "string",
37
+ description:
38
+ "Activate every not-loaded tool in this category (e.g. \"browser\", \"fetch\", \"search\").",
39
+ },
40
+ names: {
41
+ type: "array",
42
+ items: { type: "string" },
43
+ description:
44
+ "Exact tool names to activate, e.g. [\"browser_navigate\", \"browser_screenshot\"].",
45
+ },
46
+ },
47
+ },
48
+ },
49
+ },
50
+ makeHandler: (ctx) => ({ category, names } = {}) => {
51
+ const session = ctx?.toolSession;
52
+ // No lazy session (full channels, or direct handler use in tests): every
53
+ // tool is already exposed, so there's nothing to discover or activate.
54
+ if (!session) {
55
+ return {
56
+ ok: true,
57
+ loaded_all: true,
58
+ note: "En este canal todas las tools ya están cargadas; no hace falta discover_tools.",
59
+ };
60
+ }
61
+ const wantsActivate =
62
+ (Array.isArray(names) && names.length > 0) ||
63
+ (typeof category === "string" && category.trim() !== "");
64
+ if (!wantsActivate) return session.catalogResponse();
65
+ return session.activate({ names, category });
66
+ },
67
+ };
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readVaultAgents, VAULT_DIR } from "../../../../core/parser.js";
4
4
  import { addImportedAgent, ensureAgentDir } from "../../../../core/scaffold.js";
5
+ import { ensureAgentRuntimeDir } from "../../../../core/agent-memory.js";
5
6
  import { confirmedProperty, projectMeta, resolveProject } from "../helpers.js";
6
7
 
7
8
  export default {
@@ -35,6 +36,7 @@ export default {
35
36
  const p = resolveProject(projects, project || "default");
36
37
  addImportedAgent(p.path, slug);
37
38
  ensureAgentDir(p.path, slug);
39
+ ensureAgentRuntimeDir(p, slug);
38
40
  projects.rebuild(p.id);
39
41
 
40
42
  return {
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
2
  import { resolveProject } from "../helpers.js";
3
+ import { agentMemoryPath, readAgentMemory } from "../../../../core/agent-memory.js";
4
4
 
5
5
  export default {
6
6
  name: "read_agent_memory",
@@ -21,8 +21,9 @@ export default {
21
21
  },
22
22
  makeHandler: ({ projects }) => ({ project, agent }) => {
23
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") };
24
+ const file = agentMemoryPath(p, agent);
25
+ const body = readAgentMemory(p, agent);
26
+ if (!body && !fs.existsSync(file)) return { error: `no memory.md for agent ${agent}` };
27
+ return { body, path: file };
27
28
  },
28
29
  };
@@ -1,5 +1,5 @@
1
1
  // Super-agent: daemon-level action agent for Telegram, TUI, desktop, routines.
2
- import { schemasForChannel, makeToolHandlers } from "./super-agent-tools/index.js";
2
+ import { createToolSession, buildLazyToolsBlock, makeToolHandlers } from "./super-agent-tools/index.js";
3
3
  import { listSkills } from "./skills-loader.js";
4
4
  import {
5
5
  runAgent,
@@ -79,6 +79,15 @@ export async function runSuperAgent({
79
79
  }
80
80
  }
81
81
 
82
+ // Per-turn tool session. Lightweight channels (telegram/desktop/deck) start
83
+ // on the small "base" set and expand on demand via discover_tools; full
84
+ // channels (routine/api/web/code/terminal) get the whole registry up front.
85
+ // The session also enforces role gating ("*" = unrestricted, [] = none,
86
+ // array = allowlist) on BOTH the initial set and any later activation, so a
87
+ // limited sender can't discover its way past the gate.
88
+ // noTools callers (summarize/ask) get no session — text only.
89
+ const toolSession = noTools ? null : createToolSession(channel, { allowedTools });
90
+
82
91
  const system = buildSuperAgentSystem({
83
92
  globalConfig,
84
93
  projects,
@@ -90,23 +99,12 @@ export async function runSuperAgent({
90
99
  systemSuffix,
91
100
  memoryBlock,
92
101
  activeThreadsBlock,
102
+ // Compact "tools you can activate" block (names only, no schemas). Empty on
103
+ // full channels and tool-free callers, where it's omitted from the prompt.
104
+ lazyToolsBlock: buildLazyToolsBlock(toolSession),
93
105
  });
94
106
 
95
- // Pick the schema subset for this channel: chit-chat surfaces get a small
96
- // "core" set (~700 tokens) to fit cheap-tier TPM caps; routines get the
97
- // full registry. The model can still call load_skill / read more on demand.
98
- // noTools callers (summarize/ask) get an empty set — text only.
99
- let toolSchemas = noTools ? [] : schemasForChannel(channel);
100
- // Role gating: restrict the visible tools for limited senders (e.g. guests
101
- // on Telegram). "*" = unrestricted; [] = no tools; array = allowlist.
102
- if (allowedTools !== "*" && Array.isArray(allowedTools)) {
103
- if (allowedTools.length === 0) {
104
- toolSchemas = [];
105
- } else {
106
- const allow = new Set(allowedTools);
107
- toolSchemas = toolSchemas.filter((t) => allow.has(t?.function?.name || t?.name));
108
- }
109
- }
107
+ const toolSchemas = noTools ? [] : toolSession.initialSchemas;
110
108
 
111
109
  return runAgent({
112
110
  globalConfig,
@@ -116,7 +114,7 @@ export async function runSuperAgent({
116
114
  overrideModel,
117
115
  toolSchemas,
118
116
  makeToolHandlers,
119
- toolHandlerCtx: { projects, plugins, registries, globalConfig, channel },
117
+ toolHandlerCtx: { projects, plugins, registries, globalConfig, channel, toolSession },
120
118
  onEvent,
121
119
  signal,
122
120
  onToken,
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { findApfRoot, readAgents, readVaultAgents, readVaultAgent, VAULT_DIR, SLUG_RE } from "../../../core/parser.js";
4
4
  import { writeAgentFile, writeVaultAgentFile, removeVaultAgent, restoreVaultAgent, addImportedAgent, ensureAgentDir } from "../../../core/scaffold.js";
5
+ import { ensureAgentRuntimeDir, agentMemoryPath } from "../../../core/agent-memory.js";
5
6
  import { http } from "../http.js";
6
7
 
7
8
  // ── ANSI ──────────────────────────────────────────────────────────────────────
@@ -49,6 +50,7 @@ export async function cmdAgentAdd(args) {
49
50
 
50
51
  writeAgentFile(root, slug, fields);
51
52
  ensureAgentDir(root, slug);
53
+ ensureAgentRuntimeDir(root, slug);
52
54
  await nudgeDaemon(root);
53
55
 
54
56
  console.log(`Added agent ${slug}`);
@@ -207,10 +209,11 @@ export async function cmdAgentImport(args) {
207
209
  addImportedAgent(root, slug);
208
210
  console.log(`\n ${bold(slug)} imported from vault ${tag("↑ vault")}\n`);
209
211
  console.log(gray(` definition: ${vaultPath}`));
210
- console.log(gray(` memory: ${path.join(root, ".apc", "agents", slug, "memory.md")} (project-local)`));
212
+ console.log(gray(` memory: ${agentMemoryPath(root, slug)} (runtime-local)`));
211
213
  console.log();
212
214
  }
213
215
 
214
216
  ensureAgentDir(root, slug);
217
+ ensureAgentRuntimeDir(root, slug);
215
218
  await nudgeDaemon(root);
216
219
  }
@@ -1,7 +1,6 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
2
  import { findApfRoot } from "../../../core/parser.js";
4
- import { ensureAgentDir } from "../../../core/scaffold.js";
3
+ import { agentMemoryPath, readAgentMemory, writeAgentMemory, ensureAgentRuntimeDir } from "../../../core/agent-memory.js";
5
4
  import { http } from "../http.js";
6
5
 
7
6
  function requireRoot() {
@@ -23,12 +22,11 @@ export async function cmdMemory(args) {
23
22
  const slug = args._[0];
24
23
  if (!slug) throw new Error("apx memory: missing <agent-slug>");
25
24
  const root = requireRoot();
26
- const memPath = path.join(root, ".apc", "agents", slug, "memory.md");
25
+ const memPath = agentMemoryPath(root, slug);
27
26
 
28
27
  if (args.flags.replace) {
29
28
  const newBody = readStdinSync();
30
- ensureAgentDir(root, slug);
31
- fs.writeFileSync(memPath, newBody);
29
+ writeAgentMemory(root, slug, newBody);
32
30
  await nudgeDaemon(root);
33
31
  console.log(`replaced memory for ${slug} (${Buffer.byteLength(newBody)} bytes)`);
34
32
  return;
@@ -36,23 +34,24 @@ export async function cmdMemory(args) {
36
34
 
37
35
  if (args.flags.append && args.flags.append !== true) {
38
36
  const note = String(args.flags.append);
39
- ensureAgentDir(root, slug);
40
- let body = fs.existsSync(memPath) ? fs.readFileSync(memPath, "utf8") : "";
37
+ ensureAgentRuntimeDir(root, slug);
38
+ let body = readAgentMemory(root, slug);
41
39
  if (!/##\s+Recent context/i.test(body)) {
42
40
  body += body.endsWith("\n") ? "\n## Recent context\n" : "\n\n## Recent context\n";
43
41
  }
44
42
  const today = new Date().toISOString().slice(0, 10);
45
43
  body = body.replace(/(##\s+Recent context\s*\n)/i, `$1- ${today}: ${note}\n`);
46
- fs.writeFileSync(memPath, body);
44
+ writeAgentMemory(root, slug, body);
47
45
  await nudgeDaemon(root);
48
46
  console.log(`appended to ${slug} memory: ${note}`);
49
47
  return;
50
48
  }
51
49
 
52
- if (!fs.existsSync(memPath)) {
50
+ const body = readAgentMemory(root, slug);
51
+ if (!body && !fs.existsSync(memPath)) {
53
52
  throw new Error(`no memory for "${slug}" — agent dir not yet created`);
54
53
  }
55
- process.stdout.write(fs.readFileSync(memPath, "utf8"));
54
+ process.stdout.write(body);
56
55
  }
57
56
 
58
57
  function readStdinSync() {