@agentprojectcontext/apx 1.31.1 → 1.32.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/README.md +0 -1
- package/package.json +1 -1
- package/skills/apc-context/SKILL.md +0 -1
- package/src/core/agent/constants.js +5 -0
- package/src/core/agent/run-agent.js +29 -1
- 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/host/daemon/api/artifacts.js +117 -0
- package/src/host/daemon/api/code.js +14 -0
- package/src/host/daemon/api/confirm.js +30 -0
- package/src/host/daemon/api/super-agent.js +12 -4
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/plugins/desktop.js +34 -0
- package/src/host/daemon/plugins/telegram-ask.js +309 -0
- package/src/host/daemon/plugins/telegram.js +358 -2
- 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/ask-questions.js +96 -13
- 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 +2 -2
- 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/artifact.js +99 -0
- package/src/interfaces/cli/index.js +4 -0
- package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js +571 -0
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-DLWy6dYz.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +6 -6
- package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
- package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
- package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +12 -4
- package/src/interfaces/web/src/i18n/en.ts +20 -0
- package/src/interfaces/web/src/i18n/es.ts +20 -0
- package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +23 -2
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +15 -0
- package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
- package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
- package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
|
@@ -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,
|
|
@@ -1,6 +1,40 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import { http } from "../http.js";
|
|
2
5
|
import { resolveProjectId } from "./project.js";
|
|
3
6
|
|
|
7
|
+
// First two bytes of an executable script. Used as a hint when the file
|
|
8
|
+
// doesn't have the exec bit but clearly intends to run (shebang line).
|
|
9
|
+
const SHEBANG = "#!";
|
|
10
|
+
|
|
11
|
+
// Decide if an artifact is "runnable": exec bit on the file, OR the file
|
|
12
|
+
// starts with a shebang. If shebang-but-not-exec, we set the bit before
|
|
13
|
+
// spawning so `./script` works without the user having to chmod +x.
|
|
14
|
+
function detectRunnable(absPath) {
|
|
15
|
+
let stat;
|
|
16
|
+
try {
|
|
17
|
+
stat = fs.statSync(absPath);
|
|
18
|
+
} catch {
|
|
19
|
+
return { runnable: false, reason: "not_found" };
|
|
20
|
+
}
|
|
21
|
+
if (!stat.isFile()) return { runnable: false, reason: "not_a_file" };
|
|
22
|
+
const execBit = (stat.mode & 0o111) !== 0;
|
|
23
|
+
let hasShebang = false;
|
|
24
|
+
try {
|
|
25
|
+
const fd = fs.openSync(absPath, "r");
|
|
26
|
+
const buf = Buffer.alloc(2);
|
|
27
|
+
fs.readSync(fd, buf, 0, 2, 0);
|
|
28
|
+
fs.closeSync(fd);
|
|
29
|
+
hasShebang = buf.toString("utf8") === SHEBANG;
|
|
30
|
+
} catch {
|
|
31
|
+
// ignore: hasShebang stays false
|
|
32
|
+
}
|
|
33
|
+
if (execBit) return { runnable: true, reason: "exec_bit", autoChmod: false };
|
|
34
|
+
if (hasShebang) return { runnable: true, reason: "shebang", autoChmod: true };
|
|
35
|
+
return { runnable: false, reason: "no_exec_no_shebang" };
|
|
36
|
+
}
|
|
37
|
+
|
|
4
38
|
export async function cmdArtifactCreate(args) {
|
|
5
39
|
const name = args._[0];
|
|
6
40
|
if (!name) throw new Error("apx artifact create: missing <name>");
|
|
@@ -43,3 +77,68 @@ export async function cmdArtifactRemove(args) {
|
|
|
43
77
|
await http.delete(`/projects/${pid}/artifacts/${encodeURIComponent(name)}`);
|
|
44
78
|
console.log(`removed artifact "${name}"`);
|
|
45
79
|
}
|
|
80
|
+
|
|
81
|
+
// `apx artifact run <name> [-- args...]`
|
|
82
|
+
//
|
|
83
|
+
// Resolves the artifact's absolute path via the daemon (single source of
|
|
84
|
+
// truth for project storage), then spawns the file LOCALLY with stdio
|
|
85
|
+
// inherited so the caller sees output as if they typed `./artifact <args>`.
|
|
86
|
+
// Detection is lenient: exec bit OR shebang → runnable; shebang-only files
|
|
87
|
+
// get a one-shot chmod +x so the user doesn't have to do it themselves.
|
|
88
|
+
//
|
|
89
|
+
// The remaining argv after `run <name>` is passed straight to the script,
|
|
90
|
+
// so `apx artifact run hello.sh -- hola mundo` becomes `./hello.sh hola mundo`.
|
|
91
|
+
export async function cmdArtifactRun(args) {
|
|
92
|
+
const name = args._[0];
|
|
93
|
+
if (!name) throw new Error("apx artifact run: missing <name>");
|
|
94
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
95
|
+
// Pull the artifact record from the daemon for the absolute path.
|
|
96
|
+
let entry;
|
|
97
|
+
try {
|
|
98
|
+
entry = await http.get(`/projects/${pid}/artifacts/${encodeURIComponent(name)}`);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
throw new Error(`artifact "${name}" not found in project #${pid}: ${e.message}`);
|
|
101
|
+
}
|
|
102
|
+
const absPath = entry.path;
|
|
103
|
+
if (!absPath || !fs.existsSync(absPath)) {
|
|
104
|
+
throw new Error(`artifact "${name}" path missing on disk: ${absPath}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const detection = detectRunnable(absPath);
|
|
108
|
+
if (!detection.runnable) {
|
|
109
|
+
const hint = detection.reason === "no_exec_no_shebang"
|
|
110
|
+
? "no es ejecutable (sin shebang ni bit +x). Probá `apx artifact show` para ver el contenido."
|
|
111
|
+
: `no se puede ejecutar (${detection.reason}).`;
|
|
112
|
+
throw new Error(`artifact "${name}" ${hint}`);
|
|
113
|
+
}
|
|
114
|
+
if (detection.autoChmod) {
|
|
115
|
+
try {
|
|
116
|
+
const st = fs.statSync(absPath);
|
|
117
|
+
fs.chmodSync(absPath, st.mode | 0o111);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
throw new Error(`could not chmod +x ${absPath}: ${e.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Everything after the artifact name is forwarded to the script. The CLI's
|
|
124
|
+
// argv parser already strips top-level flags; what's left in `_` is name
|
|
125
|
+
// + script args.
|
|
126
|
+
const scriptArgs = (args._ || []).slice(1);
|
|
127
|
+
const cwd = path.dirname(absPath);
|
|
128
|
+
const child = spawn(absPath, scriptArgs, { stdio: "inherit", cwd });
|
|
129
|
+
|
|
130
|
+
await new Promise((resolve) => {
|
|
131
|
+
child.on("exit", (code, signal) => {
|
|
132
|
+
if (signal) {
|
|
133
|
+
// Killed by signal — surface a non-zero exit for shell pipelines.
|
|
134
|
+
process.exit(128 + (signal === "SIGINT" ? 2 : 15));
|
|
135
|
+
}
|
|
136
|
+
process.exit(code ?? 0);
|
|
137
|
+
});
|
|
138
|
+
child.on("error", (err) => {
|
|
139
|
+
console.error(`apx artifact run: spawn failed — ${err.message}`);
|
|
140
|
+
resolve();
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -125,6 +125,7 @@ import {
|
|
|
125
125
|
cmdArtifactList,
|
|
126
126
|
cmdArtifactShow,
|
|
127
127
|
cmdArtifactRemove,
|
|
128
|
+
cmdArtifactRun,
|
|
128
129
|
} from "./commands/artifact.js";
|
|
129
130
|
import {
|
|
130
131
|
cmdTaskAdd,
|
|
@@ -1273,12 +1274,14 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
1273
1274
|
["create <name>", "Create a new empty artifact. Prints its absolute path."],
|
|
1274
1275
|
["list | ls", "List artifacts in the project."],
|
|
1275
1276
|
["show <name>", "Print artifact content."],
|
|
1277
|
+
["run <name> [args...]", "Execute a runnable artifact (shebang or +x). Stdio is inherited."],
|
|
1276
1278
|
["remove | rm <name>", "Delete an artifact."],
|
|
1277
1279
|
],
|
|
1278
1280
|
examples: [
|
|
1279
1281
|
"apx artifact create check_asana.sh --project 0",
|
|
1280
1282
|
"apx artifact list",
|
|
1281
1283
|
"apx artifact show check_asana.sh",
|
|
1284
|
+
"apx artifact run check_asana.sh",
|
|
1282
1285
|
],
|
|
1283
1286
|
}),
|
|
1284
1287
|
"artifact create": topic({
|
|
@@ -2490,6 +2493,7 @@ async function dispatch(cmd, rest) {
|
|
|
2490
2493
|
else if (sub === "create" || sub === "new") await cmdArtifactCreate(a);
|
|
2491
2494
|
else if (sub === "show" || sub === "get") await cmdArtifactShow(a);
|
|
2492
2495
|
else if (sub === "remove" || sub === "rm") await cmdArtifactRemove(a);
|
|
2496
|
+
else if (sub === "run") await cmdArtifactRun(a);
|
|
2493
2497
|
else die(`unknown artifact subcommand: ${sub}`);
|
|
2494
2498
|
break;
|
|
2495
2499
|
}
|
|
@@ -326,11 +326,31 @@ function transcriptLines(transcript, width) {
|
|
|
326
326
|
if (isQuestion && trace.args?.questions) {
|
|
327
327
|
addLine(lines, "", C.bg);
|
|
328
328
|
addLine(lines, margin + label + C.muted + " (…)" + C.bg, C.bg);
|
|
329
|
-
for (const
|
|
330
|
-
|
|
329
|
+
for (const rawQ of trace.args.questions) {
|
|
330
|
+
// Rich shape: {question, options[], multiSelect, allowText}. Legacy: string.
|
|
331
|
+
const q = typeof rawQ === "string" ? { question: rawQ } : (rawQ || {});
|
|
332
|
+
const qText = typeof q.question === "string" ? q.question : "";
|
|
333
|
+
if (!qText) continue;
|
|
334
|
+
const qWrapped = wrapText(`• ${qText}`, inner - 2);
|
|
331
335
|
for (const line of qWrapped) {
|
|
332
336
|
addLine(lines, margin + C.primary + "┃ " + C.text + padAnsi(line, inner - 2), C.bg);
|
|
333
337
|
}
|
|
338
|
+
const opts = Array.isArray(q.options) ? q.options : [];
|
|
339
|
+
if (opts.length > 0) {
|
|
340
|
+
const marker = q.multiSelect ? "[ ]" : "( )";
|
|
341
|
+
opts.forEach((opt, i) => {
|
|
342
|
+
const label = (opt && typeof opt === "object" && typeof opt.label === "string")
|
|
343
|
+
? opt.label
|
|
344
|
+
: (typeof opt === "string" ? opt : "");
|
|
345
|
+
if (!label) return;
|
|
346
|
+
const desc = (opt && typeof opt === "object" && typeof opt.description === "string")
|
|
347
|
+
? ` — ${opt.description}` : "";
|
|
348
|
+
const optLine = ` ${marker} ${i + 1}. ${label}${desc}`;
|
|
349
|
+
for (const line of wrapText(optLine, inner - 2)) {
|
|
350
|
+
addLine(lines, margin + C.primary + "┃ " + C.muted + padAnsi(line, inner - 2), C.bg);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
334
354
|
}
|
|
335
355
|
} else {
|
|
336
356
|
addLine(lines, margin + label + " " + name + C.bg, C.bg);
|