@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.
- package/README.md +0 -1
- package/package.json +1 -1
- package/skills/apc-context/SKILL.md +0 -1
- package/skills/apx-agency-agents/SKILL.md +1 -1
- package/skills/apx-agent/SKILL.md +6 -6
- package/skills/apx-project/SKILL.md +1 -2
- package/src/core/agent/self-memory.js +1 -1
- package/src/core/agent-memory.js +64 -0
- package/src/core/agent-system.js +3 -2
- package/src/core/confirmation/adapters/code.js +41 -0
- package/src/core/confirmation/adapters/telegram.js +134 -0
- package/src/core/confirmation/adapters/terminal.js +35 -0
- package/src/core/confirmation/adapters/web.js +53 -0
- package/src/core/confirmation/index.js +44 -0
- package/src/core/confirmation/pending-store.js +68 -0
- package/src/core/scaffold.js +43 -18
- package/src/core/tools/registry.js +7 -7
- package/src/host/daemon/api/agents.js +19 -21
- package/src/host/daemon/api/code.js +2 -0
- package/src/host/daemon/api/confirm.js +30 -0
- package/src/host/daemon/api/sessions-search.js +1 -1
- package/src/host/daemon/api/shared.js +5 -8
- package/src/host/daemon/api/super-agent.js +12 -4
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/plugins/telegram.js +28 -0
- package/src/host/daemon/super-agent-tools/helpers.js +27 -6
- package/src/host/daemon/super-agent-tools/index.js +1 -0
- package/src/host/daemon/super-agent-tools/tools/add-project.js +2 -2
- package/src/host/daemon/super-agent-tools/tools/call-mcp.js +1 -1
- package/src/host/daemon/super-agent-tools/tools/call-runtime.js +1 -1
- package/src/host/daemon/super-agent-tools/tools/edit-file.js +2 -2
- package/src/host/daemon/super-agent-tools/tools/import-agent.js +4 -2
- package/src/host/daemon/super-agent-tools/tools/read-agent-memory.js +5 -4
- package/src/host/daemon/super-agent-tools/tools/run-shell.js +1 -1
- package/src/host/daemon/super-agent-tools/tools/search-files.js +1 -4
- package/src/host/daemon/super-agent-tools/tools/send-telegram.js +1 -1
- package/src/host/daemon/super-agent-tools/tools/set-identity.js +2 -2
- package/src/host/daemon/super-agent-tools/tools/set-permission-mode.js +2 -2
- package/src/host/daemon/super-agent-tools/tools/write-file.js +2 -2
- package/src/host/daemon/super-agent.js +5 -1
- package/src/interfaces/cli/commands/agent.js +4 -1
- package/src/interfaces/cli/commands/memory.js +9 -10
- package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js → index-BV615I9p.js} +5 -5
- package/src/interfaces/web/dist/assets/{index-CfWyjPBa.js.map → index-BV615I9p.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/package-lock.json +3 -3
- package/src/interfaces/web/src/i18n/en.ts +6 -6
- package/src/interfaces/web/src/i18n/es.ts +6 -6
- 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
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
|
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
|
|
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
|
|
221
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
115
|
+
// Prefer the first agent's runtime-local memory; else project-level .apc/memory.md.
|
|
114
116
|
export function resolveMemoryPath(p) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
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
|
|
98
|
-
|
|
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 });
|
package/src/host/daemon/api.js
CHANGED
|
@@ -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 = {}, {
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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 =
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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: ${
|
|
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
|
}
|