@agentprojectcontext/apx 1.33.0 → 1.34.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 (172) hide show
  1. package/package.json +1 -1
  2. package/skills/apc-context/SKILL.md +2 -5
  3. package/skills/apx/SKILL.md +49 -61
  4. package/src/core/agent/a2a/reply.js +48 -0
  5. package/src/core/agent/build-agent-system.js +4 -3
  6. package/src/core/agent/channels/voice-context.js +98 -0
  7. package/src/core/agent/memory.js +2 -1
  8. package/src/core/agent/prompt-builder.js +2 -1
  9. package/src/core/agent/prompts/modes/code-build.md +1 -0
  10. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  11. package/src/core/agent/prompts/modes/index.js +28 -0
  12. package/src/core/agent/skills/loader.js +22 -18
  13. package/src/core/agent/stream/turn-accumulator.js +73 -0
  14. package/src/core/agent/suggestions.js +37 -0
  15. package/src/core/agent/tools/handlers/add-project.js +5 -2
  16. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  17. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  18. package/src/core/agent/tools/helpers.js +2 -2
  19. package/src/core/agent/tools/names.js +138 -0
  20. package/src/core/agent/tools/registry-bridge.js +6 -14
  21. package/src/core/agent/tools/registry.js +68 -65
  22. package/src/core/apc/context-copy.js +27 -0
  23. package/src/core/apc/notes.js +19 -0
  24. package/src/core/apc/parser.js +13 -6
  25. package/src/core/apc/paths.js +87 -0
  26. package/src/core/apc/scaffold.js +82 -74
  27. package/src/core/apc/skill-sync.js +13 -1
  28. package/src/core/channels/telegram/dispatch.js +595 -0
  29. package/src/core/channels/telegram/helpers.js +130 -0
  30. package/src/core/config/index.js +3 -2
  31. package/src/core/config/redact.js +95 -0
  32. package/src/core/constants/channels.js +2 -0
  33. package/src/core/constants/code-modes.js +10 -0
  34. package/src/core/constants/index.js +1 -0
  35. package/src/core/deck/manifest.js +186 -0
  36. package/src/core/engines/catalog.js +83 -0
  37. package/src/core/engines/gemini.js +28 -11
  38. package/src/core/engines/index.js +11 -1
  39. package/src/core/{tools → http-tools}/browser.js +0 -1
  40. package/src/core/{tools → http-tools}/fetch.js +0 -1
  41. package/src/core/{tools → http-tools}/glob.js +0 -1
  42. package/src/core/{tools → http-tools}/grep.js +0 -1
  43. package/src/core/{tools → http-tools}/registry.js +0 -1
  44. package/src/core/{tools → http-tools}/search.js +0 -1
  45. package/src/core/i18n/en.js +9 -0
  46. package/src/core/i18n/es.js +12 -0
  47. package/src/core/i18n/index.js +54 -0
  48. package/src/core/i18n/pt.js +9 -0
  49. package/src/core/identity/telegram.js +2 -1
  50. package/src/core/mcp/runner.js +272 -14
  51. package/src/core/mcp/sources.js +3 -2
  52. package/src/core/routines/index.js +16 -0
  53. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  54. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  55. package/src/core/runtime-skills/apx/SKILL.md +95 -0
  56. package/src/core/runtime-skills/apx-mcp/SKILL.md +116 -0
  57. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  58. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  59. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  60. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  61. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  62. package/src/core/stores/code-sessions.js +50 -2
  63. package/src/core/stores/routine-memory.js +1 -1
  64. package/src/core/stores/sessions-search.js +121 -0
  65. package/src/core/stores/sessions.js +38 -0
  66. package/src/core/vars/index.js +14 -0
  67. package/src/core/vars/interpolate.js +86 -0
  68. package/src/core/vars/sources.js +151 -0
  69. package/src/core/voice/audio-decode.js +38 -0
  70. package/src/core/voice/transcription.js +225 -0
  71. package/src/host/daemon/api/admin-config.js +5 -82
  72. package/src/host/daemon/api/agents.js +5 -5
  73. package/src/host/daemon/api/code.js +17 -169
  74. package/src/host/daemon/api/config.js +3 -4
  75. package/src/host/daemon/api/conversations.js +8 -29
  76. package/src/host/daemon/api/deck.js +37 -404
  77. package/src/host/daemon/api/engines.js +1 -50
  78. package/src/host/daemon/api/exec.js +1 -1
  79. package/src/host/daemon/api/mcps.js +32 -0
  80. package/src/host/daemon/api/routines.js +1 -1
  81. package/src/host/daemon/api/runtimes.js +4 -3
  82. package/src/host/daemon/api/sessions-search.js +24 -140
  83. package/src/host/daemon/api/sessions.js +12 -30
  84. package/src/host/daemon/api/shared.js +2 -1
  85. package/src/host/daemon/api/telegram.js +1 -11
  86. package/src/host/daemon/api/tools.js +6 -6
  87. package/src/host/daemon/api/transcribe.js +2 -2
  88. package/src/host/daemon/api/vars.js +137 -0
  89. package/src/host/daemon/api/voice.js +13 -290
  90. package/src/host/daemon/api.js +2 -0
  91. package/src/host/daemon/db.js +6 -6
  92. package/src/host/daemon/deck-exec.js +148 -0
  93. package/src/host/daemon/index.js +3 -3
  94. package/src/host/daemon/plugins/telegram/index.js +24 -687
  95. package/src/host/daemon/routines-scheduler.js +64 -0
  96. package/src/host/daemon/smoke.js +3 -2
  97. package/src/host/daemon/whisper-server.js +225 -0
  98. package/src/interfaces/cli/commands/agent.js +3 -2
  99. package/src/interfaces/cli/commands/command.js +2 -3
  100. package/src/interfaces/cli/commands/messages.js +6 -2
  101. package/src/interfaces/cli/commands/pair.js +5 -4
  102. package/src/interfaces/cli/commands/search.js +1 -1
  103. package/src/interfaces/cli/commands/sessions.js +3 -2
  104. package/src/interfaces/cli/commands/skills.js +36 -55
  105. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +1 -0
  106. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +613 -0
  107. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +1 -0
  108. package/src/interfaces/web/dist/index.html +2 -2
  109. package/src/interfaces/web/package-lock.json +182 -182
  110. package/src/interfaces/web/src/components/ModelCombobox.tsx +44 -8
  111. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  112. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  113. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  114. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  115. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  116. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  117. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  118. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  119. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  120. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  121. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  122. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  123. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  124. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  125. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  126. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  127. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +5 -4
  128. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  129. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  130. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  131. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  132. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  133. package/src/interfaces/web/src/constants/index.ts +1 -1
  134. package/src/interfaces/web/src/i18n/en.ts +174 -7
  135. package/src/interfaces/web/src/i18n/es.ts +179 -15
  136. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  137. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  138. package/src/interfaces/web/src/lib/api.ts +1 -0
  139. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  140. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  141. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  142. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  143. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  144. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  145. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  146. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  147. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  148. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  149. package/src/interfaces/web/src/types/daemon.ts +5 -0
  150. package/src/host/daemon/transcription.js +0 -538
  151. package/src/host/daemon/whisper-transcribe.py +0 -73
  152. package/src/interfaces/web/dist/assets/index-7dVT2O1S.css +0 -1
  153. package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js +0 -602
  154. package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js.map +0 -1
  155. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  156. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  157. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  158. /package/src/core/{tools → http-tools}/index.js +0 -0
  159. /package/{skills → src/core/runtime-skills}/apx-agency-agents/SKILL.md +0 -0
  160. /package/{skills → src/core/runtime-skills}/apx-agent/SKILL.md +0 -0
  161. /package/{skills → src/core/runtime-skills}/apx-mcp-builder/SKILL.md +0 -0
  162. /package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +0 -0
  163. /package/{skills → src/core/runtime-skills}/apx-routine/SKILL.md +0 -0
  164. /package/{skills → src/core/runtime-skills}/apx-runtime/SKILL.md +0 -0
  165. /package/{skills → src/core/runtime-skills}/apx-sessions/SKILL.md +0 -0
  166. /package/{skills → src/core/runtime-skills}/apx-skill-builder/SKILL.md +0 -0
  167. /package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +0 -0
  168. /package/{skills → src/core/runtime-skills}/apx-telegram/SKILL.md +0 -0
  169. /package/{skills → src/core/runtime-skills}/apx-voice/SKILL.md +0 -0
  170. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  171. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  172. /package/src/{host/daemon → core/util}/thinking.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: apc-context
