@agentprojectcontext/apx 1.31.2 → 1.32.2

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 (229) hide show
  1. package/package.json +6 -1
  2. package/skills/apc-context/SKILL.md +5 -2
  3. package/skills/apx/SKILL.md +3 -3
  4. package/skills/apx-agency-agents/SKILL.md +5 -5
  5. package/skills/apx-agent/SKILL.md +7 -7
  6. package/skills/apx-mcp/SKILL.md +6 -4
  7. package/skills/apx-mcp-builder/SKILL.md +4 -7
  8. package/skills/apx-project/SKILL.md +4 -5
  9. package/skills/apx-routine/SKILL.md +14 -12
  10. package/skills/apx-runtime/SKILL.md +5 -3
  11. package/skills/apx-sessions/SKILL.md +5 -5
  12. package/skills/apx-skill-builder/SKILL.md +10 -6
  13. package/skills/apx-task/SKILL.md +8 -8
  14. package/skills/apx-telegram/SKILL.md +23 -7
  15. package/skills/apx-voice/SKILL.md +8 -6
  16. package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
  17. package/src/core/agent/constants.js +5 -0
  18. package/src/core/agent/index.js +0 -2
  19. package/src/core/{agent-memory.js → agent/memory.js} +2 -2
  20. package/src/core/agent/model-router.js +21 -43
  21. package/src/core/agent/prompt-builder.js +17 -63
  22. package/src/core/agent/prompts/action-discipline.md +17 -0
  23. package/src/core/agent/prompts/channels/code.md +8 -12
  24. package/src/core/agent/prompts/channels/desktop.md +6 -4
  25. package/src/core/agent/prompts/channels/routine.md +10 -1
  26. package/src/core/agent/prompts/channels/telegram.md +5 -0
  27. package/src/core/agent/prompts/channels/web_code.md +20 -0
  28. package/src/core/agent/prompts/modes/voice.md +2 -2
  29. package/src/core/agent/prompts/super-agent-base.md +2 -2
  30. package/src/core/agent/run-agent.js +66 -36
  31. package/src/core/agent/runtime-bridge.js +42 -0
  32. package/src/core/agent/self-memory.js +19 -9
  33. package/src/core/agent/skills/catalog.js +65 -0
  34. package/src/core/agent/skills/index.js +6 -0
  35. package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
  36. package/src/core/agent/skills/rag.js +91 -0
  37. package/src/core/agent/skills/trigger.js +71 -0
  38. package/src/{host/daemon → core/agent}/super-agent.js +5 -5
  39. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
  40. package/src/core/agent/tools/handlers/ask-questions.js +115 -0
  41. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
  42. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
  43. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
  44. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
  45. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
  46. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
  47. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
  48. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
  49. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
  50. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
  51. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
  52. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
  53. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
  54. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
  55. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
  56. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
  57. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
  58. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
  59. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
  60. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
  61. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
  62. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
  63. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
  64. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
  65. package/src/core/agent/tools/helpers.js +74 -0
  66. package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
  67. package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
  68. package/src/core/apc/agents-vault.js +37 -0
  69. package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
  70. package/src/core/{config.js → config/index.js} +21 -27
  71. package/src/core/config/paths.js +32 -0
  72. package/src/core/constants/actors.js +8 -0
  73. package/src/core/constants/channels.js +19 -0
  74. package/src/core/constants/index.js +5 -0
  75. package/src/core/constants/permissions.js +17 -0
  76. package/src/core/constants/roles.js +9 -0
  77. package/src/core/engines/_streaming.js +63 -0
  78. package/src/core/engines/anthropic.js +11 -22
  79. package/src/core/engines/ollama.js +7 -16
  80. package/src/core/identity/index.js +8 -0
  81. package/src/core/{identity.js → identity/self.js} +5 -5
  82. package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
  83. package/src/core/logging.js +1 -1
  84. package/src/core/mascot.js +1 -1
  85. package/src/core/memory/active-threads.js +10 -10
  86. package/src/core/memory/broker.js +9 -9
  87. package/src/core/memory/compactor.js +2 -2
  88. package/src/core/memory/index.js +2 -2
  89. package/src/core/memory/indexer.js +1 -1
  90. package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
  91. package/src/core/{messages-store.js → stores/messages.js} +6 -4
  92. package/src/core/stores/routine-memory.js +71 -0
  93. package/src/core/{routines-store.js → stores/routines.js} +1 -3
  94. package/src/core/stores/runtime-sessions.js +99 -0
  95. package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
  96. package/src/core/update-check.js +1 -1
  97. package/src/core/util/ids.js +14 -0
  98. package/src/core/util/index.js +2 -0
  99. package/src/core/util/text-similarity.js +52 -0
  100. package/src/core/util/time.js +9 -0
  101. package/src/core/voice/tts.js +1 -1
  102. package/src/host/daemon/api/admin-config.js +4 -3
  103. package/src/host/daemon/api/admin.js +1 -1
  104. package/src/host/daemon/api/agents.js +4 -25
  105. package/src/host/daemon/api/artifacts.js +118 -1
  106. package/src/host/daemon/api/code.js +60 -16
  107. package/src/host/daemon/api/confirm.js +1 -1
  108. package/src/host/daemon/api/connections.js +2 -2
  109. package/src/host/daemon/api/conversations.js +2 -2
  110. package/src/host/daemon/api/deck.js +1 -1
  111. package/src/host/daemon/api/desktop.js +1 -1
  112. package/src/host/daemon/api/embeddings.js +4 -4
  113. package/src/host/daemon/api/engines.js +2 -2
  114. package/src/host/daemon/api/exec.js +3 -3
  115. package/src/host/daemon/api/identity.js +1 -1
  116. package/src/host/daemon/api/mcps.js +1 -1
  117. package/src/host/daemon/api/messages.js +1 -1
  118. package/src/host/daemon/api/runtimes.js +9 -8
  119. package/src/host/daemon/api/sessions-search.js +1 -1
  120. package/src/host/daemon/api/sessions.js +2 -2
  121. package/src/host/daemon/api/shared.js +5 -4
  122. package/src/host/daemon/api/skills.js +30 -0
  123. package/src/host/daemon/api/super-agent.js +29 -9
  124. package/src/host/daemon/api/tasks.js +2 -2
  125. package/src/host/daemon/api/telegram.js +1 -1
  126. package/src/host/daemon/api/tools.js +6 -6
  127. package/src/host/daemon/api/tts.js +2 -2
  128. package/src/host/daemon/api/voice.js +14 -12
  129. package/src/host/daemon/api.js +2 -0
  130. package/src/host/daemon/compact.js +1 -1
  131. package/src/host/daemon/db.js +4 -4
  132. package/src/host/daemon/desktop-ws.js +1 -1
  133. package/src/host/daemon/index.js +4 -4
  134. package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +45 -6
  135. package/src/host/daemon/plugins/index.js +2 -2
  136. package/src/host/daemon/plugins/telegram/ask.js +309 -0
  137. package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +390 -191
  138. package/src/host/daemon/plugins/telegram/media.js +162 -0
  139. package/src/host/daemon/projects-helpers.js +54 -0
  140. package/src/host/daemon/routines.js +28 -12
  141. package/src/host/daemon/smoke.js +2 -2
  142. package/src/host/daemon/token-store.js +1 -1
  143. package/src/host/daemon/transcription.js +2 -2
  144. package/src/host/daemon/wakeup.js +2 -2
  145. package/src/interfaces/cli/commands/agent.js +3 -3
  146. package/src/interfaces/cli/commands/artifact.js +99 -0
  147. package/src/interfaces/cli/commands/command.js +1 -1
  148. package/src/interfaces/cli/commands/config.js +3 -2
  149. package/src/interfaces/cli/commands/desktop.js +1 -1
  150. package/src/interfaces/cli/commands/exec.js +2 -1
  151. package/src/interfaces/cli/commands/identity.js +2 -2
  152. package/src/interfaces/cli/commands/init.js +1 -1
  153. package/src/interfaces/cli/commands/mcp.js +1 -1
  154. package/src/interfaces/cli/commands/memory.js +2 -2
  155. package/src/interfaces/cli/commands/model.js +16 -6
  156. package/src/interfaces/cli/commands/project.js +1 -1
  157. package/src/interfaces/cli/commands/routine.js +58 -0
  158. package/src/interfaces/cli/commands/search.js +1 -1
  159. package/src/interfaces/cli/commands/session.js +4 -4
  160. package/src/interfaces/cli/commands/setup.js +4 -3
  161. package/src/interfaces/cli/commands/skills.js +25 -4
  162. package/src/interfaces/cli/commands/status.js +1 -1
  163. package/src/interfaces/cli/commands/sys.js +11 -4
  164. package/src/interfaces/cli/commands/update.js +1 -1
  165. package/src/interfaces/cli/index.js +8 -4
  166. package/src/interfaces/cli/postinstall.js +2 -2
  167. package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
  168. package/src/interfaces/mcp-server/index.js +1 -1
  169. package/src/interfaces/tui/component/prompt/index.tsx +3 -1
  170. package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
  171. package/src/interfaces/tui/context/sync-apx.tsx +20 -2
  172. package/src/interfaces/tui/context/sync.tsx +2 -1
  173. package/src/interfaces/tui/routes/session/index.tsx +151 -136
  174. package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
  175. package/src/interfaces/tui/run.ts +2 -0
  176. package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
  177. package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
  178. package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
  179. package/src/interfaces/web/dist/index.html +2 -2
  180. package/src/interfaces/web/package-lock.json +9 -9
  181. package/src/interfaces/web/src/App.tsx +51 -32
  182. package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
  183. package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
  184. package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
  185. package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
  186. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  187. package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
  188. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
  189. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
  190. package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
  191. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -17
  192. package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
  193. package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
  194. package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
  195. package/src/interfaces/web/src/hooks/useChat.ts +47 -2
  196. package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
  197. package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
  198. package/src/interfaces/web/src/i18n/en.ts +27 -7
  199. package/src/interfaces/web/src/i18n/es.ts +27 -7
  200. package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
  201. package/src/interfaces/web/src/lib/api/skills.ts +25 -0
  202. package/src/interfaces/web/src/lib/api.ts +2 -0
  203. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +41 -20
  204. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
  205. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
  206. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
  207. package/src/interfaces/web/src/screens/project/ChatTab.tsx +27 -9
  208. package/src/skills/apc-context/SKILL.md +159 -0
  209. package/src/core/agent/ghost-guard.js +0 -24
  210. package/src/core/agent/prompts/channels/terminal.md +0 -16
  211. package/src/host/daemon/apc-runtime-context.js +0 -124
  212. package/src/host/daemon/super-agent-tools/helpers.js +0 -124
  213. package/src/host/daemon/super-agent-tools/tools/ask-questions.js +0 -32
  214. package/src/host/daemon/tool-call-parser.js +0 -2
  215. package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
  216. package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
  217. package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
  218. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
  219. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
  220. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
  221. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
  222. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
  223. /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
  224. /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
  225. /package/src/core/{parser.js → apc/parser.js} +0 -0
  226. /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
  227. /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
  228. /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
  229. /package/src/core/{session-store.js → stores/sessions.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  // Ollama adapter (https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion).
2
2
  // Local-only. No API key. Default base_url http://localhost:11434.
3
3
  import { pingUrl, fetchJsonWithTimeout, modelInOllamaTags } from "./_health.js";
4
+ import { streamJsonLines } from "./_streaming.js";
4
5
 
5
6
  function baseUrl(config) {
6
7
  return config.base_url || process.env.OLLAMA_HOST || "http://localhost:11434";
@@ -102,25 +103,15 @@ export default {
102
103
  const t = await res.text();
103
104
  throw new Error(`ollama ${res.status}: ${t}`);
104
105
  }
105
- const decoder = new TextDecoder();
106
106
  let text = "";
107
107
  let inputTokens = 0;
108
108
  let outputTokens = 0;
109
- let buf = "";
110
- for await (const chunk of res.body) {
111
- buf += decoder.decode(chunk, { stream: true });
112
- const lines = buf.split("\n");
113
- buf = lines.pop();
114
- for (const line of lines) {
115
- if (!line.trim()) continue;
116
- let evt;
117
- try { evt = JSON.parse(line); } catch { continue; }
118
- const t = evt.message?.content || "";
119
- if (t) { text += t; onToken(t); }
120
- if (evt.done) {
121
- inputTokens = evt.prompt_eval_count || 0;
122
- outputTokens = evt.eval_count || 0;
123
- }
109
+ for await (const evt of streamJsonLines(res)) {
110
+ const t = evt.message?.content || "";
111
+ if (t) { text += t; onToken(t); }
112
+ if (evt.done) {
113
+ inputTokens = evt.prompt_eval_count || 0;
114
+ outputTokens = evt.eval_count || 0;
124
115
  }
125
116
  }
126
117
  return {
@@ -0,0 +1,8 @@
1
+ // Public entry for everything identity-related.
2
+ //
3
+ // self.js → the super-agent's persona (identity.json) — agent_name,
4
+ // personality, owner.
5
+ // telegram.js → sender resolution for inbound Telegram messages — who is
6
+ // writing right now (owner / contact / guest), per-user_id roster.
7
+ export * from "./self.js";
8
+ export * from "./telegram.js";
@@ -1,13 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
+ import { SUPERAGENT_ACTOR_ID } from "../constants/actors.js";
4
5
 
5
- export const IDENTITY_PATH = path.join(os.homedir(), ".apx", "identity.json");
6
+ // Re-export so callers that already imported the actor id from here keep
7
+ // working. The single source of truth lives in core/constants/actors.js.
8
+ export { SUPERAGENT_ACTOR_ID };
6
9
 
7
- // Stable machine id for the daemon-level "super-agent" mode. Never tied to the
8
- // persona name, so renaming the persona (identity.json) doesn't break message
9
- // attribution / history queries.
10
- export const SUPERAGENT_ACTOR_ID = "super_agent";
10
+ export const IDENTITY_PATH = path.join(os.homedir(), ".apx", "identity.json");
11
11
 
12
12
  // Shown when no persona is configured yet. Brand of the app, not a persona.
13
13
  export const SUPERAGENT_DISPLAY_FALLBACK = "APX";
@@ -16,7 +16,7 @@ import {
16
16
  findTelegramChannel,
17
17
  upsertContact,
18
18
  upsertTelegramChannel,
19
- } from "./config.js";
19
+ } from "../config/index.js";
20
20
 
21
21
  function telegramDisplayName(from) {
22
22
  const full = [from?.first_name, from?.last_name].filter(Boolean).join(" ").trim();
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { APX_HOME } from "./config.js";
3
+ import { APX_HOME } from "./config/index.js";
4
4
 
5
5
  export const LOG_DIR = path.join(APX_HOME, "logs");
6
6
  export const ERROR_TRACE_PATH = path.join(LOG_DIR, "errors.jsonl");
@@ -1,5 +1,5 @@
1
1
  // APX mascot — a panda that appears in different moods across the CLI.
2
- // Usage: import { mascot } from '../core/mascot.js'; mascot('happy');
2
+ // Usage: import { mascot } from '#core/mascot.js'; mascot('happy');
3
3
 
4
4
  const R = "\x1b[0m";
5
5
  const B = "\x1b[1m";
@@ -1,4 +1,4 @@
1
- // "Hilos activos en otros canales" — a tiny, recency-based awareness block.
1
+ // "Active threads on other channels" — a tiny, recency-based awareness block.
2
2
  //
3
3
  // Unlike the Memory Broker (semantic RAG + remembered notes), this reads the
4
4
  // raw cross-channel message log and surfaces the most recent turn from every
@@ -11,8 +11,8 @@
11
11
 
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
- import { GLOBAL_MESSAGES_DIR } from "../config.js";
15
- import { parseDayJsonl } from "../messages-store.js";
14
+ import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
15
+ import { parseDayJsonl } from "../stores/messages.js";
16
16
 
17
17
  const BODY_CAP = 70;
18
18
 
@@ -20,9 +20,9 @@ function ago(ts) {
20
20
  const then = Date.parse(ts);
21
21
  if (!Number.isFinite(then)) return "";
22
22
  const mins = Math.max(0, Math.round((Date.now() - then) / 60000));
23
- if (mins < 60) return `hace ${mins} min`;
23
+ if (mins < 60) return `${mins} min ago`;
24
24
  const hrs = Math.round(mins / 60);
25
- return `hace ${hrs} h`;
25
+ return `${hrs} h ago`;
26
26
  }
27
27
 
28
28
  // Prefer the last USER turn (most recognizable for "lo de antes"); fall back to
@@ -69,7 +69,7 @@ function readChannelRecentTurn(baseDir, channel, sinceMs) {
69
69
  return turn;
70
70
  }
71
71
 
72
- // Build the "# Hilos activos en otros canales" block. Returns "" when there's
72
+ // Build the "# Active threads on other channels" block. Returns "" when there's
73
73
  // nothing recent on another channel (or the feature is disabled).
74
74
  export function buildActiveThreadsBlock(currentChannel, { config, messagesDir } = {}) {
75
75
  try {
@@ -111,10 +111,10 @@ export function buildActiveThreadsBlock(currentChannel, { config, messagesDir }
111
111
  });
112
112
 
113
113
  return [
114
- "# Hilos activos en otros canales",
115
- 'Charlas recientes en otras superficies (NO es este chat). Si el usuario dice',
116
- '"seguimos" / "lo de antes" / "lo de telegram", probablemente sea uno de estos —',
117
- "retomalo naturalmente; usá search_messages para el detalle exacto.",
114
+ "# Active threads on other channels",
115
+ "Recent chatter on other surfaces (NOT this chat). If the user says",
116
+ '"let\'s continue" / "the thing from before" / "the telegram one" it\'s probably',
117
+ "one of these — pick it up naturally; use search_messages for exact detail.",
118
118
  "",
119
119
  ...lines,
120
120
  ].join("\n");
@@ -1,10 +1,10 @@
1
1
  // Memory Broker (Pieza 4) — runs before each super-agent turn and assembles the
2
- // [MEMORIA RELEVANTE] block injected into the system prompt.
2
+ // [RELEVANT MEMORY] block injected into the system prompt.
3
3
  //
4
4
  // incoming message
5
5
  // → embed it + RAG retriever (Pieza 2) → top-K relevant chunks
6
6
  // → read the last N entries of memory.md
7
- // → merge, dedupe, format as a [MEMORIA RELEVANTE] block
7
+ // → merge, dedupe, format as a [RELEVANT MEMORY] block
8
8
  //
9
9
  // Silent and bounded: the whole thing races an 800 ms budget. If RAG is slow
10
10
  // (Ollama lagging), the block still returns with whatever memory.md gave us —
@@ -79,7 +79,7 @@ function bulletFor({ date, channel, text }) {
79
79
  return `• ${d}${c} ${trunc(text)}`.replace(/\s+/g, " ").trim();
80
80
  }
81
81
 
82
- // Build the [MEMORIA RELEVANTE] block. Returns "" when there's nothing useful.
82
+ // Build the [RELEVANT MEMORY] block. Returns "" when there's nothing useful.
83
83
  //
84
84
  // opts: { store, config, memoryPath, budgetMs, topK, channel, embed }
85
85
  export async function buildMemoryBlock(message, opts = {}) {
@@ -132,13 +132,13 @@ export async function buildMemoryBlock(message, opts = {}) {
132
132
  if (bullets.length === 0) return "";
133
133
 
134
134
  return [
135
- "# Memoria relevante (cross-channel)",
136
- "Contexto recuperado de tu memoria y del historial de todos los canales. Tratá",
137
- "como hechos conocidos; si arrancás una sesión nueva y algo de acá sigue abierto,",
138
- "mencionalo naturalmente (\"ayer estuvimos con X, ¿seguimos?\") sin que te pregunten.",
135
+ "# Relevant memory (cross-channel)",
136
+ "Context recovered from your notebook and from the message log across channels.",
137
+ "Treat these as known facts. If a fresh session opens and something here is still",
138
+ "open, bring it up naturally in the user's language (e.g. \"yesterday we were on X — shall we continue?\") without being asked.",
139
139
  "",
140
- "[MEMORIA RELEVANTE]",
140
+ "[RELEVANT MEMORY]",
141
141
  ...bullets,
142
- "[/MEMORIA RELEVANTE]",
142
+ "[/RELEVANT MEMORY]",
143
143
  ].join("\n");
144
144
  }
@@ -15,8 +15,8 @@
15
15
 
16
16
  import fs from "node:fs";
17
17
  import path from "node:path";
18
- import { GLOBAL_MESSAGES_DIR } from "../config.js";
19
- import { parseDayJsonl, appendGlobalMessage } from "../messages-store.js";
18
+ import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
19
+ import { parseDayJsonl, appendGlobalMessage } from "../stores/messages.js";
20
20
  import { callEngine } from "../engines/index.js";
21
21
 
22
22
  const DEFAULT_MAX_TURNS = 60;
@@ -12,7 +12,7 @@
12
12
  // affected piece simply contributes nothing.
13
13
 
14
14
  import path from "node:path";
15
- import { APX_HOME } from "../config.js";
15
+ import { APX_HOME } from "../config/index.js";
16
16
  import { ensureSelfMemoryFile } from "../agent/self-memory.js";
17
17
  import fs from "node:fs";
18
18
  import { openMemoryStore } from "./store.js";
@@ -141,7 +141,7 @@ export function stopMemory() {
141
141
  _ready = null;
142
142
  }
143
143
 
144
- // Build the [MEMORIA RELEVANTE] block for a turn (Pieza 4). Never throws —
144
+ // Build the [RELEVANT MEMORY] block for a turn (Pieza 4). Never throws —
145
145
  // returns "" on any failure so the prompt builder can drop the block.
146
146
  export async function memoryBlockFor(message, { config, channel, budgetMs } = {}) {
147
147
  try {
@@ -19,7 +19,7 @@
19
19
 
20
20
  import fs from "node:fs";
21
21
  import path from "node:path";
22
- import { GLOBAL_MESSAGES_DIR, APX_HOME } from "../config.js";
22
+ import { GLOBAL_MESSAGES_DIR, APX_HOME } from "../config/index.js";
23
23
  import { SELF_MEMORY_PATH, parseSelfMemoryEntries } from "../agent/self-memory.js";
24
24
  import { embedBatch, embedOne } from "./embeddings.js";
25
25
 
@@ -11,7 +11,8 @@
11
11
  // translation.
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
- import { randomUUID } from "node:crypto";
14
+ import { nowIso } from "../util/time.js";
15
+ import { shortId as makeShortId } from "../util/ids.js";
15
16
 
16
17
  function sessionsDir(storagePath) {
17
18
  return path.join(storagePath, "code-sessions");
@@ -21,13 +22,8 @@ function sessionFile(storagePath, id) {
21
22
  return path.join(sessionsDir(storagePath), `${id}.json`);
22
23
  }
23
24
 
24
- function nowIso() {
25
- return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
26
- }
27
-
28
25
  function shortId() {
29
- const hex = randomUUID().replace(/-/g, "").slice(0, 8);
30
- return "cs_" + parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
26
+ return makeShortId("cs");
31
27
  }
32
28
 
33
29
  // Atomic write: tmp file + rename so a crash mid-write can't corrupt a session.
@@ -18,9 +18,11 @@
18
18
 
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
- import { GLOBAL_MESSAGES_DIR } from "./config.js";
21
+ import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
22
+ import { CHANNELS } from "../constants/channels.js";
23
+ import { SUPERAGENT_ACTOR_ID } from "../constants/actors.js";
22
24
 
23
- const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
25
+ import { nowIso } from "../util/time.js";
24
26
 
25
27
  function dayPathJsonl(projectRoot, ts) {
26
28
  const day = (ts || nowIso()).slice(0, 10);
@@ -63,7 +65,7 @@ function inferActorKind({ actor_kind, type, actor_id, meta = {} } = {}) {
63
65
  if (explicit) return explicit;
64
66
  if (type === "compact") return "compact";
65
67
  if (type === "user" || type === "tool" || type === "system") return type;
66
- if (type === "agent") return actor_id === "super_agent" ? "superagent" : "agent";
68
+ if (type === "agent") return actor_id === SUPERAGENT_ACTOR_ID ? "superagent" : "agent";
67
69
  return null;
68
70
  }
69
71
 
@@ -531,7 +533,7 @@ export function getRecentChannelTurnsFromFs({
531
533
 
532
534
  // Telegram-specific wrapper kept for back-compat with existing call sites.
533
535
  export function getRecentTelegramTurnsFromFs(opts = {}) {
534
- return getRecentChannelTurnsFromFs({ ...opts, channel: "telegram" });
536
+ return getRecentChannelTurnsFromFs({ ...opts, channel: CHANNELS.TELEGRAM });
535
537
  }
536
538
 
537
539
  // ---------------------------------------------------------------------------
@@ -0,0 +1,71 @@
1
+ // Per-routine memory.md — durable notes scoped to a single routine.
2
+ //
3
+ // Path: <projectStoragePath>/routines/<routineId>/memory.md
4
+ //
5
+ // The routine handler (host/daemon/routines.js) creates the file on first read
6
+ // and injects a bounded slice into the super-agent prompt via
7
+ // channelMeta.routineMemory. The routine can write back with future tooling;
8
+ // today we only read.
9
+ //
10
+ // Distinct from:
11
+ // - Agent memory (~/.apx/projects/<id>/agents/<slug>/memory.md) — per-agent.
12
+ // - Super-agent self-memory (~/.apx/memory.md) — global to the super-agent.
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+
16
+ const PROMPT_LIMIT = 1500;
17
+
18
+ export function routineMemoryDir(storagePath, routineId) {
19
+ return path.join(storagePath, "routines", String(routineId || "_unknown"));
20
+ }
21
+
22
+ export function routineMemoryPath(storagePath, routineId) {
23
+ return path.join(routineMemoryDir(storagePath, routineId), "memory.md");
24
+ }
25
+
26
+ /** Read the memory body. Returns "" when the file doesn't exist. Never throws. */
27
+ export function readRoutineMemory(storagePath, routineId) {
28
+ try {
29
+ return fs.readFileSync(routineMemoryPath(storagePath, routineId), "utf8");
30
+ } catch {
31
+ return "";
32
+ }
33
+ }
34
+
35
+ /** Bounded slice for the system prompt. Returns "" when empty. */
36
+ export function readRoutineMemoryForPrompt(storagePath, routineId, limit = PROMPT_LIMIT) {
37
+ const body = readRoutineMemory(storagePath, routineId).trim();
38
+ if (!body) return "";
39
+ if (body.length <= limit) return body;
40
+ return body.slice(0, limit).trimEnd() + "\n… (truncated)";
41
+ }
42
+
43
+ /** Ensure the routine memory file exists. Returns true if it was created. */
44
+ export function ensureRoutineMemory(storagePath, routineId, routineName = "") {
45
+ const file = routineMemoryPath(storagePath, routineId);
46
+ if (fs.existsSync(file)) return false;
47
+ fs.mkdirSync(path.dirname(file), { recursive: true });
48
+ const header = routineName
49
+ ? `# Routine memory — ${routineName}\n`
50
+ : "# Routine memory\n";
51
+ fs.writeFileSync(file, header);
52
+ return true;
53
+ }
54
+
55
+ /** Append a dated note to the routine memory. Creates the file on first write. */
56
+ export function appendRoutineMemory(storagePath, routineId, note, { routineName = "" } = {}) {
57
+ const text = String(note || "").trim();
58
+ if (!text) throw new Error("nothing to remember (empty note)");
59
+ ensureRoutineMemory(storagePath, routineId, routineName);
60
+ const file = routineMemoryPath(storagePath, routineId);
61
+ const today = new Date().toISOString().slice(0, 10);
62
+ const heading = `## ${today}`;
63
+ const oneLine = text.replace(/\n+/g, " ").trim();
64
+ const bullet = `- ${oneLine}`;
65
+ const existing = fs.readFileSync(file, "utf8");
66
+ const next = existing.includes(heading)
67
+ ? existing.trimEnd() + `\n${bullet}\n`
68
+ : existing.trimEnd() + `\n\n${heading}\n${bullet}\n`;
69
+ fs.writeFileSync(file, next);
70
+ return { path: file, note: text };
71
+ }
@@ -3,9 +3,7 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import cronParser from "cron-parser";
6
-
7
- const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
8
- const isoToMs = (iso) => (iso ? Date.parse(iso) : 0);
6
+ import { nowIso, isoToMs } from "../util/time.js";
9
7
 
10
8
  function routinesPath(storagePath) {
11
9
  // storagePath is always ~/.apx/projects/{apxId}/ — flat, no .apc subdir needed.
@@ -0,0 +1,99 @@
1
+ // On-disk session state for external runtimes (Claude Code, Codex, OpenCode,
2
+ // Aider, Cursor Agent, Gemini CLI, Qwen Code). One markdown file per session
3
+ // under <storageRoot>/agents/<slug>/sessions/<id>.md with YAML frontmatter
4
+ // (id, agent, title, task_ref, status, started, completed, result, runtime,
5
+ // external_session_path).
6
+ //
7
+ // The "bridge" prompt-text builder that explains this layout to the external
8
+ // runtime lives in core/agent/runtime-bridge.js. Both used to live together
9
+ // in host/daemon/apc-runtime-context.js; they were split because they have
10
+ // different homes (text → core/agent, state → core/stores).
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { generateSessionId } from "./sessions.js";
14
+ import { nowIso } from "#core/util/time.js";
15
+
16
+ /** Create the APX runtime session file. Returns { id, filename, path }. */
17
+ export function createRuntimeSession({
18
+ projectRoot,
19
+ storageRoot = projectRoot,
20
+ agentSlug,
21
+ runtime,
22
+ taskRef = "",
23
+ title,
24
+ }) {
25
+ const dir = path.join(storageRoot, "agents", agentSlug, "sessions");
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ const id = generateSessionId(storageRoot, agentSlug);
28
+ const file = path.join(dir, `${id}.md`);
29
+ const started = nowIso();
30
+ const sessionTitle = title || `Runtime: ${runtime}`;
31
+ const body =
32
+ `---\n` +
33
+ `id: ${id}\n` +
34
+ `agent: ${agentSlug}\n` +
35
+ `title: ${sessionTitle}\n` +
36
+ `task_ref: ${taskRef}\n` +
37
+ `status: 🔄 In progress\n` +
38
+ `started: ${started}\n` +
39
+ `completed: \n` +
40
+ `result: \n` +
41
+ `runtime: ${runtime}\n` +
42
+ `external_session_path: \n` +
43
+ `---\n\n` +
44
+ `# ${sessionTitle}\n\n`;
45
+ fs.writeFileSync(file, body);
46
+ return { id, filename: `${id}.md`, path: file };
47
+ }
48
+
49
+ /** Update session frontmatter with the external transcript path + final state. */
50
+ export function closeRuntimeSession({ filePath, externalSessionPath, exitCode, result }) {
51
+ let text = fs.readFileSync(filePath, "utf8");
52
+ text = setField(text, "completed", nowIso());
53
+ if (externalSessionPath) {
54
+ text = setField(text, "external_session_path", externalSessionPath);
55
+ }
56
+ if (typeof exitCode === "number") {
57
+ text = setField(
58
+ text,
59
+ "result",
60
+ `${exitCode === 0 ? "✅" : "⚠️"} exit ${exitCode}: ${(result || "").slice(0, 200)}`
61
+ );
62
+ } else if (result) {
63
+ text = setField(text, "result", result.slice(0, 300));
64
+ }
65
+ text = setField(text, "status", exitCode === 0 ? "✅ Completed" : "⚠️ Closed with error");
66
+ fs.writeFileSync(filePath, text);
67
+ }
68
+
69
+ function setField(text, field, value) {
70
+ if (!text.startsWith("---\n")) return text;
71
+ const end = text.indexOf("\n---", 4);
72
+ if (end === -1) return text;
73
+ const fmText = text.slice(4, end);
74
+ const lines = fmText.split("\n");
75
+ let found = false;
76
+ const out = lines.map((line) => {
77
+ if (line.startsWith(`${field}:`)) {
78
+ found = true;
79
+ return `${field}: ${value}`;
80
+ }
81
+ return line;
82
+ });
83
+ if (!found) out.push(`${field}: ${value}`);
84
+ return `---\n${out.join("\n")}\n---${text.slice(end + 4)}`;
85
+ }
86
+
87
+ /**
88
+ * Extract a self-reported "APC_RESULT: ..." line from the runtime's stdout
89
+ * (the convention printed in the bridge hint). Returns the captured string
90
+ * or null. Fallback for runtimes that can't shell out to `apx session close`.
91
+ */
92
+ export function extractRuntimeResult(stdout) {
93
+ if (!stdout || typeof stdout !== "string") return null;
94
+ const m = stdout.match(/^APC_RESULT:\s*(.+?)\s*$/m);
95
+ return m ? m[1].trim() : null;
96
+ }
97
+
98
+ // Back-compat alias.
99
+ export const extractApfResult = extractRuntimeResult;
@@ -16,7 +16,8 @@
16
16
  // caller explicitly re-opens with op="reopen".
17
17
  import fs from "node:fs";
18
18
  import path from "node:path";
19
- import { randomUUID } from "node:crypto";
19
+ import { nowIso } from "../util/time.js";
20
+ import { shortId as makeShortId } from "../util/ids.js";
20
21
 
21
22
  function tasksDir(storagePath) {
22
23
  return path.join(storagePath, "tasks");
@@ -27,14 +28,8 @@ function monthlyFile(storagePath, date = new Date()) {
27
28
  return path.join(tasksDir(storagePath), `${ym}.jsonl`);
28
29
  }
29
30
 
30
- function nowIso() {
31
- return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
32
- }
33
-
34
31
  function shortId() {
35
- // 6 base36 chars from randomUUID's first 8 hex chars → 32 bits → ~4B keyspace.
36
- const hex = randomUUID().replace(/-/g, "").slice(0, 8);
37
- return "t_" + parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
32
+ return makeShortId("t");
38
33
  }
39
34
 
40
35
  function appendEvent(storagePath, event) {
@@ -6,7 +6,7 @@
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import https from "node:https";
9
- import { APX_HOME } from "./config.js";
9
+ import { APX_HOME } from "./config/index.js";
10
10
 
11
11
  const PACKAGE_NAME = "@agentprojectcontext/apx";
12
12
  const CACHE_PATH = path.join(APX_HOME, "update-check.json");
@@ -0,0 +1,14 @@
1
+ // Short, prefix-able ids derived from a UUID. Stores use these for primary
2
+ // keys when an autoincrement isn't a fit (no SQL row counter, JSON files).
3
+ import { randomUUID } from "node:crypto";
4
+
5
+ /**
6
+ * Six-character base36 id derived from a UUID. Random enough for in-process
7
+ * uniqueness inside a single file/store; combine with a prefix when collisions
8
+ * across stores would be ambiguous.
9
+ */
10
+ export function shortId(prefix = "") {
11
+ const hex = randomUUID().replace(/-/g, "").slice(0, 8);
12
+ const id = parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
13
+ return prefix ? `${prefix}_${id}` : id;
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./time.js";
2
+ export * from "./ids.js";
@@ -0,0 +1,52 @@
1
+ // Quick "are these two messages substantively the same thing?" check.
2
+ //
3
+ // Used when a turn can produce multiple text segments (pre-tool narration +
4
+ // final answer) and weaker models — gemini-flash et al. — restate the same
5
+ // content in both. The exact-equality check that lives next to the send call
6
+ // doesn't catch that because the paraphrases differ by a word or two.
7
+ //
8
+ // Jaccard on significant words (≥ 4 chars, lowercased, accent-stripped,
9
+ // punctuation stripped) — plus a "shorter-is-mostly-inside-longer" guard for
10
+ // the case where one segment is essentially a more verbose version of the
11
+ // other.
12
+
13
+ function normalizeWords(s) {
14
+ return String(s || "")
15
+ .toLowerCase()
16
+ .normalize("NFD")
17
+ .replace(/\p{Diacritic}+/gu, "")
18
+ .replace(/[^a-z0-9\s]/g, " ")
19
+ .split(/\s+/)
20
+ .filter((w) => w.length > 3);
21
+ }
22
+
23
+ /**
24
+ * Return true when `a` and `b` look like paraphrases of the same message:
25
+ * - Jaccard ≥ 0.4 on the significant-word sets, OR
26
+ * - ≥ 70% of the shorter message's significant words appear in the longer.
27
+ *
28
+ * Two very short inputs (< 3 significant words) are treated as "not enough
29
+ * signal" and never flagged as duplicates — we don't want to merge two real
30
+ * one-word replies like "Listo." / "Hecho.".
31
+ */
32
+ export function isLikelyDuplicate(a, b) {
33
+ if (!a || !b) return false;
34
+ const wa = normalizeWords(a);
35
+ const wb = normalizeWords(b);
36
+ if (wa.length < 3 || wb.length < 3) return false;
37
+
38
+ const setA = new Set(wa);
39
+ const setB = new Set(wb);
40
+ let inter = 0;
41
+ for (const t of setA) if (setB.has(t)) inter += 1;
42
+ const union = setA.size + setB.size - inter;
43
+ const jaccard = union > 0 ? inter / union : 0;
44
+ if (jaccard >= 0.4) return true;
45
+
46
+ // Shorter-as-subset: if 70%+ of the shorter message's significant words
47
+ // are present in the longer one, treat as the same content restated.
48
+ const [smaller, bigger] = setA.size <= setB.size ? [setA, setB] : [setB, setA];
49
+ let contained = 0;
50
+ for (const w of smaller) if (bigger.has(w)) contained += 1;
51
+ return contained / smaller.size >= 0.7;
52
+ }
@@ -0,0 +1,9 @@
1
+ // Time helpers. Single source for ISO timestamps so every store agrees on
2
+ // resolution (seconds) and shape ("YYYY-MM-DDTHH:MM:SSZ").
3
+ export function nowIso() {
4
+ return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
5
+ }
6
+
7
+ export function isoToMs(iso) {
8
+ return iso ? Date.parse(iso) : 0;
9
+ }
@@ -10,7 +10,7 @@
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
12
12
  import os from "node:os";
13
- import { readConfig } from "../config.js";
13
+ import { readConfig } from "../config/index.js";
14
14
  import {
15
15
  selectTtsEngine,
16
16
  listAvailableTtsEngines,
@@ -5,9 +5,10 @@
5
5
  // The PATCH variant is intentional: PUT would force the caller to send the
6
6
  // whole credentials block, and a UI that forgot one field would wipe secrets.
7
7
  // Dotted keys make every edit narrowly-scoped.
8
- import { readConfig, writeConfig } from "../../../core/config.js";
9
- import { resolveAgentName } from "../../../core/identity.js";
8
+ import { readConfig, writeConfig } from "#core/config/index.js";
9
+ import { resolveAgentName } from "#core/identity/index.js";
10
10
  import { setDottedKey, unsetDottedKey } from "../project-config.js";
11
+ import { PERMISSION_MODES, DEFAULT_PERMISSION_MODE } from "#core/constants/permissions.js";
11
12
 
12
13
  const SECRET_PATHS = [
13
14
  "engines.anthropic.api_key",
@@ -148,7 +149,7 @@ export function register(app, { config, scheduler, plugins }) {
148
149
  name: resolveAgentName(fresh),
149
150
  model: sa.model || "",
150
151
  system: sa.system || "",
151
- permission_mode: sa.permission_mode || "permiso",
152
+ permission_mode: sa.permission_mode || PERMISSION_MODES.PERMISO,
152
153
  allowed_tools: sa.allowed_tools || [],
153
154
  model_fallback: sa.model_fallback || { enabled: false, models: [], order: [] },
154
155
  });
@@ -3,7 +3,7 @@
3
3
  // POST /admin/shutdown — clean exit (50 ms grace so the response flushes).
4
4
  //
5
5
  // Both are auth-gated (the global middleware applies).
6
- import { readConfig } from "../../../core/config.js";
6
+ import { readConfig } from "#core/config/index.js";
7
7
  import fs from "node:fs";
8
8
  import os from "node:os";
9
9
  import path from "node:path";