@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
@@ -1,803 +1,26 @@
1
- // apx sessions list AI engine sessions (Claude Code, Codex, APX, ...) without
2
- // opening an interactive picker. Each engine is a small adapter; the command
3
- // resolves a working directory (from a registered APX project or an explicit
4
- // --dir) and asks the adapter to list that engine's sessions for it.
5
- import fs from "node:fs";
6
- import os from "node:os";
7
- import path from "node:path";
8
- import { apcProjectFile } from "#core/apc/paths.js";
9
-
10
- // ── shared helpers ───────────────────────────────────────────────────────────
11
-
12
- const homeDir = (opts) => (opts && opts.home) || os.homedir();
13
-
14
- // Claude Code encodes a project cwd into a folder name by replacing every
15
- // non-alphanumeric character with "-". Mirrors encodeClaudeProjectPath in the
16
- // claude-code runtime adapter.
17
- function encodeClaudeProjectPath(cwd) {
18
- return String(cwd || "").replace(/[^A-Za-z0-9]/g, "-");
19
- }
20
-
21
- function safeStatMtime(p) {
22
- try {
23
- return fs.statSync(p).mtimeMs;
24
- } catch {
25
- return 0;
26
- }
27
- }
28
-
29
- // File-modification timestamps get clobbered by Time Machine and other backup
30
- // tools. Most engines write a `"timestamp": "<iso>"` field on every JSONL line
31
- // — when present, that's a far more reliable order key than mtime. We read a
32
- // short tail of the file and scan back for the latest valid timestamp,
33
- // falling back to the head, then mtime, when nothing parses.
34
- function readInternalTimestamp(file, { tailBytes = 8192, headBytes = 8192 } = {}) {
35
- let stat;
36
- try {
37
- stat = fs.statSync(file);
38
- } catch {
39
- return 0;
40
- }
41
- const tail = readTimestampFromRange(file, Math.max(0, stat.size - tailBytes), stat.size, { fromEnd: true });
42
- if (tail) return tail;
43
- if (stat.size > tailBytes) {
44
- const head = readTimestampFromRange(file, 0, Math.min(stat.size, headBytes), { fromEnd: false });
45
- if (head) return head;
46
- }
47
- return stat.mtimeMs || 0;
48
- }
49
-
50
- function readTimestampFromRange(file, start, end, { fromEnd }) {
51
- if (end <= start) return 0;
52
- let buf;
53
- try {
54
- const fd = fs.openSync(file, "r");
55
- try {
56
- buf = Buffer.alloc(end - start);
57
- fs.readSync(fd, buf, 0, buf.length, start);
58
- } finally {
59
- try { fs.closeSync(fd); } catch {}
60
- }
61
- } catch {
62
- return 0;
63
- }
64
- const lines = buf.toString("utf8").split("\n");
65
- const order = fromEnd ? [...lines.keys()].reverse() : [...lines.keys()];
66
- for (const i of order) {
67
- const line = lines[i];
68
- if (!line || !line.includes('"timestamp"')) continue;
69
- let d;
70
- try {
71
- d = JSON.parse(line);
72
- } catch {
73
- continue;
74
- }
75
- const ts = d?.timestamp || d?.payload?.timestamp;
76
- if (!ts) continue;
77
- const t = Date.parse(ts);
78
- if (!Number.isNaN(t)) return t;
79
- }
80
- return 0;
81
- }
82
-
83
- function sessionTimestamp(file) {
84
- return readInternalTimestamp(file) || safeStatMtime(file);
85
- }
86
-
87
- function fmtDate(ms) {
88
- if (!ms) return " ";
89
- const d = new Date(ms);
90
- const p = (n) => String(n).padStart(2, "0");
91
- return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
92
- }
93
-
94
- // Read the first bytes of a file (used for one-line JSONL headers).
95
- function readHead(file, bytes = 8192) {
96
- let fd;
97
- try {
98
- fd = fs.openSync(file, "r");
99
- const buf = Buffer.alloc(bytes);
100
- const n = fs.readSync(fd, buf, 0, bytes, 0);
101
- return buf.subarray(0, n).toString("utf8");
102
- } catch {
103
- return "";
104
- } finally {
105
- if (fd !== undefined) {
106
- try {
107
- fs.closeSync(fd);
108
- } catch {}
109
- }
110
- }
111
- }
112
-
113
- // Registered APX projects come from ~/.apx/config.json — APX does not know
114
- // every project on disk, so an unregistered project must be passed via --dir.
115
- function readApxProjects(opts) {
116
- const cfgPath = path.join(homeDir(opts), ".apx", "config.json");
117
- let entries = [];
118
- try {
119
- const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
120
- entries = Array.isArray(cfg.projects) ? cfg.projects : [];
121
- } catch {}
122
- return entries
123
- .filter((e) => e && e.path)
124
- .map((e) => {
125
- const proj = { path: path.resolve(e.path), name: null, apxId: null };
126
- try {
127
- const pj = JSON.parse(
128
- fs.readFileSync(apcProjectFile(proj.path), "utf8")
129
- );
130
- if (pj.name) proj.name = pj.name;
131
- if (pj.apx_id) proj.apxId = pj.apx_id;
132
- } catch {}
133
- if (!proj.name) proj.name = path.basename(proj.path);
134
- return proj;
135
- });
136
- }
137
-
138
- // Resolve the working directory the user wants sessions for.
139
- // --dir <path> → explicit path
140
- // --project <name> → look up a registered APX project
141
- // neither → null (caller enters discovery mode)
142
- function resolveTargetDir(args, opts) {
143
- const dirFlag = args.flags.dir;
144
- if (dirFlag && dirFlag !== true) return path.resolve(String(dirFlag));
145
-
146
- const projFlag = args.flags.project;
147
- if (projFlag && projFlag !== true) {
148
- const q = String(projFlag).toLowerCase();
149
- const projects = readApxProjects(opts);
150
- const exact =
151
- projects.find((p) => p.name.toLowerCase() === q) ||
152
- projects.find((p) => path.basename(p.path).toLowerCase() === q);
153
- if (exact) return exact.path;
154
-
155
- const fuzzy = projects.filter(
156
- (p) =>
157
- p.name.toLowerCase().includes(q) || p.path.toLowerCase().includes(q)
158
- );
159
- if (fuzzy.length === 1) return fuzzy[0].path;
160
- if (fuzzy.length > 1) {
161
- throw new Error(
162
- `--project "${projFlag}" is ambiguous; matches: ${fuzzy
163
- .map((p) => p.name)
164
- .join(", ")}`
165
- );
166
- }
167
- const known = projects.length
168
- ? projects.map((p) => p.name).join(", ")
169
- : "(none registered)";
170
- throw new Error(
171
- `--project "${projFlag}" not found in registered APX projects (${known}). ` +
172
- `Use --dir <path> for an unregistered project.`
173
- );
174
- }
175
- return null;
176
- }
177
-
178
- // ── claude code engine ───────────────────────────────────────────────────────
179
-
180
- function claudeProjectsDir(opts) {
181
- return path.join(homeDir(opts), ".claude", "projects");
182
- }
183
-
184
- function claudeReadTitle(file) {
185
- let title = null;
186
- let lastPrompt = null;
187
- let text;
188
- try {
189
- text = fs.readFileSync(file, "utf8");
190
- } catch {
191
- return null;
192
- }
193
- for (const line of text.split("\n")) {
194
- if (!line.includes('"aiTitle"') && !line.includes('"lastPrompt"')) continue;
195
- let d;
196
- try {
197
- d = JSON.parse(line);
198
- } catch {
199
- continue;
200
- }
201
- if (d.type === "ai-title" && d.aiTitle) title = d.aiTitle;
202
- else if (d.type === "last-prompt" && d.lastPrompt) lastPrompt = d.lastPrompt;
203
- }
204
- return title || lastPrompt || null;
205
- }
206
-
207
- // Decode a Claude project folder name back to a cwd-like path. The encoding is
208
- // lossy (every non-alphanumeric becomes "-"), so this returns the encoded name
209
- // when no registered APX project maps back to a real cwd.
210
- function claudeDecodeProject(encoded, opts) {
211
- const known = readApxProjects(opts);
212
- const hit = known.find((p) => encodeClaudeProjectPath(p.path) === encoded);
213
- return hit ? hit.path : null;
214
- }
215
-
216
- const claudeEngine = {
217
- id: "claude",
218
- label: "Claude Code",
219
- implemented: true,
220
- detect(opts) {
221
- const dir = claudeProjectsDir(opts);
222
- return fs.existsSync(dir)
223
- ? { available: true }
224
- : { available: false, reason: `${dir} not found` };
225
- },
226
- // Locate <id>.jsonl across every Claude project folder. Used by
227
- // `apx session resume <id>` so the user doesn't need to know the cwd.
228
- findSessionById(id, opts) {
229
- const root = claudeProjectsDir(opts);
230
- if (!fs.existsSync(root)) return null;
231
- for (const name of fs.readdirSync(root)) {
232
- const file = path.join(root, name, `${id}.jsonl`);
233
- if (fs.existsSync(file)) {
234
- return {
235
- engine: "claude",
236
- id,
237
- path: file,
238
- cwd: claudeDecodeProject(name, opts) || name,
239
- mtime: sessionTimestamp(file),
240
- title: claudeReadTitle(file) || "(sin título)",
241
- };
242
- }
243
- }
244
- return null;
245
- },
246
- // Read the full JSONL transcript. Returns raw text plus a tail trimmed to
247
- // tailBytes (default 64 KB) so callers can show "last N bytes" without
248
- // loading huge logs into memory.
249
- readSession(meta, { tailBytes = 64 * 1024 } = {}) {
250
- if (!meta?.path || !fs.existsSync(meta.path)) {
251
- return { found: false };
252
- }
253
- const raw = fs.readFileSync(meta.path, "utf8");
254
- return {
255
- found: true,
256
- raw,
257
- tail: raw.length > tailBytes ? raw.slice(-tailBytes) : raw,
258
- size: raw.length,
259
- format: "jsonl",
260
- };
261
- },
262
- listProjects(opts) {
263
- const root = claudeProjectsDir(opts);
264
- const known = new Map(
265
- readApxProjects(opts).map((p) => [encodeClaudeProjectPath(p.path), p])
266
- );
267
- const out = [];
268
- for (const name of fs.readdirSync(root)) {
269
- const dir = path.join(root, name);
270
- let files;
271
- try {
272
- files = fs.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
273
- } catch {
274
- continue;
275
- }
276
- if (files.length === 0) continue;
277
- const matched = known.get(name);
278
- out.push({
279
- key: matched ? matched.name : name,
280
- dir: matched ? matched.path : null,
281
- label: matched ? matched.path : name,
282
- count: files.length,
283
- mtime: Math.max(...files.map((f) => sessionTimestamp(path.join(dir, f)))),
284
- });
285
- }
286
- return out.sort((a, b) => b.mtime - a.mtime);
287
- },
288
- listSessions(dir, opts) {
289
- const folder = path.join(
290
- claudeProjectsDir(opts),
291
- encodeClaudeProjectPath(dir)
292
- );
293
- if (!fs.existsSync(folder)) return { found: false, location: folder };
294
- const sessions = [];
295
- for (const f of fs.readdirSync(folder)) {
296
- if (!f.endsWith(".jsonl")) continue;
297
- const file = path.join(folder, f);
298
- sessions.push({
299
- id: f.slice(0, -6),
300
- mtime: sessionTimestamp(file),
301
- title: claudeReadTitle(file) || "(sin título)",
302
- path: file,
303
- });
304
- }
305
- sessions.sort((a, b) => b.mtime - a.mtime);
306
- return { found: true, location: folder, sessions };
307
- },
308
- continueHint() {
309
- return `claude --continue (run from the project directory)`;
310
- },
311
- resumeHint(id) {
312
- return `claude -p --resume ${id} "your prompt"`;
313
- },
314
- };
315
-
316
- // ── codex engine ─────────────────────────────────────────────────────────────
317
-
318
- function codexSessionsDir(opts) {
319
- return path.join(homeDir(opts), ".codex", "sessions");
320
- }
321
-
322
- // Codex stores titles in ~/.codex/session_index.jsonl keyed by session id.
323
- function codexTitleIndex(opts) {
324
- const file = path.join(homeDir(opts), ".codex", "session_index.jsonl");
325
- const map = new Map();
326
- let text;
327
- try {
328
- text = fs.readFileSync(file, "utf8");
329
- } catch {
330
- return map;
331
- }
332
- for (const line of text.split("\n")) {
333
- if (!line.trim()) continue;
334
- try {
335
- const d = JSON.parse(line);
336
- if (d.id && d.thread_name) map.set(d.id, d.thread_name);
337
- } catch {}
338
- }
339
- return map;
340
- }
341
-
342
- // Walk ~/.codex/sessions/YYYY/MM/DD/ collecting rollout-*.jsonl files and
343
- // reading their session_meta header (first line) for id + cwd. Prefers the
344
- // internal timestamp over mtime so backups don't scramble the order.
345
- function codexScanRollouts(opts) {
346
- const root = codexSessionsDir(opts);
347
- const found = [];
348
- const walk = (dir) => {
349
- let entries;
350
- try {
351
- entries = fs.readdirSync(dir, { withFileTypes: true });
352
- } catch {
353
- return;
354
- }
355
- for (const e of entries) {
356
- const full = path.join(dir, e.name);
357
- if (e.isDirectory()) walk(full);
358
- else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
359
- const head = readHead(full);
360
- const id = (head.match(/"id":"([^"]+)"/) || [])[1];
361
- const cwd = (head.match(/"cwd":"([^"]+)"/) || [])[1];
362
- if (id) found.push({ id, cwd: cwd || null, mtime: sessionTimestamp(full) });
363
- }
364
- }
365
- };
366
- walk(root);
367
- return found;
368
- }
369
-
370
- // Scan rollouts but also keep the absolute path so resume-by-id can read the
371
- // file straight off disk. Old codexScanRollouts dropped the path; this helper
372
- // keeps it.
373
- function codexScanRolloutsWithPath(opts) {
374
- const root = codexSessionsDir(opts);
375
- const found = [];
376
- const walk = (dir) => {
377
- let entries;
378
- try {
379
- entries = fs.readdirSync(dir, { withFileTypes: true });
380
- } catch {
381
- return;
382
- }
383
- for (const e of entries) {
384
- const full = path.join(dir, e.name);
385
- if (e.isDirectory()) walk(full);
386
- else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")) {
387
- const head = readHead(full);
388
- const id = (head.match(/"id":"([^"]+)"/) || [])[1];
389
- const cwd = (head.match(/"cwd":"([^"]+)"/) || [])[1];
390
- if (id)
391
- found.push({
392
- id,
393
- cwd: cwd || null,
394
- mtime: sessionTimestamp(full),
395
- path: full,
396
- });
397
- }
398
- }
399
- };
400
- walk(root);
401
- return found;
402
- }
403
-
404
- const codexEngine = {
405
- id: "codex",
406
- label: "Codex",
407
- implemented: true,
408
- detect(opts) {
409
- const dir = codexSessionsDir(opts);
410
- return fs.existsSync(dir)
411
- ? { available: true }
412
- : { available: false, reason: `${dir} not found` };
413
- },
414
- findSessionById(id, opts) {
415
- const hit = codexScanRolloutsWithPath(opts).find((r) => r.id === id);
416
- if (!hit) return null;
417
- const titles = codexTitleIndex(opts);
418
- return {
419
- engine: "codex",
420
- id,
421
- path: hit.path,
422
- cwd: hit.cwd,
423
- mtime: hit.mtime,
424
- title: titles.get(id) || "(sin título)",
425
- };
426
- },
427
- readSession(meta, { tailBytes = 64 * 1024 } = {}) {
428
- if (!meta?.path || !fs.existsSync(meta.path)) {
429
- return { found: false };
430
- }
431
- const raw = fs.readFileSync(meta.path, "utf8");
432
- return {
433
- found: true,
434
- raw,
435
- tail: raw.length > tailBytes ? raw.slice(-tailBytes) : raw,
436
- size: raw.length,
437
- format: "jsonl",
438
- };
439
- },
440
- listProjects(opts) {
441
- const byCwd = new Map();
442
- for (const r of codexScanRollouts(opts)) {
443
- if (!r.cwd) continue;
444
- const cur = byCwd.get(r.cwd) || { count: 0, mtime: 0 };
445
- cur.count++;
446
- if (r.mtime > cur.mtime) cur.mtime = r.mtime;
447
- byCwd.set(r.cwd, cur);
448
- }
449
- return [...byCwd.entries()]
450
- .map(([cwd, v]) => ({
451
- key: cwd,
452
- dir: cwd,
453
- label: cwd,
454
- count: v.count,
455
- mtime: v.mtime,
456
- }))
457
- .sort((a, b) => b.mtime - a.mtime);
458
- },
459
- listSessions(dir, opts) {
460
- const titles = codexTitleIndex(opts);
461
- const sessions = codexScanRolloutsWithPath(opts)
462
- .filter((r) => r.cwd === dir)
463
- .map((r) => ({
464
- id: r.id,
465
- mtime: r.mtime,
466
- title: titles.get(r.id) || "(sin título)",
467
- path: r.path,
468
- }))
469
- .sort((a, b) => b.mtime - a.mtime);
470
- return { found: sessions.length > 0, location: dir, sessions };
471
- },
472
- continueHint() {
473
- return `codex resume --last`;
474
- },
475
- resumeHint(id) {
476
- return `codex exec resume ${id} "your prompt" (interactive: codex resume ${id})`;
477
- },
478
- };
479
-
480
- // ── apx engine (default) ─────────────────────────────────────────────────────
481
-
482
- function parseFrontmatter(text) {
483
- if (!text.startsWith("---\n")) return {};
484
- const end = text.indexOf("\n---", 4);
485
- if (end === -1) return {};
486
- const fm = {};
487
- for (const line of text.slice(4, end).split("\n")) {
488
- const m = line.match(/^([a-zA-Z_]+):\s*(.*)$/);
489
- if (m) fm[m[1]] = m[2].trim();
490
- }
491
- return fm;
492
- }
493
-
494
- // APX sessions are .md with ISO timestamps in frontmatter — prefer those over
495
- // mtime for the same backup-safety reason the JSONL engines do.
496
- function apxSessionTimestamp(fm, file) {
497
- for (const field of ["completed", "started"]) {
498
- const v = fm?.[field];
499
- if (!v || typeof v !== "string") continue;
500
- const t = Date.parse(v);
501
- if (!Number.isNaN(t)) return t;
502
- }
503
- return safeStatMtime(file);
504
- }
505
-
506
- function apxStorageRoot(opts) {
507
- return path.join(homeDir(opts), ".apx", "projects");
508
- }
509
-
510
- const apxEngine = {
511
- id: "apx",
512
- label: "APX",
513
- implemented: true,
514
- detect() {
515
- return { available: true };
516
- },
517
- // Walk every registered APX project looking for a session file whose
518
- // frontmatter `id:` or filename (without .md) equals the given id.
519
- findSessionById(id, opts) {
520
- const projects = readApxProjects(opts);
521
- for (const proj of projects) {
522
- if (!proj.apxId) continue;
523
- const agentsDir = path.join(apxStorageRoot(opts), proj.apxId, "agents");
524
- if (!fs.existsSync(agentsDir)) continue;
525
- for (const slug of fs.readdirSync(agentsDir)) {
526
- const sdir = path.join(agentsDir, slug, "sessions");
527
- let files;
528
- try {
529
- files = fs.readdirSync(sdir);
530
- } catch {
531
- continue;
532
- }
533
- for (const f of files) {
534
- if (!f.endsWith(".md")) continue;
535
- const file = path.join(sdir, f);
536
- const baseId = f.slice(0, -3);
537
- const fm = parseFrontmatter(fs.readFileSync(file, "utf8"));
538
- if (fm.id === id || baseId === id) {
539
- return {
540
- engine: "apx",
541
- id: fm.id || baseId,
542
- path: file,
543
- cwd: proj.path,
544
- mtime: apxSessionTimestamp(fm, file),
545
- title: fm.title || baseId,
546
- agentSlug: slug,
547
- apxId: proj.apxId,
548
- projectName: proj.name,
549
- externalSessionPath: fm.external_session_path || null,
550
- };
551
- }
552
- }
553
- }
554
- }
555
- return null;
556
- },
557
- // Read the APX session markdown. If the frontmatter points to an external
558
- // transcript (the JSONL the underlying engine left behind), include its tail
559
- // too — same data shape callers see for claude/codex direct reads.
560
- readSession(meta, { tailBytes = 64 * 1024 } = {}) {
561
- if (!meta?.path || !fs.existsSync(meta.path)) {
562
- return { found: false };
563
- }
564
- const raw = fs.readFileSync(meta.path, "utf8");
565
- const result = {
566
- found: true,
567
- raw,
568
- tail: raw,
569
- size: raw.length,
570
- format: "markdown",
571
- };
572
- if (meta.externalSessionPath && fs.existsSync(meta.externalSessionPath)) {
573
- const ext = fs.readFileSync(meta.externalSessionPath, "utf8");
574
- result.external = {
575
- path: meta.externalSessionPath,
576
- raw: ext,
577
- tail: ext.length > tailBytes ? ext.slice(-tailBytes) : ext,
578
- size: ext.length,
579
- format: "jsonl",
580
- };
581
- }
582
- return result;
583
- },
584
- listProjects(opts) {
585
- return readApxProjects(opts).map((p) => {
586
- let count = 0;
587
- let mtime = 0;
588
- if (p.apxId) {
589
- const agentsDir = path.join(apxStorageRoot(opts), p.apxId, "agents");
590
- try {
591
- for (const slug of fs.readdirSync(agentsDir)) {
592
- const sdir = path.join(agentsDir, slug, "sessions");
593
- try {
594
- for (const f of fs.readdirSync(sdir)) {
595
- if (!f.endsWith(".md")) continue;
596
- count++;
597
- const file = path.join(sdir, f);
598
- const fm = parseFrontmatter(fs.readFileSync(file, "utf8"));
599
- mtime = Math.max(mtime, apxSessionTimestamp(fm, file));
600
- }
601
- } catch {}
602
- }
603
- } catch {}
604
- }
605
- return { key: p.name, dir: p.path, label: p.path, count, mtime };
606
- });
607
- },
608
- listSessions(dir, opts) {
609
- let apxId = null;
610
- try {
611
- const pj = JSON.parse(
612
- fs.readFileSync(apcProjectFile(dir), "utf8")
613
- );
614
- apxId = pj.apx_id || null;
615
- } catch {}
616
- if (!apxId) return { found: false, location: dir };
617
- const agentsDir = path.join(apxStorageRoot(opts), apxId, "agents");
618
- if (!fs.existsSync(agentsDir)) return { found: false, location: agentsDir };
619
- const sessions = [];
620
- for (const slug of fs.readdirSync(agentsDir)) {
621
- const sdir = path.join(agentsDir, slug, "sessions");
622
- let files;
623
- try {
624
- files = fs.readdirSync(sdir);
625
- } catch {
626
- continue;
627
- }
628
- for (const f of files) {
629
- if (!f.endsWith(".md")) continue;
630
- const file = path.join(sdir, f);
631
- const fm = parseFrontmatter(fs.readFileSync(file, "utf8"));
632
- sessions.push({
633
- id: fm.id || f.slice(0, -3),
634
- mtime: apxSessionTimestamp(fm, file),
635
- title: `[${slug}] ${fm.title || "(sin título)"}`,
636
- path: file,
637
- });
638
- }
639
- }
640
- sessions.sort((a, b) => b.mtime - a.mtime);
641
- return { found: sessions.length > 0, location: agentsDir, sessions };
642
- },
643
- continueHint() {
644
- return `apx session list (run from the project directory)`;
645
- },
646
- resumeHint(id) {
647
- return `apx session resume ${id}`;
648
- },
1
+ // apx sessions / apx session find terminal presentation for the session
2
+ // engine in #core/sessions. This file owns printing and argument handling
3
+ // only; the scanning, decoding and filtering live in core (AGENTS.md rule 8).
4
+ import {
5
+ ENGINES,
6
+ collectAllSessions,
7
+ filterSessionsByQuery,
8
+ findSessionAcrossEngines,
9
+ findSessionInEngine,
10
+ resolveTargetDir,
11
+ fmtDate,
12
+ } from "#core/sessions/index.js";
13
+
14
+ // Re-exported for the CLI's own tests and for `apx session` siblings that
15
+ // still import them from here.
16
+ export {
17
+ ENGINES,
18
+ collectAllSessions,
19
+ filterSessionsByQuery,
20
+ findSessionAcrossEngines,
21
+ findSessionInEngine,
649
22
  };
650
23
 
651
- // ── antigravity engine (detected, listing not implemented) ───────────────────
652
-
653
- const antigravityEngine = {
654
- id: "antigravity",
655
- label: "Antigravity",
656
- implemented: false,
657
- detect(opts) {
658
- const candidates = [
659
- path.join(homeDir(opts), ".antigravity"),
660
- path.join(
661
- homeDir(opts),
662
- "Library",
663
- "Application Support",
664
- "Antigravity"
665
- ),
666
- ];
667
- const hit = candidates.find((c) => fs.existsSync(c));
668
- return hit
669
- ? { available: true }
670
- : { available: false, reason: "Antigravity not installed" };
671
- },
672
- };
673
-
674
- export const ENGINES = {
675
- apx: apxEngine,
676
- claude: claudeEngine,
677
- codex: codexEngine,
678
- antigravity: antigravityEngine,
679
- };
680
-
681
- // ── cross-engine helpers (used by `apx session resume <id>` etc.) ────────────
682
-
683
- /**
684
- * Search every implemented + detected engine for a session whose id matches.
685
- * Returns an array of hits (0, 1, or more if the same id exists in multiple
686
- * engines — callers should disambiguate via --engine).
687
- */
688
- export function findSessionAcrossEngines(id, opts = {}) {
689
- const hits = [];
690
- for (const engine of Object.values(ENGINES)) {
691
- if (!engine.implemented || typeof engine.findSessionById !== "function") continue;
692
- const detected = engine.detect(opts);
693
- if (!detected.available) continue;
694
- try {
695
- const hit = engine.findSessionById(id, opts);
696
- if (hit) hits.push(hit);
697
- } catch {}
698
- }
699
- return hits;
700
- }
701
-
702
- /** Same as findSessionAcrossEngines but limited to one engine id. */
703
- export function findSessionInEngine(engineId, id, opts = {}) {
704
- const engine = ENGINES[engineId];
705
- if (!engine) return null;
706
- if (!engine.implemented || typeof engine.findSessionById !== "function") return null;
707
- const detected = engine.detect(opts);
708
- if (!detected.available) return null;
709
- return engine.findSessionById(id, opts);
710
- }
711
-
712
- /**
713
- * Enumerate every session across every detected+implemented engine, returning
714
- * flat rows of { engine, id, title, mtime, cwd, path }. Used by `apx session
715
- * find`. When `dir` is given, only that working directory is scanned (cheap);
716
- * otherwise every project the engine knows about is walked.
717
- *
718
- * Caveat: an engine can only enumerate a project when it can resolve its cwd.
719
- * Codex always records cwd; APX uses registered projects; Claude can only list
720
- * folders that map back to a registered APX project (its folder names are a
721
- * lossy encoding of the original path). Scope with --dir to reach the rest.
722
- */
723
- export function collectAllSessions(opts = {}, { dir = null, engineId = null } = {}) {
724
- const out = [];
725
- for (const engine of Object.values(ENGINES)) {
726
- if (engineId && engine.id !== engineId) continue;
727
- if (!engine.implemented || typeof engine.listSessions !== "function") continue;
728
- if (!engine.detect(opts).available) continue;
729
-
730
- const dirs = [];
731
- if (dir) {
732
- dirs.push(dir);
733
- } else {
734
- let projects = [];
735
- try {
736
- projects = engine.listProjects(opts) || [];
737
- } catch {}
738
- for (const p of projects) if (p.dir) dirs.push(p.dir);
739
- }
740
-
741
- for (const d of dirs) {
742
- let res;
743
- try {
744
- res = engine.listSessions(d, opts);
745
- } catch {
746
- continue;
747
- }
748
- if (!res || !res.found) continue;
749
- for (const s of res.sessions || []) {
750
- out.push({
751
- engine: engine.id,
752
- id: s.id,
753
- title: s.title || "",
754
- mtime: s.mtime || 0,
755
- cwd: d,
756
- path: s.path || null,
757
- });
758
- }
759
- }
760
- }
761
- return out;
762
- }
763
-
764
- // Read a session file off disk for content (deep) search. Cheap: we already
765
- // have the absolute path from collectAllSessions, so no per-candidate rescan.
766
- function sessionContainsText(row, needle) {
767
- if (!row.path) return false;
768
- let text;
769
- try {
770
- text = fs.readFileSync(row.path, "utf8");
771
- } catch {
772
- return false;
773
- }
774
- return text.toLowerCase().includes(needle);
775
- }
776
-
777
- // Filter collected session rows by a free-text query. Always matches the
778
- // title; with deep=true also scans transcript content (slower). De-dupes by
779
- // engine:id and returns full rows + a `match` field ("title"|"content"),
780
- // newest first. Shared core for the CLI (apx session find) and the daemon
781
- // (GET /sessions?q=…) so terminal and web search behave identically.
782
- export function filterSessionsByQuery(rows, { query, deep = false, limit = 0 } = {}) {
783
- const needle = String(query || "").trim().toLowerCase();
784
- if (!needle) return [];
785
- const seen = new Set();
786
- const matches = [];
787
- for (const row of rows) {
788
- const titleHit = String(row.title).toLowerCase().includes(needle);
789
- let where = titleHit ? "title" : null;
790
- if (!titleHit && deep && sessionContainsText(row, needle)) where = "content";
791
- if (!where) continue;
792
- const key = `${row.engine}:${row.id}`;
793
- if (seen.has(key)) continue;
794
- seen.add(key);
795
- matches.push({ ...row, match: where });
796
- }
797
- matches.sort((a, b) => b.mtime - a.mtime);
798
- return limit > 0 ? matches.slice(0, limit) : matches;
799
- }
800
-
801
24
  export function cmdSessionFind(args, opts = {}) {
802
25
  const query = (args._ || []).join(" ").trim();
803
26
  if (!query) {