@agentprojectcontext/apx 1.33.1 → 1.35.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 (208) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +49 -61
  3. package/src/core/agent/a2a/reply.js +48 -0
  4. package/src/core/agent/build-agent-system.js +136 -59
  5. package/src/core/agent/channels/voice-context.js +98 -0
  6. package/src/core/agent/memory.js +2 -1
  7. package/src/core/agent/prompt-builder.js +178 -124
  8. package/src/core/agent/prompts/channels/code.md +12 -10
  9. package/src/core/agent/prompts/channels/desktop.md +5 -32
  10. package/src/core/agent/prompts/channels/telegram.md +4 -15
  11. package/src/core/agent/prompts/channels/web_code.md +11 -11
  12. package/src/core/agent/prompts/core/agent-base.md +24 -0
  13. package/src/core/agent/prompts/core/project-agent.md +11 -0
  14. package/src/core/agent/prompts/core/super-agent.md +21 -0
  15. package/src/core/agent/prompts/discipline/action.md +10 -0
  16. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  17. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  18. package/src/core/agent/prompts/modes/code-build.md +1 -0
  19. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  20. package/src/core/agent/prompts/modes/index.js +28 -0
  21. package/src/core/agent/self-memory.js +43 -1
  22. package/src/core/agent/skills/index-store.js +307 -0
  23. package/src/core/agent/skills/index.js +15 -1
  24. package/src/core/agent/skills/inspector.js +317 -0
  25. package/src/core/agent/skills/loader.js +22 -18
  26. package/src/core/agent/stream/turn-accumulator.js +73 -0
  27. package/src/core/agent/suggestions.js +37 -0
  28. package/src/core/agent/super-agent.js +7 -1
  29. package/src/core/agent/tools/handlers/_git.js +50 -0
  30. package/src/core/agent/tools/handlers/add-project.js +5 -2
  31. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  32. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  33. package/src/core/agent/tools/handlers/git-log.js +38 -0
  34. package/src/core/agent/tools/handlers/git-show.js +34 -0
  35. package/src/core/agent/tools/handlers/git-status.js +61 -0
  36. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  37. package/src/core/agent/tools/helpers.js +2 -2
  38. package/src/core/agent/tools/names.js +169 -0
  39. package/src/core/agent/tools/registry-bridge.js +6 -14
  40. package/src/core/agent/tools/registry.js +103 -69
  41. package/src/core/apc/context-copy.js +27 -0
  42. package/src/core/apc/notes.js +19 -0
  43. package/src/core/apc/parser.js +12 -5
  44. package/src/core/apc/paths.js +87 -0
  45. package/src/core/apc/scaffold.js +82 -76
  46. package/src/core/apc/skill-sync.js +10 -0
  47. package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
  48. package/src/core/config/index.js +24 -2
  49. package/src/core/config/redact.js +95 -0
  50. package/src/core/constants/channels.js +2 -0
  51. package/src/core/constants/code-modes.js +10 -0
  52. package/src/core/constants/index.js +1 -0
  53. package/src/core/deck/manifest.js +186 -0
  54. package/src/core/engines/catalog.js +83 -0
  55. package/src/core/{tools → http-tools}/browser.js +0 -1
  56. package/src/core/{tools → http-tools}/fetch.js +0 -1
  57. package/src/core/{tools → http-tools}/glob.js +0 -1
  58. package/src/core/{tools → http-tools}/grep.js +0 -1
  59. package/src/core/{tools → http-tools}/registry.js +0 -1
  60. package/src/core/{tools → http-tools}/search.js +0 -1
  61. package/src/core/i18n/en.js +9 -0
  62. package/src/core/i18n/es.js +12 -0
  63. package/src/core/i18n/index.js +54 -0
  64. package/src/core/i18n/pt.js +9 -0
  65. package/src/core/identity/telegram.js +2 -1
  66. package/src/core/mcp/runner.js +272 -14
  67. package/src/core/mcp/sources.js +3 -2
  68. package/src/core/routines/index.js +16 -0
  69. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  70. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  71. package/src/core/runtime-skills/apx/SKILL.md +83 -0
  72. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
  73. package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
  74. package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
  75. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
  76. package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
  77. package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
  78. package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
  79. package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
  80. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
  81. package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
  82. package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
  83. package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
  84. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  85. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  86. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  87. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  88. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  89. package/src/core/stores/code-sessions.js +50 -2
  90. package/src/core/stores/routine-memory.js +1 -1
  91. package/src/core/stores/sessions-search.js +121 -0
  92. package/src/core/stores/sessions.js +38 -0
  93. package/src/core/vars/index.js +14 -0
  94. package/src/core/vars/interpolate.js +86 -0
  95. package/src/core/vars/sources.js +151 -0
  96. package/src/core/voice/audio-decode.js +38 -0
  97. package/src/core/voice/transcription.js +225 -0
  98. package/src/host/daemon/api/admin-config.js +5 -82
  99. package/src/host/daemon/api/agents.js +5 -5
  100. package/src/host/daemon/api/code.js +17 -169
  101. package/src/host/daemon/api/config.js +3 -4
  102. package/src/host/daemon/api/conversations.js +8 -29
  103. package/src/host/daemon/api/deck.js +37 -404
  104. package/src/host/daemon/api/engines.js +1 -80
  105. package/src/host/daemon/api/exec.js +1 -1
  106. package/src/host/daemon/api/mcps.js +32 -0
  107. package/src/host/daemon/api/routines.js +1 -1
  108. package/src/host/daemon/api/runtimes.js +4 -3
  109. package/src/host/daemon/api/sessions-search.js +24 -140
  110. package/src/host/daemon/api/sessions.js +12 -30
  111. package/src/host/daemon/api/shared.js +2 -1
  112. package/src/host/daemon/api/skills.js +140 -6
  113. package/src/host/daemon/api/super-agent.js +56 -1
  114. package/src/host/daemon/api/telegram.js +1 -11
  115. package/src/host/daemon/api/tools.js +6 -6
  116. package/src/host/daemon/api/transcribe.js +2 -2
  117. package/src/host/daemon/api/vars.js +137 -0
  118. package/src/host/daemon/api/voice.js +13 -290
  119. package/src/host/daemon/api.js +2 -0
  120. package/src/host/daemon/db.js +6 -6
  121. package/src/host/daemon/deck-exec.js +148 -0
  122. package/src/host/daemon/index.js +20 -3
  123. package/src/host/daemon/plugins/telegram/index.js +9 -9
  124. package/src/host/daemon/routines-scheduler.js +64 -0
  125. package/src/host/daemon/smoke.js +3 -2
  126. package/src/host/daemon/whisper-server.js +225 -0
  127. package/src/interfaces/cli/branding.js +53 -0
  128. package/src/interfaces/cli/commands/agent.js +3 -2
  129. package/src/interfaces/cli/commands/command.js +2 -3
  130. package/src/interfaces/cli/commands/messages.js +6 -2
  131. package/src/interfaces/cli/commands/pair.js +5 -4
  132. package/src/interfaces/cli/commands/search.js +1 -1
  133. package/src/interfaces/cli/commands/sessions.js +3 -2
  134. package/src/interfaces/cli/commands/skills.js +290 -55
  135. package/src/interfaces/cli/index.js +84 -2
  136. package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
  137. package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
  138. package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
  139. package/src/interfaces/web/dist/index.html +2 -2
  140. package/src/interfaces/web/package-lock.json +182 -182
  141. package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
  142. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  143. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  144. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
  145. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  146. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  147. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  148. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  149. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  150. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  151. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  152. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  153. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  154. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  155. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  156. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  157. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  158. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
  159. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  160. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  161. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  162. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  163. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  164. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  165. package/src/interfaces/web/src/constants/index.ts +1 -1
  166. package/src/interfaces/web/src/hooks/useChat.ts +19 -0
  167. package/src/interfaces/web/src/i18n/en.ts +175 -7
  168. package/src/interfaces/web/src/i18n/es.ts +180 -15
  169. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  170. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  171. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  172. package/src/interfaces/web/src/lib/api.ts +1 -0
  173. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  174. package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
  175. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  176. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  177. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  178. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  179. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  180. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  181. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  182. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  183. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  184. package/src/interfaces/web/src/types/daemon.ts +15 -0
  185. package/skills/apx-agency-agents/SKILL.md +0 -141
  186. package/skills/apx-agent/SKILL.md +0 -100
  187. package/skills/apx-mcp-builder/SKILL.md +0 -183
  188. package/skills/apx-routine/SKILL.md +0 -140
  189. package/skills/apx-runtime/SKILL.md +0 -117
  190. package/skills/apx-sessions/SKILL.md +0 -281
  191. package/skills/apx-skill-builder/SKILL.md +0 -153
  192. package/skills/apx-telegram/SKILL.md +0 -131
  193. package/skills/apx-voice/SKILL.md +0 -137
  194. package/src/core/agent/prompts/action-discipline.md +0 -24
  195. package/src/core/agent/prompts/super-agent-base.md +0 -42
  196. package/src/host/daemon/transcription.js +0 -538
  197. package/src/host/daemon/whisper-transcribe.py +0 -73
  198. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
  199. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
  200. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
  201. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  202. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  203. /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
  204. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  205. /package/src/core/{tools → http-tools}/index.js +0 -0
  206. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  207. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  208. /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.1",