3
- description: "Activate whenever the project has a .apc/ directory or AGENTS.md — read .apc/ before assuming anything about agents, memory, or structure. If .apc/migrate.md exists, open with a migration offer first; if the user declines, delete it. Triggers: .apc/, AGENTS.md, 'which agents', 'list agents', 'agent context', 'who are the agents'."
3
+ description: "ALWAYS activate when the project has a .apc/ directory or AGENTS.md file. Do not wait to be asked. Read .apc/ before making any assumption about agents, memory, or project structure. Activate on: .apc/, AGENTS.md, 'which agents', 'list agents', 'agent context', 'who are the agents', any question about agents or memory in this project. IMPORTANT: if .apc/migrate.md exists, open the conversation with a migration offer before answering anything else. If the user declines, delete .apc/migrate.md immediately so it is not shown again."
4
4
  homepage: https://github.com/agentprojectcontext/agentprojectcontext
5
5
  ---
6
6
 
@@ -66,13 +66,11 @@ After migration:
66
66
  ```text
67
67
  AGENTS.md ← root project contract
68
68
  .apc/
69
- project.json ← project metadata (may carry an `apx` field)
70
- config.json ← project-only config overrides (e.g. super_agent.model)
69
+ project.json ← project metadata
71
70
  .gitignore ← safety guard
72
71
  agents/<name>.md ← agent definition
73
72
  agents/<name>/memory.md ← optional curated project memory
74
73
  skills/<name>.md ← reusable project instructions
75
- commands/ ← custom slash-commands (optional)
76
74
  mcps.json ← MCP hints without secrets
77
75
  ```
78
76
 
@@ -83,7 +81,6 @@ Do not store:
83
81
  .apc/sessions/
84
82
  .apc/conversations/
85
83
  .apc/messages/
86
- .apc/project.db
87
84
  .apc/cache/
88
85
  .apc/tmp/
89
86
  .apc/private/
@@ -1,95 +1,83 @@
1
1
  ---
2
2
  name: apx
