@agentprojectcontext/apx 1.33.1 → 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 (169) 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 +4 -3
  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 +2 -1
  8. package/src/core/agent/prompts/modes/code-build.md +1 -0
  9. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  10. package/src/core/agent/prompts/modes/index.js +28 -0
  11. package/src/core/agent/skills/loader.js +22 -18
  12. package/src/core/agent/stream/turn-accumulator.js +73 -0
  13. package/src/core/agent/suggestions.js +37 -0
  14. package/src/core/agent/tools/handlers/add-project.js +5 -2
  15. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  16. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  17. package/src/core/agent/tools/helpers.js +2 -2
  18. package/src/core/agent/tools/names.js +138 -0
  19. package/src/core/agent/tools/registry-bridge.js +6 -14
  20. package/src/core/agent/tools/registry.js +68 -65
  21. package/src/core/apc/context-copy.js +27 -0
  22. package/src/core/apc/notes.js +19 -0
  23. package/src/core/apc/parser.js +12 -5
  24. package/src/core/apc/paths.js +87 -0
  25. package/src/core/apc/scaffold.js +82 -76
  26. package/src/core/apc/skill-sync.js +10 -0
  27. package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
  28. package/src/core/config/index.js +3 -2
  29. package/src/core/config/redact.js +95 -0
  30. package/src/core/constants/channels.js +2 -0
  31. package/src/core/constants/code-modes.js +10 -0
  32. package/src/core/constants/index.js +1 -0
  33. package/src/core/deck/manifest.js +186 -0
  34. package/src/core/engines/catalog.js +83 -0
  35. package/src/core/{tools → http-tools}/browser.js +0 -1
  36. package/src/core/{tools → http-tools}/fetch.js +0 -1
  37. package/src/core/{tools → http-tools}/glob.js +0 -1
  38. package/src/core/{tools → http-tools}/grep.js +0 -1
  39. package/src/core/{tools → http-tools}/registry.js +0 -1
  40. package/src/core/{tools → http-tools}/search.js +0 -1
  41. package/src/core/i18n/en.js +9 -0
  42. package/src/core/i18n/es.js +12 -0
  43. package/src/core/i18n/index.js +54 -0
  44. package/src/core/i18n/pt.js +9 -0
  45. package/src/core/identity/telegram.js +2 -1
  46. package/src/core/mcp/runner.js +272 -14
  47. package/src/core/mcp/sources.js +3 -2
  48. package/src/core/routines/index.js +16 -0
  49. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  50. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  51. package/src/core/runtime-skills/apx/SKILL.md +95 -0
  52. package/src/core/runtime-skills/apx-mcp/SKILL.md +116 -0
  53. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  54. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  55. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  56. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  57. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  58. package/src/core/stores/code-sessions.js +50 -2
  59. package/src/core/stores/routine-memory.js +1 -1
  60. package/src/core/stores/sessions-search.js +121 -0
  61. package/src/core/stores/sessions.js +38 -0
  62. package/src/core/vars/index.js +14 -0
  63. package/src/core/vars/interpolate.js +86 -0
  64. package/src/core/vars/sources.js +151 -0
  65. package/src/core/voice/audio-decode.js +38 -0
  66. package/src/core/voice/transcription.js +225 -0
  67. package/src/host/daemon/api/admin-config.js +5 -82
  68. package/src/host/daemon/api/agents.js +5 -5
  69. package/src/host/daemon/api/code.js +17 -169
  70. package/src/host/daemon/api/config.js +3 -4
  71. package/src/host/daemon/api/conversations.js +8 -29
  72. package/src/host/daemon/api/deck.js +37 -404
  73. package/src/host/daemon/api/engines.js +1 -80
  74. package/src/host/daemon/api/exec.js +1 -1
  75. package/src/host/daemon/api/mcps.js +32 -0
  76. package/src/host/daemon/api/routines.js +1 -1
  77. package/src/host/daemon/api/runtimes.js +4 -3
  78. package/src/host/daemon/api/sessions-search.js +24 -140
  79. package/src/host/daemon/api/sessions.js +12 -30
  80. package/src/host/daemon/api/shared.js +2 -1
  81. package/src/host/daemon/api/telegram.js +1 -11
  82. package/src/host/daemon/api/tools.js +6 -6
  83. package/src/host/daemon/api/transcribe.js +2 -2
  84. package/src/host/daemon/api/vars.js +137 -0
  85. package/src/host/daemon/api/voice.js +13 -290
  86. package/src/host/daemon/api.js +2 -0
  87. package/src/host/daemon/db.js +6 -6
  88. package/src/host/daemon/deck-exec.js +148 -0
  89. package/src/host/daemon/index.js +3 -3
  90. package/src/host/daemon/plugins/telegram/index.js +9 -9
  91. package/src/host/daemon/routines-scheduler.js +64 -0
  92. package/src/host/daemon/smoke.js +3 -2
  93. package/src/host/daemon/whisper-server.js +225 -0
  94. package/src/interfaces/cli/commands/agent.js +3 -2
  95. package/src/interfaces/cli/commands/command.js +2 -3
  96. package/src/interfaces/cli/commands/messages.js +6 -2
  97. package/src/interfaces/cli/commands/pair.js +5 -4
  98. package/src/interfaces/cli/commands/search.js +1 -1
  99. package/src/interfaces/cli/commands/sessions.js +3 -2
  100. package/src/interfaces/cli/commands/skills.js +36 -55
  101. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +1 -0
  102. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +613 -0
  103. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +1 -0
  104. package/src/interfaces/web/dist/index.html +2 -2
  105. package/src/interfaces/web/package-lock.json +182 -182
  106. package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
  107. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  108. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  109. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  110. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  111. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  112. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  113. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  114. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  115. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  116. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  117. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  118. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  119. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  120. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  121. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  122. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  123. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +5 -4
  124. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  125. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  126. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  127. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  128. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  129. package/src/interfaces/web/src/constants/index.ts +1 -1
  130. package/src/interfaces/web/src/i18n/en.ts +174 -7
  131. package/src/interfaces/web/src/i18n/es.ts +179 -15
  132. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  133. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  134. package/src/interfaces/web/src/lib/api.ts +1 -0
  135. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  136. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  137. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  138. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  139. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  140. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  141. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  142. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  143. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  144. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  145. package/src/interfaces/web/src/types/daemon.ts +5 -0
  146. package/src/host/daemon/transcription.js +0 -538
  147. package/src/host/daemon/whisper-transcribe.py +0 -73
  148. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
  149. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
  150. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
  151. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  152. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  153. /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
  154. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  155. /package/src/core/{tools → http-tools}/index.js +0 -0
  156. /package/{skills → src/core/runtime-skills}/apx-agency-agents/SKILL.md +0 -0
  157. /package/{skills → src/core/runtime-skills}/apx-agent/SKILL.md +0 -0
  158. /package/{skills → src/core/runtime-skills}/apx-mcp-builder/SKILL.md +0 -0
  159. /package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +0 -0
  160. /package/{skills → src/core/runtime-skills}/apx-routine/SKILL.md +0 -0
  161. /package/{skills → src/core/runtime-skills}/apx-runtime/SKILL.md +0 -0
  162. /package/{skills → src/core/runtime-skills}/apx-sessions/SKILL.md +0 -0
  163. /package/{skills → src/core/runtime-skills}/apx-skill-builder/SKILL.md +0 -0
  164. /package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +0 -0
  165. /package/{skills → src/core/runtime-skills}/apx-telegram/SKILL.md +0 -0
  166. /package/{skills → src/core/runtime-skills}/apx-voice/SKILL.md +0 -0
  167. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  168. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  169. /package/src/{host/daemon → core/util}/thinking.js +0 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: apc-context
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
+ homepage: https://github.com/agentprojectcontext/agentprojectcontext
5
+ ---
6
+
7
+ # Agent Project Context
8
+
9
+ This project uses APC. APC stores portable project context in `.apc/` and `AGENTS.md`.
10
+
11
+ APC does not store raw runtime sessions. Sessions, conversations, messages, caches, provider
12
+ threads, and private runtime memory stay in the IDE, CLI, daemon, or user-level store that created
13
+ them.
14
+
15
+ ## FIRST: check for pending migration
16
+
17
+ Before doing anything else, check if `.apc/migrate.md` exists:
18
+
19
+ ```bash
20
+ cat .apc/migrate.md 2>/dev/null
21
+ ```
22
+
23
+ If it exists, open with this offer before answering anything else:
24
+
25
+ > I see this project was initialized with Agent Project Context (APC).
26
+ >
27
+ > I found context files that may need migration:
28
+ > [list files from .apc/migrate.md]
29
+ >
30
+ > I can read them, separate durable project context from runtime/private state, and migrate only
31
+ > what belongs in APC.
32
+ >
33
+ > Want me to start?
34
+
35
+ If the user says no or later, delete `.apc/migrate.md` so the offer is not repeated.
36
+
37
+ ## Migration rule: think, do not copy
38
+
39
+ Read detected files first. Also read `AGENTS.md` if it exists.
40
+
41
+ Classify content:
42
+
43
+ | Content | Action |
44
+ |---|---|
45
+ | Agent definitions: name, model, description | Put in `.apc/agents/<name>.md` and/or `AGENTS.md` |
46
+ | Shared project rules, stack notes, commands, testing policy | Keep in `AGENTS.md` |
47
+ | Reusable instruction blocks | Move to `.apc/skills/<name>.md` |
48
+ | Durable safe facts useful to all contributors | Add to `.apc/agents/<name>/memory.md` only after curation |
49
+ | MCP expectations without secrets | Add to `.apc/mcps.json` |
50
+ | Raw sessions, transcripts, conversations, messages, tool logs | Do not move into `.apc/`; leave with source runtime |
51
+ | Secrets, tokens, credentials, private headers | Do not store in repository |
52
+ | IDE UI settings or personal aliases | Leave in IDE/user config |
53
+ | Instructions to store sessions under `.apc/` | Drop as obsolete |
54
+
55
+ After migration:
56
+
57
+ 1. Update `AGENTS.md` as the root project contract.
58
+ 2. Create or update `.apc/agents/`, `.apc/skills/`, `.apc/mcps.json` as needed.
59
+ 3. Do not create `.apc/**/sessions`, `.apc/messages`, or `.apc/conversations`.
60
+ 4. Delete obsolete source files only when their useful project context was migrated or intentionally dropped.
61
+ 5. Delete `.apc/migrate.md`.
62
+ 6. Summarize what moved, what stayed local, and what was dropped.
63
+
64
+ ## APC structure
65
+
66
+ ```text
67
+ AGENTS.md ← root project contract
68
+ .apc/
69
+ project.json ← project metadata
70
+ .gitignore ← safety guard
71
+ agents/<name>.md ← agent definition
72
+ agents/<name>/memory.md ← optional curated project memory
73
+ skills/<name>.md ← reusable project instructions
74
+ mcps.json ← MCP hints without secrets
75
+ ```
76
+
77
+ Do not store:
78
+
79
+ ```text
80
+ .apc/agents/<name>/sessions/
81
+ .apc/sessions/
82
+ .apc/conversations/
83
+ .apc/messages/
84
+ .apc/cache/
85
+ .apc/tmp/
86
+ .apc/private/
87
+ .apc/secrets/
88
+ ```
89
+
90
+ ## Visibility rules
91
+
92
+ | Data | Visibility | Commit? |
93
+ |---|---|---|
94
+ | Agent definitions, skills, project rules | `stable` / `project` | Yes |
95
+ | Curated safe `memory.md` | `project` | Yes, if team-safe |
96
+ | MCP hints without secrets | `project` | Yes |
97
+ | Sessions, conversations, messages | `local` | No; runtime-owned |
98
+ | Secrets, tokens, `*.secret.json`, `*.env` | `private` | Never |
99
+ | Caches, temp files, databases | `ephemeral` | No |
100
+
101
+ ## Operating rules
102
+
103
+ 1. Read `AGENTS.md` and relevant `.apc/` files before assuming project context.
104
+ 2. Read agent definitions from `.apc/agents/<name>.md` when present.
105
+ 3. Read curated project memory from `.apc/agents/<name>/memory.md` when present.
106
+ 4. Write only durable, safe, curated facts to APC memory.
107
+ 5. Never write raw sessions, transcripts, messages, conversations, or tool logs into `.apc/`.
108
+ 6. Keep secrets out of APC and out of git.
109
+ 7. Treat `.apc/mcps.json` as MCP configuration hints, not as an MCP implementation.
110
+
111
+ ## Normalization
112
+
113
+ If agent formats are broken or use legacy fields (role, skills in YAML), offer to normalize:
114
+
115
+ ```yaml
116
+ ---
117
+ name: agent-name
118
+ model: inherit
119
+ description: Semantic activation trigger
120
+ ---
121
+ ```
122
+
123
+ Identify and fix inconsistencies in `model` (use technical IDs or `inherit`) and ensure `description` is present for semantic activation.
124
+
125
+ ## Sessions
126
+
127
+ Sessions belong to the runtime that created them.
128
+
129
+ Examples:
130
+
131
+ ```text
132
+ Codex runtime storage
133
+ Claude Code runtime storage
134
+ OpenCode runtime storage
135
+ ~/.apx/projects/<project-id>/agents/<name>/sessions/
136
+ ```
137
+
138
+ At task end, provide the user a concise result. If project memory should be updated, write a short
139
+ sanitized fact to `.apc/agents/<name>/memory.md` only when useful and safe.
140
+
141
+ ## APX
142
+
143
+ Read `.apc/project.json` if present. It may contain an `apx` field:
144
+
145
+ - `"installed"`: APX is available; use `apx` commands when useful.
146
+ - `"declined"`: user chose not to install; do not suggest or run APX.
147
+ - missing or `null`: unknown; do not assume APX is available.
148
+
149
+ If APX is installed, it may manage runtime state outside the repository:
150
+
151
+ ```text
152
+ ~/.apx/projects/<project-id>/
153
+ ```
154
+
155
+ APX can provide a local daemon, MCP management, Telegram bridge, routines, and runtime dispatch
156
+ across Codex, Claude Code, OpenCode, Aider, or direct LLM engines. Those are APX runtime features,
157
+ not APC portable-core requirements.
158
+
159
+ Never use APX to write secrets or raw sessions into `.apc/`.
@@ -0,0 +1,95 @@
1
+ ---
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'."
4
+ homepage: https://github.com/agentprojectcontext/apx
5
+ ---
6
+
7
+ # APX — Agent Project Context Runtime
8
+
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.
10
+
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.
12
+
13
+ ---
14
+
15
+ ## When to use APX (vs. spawning a subagent natively)
16
+
17
+ If you can spawn a subagent natively in the IDE you're in (Claude Code, Cursor, …) — **do that**. No APX needed.
18
+
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).
23
+
24
+ ---
25
+
26
+ ## Sub-skill index — open the one that matches the task
27
+
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 |
41
+
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>
58
+ ```
59
+
60
+ APX's `extractApfResult()` parses that and stores it as the session's `result` field. Useful for automation, routines, and CI.
61
+
62
+ ### Tool permissions
63
+
64
+ ```bash
65
+ apx permission show
66
+ apx permission set automatico # total | automatico | permiso
67
+ ```
68
+
69
+ `automatico` runs read/list/safe shell checks directly and asks before destructive shell, MCP, runtime, outbound, config, or filesystem mutation actions.
70
+
71
+ ### Memory
72
+
73
+ Write memory only for durable, safe project facts. Do not store raw transcripts or secrets.
74
+
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
+ ```
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
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Anti-patterns
92
+
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).
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: apx-mcp
3
+ description: How to register, list, debug, and scope MCP servers in APX. Use BEFORE adding any MCP — three scopes (shared/runtime/global) with different commit and secrecy semantics.
4
+ ---
5
+
6
+ # apx-mcp
7
+
8
+ APX exposes Model Context Protocol (MCP) servers to agents. Three scopes, each in a different file with different rules:
9
+
10
+ | Scope | File | Committed? | Secrets OK? | When |
11
+ |---|---|---|---|---|
12
+ | `shared` | `<repo>/.apc/mcps.json` | yes | **no** | Team-wide MCPs (filesystem, brave, github public) |
13
+ | `runtime` | `~/.apx/projects/<apxId>/mcps.json` (chmod 0600) | no | yes | Per-project local — tokens, machine-specific endpoints |
14
+ | `global` | `~/.apx/mcps.json` | n/a | yes | Machine-wide — not tied to any project |
15
+
16
+ Resolution priority when a name appears in more than one: **runtime > shared > global**. Conflicts surface in `apx mcp check`.
17
+
18
+ ## Concrete CLI calls
19
+
20
+ ```bash
21
+ # List (all scopes, this is the default)
22
+ apx mcp list --project iacrmar
23
+ apx mcp list --scope runtime --project iacrmar
24
+ apx mcp list --scope shared --project iacrmar
25
+ apx mcp list --scope global
26
+
27
+ # Inspect sources and conflicts
28
+ apx mcp check --project iacrmar
29
+
30
+ # Add — shared (commit to repo)
31
+ apx mcp add filesystem --command npx --project iacrmar \
32
+ -- -y @modelcontextprotocol/server-filesystem .
33
+
34
+ # Add — runtime (per-project, local, secrets safe)
35
+ apx mcp add github --scope runtime --project iacrmar \
36
+ --command npx --env GITHUB_TOKEN=ghp_xxx \
37
+ -- -y @modelcontextprotocol/server-github
38
+
39
+ # Add — global (machine-wide, not tied to a project)
40
+ apx mcp add brave --scope global \
41
+ --command npx --env BRAVE_API_KEY=BSAxxx \
42
+ -- -y @modelcontextprotocol/server-brave-search
43
+
44
+ # Remove (pass --scope when the MCP isn't in the default scope:
45
+ # shared inside an APC project, else global)
46
+ apx mcp remove filesystem --project iacrmar
47
+ apx mcp remove github --scope runtime --project iacrmar
48
+
49
+ # Toggle (defaults to the scope that owns the MCP)
50
+ apx mcp enable filesystem --project iacrmar
51
+ apx mcp disable filesystem --project iacrmar
52
+
53
+ # Call a tool through the daemon (useful for debugging)
54
+ apx mcp run filesystem read_file '{"path":"README.md"}'
55
+ ```
56
+
57
+ ## When the user asks for a new MCP
58
+
59
+ Decision tree:
60
+ 1. **Has secrets / tokens?** → `runtime` scope. Always.
61
+ 2. **Is part of the project's shared dev environment?** → `shared` (committed).
62
+ 3. **Used across all your projects?** → `global`.
63
+
64
+ Default if none is obvious: `shared` when inside an APC project, `global` outside.
65
+
66
+ ## Common command shapes by transport
67
+
68
+ ```bash
69
+ # stdio MCP (most common — npx, uvx, node, python)
70
+ apx mcp add <name> --command npx -- -y <package-or-flag-list>
71
+ apx mcp add <name> --command uvx -- <python-cli-name>
72
+ apx mcp add <name> --command python -- /abs/path/to/server.py
73
+
74
+ # Env vars (one --env per var)
75
+ apx mcp add <name> --command npx \
76
+ --env GITHUB_TOKEN=ghp_xxx \
77
+ --env GITHUB_OWNER=manuel \
78
+ -- -y @modelcontextprotocol/server-github
79
+ ```
80
+
81
+ Anything after `--` is forwarded verbatim as args to the command. Quote carefully.
82
+
83
+ ## Anti-examples
84
+
85
+ ```bash
86
+ # DON'T put tokens in shared scope. It commits.
87
+ apx mcp add github --scope shared --env GITHUB_TOKEN=ghp_xxx ...
88
+ # ↑ Token ends up in .apc/mcps.json in your repo. Use --scope runtime.
89
+
90
+ # DON'T remove an MCP from the wrong scope.
91
+ apx mcp remove github # if github lives in runtime, this errors with a hint
92
+ # ↑ Daemon returns 409 with the right scope to use.
93
+
94
+ # DON'T expect IDE-foreign configs (~/.cursor/mcps.json, ~/.claude/mcps.json) to be
95
+ # removable via apx mcp remove. APX reads them as advisory (source=cursor/claude/etc)
96
+ # but won't write them. Edit the IDE config directly.
97
+ ```
98
+
99
+ ## Debugging connection issues
100
+
101
+ ```bash
102
+ apx mcp check --project iacrmar # what scopes APX sees + which files exist
103
+ apx mcp run <name> <tool> '{...}' # spawn the server and call a tool for real
104
+ apx log -f # tail unified log for spawn errors
105
+ ```
106
+
107
+ A server that "doesn't show tools" usually means: the command failed to start (env vars missing, package not found), or the server crashed during initialize. The unified log has the stderr buffer.
108
+
109
+ > `apx mcp tools <name>` is a placeholder stub (prints a "coming in v0.2" notice, lists nothing). To verify a server actually spawns, call a tool with `apx mcp run`.
110
+
111
+ ## Don't
112
+
113
+ - Don't mix scopes for the same MCP name unless you actually want shadowing. The result is "the one with highest priority wins, others stay invisible."
114
+ - Don't edit `~/.apx/projects/<id>/mcps.json` by hand; use `apx mcp add --scope runtime`. The file is chmod 0600 — the CLI keeps it that way.
115
+ - Don't add tokens via `--env KEY=` inline if your shell history is public. Set them in your shell first, then `--env KEY=$KEY`.
116
+ - Don't forget to `apx daemon reload` after editing config — actually `apx mcp` does this for you, but if you hand-edited the JSON, it's manual.
@@ -1,4 +1,5 @@
1
1
  ---
