@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.
- 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 +4 -3
- 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 +2 -1
- 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/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/tools/handlers/add-project.js +5 -2
- package/src/core/agent/tools/handlers/call-runtime.js +3 -2
- 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 +138 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +68 -65
- 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 +3 -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 +95 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +116 -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/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 +3 -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/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 +36 -55
- package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +1 -0
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js +613 -0
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +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 +16 -3
- 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 +5 -4
- 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/i18n/en.ts +174 -7
- package/src/interfaces/web/src/i18n/es.ts +179 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -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/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 +5 -0
- 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/{skills → src/core/runtime-skills}/apx-agency-agents/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-agent/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-mcp-builder/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-routine/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-runtime/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-sessions/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-skill-builder/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-telegram/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-voice/SKILL.md +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', 'pedile a codex/claude/opencode/gemini', 'que codex haga …', 'delegar a otro engine', 'ejecutar en codex/claude', 'run this in <runtime>'.
|
|
4
7
|
homepage: https://github.com/agentprojectcontext/apx
|
|
5
8
|
---
|
|
6
9
|
|
|
7
|
-
# APX — Agent Project Context Runtime
|
|
10
|
+
# APX — Agent Project Context Runtime (engine view)
|
|
8
11
|
|
|
9
|
-
APX is a daemon (`127.0.0.1:7430`, auto-starts on first call) that turns external coding CLIs (Claude Code, Codex, OpenCode,
|
|
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
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { readAgentMemory } from "./memory.js";
|
|
5
|
+
import { apcProjectFile, apcSkillFile } from "../apc/paths.js";
|
|
5
6
|
|
|
6
7
|
// Anti-ghost-response rules injected into every agent system prompt. The text
|
|
7
8
|
// lives next to the agent prompts (src/core/agent/prompts/action-discipline.md)
|
|
@@ -19,7 +20,7 @@ function listField(value) {
|
|
|
19
20
|
function projectName(project) {
|
|
20
21
|
if (project?.name) return project.name;
|
|
21
22
|
try {
|
|
22
|
-
const meta = JSON.parse(fs.readFileSync(
|
|
23
|
+
const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
|
|
23
24
|
return meta.name || path.basename(project.path);
|
|
24
25
|
} catch {
|
|
25
26
|
return path.basename(project?.path || "");
|
|
@@ -64,11 +65,11 @@ export function buildAgentSystem(project, agent, {
|
|
|
64
65
|
const memory = readAgentMemory(project, agent.slug);
|
|
65
66
|
if (memory) parts.push("## Memory\n" + memory);
|
|
66
67
|
|
|
67
|
-
const apxSkill =
|
|
68
|
+
const apxSkill = apcSkillFile(project.path, "apx");
|
|
68
69
|
if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
|
|
69
70
|
|
|
70
71
|
for (const skill of agentSkills(agent)) {
|
|
71
|
-
const skillPath =
|
|
72
|
+
const skillPath = apcSkillFile(project.path, skill);
|
|
72
73
|
if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Channel-aware pre-processor for surfaces that drive the super-agent loop
|
|
2
|
+
// from a voice/deck/desktop entrypoint.
|
|
3
|
+
//
|
|
4
|
+
// Each surface has different ergonomics: how long the reply can be, whether
|
|
5
|
+
// the UI can render structured suggestion chips, what the default project
|
|
6
|
+
// resolution should be. buildVoiceChannelContext() is the single place where
|
|
7
|
+
// those decisions live. Callers (api/voice.js today; any future overlay or
|
|
8
|
+
// device adapter tomorrow) pass the channel string + dynamic context and
|
|
9
|
+
// receive the context note + system suffix to feed into the super-agent.
|
|
10
|
+
//
|
|
11
|
+
// Shape:
|
|
12
|
+
// contextNote — prepended to the prompt (dynamic, per-request)
|
|
13
|
+
// systemSuffix — concatenated onto the system prompt (per-surface rules)
|
|
14
|
+
// wantsSuggestions — whether the surface can render the trailing
|
|
15
|
+
// `suggestions` JSON block (deck/desktop UI can; raw
|
|
16
|
+
// Telegram cannot)
|
|
17
|
+
// channel — resolved surface ("deck"/"desktop"/"telegram"/…)
|
|
18
|
+
// channelMeta — surface metadata (e.g. `{ voice: true }` flags spoken mode)
|
|
19
|
+
import { CHANNELS } from "../../constants/channels.js";
|
|
20
|
+
|
|
21
|
+
// Balanced suggestions instruction. An earlier, more aggressive version
|
|
22
|
+
// ("EJECUTA, no narres — LLAMÁ A LA TOOL") made Gemini call tools for
|
|
23
|
+
// EVERYTHING, even "hola" → send_telegram("hola"). The rule below gates
|
|
24
|
+
// tool use on a *clear* action request and explicitly tells the model to
|
|
25
|
+
// just talk for chit-chat.
|
|
26
|
+
export const SUGGESTIONS_INSTRUCTION = `
|
|
27
|
+
|
|
28
|
+
# Cuándo usar tools
|
|
29
|
+
SOLO llamá una tool cuando el usuario pide CLARAMENTE una acción
|
|
30
|
+
concreta: "creá una tarea …", "mandá un telegram …", "listá …",
|
|
31
|
+
"abrí …", "marcá como hecha …". En esos casos ejecutá la tool (no
|
|
32
|
+
digas "lo voy a hacer" — hacelo) y después confirmá en una frase corta
|
|
33
|
+
en castellano lo que YA hiciste.
|
|
34
|
+
|
|
35
|
+
Si el mensaje es un saludo, una pregunta, o charla ("hola", "cómo
|
|
36
|
+
andás", "qué podés hacer") NO llames ninguna tool: respondé en texto,
|
|
37
|
+
breve, en castellano.
|
|
38
|
+
|
|
39
|
+
Nunca llames la misma tool dos veces en el mismo turno.
|
|
40
|
+
|
|
41
|
+
# Sugerencias (opcional)
|
|
42
|
+
Al final, en su propia línea, podés agregar un bloque fenced
|
|
43
|
+
\`suggestions\` con 2-3 próximos pasos. El usuario NO lo ve (la deck lo
|
|
44
|
+
quita):
|
|
45
|
+
\`\`\`suggestions
|
|
46
|
+
[{"label":"Ver tareas","command":"deck.view:tasks"}]
|
|
47
|
+
\`\`\`
|
|
48
|
+
Si no hay próximos pasos útiles, omití el bloque.`;
|
|
49
|
+
|
|
50
|
+
function buildLanguageDirective(language) {
|
|
51
|
+
return language === "es"
|
|
52
|
+
? "IMPORTANT: Reply ALWAYS in Spanish (rioplatense/Argentina). The user speaks Spanish."
|
|
53
|
+
: `IMPORTANT: Reply in language "${language}".`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildProjectHint(projectId) {
|
|
57
|
+
// Project resolution hint:
|
|
58
|
+
// per-project mic (projectId set): use it imperatively, don't ask.
|
|
59
|
+
// global deck mic (no projectId): default to project id=0 ("default")
|
|
60
|
+
// for actions unless the user names a project out loud.
|
|
61
|
+
return projectId
|
|
62
|
+
? `\nThe active project is id=${projectId}. For ANY task/note/list ` +
|
|
63
|
+
`action, pass project_id=${projectId} automatically. Do NOT ask the ` +
|
|
64
|
+
`user which project — only switch if they explicitly name another.`
|
|
65
|
+
: `\nThis is the GLOBAL mic (no project in focus). For task/note/list ` +
|
|
66
|
+
`actions, default to project_id=0 ("default") UNLESS the user names ` +
|
|
67
|
+
`a project out loud (e.g. "en evolution-registry…", "en el proyecto ` +
|
|
68
|
+
`apx…") — then resolve that project by name. Never ask "¿en qué ` +
|
|
69
|
+
`proyecto?"; pick the default and act.`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function buildVoiceChannelContext(channel, { projectId, language = "es" } = {}) {
|
|
73
|
+
const base = {
|
|
74
|
+
contextNote: "",
|
|
75
|
+
systemSuffix: "",
|
|
76
|
+
wantsSuggestions: false,
|
|
77
|
+
channel: "",
|
|
78
|
+
channelMeta: {},
|
|
79
|
+
};
|
|
80
|
+
const dynamicNote = `${buildLanguageDirective(language)}${buildProjectHint(projectId)}`;
|
|
81
|
+
|
|
82
|
+
// Channels are surfaces; "voice" is NOT a surface — it's the spoken MODE of
|
|
83
|
+
// the deck. All channel FORMATTING lives in channels/*.md + modes/voice.md
|
|
84
|
+
// (injected by buildSuperAgentSystem); contextNote here carries ONLY
|
|
85
|
+
// per-request dynamic bits (language + project).
|
|
86
|
+
switch (channel) {
|
|
87
|
+
case "voice":
|
|
88
|
+
return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: { voice: true } };
|
|
89
|
+
case "deck":
|
|
90
|
+
return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: {} };
|
|
91
|
+
case "desktop":
|
|
92
|
+
return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DESKTOP, channelMeta: { voice: true } };
|
|
93
|
+
case "telegram":
|
|
94
|
+
return { ...base, contextNote: dynamicNote, channel: CHANNELS.TELEGRAM, channelMeta: {} };
|
|
95
|
+
default:
|
|
96
|
+
return { ...base, contextNote: dynamicNote, channel: channel || "api", channelMeta: {} };
|
|
97
|
+
}
|
|
98
|
+
}
|
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 } = {}) {
|
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { readIdentity } from "../identity/index.js";
|
|
11
|
+
import { agentsMdFile } from "../apc/paths.js";
|
|
11
12
|
import { readSelfMemoryForPrompt } from "./self-memory.js";
|
|
12
13
|
import { buildSkillsHintBlock } from "./skills/catalog.js";
|
|
13
14
|
|
|
@@ -97,7 +98,7 @@ export const PROJECT_AGENTS_MAX_CHARS = 6000;
|
|
|
97
98
|
export function buildProjectAgentsBlock(projectPath) {
|
|
98
99
|
if (!projectPath) return "";
|
|
99
100
|
try {
|
|
100
|
-
const file =
|
|
101
|
+
const file = agentsMdFile(projectPath);
|
|
101
102
|
if (!fs.existsSync(file)) return "";
|
|
102
103
|
let text = fs.readFileSync(file, "utf8").trim();
|
|
103
104
|
if (!text) return "";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MODE: build. Make the changes directly using your file and shell tools (read_file, write_file, edit_file, run_shell, …). Do not ask for confirmation and do not stop after one step — keep calling tools until the entire task is done, then briefly summarize what you changed and why. Prefer surgical edits over rewrites. When the user asks for a reusable script, snippet, or 'artifact' (something they want to keep and run later), put it under `artifacts/<name>` inside the project — it then shows up in the Artifacts tab. Don't drop reusable scripts at the project root. If a parameter you need is missing (API key, app id, target URL, …), call `ask_questions` ONCE with all your questions and stop — control returns to the user. Do not call ask_questions again in the same turn; you'll just get the same blank state back. Each question can be a string (free-text answer) OR an object {question, options:[{label, description}], multiSelect} for choices. Prefer 2–4 mutually-exclusive options when a question has a natural shortlist (yes/no, which-of-these, …); leave options empty for open-ended answers (API keys, names, free-form ideas). If the previous assistant turn already asked these same questions and the current user message is the compiled answers, DO NOT call ask_questions again — process the answers and proceed with the task.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MODE: plan. Investigate the codebase (read/list/search/grep) and propose an approach with the EXACT changes you would make (files + diffs/snippets). Do NOT write or edit files and do NOT run mutating shell commands — your editing tools are disabled in this mode. End with a concise, ordered plan.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Mode-specific system-prompt fragments for the Code module (plan / build).
|
|
2
|
+
// Each mode lives in its own .md sibling and is loaded once at boot.
|
|
3
|
+
//
|
|
4
|
+
// Why .md files instead of inline strings: the prompts are content, not code.
|
|
5
|
+
// Editing prompt copy shouldn't require touching code, and reviewing changes
|
|
6
|
+
// to behavior should be a doc-shaped diff, not a JS-shaped one.
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { CODE_MODES } from "#core/constants/code-modes.js";
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
function load(file) {
|
|
15
|
+
return fs.readFileSync(path.join(__dirname, file), "utf8").trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CODE_PLAN_GUIDANCE = load("code-plan.md");
|
|
19
|
+
const CODE_BUILD_GUIDANCE = load("code-build.md");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Return the system-prompt fragment that explains how this mode behaves to
|
|
23
|
+
* the agent. Falls back to BUILD when the mode is missing or unknown — build
|
|
24
|
+
* is the safer default for the Code module (no mid-edit "do nothing" stall).
|
|
25
|
+
*/
|
|
26
|
+
export function codeModeGuidance(mode) {
|
|
27
|
+
return mode === CODE_MODES.PLAN ? CODE_PLAN_GUIDANCE : CODE_BUILD_GUIDANCE;
|
|
28
|
+
}
|
|
@@ -10,15 +10,19 @@
|
|
|
10
10
|
// 1. <projectPath>/.apc/skills/<slug>.md ← project-scoped (flat)
|
|
11
11
|
// 1b.<projectPath>/.apc/skills/<slug>/SKILL.md ← project-scoped (dir)
|
|
12
12
|
// 2. ~/.apx/skills/<slug>/SKILL.md ← user-installed global
|
|
13
|
-
// 3. <packageRoot>/skills/<slug>/SKILL.md
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
13
|
+
// 3. <packageRoot>/src/core/runtime-skills/<slug>/SKILL.md
|
|
14
|
+
// ← runtime-internal set
|
|
15
|
+
// (rich apx-*, apc-context,
|
|
16
|
+
// claude-code, codex-cli,
|
|
17
|
+
// opencode-cli, openrouter)
|
|
18
18
|
//
|
|
19
|
-
// A slug found in a higher-priority location SHADOWS lower ones.
|
|
20
|
-
// override
|
|
21
|
-
//
|
|
19
|
+
// A slug found in a higher-priority location SHADOWS lower ones. The user can
|
|
20
|
+
// override any runtime skill by dropping `~/.apx/skills/<slug>/SKILL.md`; the
|
|
21
|
+
// in-repo copy stays as a safety net.
|
|
22
|
+
//
|
|
23
|
+
// NOTE: <packageRoot>/skills/<slug>/SKILL.md is intentionally NOT in this chain.
|
|
24
|
+
// That dir holds the engine-side slim set replicated to external CLIs/IDEs
|
|
25
|
+
// (~/.claude/skills/, ~/.codex/skills/, ...) — it's not for the super-agent.
|
|
22
26
|
//
|
|
23
27
|
// Note: the bundled `apc-context` skill is REFRESHED from the canonical apc
|
|
24
28
|
// repo on every npm install / update (see src/interfaces/cli/postinstall.js). APC is a
|
|
@@ -28,6 +32,7 @@ import fs from "node:fs";
|
|
|
28
32
|
import path from "node:path";
|
|
29
33
|
import os from "node:os";
|
|
30
34
|
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { apcSkillsDir } from "#core/apc/paths.js";
|
|
31
36
|
|
|
32
37
|
const __filename = fileURLToPath(import.meta.url);
|
|
33
38
|
const __dirname = path.dirname(__filename);
|
|
@@ -35,8 +40,10 @@ const __dirname = path.dirname(__filename);
|
|
|
35
40
|
// → repo root. Used to find the bundled skills/ folder at the repo root.
|
|
36
41
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..", "..");
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
// Runtime-internal skills — full apx-* catalog + CLI docs. Lives outside
|
|
44
|
+
// <packageRoot>/skills/ so external tools that copy "skills/" from the repo
|
|
45
|
+
// don't accidentally pull the rich set or the runtime CLI docs.
|
|
46
|
+
const BUILTIN_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
|
|
40
47
|
const GLOBAL_DIR = path.join(os.homedir(), ".apx", "skills");
|
|
41
48
|
|
|
42
49
|
// ---------------------------------------------------------------------------
|
|
@@ -119,7 +126,7 @@ export function listSkills({ projectPath } = {}) {
|
|
|
119
126
|
|
|
120
127
|
// priority 1: project-scoped
|
|
121
128
|
if (projectPath) {
|
|
122
|
-
const apcSkills =
|
|
129
|
+
const apcSkills = apcSkillsDir(projectPath);
|
|
123
130
|
found.push(...scanDirStyle(apcSkills, "project"));
|
|
124
131
|
found.push(...scanFlatStyle(apcSkills, "project"));
|
|
125
132
|
}
|
|
@@ -127,11 +134,9 @@ export function listSkills({ projectPath } = {}) {
|
|
|
127
134
|
// priority 2: user-installed global
|
|
128
135
|
found.push(...scanDirStyle(GLOBAL_DIR, "global"));
|
|
129
136
|
|
|
130
|
-
// priority 3:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// priority 4: runtime docs (claude-code, codex-cli, opencode-cli, openrouter)
|
|
134
|
-
found.push(...scanFlatStyle(RUNTIME_SKILLS_DIR, "builtin"));
|
|
137
|
+
// priority 3: runtime-internal builtin set
|
|
138
|
+
// (rich apx-*, apc-context, plus claude-code, codex-cli, opencode-cli, openrouter)
|
|
139
|
+
found.push(...scanDirStyle(BUILTIN_SKILLS_DIR, "builtin"));
|
|
135
140
|
|
|
136
141
|
// dedupe by slug (first-wins = higher priority shadows lower)
|
|
137
142
|
const seen = new Set();
|
|
@@ -189,7 +194,6 @@ export function loadSkill(slug, { projectPath } = {}) {
|
|
|
189
194
|
|
|
190
195
|
// Useful for diagnostics
|
|
191
196
|
export const SKILL_LOCATIONS = {
|
|
192
|
-
|
|
193
|
-
bundled: BUNDLED_SKILLS_DIR,
|
|
197
|
+
builtin: BUILTIN_SKILLS_DIR,
|
|
194
198
|
global: GLOBAL_DIR,
|
|
195
199
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Accumulate super-agent stream events into the rich ChatPart shape so a
|
|
2
|
+
// persisted assistant turn matches exactly what the UI rendered live.
|
|
3
|
+
// Mirrors the front-end reducer in hooks/useChat.ts (applyStreamEvent) — keep
|
|
4
|
+
// the two in sync if you add a new event type.
|
|
5
|
+
//
|
|
6
|
+
// Pure: no I/O, no globals. Caller drives it event-by-event and finally calls
|
|
7
|
+
// build() to snapshot the resulting parts/notes/model/usage.
|
|
8
|
+
export function makeTurnAccumulator() {
|
|
9
|
+
const parts = [];
|
|
10
|
+
const notes = [];
|
|
11
|
+
let model = null;
|
|
12
|
+
let usage = null;
|
|
13
|
+
const findTool = (id) => parts.find((p) => p.kind === "tool" && p.id === id);
|
|
14
|
+
return {
|
|
15
|
+
apply(ev) {
|
|
16
|
+
switch (ev?.type) {
|
|
17
|
+
case "model_start":
|
|
18
|
+
if (ev.model) model = ev.model;
|
|
19
|
+
break;
|
|
20
|
+
case "model_routed":
|
|
21
|
+
if (ev.model) model = ev.model;
|
|
22
|
+
if (ev.from_fallback) notes.push(`routing fell back → ${ev.model}`);
|
|
23
|
+
break;
|
|
24
|
+
case "engine_failed":
|
|
25
|
+
notes.push(`engine ${ev.model || "?"} failed → ${ev.retry_with || "retry"}`);
|
|
26
|
+
break;
|
|
27
|
+
case "model_retry":
|
|
28
|
+
notes.push(`retry (${ev.reason || "?"})`);
|
|
29
|
+
break;
|
|
30
|
+
case "tools_suppressed":
|
|
31
|
+
notes.push(`tools suppressed: ${(ev.tools || []).join(", ")}`);
|
|
32
|
+
break;
|
|
33
|
+
case "assistant_text":
|
|
34
|
+
if (ev.text) parts.push({ kind: "text", text: ev.text });
|
|
35
|
+
break;
|
|
36
|
+
case "tool_start":
|
|
37
|
+
if (ev.trace)
|
|
38
|
+
parts.push({
|
|
39
|
+
kind: "tool",
|
|
40
|
+
id: ev.trace.id,
|
|
41
|
+
tool: ev.trace.tool,
|
|
42
|
+
args: ev.trace.args,
|
|
43
|
+
status: "running",
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
case "tool_deduped": {
|
|
47
|
+
const t = ev.trace && findTool(ev.trace.id);
|
|
48
|
+
if (t) t.status = "deduped";
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "tool_result": {
|
|
52
|
+
const t = ev.trace && findTool(ev.trace.id);
|
|
53
|
+
if (t) {
|
|
54
|
+
t.result = ev.trace.result;
|
|
55
|
+
const errored =
|
|
56
|
+
ev.trace.result && typeof ev.trace.result === "object" && ev.trace.result.error;
|
|
57
|
+
t.status = errored ? "error" : t.status === "deduped" ? "deduped" : "done";
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "final":
|
|
62
|
+
usage = ev.result?.usage ?? usage;
|
|
63
|
+
if (!model) model = ev.result?.name || null;
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
build() {
|
|
70
|
+
return { parts, notes, model, usage };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Parse the trailing ```suggestions JSON``` block out of an agent reply.
|
|
2
|
+
// Surfaces like the deck/desktop render the JSON as chips; the visible reply
|
|
3
|
+
// should be stripped of the fenced block so the user (and TTS) never sees it.
|
|
4
|
+
//
|
|
5
|
+
// Pure: just regex + JSON.parse. Malformed JSON drops suggestions silently
|
|
6
|
+
// rather than failing the turn — better UX to show the reply without chips
|
|
7
|
+
// than an error.
|
|
8
|
+
const SUGGESTIONS_BLOCK_RE = /\n*```\s*suggestions\s*\n([\s\S]*?)\n?```\s*$/i;
|
|
9
|
+
|
|
10
|
+
const MAX_SUGGESTIONS = 4;
|
|
11
|
+
const MAX_LABEL_LEN = 48;
|
|
12
|
+
const MAX_COMMAND_LEN = 96;
|
|
13
|
+
|
|
14
|
+
export function extractSuggestions(text) {
|
|
15
|
+
if (typeof text !== "string" || !text) {
|
|
16
|
+
return { cleanText: text || "", suggestions: [] };
|
|
17
|
+
}
|
|
18
|
+
const m = SUGGESTIONS_BLOCK_RE.exec(text);
|
|
19
|
+
if (!m) return { cleanText: text, suggestions: [] };
|
|
20
|
+
const cleanText = text.slice(0, m.index).trim();
|
|
21
|
+
let suggestions = [];
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(m[1]);
|
|
24
|
+
if (Array.isArray(parsed)) {
|
|
25
|
+
suggestions = parsed
|
|
26
|
+
.filter((s) => s && typeof s === "object" && typeof s.label === "string")
|
|
27
|
+
.slice(0, MAX_SUGGESTIONS)
|
|
28
|
+
.map((s) => ({
|
|
29
|
+
label: String(s.label).slice(0, MAX_LABEL_LEN),
|
|
30
|
+
...(typeof s.command === "string" ? { command: s.command.slice(0, MAX_COMMAND_LEN) } : {}),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// Malformed JSON — drop silently.
|
|
35
|
+
}
|
|
36
|
+
return { cleanText, suggestions };
|
|
37
|
+
}
|
|
@@ -2,12 +2,15 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readConfig, addProject as addProjectInConfig } from "#core/config/index.js";
|
|
4
4
|
import { initApf } from "#core/apc/scaffold.js";
|
|
5
|
+
import { agentsMdFile, apcProjectFile } from "#core/apc/paths.js";
|
|
5
6
|
import { projectMeta } from "../helpers.js";
|
|
6
7
|
|
|
8
|
+
// Stricter than core/apc/paths.js::isApcProject — `add_project` also requires
|
|
9
|
+
// AGENTS.md at the root, matching the gate that config.addProject() enforces.
|
|
7
10
|
function isApcProject(absPath) {
|
|
8
11
|
return (
|
|
9
|
-
fs.existsSync(
|
|
10
|
-
fs.existsSync(
|
|
12
|
+
fs.existsSync(agentsMdFile(absPath)) &&
|
|
13
|
+
fs.existsSync(apcProjectFile(absPath))
|
|
11
14
|
);
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -2,13 +2,14 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { loggerFor } from "#core/logging.js";
|
|
4
4
|
import { readAgents } from "#core/apc/parser.js";
|
|
5
|
+
import { apcProjectFile } from "#core/apc/paths.js";
|
|
5
6
|
import {
|
|
6
7
|
closeRuntimeSession,
|
|
7
8
|
createRuntimeSession,
|
|
8
9
|
extractRuntimeResult as extractApfResult,
|
|
9
10
|
} from "#core/stores/runtime-sessions.js";
|
|
10
11
|
import { buildRuntimeBridgeHint as buildApfHint } from "#core/agent/runtime-bridge.js";
|
|
11
|
-
import { detectAll } from "#
|
|
12
|
+
import { detectAll } from "#core/runtimes/detect.js";
|
|
12
13
|
import {
|
|
13
14
|
findEngineSessionById,
|
|
14
15
|
readEngineSessionContext,
|
|
@@ -88,7 +89,7 @@ function resolveProjectForAgent(projects, project, slug) {
|
|
|
88
89
|
|
|
89
90
|
function projectName(project) {
|
|
90
91
|
try {
|
|
91
|
-
const meta = JSON.parse(fs.readFileSync(
|
|
92
|
+
const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
|
|
92
93
|
if (meta.name) return meta.name;
|
|
93
94
|
} catch {}
|
|
94
95
|
return path.basename(project.path);
|