3
- description: "APX CLI umbrella — which sub-skill handles each operation (sessions, MCPs, routines, tasks, telegram, projects, agents, runtimes). Activate when the user mentions `apx`, the APX daemon, or wants to coordinate/run/delegate agents. Not for `.apc/` alone (use apc-context). Triggers: 'apx', 'apx run', 'apx daemon', 'coordinate agents', 'apx help'."
3
+ description: >-
4
+ APX CLI — local daemon that orchestrates agents, sessions, MCPs, and channels across CLIs.
5
+ Use `apx exec "prompt"` for the local super-agent, or `apx run <agent> --runtime <claude-code|codex|opencode|aider|cursor-agent|gemini-cli|qwen-code> "prompt"` to hand the task to another CLI.
6
+ Activate on: 'apx', 'apx exec', 'apx run', 'apx daemon', 'pedile a codex/claude/opencode/gemini', 'que codex haga …', 'delegar a otro engine', 'ejecutar en codex/claude', 'run this in <runtime>'.
4
7
  homepage: https://github.com/agentprojectcontext/apx
5
8
  ---
6
9
 
7
- # APX — Agent Project Context Runtime
10
+ # APX — Agent Project Context Runtime (engine view)
8
11
 
9
- APX is a daemon (`127.0.0.1:7430`, auto-starts on first call) that turns external coding CLIs (Claude Code, Codex, OpenCode, Aider, …) and configurable agents into a unified orchestration surface.
12
+ APX is a local daemon (`127.0.0.1:7430`, auto-starts on first call) that turns external coding CLIs (Claude Code, Codex, OpenCode, …) and configurable agents into a unified orchestration surface.
10
13
 
11
- It reads APC project context from `.apc/` (committed) but keeps runtime state outside the repo under `~/.apx/projects/<project-id>/`. The super-agent has a default workspace at `~/.apx/projects/default` for system-level work.
14
+ This is the **engine-side** skill: a slim reference for runtimes invoked by APX. The full umbrella skill (with all sub-skills) lives in APX itself.
12
15
 
13
16
  ---
14
17
 
15
- ## When to use APX (vs. spawning a subagent natively)
18
+ ## When you (as an engine) interact with APX
16
19
 
17
- If you can spawn a subagent natively in the IDE you're in (Claude Code, Cursor, …) **do that**. No APX needed.
20
+ - You were spawned by `apx run` your CWD is a project and APX is reachable on `127.0.0.1:7430`.
21
+ - The user asks you to call APX from inside your session ("send a telegram via apx", "list apx sessions").
22
+ - You're inside an `.apc/` project and want to consult APX-managed state.
18
23
 
