@agentprojectcontext/apx 1.31.2 → 1.32.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 (229) hide show
  1. package/package.json +6 -1
  2. package/skills/apc-context/SKILL.md +5 -2
  3. package/skills/apx/SKILL.md +3 -3
  4. package/skills/apx-agency-agents/SKILL.md +5 -5
  5. package/skills/apx-agent/SKILL.md +7 -7
  6. package/skills/apx-mcp/SKILL.md +6 -4
  7. package/skills/apx-mcp-builder/SKILL.md +4 -7
  8. package/skills/apx-project/SKILL.md +4 -5
  9. package/skills/apx-routine/SKILL.md +14 -12
  10. package/skills/apx-runtime/SKILL.md +5 -3
  11. package/skills/apx-sessions/SKILL.md +5 -5
  12. package/skills/apx-skill-builder/SKILL.md +10 -6
  13. package/skills/apx-task/SKILL.md +8 -8
  14. package/skills/apx-telegram/SKILL.md +23 -7
  15. package/skills/apx-voice/SKILL.md +8 -6
  16. package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
  17. package/src/core/agent/constants.js +5 -0
  18. package/src/core/agent/index.js +0 -2
  19. package/src/core/{agent-memory.js → agent/memory.js} +2 -2
  20. package/src/core/agent/model-router.js +21 -43
  21. package/src/core/agent/prompt-builder.js +17 -63
  22. package/src/core/agent/prompts/action-discipline.md +17 -0
  23. package/src/core/agent/prompts/channels/code.md +8 -12
  24. package/src/core/agent/prompts/channels/desktop.md +6 -4
  25. package/src/core/agent/prompts/channels/routine.md +10 -1
  26. package/src/core/agent/prompts/channels/telegram.md +5 -0
  27. package/src/core/agent/prompts/channels/web_code.md +20 -0
  28. package/src/core/agent/prompts/modes/voice.md +2 -2
  29. package/src/core/agent/prompts/super-agent-base.md +2 -2
  30. package/src/core/agent/run-agent.js +66 -36
  31. package/src/core/agent/runtime-bridge.js +42 -0
  32. package/src/core/agent/self-memory.js +19 -9
  33. package/src/core/agent/skills/catalog.js +65 -0
  34. package/src/core/agent/skills/index.js +6 -0
  35. package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
  36. package/src/core/agent/skills/rag.js +91 -0
  37. package/src/core/agent/skills/trigger.js +71 -0
  38. package/src/{host/daemon → core/agent}/super-agent.js +5 -5
  39. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
  40. package/src/core/agent/tools/handlers/ask-questions.js +115 -0
  41. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
  42. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
  43. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
  44. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
  45. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
  46. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
  47. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
  48. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
  49. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
  50. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
  51. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
  52. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
  53. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
  54. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
  55. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
  56. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
  57. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
  58. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
  59. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
  60. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
  61. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
  62. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
  63. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
  64. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
  65. package/src/core/agent/tools/helpers.js +74 -0
  66. package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
  67. package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
  68. package/src/core/apc/agents-vault.js +37 -0
  69. package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
  70. package/src/core/{config.js → config/index.js} +21 -27
  71. package/src/core/config/paths.js +32 -0
  72. package/src/core/constants/actors.js +8 -0
  73. package/src/core/constants/channels.js +19 -0
  74. package/src/core/constants/index.js +5 -0
  75. package/src/core/constants/permissions.js +17 -0
  76. package/src/core/constants/roles.js +9 -0
  77. package/src/core/engines/_streaming.js +63 -0
  78. package/src/core/engines/anthropic.js +11 -22
  79. package/src/core/engines/ollama.js +7 -16
  80. package/src/core/identity/index.js +8 -0
  81. package/src/core/{identity.js → identity/self.js} +5 -5
  82. package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
  83. package/src/core/logging.js +1 -1
  84. package/src/core/mascot.js +1 -1
  85. package/src/core/memory/active-threads.js +10 -10
  86. package/src/core/memory/broker.js +9 -9
  87. package/src/core/memory/compactor.js +2 -2
  88. package/src/core/memory/index.js +2 -2
  89. package/src/core/memory/indexer.js +1 -1
  90. package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
  91. package/src/core/{messages-store.js → stores/messages.js} +6 -4
  92. package/src/core/stores/routine-memory.js +71 -0
  93. package/src/core/{routines-store.js → stores/routines.js} +1 -3
  94. package/src/core/stores/runtime-sessions.js +99 -0
  95. package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
  96. package/src/core/update-check.js +1 -1
  97. package/src/core/util/ids.js +14 -0
  98. package/src/core/util/index.js +2 -0
  99. package/src/core/util/text-similarity.js +52 -0
  100. package/src/core/util/time.js +9 -0
  101. package/src/core/voice/tts.js +1 -1
  102. package/src/host/daemon/api/admin-config.js +4 -3
  103. package/src/host/daemon/api/admin.js +1 -1
  104. package/src/host/daemon/api/agents.js +4 -25
  105. package/src/host/daemon/api/artifacts.js +118 -1
  106. package/src/host/daemon/api/code.js +60 -16
  107. package/src/host/daemon/api/confirm.js +1 -1
  108. package/src/host/daemon/api/connections.js +2 -2
  109. package/src/host/daemon/api/conversations.js +2 -2
  110. package/src/host/daemon/api/deck.js +1 -1
  111. package/src/host/daemon/api/desktop.js +1 -1
  112. package/src/host/daemon/api/embeddings.js +4 -4
  113. package/src/host/daemon/api/engines.js +2 -2
  114. package/src/host/daemon/api/exec.js +3 -3
  115. package/src/host/daemon/api/identity.js +1 -1
  116. package/src/host/daemon/api/mcps.js +1 -1
  117. package/src/host/daemon/api/messages.js +1 -1
  118. package/src/host/daemon/api/runtimes.js +9 -8
  119. package/src/host/daemon/api/sessions-search.js +1 -1
  120. package/src/host/daemon/api/sessions.js +2 -2
  121. package/src/host/daemon/api/shared.js +5 -4
  122. package/src/host/daemon/api/skills.js +30 -0
  123. package/src/host/daemon/api/super-agent.js +29 -9
  124. package/src/host/daemon/api/tasks.js +2 -2
  125. package/src/host/daemon/api/telegram.js +1 -1
  126. package/src/host/daemon/api/tools.js +6 -6
  127. package/src/host/daemon/api/tts.js +2 -2
  128. package/src/host/daemon/api/voice.js +14 -12
  129. package/src/host/daemon/api.js +2 -0
  130. package/src/host/daemon/compact.js +1 -1
  131. package/src/host/daemon/db.js +4 -4
  132. package/src/host/daemon/desktop-ws.js +1 -1
  133. package/src/host/daemon/index.js +4 -4
  134. package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +45 -6
  135. package/src/host/daemon/plugins/index.js +2 -2
  136. package/src/host/daemon/plugins/telegram/ask.js +309 -0
  137. package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +390 -191
  138. package/src/host/daemon/plugins/telegram/media.js +162 -0
  139. package/src/host/daemon/projects-helpers.js +54 -0
  140. package/src/host/daemon/routines.js +28 -12
  141. package/src/host/daemon/smoke.js +2 -2
  142. package/src/host/daemon/token-store.js +1 -1
  143. package/src/host/daemon/transcription.js +2 -2
  144. package/src/host/daemon/wakeup.js +2 -2
  145. package/src/interfaces/cli/commands/agent.js +3 -3
  146. package/src/interfaces/cli/commands/artifact.js +99 -0
  147. package/src/interfaces/cli/commands/command.js +1 -1
  148. package/src/interfaces/cli/commands/config.js +3 -2
  149. package/src/interfaces/cli/commands/desktop.js +1 -1
  150. package/src/interfaces/cli/commands/exec.js +2 -1
  151. package/src/interfaces/cli/commands/identity.js +2 -2
  152. package/src/interfaces/cli/commands/init.js +1 -1
  153. package/src/interfaces/cli/commands/mcp.js +1 -1
  154. package/src/interfaces/cli/commands/memory.js +2 -2
  155. package/src/interfaces/cli/commands/model.js +16 -6
  156. package/src/interfaces/cli/commands/project.js +1 -1
  157. package/src/interfaces/cli/commands/routine.js +58 -0
  158. package/src/interfaces/cli/commands/search.js +1 -1
  159. package/src/interfaces/cli/commands/session.js +4 -4
  160. package/src/interfaces/cli/commands/setup.js +4 -3
  161. package/src/interfaces/cli/commands/skills.js +25 -4
  162. package/src/interfaces/cli/commands/status.js +1 -1
  163. package/src/interfaces/cli/commands/sys.js +11 -4
  164. package/src/interfaces/cli/commands/update.js +1 -1
  165. package/src/interfaces/cli/index.js +8 -4
  166. package/src/interfaces/cli/postinstall.js +2 -2
  167. package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
  168. package/src/interfaces/mcp-server/index.js +1 -1
  169. package/src/interfaces/tui/component/prompt/index.tsx +3 -1
  170. package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
  171. package/src/interfaces/tui/context/sync-apx.tsx +20 -2
  172. package/src/interfaces/tui/context/sync.tsx +2 -1
  173. package/src/interfaces/tui/routes/session/index.tsx +151 -136
  174. package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
  175. package/src/interfaces/tui/run.ts +2 -0
  176. package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
  177. package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
  178. package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
  179. package/src/interfaces/web/dist/index.html +2 -2
  180. package/src/interfaces/web/package-lock.json +9 -9
  181. package/src/interfaces/web/src/App.tsx +51 -32
  182. package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
  183. package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
  184. package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
  185. package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
  186. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  187. package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
  188. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
  189. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
  190. package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
  191. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -17
  192. package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
  193. package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
  194. package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
  195. package/src/interfaces/web/src/hooks/useChat.ts +47 -2
  196. package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
  197. package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
  198. package/src/interfaces/web/src/i18n/en.ts +27 -7
  199. package/src/interfaces/web/src/i18n/es.ts +27 -7
  200. package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
  201. package/src/interfaces/web/src/lib/api/skills.ts +25 -0
  202. package/src/interfaces/web/src/lib/api.ts +2 -0
  203. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +41 -20
  204. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
  205. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
  206. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
  207. package/src/interfaces/web/src/screens/project/ChatTab.tsx +27 -9
  208. package/src/skills/apc-context/SKILL.md +159 -0
  209. package/src/core/agent/ghost-guard.js +0 -24
  210. package/src/core/agent/prompts/channels/terminal.md +0 -16
  211. package/src/host/daemon/apc-runtime-context.js +0 -124
  212. package/src/host/daemon/super-agent-tools/helpers.js +0 -124
  213. package/src/host/daemon/super-agent-tools/tools/ask-questions.js +0 -32
  214. package/src/host/daemon/tool-call-parser.js +0 -2
  215. package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
  216. package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
  217. package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
  218. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
  219. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
  220. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
  221. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
  222. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
  223. /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
  224. /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
  225. /package/src/core/{parser.js → apc/parser.js} +0 -0
  226. /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
  227. /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
  228. /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
  229. /package/src/core/{session-store.js → stores/sessions.js} +0 -0
