@agentprojectcontext/apx 1.31.0 → 1.31.2

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 (49) hide show
  1. package/README.md +0 -1
  2. package/package.json +1 -1
  3. package/skills/apc-context/SKILL.md +0 -1
  4. package/skills/apx-agency-agents/SKILL.md +1 -1
  5. package/skills/apx-agent/SKILL.md +6 -6
  6. package/skills/apx-project/SKILL.md +1 -2
  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/confirmation/adapters/code.js +41 -0
  11. package/src/core/confirmation/adapters/telegram.js +134 -0
  12. package/src/core/confirmation/adapters/terminal.js +35 -0
  13. package/src/core/confirmation/adapters/web.js +53 -0
  14. package/src/core/confirmation/index.js +44 -0
  15. package/src/core/confirmation/pending-store.js +68 -0
  16. package/src/core/scaffold.js +43 -18
  17. package/src/core/tools/registry.js +7 -7
  18. package/src/host/daemon/api/agents.js +19 -21
  19. package/src/host/daemon/api/code.js +2 -0
  20. package/src/host/daemon/api/confirm.js +30 -0
  21. package/src/host/daemon/api/sessions-search.js +1 -1
  22. package/src/host/daemon/api/shared.js +5 -8
  23. package/src/host/daemon/api/super-agent.js +12 -4
  24. package/src/host/daemon/api.js +2 -0
  25. package/src/host/daemon/plugins/telegram.js +28 -0
  26. package/src/host/daemon/super-agent-tools/helpers.js +27 -6
  27. package/src/host/daemon/super-agent-tools/index.js +1 -0
  28. package/src/host/daemon/super-agent-tools/tools/add-project.js +2 -2
  29. package/src/host/daemon/super-agent-tools/tools/call-mcp.js +1 -1
  30. package/src/host/daemon/super-agent-tools/tools/call-runtime.js +1 -1
  31. package/src/host/daemon/super-agent-tools/tools/edit-file.js +2 -2
  32. package/src/host/daemon/super-agent-tools/tools/import-agent.js +4 -2
  33. package/src/host/daemon/super-agent-tools/tools/read-agent-memory.js +5 -4
  34. package/src/host/daemon/super-agent-tools/tools/run-shell.js +1 -1
  35. package/src/host/daemon/super-agent-tools/tools/search-files.js +1 -4
  36. package/src/host/daemon/super-agent-tools/tools/send-telegram.js +1 -1
  37. package/src/host/daemon/super-agent-tools/tools/set-identity.js +2 -2
  38. package/src/host/daemon/super-agent-tools/tools/set-permission-mode.js +2 -2
  39. package/src/host/daemon/super-agent-tools/tools/write-file.js +2 -2
  40. package/src/host/daemon/super-agent.js +5 -1
  41. package/src/interfaces/cli/commands/agent.js +4 -1
  42. package/src/interfaces/cli/commands/memory.js +9 -10
  43. package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js → index-BV615I9p.js} +5 -5
  44. package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js.map → index-BV615I9p.js.map} +1 -1
  45. package/src/interfaces/web/dist/index.html +1 -1
  46. package/src/interfaces/web/package-lock.json +3 -3
  47. package/src/interfaces/web/src/i18n/en.ts +6 -6
  48. package/src/interfaces/web/src/i18n/es.ts +6 -6
  49. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
