@agentprojectcontext/apx 1.34.0 → 1.36.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 (75) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +1 -1
  3. package/src/core/agent/build-agent-system.js +134 -58
  4. package/src/core/agent/channels/voice-context.js +4 -4
  5. package/src/core/agent/prompt-builder.js +176 -123
  6. package/src/core/agent/prompts/channels/code.md +12 -10
  7. package/src/core/agent/prompts/channels/desktop.md +5 -32
  8. package/src/core/agent/prompts/channels/telegram.md +4 -15
  9. package/src/core/agent/prompts/channels/web_code.md +11 -11
  10. package/src/core/agent/prompts/core/agent-base.md +24 -0
  11. package/src/core/agent/prompts/core/project-agent.md +11 -0
  12. package/src/core/agent/prompts/core/super-agent.md +21 -0
  13. package/src/core/agent/prompts/discipline/action.md +10 -0
  14. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  15. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  16. package/src/core/agent/self-memory.js +43 -1
  17. package/src/core/agent/skills/index-store.js +307 -0
  18. package/src/core/agent/skills/index.js +15 -1
  19. package/src/core/agent/skills/inspector.js +317 -0
  20. package/src/core/agent/super-agent.js +7 -1
  21. package/src/core/agent/tools/handlers/_git.js +50 -0
  22. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  23. package/src/core/agent/tools/handlers/git-log.js +38 -0
  24. package/src/core/agent/tools/handlers/git-show.js +34 -0
  25. package/src/core/agent/tools/handlers/git-status.js +61 -0
  26. package/src/core/agent/tools/names.js +31 -0
  27. package/src/core/agent/tools/registry.js +36 -5
  28. package/src/core/config/index.js +21 -0
  29. package/src/core/runtime-skills/apx/SKILL.md +27 -39
  30. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +40 -56
  31. package/src/core/runtime-skills/apx-agent/SKILL.md +27 -30
  32. package/src/core/runtime-skills/apx-mcp/SKILL.md +31 -36
  33. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +37 -51
  34. package/src/core/runtime-skills/apx-project/SKILL.md +20 -29
  35. package/src/core/runtime-skills/apx-routine/SKILL.md +34 -47
  36. package/src/core/runtime-skills/apx-runtime/SKILL.md +32 -50
  37. package/src/core/runtime-skills/apx-sessions/SKILL.md +96 -145
  38. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +53 -77
  39. package/src/core/runtime-skills/apx-task/SKILL.md +18 -21
  40. package/src/core/runtime-skills/apx-telegram/SKILL.md +43 -54
  41. package/src/core/runtime-skills/apx-voice/SKILL.md +36 -56
  42. package/src/core/stores/conversations.js +27 -2
  43. package/src/host/daemon/api/exec.js +2 -0
  44. package/src/host/daemon/api/skills.js +140 -6
  45. package/src/host/daemon/api/super-agent.js +56 -1
  46. package/src/host/daemon/index.js +17 -0
  47. package/src/interfaces/cli/branding.js +53 -0
  48. package/src/interfaces/cli/commands/skills.js +254 -0
  49. package/src/interfaces/cli/index.js +84 -2
  50. package/src/interfaces/web/dist/assets/index-Cm0KyPoZ.css +1 -0
  51. package/src/interfaces/web/dist/assets/index-DJKA763h.js +628 -0
  52. package/src/interfaces/web/dist/assets/index-DJKA763h.js.map +1 -0
  53. package/src/interfaces/web/dist/index.html +2 -2
  54. package/src/interfaces/web/src/App.tsx +0 -1
  55. package/src/interfaces/web/src/components/chat/ChatList.tsx +412 -0
  56. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +21 -1
  57. package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
  58. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +69 -1
  59. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  60. package/src/interfaces/web/src/hooks/useChat.ts +54 -2
  61. package/src/interfaces/web/src/i18n/en.ts +12 -1
  62. package/src/interfaces/web/src/i18n/es.ts +12 -1
  63. package/src/interfaces/web/src/lib/api/agents.ts +1 -1
  64. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  65. package/src/interfaces/web/src/screens/ProjectScreen.tsx +3 -5
  66. package/src/interfaces/web/src/screens/SettingsScreen.tsx +12 -6
  67. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  68. package/src/interfaces/web/src/screens/project/ChatTab.tsx +120 -87
  69. package/src/interfaces/web/src/types/daemon.ts +10 -0
  70. package/src/core/agent/prompts/action-discipline.md +0 -24
  71. package/src/core/agent/prompts/super-agent-base.md +0 -42
  72. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +0 -1
  73. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +0 -613
  74. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +0 -1
  75. package/src/interfaces/web/src/screens/project/ThreadsTab.tsx +0 -100
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.34.0",
3
+ "version": "1.36.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -3,7 +3,7 @@ name: apx
3
3
  description: >-