@@ -1,21 +1,31 @@
1
- // Roby's own notebook — the super-agent's personal, persistent memory.
1
+ // The super-agent's personal, persistent notebook.
2
2
  //
3
- // This is distinct from:
4
- // - identity.json → who Roby is (name, personality, owner)
3
+ // Distinct from:
4
+ // - identity.json → who the super-agent is (name, personality, owner)
5
5
  // - project agents' ~/.apx/projects/<apx_id>/agents/<slug>/memory.md → per-agent, per-project
6
6
  // - sessions → raw transcripts of past work (search_sessions)
7
7
  //
8
- // It is a single free-form markdown file at ~/.apx/memory.md that Roby keeps
8
+ // A single free-form markdown file at ~/.apx/memory.md kept by the super-agent
9
9
  // itself: durable facts about the owner, ongoing threads, decisions, and the
10
- // gist of what it has been working on (which it refreshes by skimming its own
11
- // recent sessions). A bounded slice is injected into every super-agent prompt;
12
- // the `remember` / `read_self_memory` tools write and read it.
10
+ // gist of what it has been working on (refreshed by skimming its own recent
11
+ // sessions). A bounded slice is injected into every super-agent prompt; the
12
+ // `remember` / `read_self_memory` tools write and read it.
13
+ //
14
+ // The header inside the file picks up the current persona name from identity
15
+ // (resolveAgentName) — never hardcode the agent name here.
13
16
  import fs from "node:fs";