@@ -619,6 +619,8 @@ function makeInlineHandlers({ projects, registries }) {
619
619
  memory_list: async (body) => {
620
620
  const { default: fs } = await import("node:fs");
621
621
  const { default: path } = await import("node:path");
622
+ const { readAgents } = await import("../parser.js");
623
+ const { agentMemoryPath } = await import("../agent-memory.js");
622
624
  // Find the project
623
625
  const all = projects.list();
624
626
  let p = null;
@@ -629,15 +631,13 @@ function makeInlineHandlers({ projects, registries }) {
629
631
  }
630
632
  if (!p) p = projects.get(all.filter((x) => x.id !== 0)[0]?.id) || projects.get(0);
631
633
  if (!p) throw new Error("no project registered");
632
- const agentsDir = path.join(p.path, ".apc", "agents");
633
- if (!fs.existsSync(agentsDir)) return { agents_with_memory: [] };
634
- const result = fs.readdirSync(agentsDir).filter((slug) => {
635
- return fs.existsSync(path.join(agentsDir, slug, "memory.md"));
636
- }).map((slug) => {
637
- const memPath = path.join(agentsDir, slug, "memory.md");
634
+ const result = readAgents(p.path).map((agent) => {
635
+ const slug = agent.slug;
636
+ const memPath = agentMemoryPath(p, slug);
637
+ if (!fs.existsSync(memPath)) return null;
638
638
  const stat = fs.statSync(memPath);
639
639
  return { agent: slug, path: memPath, size: stat.size, mtime: stat.mtime };
640
- });
640
+ }).filter(Boolean);
641
641
  return { project: p.path, agents_with_memory: result };
642
642
  },
643
643
  };
@@ -14,6 +14,13 @@ import {
14
14
  restoreVaultAgent,
15
15
  ensureAgentDir,
16
16
  } from "../../../core/scaffold.js";
17
+ import {
18
+ ensureAgentRuntimeDir,
19
+ readAgentMemory,
20
+ writeAgentMemory,
21
+ agentMemoryPath,
22
+ legacyAgentMemoryPath,
23
+ } from "../../../core/agent-memory.js";
17
24
  import { agentToResponse } from "./shared.js";
18
25
 
19
26
  // Lowercase the patch keys we accept on the vault and turn skills/tools into
@@ -114,6 +121,7 @@ export function register(app, { projects, project }) {
114
121
  try {
115
122
  writeAgentFile(p.path, slug, vault.fields || {}, vault.body || "");
116
123
  ensureAgentDir(p.path, slug);
124
+ ensureAgentRuntimeDir(p, slug);
117
125
  projects.rebuild(p.id);
118
126
  res.status(201).json(agentToResponse(readAgents(p.path).find((a) => a.slug === slug)));
119
127
  } catch (e) {
@@ -133,10 +141,7 @@ export function register(app, { projects, project }) {
133
141
  const agents = readAgents(p.path);
134
142
  const a = agents.find((x) => x.slug === req.params.slug);
135
143
  if (!a) return res.status(404).json({ error: "agent not found" });
136
- const memPath = path.join(p.path, ".apc", "agents", a.slug, "memory.md");
137
- const memory = fs.existsSync(memPath)
138
- ? fs.readFileSync(memPath, "utf8")
139
- : "";
144
+ const memory = readAgentMemory(p, a.slug);
140
145
  res.json({ ...agentToResponse(a), memory, system: a.body || "" });
141
146
  });
142
147
 
@@ -163,6 +168,7 @@ export function register(app, { projects, project }) {
163
168
  Parent: parent || null,
164
169
  });
165
170
  ensureAgentDir(p.path, slug);
171
+ ensureAgentRuntimeDir(p, slug);
166
172
  projects.rebuild(p.id);
167
173
  const created = readAgents(p.path).find((a) => a.slug === slug);
168
174
  res.status(201).json(agentToResponse(created));
@@ -203,6 +209,7 @@ export function register(app, { projects, project }) {
203
209
  try {
204
210
  writeAgentFile(p.path, slug, fields, body);
205
211
  ensureAgentDir(p.path, slug);
212
+ ensureAgentRuntimeDir(p, slug);
206
213
  projects.rebuild(p.id);
207
214
  const updated = readAgents(p.path).find((a) => a.slug === slug);
208
215
  res.json(agentToResponse(updated));
@@ -211,18 +218,20 @@ export function register(app, { projects, project }) {
211
218
  }
212
219
  });
213
220
 
