@agentprojectcontext/apx 1.77.3 → 1.79.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 (356) hide show
  1. package/README.md +23 -9
  2. package/package.json +12 -12
  3. package/src/core/agent/build-agent-system.js +3 -2
  4. package/src/core/agent/constants.js +3 -2
  5. package/src/core/agent/loop/greeting-guard.js +55 -0
  6. package/src/core/agent/loop/side-effects.js +52 -0
  7. package/src/core/agent/prompt-builder.js +49 -8
  8. package/src/core/agent/run-agent.js +43 -53
  9. package/src/core/agent/security.js +1 -1
  10. package/src/core/agent/self-memory.js +4 -2
  11. package/src/core/agent/skills/index-store.js +2 -2
  12. package/src/core/agent/skills/loader.js +3 -22
  13. package/src/core/agent/super-agent.js +0 -2
  14. package/src/core/agent/tool-summary.js +65 -0
  15. package/src/core/agent/tools/handlers/call-runtime.js +4 -4
  16. package/src/core/agent/tools/handlers/list-commitments.js +80 -0
  17. package/src/core/agent/tools/handlers/list-tasks.js +66 -27
  18. package/src/core/agent/tools/handlers/record-commitment.js +68 -0
  19. package/src/core/agent/tools/handlers/search-sessions.js +1 -1
  20. package/src/core/agent/tools/handlers/send-telegram.js +68 -2
  21. package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
  22. package/src/core/agent/tools/names.js +64 -0
  23. package/src/core/agent/tools/registry.js +9 -0
  24. package/src/core/agent/tools/tool-call-parser.js +70 -1
  25. package/src/core/apc/context-copy.js +1 -1
  26. package/src/core/apc/frontmatter.js +85 -0
  27. package/src/core/apc/parser.js +9 -21
  28. package/src/core/apc/paths.js +8 -8
  29. package/src/core/apc/scaffold.js +5 -4
  30. package/src/core/channels/telegram/ask-callbacks.js +35 -0
  31. package/src/core/channels/telegram/config.js +201 -0
  32. package/src/core/channels/telegram/dispatch.js +3 -0
  33. package/src/core/channels/telegram/reply.js +30 -5
  34. package/src/core/config/index.js +25 -183
  35. package/src/core/config/paths.js +57 -1
  36. package/src/core/config/redact.js +22 -0
  37. package/src/core/confirmation/adapters/web.js +1 -1
  38. package/src/core/daemon/service.js +238 -0
  39. package/src/core/deck/manifest.js +12 -12
  40. package/src/core/desktop/autostart.js +3 -2
  41. package/src/core/desktop/process.js +4 -4
  42. package/src/core/engines/gemini.js +322 -60
  43. package/src/core/engines/openai-compatible.js +21 -2
  44. package/src/core/engines/presets.js +2 -2
  45. package/src/core/http-tools/browser.js +1 -1
  46. package/src/core/http-tools/catalog.js +551 -0
  47. package/src/core/http-tools/fetch.js +1 -1
  48. package/src/core/http-tools/glob.js +3 -3
  49. package/src/core/http-tools/grep.js +2 -9
  50. package/src/core/http-tools/inline-handlers.js +59 -0
  51. package/src/core/http-tools/registry.js +31 -615
  52. package/src/core/http-tools/search.js +2 -2
  53. package/src/core/identity/self.js +6 -7
  54. package/src/core/identity/telegram.js +3 -3
  55. package/src/core/logging.js +6 -5
  56. package/src/core/mcp/sources.js +12 -16
  57. package/src/core/memory/consolidate.js +225 -0
  58. package/src/core/memory/indexer.js +2 -5
  59. package/src/core/nudge/index.js +192 -0
  60. package/src/core/nudge/policy.js +143 -0
  61. package/src/core/nudge/store.js +141 -0
  62. package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
  63. package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
  64. package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
  65. package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
  66. package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
  67. package/src/core/profiles/lifecycle.js +0 -2
  68. package/src/core/profiles/store.js +2 -7
  69. package/src/core/routines/runner.js +103 -4
  70. package/src/core/routines/signals.js +270 -0
  71. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
  72. package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
  73. package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
  74. package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
  75. package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
  76. package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
  77. package/src/core/runtimes/detect.js +2 -2
  78. package/src/core/sessions/index.js +800 -0
  79. package/src/core/stores/code-sessions.js +3 -14
  80. package/src/core/stores/commitments.js +331 -0
  81. package/src/core/stores/engine-sessions.js +0 -4
  82. package/src/core/stores/messages.js +4 -5
  83. package/src/core/stores/organization.js +9 -11
  84. package/src/core/stores/routines.js +23 -14
  85. package/src/core/stores/runtime-callbacks.js +2 -1
  86. package/src/core/util/json-file.js +128 -0
  87. package/src/core/util/thinking.js +51 -0
  88. package/src/core/vars/sources.js +14 -21
  89. package/src/core/voice/stt-models.js +1 -1
  90. package/src/core/voice/tts.js +5 -4
  91. package/src/host/daemon/api/admin-config.js +5 -5
  92. package/src/host/daemon/api/admin.js +8 -7
  93. package/src/host/daemon/api/agents.js +16 -16
  94. package/src/host/daemon/api/artifact-preview.js +8 -8
  95. package/src/host/daemon/api/artifacts.js +7 -7
  96. package/src/host/daemon/api/code.js +8 -8
  97. package/src/host/daemon/api/commitments.js +135 -0
  98. package/src/host/daemon/api/config.js +6 -6
  99. package/src/host/daemon/api/confirm.js +2 -2
  100. package/src/host/daemon/api/connections.js +2 -2
  101. package/src/host/daemon/api/conversations.js +12 -12
  102. package/src/host/daemon/api/deck.js +6 -6
  103. package/src/host/daemon/api/desktop.js +8 -8
  104. package/src/host/daemon/api/embeddings.js +4 -4
  105. package/src/host/daemon/api/engines.js +8 -7
  106. package/src/host/daemon/api/exec.js +5 -4
  107. package/src/host/daemon/api/files-project.js +6 -6
  108. package/src/host/daemon/api/health.js +2 -2
  109. package/src/host/daemon/api/identity.js +3 -3
  110. package/src/host/daemon/api/inbox.js +2 -2
  111. package/src/host/daemon/api/integrations.js +23 -19
  112. package/src/host/daemon/api/mcps.js +16 -12
  113. package/src/host/daemon/api/messages.js +5 -5
  114. package/src/host/daemon/api/nudges.js +112 -0
  115. package/src/host/daemon/api/organization.js +8 -8
  116. package/src/host/daemon/api/pairing.js +6 -6
  117. package/src/host/daemon/api/plugins.js +3 -3
  118. package/src/host/daemon/api/prefix.js +20 -0
  119. package/src/host/daemon/api/profiles.js +9 -9
  120. package/src/host/daemon/api/projects.js +5 -5
  121. package/src/host/daemon/api/routines.js +32 -8
  122. package/src/host/daemon/api/run.js +2 -2
  123. package/src/host/daemon/api/runtimes.js +6 -6
  124. package/src/host/daemon/api/self-memory.js +50 -0
  125. package/src/host/daemon/api/sessions-search.js +18 -11
  126. package/src/host/daemon/api/sessions.js +6 -6
  127. package/src/host/daemon/api/shared.js +59 -13
  128. package/src/host/daemon/api/skills.js +12 -12
  129. package/src/host/daemon/api/super-agent.js +4 -4
  130. package/src/host/daemon/api/tasks.js +11 -11
  131. package/src/host/daemon/api/telegram.js +61 -24
  132. package/src/host/daemon/api/tools.js +7 -7
  133. package/src/host/daemon/api/top-level.js +7 -7
  134. package/src/host/daemon/api/transcribe.js +6 -6
  135. package/src/host/daemon/api/tts.js +3 -3
  136. package/src/host/daemon/api/vars.js +11 -8
  137. package/src/host/daemon/api/voice.js +9 -7
  138. package/src/host/daemon/api/web.js +28 -32
  139. package/src/host/daemon/api.js +83 -48
  140. package/src/host/daemon/callback-reconciler.js +16 -0
  141. package/src/host/daemon/db.js +1 -1
  142. package/src/host/daemon/desktop-ws.js +7 -3
  143. package/src/host/daemon/index.js +11 -5
  144. package/src/host/daemon/plugins/desktop/index.js +8 -2
  145. package/src/host/daemon/plugins/telegram/index.js +7 -2
  146. package/src/host/daemon/stt-venv.js +20 -81
  147. package/src/host/daemon/wakeup.js +18 -4
  148. package/src/interfaces/acp/index.js +3 -3
  149. package/src/interfaces/acp/session.js +2 -2
  150. package/src/interfaces/cli/commands/a2a.js +2 -2
  151. package/src/interfaces/cli/commands/agent.js +3 -3
  152. package/src/interfaces/cli/commands/artifact.js +13 -13
  153. package/src/interfaces/cli/commands/chat.js +3 -3
  154. package/src/interfaces/cli/commands/command.js +2 -2
  155. package/src/interfaces/cli/commands/commitment.js +154 -0
  156. package/src/interfaces/cli/commands/config.js +4 -4
  157. package/src/interfaces/cli/commands/daemon.js +74 -18
  158. package/src/interfaces/cli/commands/desktop.js +7 -14
  159. package/src/interfaces/cli/commands/exec.js +2 -2
  160. package/src/interfaces/cli/commands/log.js +3 -4
  161. package/src/interfaces/cli/commands/mcp.js +9 -9
  162. package/src/interfaces/cli/commands/memory.js +75 -2
  163. package/src/interfaces/cli/commands/messages.js +5 -5
  164. package/src/interfaces/cli/commands/model.js +0 -18
  165. package/src/interfaces/cli/commands/nudge.js +130 -0
  166. package/src/interfaces/cli/commands/obsidian.js +5 -5
  167. package/src/interfaces/cli/commands/org.js +5 -5
  168. package/src/interfaces/cli/commands/pair.js +6 -6
  169. package/src/interfaces/cli/commands/plugins.js +2 -2
  170. package/src/interfaces/cli/commands/profile.js +10 -10
  171. package/src/interfaces/cli/commands/project-config.js +6 -6
  172. package/src/interfaces/cli/commands/project.js +10 -10
  173. package/src/interfaces/cli/commands/routine.js +8 -9
  174. package/src/interfaces/cli/commands/runtime.js +2 -2
  175. package/src/interfaces/cli/commands/search.js +0 -1
  176. package/src/interfaces/cli/commands/session.js +12 -40
  177. package/src/interfaces/cli/commands/sessions.js +21 -798
  178. package/src/interfaces/cli/commands/setup.js +16 -6
  179. package/src/interfaces/cli/commands/skills.js +1 -2
  180. package/src/interfaces/cli/commands/status.js +3 -3
  181. package/src/interfaces/cli/commands/task.js +8 -8
  182. package/src/interfaces/cli/commands/telegram.js +21 -21
  183. package/src/interfaces/cli/commands/voice.js +6 -6
  184. package/src/interfaces/cli/help/index.js +2245 -0
  185. package/src/interfaces/cli/http.js +9 -4
  186. package/src/interfaces/cli/index.js +14 -2857
  187. package/src/interfaces/cli/routes/acp.js +13 -0
  188. package/src/interfaces/cli/routes/agent.js +27 -0
  189. package/src/interfaces/cli/routes/artifact.js +28 -0
  190. package/src/interfaces/cli/routes/chat.js +11 -0
  191. package/src/interfaces/cli/routes/code.js +11 -0
  192. package/src/interfaces/cli/routes/command.js +21 -0
  193. package/src/interfaces/cli/routes/commitment.js +19 -0
  194. package/src/interfaces/cli/routes/config.js +16 -0
  195. package/src/interfaces/cli/routes/connections.js +11 -0
  196. package/src/interfaces/cli/routes/conversations.js +21 -0
  197. package/src/interfaces/cli/routes/daemon.js +25 -0
  198. package/src/interfaces/cli/routes/desktop.js +20 -0
  199. package/src/interfaces/cli/routes/env.js +13 -0
  200. package/src/interfaces/cli/routes/exec.js +11 -0
  201. package/src/interfaces/cli/routes/identity.js +11 -0
  202. package/src/interfaces/cli/routes/index.js +75 -0
  203. package/src/interfaces/cli/routes/init.js +11 -0
  204. package/src/interfaces/cli/routes/log.js +20 -0
  205. package/src/interfaces/cli/routes/mcp.js +22 -0
  206. package/src/interfaces/cli/routes/memory.js +19 -0
  207. package/src/interfaces/cli/routes/messages.js +16 -0
  208. package/src/interfaces/cli/routes/model.js +11 -0
  209. package/src/interfaces/cli/routes/nudge.js +17 -0
  210. package/src/interfaces/cli/routes/obsidian.js +17 -0
  211. package/src/interfaces/cli/routes/org.js +34 -0
  212. package/src/interfaces/cli/routes/overlay.js +10 -0
  213. package/src/interfaces/cli/routes/pair.js +17 -0
  214. package/src/interfaces/cli/routes/panel.js +16 -0
  215. package/src/interfaces/cli/routes/permission.js +11 -0
  216. package/src/interfaces/cli/routes/plugins.js +21 -0
  217. package/src/interfaces/cli/routes/profile.js +27 -0
  218. package/src/interfaces/cli/routes/project.js +41 -0
  219. package/src/interfaces/cli/routes/restart.js +15 -0
  220. package/src/interfaces/cli/routes/routine.js +28 -0
  221. package/src/interfaces/cli/routes/run.js +11 -0
  222. package/src/interfaces/cli/routes/search.js +11 -0
  223. package/src/interfaces/cli/routes/send.js +11 -0
  224. package/src/interfaces/cli/routes/session.js +26 -0
  225. package/src/interfaces/cli/routes/sessions.js +15 -0
  226. package/src/interfaces/cli/routes/setup.js +18 -0
  227. package/src/interfaces/cli/routes/skills.js +20 -0
  228. package/src/interfaces/cli/routes/status.js +12 -0
  229. package/src/interfaces/cli/routes/task.js +26 -0
  230. package/src/interfaces/cli/routes/telegram.js +39 -0
  231. package/src/interfaces/cli/routes/update.js +24 -0
  232. package/src/interfaces/cli/routes/voice.js +17 -0
  233. package/src/interfaces/desktop/main.js +6 -7
  234. package/src/interfaces/desktop/renderer.js +0 -5
  235. package/src/interfaces/mcp-server/index.js +15 -14
  236. package/src/interfaces/tui/_shims/globals.d.ts +8 -0
  237. package/src/interfaces/tui/tsconfig.json +4 -1
  238. package/src/interfaces/web/README.md +6 -6
  239. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
  240. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
  241. package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
  242. package/src/interfaces/web/dist/index.html +2 -2
  243. package/src/interfaces/web/package-lock.json +11 -10
  244. package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
  245. package/src/interfaces/web/src/components/Section.tsx +18 -3
  246. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
  247. package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
  248. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
  249. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
  250. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
  251. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
  252. package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
  253. package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
  254. package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
  255. package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
  256. package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +36 -6
  257. package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
  258. package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
  259. package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +14 -4
  260. package/src/interfaces/web/src/components/routines/shared.ts +14 -5
  261. package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
  262. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
  263. package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
  264. package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
  265. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
  266. package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
  267. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
  268. package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
  269. package/src/interfaces/web/src/components/ui.tsx +1 -0
  270. package/src/interfaces/web/src/constants/index.ts +1 -0
  271. package/src/interfaces/web/src/hooks/useChat.ts +5 -1
  272. package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
  273. package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
  274. package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
  275. package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
  276. package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
  277. package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
  278. package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
  279. package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
  280. package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
  281. package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
  282. package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +3 -3
  283. package/src/interfaces/web/src/i18n/en.ts +127 -0
  284. package/src/interfaces/web/src/i18n/es.ts +127 -0
  285. package/src/interfaces/web/src/lib/api/admin.ts +11 -11
  286. package/src/interfaces/web/src/lib/api/agents.ts +14 -14
  287. package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
  288. package/src/interfaces/web/src/lib/api/code.ts +1 -1
  289. package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
  290. package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
  291. package/src/interfaces/web/src/lib/api/deck.ts +3 -3
  292. package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
  293. package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
  294. package/src/interfaces/web/src/lib/api/engines.ts +3 -3
  295. package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
  296. package/src/interfaces/web/src/lib/api/health.ts +1 -1
  297. package/src/interfaces/web/src/lib/api/identity.ts +2 -2
  298. package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
  299. package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
  300. package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
  301. package/src/interfaces/web/src/lib/api/messages.ts +3 -3
  302. package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
  303. package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
  304. package/src/interfaces/web/src/lib/api/organization.ts +7 -7
  305. package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
  306. package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
  307. package/src/interfaces/web/src/lib/api/projects.ts +12 -12
  308. package/src/interfaces/web/src/lib/api/routines.ts +7 -7
  309. package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
  310. package/src/interfaces/web/src/lib/api/skills.ts +11 -11
  311. package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
  312. package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
  313. package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
  314. package/src/interfaces/web/src/lib/api/tools.ts +1 -1
  315. package/src/interfaces/web/src/lib/api/vars.ts +4 -4
  316. package/src/interfaces/web/src/lib/api/voice.ts +6 -6
  317. package/src/interfaces/web/src/lib/cron.ts +196 -0
  318. package/src/interfaces/web/src/lib/when.ts +32 -0
  319. package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
  320. package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
  321. package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
  322. package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
  323. package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
  324. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +103 -20
  325. package/src/interfaces/web/src/screens/base/LogsTab.tsx +18 -3
  326. package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
  327. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  328. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
  329. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
  330. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  331. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
  332. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
  333. package/src/interfaces/web/src/screens/project/ChatTab.tsx +24 -6
  334. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
  335. package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
  336. package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
  337. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +15 -13
  338. package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
  339. package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
  340. package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
  341. package/src/interfaces/web/src/types/daemon.ts +10 -1
  342. package/src/interfaces/web/vite.config.ts +13 -18
  343. package/src/interfaces/web/dist/assets/index-BptlbKjU.js +0 -824
  344. package/src/interfaces/web/dist/assets/index-BptlbKjU.js.map +0 -1
  345. package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +0 -1
  346. package/src/skills/apc-context/SKILL.md +0 -159
  347. /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
  348. /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
  349. /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
  350. /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
  351. /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
  352. /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
  353. /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
  354. /package/src/{host/daemon → core}/runtimes/index.js +0 -0
  355. /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
  356. /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