2
+ scope: internal
2
3
  name: claude-code
3
4
  description: "Activate ONLY when the user explicitly mentions Claude Code, Claude CLI, claude command, Anthropic Claude Code, installing Claude Code, using Claude Code, or APX runtime claude-code. Do not activate for generic Claude model discussion."
4
5
  homepage: https://docs.anthropic.com/en/docs/claude-code
@@ -1,4 +1,5 @@
1
1
  ---
2
+ scope: internal
2
3
  name: codex-cli
3
4
  description: "Activate ONLY when the user explicitly mentions Codex CLI, OpenAI Codex, @openai/codex, codex command, codex exec, installing Codex, using Codex, ~/.codex, or APX runtime codex."
4
5
  homepage: https://developers.openai.com/codex
@@ -1,4 +1,5 @@
1
1
  ---
2
+ scope: internal
2
3
  name: opencode-cli
3
4
  description: "Activate ONLY when the user explicitly mentions OpenCode, opencode command, installing OpenCode, using OpenCode, OpenCode provider setup, or APX runtime opencode."
4
5
  homepage: https://opencode.ai/docs
@@ -1,4 +1,5 @@
1
1
  ---
2
+ scope: internal
2
3
  name: openrouter
3
4
  description: "Activate ONLY when the user explicitly mentions OpenRouter, OPENROUTER_API_KEY, OpenRouter models, installing OpenRouter provider config, or using OpenRouter with APX, OpenCode, LiteLLM, or an OpenAI-compatible client."