19
- Use APX when:
20
- - The user explicitly asks for a specific external runtime ("run this in Codex", "delegate to OpenCode").
21
- - You need to run an agent in a runtime different from the one you're in.
22
- - You're orchestrating from outside any IDE (a script, Telegram bot, CI, routine).
24
+ If you can do the task natively (you're an IDE/CLI with your own tools), prefer that. Only shell out to `apx` when the task is APX-specific.
23
25
 
24
26
  ---
25
27
 
26
- ## Sub-skill index — open the one that matches the task
28
+ ## Verify before recommending
27
29
 
28
- | Topic | Sub-skill | When |
29
- |-------|-----------|------|
30
- | Delegate to an external coding CLI | **apx-runtime** | `apx run <agent> --runtime claude-code\|codex\|...` |
31
- | List / read / resume / summarise / continue sessions across engines | **apx-sessions** | `apx session resume`, `apx sessions list`, "import a codex session" |
32
- | Use a registered MCP tool | **apx-mcp** | `apx mcp run`, "call MCP filesystem", "the MCP is failing" |
33
- | Add / configure / use a project agent | **apx-agent** | "add an agent", "import from vault", per-agent model, agent memory |
34
- | Register / list / configure a project | **apx-project** | "register this project", `apx project list`, per-project config |
35
- | Per-project TODO list | **apx-task** | "add a task", "remind me to…", "what's pending" |
36
- | Scheduled / recurring agents | **apx-routine** | `apx routine add`, every-5m, cron-like jobs |
37
- | Telegram I/O | **apx-telegram** | configure bot, channels, send a message |
38
- | Voice channel (TTS, speech) — *optional* | **apx-voice** | only if voice is being set up |
39
- | Build a new MCP server — *internal/dev* | **apx-mcp-builder** | when developing a brand-new MCP from scratch |
40
- | Author a new APX skill — *internal/dev* | **apx-skill-builder** | when adding to APX itself |
30
+ Do not invent subcommands. Confirm exact form with:
41
31
 
42
- > Sub-skills marked *internal/dev* are not pushed to IDE skill dirs by default. They live in the APX repo and are loaded by APX itself; install one to your IDE with `apx skills add <slug> --global` if you want it there.
43
-
44
- ---
45
-
46
- ## Generic patterns (apply to every sub-skill)
47
-
48
- ### Verify commands before recommending them
49
-
50
- Do not invent APX subcommands. Confirm exact CLI form with `apx --help` or `apx <command> --help` before telling another runtime to invoke APX. Avoid guessed aliases (e.g. `apx send-telegram` is *not* a thing — see apx-telegram).
51
-
52
- ### `APC_RESULT` contract — structured return values
53
-
54
- When you want APX to capture a structured value from an agent (any runtime), instruct the agent to print on its last meaningful line:
55
-
56
- ```
57
- APC_RESULT: <one-line value>
32
+ ```bash
33
+ apx --help
34
+ apx <command> --help
58
35
  ```
59
36
 
60
- APX's `extractApfResult()` parses that and stores it as the session's `result` field. Useful for automation, routines, and CI.
37
+ ---
61
38
 
62
- ### Tool permissions
39
+ ## Core commands you'll actually use
63
40
 
64
41
  ```bash
65
- apx permission show
66
- apx permission set automatico # total | automatico | permiso
42
+ # Project + daemon
43
+ apx status # daemon health
44
+ apx project list # registered projects
45
+ apx project current # which project resolves from CWD
46
+
47
+ # Sessions (cross-engine)
48
+ apx sessions list --engine <claude|codex|opencode> --project <name>
49
+ apx sessions list --dir <path>
50
+
51
+ # MCPs — see the apx-mcp skill for the full guide
52
+ apx mcp list
53
+ apx mcp run <name> <tool> '{"...":"..."}'
54
+
55
+ # Memory (curated, durable facts only)
56
+ apx memory <agent-slug>
57
+ apx memory <agent-slug> --append "<fact>"
58
+
59
+ # Observe activity
60
+ apx messages tail
61
+ apx messages chat --channel <name> -n 20
67
62
  ```
68
63
 
69
- `automatico` runs read/list/safe shell checks directly and asks before destructive shell, MCP, runtime, outbound, config, or filesystem mutation actions.
64
+ ---
70
65
 
71
- ### Memory
66
+ ## APC_RESULT contract
72
67
 
73
- Write memory only for durable, safe project facts. Do not store raw transcripts or secrets.
68
+ When APX captures a structured value from your run, end with:
74
69
 
75
- ```bash
76
- apx memory <slug> # read agent's memory.md
77
- apx memory <slug> --append "<fact>" # append a durable note
78
- apx memory <slug> --replace < file.md # replace entire memory from stdin
79
70
  ```
80
-
81
- ### Observe activity
82
-
83
- ```bash
84
- apx messages tail # last 50 messages, all channels
85
- apx messages chat --channel telegram -n 20 # readable chat view
86
- apx messages tail --channel runtime --agent <slug> -n 20
71
+ APC_RESULT: <one-line value>
87
72
  ```
88
73
 
74
+ `extractApfResult()` parses that and stores it as the session's `result`. Use it for routines, CI, automation.
75
+
89
76
  ---
90
77
 
91
78
  ## Anti-patterns
92
79
 
93
- - Don't activate APX-sessions inside a request that's purely about `apx run` orchestration use apx-runtime.
94
- - Don't activate apx-mcp-builder unless the user is actually authoring a new MCP server (it's a deep developer guide, not a usage guide).
95
- - Don't push state to `.apc/` that belongs in `~/.apx/projects/<id>/` (sessions, conversations, runtime logs).
80
+ - Don't write raw transcripts, sessions, or secrets into `.apc/` they belong in `~/.apx/projects/<id>/`.
81
+ - Don't guess subcommands. If `apx --help` doesn't show it, it doesn't exist.
82
+ - Don't activate this skill for pure `.apc/` reading that's [[apc-context]].
83
+ - For MCP details (scopes, secrets, add/remove), open [[apx-mcp]] instead of guessing flags here.
@@ -0,0 +1,48 @@
1
+ // Agent-to-agent (A2A) one-shot reply: given a sender + recipient agent and a
2
+ // message body, build the recipient's system prompt and call the engine. Pure
3
+ // orchestration over core/agent + core/engines — no HTTP, no message log
4
+ // writes (the caller decides whether/where to persist).
5
+ import fs from "node:fs";
6
+ import { callEngine } from "../../engines/index.js";
7
+ import { apcAgentMemoryFile } from "../../apc/paths.js";
8
+
9
+ /**
10
+ * Build the recipient's system prompt for an A2A reply.
11
+ * Includes Description, Role, Language, a persona line naming the sender,
12
+ * and the recipient's memory.md if present.
13
+ */
14
+ export function buildA2AReplySystem({ projectPath, toAgent, fromAgent }) {
15
+ const tf = toAgent?.fields || {};
16
+ const parts = [];
17
+ if (tf.Description) parts.push(tf.Description);
18
+ if (tf.Role) parts.push(`Role: ${tf.Role}`);
19
+ if (tf.Language) parts.push(`Default language: ${tf.Language}`);
20
+ parts.push(
21
+ `You are ${toAgent.slug}. You just received a message from ${fromAgent.slug}. Reply concisely.`
22
+ );
23
+ if (projectPath && toAgent.slug) {
24
+ const memPath = apcAgentMemoryFile(projectPath, toAgent.slug);
25
+ if (fs.existsSync(memPath)) {
26
+ parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
27
+ }
28
+ }
29
+ return parts.join("\n\n");
30
+ }
31
+
32
+ /**
33
+ * Run one A2A turn: build system, call engine, return { text, usage }.
34
+ * Throws on engine failure — caller decides how to surface.
35
+ */
36
+ export async function replyAsAgent({ projectPath, toAgent, fromAgent, body, config }) {
37
+ if (!toAgent?.fields?.Model) {
38
+ throw new Error(`agent ${toAgent?.slug || "?"} has no model`);
39
+ }
40
+ const system = buildA2AReplySystem({ projectPath, toAgent, fromAgent });
41
+ const result = await callEngine({
42
+ modelId: toAgent.fields.Model,
43
+ system,
44
+ messages: [{ role: "user", content: `From ${fromAgent.slug}:\n\n${body}` }],
45
+ config,
46
+ });
47
+ return { text: result.text, usage: result.usage };
48
+ }
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { readAgentMemory } from "./memory.js";
5
+ import { apcProjectFile, apcSkillFile } from "../apc/paths.js";
5
6
 
6
7
  // Anti-ghost-response rules injected into every agent system prompt. The text
7
8
  // lives next to the agent prompts (src/core/agent/prompts/action-discipline.md)
@@ -19,7 +20,7 @@ function listField(value) {
19
20
  function projectName(project) {
20
21
  if (project?.name) return project.name;
21
22
  try {
22
- const meta = JSON.parse(fs.readFileSync(path.join(project.path, ".apc", "project.json"), "utf8"));
23
+ const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
23
24
  return meta.name || path.basename(project.path);
24
25
  } catch {
25
26
  return path.basename(project?.path || "");
@@ -64,11 +65,11 @@ export function buildAgentSystem(project, agent, {
64
65
  const memory = readAgentMemory(project, agent.slug);
65
66
  if (memory) parts.push("## Memory\n" + memory);
66
67
 
67
- const apxSkill = path.join(project.path, ".apc", "skills", "apx.md");
68
+ const apxSkill = apcSkillFile(project.path, "apx");
68
69
  if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
69
70
 
70
71
  for (const skill of agentSkills(agent)) {
71
- const skillPath = path.join(project.path, ".apc", "skills", `${skill}.md`);
72
+ const skillPath = apcSkillFile(project.path, skill);
72
73
  if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
73
74
  }
74
75
 
@@ -0,0 +1,98 @@
1
+ // Channel-aware pre-processor for surfaces that drive the super-agent loop
2
+ // from a voice/deck/desktop entrypoint.
3
+ //
4
+ // Each surface has different ergonomics: how long the reply can be, whether
5
+ // the UI can render structured suggestion chips, what the default project
6
+ // resolution should be. buildVoiceChannelContext() is the single place where
7
+ // those decisions live. Callers (api/voice.js today; any future overlay or
8
+ // device adapter tomorrow) pass the channel string + dynamic context and
9
+ // receive the context note + system suffix to feed into the super-agent.
10
+ //
11
+ // Shape:
12
+ // contextNote — prepended to the prompt (dynamic, per-request)
13
+ // systemSuffix — concatenated onto the system prompt (per-surface rules)
14
+ // wantsSuggestions — whether the surface can render the trailing
15
+ // `suggestions` JSON block (deck/desktop UI can; raw
16
+ // Telegram cannot)
17
+ // channel — resolved surface ("deck"/"desktop"/"telegram"/…)
18
+ // channelMeta — surface metadata (e.g. `{ voice: true }` flags spoken mode)
19
+ import { CHANNELS } from "../../constants/channels.js";
20
+
21
+ // Balanced suggestions instruction. An earlier, more aggressive version
22
+ // ("EJECUTA, no narres — LLAMÁ A LA TOOL") made Gemini call tools for
23
+ // EVERYTHING, even "hola" → send_telegram("hola"). The rule below gates
24
+ // tool use on a *clear* action request and explicitly tells the model to
25
+ // just talk for chit-chat.
26
+ export const SUGGESTIONS_INSTRUCTION = `
27
+
28
+ # Cuándo usar tools
29
+ SOLO llamá una tool cuando el usuario pide CLARAMENTE una acción
30
+ concreta: "creá una tarea …", "mandá un telegram …", "listá …",
31
+ "abrí …", "marcá como hecha …". En esos casos ejecutá la tool (no
32
+ digas "lo voy a hacer" — hacelo) y después confirmá en una frase corta
33
+ en castellano lo que YA hiciste.
34
+
35
+ Si el mensaje es un saludo, una pregunta, o charla ("hola", "cómo
36
+ andás", "qué podés hacer") NO llames ninguna tool: respondé en texto,
37
+ breve, en castellano.
38
+
39
+ Nunca llames la misma tool dos veces en el mismo turno.
40
+
41
+ # Sugerencias (opcional)
42
+ Al final, en su propia línea, podés agregar un bloque fenced
43
+ \`suggestions\` con 2-3 próximos pasos. El usuario NO lo ve (la deck lo
44
+ quita):
45
+ \`\`\`suggestions
46
+ [{"label":"Ver tareas","command":"deck.view:tasks"}]
47
+ \`\`\`
48
+ Si no hay próximos pasos útiles, omití el bloque.`;
49
+
50
+ function buildLanguageDirective(language) {
51
+ return language === "es"
52
+ ? "IMPORTANT: Reply ALWAYS in Spanish (rioplatense/Argentina). The user speaks Spanish."
53
+ : `IMPORTANT: Reply in language "${language}".`;
54
+ }
55
+
56
+ function buildProjectHint(projectId) {
57
+ // Project resolution hint:
58
+ // per-project mic (projectId set): use it imperatively, don't ask.
59
+ // global deck mic (no projectId): default to project id=0 ("default")
60
+ // for actions unless the user names a project out loud.
61
+ return projectId
62
+ ? `\nThe active project is id=${projectId}. For ANY task/note/list ` +
63
+ `action, pass project_id=${projectId} automatically. Do NOT ask the ` +
64
+ `user which project — only switch if they explicitly name another.`
65
+ : `\nThis is the GLOBAL mic (no project in focus). For task/note/list ` +
66
+ `actions, default to project_id=0 ("default") UNLESS the user names ` +
67
+ `a project out loud (e.g. "en evolution-registry…", "en el proyecto ` +
68
+ `apx…") — then resolve that project by name. Never ask "¿en qué ` +
69
+ `proyecto?"; pick the default and act.`;
70
+ }
71
+
72
+ export function buildVoiceChannelContext(channel, { projectId, language = "es" } = {}) {
73
+ const base = {
74
+ contextNote: "",
75
+ systemSuffix: "",
76
+ wantsSuggestions: false,
77
+ channel: "",
78
+ channelMeta: {},
79
+ };
80
+ const dynamicNote = `${buildLanguageDirective(language)}${buildProjectHint(projectId)}`;
81
+
82
+ // Channels are surfaces; "voice" is NOT a surface — it's the spoken MODE of
83
+ // the deck. All channel FORMATTING lives in channels/*.md + modes/voice.md
84
+ // (injected by buildSuperAgentSystem); contextNote here carries ONLY
85
+ // per-request dynamic bits (language + project).
86
+ switch (channel) {
87
+ case "voice":
88
+ return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: { voice: true } };
89
+ case "deck":
90
+ return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: {} };
91
+ case "desktop":
92
+ return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DESKTOP, channelMeta: { voice: true } };
93
+ case "telegram":
94
+ return { ...base, contextNote: dynamicNote, channel: CHANNELS.TELEGRAM, channelMeta: {} };
95
+ default:
96
+ return { ...base, contextNote: dynamicNote, channel: channel || "api", channelMeta: {} };
97
+ }
98
+ }
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { projectStorageRoot } from "../config/index.js";
4
4
  import { getOrCreateApxId } from "../apc/scaffold.js";