3
+ "version": "1.35.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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', 'ask codex/claude/opencode/gemini to …', 'have <runtime> do …', 'delegate to another runtime', 'run this in <runtime>', 'route this task to <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
+ }
@@ -1,15 +1,26 @@
1
+ // System prompt for project agents (Cody, Sofía, etc.). Shares the agent-base
2
+ // + action discipline with the super-agent, layered with a project-agent role
3
+ // delta plus the agent's own profile fields.
4
+ //
5
+ // When a project agent answers through a real user channel (Telegram, web,
6
+ // desktop), pass `channel` / `channelMeta` / `sender` so the channel-context,
7
+ // relationship and voice-mode / segmenting blocks come along — without that
8
+ // the agent has no idea HOW the user is talking to it.
1
9
  import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
10
  import { readAgentMemory } from "./memory.js";
5
-
6
- // Anti-ghost-response rules injected into every agent system prompt. The text
7
- // lives next to the agent prompts (src/core/agent/prompts/action-discipline.md)
8
- // so it can be edited without touching code. Cached at module load.
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const ACTION_DISCIPLINE_RULES = fs
11
- .readFileSync(path.join(__dirname, "prompts", "action-discipline.md"), "utf8")
12
- .trimEnd();
11
+ import { apcProjectFile, apcSkillFile } from "../apc/paths.js";
12
+ import {
13
+ PROMPTS,
14
+ buildChannelContextBlock,
15
+ buildVoiceModeBlock,
16
+ buildRelationshipBlock,
17
+ buildUserContextBlock,
18
+ buildSegmentDiscipline,
19
+ } from "./prompt-builder.js";
20
+
21
+ // Cap the injected agent body so an over-long authored file can't blow the
22
+ // token budget. Mirrors PROJECT_AGENTS_MAX_CHARS for AGENTS.md.
23
+ const AGENT_BODY_MAX_CHARS = 6000;
13
24
 
