@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.
- package/package.json +1 -1
- package/skills/apx/SKILL.md +49 -61
- package/src/core/agent/a2a/reply.js +48 -0
- package/src/core/agent/build-agent-system.js +136 -59
- package/src/core/agent/channels/voice-context.js +98 -0
- package/src/core/agent/memory.js +2 -1
- package/src/core/agent/prompt-builder.js +178 -124
- package/src/core/agent/prompts/channels/code.md +12 -10
- package/src/core/agent/prompts/channels/desktop.md +5 -32
- package/src/core/agent/prompts/channels/telegram.md +4 -15
- package/src/core/agent/prompts/channels/web_code.md +11 -11
- package/src/core/agent/prompts/core/agent-base.md +24 -0
- package/src/core/agent/prompts/core/project-agent.md +11 -0
- package/src/core/agent/prompts/core/super-agent.md +21 -0
- package/src/core/agent/prompts/discipline/action.md +10 -0
- package/src/core/agent/prompts/discipline/single-segment.md +6 -0
- package/src/core/agent/prompts/discipline/two-segment.md +11 -0
- package/src/core/agent/prompts/modes/code-build.md +1 -0
- package/src/core/agent/prompts/modes/code-plan.md +1 -0
- package/src/core/agent/prompts/modes/index.js +28 -0
- package/src/core/agent/self-memory.js +43 -1
- package/src/core/agent/skills/index-store.js +307 -0
- package/src/core/agent/skills/index.js +15 -1
- package/src/core/agent/skills/inspector.js +317 -0
- package/src/core/agent/skills/loader.js +22 -18
- package/src/core/agent/stream/turn-accumulator.js +73 -0
- package/src/core/agent/suggestions.js +37 -0
- package/src/core/agent/super-agent.js +7 -1
- package/src/core/agent/tools/handlers/_git.js +50 -0
- package/src/core/agent/tools/handlers/add-project.js +5 -2
- package/src/core/agent/tools/handlers/call-runtime.js +3 -2
- package/src/core/agent/tools/handlers/git-diff.js +44 -0
- package/src/core/agent/tools/handlers/git-log.js +38 -0
- package/src/core/agent/tools/handlers/git-show.js +34 -0
- package/src/core/agent/tools/handlers/git-status.js +61 -0
- package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
- package/src/core/agent/tools/helpers.js +2 -2
- package/src/core/agent/tools/names.js +169 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +103 -69
- package/src/core/apc/context-copy.js +27 -0
- package/src/core/apc/notes.js +19 -0
- package/src/core/apc/parser.js +12 -5
- package/src/core/apc/paths.js +87 -0
- package/src/core/apc/scaffold.js +82 -76
- package/src/core/apc/skill-sync.js +10 -0
- package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
- package/src/core/config/index.js +24 -2
- package/src/core/config/redact.js +95 -0
- package/src/core/constants/channels.js +2 -0
- package/src/core/constants/code-modes.js +10 -0
- package/src/core/constants/index.js +1 -0
- package/src/core/deck/manifest.js +186 -0
- package/src/core/engines/catalog.js +83 -0
- package/src/core/{tools → http-tools}/browser.js +0 -1
- package/src/core/{tools → http-tools}/fetch.js +0 -1
- package/src/core/{tools → http-tools}/glob.js +0 -1
- package/src/core/{tools → http-tools}/grep.js +0 -1
- package/src/core/{tools → http-tools}/registry.js +0 -1
- package/src/core/{tools → http-tools}/search.js +0 -1
- package/src/core/i18n/en.js +9 -0
- package/src/core/i18n/es.js +12 -0
- package/src/core/i18n/index.js +54 -0
- package/src/core/i18n/pt.js +9 -0
- package/src/core/identity/telegram.js +2 -1
- package/src/core/mcp/runner.js +272 -14
- package/src/core/mcp/sources.js +3 -2
- package/src/core/routines/index.js +16 -0
- package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
- package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
- package/src/core/runtime-skills/apx/SKILL.md +83 -0
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
- package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
- package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
- package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
- package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
- package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
- package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
- package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
- package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
- package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
- package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
- package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
- package/src/core/stores/code-sessions.js +50 -2
- package/src/core/stores/routine-memory.js +1 -1
- package/src/core/stores/sessions-search.js +121 -0
- package/src/core/stores/sessions.js +38 -0
- package/src/core/vars/index.js +14 -0
- package/src/core/vars/interpolate.js +86 -0
- package/src/core/vars/sources.js +151 -0
- package/src/core/voice/audio-decode.js +38 -0
- package/src/core/voice/transcription.js +225 -0
- package/src/host/daemon/api/admin-config.js +5 -82
- package/src/host/daemon/api/agents.js +5 -5
- package/src/host/daemon/api/code.js +17 -169
- package/src/host/daemon/api/config.js +3 -4
- package/src/host/daemon/api/conversations.js +8 -29
- package/src/host/daemon/api/deck.js +37 -404
- package/src/host/daemon/api/engines.js +1 -80
- package/src/host/daemon/api/exec.js +1 -1
- package/src/host/daemon/api/mcps.js +32 -0
- package/src/host/daemon/api/routines.js +1 -1
- package/src/host/daemon/api/runtimes.js +4 -3
- package/src/host/daemon/api/sessions-search.js +24 -140
- package/src/host/daemon/api/sessions.js +12 -30
- package/src/host/daemon/api/shared.js +2 -1
- package/src/host/daemon/api/skills.js +140 -6
- package/src/host/daemon/api/super-agent.js +56 -1
- package/src/host/daemon/api/telegram.js +1 -11
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/transcribe.js +2 -2
- package/src/host/daemon/api/vars.js +137 -0
- package/src/host/daemon/api/voice.js +13 -290
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/db.js +6 -6
- package/src/host/daemon/deck-exec.js +148 -0
- package/src/host/daemon/index.js +20 -3
- package/src/host/daemon/plugins/telegram/index.js +9 -9
- package/src/host/daemon/routines-scheduler.js +64 -0
- package/src/host/daemon/smoke.js +3 -2
- package/src/host/daemon/whisper-server.js +225 -0
- package/src/interfaces/cli/branding.js +53 -0
- package/src/interfaces/cli/commands/agent.js +3 -2
- package/src/interfaces/cli/commands/command.js +2 -3
- package/src/interfaces/cli/commands/messages.js +6 -2
- package/src/interfaces/cli/commands/pair.js +5 -4
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/sessions.js +3 -2
- package/src/interfaces/cli/commands/skills.js +290 -55
- package/src/interfaces/cli/index.js +84 -2
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +182 -182
- package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
- package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
- package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
- package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
- package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
- package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
- package/src/interfaces/web/src/constants/index.ts +1 -1
- package/src/interfaces/web/src/hooks/useChat.ts +19 -0
- package/src/interfaces/web/src/i18n/en.ts +175 -7
- package/src/interfaces/web/src/i18n/es.ts +180 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
- package/src/interfaces/web/src/lib/api/skills.ts +70 -0
- package/src/interfaces/web/src/lib/api/vars.ts +38 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
- package/src/interfaces/web/src/types/daemon.ts +15 -0
- package/skills/apx-agency-agents/SKILL.md +0 -141
- package/skills/apx-agent/SKILL.md +0 -100
- package/skills/apx-mcp-builder/SKILL.md +0 -183
- package/skills/apx-routine/SKILL.md +0 -140
- package/skills/apx-runtime/SKILL.md +0 -117
- package/skills/apx-sessions/SKILL.md +0 -281
- package/skills/apx-skill-builder/SKILL.md +0 -153
- package/skills/apx-telegram/SKILL.md +0 -131
- package/skills/apx-voice/SKILL.md +0 -137
- package/src/core/agent/prompts/action-discipline.md +0 -24
- package/src/core/agent/prompts/super-agent-base.md +0 -42
- package/src/host/daemon/transcription.js +0 -538
- package/src/host/daemon/whisper-transcribe.py +0 -73
- package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
- /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
- /package/src/core/{tools → http-tools}/index.js +0 -0
- /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
- /package/src/{host/daemon → core/stores}/conversations.js +0 -0
- /package/src/{host/daemon → core/util}/thinking.js +0 -0
package/package.json
CHANGED
package/skills/apx/SKILL.md
CHANGED
|
@@ -1,95 +1,83 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apx
|
|
3
|
-
description:
|
|
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,
|
|
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
|
-
|
|
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
|
|
18
|
+
## When you (as an engine) interact with APX
|
|
16
19
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
28
|
+
## Verify before recommending
|
|
27
29
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
---
|
|
61
38
|
|
|
62
|
-
|
|
39
|
+
## Core commands you'll actually use
|
|
63
40
|
|
|
64
41
|
```bash
|
|
65
|
-
|
|
66
|
-
apx
|
|
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
|
-
|
|
64
|
+
---
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
## APC_RESULT contract
|
|
72
67
|
|
|
73
|
-
|
|
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
|
|
94
|
-
- Don't
|
|
95
|
-
- Don't
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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(
|
|
23
|
-
return meta.name ||
|
|
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.
|
|
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
|
|
43
|
-
|
|
44
|
-
`Project: ${projectName(project)} (${project.path}).`,
|
|
45
|
-
];
|
|
72
|
+
const channelLow = String(channel || "").toLowerCase();
|
|
73
|
+
const voice = !!channelMeta?.voice || channelLow === "voice";
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
//
|
|
80
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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(
|
|
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
|
+
}
|
package/src/core/agent/memory.js
CHANGED
|
@@ -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
|
|
31
|
+
return apcAgentMemoryFile(projectRoot, slug);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export function ensureAgentRuntimeDir(projectOrRoot, slug, { createMemory = false } = {}) {
|