@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.
Files changed (57) 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/src/core/agent/constants.js +5 -0
  5. package/src/core/agent/run-agent.js +29 -1
  6. package/src/core/confirmation/adapters/code.js +41 -0
  7. package/src/core/confirmation/adapters/telegram.js +134 -0
  8. package/src/core/confirmation/adapters/terminal.js +35 -0
  9. package/src/core/confirmation/adapters/web.js +53 -0
  10. package/src/core/confirmation/index.js +44 -0
  11. package/src/core/confirmation/pending-store.js +68 -0
  12. package/src/host/daemon/api/artifacts.js +117 -0
  13. package/src/host/daemon/api/code.js +14 -0
  14. package/src/host/daemon/api/confirm.js +30 -0
  15. package/src/host/daemon/api/super-agent.js +12 -4
  16. package/src/host/daemon/api.js +2 -0
  17. package/src/host/daemon/plugins/desktop.js +34 -0
  18. package/src/host/daemon/plugins/telegram-ask.js +309 -0
  19. package/src/host/daemon/plugins/telegram.js +358 -2
  20. package/src/host/daemon/super-agent-tools/helpers.js +27 -6
  21. package/src/host/daemon/super-agent-tools/index.js +1 -0
  22. package/src/host/daemon/super-agent-tools/tools/add-project.js +2 -2
  23. package/src/host/daemon/super-agent-tools/tools/ask-questions.js +96 -13
  24. package/src/host/daemon/super-agent-tools/tools/call-mcp.js +1 -1
  25. package/src/host/daemon/super-agent-tools/tools/call-runtime.js +1 -1
  26. package/src/host/daemon/super-agent-tools/tools/edit-file.js +2 -2
  27. package/src/host/daemon/super-agent-tools/tools/import-agent.js +2 -2
  28. package/src/host/daemon/super-agent-tools/tools/run-shell.js +1 -1
  29. package/src/host/daemon/super-agent-tools/tools/search-files.js +1 -4
  30. package/src/host/daemon/super-agent-tools/tools/send-telegram.js +1 -1
  31. package/src/host/daemon/super-agent-tools/tools/set-identity.js +2 -2
  32. package/src/host/daemon/super-agent-tools/tools/set-permission-mode.js +2 -2
  33. package/src/host/daemon/super-agent-tools/tools/write-file.js +2 -2
  34. package/src/host/daemon/super-agent.js +5 -1
  35. package/src/interfaces/cli/commands/artifact.js +99 -0
  36. package/src/interfaces/cli/index.js +4 -0
  37. package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
  38. package/src/interfaces/web/dist/assets/index-63P_ji1a.js +571 -0
  39. package/src/interfaces/web/dist/assets/index-63P_ji1a.js.map +1 -0
  40. package/src/interfaces/web/dist/assets/index-DLWy6dYz.css +1 -0
  41. package/src/interfaces/web/dist/index.html +2 -2
  42. package/src/interfaces/web/package-lock.json +6 -6
  43. package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
  44. package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
  45. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  46. package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
  47. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
  48. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +12 -4
  49. package/src/interfaces/web/src/i18n/en.ts +20 -0
  50. package/src/interfaces/web/src/i18n/es.ts +20 -0
  51. package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
  52. package/src/interfaces/web/src/lib/api.ts +1 -0
  53. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +23 -2
  54. package/src/interfaces/web/src/screens/project/ChatTab.tsx +15 -0
  55. package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
  56. package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
  57. 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, 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,
@@ -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 q of trace.args.questions) {
330
- const qWrapped = wrapText(`• ${q}`, inner - 2);
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);