4
4
  APX CLI — local daemon that orchestrates agents, sessions, MCPs, and channels across CLIs.
5
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>'.
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>'.
7
7
  homepage: https://github.com/agentprojectcontext/apx
8
8
  ---
9
9
 
@@ -1,16 +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
11
  import { apcProjectFile, apcSkillFile } from "../apc/paths.js";
6
-
7
- // Anti-ghost-response rules injected into every agent system prompt. The text
8
- // lives next to the agent prompts (src/core/agent/prompts/action-discipline.md)
9
- // so it can be edited without touching code. Cached at module load.
10
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
- const ACTION_DISCIPLINE_RULES = fs
12
- .readFileSync(path.join(__dirname, "prompts", "action-discipline.md"), "utf8")
13
- .trimEnd();
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;
14
24
 
15
25
  function listField(value) {
16
26
  if (Array.isArray(value)) return value.map(String).map((s) => s.trim()).filter(Boolean);
@@ -21,9 +31,9 @@ function projectName(project) {
21
31
  if (project?.name) return project.name;
22
32
  try {
23
33
  const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
24
- return meta.name || path.basename(project.path);
34
+ return meta.name || project.path?.split("/").pop() || "";
25
35
  } catch {
26
- return path.basename(project?.path || "");
36
+ return project?.path?.split("/").pop() || "";
27
37
  }
28
38
  }
29
39
 
@@ -31,80 +41,146 @@ export function agentSkills(agent) {
31
41
  return listField(agent?.fields?.Skills);
32
42
  }
33
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
+ */
34
60
  export function buildAgentSystem(project, agent, {
35
61
  invocation = "engine",
36
62
  runtime = null,
37
63
  channel = null,
64
+ channelMeta = {},
65
+ sender = null,
38
66
  caller = null,
39
67
  routine = null,
68
+ globalConfig = {},
40
69
  extraParts = [],
41
70
  } = {}) {
42
71
  const fields = agent.fields || {};
43
- const parts = [
44
- `You are APC agent "${agent.slug}".`,
45
- `Project: ${projectName(project)} (${project.path}).`,
46
- ];
72
+ const channelLow = String(channel || "").toLowerCase();
73
+ const voice = !!channelMeta?.voice || channelLow === "voice";
47
74
 
48
- if (fields.Description) parts.push(String(fields.Description));
49
- if (fields.Role) parts.push(`Role: ${fields.Role}`);
50
- 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");
51
77
 
52
- const declaredTools = listField(fields.Tools);
53
- if (declaredTools.length) {
54
- parts.push(
55
- [
56
- "## Declared Tool Hints",
57
- declaredTools.join(", "),
58
- "These are agent-level tool expectations, not a guarantee. Actual callable tools depend on invocation surface.",
59
- ].join("\n")
60
- );
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)";
61
94
  }
95
+ const customInstructions = customBody ? `# Custom instructions\n${customBody}` : "";
62
96
 
63
- parts.push(buildInvocationContext({ invocation, runtime, channel, caller, routine }));
64
-
65
- const memory = readAgentMemory(project, agent.slug);
66
- 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 });
67
100
 
68
- const apxSkill = apcSkillFile(project.path, "apx");
69
- if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
101
+ // Channel context — the same channels/*.md the super-agent uses. Project
102
+ // agents talk through the same surfaces; they need the same formatting rules.
103
+ const channelBlock = buildChannelContextBlock(channel, channelMeta);
104
+ const voiceBlock = buildVoiceModeBlock(voice);
105
+ const segmentDiscipline = buildSegmentDiscipline({ channel: channelLow, voice });
70
106
 
71
- for (const skill of agentSkills(agent)) {
72
- const skillPath = apcSkillFile(project.path, skill);
73
- if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
74
- }
107
+ // Relationship block "you're talking to <owner>" / "<contact>" / "<guest>".
108
+ const relationship = buildRelationshipBlock(sender);
75
109
 
76
- for (const ep of extraParts) {
77
- if (ep) parts.push(ep);
78
- }
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
+ : "";
79
119
 