214
- // Delete an agent: removes .apc/agents/<slug>.md and its data dir.
221
+ // Delete an agent: removes .apc/agents/<slug>.md and runtime data dir.
215
222
  app.delete("/projects/:pid/agents/:slug", (req, res) => {
216
223
  const p = project(req, res);
217
224
  if (!p) return;
218
225
  const slug = req.params.slug;
219
226
  const file = path.join(p.path, ".apc", "agents", `${slug}.md`);
220
- const dir = path.join(p.path, ".apc", "agents", slug);
221
- if (!fs.existsSync(file) && !fs.existsSync(dir))
227
+ const runtimeDir = path.dirname(agentMemoryPath(p, slug));
228
+ const legacyDir = path.dirname(legacyAgentMemoryPath(p.path, slug));
229
+ if (!fs.existsSync(file) && !fs.existsSync(runtimeDir) && !fs.existsSync(legacyDir))
222
230
  return res.status(404).json({ error: "agent not found" });
223
231
  try {
224
232
  if (fs.existsSync(file)) fs.rmSync(file);
225
- if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
233
+ if (fs.existsSync(runtimeDir)) fs.rmSync(runtimeDir, { recursive: true, force: true });
234
+ if (fs.existsSync(legacyDir)) fs.rmSync(legacyDir, { recursive: true, force: true });
226
235
  projects.rebuild(p.id);
227
236
  res.json({ ok: true });
228
237
  } catch (e) {
@@ -257,15 +266,7 @@ export function register(app, { projects, project }) {
257
266
  app.get("/projects/:pid/agents/:slug/memory", (req, res) => {
258
267
  const p = project(req, res);
259
268
  if (!p) return;
260
- const memPath = path.join(
261
- p.path,
262
- ".apc",
263
- "agents",
264
- req.params.slug,
265
- "memory.md"
266
- );
267
- if (!fs.existsSync(memPath)) return res.json({ body: "" });
268
- res.json({ body: fs.readFileSync(memPath, "utf8") });
269
+ res.json({ body: readAgentMemory(p, req.params.slug) });
269
270
  });
270
271
 
271
272
  app.put("/projects/:pid/agents/:slug/memory", (req, res) => {
@@ -274,10 +275,7 @@ export function register(app, { projects, project }) {
274
275
  const { body } = req.body || {};
275
276
  if (typeof body !== "string")
276
277
  return res.status(400).json({ error: "body must be string" });
277
- const dir = path.join(p.path, ".apc", "agents", req.params.slug);
278
- fs.mkdirSync(path.join(dir, "sessions"), { recursive: true });
279
- const memPath = path.join(dir, "memory.md");
280
- fs.writeFileSync(memPath, body);
278
+ writeAgentMemory(p, req.params.slug, body);
281
279
  projects.rebuild(p.id);
282
280
  res.json({ ok: true, bytes: Buffer.byteLength(body, "utf8") });
283
281
  });
@@ -14,6 +14,7 @@
14
14
  // + per-mode tool gating), then persists the rich assistant turn.
15
15
  import { runSuperAgent } from "../super-agent.js";
16
16
  import { appendSuperAgentErrorTrace } from "./shared.js";
17
+ import { createWebConfirmAdapter } from "../../../core/confirmation/adapters/web.js";
17
18
  import {
18
19
  listCodeSessions,
19
20
  getCodeSession,
@@ -295,6 +296,7 @@ export function register(app, { projects, project, config, registries, plugins }
295
296
  // it keeps the normal "text ends the turn" behavior.
296
297
  completionContract: mode === "build",
297
298
  onEvent,
299
+ requestConfirmation: createWebConfirmAdapter({ onEvent }),
298
300
  });
299
301
  projects.rebuild(p.id);
300
302
 
@@ -0,0 +1,30 @@
1
+ // Confirmation resolution endpoint for web and TUI channels.
2
+ //
3
+ // POST /super-agent/confirm/:correlationId
4
+ // Body: { confirmed: boolean }
5
+ //
6
+ // Called by the frontend after the user responds to a "confirmation_required"
7
+ // SSE event emitted by the web confirmation adapter. Resolves the pending
8
+ // Promise in the agent loop, unblocking it to continue with confirmed: true
9
+ // or to return a cancelled error.
10
+
11
+ import { getConfirmationStore } from "../../../core/confirmation/pending-store.js";
12
+
13
+ export function register(app) {
14
+ app.post("/super-agent/confirm/:correlationId", async (req, res) => {
15
+ const { correlationId } = req.params;
16
+ const { confirmed } = req.body;
17
+
18
+ if (typeof confirmed !== "boolean") {
19
+ return res.status(400).json({ error: "confirmed must be a boolean" });
20
+ }
21
+
22
+ const resolved = getConfirmationStore().resolve(correlationId, confirmed);
23
+
24
+ if (!resolved) {
25
+ return res.status(404).json({ error: "confirmation not found or already expired" });
26
+ }
27
+
28
+ return res.json({ ok: true, confirmed });
29
+ });
30
+ }
@@ -31,7 +31,7 @@ export function register(app, { projects, config }) {
31
31
  for (const p of targetProjects) {
32
32
  if (!p) continue;
33
33
 
34
- // 1) Session files in the repo (.apc/agents/<slug>/sessions/)
34
+ // 1) Legacy session files in the repo (.apc/agents/<slug>/sessions/)
35
35
  const sessionAgentsDir = path.join(p.path, ".apc", "agents");
36
36
  if (fs.existsSync(sessionAgentsDir)) {
37
37
  for (const slug of fs.readdirSync(sessionAgentsDir)) {
@@ -6,6 +6,8 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { appendErrorTrace, previewText } from "../../../core/logging.js";
9
+ import { readAgents } from "../../../core/parser.js";
10
+ import { agentMemoryPath } from "../../../core/agent-memory.js";
9
11
 
10
12
  export const nowIso = () =>
11
13
  new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -110,15 +112,10 @@ export function makeTopProjectResolver(projects) {
110
112
  }
111
113
 
112
114
  // Pick the memory.md to use when /memory is called without an agent ref.
113
- // Prefer the first .apc/agents/<slug>/memory.md; else .apc/memory.md.
115
+ // Prefer the first agent's runtime-local memory; else project-level .apc/memory.md.
114
116
  export function resolveMemoryPath(p) {
115
- const agentsDir = path.join(p.path, ".apc", "agents");
116
- if (fs.existsSync(agentsDir)) {
117
- const slugs = fs.readdirSync(agentsDir).filter((s) =>
118
- fs.statSync(path.join(agentsDir, s)).isDirectory()
119
- );
120
- if (slugs.length) return path.join(agentsDir, slugs[0], "memory.md");
121
- }
117
+ const firstAgent = readAgents(p.path)[0];
118
+ if (firstAgent) return agentMemoryPath(p, firstAgent.slug);
122
119
  return path.join(p.path, ".apc", "memory.md");
123
120
  }
124
121
 
@@ -12,6 +12,7 @@ import {
12
12
  } from "./shared.js";
13
13
  import { loggerFor } from "../../../core/logging.js";
14
14
  import { appendGlobalMessage } from "../../../core/messages-store.js";
15
+ import { createWebConfirmAdapter } from "../../../core/confirmation/adapters/web.js";
15
16
 
16
17
  const log = loggerFor("super-agent");
17
18
 
@@ -79,6 +80,15 @@ export function register(app, { projects, registries, plugins, project, config }
79
80
  res.write(JSON.stringify(event) + "\n");
80
81
  };
81
82
 
83
+ const onEvent = wrapOnEventForLog(send, {
84
+ trace_id: req.apxTraceId,
85
+ channel: ctx.channel,
86
+ });
87
+
88
+ // Web/TUI channels receive a "confirmation_required" SSE event and respond
89
+ // via POST /super-agent/confirm/:correlationId (see api/confirm.js).
90
+ const requestConfirmation = createWebConfirmAdapter({ onEvent });
91
+
82
92
  try {
83
93
  const saResult = await runSuperAgent({
84
94
  globalConfig: config,
@@ -94,10 +104,8 @@ export function register(app, { projects, registries, plugins, project, config }
94
104
  ...(Number.isFinite(Number(maxIters)) ? { maxIters: Number(maxIters) } : {}),
95
105
  ...(Number.isFinite(Number(maxTokens)) ? { maxTokens: Number(maxTokens) } : {}),
96
106
  ...(completionContract ? { completionContract: true } : {}),
97
- onEvent: wrapOnEventForLog(send, {
98
- trace_id: req.apxTraceId,
99
- channel: ctx.channel,
100
- }),
107
+ onEvent,
108
+ requestConfirmation,
101
109
  });
102
110
  projects.rebuild(p.id);
103
111
  logWebTurn(ctx.channel, { prompt, replyText: saResult.text });
@@ -46,6 +46,7 @@ import { register as registerAdmin } from "./api/admin.js";
46
46
  import { register as registerAdminConfig } from "./api/admin-config.js";
47
47
  import { register as registerIdentity } from "./api/identity.js";
48
48
  import { register as registerWeb } from "./api/web.js";
49
+ import { register as registerConfirm } from "./api/confirm.js";
49
50
 
50
51
  export function buildApi({
51
52
  projects,
@@ -108,6 +109,7 @@ export function buildApi({
108
109
  registerEngines(app, ctx);
109
110
  registerExec(app, ctx);
110
111
  registerSuperAgent(app, ctx);
112
+ registerConfirm(app, ctx);
111
113
  registerCode(app, ctx);
112
114
  registerConversations(app, ctx);
113
115
  registerConnections(app, ctx);
@@ -41,6 +41,8 @@ import { transcribe as transcribeAudioFile } from "../transcription.js";
41
41
  import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "../../../core/identity.js";
42
42
  import { registerSender, resolveAllowedTools } from "../../../core/telegram-identity.js";
43
43
  import { buildRelationshipBlock } from "../../../core/agent/index.js";
44
+ import { getConfirmationStore as getConfirmStore } from "../../../core/confirmation/pending-store.js";
45
+ import { createTelegramConfirmAdapter } from "../../../core/confirmation/adapters/telegram.js";
44
46
 
45
47
  const API_BASE = "https://api.telegram.org";
46
48
  const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -422,6 +424,13 @@ class ChannelPoller {
422
424
 
423
425
  async _handleUpdate(u) {
424
426
  this.lastUpdateAt = nowIso();
427
+
428
+ // Inline keyboard button press: route to the confirmation adapter.
429
+ if (u.callback_query) {
430
+ await this._handleCallbackQuery(u.callback_query);
431
+ return;
432
+ }
433
+
425
434
  const msg = u.message || u.edited_message;
426
435
  if (!msg) return;
427
436
  const target = this.resolveProject();
@@ -806,6 +815,12 @@ class ChannelPoller {
806
815
  }
807
816
  };
808
817
 
818
+ const confirmAdapter = createTelegramConfirmAdapter({
819
+ token: resolveBotToken(this.channel),
820
+ chatId: chat_id,
821
+ pendingStore: getConfirmStore(),
822
+ });
823
+
809
824
  try {
810
825
  const sa = await runSuperAgent({
811
826
  globalConfig: this.globalConfig,
@@ -826,6 +841,7 @@ class ChannelPoller {
826
841
  }),
827
842
  signal: abortCtrl.signal,
828
843
  onEvent,
844
+ requestConfirmation: confirmAdapter.requestConfirmation,
829
845
  });
830
846
  replyText = sa.text;
831
847
  replyAuthor = sa.name || agentDisplay;
@@ -911,6 +927,18 @@ class ChannelPoller {
911
927
  }
912
928
  }
913
929
 
930
+ async _handleCallbackQuery(callbackQuery) {
931
+ const adapter = createTelegramConfirmAdapter({
932
+ token: resolveBotToken(this.channel),
933
+ chatId: callbackQuery.message?.chat?.id,
934
+ pendingStore: getConfirmStore(),
935
+ });
936
+ const handled = await adapter.handleCallbackQuery(callbackQuery);
937
+ if (!handled) {
938
+ this.log(`telegram[${this.channel.name}] unhandled callback_query: ${callbackQuery.data}`);
939
+ }
940
+ }
941
+
914
942
  // Show "typing..." indicator in the chat. Telegram clears it automatically
915
943
  // after 5 seconds, so call this every ~4s while a long operation is going.
916
944
  async _typing(chat_id) {
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { agentSkills, buildAgentSystem as buildCoreAgentSystem } from "../../../core/agent-system.js";
3
+ import { buildConfirmDescription } from "../../../core/confirmation/index.js";
3
4
 
4
5
  export function projectMeta(projects, entry) {
5
6
  const meta = projects.list().find((p) => p.id === entry.id);
@@ -79,19 +80,39 @@ export function buildAgentSystem(project, agent, opts = {}) {
79
80
  return buildCoreAgentSystem(project, agent, opts);
80
81
  }
81
82
 
82
- export function createPermissionGuard(globalConfig = {}, { implicitConfirmation = false } = {}) {
83
+ export function createPermissionGuard(globalConfig = {}, {
84
+ implicitConfirmation = false,
85
+ requestConfirmation = null,
86
+ } = {}) {
83
87
  const permissionMode = globalConfig.super_agent?.permission_mode || "automatico";
84
88
  const allowedTools = new Set(globalConfig.super_agent?.allowed_tools || []);
85
89
 
86
- return function requirePermission(tool, { dangerous = false, confirmed = false } = {}) {
90
+ // async so tools can `await requirePermission(...)` and the confirmation
91
+ // dialog resolves transparently before execution continues.
92
+ return async function requirePermission(tool, { dangerous = false, confirmed = false, args } = {}) {
87
93
  const ok = confirmed || implicitConfirmation;
88
94
  if (permissionMode === "total") return;
89
- if (permissionMode === "permiso" && !allowedTools.has(tool) && !ok) {
90
- throw new Error(`requires_confirmation: permission_mode=permiso blocks ${tool}`);
95
+
96
+ const blocked =
97
+ (permissionMode === "permiso" && !allowedTools.has(tool) && !ok) ||
98
+ (permissionMode === "automatico" && dangerous && !ok);
99
+
100
+ if (!blocked) return;
101
+
102
+ const description = buildConfirmDescription(tool, args || {});
103
+
104
+ if (!requestConfirmation) {
105
+ // No confirmation channel wired for this invocation context (e.g. routine,
106
+ // autonomous agent). Surface a clear message so the model can explain it.
107
+ throw new Error(`Action requires user confirmation: ${description}`);
91
108
  }
92
- if (permissionMode === "automatico" && dangerous && !ok) {
93
- throw new Error(`requires_confirmation: permission_mode=automatico requires confirmation for ${tool}`);
109
+
110
+ const userConfirmed = await requestConfirmation(tool, args || {}, description);
111
+
112
+ if (!userConfirmed) {
113
+ throw new Error(`User did not confirm: ${description}`);
94
114
  }
115
+ // Confirmed — fall through, tool executes normally.
95
116
  };
96
117
  }
97
118
 
@@ -335,6 +335,7 @@ export function makeToolHandlers(ctx) {
335
335
  ...ctx,
336
336
  requirePermission: createPermissionGuard(ctx.globalConfig || {}, {
337
337
  implicitConfirmation: !!ctx.implicitConfirmation,
338
+ requestConfirmation: ctx.requestConfirmation || null,
338
339
  }),
339
340
  };
340
341
  return Object.fromEntries(TOOLS.map((tool) => [tool.name, tool.makeHandler(toolCtx)]));
@@ -31,8 +31,8 @@ export default {
31
31
  },
32
32
  },
33
33
  },
34
- makeHandler: ({ projects, requirePermission }) => ({ path: projectPath, name, init = true, confirmed = false }) => {
35
- requirePermission("add_project", { dangerous: true, confirmed });
34
+ makeHandler: ({ projects, requirePermission }) => async ({ path: projectPath, name, init = true, confirmed = false }) => {
35
+ await requirePermission("add_project", { dangerous: true, confirmed, args: { path: projectPath } });
36
36
  if (!projectPath) throw new Error("add_project: path required");
37
37
 
38
38
  const abs = path.resolve(projectPath);
@@ -21,7 +21,7 @@ export default {
21
21
  },
22
22
  },
23
23
  makeHandler: ({ projects, registries, requirePermission }) => async ({ project, mcp, tool, args = {}, confirmed = false }) => {
24
- requirePermission("call_mcp", { dangerous: true, confirmed });
24
+ await requirePermission("call_mcp", { dangerous: true, confirmed, args: { mcp, tool } });
25
25
  const p = resolveProject(projects, project);
26
26
  if (!registries) throw new Error("MCP registry unavailable");
27
27
  const registry = registries.for ? registries.for(p) : registries.ensure(p);
@@ -174,7 +174,7 @@ export default {
174
174
  },
175
175
  },
176
176
  makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, runtime, prompt, resume_session_id = null, timeout_s = 300, confirmed = false }) => {
177
- requirePermission("call_runtime", { dangerous: true, confirmed });
177
+ await requirePermission("call_runtime", { dangerous: true, confirmed, args: { runtime } });
178
178
 
179
179
  const p = slug ? resolveProjectForAgent(projects, project, slug) : resolveProject(projects, project);
180
180
  const agent = slug ? readAgents(p.path).find((a) => a.slug === slug) : null;
@@ -22,8 +22,8 @@ export default {
22
22
  },
23
23
  },
24
24
  },
25
- makeHandler: ({ projects, requirePermission }) => ({ project, path, search, replace, all = false, confirmed = false }) => {
26
- requirePermission("edit_file", { dangerous: true, confirmed });
25
+ makeHandler: ({ projects, requirePermission }) => async ({ project, path, search, replace, all = false, confirmed = false }) => {
26
+ await requirePermission("edit_file", { dangerous: true, confirmed, args: { path } });
27
27
  if (!path) throw new Error("edit_file: path required");
28
28
  if (!search) throw new Error("edit_file: search required");
29
29
 
@@ -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 {
@@ -22,8 +23,8 @@ export default {
22
23
  },
23
24
  },
24
25
  },
25
- makeHandler: ({ projects, requirePermission }) => ({ project, agent: slug, confirmed = false }) => {
26
- requirePermission("import_agent", { dangerous: true, confirmed });
26
+ makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, confirmed = false }) => {
27
+ await requirePermission("import_agent", { dangerous: true, confirmed, args: { agent: slug } });
27
28
  if (!slug) throw new Error("import_agent: agent required");
28
29
 
29
30
  const vaultPath = path.join(VAULT_DIR, `${slug}.md`);
@@ -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
  };
@@ -64,7 +64,7 @@ export default {
64
64
  },
65
65
  },
66
66
  makeHandler: ({ projects, requirePermission }) => async ({ project, cwd = ".", command, timeout_s = 60, confirmed = false }) => {
67
- requirePermission("run_shell", { dangerous: !isSafeShellCommand(command), confirmed });
67
+ await requirePermission("run_shell", { dangerous: !isSafeShellCommand(command), confirmed, args: { command } });
68
68
  if (!command) throw new Error("run_shell: command required");
69
69
 
70
70
  const p = resolveProject(projects, project);
@@ -22,10 +22,7 @@ export default {
22
22
  },
23
23
  },
24
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
-
25
+ makeHandler: ({ projects }) => async ({ query, project, path: sub = "." } = {}) => {
29
26
  const p = resolveProject(projects, project);
30
27
  const target = safePathJoin(p.path, sub);
31
28
 
@@ -87,7 +87,7 @@ export default {
87
87
  confirmed = false,
88
88
  } = args;
89
89
 
90
- requirePermission("send_telegram", { dangerous: true, confirmed });
90
+ await requirePermission("send_telegram", { dangerous: true, confirmed, args: { text } });
91
91
  if (!plugins) throw new Error("plugins unavailable");
92
92
  const telegram = plugins.get("telegram");
93
93
  if (!telegram) throw new Error("telegram plugin not loaded");
@@ -20,8 +20,8 @@ export default {
20
20
  },
21
21
  },
22
22
  },
23
- makeHandler: ({ requirePermission }) => ({ agent_name, owner_name, owner_context, personality, confirmed = false } = {}) => {
24
- requirePermission("set_identity", { dangerous: true, confirmed });
23
+ makeHandler: ({ requirePermission }) => async ({ agent_name, owner_name, owner_context, personality, confirmed = false } = {}) => {
24
+ await requirePermission("set_identity", { dangerous: true, confirmed, args: { agent_name } });
25
25
  const fields = {};
26
26
  if (agent_name) fields.agent_name = agent_name;
27
27
  if (owner_name) fields.owner_name = owner_name;
@@ -20,8 +20,8 @@ export default {
20
20
  },
21
21
  },
22
22
  },
23
- makeHandler: ({ requirePermission }) => ({ mode, confirmed = false }) => {
24
- requirePermission("set_permission_mode", { dangerous: true, confirmed });
23
+ makeHandler: ({ requirePermission }) => async ({ mode, confirmed = false }) => {
24
+ await requirePermission("set_permission_mode", { dangerous: true, confirmed, args: { mode } });
25
25
  if (!MODES.has(mode)) throw new Error("mode must be total, automatico, or permiso");
26
26
  const cfg = readConfig();
27
27
  cfg.super_agent = cfg.super_agent || {};
@@ -21,8 +21,8 @@ export default {
21
21
  },
22
22
  },
23
23
  },
24
- makeHandler: ({ projects, requirePermission }) => ({ project, path: sub, content, confirmed = false }) => {
25
- requirePermission("write_file", { dangerous: true, confirmed });
24
+ makeHandler: ({ projects, requirePermission }) => async ({ project, path: sub, content, confirmed = false }) => {
25
+ await requirePermission("write_file", { dangerous: true, confirmed, args: { path: sub } });
26
26
  if (!sub) throw new Error("write_file: path required");
27
27
  const p = resolveProject(projects, project);
28
28
  const target = safePathJoin(p.path, sub);
@@ -54,6 +54,10 @@ export async function runSuperAgent({
54
54
  // restricts the visible tool schemas to those names; [] means no tools.
55
55
  // Used to gate guests/limited roles on Telegram (see resolveAllowedTools).
56
56
  allowedTools = "*",
57
+ // Channel-specific confirmation handler. See run-agent.js for contract.
58
+ // Null disables human-in-the-loop (tools that need confirmation fail
59
+ // immediately instead of waiting for user input).
60
+ requestConfirmation = null,
57
61
  }) {
58
62
  if (!isSuperAgentEnabled(globalConfig)) {
59
63
  throw new Error("super-agent not enabled (set super_agent.enabled and .model in ~/.apx/config.json)");
@@ -114,7 +118,7 @@ export async function runSuperAgent({
114
118
  overrideModel,
115
119
  toolSchemas,
116
120
  makeToolHandlers,
117
- toolHandlerCtx: { projects, plugins, registries, globalConfig, channel, toolSession },
121
+ toolHandlerCtx: { projects, plugins, registries, globalConfig, channel, toolSession, requestConfirmation },
118
122
  onEvent,
119
123
  signal,
120
124
  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
  }