14
17
  import os from "node:os";
15
18
  import path from "node:path";
19
+ import { resolveAgentName } from "../identity/index.js";
16
20
 
17
21
  export const SELF_MEMORY_PATH = path.join(os.homedir(), ".apx", "memory.md");
18
22
 
23
+ function notebookHeader() {
24
+ let name = "";
25
+ try { name = resolveAgentName(); } catch { /* identity missing */ }
26
+ return name ? `# ${name}'s notebook` : "# Self-memory";
27
+ }
28
+
19
29
  // How much of the notebook to inline into the system prompt. The full file is
20
30
  // always readable via read_self_memory; this only bounds the always-on slice
21
31
  // so a long notebook can't blow the token budget on cheap channels.
@@ -67,7 +77,7 @@ export function appendSelfMemory(note, opts = {}) {
67
77
 
68
78
  let next;
69
79
  if (!existing.trim()) {
70
- next = `# Roby's notebook\n\n${heading}\n${bullet}\n`;
80
+ next = `${notebookHeader()}\n\n${heading}\n${bullet}\n`;
71
81
  } else if (existing.includes(heading)) {
72
82
  // Append the bullet under today's existing heading.
73
83
  const lines = existing.split("\n");
@@ -100,7 +110,7 @@ export function ensureSelfMemoryFile() {
100
110
  try {
101
111
  if (fs.existsSync(SELF_MEMORY_PATH)) return false;
102
112
  fs.mkdirSync(path.dirname(SELF_MEMORY_PATH), { recursive: true });
103
- fs.writeFileSync(SELF_MEMORY_PATH, "# Roby's notebook\n");
113
+ fs.writeFileSync(SELF_MEMORY_PATH, `${notebookHeader()}\n`);
104
114
  return true;
105
115
  } catch {
106
116
  return false;
@@ -0,0 +1,65 @@
1
+ // Helpers for working with the skills catalog in the agent prompt.
2
+ //
3
+ // Two consumers:
4
+ // 1. The system-prompt builder injects a short HINT block (slugs only, no
5
+ // bodies, no descriptions) so the model knows skills exist and how to
6
+ // reach them — see buildSkillsHintBlock(). The full catalog (slug +
7
+ // condensed description) is reached via the `list_skills` tool.
8
+ // 2. The `list_skills` tool itself renders descriptions using
9
+ // condenseSkillDescription() — keeps the trigger-list tails out of the
10
+ // tool result, same way the legacy catalog did.
11
+ //
12
+ // Skill descriptions are authored for Claude Code's skill matcher, so many end
13
+ // with verbose "Trigger on: …" / "Activate when …" / "Activa cuando…" lists.
14
+ // Inside the super-agent prompt those tails are pure noise (it matches
15
+ // semantically, not by trigger string). Keep the first sentence only, drop
16
+ // the trigger/activation tail, and cap length.
17
+
18
+ const TRIGGER_MARKER =
19
+ /\s*(?:Trigger(?:s)? on|Triggers|TRIGGER|Activate (?:on|when|only)|Use this skill (?:whenever|when)|Use (?:it )?when|Triggers include|SKIP|Also (?:use|triggers)|Activa(?:r)? (?:cuando|en)|Disparadores|Usar cuando|Usá cuando|Se activa cuando)\b/i;
20
+
21
+ export function condenseSkillDescription(desc) {
22
+ if (!desc) return "(no description)";
23
+ const full = String(desc).replace(/\s+/g, " ").trim();
24
+ // Prefer the gist before any trigger/activation marker; but if a skill leads
25
+ // straight into "Activate ONLY when…" (no gist first), that head is empty —
26
+ // fall back to the first sentence of the full text so we keep real info.
27
+ let d = full.split(TRIGGER_MARKER)[0].trim();
28
+ if (d.length < 15) d = full;
29
+ // First sentence only, then cap length.
30
+ const firstStop = d.search(/\.(\s|$)/);
31
+ if (firstStop > 0) d = d.slice(0, firstStop + 1);
32
+ d = d.trim();
33
+ if (d.length > 160) d = d.slice(0, 157).trimEnd() + "…";
34
+ return d || "(no description)";
35
+ }
36
+
37
+ /**
38
+ * Compact "skills exist, here's how to reach them" block injected into every
39
+ * super-agent system prompt. Lists slugs only (no descriptions, no bodies) so
40
+ * the agent knows which slugs are reachable without paying the full catalog's
41
+ * token cost. The model calls `list_skills` to see descriptions and
42
+ * `load_skill` to load a body.
43
+ *
44
+ * Returns "" when there are no skills.
45
+ */
46
+ export function buildSkillsHintBlock(listSkills) {
47
+ let list = [];
48
+ try {
49
+ list = listSkills();
50
+ } catch {
51
+ /* empty */
52
+ }
53
+ if (!list.length) return "";
54
+ const slugs = list.map((s) => s.slug).filter(Boolean);
55
+ return [
56
+ "# Available skills (catalog on demand)",
57
+ `${slugs.length} skills are available. Bodies are NOT loaded. For details,`,
58
+ "call `list_skills` (catalog with one-line descriptions) and then",
59
+ "`load_skill({slug})` to load the body of the matching one. Match",
60
+ "semantically — never by trigger string. Don't load a skill unless the",
61
+ "current user request actually needs its exact syntax.",
62
+ "",
63
+ "Slugs: " + slugs.join(", "),
64
+ ].join("\n");
65
+ }
@@ -0,0 +1,6 @@
1
+ // Public entrypoint for skills.
2
+ export { condenseSkillDescription, buildSkillsHintBlock } from "./catalog.js";
3
+ export { tryResolveSkillCommand } from "./trigger.js";
4
+ export { suggestSkillForPrompt, clearSkillVectorCache } from "./rag.js";
5
+ export { listSkills, loadSkill, SKILL_LOCATIONS } from "./loader.js";
6
+
@@ -31,9 +31,9 @@ import { fileURLToPath } from "node:url";
31
31
 
32
32
  const __filename = fileURLToPath(import.meta.url);
33
33
  const __dirname = path.dirname(__filename);
34
- // Three levels up: __dirname = src/host/daemon/ → src/host/src/ → repo root.
35
- // Used to find the bundled skills/ folder at the repo root.
36
- const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
34
+ // Four levels up: __dirname = src/core/agent/skills/agent/ → core/ → src/
35
+ // → repo root. Used to find the bundled skills/ folder at the repo root.
36
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..", "..");
37
37
 
38
38
  const RUNTIME_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
39
39
  const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, "skills");
@@ -0,0 +1,91 @@
1
+ // Suggest a skill semantically when the user prompt looks like it would
2
+ // benefit from one — without paying the catalog's token cost every turn.
3
+ //
4
+ // On every turn we:
5
+ // 1. Embed the user prompt.
6
+ // 2. Embed each skill's condensed description (cached per-process by slug).
7
+ // 3. Pick the top match if its cosine ≥ THRESHOLD AND noticeably better
8
+ // than the runner-up (so a vague prompt doesn't drag a random skill in).
9
+ // 4. Return a SOFT HINT block — not a full skill body — that nudges the
10
+ // model to call `load_skill({slug})` if the gist matches.
11
+ //
12
+ // Soft, not hard:
13
+ // - We never inject the body; the model still has to decide and call.
14
+ // - We never block the request: any embedding error → "" return.
15
+ // - We never re-call the indexer; the embedder is the same one already
16
+ // selected by core/memory/embeddings.js.
17
+ //
18
+ // Costs: one extra embedOne(promptText) per turn. Skill description
19
+ // embeddings are cached in-process by (slug, source) so the cold cost is
20
+ // O(skills) once per process start.
21
+ import { embedOne, cosineSim } from "#core/memory/embeddings.js";
22
+ import { condenseSkillDescription } from "#core/agent/skills/index.js";
23
+ import { listSkills } from "./loader.js";
24
+
25
+ const SIM_THRESHOLD = 0.45; // below this, no suggestion
26
+ const MARGIN = 0.05; // top must beat runner-up by at least this
27
+ const PROMPT_LEN_FLOOR = 8; // skip "hola" / "ok" / single-word prompts
28
+
29
+ const cache = new Map(); // slug -> { vector, descHash }
30
+
31
+ function descHash(text) {
32
+ let h = 0;
33
+ const s = String(text || "");
34
+ for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
35
+ return h;
36
+ }
37
+
38
+ async function ensureSkillVector(skill, opts) {
39
+ const desc = condenseSkillDescription(skill.description);
40
+ const h = descHash(desc);
41
+ const hit = cache.get(skill.slug);
42
+ if (hit && hit.descHash === h) return hit.vector;
43
+ const vector = await embedOne(desc, opts);
44
+ if (vector) cache.set(skill.slug, { vector, descHash: h });
45
+ return vector;
46
+ }
47
+
48
+ /**
49
+ * Returns "" when no confident match exists, or a one-paragraph hint that
50
+ * names the slug + the matched description. Callers append it to contextNote.
51
+ */
52
+ export async function suggestSkillForPrompt(prompt, { projectPath, embedOpts } = {}) {
53
+ try {
54
+ const p = String(prompt || "").trim();
55
+ if (p.length < PROMPT_LEN_FLOOR) return "";
56
+
57
+ const skills = listSkills({ projectPath });
58
+ if (!skills.length) return "";
59
+
60
+ const promptVec = await embedOne(p, embedOpts);
61
+ if (!promptVec) return "";
62
+
63
+ const scored = [];
64
+ for (const skill of skills) {
65
+ const v = await ensureSkillVector(skill, embedOpts);
66
+ if (!v) continue;
67
+ scored.push({ slug: skill.slug, sim: cosineSim(promptVec, v) });
68
+ }
69
+ if (scored.length === 0) return "";
70
+ scored.sort((a, b) => b.sim - a.sim);
71
+
72
+ const top = scored[0];
73
+ const runner = scored[1] || { sim: 0 };
74
+ if (top.sim < SIM_THRESHOLD) return "";
75
+ if (top.sim - runner.sim < MARGIN) return "";
76
+
77
+ return [
78
+ "# Skill semantically relevant to this prompt",
79
+ `The skill \`${top.slug}\` matches what the user is asking about (sim ${top.sim.toFixed(2)}).`,
80
+ "If exact syntax/behaviour for that skill is needed, call `load_skill({slug:\"" + top.slug + "\"})`",
81
+ "BEFORE answering. If the user's question is actually about something else, ignore this hint.",
82
+ ].join("\n");
83
+ } catch {
84
+ return "";
85
+ }
86
+ }
87
+
88
+ // Exposed for tests / cache hygiene if a caller wants to reset between runs.
89
+ export function clearSkillVectorCache() {
90
+ cache.clear();
91
+ }
@@ -0,0 +1,71 @@
1
+ // Interface-agnostic skill trigger.
2
+ //
3
+ // When a user message starts with `/<slug>` (e.g. "/apx-routine help me add one"),
4
+ // this helper resolves the slug against the skill catalog and pulls the body
5
+ // into a structured block to inject into the next turn — so the model has the
6
+ // exact syntax without paying for the catalog every turn.
7
+ //
8
+ // Returns:
9
+ // { handled: false } — message had no slash command
10
+ // { handled: true, prompt, contextNote, skill, rest }
11
+ // prompt: the user's text minus the slash command (or "tell me how to use <skill>"
12
+ // when only the slash command was sent)
13
+ // contextNote: a multi-line block ready to pass as `contextNote` to runSuperAgent
14
+ // skill: { slug, body, source } — the loaded skill
15
+ // rest: the user's text without the `/<slug>` prefix (handy for callers
16
+ // that want to render or transform it themselves)
17
+ //
18
+ // Resolution rules:
19
+ // - The first whitespace-separated token must start with `/`.
20
+ // - The slug after the slash is matched case-insensitively against listSkills().
21
+ // - Project-scoped skills (.apc/skills/) win when projectPath is provided.
22
+ // - An unknown slug falls through ({ handled: false }) so callers can choose
23
+ // to surface a "no such skill" message or pass through unchanged.
24
+ import { listSkills, loadSkill } from "./loader.js";
25
+
26
+ const SLASH_RE = /^\/([A-Za-z][A-Za-z0-9_-]*)\b/;
27
+
28
+ export function tryResolveSkillCommand(message, { projectPath } = {}) {
29
+ if (typeof message !== "string") return { handled: false };
30
+ const trimmed = message.replace(/^\s+/, "");
31
+ const m = trimmed.match(SLASH_RE);
32
+ if (!m) return { handled: false };
33
+
34
+ const slug = m[1].toLowerCase();
35
+ const rest = trimmed.slice(m[0].length).trimStart();
36
+
37
+ let skills = [];
38
+ try {
39
+ skills = listSkills({ projectPath });
40
+ } catch {
41
+ return { handled: false };
42
+ }
43
+ const match = skills.find((s) => s.slug.toLowerCase() === slug);
44
+ if (!match) return { handled: false };
45
+
46
+ let body = "";
47
+ try {
48
+ const loaded = loadSkill(match.slug, { projectPath });
49
+ body = loaded.body || "";
50
+ } catch {
51
+ body = "";
52
+ }
53
+
54
+ const prompt = rest || `Use the **${match.slug}** skill to help me.`;
55
+ const contextNote = [
56
+ `# Skill loaded on demand: \`${match.slug}\``,
57
+ "The user invoked this skill explicitly with a `/slug` prefix. Use the",
58
+ "instructions below for this turn instead of guessing. Don't re-call",
59
+ "load_skill for the same slug — its body is right here.",
60
+ "",
61
+ body,
62
+ ].join("\n");
63
+
64
+ return {
65
+ handled: true,
66
+ prompt,
67
+ contextNote,
68
+ skill: { slug: match.slug, body, source: match.source },
69
+ rest,
70
+ };
71
+ }
@@ -1,15 +1,15 @@
1
1
  // Super-agent: daemon-level action agent for Telegram, TUI, desktop, routines.
2
- import { createToolSession, buildLazyToolsBlock, makeToolHandlers } from "./super-agent-tools/index.js";
3
- import { listSkills } from "./skills-loader.js";
2
+ import { createToolSession, buildLazyToolsBlock, makeToolHandlers } from "#core/agent/tools/registry.js";
3
+ import { listSkills } from "#core/agent/skills/loader.js";
4
4
  import {
5
5
  runAgent,
6
6
  buildSuperAgentSystem,
7
7
  isSuperAgentEnabled,
8
8
  buildIdentityBlock,
9
9
  loadDefaultSystemPrompt,
10
- } from "../../core/agent/index.js";
11
- import { resolveAgentName } from "../../core/identity.js";
12
- import { memoryBlockFor } from "../../core/memory/index.js";
10
+ } from "#core/agent/index.js";
11
+ import { resolveAgentName } from "#core/identity/index.js";
12
+ import { memoryBlockFor } from "#core/memory/index.js";
13
13
 
14
14
  export {
15
15
  buildIdentityBlock,
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { readConfig, addProject as addProjectInConfig } from "../../../../core/config.js";
4
- import { initApf } from "../../../../core/scaffold.js";
5
- import { confirmedProperty, projectMeta } from "../helpers.js";
3
+ import { readConfig, addProject as addProjectInConfig } from "#core/config/index.js";
4
+ import { initApf } from "#core/apc/scaffold.js";
5
+ import { projectMeta } from "../helpers.js";
6
6
 
7
7
  function isApcProject(absPath) {
8
8
  return (
@@ -25,7 +25,6 @@ export default {
25
25
  path: { type: "string", description: "absolute or relative filesystem path to add" },
26
26
  name: { type: "string", description: "optional project name (used only when initializing a new APC project)" },
27
27
  init: { type: "boolean", description: "auto-create AGENTS.md and .apc/project.json if missing (default true)" },
28
- confirmed: confirmedProperty("true only after explicit user confirmation for this exact project registration"),
29
28
  },
30
29
  required: ["path"],
31
30
  },
@@ -0,0 +1,115 @@
1
+ // Normalize a raw question entry into the canonical shape rendered by every
2
+ // surface (web InlineAskPanel, future desktop/telegram/CLI). The model can
3
+ // pass either a plain string (legacy) or a rich object with options.
4
+ function normalizeQuestion(q) {
5
+ if (typeof q === "string") {
6
+ return { question: q, options: [], multiSelect: false, allowText: true };
7
+ }
8
+ if (!q || typeof q !== "object") return null;
9
+ const text = typeof q.question === "string" ? q.question : "";
10
+ if (!text) return null;
11
+ const options = Array.isArray(q.options)
12
+ ? q.options
13
+ .map((o) => {
14
+ if (typeof o === "string") return { label: o };
15
+ if (o && typeof o === "object" && typeof o.label === "string") {
16
+ return {
17
+ label: o.label,
18
+ description: typeof o.description === "string" ? o.description : undefined,
19
+ };
20
+ }
21
+ return null;
22
+ })
23
+ .filter(Boolean)
24
+ : [];
25
+ return {
26
+ question: text,
27
+ header: typeof q.header === "string" ? q.header : undefined,
28
+ options,
29
+ multiSelect: q.multiSelect === true,
30
+ // Free-text fallback: on by default. Set false explicitly to force a
31
+ // pick from `options`. Has no effect when options is empty.
32
+ allowText: q.allowText === false ? false : true,
33
+ };
34
+ }
35
+
36
+ export default {
37
+ name: "ask_questions",
38
+ schema: {
39
+ // type: "function" is REQUIRED at the top level — OpenAI and Groq reject
40
+ // schemas without it (Groq → 400 'tools.N.type': property 'type' is missing).
41
+ // Anthropic / Ollama tolerate its absence but the contract is clearer with it.
42
+ type: "function",
43
+ function: {
44
+ name: "ask_questions",
45
+ description:
46
+ "Ask the user one or more questions when you genuinely need input to proceed. " +
47
+ "Each question can be free-text OR a selectable list of options (single- or multi-select). " +
48
+ "Call this ONCE per turn — the loop hands control back to the user immediately.",
49
+ parameters: {
50
+ type: "object",
51
+ properties: {
52
+ questions: {
53
+ type: "array",
54
+ description:
55
+ "Questions for the user. Each item is an object with the question text " +
56
+ "and optional `options` for selectable answers (single- or multi-select). " +
57
+ "Leave `options` empty for free-text questions.",
58
+ items: {
59
+ type: "object",
60
+ properties: {
61
+ question: { type: "string", description: "The question text." },
62
+ header: {
63
+ type: "string",
64
+ description: "Optional short chip (≤12 chars) shown next to the question.",
65
+ },
66
+ options: {
67
+ type: "array",
68
+ description:
69
+ "Selectable answers. Omit or leave empty for a free-text question. " +
70
+ "Prefer 2–4 distinct, mutually-exclusive choices.",
71
+ items: {
72
+ type: "object",
73
+ properties: {
74
+ label: { type: "string", description: "Visible label." },
75
+ description: {
76
+ type: "string",
77
+ description: "Optional explanation shown under the label.",
78
+ },
79
+ },
80
+ required: ["label"],
81
+ },
82
+ },
83
+ multiSelect: {
84
+ type: "boolean",
85
+ description:
86
+ "true → user can pick several options (checkboxes). Default false (single-select).",
87
+ },
88
+ allowText: {
89
+ type: "boolean",
90
+ description:
91
+ "When options is non-empty, also show an 'Otro' free-text field. Default true.",
92
+ },
93
+ },
94
+ required: ["question"],
95
+ },
96
+ },
97
+ },
98
+ required: ["questions"],
99
+ },
100
+ },
101
+ },
102
+ makeHandler: () => async ({ questions }) => {
103
+ // Normalize so downstream code (UI panels, persistence) always sees the
104
+ // canonical shape. The agent loop treats this tool as turn-ending
105
+ // (see TURN_ENDING_TOOLS in src/core/agent/constants.js).
106
+ const normalized = Array.isArray(questions)
107
+ ? questions.map(normalizeQuestion).filter(Boolean)
108
+ : [];
109
+ return {
110
+ status: "Questions presented to user. Waiting for input.",
111
+ count: normalized.length,
112
+ questions: normalized,
113
+ };
114
+ },
115
+ };
@@ -1,5 +1,5 @@
1
- import { callEngine } from "../../../../core/engines/index.js";
2
- import { readAgents } from "../../../../core/parser.js";
1
+ import { callEngine } from "#core/engines/index.js";
2
+ import { readAgents } from "#core/apc/parser.js";
3
3
  import { buildAgentSystem, resolveProject } from "../helpers.js";
4
4
 
5
5
  export default {
@@ -1,4 +1,4 @@
1
- import { confirmedProperty, resolveProject } from "../helpers.js";
1
+ import { resolveProject } from "../helpers.js";
2
2
 
3
3
  export default {
4
4
  name: "call_mcp",
@@ -14,7 +14,6 @@ export default {
14
14
  mcp: { type: "string", description: "MCP server name" },
15
15
  tool: { type: "string", description: "tool name on that MCP" },
16
16
  args: { type: "object", description: "arguments object" },
17
- confirmed: confirmedProperty("true only after explicit user confirmation for this exact MCP call"),
18
17
  },
19
18
  required: ["mcp", "tool"],
20
19
  },
@@ -1,21 +1,21 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { loggerFor } from "../../../../core/logging.js";
4
- import { readAgents } from "../../../../core/parser.js";
3
+ import { loggerFor } from "#core/logging.js";
4
+ import { readAgents } from "#core/apc/parser.js";
5
5
  import {
6
- buildApfHint,
7
6
  closeRuntimeSession,
8
7
  createRuntimeSession,
9
- extractApfResult,
10
- } from "../../apc-runtime-context.js";
11
- import { detectAll } from "../../env-detect.js";
8
+ extractRuntimeResult as extractApfResult,
9
+ } from "#core/stores/runtime-sessions.js";
10
+ import { buildRuntimeBridgeHint as buildApfHint } from "#core/agent/runtime-bridge.js";
11
+ import { detectAll } from "#host/daemon/env-detect.js";
12
12
  import {
13
13
  findEngineSessionById,
14
14
  readEngineSessionContext,
15
- } from "../../engine-sessions.js";
16
- import { runProcess } from "../../runtimes/_spawn.js";
17
- import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
18
- import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
15
+ } from "#core/stores/engine-sessions.js";
16
+ import { runProcess } from "#host/daemon/runtimes/_spawn.js";
17
+ import { getRuntime, RUNTIME_IDS } from "#host/daemon/runtimes/index.js";
18
+ import { buildAgentSystem, resolveProject } from "../helpers.js";
19
19
 
20
20
  const log = loggerFor("call_runtime");
21
21
 
@@ -167,7 +167,6 @@ export default {
167
167
  description: "Optional prior session id (claude/codex/apx) — APX prepends that session's title + last prompt to the prompt so the runtime has context.",
168
168
  },
169
169
  timeout_s: { type: "integer", description: "seconds before SIGTERM; default 300" },
170
- confirmed: confirmedProperty("true only after explicit user confirmation for this exact runtime command"),
171
170
  },
172
171
  required: ["runtime", "prompt"],
173
172
  },
@@ -1,4 +1,4 @@
1
- import { createTask } from "../../../../core/tasks-store.js";
1
+ import { createTask } from "#core/stores/tasks.js";
2
2
 
3
3
  export default {
4
4
  name: "create_task",
@@ -1,6 +1,6 @@
1
1
  // discover_tools — lazy tool discovery + activation.
2
2
  //
3
- // Roby (and any super-agent surface) only carries a small "base" set of tool
3
+ // The super-agent only carries a small "base" set of tool
4
4
  // schemas on lightweight channels (Telegram/desktop/deck) to stay under
5
5
  // cheap-tier TPM caps. The rest (browser/Puppeteer, fetch, web_search, runtime,
6
6
  // voice, …) exist but are NOT sent to the model by default. This tool is how
@@ -1,5 +1,5 @@
1
1
  import fs from "node:fs";
2
- import { confirmedProperty, resolveProject, safePathJoin } from "../helpers.js";
2
+ import { resolveProject, safePathJoin } from "../helpers.js";
3
3
 
4
4
  export default {
5
5
  name: "edit_file",
@@ -16,7 +16,6 @@ export default {
16
16
  search: { type: "string", description: "exact text to replace" },
17
17
  replace: { type: "string", description: "replacement text" },
18
18
  all: { type: "boolean", description: "replace all matches; default false replaces one match" },
19
- confirmed: confirmedProperty("true only after explicit user confirmation for this exact file edit"),
20
19
  },
21
20
  required: ["path", "search", "replace"],
22
21
  },
@@ -1,9 +1,9 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { readVaultAgents, VAULT_DIR } from "../../../../core/parser.js";
4
- import { addImportedAgent, ensureAgentDir } from "../../../../core/scaffold.js";
5
- import { ensureAgentRuntimeDir } from "../../../../core/agent-memory.js";
6
- import { confirmedProperty, projectMeta, resolveProject } from "../helpers.js";
3
+ import { readVaultAgents, VAULT_DIR } from "#core/apc/parser.js";
4
+ import { addImportedAgent, ensureAgentDir } from "#core/apc/scaffold.js";
5
+ import { ensureAgentRuntimeDir } from "#core/agent/memory.js";
6
+ import { projectMeta, resolveProject } from "../helpers.js";
7
7
 
8
8
  export default {
9
9
  name: "import_agent",
@@ -17,7 +17,6 @@ export default {
17
17
  properties: {
18
18
  project: { type: "string", description: "project id/name/path; omit or use 'default' for ~/.apx/projects/default" },
19
19
  agent: { type: "string", description: "agent slug from list_vault_agents" },
20
- confirmed: confirmedProperty("true only after explicit user confirmation for this exact import"),
21
20
  },
22
21
  required: ["agent"],
23
22
  },
@@ -1,4 +1,4 @@
1
- import { readAgents } from "../../../../core/parser.js";
1
+ import { readAgents } from "#core/apc/parser.js";
2
2
  import { agentRow, resolveProject } from "../helpers.js";
3
3
 
4
4
  export default {
@@ -1,4 +1,5 @@
1
- import { listSkills, SKILL_LOCATIONS } from "../../skills-loader.js";
1
+ import { listSkills, SKILL_LOCATIONS } from "#core/agent/skills/loader.js";
2
+ import { condenseSkillDescription } from "#core/agent/skills/index.js";
2
3
 
3
4
  export default {
4
5
  name: "list_skills",
@@ -26,7 +27,11 @@ export default {
26
27
  count: skills.length,
27
28
  locations: SKILL_LOCATIONS,
28
29
  project_path: project_path || null,
29
- skills: skills.map(({ slug, source, description }) => ({ slug, source, description })),
30
+ skills: skills.map(({ slug, source, description }) => ({
31
+ slug,
32
+ source,
33
+ description: condenseSkillDescription(description),
34
+ })),
30
35
  };
31
36
  },
32
37
  };
@@ -1,4 +1,4 @@
1
- import { listTasks } from "../../../../core/tasks-store.js";
1
+ import { listTasks } from "#core/stores/tasks.js";
2
2
 
3
3
  export default {
4
4
  name: "list_tasks",
@@ -1,4 +1,4 @@
1
- import { readVaultAgents, VAULT_DIR } from "../../../../core/parser.js";
1
+ import { readVaultAgents, VAULT_DIR } from "#core/apc/parser.js";
2
2
  import { agentRow } from "../helpers.js";
3
3
 
4
4
  export default {
@@ -1,4 +1,4 @@
1
- import { loadSkill } from "../../skills-loader.js";
1
+ import { loadSkill } from "#core/agent/skills/loader.js";
2
2
 
3
3
  export default {
4
4
  name: "load_skill",
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { resolveProject } from "../helpers.js";
3
- import { agentMemoryPath, readAgentMemory } from "../../../../core/agent-memory.js";
3
+ import { agentMemoryPath, readAgentMemory } from "#core/agent/memory.js";
4
4
 
5
5
  export default {
6
6
  name: "read_agent_memory",