@agentprojectcontext/apx 1.11.0 → 1.12.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -359,9 +359,14 @@ async function runPrompt(pid, state, previousMessages, renderScreen, text, userI
359
359
  const startTime = Date.now();
360
360
 
361
361
  try {
362
+ const cwd = process.cwd();
362
363
  const body = {
363
364
  prompt: `[Mode: ${MODES[state.currentModeIdx]}]\n${text}`,
364
- contextNote: "Channel: terminal. Format freely using markdown, but keep it readable. Use code diffs when editing.",
365
+ contextNote: [
366
+ "Channel: terminal. Format freely using markdown, but keep it readable. Use code diffs when editing.",
367
+ `CWD: ${cwd}`,
368
+ "When the user says \"este directorio\", \"este proyecto\", \"acá\", \"aquí\", \"this directory\", \"current dir\" or any equivalent reference without naming a path, they mean exactly the CWD above. Use it as the path argument directly — don't ask the user to provide it.",
369
+ ].join("\n"),
365
370
  previousMessages,
366
371
  model: state.activeModel,
367
372
  };
@@ -20,8 +20,9 @@ import setIdentity from "./tools/set-identity.js";
20
20
  import setPermissionMode from "./tools/set-permission-mode.js";
21
21
  import searchFiles from "./tools/search-files.js";
22
22
  import { createPermissionGuard } from "./helpers.js";
23
+ import { buildBridgedTools, DEFAULT_CATEGORIES } from "./registry-bridge.js";
23
24
 
24
- const TOOLS = [
25
+ const NATIVE_TOOLS = [
25
26
  listProjects,
26
27
  listAgents,
27
28
  listVaultAgents,
@@ -45,6 +46,18 @@ const TOOLS = [
45
46
  searchFiles,
46
47
  ];
47
48
 
49
+ // Registry-backed bridges. Categories can be overridden per-process via env
50
+ // APX_BRIDGE_CATEGORIES (comma-separated), e.g. "browser,fetch,search".
51
+ // Default: browser, fetch, search, glob, grep (see registry-bridge.js).
52
+ function resolveBridgeCategories() {
53
+ const env = (process.env.APX_BRIDGE_CATEGORIES || "").trim();
54
+ if (!env) return DEFAULT_CATEGORIES;
55
+ return new Set(env.split(",").map(s => s.trim()).filter(Boolean));
56
+ }
57
+
58
+ const BRIDGED_TOOLS = buildBridgedTools({ categories: resolveBridgeCategories() });
59
+ const TOOLS = [...NATIVE_TOOLS, ...BRIDGED_TOOLS];
60
+
48
61
  export const TOOL_SCHEMAS = TOOLS.map((tool) => tool.schema);
49
62
 
50
63
  export function makeToolHandlers(ctx) {
@@ -56,3 +69,8 @@ export function makeToolHandlers(ctx) {
56
69
  };
57
70
  return Object.fromEntries(TOOLS.map((tool) => [tool.name, tool.makeHandler(toolCtx)]));
58
71
  }
72
+
73
+ // Diagnostic helper — useful for `apx daemon status` or debug logging.
74
+ export function listBridgedToolNames() {
75
+ return BRIDGED_TOOLS.map(t => t.name);
76
+ }
@@ -0,0 +1,122 @@
1
+ // daemon/super-agent-tools/registry-bridge.js
2
+ //
3
+ // Generic bridge that exposes registry-backed HTTP tools (browser, fetch,
4
+ // search, glob, grep, etc.) to the super-agent — no per-tool import boilerplate.
5
+ //
6
+ // How it works:
7
+ // 1. Read TOOL_DEFINITIONS from daemon/tools/registry.js
8
+ // 2. Drop entries whose names collide with native super-agent tools (those
9
+ // win — they touch in-process state directly).
10
+ // 3. For each remaining entry, produce { name, schema, makeHandler } in the
11
+ // exact shape index.js expects, so they slot into TOOL_SCHEMAS alongside
12
+ // the native ones.
13
+ // 4. The generated handler POSTs/GETs to the daemon's own HTTP server on
14
+ // 127.0.0.1:<port>. Yes, the super-agent talks to its own daemon — that
15
+ // keeps the bridge dead-simple, lets the engine adapter format tool
16
+ // schemas uniformly, and reuses the exact code path external callers hit.
17
+ //
18
+ // Net result: adding a tool = adding one entry to registry.js. No file in
19
+ // super-agent-tools/tools/, no import in index.js.
20
+
21
+ import { TOOL_DEFINITIONS } from "../tools/registry.js";
22
+
23
+ // Native handlers in super-agent-tools/tools/ that own these names. The bridge
24
+ // MUST skip them or the registry version (HTTP roundtrip) would shadow the
25
+ // native one with possibly different semantics.
26
+ const NATIVE_NAMES = new Set([
27
+ "list_projects", "list_agents", "list_vault_agents", "import_agent",
28
+ "add_project", "list_mcps", "read_agent_memory",
29
+ "list_files", "read_file", "write_file", "edit_file", "search_files",
30
+ "run_shell", "tail_messages", "search_messages",
31
+ "call_agent", "call_mcp", "call_runtime",
32
+ "send_telegram", "set_identity", "set_permission_mode",
33
+ ]);
34
+
35
+ // Default allow-list of categories the bridge will expose. The NATIVE_NAMES
36
+ // filter handles duplicates inside these categories (e.g. "file" contains
37
+ // both read_file [native] and glob [bridged]). Anything outside is ignored
38
+ // — "shell"/"mcp"/"memory"/"session" have different semantics handled
39
+ // natively. Override with env APX_BRIDGE_CATEGORIES.
40
+ const DEFAULT_CATEGORIES = new Set(["browser", "fetch", "search", "file"]);
41
+
42
+ function buildSchema(entry) {
43
+ return {
44
+ type: "function",
45
+ function: {
46
+ name: entry.name,
47
+ description: entry.description,
48
+ parameters: entry.parameters || { type: "object", properties: {} },
49
+ },
50
+ };
51
+ }
52
+
53
+ function buildHandler(entry) {
54
+ return ({ globalConfig }) => async (args = {}) => {
55
+ const port = globalConfig?.port || process.env.APX_PORT || 7430;
56
+ const method = String(entry.endpoint?.method || "POST").toUpperCase();
57
+ let url = `http://127.0.0.1:${port}${entry.endpoint?.path || ""}`;
58
+
59
+ const opts = {
60
+ method,
61
+ headers: { "content-type": "application/json" },
62
+ };
63
+
64
+ if (method === "GET" || method === "HEAD") {
65
+ const qs = new URLSearchParams();
66
+ for (const [k, v] of Object.entries(args)) {
67
+ if (v === undefined || v === null) continue;
68
+ qs.set(k, typeof v === "object" ? JSON.stringify(v) : String(v));
69
+ }
70
+ const q = qs.toString();
71
+ if (q) url += (url.includes("?") ? "&" : "?") + q;
72
+ } else {
73
+ opts.body = JSON.stringify(args);
74
+ }
75
+
76
+ let res, text;
77
+ try {
78
+ res = await fetch(url, opts);
79
+ text = await res.text();
80
+ } catch (e) {
81
+ return { error: `bridge fetch failed: ${e.message}`, url };
82
+ }
83
+
84
+ let parsed;
85
+ try { parsed = JSON.parse(text); }
86
+ catch { parsed = { raw: text }; }
87
+
88
+ if (!res.ok) {
89
+ return {
90
+ error: parsed?.error || `HTTP ${res.status}`,
91
+ status: res.status,
92
+ ...(typeof parsed === "object" ? parsed : {}),
93
+ };
94
+ }
95
+ return parsed;
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Returns an array of tool objects in the shape super-agent-tools/index.js
101
+ * expects: { name, schema, makeHandler }.
102
+ *
103
+ * @param {object} opts
104
+ * @param {Set<string>=} opts.categories override DEFAULT_CATEGORIES
105
+ * @param {Set<string>=} opts.skipNames extra names to skip in addition to NATIVE_NAMES
106
+ */
107
+ export function buildBridgedTools(opts = {}) {
108
+ const categories = opts.categories instanceof Set ? opts.categories : DEFAULT_CATEGORIES;
109
+ const skipNames = opts.skipNames instanceof Set ? opts.skipNames : new Set();
110
+
111
+ return TOOL_DEFINITIONS
112
+ .filter(e => categories.has(e.category))
113
+ .filter(e => !NATIVE_NAMES.has(e.name) && !skipNames.has(e.name))
114
+ .filter(e => e.endpoint?.path)
115
+ .map(entry => ({
116
+ name: entry.name,
117
+ schema: buildSchema(entry),
118
+ makeHandler: buildHandler(entry),
119
+ }));
120
+ }
121
+
122
+ export { NATIVE_NAMES, DEFAULT_CATEGORIES };
@@ -61,7 +61,8 @@ HARD RULES (do not deviate):
61
61
  15. 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.
62
62
  16. IDENTITY RULE: when the user asks you to change your name, call yourself something, or update your personality/language, call set_identity and persist the change. Then confirm with your new name.
63
63
  17. ROUTINES RULE: NEVER create a routine in the default project (id=0). Routines MUST be tied to a specific registered project. Before adding a routine, call list_projects to find the correct project id or name. Then pass --project <id|name> to apx routine add. If no project fits, ask the user which project to use. Creating routines in project 0/default mixes unrelated projects' schedules and corrupts state.
64
- 18. **NO EMPTY RESPONSES**: Never respond with only text when you have tools available and the user is asking you to DO something. Call the tool FIRST, then explain. Never say "I'll do X" without immediately calling the tool. Empty acknowledgments ("ok", "entendido", "dame un minuto", "voy", "checking", "stand by") without a tool call are invalid responses — they will be re-prompted and waste a turn.`;
64
+ 18. **NO EMPTY RESPONSES**: Never respond with only text when you have tools available and the user is asking you to DO something. Call the tool FIRST, then explain. Never say "I'll do X" without immediately calling the tool. Empty acknowledgments ("ok", "entendido", "dame un minuto", "voy", "checking", "stand by") without a tool call are invalid responses — they will be re-prompted and waste a turn.
65
+ 19. **CWD RULE**: When the channel context includes a "CWD: <path>" line, that is the user's current working directory. References to "este directorio", "este proyecto", "esta carpeta", "acá", "aquí", "this directory", "this project", "current dir/folder" all mean that exact CWD path. Use it as the path argument directly — DO NOT ask the user "what's the path?" when CWD is already given. Example: if user says "agregá este proyecto a la lista", call add_project({path: <CWD>}) immediately.`;
65
66
 
66
67
  function isShortConfirmation(text) {
67
68
  return /^(yes|y|si|si dale|dale|ok|okay|confirm|confirmed|go|proceed|do it)\b/i