@@ -28,6 +28,8 @@ import loadSkill from "./handlers/load-skill.js";
28
28
  import transcribeAudio from "./handlers/transcribe-audio.js";
29
29
  import askQuestions from "./handlers/ask-questions.js";
30
30
  import createTask from "./handlers/create-task.js";
31
+ import recordCommitment from "./handlers/record-commitment.js";
32
+ import listCommitments from "./handlers/list-commitments.js";
31
33
  import listTasks from "./handlers/list-tasks.js";
32
34
  import discoverTools from "./handlers/discover-tools.js";
33
35
  import gitStatus from "./handlers/git-status.js";
@@ -81,6 +83,8 @@ const NATIVE_TOOLS = [
81
83
  askQuestions,
82
84
  createTask,
83
85
  listTasks,
86
+ recordCommitment,
87
+ listCommitments,
84
88
  discoverTools,
85
89
  gitStatus,
86
90
  gitDiff,
@@ -151,6 +155,11 @@ export const BASE_TOOL_NAMES = new Set([
151
155
  // Tasks (very common ask via chat).
152
156
  TOOLS.CREATE_TASK,
153
157
  TOOLS.LIST_TASKS,
158
+ // Commitments. In the base set on purpose: a promise is caught in passing
159
+ // ("le dije a Ana que el viernes"), and a tool the model has to discover
160
+ // first is a tool it will not reach for mid-sentence.
161
+ TOOLS.RECORD_COMMITMENT,
162
+ TOOLS.LIST_COMMITMENTS,
154
163
  // Files + basic shell — frequent enough on chat to keep hot.
155
164
  TOOLS.READ_FILE,
156
165
  TOOLS.WRITE_FILE,
@@ -162,7 +162,70 @@ function extractLlamaDottedFunctionCalls(text) {
162
162
  // trivial wrappers (<tool_call>, ```tool_use, _icall(), etc.) that often sit
163
163
  // around them. Used to clean up final answers that the model emitted with
164
164
  // leftover textual tool-call gunk.
165
- export function cleanTextOfPseudoToolCalls(text) {
165
+ /**
166
+ * The bare `tool_name({...json...})` form.
167
+ *
168
+ * WHERE THIS COMES FROM, and why it is not hypothetical: APX renders past tool
169
+ * results into model context as `[tool result: <name>] <body>`
170
+ * (stores/messages.js). A weaker model reads that pattern in its own history
171
+ * and imitates it in PROSE — gemini-3.5-flash produced
172
+ *
173
+ * [tool result: create_task] create_task({"project":"apx","title":"…"})
174
+ *
175
+ * and then told the user the task was filed. It was not. That is the worst
176
+ * failure mode available: a confident false confirmation, with nothing on disk.
177
+ *
178
+ * GATED ON KNOWN TOOL NAMES, deliberately. The other two passes key off
179
+ * unambiguous markers (`<function.` or a `{name, arguments}` pair); a bare
180
+ * `foo({...})` is ordinary prose about code. Without the allow-list, a model
181
+ * EXPLAINING `create_task({...})` in an answer would silently create a task.
182
+ * So: no known names, no matches.
183
+ *
184
+ * @param {string} text
185
+ * @param {Iterable<string>} knownNames tool names callable on this turn
186
+ */
187
+ export function extractBareFunctionCalls(text, knownNames) {
188
+ const allowed = new Set(knownNames || []);
189
+ if (!text || typeof text !== "string" || allowed.size === 0) return [];
190
+
191
+ const out = [];
192
+ // `name(` where name is a plausible identifier. The `[tool result: x] `
193
+ // prefix the model copies is left for the cleaner to strip.
194
+ const re = /(^|[^A-Za-z0-9_.])([a-z][a-z0-9_]{2,63})\s*\(\s*(?=\{)/g;
195
+ let m;
196
+ while ((m = re.exec(text)) !== null) {
197
+ const name = m[2];
198
+ if (!allowed.has(name)) continue;
199
+ // The '{' the regex already looked ahead to.
200
+ const i = text.indexOf("{", m.index + m[0].length - 1);
201
+ if (i < 0) continue;
202
+ const balanced = readBalancedJson(text, i);
203
+ if (!balanced.ok) continue;
204
+ let args;
205
+ try {
206
+ args = JSON.parse(text.slice(i, balanced.end));
207
+ } catch {
208
+ continue;
209
+ }
210
+ if (!args || typeof args !== "object" || Array.isArray(args)) continue;
211
+
212
+ // Include the closing paren in the raw span when it is there, so the
213
+ // cleaner removes the whole call rather than leaving a dangling `)`.
214
+ let end = balanced.end;
215
+ const after = text.slice(end).match(/^\s*\)/);
216
+ if (after) end += after[0].length;
217
+
218
+ const rawStart = m.index + (m[1] ? m[1].length : 0);
219
+ out.push({
220
+ id: nextId(),
221
+ type: "function",
222
+ function: { name, arguments: JSON.stringify(args) },
223
+ _raw: text.slice(rawStart, end),
224
+ });
225
+ }
226
+ return out;
227
+ }
228
+ export function cleanTextOfPseudoToolCalls(text, knownNames) {
166
229
  if (!text || typeof text !== "string") return text;
167
230
 
168
231
  // Strip explicit XML-like fences first
@@ -179,6 +242,12 @@ export function cleanTextOfPseudoToolCalls(text) {
179
242
  for (const call of extractPseudoToolCalls(out)) {
180
243
  if (call._raw) out = out.replace(call._raw, "");
181
244
  }
245
+ // The bare `name({...})` form, and the `[tool result: name]` prefix the
246
+ // model copies out of its own transcript.
247
+ for (const call of extractBareFunctionCalls(out, knownNames)) {
248
+ if (call._raw) out = out.replace(call._raw, "");
249
+ }
250
+ out = out.replace(/\[tool result:\s*[^\]]+\]\s*/gi, "");
182
251
  // Some models emit a stray `</function>` after the args without the
183
252
  // opening tag — sweep those too.
184
253
  out = out.replace(/<\/?function(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?>/gi, "");
@@ -1,5 +1,5 @@
1
1
  // Snapshot a project's read-aloud context — AGENTS.md + .apc/memory.md —
2
- // into a single labelled markdown string. Used by /deck/context/copy and
2
+ // into a single labelled markdown string. Used by /api/deck/context/copy and
3
3
  // (potentially) any other "give me the context" surface.
4
4
  import fs from "node:fs/promises";
5
5
  import { AGENTS_MD, apcMemoryFile } from "./paths.js";
@@ -0,0 +1,85 @@
1
+ // YAML-ish frontmatter — one parser.
2
+ //
3
+ // Four copies existed, and they did not agree on which keys are legal:
4
+ //
5
+ // apc/parser.js /^([a-zA-Z_-]+):/ dashes, no digits
6
+ // agent/skills/loader /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/ digits and dashes, strips quotes
7
+ // core/sessions /^([a-zA-Z_]+):/ neither
8
+ // cli/commands/session /^([a-zA-Z_]+):/ neither
9
+ //
10
+ // That is not cosmetic duplication: a key like `route_to_agent2` or
11
+ // `agent-slug` parsed in some code paths and vanished in others, so the same
12
+ // file read by the CLI and by the daemon yielded different data — with no
13
+ // error anywhere, because a non-matching line is simply skipped.
14
+ //
15
+ // This takes the most permissive correct behaviour (identifiers may contain
16
+ // digits and dashes, quoted values are unquoted) and returns everything the
17
+ // callers between them needed: the fields, the body, and where the body starts.
18
+ //
19
+ // Deliberately not a YAML library: the frontmatter APX writes is flat
20
+ // `key: value` and staying strict about that keeps the format predictable for
21
+ // the models that also read these files.
22
+
23
+ const KEY_VALUE = /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/;
24
+
25
+ function unquote(value) {
26
+ const v = value.trim();
27
+ if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
28
+ return v.slice(1, -1);
29
+ }
30
+ return v;
31
+ }
32
+
33
+ /**
34
+ * Parse leading `---` frontmatter.
35
+ *
36
+ * @param {string} text
37
+ * @returns {{ fm: Record<string,string>, body: string, bodyStart: number }}
38
+ * `fm` is `{}` when there is no frontmatter; `body` is then the whole text
39
+ * and `bodyStart` is 0.
40
+ */
41
+ export function parseFrontmatter(text) {
42
+ const raw = String(text ?? "");
43
+ // Accept both "---\n" and a bare leading "---" (the skills loader allowed
44
+ // the latter; session files always write the former).
45
+ if (!raw.startsWith("---")) return { fm: {}, body: raw, bodyStart: 0 };
46
+
47
+ const openLen = raw.startsWith("---\n") ? 4 : 3;
48
+ const end = raw.indexOf("\n---", openLen - 1);
49
+ if (end === -1) return { fm: {}, body: raw, bodyStart: 0 };
50
+
51
+ const fm = {};
52
+ for (const line of raw.slice(openLen, end).split("\n")) {
53
+ const m = line.match(KEY_VALUE);
54
+ if (m) fm[m[1]] = unquote(m[2]);
55
+ }
56
+
57
+ // The closing fence is followed by a newline, and authors conventionally
58
+ // leave a blank line after it — consume both so `body` starts at content.
59
+ const bodyStart = end + 4;
60
+ return { fm, body: raw.slice(bodyStart).replace(/^\n+/, ""), bodyStart };
61
+ }
62
+
63
+ /** Just the fields, for callers that don't need the body. */
64
+ export function parseFrontmatterFields(text) {
65
+ return parseFrontmatter(text).fm;
66
+ }
67
+
68
+ /**
69
+ * Set or replace one field in place, preserving the rest of the file byte for
70
+ * byte. Returns the text unchanged when there is no frontmatter to edit.
71
+ */
72
+ export function setFrontmatterField(text, field, value) {
73
+ const raw = String(text ?? "");
74
+ if (!raw.startsWith("---\n")) return raw;
75
+ const end = raw.indexOf("\n---", 4);
76
+ if (end === -1) return raw;
77
+
78
+ const lines = raw.slice(4, end).split("\n");
79
+ const idx = lines.findIndex((l) => l.match(KEY_VALUE)?.[1] === field);
80
+ const entry = `${field}: ${value}`;
81
+ if (idx >= 0) lines[idx] = entry;
82
+ else lines.push(entry);
83
+
84
+ return `---\n${lines.join("\n")}${raw.slice(end)}`;
85
+ }
@@ -1,6 +1,7 @@
1
1
  // Core parsers for APC — pure ESM, no deps.
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { AGENT_VAULT_DIR } from "../config/paths.js";
4
5
  import {
5
6
  apcAgentsDir,
6
7
  apcAgentFile,
@@ -124,12 +125,13 @@ export function readAgentsFromDir(root) {
124
125
  // if it's a bundled slug, deletes the user file otherwise.
125
126
  // ---------------------------------------------------------------------------
126
127
 
127
- import os from "node:os";
128
128
  import { fileURLToPath } from "node:url";
129
+ import { parseFrontmatterFields } from "./frontmatter.js";
130
+ import { readJson } from "#core/util/json-file.js";
129
131
 
130
132
  const __parserDir = path.dirname(fileURLToPath(import.meta.url));
131
133
 
132
- export const VAULT_DIR = path.join(os.homedir(), ".apx", "agents");
134
+ export const VAULT_DIR = AGENT_VAULT_DIR;
133
135
  export const BUNDLED_VAULT_DIR = path.resolve(__parserDir, "../../../assets/agent-vault-defaults");
134
136
  export const VAULT_TOMBSTONE_PATH = path.join(VAULT_DIR, ".removed.json");
135
137
 
@@ -144,10 +146,8 @@ function readVaultDirRaw(dir) {
144
146
 
145
147
  export function readVaultTombstones() {
146
148
  if (!fs.existsSync(VAULT_TOMBSTONE_PATH)) return new Set();
147
- try {
148
- const raw = JSON.parse(fs.readFileSync(VAULT_TOMBSTONE_PATH, "utf8"));
149
- return new Set(Array.isArray(raw.slugs) ? raw.slugs : []);
150
- } catch { return new Set(); }
149
+ const raw = readJson(VAULT_TOMBSTONE_PATH, {});
150
+ return new Set(Array.isArray(raw?.slugs) ? raw.slugs : []);
151
151
  }
152
152
 
153
153
  export function writeVaultTombstones(slugs) {
@@ -212,10 +212,7 @@ export { readVaultAgent };
212
212
  export function importedVaultSlugs(root) {
213
213
  const p = apcProjectFile(root);
214
214
  if (!fs.existsSync(p)) return [];
215
- try {
216
- const cfg = JSON.parse(fs.readFileSync(p, "utf8"));
217
- return cfg.agents?.imported ?? [];
218
- } catch { return []; }
215
+ return readJson(p, {})?.agents?.imported ?? [];
219
216
  }
220
217
 
221
218
  // Primary entry point.
@@ -273,14 +270,5 @@ export function findApfRoot(start = process.cwd()) {
273
270
  // Session / conversation frontmatter
274
271
  // ---------------------------------------------------------------------------
275
272
 
276
- export function parseSessionFrontmatter(text) {
277
- if (!text.startsWith("---\n")) return {};
278
- const end = text.indexOf("\n---", 4);
279
- if (end === -1) return {};
280
- const out = {};
281
- for (const line of text.slice(4, end).split("\n")) {
282
- const m = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
283
- if (m) out[m[1]] = m[2].trim();
284
- }
285
- return out;
286
- }
273
+ // Kept as the public name; the implementation is the shared parser.
274
+ export const parseSessionFrontmatter = parseFrontmatterFields;
@@ -9,16 +9,16 @@ import fs from "node:fs";
9
9
  import path from "node:path";
10
10
 
11
11
  // Raw names — exported for the rare caller that needs to glob/match by name.
12
- export const APC_DIR = ".apc";
13
- export const APC_PROJECT_FILE = "project.json";
14
- export const APC_PROJECT_CONFIG_FILE = "config.json";
15
- export const APC_PROJECT_MEMORY_FILE = "memory.md";
16
- export const APC_AGENTS_DIR = "agents";
17
- export const APC_SKILLS_DIR = "skills";
12
+ const APC_DIR = ".apc";
13
+ const APC_PROJECT_FILE = "project.json";
14
+ const APC_PROJECT_CONFIG_FILE = "config.json";
15
+ const APC_PROJECT_MEMORY_FILE = "memory.md";
16
+ const APC_AGENTS_DIR = "agents";
17
+ const APC_SKILLS_DIR = "skills";
18
18
  export const APC_COMMANDS_DIR = "commands";
19
19
  export const APC_NOTES_DIR = "notes";
20
- export const APC_MCPS_FILE = "mcps.json";
21
- export const APC_ORGANIZATION_FILE = "organization.json";
20
+ const APC_MCPS_FILE = "mcps.json";
21
+ const APC_ORGANIZATION_FILE = "organization.json";
22
22
  export const APC_REMOVED_FILE = ".removed.json";
23
23
  export const AGENTS_MD = "AGENTS.md";
24
24
 
@@ -18,6 +18,7 @@ import {
18
18
  apcAgentFile,
19
19
  agentsMdFile,
20
20
  } from "./paths.js";
21
+ import { readJson } from "#core/util/json-file.js";
21
22
 
22
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
24
  // Now under src/core/apc/ — one more "../" to escape than before.
@@ -431,8 +432,8 @@ function writeMigrateMd(apfDir, found) {
431
432
  export function getOrCreateApxId(root) {
432
433
  const p = apcProjectFile(root);
433
434
  if (!fs.existsSync(p)) return null;
434
- let cfg;
435
- try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
435
+ const cfg = readJson(p, null);
436
+ if (!cfg) return null;
436
437
  if (cfg.apx_id) return cfg.apx_id;
437
438
  const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
438
439
  console.log(`[apx] Generating new stable ID ${apxId} for project at ${root}`);
@@ -490,7 +491,7 @@ export function initApf(directory, { name } = {}) {
490
491
  return { root, agentsMd, projectJson, pendingMigration: scattered };
491
492
  }
492
493
 
493
- export function ensureAgentDir(root, slug) {
494
+ export function ensureAgentDir(root, _slug) {
494
495
  fs.mkdirSync(apcAgentsDir(root), { recursive: true });
495
496
  return apcAgentsDir(root);
496
497
  }
@@ -587,7 +588,7 @@ export function restoreVaultAgent(slug) {
587
588
  export function addImportedAgent(root, slug) {
588
589
  const p = apcProjectFile(root);
589
590
  let cfg = {};
590
- try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch {}
591
+ cfg = readJson(p, {});
591
592
  if (!cfg.agents) cfg.agents = {};
592
593
  if (!cfg.agents.imported) cfg.agents.imported = [];
593
594
  if (!cfg.agents.imported.includes(slug)) cfg.agents.imported.push(slug);
@@ -12,6 +12,7 @@ import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pend
12
12
  import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
13
13
  import { CHANNELS } from "#core/constants/channels.js";
14
14
  import { SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
15
+ import { applyNudgeCallback } from "#core/nudge/index.js";
15
16
 
16
17
  /**
17
18
  * Route an inbound callback_query. ask_questions button presses are handled
@@ -24,6 +25,10 @@ export async function handleCallbackQuery(self, callbackQuery) {
24
25
  await handleAskCallback(self, callbackQuery);
25
26
  return;
26
27
  }
28
+ if (data.startsWith("apx:nudge:")) {
29
+ await handleNudgeCallback(self, callbackQuery);
30
+ return;
31
+ }
27
32
  const adapter = createTelegramConfirmAdapter({
28
33
  token: resolveBotToken(self.channel),
29
34
  chatId: callbackQuery.message?.chat?.id,
@@ -35,6 +40,33 @@ export async function handleCallbackQuery(self, callbackQuery) {
35
40
  }
36
41
  }
37
42
 
43
+ /**
44
+ * "Was that worth interrupting you for?" — the feedback loop on proactive
45
+ * pushes (core/nudge). One tap, no reply, and the keyboard disappears so the
46
+ * chat does not accumulate stale buttons. Never re-enters the super-agent: an
47
+ * opinion about a message is not a new turn to answer.
48
+ */
49
+ export async function handleNudgeCallback(self, callbackQuery) {
50
+ const chatId = callbackQuery.message?.chat?.id;
51
+ const result = applyNudgeCallback(callbackQuery.data || "");
52
+ await self._answerCallback({
53
+ callback_query_id: callbackQuery.id,
54
+ text: result?.ack || "",
55
+ });
56
+ if (!result || !chatId) return;
57
+ try {
58
+ await self._editKeyboard({
59
+ chat_id: chatId,
60
+ message_id: callbackQuery.message?.message_id,
61
+ reply_markup: { inline_keyboard: [] },
62
+ });
63
+ } catch { /* best-effort */ }
64
+ self.log(
65
+ `telegram[${self.channel.name}] nudge feedback: ${result.entry?.kind || "?"} → ` +
66
+ `${result.entry?.feedback?.useful ? "useful" : "noise"}`
67
+ );
68
+ }
69
+
38
70
  /**
39
71
  * Draw the current question as a fresh message with its inline keyboard, wiping
40
72
  * the previous question's keyboard so the chat reads as a clean history.
@@ -182,6 +214,7 @@ export async function runResumedTurn(self, ctx) {
182
214
  let replyAuthor;
183
215
  let saUsage = null;
184
216
  let saModel = null;
217
+ let saTrace = null;
185
218
  try {
186
219
  const sa = await runTelegramSuperAgent(self, {
187
220
  chat_id,
@@ -218,6 +251,7 @@ export async function runResumedTurn(self, ctx) {
218
251
  replyAuthor = sa.name || agentDisplay;
219
252
  saUsage = sa.usage;
220
253
  saModel = sa.model || state.model || null;
254
+ saTrace = sa.trace || null;
221
255
  } catch (e) {
222
256
  self.log(`telegram[${self.channel.name}] ask resume failed: ${e.message}`);
223
257
  replyText = telegramErrorText(self, e);
@@ -235,6 +269,7 @@ export async function runResumedTurn(self, ctx) {
235
269
  replyKind: "superagent",
236
270
  saUsage,
237
271
  saModel,
272
+ saTrace,
238
273
  streamedCount: state.streamedCount,
239
274
  lastStreamedText: state.lastStreamedText,
240
275
  agentDisplay,
@@ -0,0 +1,201 @@
1
+ // Telegram identity: channels, contacts and roles.
2
+ //
3
+ // This is channel domain, not configuration plumbing — it happened to live in
4
+ // core/config/index.js because the data is stored in the global config file.
5
+ // That made the config module 711 lines, half of them Telegram, and forced
6
+ // host/daemon/api/telegram.js to import 13 symbols from "the config module".
7
+ //
8
+ // The shapes:
9
+ // cfg.telegram.channels[] — { name, bot_token, chat_id, route_to_agent,
10
+ // project, respond_with_engine, poll_interval_ms, owner_user_id }
11
+ // cfg.telegram.contacts[] — the global roster, keyed by Telegram user_id.
12
+ // Identity is per-person and the same across every chat; a channel's
13
+ // owner_user_id only marks who owns *that* channel.
14
+ // cfg.telegram.roles — role -> capabilities. "owner" is implicit (full),
15
+ // "guest" is the default for unknown senders (no tools).
16
+ //
17
+ // Re-exported from core/config/index.js so existing import sites keep working.
18
+ import { writeConfig } from "../../config/index.js";
19
+
20
+ // ── Telegram channels (multi-channel mode) ──────────────────────────────────
21
+ // Each entry in cfg.telegram.channels[] is { name, bot_token, chat_id,
22
+ // route_to_agent, project, respond_with_engine, poll_interval_ms }.
23
+ // These helpers keep the array shape stable for the CLI and the daemon API.
24
+
25
+ const CHANNEL_FIELDS = [
26
+ "name",
27
+ "bot_token",
28
+ "chat_id",
29
+ "route_to_agent",
30
+ "project",
31
+ "respond_with_engine",
32
+ "poll_interval_ms",
33
+ "owner_user_id",
34
+ ];
35
+
36
+ function ensureChannelsArray(cfg) {
37
+ cfg.telegram = cfg.telegram || {};
38
+ if (!Array.isArray(cfg.telegram.channels)) cfg.telegram.channels = [];
39
+ return cfg.telegram.channels;
40
+ }
41
+
42
+ export function listTelegramChannels(cfg) {
43
+ return ensureChannelsArray(cfg).slice();
44
+ }
45
+
46
+ export function findTelegramChannel(cfg, name) {
47
+ return ensureChannelsArray(cfg).find((c) => c.name === name) || null;
48
+ }
49
+
50
+ // Create-or-patch a channel by name. `patch` is a partial channel object;
51
+ // unknown keys are dropped. Returns { created, channel }.
52
+ export function upsertTelegramChannel(cfg, name, patch = {}) {
53
+ if (!name || typeof name !== "string")
54
+ throw new Error("upsertTelegramChannel: name required");
55
+ const channels = ensureChannelsArray(cfg);
56
+ let entry = channels.find((c) => c.name === name);
57
+ const created = !entry;
58
+ if (!entry) {
59
+ entry = { name };
60
+ channels.push(entry);
61
+ }
62
+ for (const k of CHANNEL_FIELDS) {
63
+ if (k === "name") continue;
64
+ if (patch[k] !== undefined) entry[k] = patch[k];
65
+ }
66
+ // Default respond_with_engine to true on create.
67
+ if (created && entry.respond_with_engine === undefined) {
68
+ entry.respond_with_engine = true;
69
+ }
70
+ writeConfig(cfg);
71
+ return { created, channel: entry };
72
+ }
73
+
74
+ export function removeTelegramChannel(cfg, name) {
75
+ const channels = ensureChannelsArray(cfg);
76
+ const before = channels.length;
77
+ cfg.telegram.channels = channels.filter((c) => c.name !== name);
78
+ const removed = before - cfg.telegram.channels.length;
79
+ if (removed > 0) {
80
+ // Explicit user-initiated removal — bypass the credential-loss guard
81
+ // so the write goes through even when this empties the array.
82
+ cfg._allowClear = true;
83
+ writeConfig(cfg);
84
+ }
85
+ return { removed };
86
+ }
87
+
88
+ // Clear specific optional fields on a channel (project, route_to_agent, …).
89
+ // Returns { channel } or null when no such channel.
90
+ export function unsetTelegramChannelFields(cfg, name, fields = []) {
91
+ const ch = findTelegramChannel(cfg, name);
92
+ if (!ch) return null;
93
+ let mutated = false;
94
+ for (const f of fields) {
95
+ if (!CHANNEL_FIELDS.includes(f) || f === "name") continue;
96
+ if (f in ch) {
97
+ delete ch[f];
98
+ mutated = true;
99
+ }
100
+ }
101
+ if (mutated) writeConfig(cfg);
102
+ return { channel: ch };
103
+ }
104
+
105
+ // ── Telegram contacts (global roster, keyed by user_id) ─────────────────────
106
+ // Identity of a person is global; the per-channel owner_user_id only marks who
107
+ // owns a given channel. Role lives on the contact (global), per the chosen
108
+ // design — owner_user_id overrides it to "owner" for that channel only.
109
+
110
+ const CONTACT_FIELDS = [
111
+ "user_id",
112
+ "name",
113
+ "username",
114
+ "role",
115
+ "note",
116
+ "first_seen",
117
+ "last_seen",
118
+ ];
119
+
120
+ function ensureContactsArray(cfg) {
121
+ cfg.telegram = cfg.telegram || {};
122
+ if (!Array.isArray(cfg.telegram.contacts)) cfg.telegram.contacts = [];
123
+ return cfg.telegram.contacts;
124
+ }
125
+
126
+ export function listContacts(cfg) {
127
+ return ensureContactsArray(cfg).slice();
128
+ }
129
+
130
+ export function findContact(cfg, userId) {
131
+ if (userId == null) return null;
132
+ return (
133
+ ensureContactsArray(cfg).find((c) => String(c.user_id) === String(userId)) ||
134
+ null
135
+ );
136
+ }
137
+
138
+ // Create-or-patch a contact by user_id. Unknown keys are dropped.
139
+ export function upsertContact(cfg, userId, patch = {}, { persist = true } = {}) {
140
+ if (userId == null) throw new Error("upsertContact: user_id required");
141
+ const contacts = ensureContactsArray(cfg);
142
+ let entry = contacts.find((c) => String(c.user_id) === String(userId));
143
+ const created = !entry;
144
+ if (!entry) {
145
+ entry = { user_id: userId };
146
+ contacts.push(entry);
147
+ }
148
+ for (const k of CONTACT_FIELDS) {
149
+ if (k === "user_id") continue;
150
+ if (patch[k] !== undefined) entry[k] = patch[k];
151
+ }
152
+ if (persist) writeConfig(cfg);
153
+ return { created, contact: entry };
154
+ }
155
+
156
+ export function setContactRole(cfg, userId, role) {
157
+ const { contact } = upsertContact(cfg, userId, { role });
158
+ return contact;
159
+ }
160
+
161
+ export function removeContact(cfg, userId) {
162
+ const contacts = ensureContactsArray(cfg);
163
+ const before = contacts.length;
164
+ cfg.telegram.contacts = contacts.filter(
165
+ (c) => String(c.user_id) !== String(userId)
166
+ );
167
+ const removed = before - cfg.telegram.contacts.length;
168
+ if (removed > 0) writeConfig(cfg);
169
+ return { removed };
170
+ }
171
+
172
+ export function setChannelOwner(cfg, channelName, userId) {
173
+ return upsertTelegramChannel(cfg, channelName, { owner_user_id: userId });
174
+ }
175
+
176
+ // ── Telegram roles (role → capability map) ──────────────────────────────────
177
+
178
+ export function listRoles(cfg) {
179
+ cfg.telegram = cfg.telegram || {};
180
+ return { ...(cfg.telegram.roles || {}) };
181
+ }
182
+
183
+ export function setRole(cfg, name, def) {
184
+ if (!name || typeof name !== "string") throw new Error("setRole: name required");
185
+ cfg.telegram = cfg.telegram || {};
186
+ cfg.telegram.roles = cfg.telegram.roles || {};
187
+ cfg.telegram.roles[name] = def;
188
+ writeConfig(cfg);
189
+ return cfg.telegram.roles[name];
190
+ }
191
+
192
+ export function removeRole(cfg, name) {
193
+ cfg.telegram = cfg.telegram || {};
194
+ if (!cfg.telegram.roles || !(name in cfg.telegram.roles)) return { removed: 0 };
195
+ if (name === "owner" || name === "guest") {
196
+ throw new Error(`role "${name}" is built-in and cannot be removed`);
197
+ }
198
+ delete cfg.telegram.roles[name];
199
+ writeConfig(cfg);
200
+ return { removed: 1 };
201
+ }
@@ -228,6 +228,7 @@ export async function handleUpdate(self, u) {
228
228
  let replyKind = "superagent"; // actor_kind: superagent | agent
229
229
  let replyModel = null; // model that actually produced the reply
230
230
  let replyUsage = null; // token accounting for this turn
231
+ let replyTrace = null; // what the turn actually did (summarised on the message)
231
232
  const projectCfg = target.config || self.globalConfig;
232
233
  // Display name for the super-agent persona on this channel (from identity.json).
233
234
  const agentDisplay = resolveAgentName(self.globalConfig);
@@ -335,6 +336,7 @@ export async function handleUpdate(self, u) {
335
336
  replyActorId = SUPERAGENT_ACTOR_ID;
336
337
  replyKind = "superagent";
337
338
  replyUsage = sa.usage;
339
+ replyTrace = sa.trace || null;
338
340
  replyModel = sa.model || state.model || null;
339
341
 
340
342
  // ── ask_questions integration ────────────────────────────────────
@@ -397,6 +399,7 @@ export async function handleUpdate(self, u) {
397
399
  replyKind,
398
400
  saUsage: replyUsage,
399
401
  saModel: replyModel,
402
+ saTrace: replyTrace,
400
403
  streamedCount,
401
404
  lastStreamedText,
402
405
  agentDisplay,