5
+ import { apcAgentMemoryFile } from "../apc/paths.js";
5
6
 
6
7
  const EMPTY_MEMORY = (slug) =>
7
8
  `# Memory — ${slug}\n\n` +
@@ -27,7 +28,7 @@ export function agentMemoryPath(projectOrRoot, slug) {
27
28
  }
28
29
 
29
30
  export function legacyAgentMemoryPath(projectRoot, slug) {
30
- return path.join(projectRoot, ".apc", "agents", slug, "memory.md");
31
+ return apcAgentMemoryFile(projectRoot, slug);
31
32
  }
32
33
 
33
34
  export function ensureAgentRuntimeDir(projectOrRoot, slug, { createMemory = false } = {}) {
@@ -8,6 +8,7 @@ import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { readIdentity } from "../identity/index.js";
11
+ import { agentsMdFile } from "../apc/paths.js";
11
12
  import { readSelfMemoryForPrompt } from "./self-memory.js";
12
13
  import { buildSkillsHintBlock } from "./skills/catalog.js";
13
14
 
@@ -97,7 +98,7 @@ export const PROJECT_AGENTS_MAX_CHARS = 6000;
97
98
  export function buildProjectAgentsBlock(projectPath) {
98
99
  if (!projectPath) return "";
99
100
  try {
100
- const file = path.join(projectPath, "AGENTS.md");
101
+ const file = agentsMdFile(projectPath);
101
102
  if (!fs.existsSync(file)) return "";
102
103
  let text = fs.readFileSync(file, "utf8").trim();
103
104
  if (!text) return "";
@@ -0,0 +1 @@
1
+ MODE: build. Make the changes directly using your file and shell tools (read_file, write_file, edit_file, run_shell, …). Do not ask for confirmation and do not stop after one step — keep calling tools until the entire task is done, then briefly summarize what you changed and why. Prefer surgical edits over rewrites. When the user asks for a reusable script, snippet, or 'artifact' (something they want to keep and run later), put it under `artifacts/<name>` inside the project — it then shows up in the Artifacts tab. Don't drop reusable scripts at the project root. If a parameter you need is missing (API key, app id, target URL, …), call `ask_questions` ONCE with all your questions and stop — control returns to the user. Do not call ask_questions again in the same turn; you'll just get the same blank state back. Each question can be a string (free-text answer) OR an object {question, options:[{label, description}], multiSelect} for choices. Prefer 2–4 mutually-exclusive options when a question has a natural shortlist (yes/no, which-of-these, …); leave options empty for open-ended answers (API keys, names, free-form ideas). If the previous assistant turn already asked these same questions and the current user message is the compiled answers, DO NOT call ask_questions again — process the answers and proceed with the task.
@@ -0,0 +1 @@
1
+ MODE: plan. Investigate the codebase (read/list/search/grep) and propose an approach with the EXACT changes you would make (files + diffs/snippets). Do NOT write or edit files and do NOT run mutating shell commands — your editing tools are disabled in this mode. End with a concise, ordered plan.
@@ -0,0 +1,28 @@
1
+ // Mode-specific system-prompt fragments for the Code module (plan / build).
2
+ // Each mode lives in its own .md sibling and is loaded once at boot.
3
+ //
4
+ // Why .md files instead of inline strings: the prompts are content, not code.
5
+ // Editing prompt copy shouldn't require touching code, and reviewing changes
6
+ // to behavior should be a doc-shaped diff, not a JS-shaped one.
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { CODE_MODES } from "#core/constants/code-modes.js";
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ function load(file) {
15
+ return fs.readFileSync(path.join(__dirname, file), "utf8").trim();
16
+ }
17
+
18
+ const CODE_PLAN_GUIDANCE = load("code-plan.md");
19
+ const CODE_BUILD_GUIDANCE = load("code-build.md");
20
+
21
+ /**
22
+ * Return the system-prompt fragment that explains how this mode behaves to
23
+ * the agent. Falls back to BUILD when the mode is missing or unknown — build
24
+ * is the safer default for the Code module (no mid-edit "do nothing" stall).
25
+ */
26
+ export function codeModeGuidance(mode) {
27
+ return mode === CODE_MODES.PLAN ? CODE_PLAN_GUIDANCE : CODE_BUILD_GUIDANCE;
28
+ }
@@ -10,15 +10,19 @@
10
10
  // 1. <projectPath>/.apc/skills/<slug>.md ← project-scoped (flat)
11
11
  // 1b.<projectPath>/.apc/skills/<slug>/SKILL.md ← project-scoped (dir)
12
12
  // 2. ~/.apx/skills/<slug>/SKILL.md ← user-installed global
13
- // 3. <packageRoot>/skills/<slug>/SKILL.md ← bundled core skills
14
- // (apx, apc-context)
15
- // 4. <packageRoot>/src/core/runtime-skills/<slug>.md
16
- // (claude-code, codex-cli,
17
- // opencode-cli, openrouter)
13
+ // 3. <packageRoot>/src/core/runtime-skills/<slug>/SKILL.md
14
+ // runtime-internal set
15
+ // (rich apx-*, apc-context,
16
+ // claude-code, codex-cli,
17
+ // opencode-cli, openrouter)
18
18
  //
19
- // A slug found in a higher-priority location SHADOWS lower ones. A user can
20
- // override the bundled apc-context by dropping `~/.apx/skills/apc-context/SKILL.md`,
21
- // but the bundled copy stays in the package as a safety net.
19
+ // A slug found in a higher-priority location SHADOWS lower ones. The user can
20
+ // override any runtime skill by dropping `~/.apx/skills/<slug>/SKILL.md`; the
21
+ // in-repo copy stays as a safety net.
22
+ //
23
+ // NOTE: <packageRoot>/skills/<slug>/SKILL.md is intentionally NOT in this chain.
24
+ // That dir holds the engine-side slim set replicated to external CLIs/IDEs
25
+ // (~/.claude/skills/, ~/.codex/skills/, ...) — it's not for the super-agent.
22
26
  //
23
27
  // Note: the bundled `apc-context` skill is REFRESHED from the canonical apc
24
28
  // repo on every npm install / update (see src/interfaces/cli/postinstall.js). APC is a
@@ -28,6 +32,7 @@ import fs from "node:fs";
28
32
  import path from "node:path";
29
33
  import os from "node:os";
30
34
  import { fileURLToPath } from "node:url";
35
+ import { apcSkillsDir } from "#core/apc/paths.js";
31
36
 
32
37
  const __filename = fileURLToPath(import.meta.url);
33
38
  const __dirname = path.dirname(__filename);
@@ -35,8 +40,10 @@ const __dirname = path.dirname(__filename);
35
40
  // → repo root. Used to find the bundled skills/ folder at the repo root.
36
41
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..", "..");
37
42
 
38
- const RUNTIME_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
39
- const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, "skills");
43
+ // Runtime-internal skills — full apx-* catalog + CLI docs. Lives outside
44
+ // <packageRoot>/skills/ so external tools that copy "skills/" from the repo
45
+ // don't accidentally pull the rich set or the runtime CLI docs.
46
+ const BUILTIN_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
40
47
  const GLOBAL_DIR = path.join(os.homedir(), ".apx", "skills");
41
48
 
42
49
  // ---------------------------------------------------------------------------
@@ -119,7 +126,7 @@ export function listSkills({ projectPath } = {}) {
119
126
 
120
127
  // priority 1: project-scoped
121
128
  if (projectPath) {
122
- const apcSkills = path.join(projectPath, ".apc", "skills");
129
+ const apcSkills = apcSkillsDir(projectPath);
123
130
  found.push(...scanDirStyle(apcSkills, "project"));
124
131
  found.push(...scanFlatStyle(apcSkills, "project"));
125
132
  }
@@ -127,11 +134,9 @@ export function listSkills({ projectPath } = {}) {
127
134
  // priority 2: user-installed global
128
135
  found.push(...scanDirStyle(GLOBAL_DIR, "global"));
129
136
 
130
- // priority 3: bundled core skills (apx, apc-context)
131
- found.push(...scanDirStyle(BUNDLED_SKILLS_DIR, "builtin"));
132
-
133
- // priority 4: runtime docs (claude-code, codex-cli, opencode-cli, openrouter)
134
- found.push(...scanFlatStyle(RUNTIME_SKILLS_DIR, "builtin"));
137
+ // priority 3: runtime-internal builtin set
138
+ // (rich apx-*, apc-context, plus claude-code, codex-cli, opencode-cli, openrouter)
139
+ found.push(...scanDirStyle(BUILTIN_SKILLS_DIR, "builtin"));
135
140
 
136
141
  // dedupe by slug (first-wins = higher priority shadows lower)
137
142
  const seen = new Set();
@@ -189,7 +194,6 @@ export function loadSkill(slug, { projectPath } = {}) {
189
194
 
190
195
  // Useful for diagnostics
191
196
  export const SKILL_LOCATIONS = {
192
- runtime_skills: RUNTIME_SKILLS_DIR,
193
- bundled: BUNDLED_SKILLS_DIR,
197
+ builtin: BUILTIN_SKILLS_DIR,
194
198
  global: GLOBAL_DIR,
195
199
  };
@@ -0,0 +1,73 @@
1
+ // Accumulate super-agent stream events into the rich ChatPart shape so a
2
+ // persisted assistant turn matches exactly what the UI rendered live.
3
+ // Mirrors the front-end reducer in hooks/useChat.ts (applyStreamEvent) — keep
4
+ // the two in sync if you add a new event type.
5
+ //
6
+ // Pure: no I/O, no globals. Caller drives it event-by-event and finally calls
7
+ // build() to snapshot the resulting parts/notes/model/usage.
8
+ export function makeTurnAccumulator() {
9
+ const parts = [];
10
+ const notes = [];
11
+ let model = null;
12
+ let usage = null;
13
+ const findTool = (id) => parts.find((p) => p.kind === "tool" && p.id === id);
14
+ return {
15
+ apply(ev) {
16
+ switch (ev?.type) {
17
+ case "model_start":
18
+ if (ev.model) model = ev.model;
19
+ break;
20
+ case "model_routed":
21
+ if (ev.model) model = ev.model;
22
+ if (ev.from_fallback) notes.push(`routing fell back → ${ev.model}`);
23
+ break;
24
+ case "engine_failed":
25
+ notes.push(`engine ${ev.model || "?"} failed → ${ev.retry_with || "retry"}`);
26
+ break;
27
+ case "model_retry":
28
+ notes.push(`retry (${ev.reason || "?"})`);
29
+ break;
30
+ case "tools_suppressed":
31
+ notes.push(`tools suppressed: ${(ev.tools || []).join(", ")}`);
32
+ break;
33
+ case "assistant_text":
34
+ if (ev.text) parts.push({ kind: "text", text: ev.text });
35
+ break;
36
+ case "tool_start":
37
+ if (ev.trace)
38
+ parts.push({
39
+ kind: "tool",
40
+ id: ev.trace.id,
41
+ tool: ev.trace.tool,
42
+ args: ev.trace.args,
43
+ status: "running",
44
+ });
45
+ break;
46
+ case "tool_deduped": {
47
+ const t = ev.trace && findTool(ev.trace.id);
48
+ if (t) t.status = "deduped";
49
+ break;
50
+ }
51
+ case "tool_result": {
52
+ const t = ev.trace && findTool(ev.trace.id);
53
+ if (t) {
54
+ t.result = ev.trace.result;
55
+ const errored =
56
+ ev.trace.result && typeof ev.trace.result === "object" && ev.trace.result.error;
57
+ t.status = errored ? "error" : t.status === "deduped" ? "deduped" : "done";
58
+ }
59
+ break;
60
+ }
61
+ case "final":
62
+ usage = ev.result?.usage ?? usage;
63
+ if (!model) model = ev.result?.name || null;
64
+ break;
65
+ default:
66
+ break;
67
+ }
68
+ },
69
+ build() {
70
+ return { parts, notes, model, usage };
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,37 @@
1
+ // Parse the trailing ```suggestions JSON``` block out of an agent reply.
2
+ // Surfaces like the deck/desktop render the JSON as chips; the visible reply
3
+ // should be stripped of the fenced block so the user (and TTS) never sees it.
4
+ //
5
+ // Pure: just regex + JSON.parse. Malformed JSON drops suggestions silently
6
+ // rather than failing the turn — better UX to show the reply without chips
7
+ // than an error.
8
+ const SUGGESTIONS_BLOCK_RE = /\n*```\s*suggestions\s*\n([\s\S]*?)\n?```\s*$/i;
9
+
10
+ const MAX_SUGGESTIONS = 4;
11
+ const MAX_LABEL_LEN = 48;
12
+ const MAX_COMMAND_LEN = 96;
13
+
14
+ export function extractSuggestions(text) {
15
+ if (typeof text !== "string" || !text) {
16
+ return { cleanText: text || "", suggestions: [] };
17
+ }
18
+ const m = SUGGESTIONS_BLOCK_RE.exec(text);
19
+ if (!m) return { cleanText: text, suggestions: [] };
20
+ const cleanText = text.slice(0, m.index).trim();
21
+ let suggestions = [];
22
+ try {
23
+ const parsed = JSON.parse(m[1]);
24
+ if (Array.isArray(parsed)) {
25
+ suggestions = parsed
26
+ .filter((s) => s && typeof s === "object" && typeof s.label === "string")
27
+ .slice(0, MAX_SUGGESTIONS)
28
+ .map((s) => ({
29
+ label: String(s.label).slice(0, MAX_LABEL_LEN),
30
+ ...(typeof s.command === "string" ? { command: s.command.slice(0, MAX_COMMAND_LEN) } : {}),
31
+ }));
32
+ }
33
+ } catch {
34
+ // Malformed JSON — drop silently.
35
+ }
36
+ return { cleanText, suggestions };
37
+ }