4
5
  homepage: https://openrouter.ai/docs
@@ -1,7 +1,7 @@
1
1
  // Best-effort detection of installed agent CLIs and LLM runners.
2
2
  // We just probe the binary with `--version` (or equivalent) and don't fail if
3
3
  // it isn't there — caller decides what to do with absence.
4
- import { runProcess } from "./runtimes/_spawn.js";
4
+ import { runProcess } from "#host/daemon/runtimes/_spawn.js";
5
5
 
6
6
  const PROBES = [
7
7
  // Coding-agent CLIs (runtimes/)
@@ -13,6 +13,7 @@ import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  import { nowIso } from "../util/time.js";
15
15
  import { shortId as makeShortId } from "../util/ids.js";
16
+ import { CODE_MODES, DEFAULT_CODE_MODE } from "../constants/code-modes.js";
16
17
 
17
18
  function sessionsDir(storagePath) {
18
19
  return path.join(storagePath, "code-sessions");
@@ -91,7 +92,7 @@ export function createCodeSession(storagePath, fields = {}) {
91
92
  createdAt: ts,
92
93
  updatedAt: ts,
93
94
  model: fields.model || null,
94
- mode: fields.mode === "plan" ? "plan" : "build",
95
+ mode: fields.mode === CODE_MODES.PLAN ? CODE_MODES.PLAN : DEFAULT_CODE_MODE,
95
96
  agentSlug: fields.agentSlug || null,
96
97
  git: fields.git && typeof fields.git === "object" ? fields.git : null,
97
98
  messages: [],
@@ -109,7 +110,7 @@ export function updateCodeSession(storagePath, id, patch = {}) {
109
110
  if (!session) return null;
110
111
  if (patch.title != null) session.title = String(patch.title).trim() || session.title;
111
112
  if (patch.model !== undefined) session.model = patch.model || null;
112
- if (patch.mode === "plan" || patch.mode === "build") session.mode = patch.mode;
113
+ if (patch.mode === CODE_MODES.PLAN || patch.mode === CODE_MODES.BUILD) session.mode = patch.mode;
113
114
  if (patch.agentSlug !== undefined) session.agentSlug = patch.agentSlug || null;
114
115
  if (patch.git !== undefined) session.git = patch.git;
115
116
  session.updatedAt = nowIso();
@@ -147,3 +148,50 @@ export function appendTurn(storagePath, id, turn) {
147
148
  writeJson(sessionFile(storagePath, id), session);
148
149
  return session;
149
150
  }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Transcript → engine history
154
+ // ---------------------------------------------------------------------------
155
+
156
+ // One-line summary of an ask_questions tool call. Without it the next turn's
157
+ // history shows only "user answered X" with no record that the model had
158
+ // asked something — which makes the model ask again forever.
159
+ function summarizeAskQuestionsPart(part) {
160
+ const raw = part?.args?.questions;
161
+ if (!Array.isArray(raw) || raw.length === 0) return null;
162
+ const lines = raw
163
+ .map((q) => {
164
+ if (typeof q === "string") return `- ${q}`;
165
+ if (!q || typeof q !== "object" || typeof q.question !== "string") return null;
166
+ const opts = Array.isArray(q.options) ? q.options : [];
167
+ const optStr = opts
168
+ .map((o) => (typeof o === "string" ? o : (o && typeof o.label === "string" ? o.label : "")))
169
+ .filter(Boolean)
170
+ .join(", ");
171
+ return optStr ? `- ${q.question} (opciones: ${optStr})` : `- ${q.question}`;
172
+ })
173
+ .filter(Boolean);
174
+ if (lines.length === 0) return null;
175
+ return `[ask_questions]\n${lines.join("\n")}`;
176
+ }
177
+
178
+ /**
179
+ * Flatten a stored rich transcript into the [{role, content}] history the
180
+ * super-agent loop expects. Text parts are concatenated; tool parts are
181
+ * normally internal, except ask_questions which is surfaced as a one-line
182
+ * summary so the model doesn't lose track of what it already asked.
183
+ */
184
+ export function codeSessionHistory(session) {
185
+ return (session?.messages || []).map((m) => {
186
+ const chunks = [];
187
+ for (const p of m.parts || []) {
188
+ if (!p) continue;
189
+ if (p.kind === "text" && p.text) chunks.push(p.text);
190
+ else if (p.kind === "tool" && p.tool === "ask_questions") {
191
+ const summary = summarizeAskQuestionsPart(p);
192
+ if (summary) chunks.push(summary);
193
+ }
194
+ }
195
+ return { role: m.role, content: chunks.join("\n\n").trim() };
196
+ });
197
+ }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Path: <projectStoragePath>/routines/<routineId>/memory.md
4
4
  //
5
- // The routine handler (host/daemon/routines.js) creates the file on first read
5
+ // The routine handler (core/routines/runner.js) creates the file on first read
6
6
  // and injects a bounded slice into the super-agent prompt via
7
7
  // channelMeta.routineMemory. The routine can write back with future tooling;
8
8
  // today we only read.
@@ -0,0 +1,121 @@
1
+ // Cross-agent, cross-conversation session search + locator.
2
+ // Walks the on-disk session and conversation files for each project and
3
+ // returns matches with a small excerpt window. Used by the HTTP adapter and
4
+ // (planned) CLI session find.
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { apcAgentsDir } from "../apc/paths.js";
8
+
9
+ const EXCERPT_CHARS = 300;
10
+ const EXCERPT_LINES_BEFORE = 1;
11
+ const EXCERPT_LINES_AFTER = 3;
12
+
13
+ function scanFile(filePath, needle) {
14
+ try {
15
+ const text = fs.readFileSync(filePath, "utf8");
16
+ if (!text.toLowerCase().includes(needle)) return null;
17
+ const lines = text.split("\n");
18
+ const matchLine = lines.findIndex((l) => l.toLowerCase().includes(needle));
19
+ const excerpt = lines
20
+ .slice(Math.max(0, matchLine - EXCERPT_LINES_BEFORE), matchLine + EXCERPT_LINES_AFTER)
21
+ .join("\n");
22
+ return excerpt.slice(0, EXCERPT_CHARS);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Search for `needle` across one project's session + conversation files.
30
+ *
31
+ * @param project { id, path, storagePath } record from ProjectManager
32
+ * @param needle lowercase query string
33
+ * @param remaining max matches to add (search short-circuits when reached)
34
+ * @returns matches array (may be empty)
35
+ */
36
+ export function searchProjectSessions(project, needle, remaining) {
37
+ const matches = [];
38
+ if (!project || remaining <= 0) return matches;
39
+
40
+ // 1) Legacy session files in the repo (.apc/agents/<slug>/sessions/)
41
+ const sessionAgentsDir = apcAgentsDir(project.path);
42
+ if (fs.existsSync(sessionAgentsDir)) {
43
+ for (const slug of fs.readdirSync(sessionAgentsDir)) {
44
+ const sessionsDir = path.join(sessionAgentsDir, slug, "sessions");
45
+ if (!fs.existsSync(sessionsDir)) continue;
46
+ for (const f of fs.readdirSync(sessionsDir).filter((x) => x.endsWith(".md"))) {
47
+ const filePath = path.join(sessionsDir, f);
48
+ const excerpt = scanFile(filePath, needle);
49
+ if (excerpt != null) {
50
+ matches.push({
51
+ type: "session",
52
+ project: project.id,
53
+ agent: slug,
54
+ filename: f,
55
+ path: filePath,
56
+ excerpt,
57
+ });
58
+ if (matches.length >= remaining) return matches;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ // 2) Conversation files in daemon storage (~/.apx/projects/<id>/agents/<slug>/conversations/)
65
+ const convAgentsDir = path.join(project.storagePath, "agents");
66
+ if (fs.existsSync(convAgentsDir)) {
67
+ for (const slug of fs.readdirSync(convAgentsDir)) {
68
+ const convDir = path.join(convAgentsDir, slug, "conversations");
69
+ if (!fs.existsSync(convDir)) continue;
70
+ for (const f of fs.readdirSync(convDir).filter((x) => x.endsWith(".md"))) {
71
+ const filePath = path.join(convDir, f);
72
+ const excerpt = scanFile(filePath, needle);
73
+ if (excerpt != null) {
74
+ matches.push({
75
+ type: "conversation",
76
+ project: project.id,
77
+ agent: slug,
78
+ filename: f,
79
+ path: filePath,
80
+ excerpt,
81
+ });
82
+ if (matches.length >= remaining) return matches;
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ return matches;
89
+ }
90
+
91
+ /** Run searchProjectSessions across an array of projects, capping at `limit`. */
92
+ export function searchSessions(projectList, query, limit) {
93
+ const needle = String(query || "").toLowerCase();
94
+ const matches = [];
95
+ for (const p of projectList) {
96
+ if (!p) continue;
97
+ const remaining = limit - matches.length;
98
+ if (remaining <= 0) break;
99
+ matches.push(...searchProjectSessions(p, needle, remaining));
100
+ }
101
+ return matches.slice(0, limit);
102
+ }
103
+
104
+ /**
105
+ * Find the conversation file (under daemon storage) for a given session id,
106
+ * scanning a list of candidate projects. Returns { project, agentSlug, filename }
107
+ * or null. `id` is taken as bare or with .md suffix.
108
+ */
109
+ export function findSessionFile(projectList, id) {
110
+ const filename = id.endsWith(".md") ? id : `${id}.md`;
111
+ for (const p of projectList) {
112
+ if (!p) continue;
113
+ const agentsDir = path.join(p.storagePath, "agents");
114
+ if (!fs.existsSync(agentsDir)) continue;
115
+ for (const slug of fs.readdirSync(agentsDir)) {
116
+ const f = path.join(agentsDir, slug, "conversations", filename);
117
+ if (fs.existsSync(f)) return { project: p, agentSlug: slug, filename };
118
+ }
119
+ }
120
+ return null;
121
+ }
@@ -4,6 +4,11 @@
4
4
 
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
+ import { nowIso } from "../util/time.js";
8
+
9
+ export function agentSessionsDir(storageRoot, agentSlug) {
10
+ return path.join(storageRoot, "agents", agentSlug, "sessions");
11
+ }
7
12
 
8
13
  export function generateSessionId(storageRoot, agentSlug) {
9
14
  const today = new Date().toISOString().slice(0, 10);
@@ -34,3 +39,36 @@ export function readSessionFrontmatter(filePath) {
34
39
  }
35
40
  return { fm, body: text.slice(end + 4).replace(/^\n+/, "") };
36
41
  }
42
+
43
+ function slugifyTitle(title) {
44
+ return (
45
+ String(title || "")
46
+ .toLowerCase()
47
+ .replace(/[^a-z0-9]+/g, "-")
48
+ .replace(/^-|-$/g, "") || "session"
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Create a new dated session file under
54
+ * `<storageRoot>/agents/<agentSlug>/sessions/YYYY-MM-DD-<titleSlug>.md`,
55
+ * with collision suffix (`-2`, `-3`, …) and standard frontmatter.
56
+ * Returns { filename, path, started }.
57
+ */
58
+ export function createAgentSessionFile(storageRoot, agentSlug, { title, body = "" }) {
59
+ if (!title) throw new Error("createAgentSessionFile: title required");
60
+ const dir = agentSessionsDir(storageRoot, agentSlug);
61
+ fs.mkdirSync(dir, { recursive: true });
62
+ const titleSlug = slugifyTitle(title);
63
+ const today = new Date().toISOString().slice(0, 10);
64
+ let candidate = path.join(dir, `${today}-${titleSlug}.md`);
65
+ let n = 2;
66
+ while (fs.existsSync(candidate)) {
67
+ candidate = path.join(dir, `${today}-${titleSlug}-${n}.md`);
68
+ n++;
69
+ }
70
+ const started = nowIso();
71
+ const content = `---\ntitle: ${title}\nstarted: ${started}\n---\n\n# ${title}\n\n${body}\n`;
72
+ fs.writeFileSync(candidate, content);
73
+ return { filename: path.basename(candidate), path: candidate, started };
74
+ }