14
25
  function listField(value) {
15
26
  if (Array.isArray(value)) return value.map(String).map((s) => s.trim()).filter(Boolean);
@@ -19,10 +30,10 @@ function listField(value) {
19
30
  function projectName(project) {
20
31
  if (project?.name) return project.name;
21
32
  try {
22
- const meta = JSON.parse(fs.readFileSync(path.join(project.path, ".apc", "project.json"), "utf8"));
23
- return meta.name || path.basename(project.path);
33
+ const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
34
+ return meta.name || project.path?.split("/").pop() || "";
24
35
  } catch {
25
- return path.basename(project?.path || "");
36
+ return project?.path?.split("/").pop() || "";
26
37
  }
27
38
  }
28
39
 
@@ -30,80 +41,146 @@ export function agentSkills(agent) {
30
41
  return listField(agent?.fields?.Skills);
31
42
  }
32
43
 
44
+ /**
45
+ * Build the system prompt for a project agent.
46
+ *
47
+ * @param project { id, name, path, ... }
48
+ * @param agent { slug, fields: { Description, Role, Language, Skills, Tools, ... }, body }
49
+ * @param opts
50
+ * @param opts.invocation "engine" (direct LLM call), "telegram", "routine", etc.
51
+ * @param opts.runtime external runtime name when relevant ("claude-code", …)
52
+ * @param opts.channel surface the user is on (channels/<name>.md is layered in)
53
+ * @param opts.channelMeta meta for the channel template + `{voice: true}` flag
54
+ * @param opts.sender resolved sender for the relationship block
55
+ * @param opts.caller who invoked us (another agent slug, "user", "routine", …)
56
+ * @param opts.routine routine name when invocation === "routine"
57
+ * @param opts.globalConfig used for user.language / user.locale / user.timezone
58
+ * @param opts.extraParts additional blocks to append before discipline
59
+ */
33
60
  export function buildAgentSystem(project, agent, {
34
61
  invocation = "engine",
35
62
  runtime = null,
36
63
  channel = null,
64
+ channelMeta = {},
65
+ sender = null,
37
66
  caller = null,
38
67
  routine = null,
68
+ globalConfig = {},
39
69
  extraParts = [],
40
70
  } = {}) {
41
71
  const fields = agent.fields || {};
42
- const parts = [
43
- `You are APC agent "${agent.slug}".`,
44
- `Project: ${projectName(project)} (${project.path}).`,
45
- ];
72
+ const channelLow = String(channel || "").toLowerCase();
73
+ const voice = !!channelMeta?.voice || channelLow === "voice";
46
74
 
47
- if (fields.Description) parts.push(String(fields.Description));
48
- if (fields.Role) parts.push(`Role: ${fields.Role}`);
49
- if (fields.Language) parts.push(`Default language: ${fields.Language}`);
75
+ // Shared base + project-agent role delta (the "I'm scoped to one project" framing).
76
+ const roleBlock = [PROMPTS.AGENT_BASE, PROMPTS.PROJECT_AGENT_ROLE].join("\n\n");
50
77
 
51
- const declaredTools = listField(fields.Tools);
52
- if (declaredTools.length) {
53
- parts.push(
54
- [
55
- "## Declared Tool Hints",
56
- declaredTools.join(", "),
57
- "These are agent-level tool expectations, not a guarantee. Actual callable tools depend on invocation surface.",
58
- ].join("\n")
59
- );
78
+ // Agent profile (its display name + description + Role + Language + owner from config).
79
+ const profileLines = [
80
+ `# Agent profile`,
81
+ `You are **${agent.slug}**, a project agent dedicated to **${projectName(project)}** (\`${project.path}\`).`,
82
+ ];
83
+ if (fields.Description) profileLines.push(fields.Description);
84
+ if (fields.Role) profileLines.push(`Role: ${fields.Role}`);
85
+ if (fields.Language) profileLines.push(`Default language: ${fields.Language}`);
86
+
87
+ // The agent's authored body (everything after the frontmatter in its
88
+ // `.apc/agents/<slug>.md`) is its real instruction set — persona, domain
89
+ // rules, API endpoints, tone, hard limits. Without injecting it the agent
90
+ // runs on its fields alone and loses everything its author actually wrote.
91
+ let customBody = String(agent.body || "").trim();
92
+ if (customBody.length > AGENT_BODY_MAX_CHARS) {
93
+ customBody = customBody.slice(0, AGENT_BODY_MAX_CHARS) + "\n\n…(instructions truncated)";
60
94
  }
95
+ const customInstructions = customBody ? `# Custom instructions\n${customBody}` : "";
61
96
 
62
- parts.push(buildInvocationContext({ invocation, runtime, channel, caller, routine }));
63
-
64
- const memory = readAgentMemory(project, agent.slug);
65
- if (memory) parts.push("## Memory\n" + memory);
97
+ // User context (owner name, language, timezone) same block the super-agent
98
+ // gets, so project agents know how to address the user.
99
+ const userContext = buildUserContextBlock(null, globalConfig, { agentName: agent.slug });
66
100
 
67
- const apxSkill = path.join(project.path, ".apc", "skills", "apx.md");
68
- if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
101
+ // Channel context the same channels/*.md the super-agent uses. Project
102
+ // agents talk through the same surfaces; they need the same formatting rules.
103
+ const channelBlock = buildChannelContextBlock(channel, channelMeta);
104
+ const voiceBlock = buildVoiceModeBlock(voice);
105
+ const segmentDiscipline = buildSegmentDiscipline({ channel: channelLow, voice });
69
106
 
70
- for (const skill of agentSkills(agent)) {
71
- const skillPath = path.join(project.path, ".apc", "skills", `${skill}.md`);
72
- if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
73
- }
107
+ // Relationship block "you're talking to <owner>" / "<contact>" / "<guest>".
108
+ const relationship = buildRelationshipBlock(sender);
74
109
 
75
- for (const ep of extraParts) {
76
- if (ep) parts.push(ep);
77
- }
110
+ // Declared tool hints (informational actual callables come from runtime).
111
+ const declaredTools = listField(fields.Tools);
112
+ const toolHints = declaredTools.length
113
+ ? [
114
+ "## Declared tool hints (agent-level expectations)",
115
+ declaredTools.join(", "),
116
+ "Actual callable tools depend on the invocation surface — use whatever the runtime sends this turn.",
117
+ ].join("\n")
118
+ : "";
78
119
 
79
- // Always append action discipline rules last so they are close to the end
80
- // of the system prompt and harder for the model to "forget".
81
- parts.push(ACTION_DISCIPLINE_RULES);
120
+ // Invocation context (who called me, through what, for what).
121
+ const invocationCtx = buildInvocationContext({ invocation, runtime, caller, routine });
82
122
 
83
- return parts.join("\n\n");
123
+ // Per-agent memory (lives under <project>/.apc/agents/<slug>/memory.md).
124
+ const memory = readAgentMemory(project, agent.slug);
125
+ const memoryBlock = memory ? "# Memory\n" + memory : "";
126
+
127
+ // Project's APX skill + agent's declared skills (loaded as full bodies — they're
128
+ // small and specific to this agent).
129
+ const projectSkills = buildProjectSkills(project, agent);
130
+
131
+ return [
132
+ roleBlock,
133
+ profileLines.join("\n"),
134
+ customInstructions,
135
+ userContext,
136
+ memoryBlock,
137
+ relationship,
138
+ channelBlock,
139
+ toolHints,
140
+ invocationCtx,
141
+ projectSkills,
142
+ ...extraParts.filter(Boolean),
143
+ voiceBlock,
144
+ PROMPTS.ACTION_DISCIPLINE,
145
+ segmentDiscipline,
146
+ ]
147
+ .filter(Boolean)
148
+ .join("\n\n");
84
149
  }
85
150
 
86
- function buildInvocationContext({ invocation, runtime, channel, caller, routine }) {
87
- const lines = [
88
- "## Invocation Context",
89
- `invocation: ${invocation}`,
90
- ];
151
+ function buildInvocationContext({ invocation, runtime, caller, routine }) {
152
+ const lines = [`## Invocation`, `invocation: ${invocation}`];
91
153
  if (runtime) lines.push(`runtime: ${runtime}`);
92
- if (channel) lines.push(`channel: ${channel}`);
93
154
  if (caller) lines.push(`caller: ${caller}`);
94
155
  if (routine) lines.push(`routine: ${routine}`);
95
156
 
96
157
  if (runtime) {
97
158
  lines.push(
98
- "You are running inside the named external runtime. Use only tools and permissions that runtime actually exposes."
159
+ "You're running inside the named external runtime. Use only tools and permissions that runtime exposes."
99
160
  );
100
- } else if (invocation === "engine") {
101
- lines.push("You are a direct LLM call through APX. Do not claim shell, file, MCP, or Telegram tools unless APX explicitly provided them.");
102
- } else if (invocation === "telegram") {
103
- lines.push("You are replying through Telegram. Keep responses brief, plain text, and matched to the user's language.");
104
161
  } else if (invocation === "routine") {
105
- lines.push("You were invoked by an APX routine. Complete the requested work now; do not say you will do it later.");
162
+ lines.push(
163
+ "You were invoked by an APX routine. Complete the requested work now; don't say you will do it later."
164
+ );
165
+ } else if (invocation === "engine") {
166
+ lines.push(
167
+ "You're a direct LLM call through APX. Don't claim shell, file, MCP, or Telegram tools unless the runtime explicitly sent them this turn."
168
+ );
106
169
  }
107
-
108
170
  return lines.join("\n");
109
171
  }
172
+
173
+ function buildProjectSkills(project, agent) {
174
+ const parts = [];
175
+ const apxSkill = apcSkillFile(project.path, "apx");
176
+ if (fs.existsSync(apxSkill)) {
177
+ parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8").trim());
178
+ }
179
+ for (const skill of agentSkills(agent)) {
180
+ const skillPath = apcSkillFile(project.path, skill);
181
+ if (fs.existsSync(skillPath)) {
182
+ parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8").trim());
183
+ }
184
+ }
185
+ return parts.join("\n\n");
186
+ }
@@ -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 CHANNELS.DECK:
90
+ return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: {} };
91
+ case CHANNELS.DESKTOP:
92
+ return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DESKTOP, channelMeta: { voice: true } };
93
+ case CHANNELS.TELEGRAM:
94
+ return { ...base, contextNote: dynamicNote, channel: CHANNELS.TELEGRAM, channelMeta: {} };
95
+ default:
96
+ return { ...base, contextNote: dynamicNote, channel: channel || CHANNELS.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 } = {}) {