80
- // Always append action discipline rules last so they are close to the end
81
- // of the system prompt and harder for the model to "forget".
82
- parts.push(ACTION_DISCIPLINE_RULES);
120
+ // Invocation context (who called me, through what, for what).
121
+ const invocationCtx = buildInvocationContext({ invocation, runtime, caller, routine });
83
122
 
84
- return parts.join("\n\n");
123
+ // Per-agent memory (lives under <project>/.apc/agents/<slug>/memory.md).
124
+ const memory = readAgentMemory(project, agent.slug);
125
+ const memoryBlock = memory ? "# Memory\n" + memory : "";
126
+
127
+ // Project's APX skill + agent's declared skills (loaded as full bodies — they're
128
+ // small and specific to this agent).
129
+ const projectSkills = buildProjectSkills(project, agent);
130
+
131
+ return [
132
+ roleBlock,
133
+ profileLines.join("\n"),
134
+ customInstructions,
135
+ userContext,
136
+ memoryBlock,
137
+ relationship,
138
+ channelBlock,
139
+ toolHints,
140
+ invocationCtx,
141
+ projectSkills,
142
+ ...extraParts.filter(Boolean),
143
+ voiceBlock,
144
+ PROMPTS.ACTION_DISCIPLINE,
145
+ segmentDiscipline,
146
+ ]
147
+ .filter(Boolean)
148
+ .join("\n\n");
85
149
  }
86
150
 
87
- function buildInvocationContext({ invocation, runtime, channel, caller, routine }) {
88
- const lines = [
89
- "## Invocation Context",
90
- `invocation: ${invocation}`,
91
- ];
151
+ function buildInvocationContext({ invocation, runtime, caller, routine }) {
152
+ const lines = [`## Invocation`, `invocation: ${invocation}`];
92
153
  if (runtime) lines.push(`runtime: ${runtime}`);
93
- if (channel) lines.push(`channel: ${channel}`);
94
154
  if (caller) lines.push(`caller: ${caller}`);
95
155
  if (routine) lines.push(`routine: ${routine}`);
96
156
 
97
157
  if (runtime) {
98
158
  lines.push(
99
- "You are running inside the named external runtime. Use only tools and permissions that runtime actually exposes."
159
+ "You're running inside the named external runtime. Use only tools and permissions that runtime exposes."
100
160
  );
101
- } else if (invocation === "engine") {
102
- lines.push("You are a direct LLM call through APX. Do not claim shell, file, MCP, or Telegram tools unless APX explicitly provided them.");
103
- } else if (invocation === "telegram") {
104
- lines.push("You are replying through Telegram. Keep responses brief, plain text, and matched to the user's language.");
105
161
  } else if (invocation === "routine") {
106
- lines.push("You were invoked by an APX routine. Complete the requested work now; do not say you will do it later.");
162
+ lines.push(
163
+ "You were invoked by an APX routine. Complete the requested work now; don't say you will do it later."
164
+ );
165
+ } else if (invocation === "engine") {
166
+ lines.push(
167
+ "You're a direct LLM call through APX. Don't claim shell, file, MCP, or Telegram tools unless the runtime explicitly sent them this turn."
168
+ );
107
169
  }
108
-
109
170
  return lines.join("\n");
110
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
+ }
@@ -86,13 +86,13 @@ export function buildVoiceChannelContext(channel, { projectId, language = "es" }
86
86
  switch (channel) {
87
87
  case "voice":
88
88
  return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: { voice: true } };
89
- case "deck":
89
+ case CHANNELS.DECK:
90
90
  return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DECK, channelMeta: {} };
91
- case "desktop":
91
+ case CHANNELS.DESKTOP:
92
92
  return { ...base, contextNote: dynamicNote, systemSuffix: SUGGESTIONS_INSTRUCTION, wantsSuggestions: true, channel: CHANNELS.DESKTOP, channelMeta: { voice: true } };
93
- case "telegram":
93
+ case CHANNELS.TELEGRAM:
94
94
  return { ...base, contextNote: dynamicNote, channel: CHANNELS.TELEGRAM, channelMeta: {} };
95
95
  default:
96
- return { ...base, contextNote: dynamicNote, channel: channel || "api", channelMeta: {} };
96
+ return { ...base, contextNote: dynamicNote, channel: channel || CHANNELS.API, channelMeta: {} };
97
97
  }
98
98
  }