talon-agent 4.6.1 → 5.0.1

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 (310) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -1
  3. package/prompts/README.md +2 -2
  4. package/prompts/system/memory-recall.md +16 -0
  5. package/src/app.ts +22 -7
  6. package/src/backend/claude-sdk/factory.ts +21 -41
  7. package/src/backend/claude-sdk/handler.ts +28 -7
  8. package/src/backend/claude-sdk/host/in-process.ts +147 -0
  9. package/src/backend/claude-sdk/one-shot.ts +1 -1
  10. package/src/backend/claude-sdk/options.ts +7 -6
  11. package/src/backend/claude-sdk/stream.ts +30 -3
  12. package/src/backend/claude-sdk/warm.ts +1 -1
  13. package/src/backend/codex/constants.ts +1 -1
  14. package/src/backend/codex/factory.ts +2 -2
  15. package/src/backend/codex/handler/events.ts +1 -1
  16. package/src/backend/codex/handler/message.ts +26 -12
  17. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  18. package/src/backend/codex/init.ts +1 -1
  19. package/src/backend/codex/mcp-config.ts +2 -2
  20. package/src/backend/codex/one-shot.ts +1 -1
  21. package/src/backend/kilo/handler/message.ts +4 -1
  22. package/src/backend/openai-agents/constants.ts +1 -1
  23. package/src/backend/openai-agents/factory.ts +2 -2
  24. package/src/backend/openai-agents/handler/events.ts +1 -1
  25. package/src/backend/openai-agents/handler/message.ts +8 -4
  26. package/src/backend/openai-agents/init.ts +1 -1
  27. package/src/backend/openai-agents/mcp-pool.ts +2 -2
  28. package/src/backend/opencode/handler/message.ts +4 -1
  29. package/src/backend/remote-server/chat-turn.ts +31 -23
  30. package/src/backend/remote-server/events.ts +3 -3
  31. package/src/backend/remote-server/factory.ts +6 -3
  32. package/src/backend/remote-server/index.ts +1 -1
  33. package/src/backend/remote-server/mcp.ts +1 -1
  34. package/src/backend/remote-server/one-shot.ts +1 -1
  35. package/src/backend/remote-server/server-bindings.ts +1 -1
  36. package/src/backend/remote-server/turn.ts +1 -1
  37. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  38. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  39. package/src/backend/{shared → runtime}/index.ts +34 -25
  40. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  41. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  42. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  43. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  44. package/src/backend/{shared → runtime/turn}/delivery.ts +3 -3
  45. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  46. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  47. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  48. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  51. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  52. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  53. package/src/bootstrap.ts +6 -21
  54. package/src/cli.ts +6 -3
  55. package/src/core/agent-runtime/README.md +15 -0
  56. package/src/core/agent-runtime/agent-host.ts +387 -0
  57. package/src/core/agent-runtime/capabilities.ts +11 -0
  58. package/src/core/agent-runtime/contract-tests.ts +92 -1
  59. package/src/core/agent-runtime/events.ts +1 -1
  60. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  61. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  62. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  63. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  64. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  65. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  66. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  67. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  68. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  69. package/src/core/background/triggers/command.ts +2 -2
  70. package/src/{util → core/config}/harden.ts +1 -1
  71. package/src/core/config/index.ts +16 -12
  72. package/src/{util → core/daemon}/boot-timer.ts +15 -1
  73. package/src/core/daemon/pidfile.ts +1 -1
  74. package/src/core/daemon/resource-sampler.ts +121 -0
  75. package/src/{util → core/daemon}/respawn.ts +1 -1
  76. package/src/core/doctor/index.ts +1 -1
  77. package/src/core/engine/dispatcher.ts +16 -0
  78. package/src/core/engine/gateway-actions/cron.ts +2 -2
  79. package/src/core/engine/gateway-actions/fetch-url.ts +1 -1
  80. package/src/core/engine/gateway-actions/index.ts +4 -1
  81. package/src/core/engine/gateway-actions/memory.ts +335 -0
  82. package/src/core/engine/gateway-actions/native/exec-background.ts +129 -0
  83. package/src/core/engine/gateway-actions/native/exec-remote.ts +85 -0
  84. package/src/core/engine/gateway-actions/native/exec.ts +182 -0
  85. package/src/core/engine/gateway-actions/native/index.ts +46 -0
  86. package/src/core/engine/gateway-actions/native/params.ts +46 -0
  87. package/src/core/engine/gateway-actions/native/read.ts +182 -0
  88. package/src/core/engine/gateway-actions/native/results.ts +34 -0
  89. package/src/core/engine/gateway-actions/native/search.ts +212 -0
  90. package/src/core/engine/gateway-actions/native/shell.ts +39 -0
  91. package/src/core/engine/gateway-actions/native/teleport.ts +55 -0
  92. package/src/core/engine/gateway-actions/native/write.ts +170 -0
  93. package/src/core/engine/gateway.ts +0 -6
  94. package/src/core/frontend-runtime/builtins.ts +2 -2
  95. package/src/{util → core/frontend-runtime}/chat-id.ts +30 -0
  96. package/src/core/mcp-hub/index.ts +1 -1
  97. package/src/core/mcp-hub/talon-server.ts +1 -1
  98. package/src/core/memory/import.ts +3 -2
  99. package/src/core/memory/taps.ts +199 -0
  100. package/src/core/memory/turn-retrieval.ts +222 -0
  101. package/src/core/plugin/mcp.ts +1 -1
  102. package/src/core/prompt/assemble.ts +2 -12
  103. package/src/core/prompt/index.ts +2 -2
  104. package/src/core/prompt/invalidation.ts +1 -1
  105. package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +1 -1
  106. package/src/core/tasks/index.ts +1 -1
  107. package/src/core/tools/{chat.ts → chat/chat.ts} +1 -1
  108. package/src/core/tools/{cross-send.ts → chat/cross-send.ts} +1 -1
  109. package/src/core/tools/{history.ts → chat/history.ts} +2 -2
  110. package/src/core/tools/{media.ts → chat/media.ts} +1 -1
  111. package/src/core/tools/{members.ts → chat/members.ts} +2 -2
  112. package/src/core/tools/{messaging.ts → chat/messaging.ts} +2 -2
  113. package/src/core/tools/{moderation.ts → chat/moderation.ts} +2 -2
  114. package/src/core/tools/{stickers.ts → chat/stickers.ts} +2 -2
  115. package/src/core/tools/{whatsapp.ts → chat/whatsapp.ts} +1 -1
  116. package/src/core/tools/content/memory.ts +128 -0
  117. package/src/core/tools/{web.ts → content/web.ts} +1 -1
  118. package/src/core/tools/index.ts +21 -19
  119. package/src/core/tools/{admin.ts → ops/admin.ts} +1 -1
  120. package/src/core/tools/{bridge.ts → ops/bridge.ts} +2 -2
  121. package/src/core/tools/{goals.ts → ops/goals.ts} +2 -2
  122. package/src/core/tools/{mesh.ts → ops/mesh.ts} +1 -1
  123. package/src/core/tools/{models.ts → ops/models.ts} +1 -1
  124. package/src/core/tools/{native.ts → ops/native.ts} +1 -1
  125. package/src/core/tools/{scheduling.ts → ops/scheduling.ts} +1 -1
  126. package/src/core/tools/{scripts.ts → ops/scripts.ts} +1 -1
  127. package/src/core/tools/{skills.ts → ops/skills.ts} +1 -1
  128. package/src/core/tools/{triggers.ts → ops/triggers.ts} +1 -1
  129. package/src/core/tools/types.ts +1 -0
  130. package/src/{util → core/vfs}/workspace.ts +2 -2
  131. package/src/core/weaver/turn-cpu.ts +32 -0
  132. package/src/core/weaver/weaver.ts +36 -0
  133. package/src/frontend/discord/admin.ts +1 -1
  134. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  135. package/src/frontend/discord/callbacks/components/effort.ts +1 -1
  136. package/src/frontend/discord/callbacks/components/index.ts +1 -1
  137. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  138. package/src/frontend/discord/callbacks/components/settings.ts +2 -2
  139. package/src/frontend/discord/callbacks/modals.ts +4 -1
  140. package/src/frontend/discord/commands/admin.ts +2 -36
  141. package/src/frontend/discord/commands/definitions.ts +0 -11
  142. package/src/frontend/discord/commands/info.ts +1 -1
  143. package/src/frontend/discord/commands/interaction.ts +1 -1
  144. package/src/frontend/discord/commands/router.ts +0 -3
  145. package/src/frontend/discord/commands/session.ts +8 -2
  146. package/src/frontend/discord/commands/settings.ts +2 -2
  147. package/src/frontend/discord/handlers/messages.ts +1 -1
  148. package/src/frontend/discord/handlers/state.ts +1 -1
  149. package/src/frontend/discord/middleware.ts +2 -26
  150. package/src/frontend/discord/ready.ts +1 -1
  151. package/src/frontend/discord/render.ts +41 -259
  152. package/src/frontend/discord/runtime.ts +1 -2
  153. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  154. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  155. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  156. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  157. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  158. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  159. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  160. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  161. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  162. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  163. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  164. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  165. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  166. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  167. package/src/frontend/native/index.ts +13 -10
  168. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  169. package/src/frontend/native/runtime.ts +1 -1
  170. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  171. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  172. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  173. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  174. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  175. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  176. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  177. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  178. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  179. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  180. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  181. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  182. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  183. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  184. package/src/frontend/{shared → presentation}/format.ts +4 -3
  185. package/src/frontend/{shared → presentation}/model-commands.ts +1 -1
  186. package/src/frontend/{shared → presentation}/reasoning-levels.ts +12 -0
  187. package/src/frontend/presentation/reports.ts +493 -0
  188. package/src/frontend/{shared → presentation}/session-status.ts +36 -9
  189. package/src/frontend/{shared → presentation}/status-context.ts +101 -2
  190. package/src/frontend/teams/commands.ts +2 -2
  191. package/src/frontend/teams/turn.ts +1 -1
  192. package/src/frontend/telegram/admin/background.ts +1 -1
  193. package/src/frontend/telegram/admin/health.ts +1 -1
  194. package/src/frontend/telegram/admin/sessions.ts +1 -1
  195. package/src/frontend/telegram/callbacks/effort.ts +2 -2
  196. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  197. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  198. package/src/frontend/telegram/callbacks/model/views.ts +1 -1
  199. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  200. package/src/frontend/telegram/callbacks/query.ts +1 -1
  201. package/src/frontend/telegram/callbacks/settings.ts +3 -3
  202. package/src/frontend/telegram/commands/admin.ts +6 -41
  203. package/src/frontend/telegram/commands/index.ts +1 -1
  204. package/src/frontend/telegram/commands/info.ts +2 -2
  205. package/src/frontend/telegram/commands/session.ts +9 -3
  206. package/src/frontend/telegram/commands/settings.ts +5 -3
  207. package/src/frontend/telegram/handlers/messages.ts +0 -10
  208. package/src/frontend/telegram/handlers/state.ts +1 -1
  209. package/src/frontend/telegram/index.ts +1 -5
  210. package/src/frontend/telegram/middleware.ts +1 -15
  211. package/src/frontend/telegram/model-menu.ts +1 -1
  212. package/src/frontend/telegram/render/html.ts +30 -0
  213. package/src/frontend/telegram/{helpers → render}/menu.ts +46 -19
  214. package/src/frontend/telegram/render/reports.ts +182 -0
  215. package/src/frontend/terminal/builtins/context.ts +2 -2
  216. package/src/frontend/terminal/builtins/session.ts +1 -1
  217. package/src/frontend/terminal/builtins/status.ts +2 -2
  218. package/src/frontend/terminal/index.ts +2 -2
  219. package/src/frontend/terminal/renderer.ts +1 -1
  220. package/src/frontend/whatsapp/access.ts +2 -2
  221. package/src/frontend/whatsapp/actions/history.ts +2 -2
  222. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  223. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  224. package/src/frontend/whatsapp/actions/send.ts +1 -1
  225. package/src/frontend/whatsapp/commands.ts +12 -6
  226. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  227. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  228. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  229. package/src/frontend/whatsapp/index.ts +4 -4
  230. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  231. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  232. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  233. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  234. package/src/frontend/whatsapp/registry.ts +2 -2
  235. package/src/frontend/whatsapp/runtime.ts +1 -1
  236. package/src/index.ts +6 -3
  237. package/src/storage/db.ts +6 -1
  238. package/src/storage/memory.ts +59 -8
  239. package/src/storage/metrics.ts +38 -0
  240. package/src/storage/repositories/sessions-repo.ts +6 -0
  241. package/src/storage/session-record.ts +13 -2
  242. package/src/storage/sessions.ts +12 -0
  243. package/src/storage/sql/db.sql +5 -0
  244. package/src/storage/sql/schema.sql +4 -0
  245. package/src/storage/sql/sessions.sql +3 -3
  246. package/src/storage/sql/statements.generated.ts +10 -3
  247. package/src/storage/sql/turn-meta.sql +1 -1
  248. package/src/util/log.ts +1 -1
  249. package/src/util/paths.ts +0 -2
  250. package/src/util/runtime.ts +1 -1
  251. package/src/core/engine/gateway-actions/native.ts +0 -1035
  252. package/src/core/soul/README.md +0 -110
  253. package/src/core/soul/RESEARCH.md +0 -98
  254. package/src/core/soul/associative.ts +0 -98
  255. package/src/core/soul/centrality.ts +0 -98
  256. package/src/core/soul/cluster.ts +0 -83
  257. package/src/core/soul/compiler.ts +0 -207
  258. package/src/core/soul/consolidate.ts +0 -179
  259. package/src/core/soul/critic.ts +0 -162
  260. package/src/core/soul/dag.ts +0 -265
  261. package/src/core/soul/delta.ts +0 -123
  262. package/src/core/soul/drift.ts +0 -99
  263. package/src/core/soul/embedder.ts +0 -129
  264. package/src/core/soul/emergent-critic.ts +0 -96
  265. package/src/core/soul/forgetting.ts +0 -131
  266. package/src/core/soul/governance.ts +0 -93
  267. package/src/core/soul/hash.ts +0 -97
  268. package/src/core/soul/hdc.ts +0 -154
  269. package/src/core/soul/kernel.ts +0 -540
  270. package/src/core/soul/lattice.ts +0 -103
  271. package/src/core/soul/lens.ts +0 -110
  272. package/src/core/soul/projector.ts +0 -240
  273. package/src/core/soul/reflect.ts +0 -170
  274. package/src/core/soul/reflex.ts +0 -164
  275. package/src/core/soul/retrieve.ts +0 -146
  276. package/src/core/soul/salience.ts +0 -146
  277. package/src/core/soul/service.ts +0 -204
  278. package/src/core/soul/settings.ts +0 -47
  279. package/src/core/soul/signals.ts +0 -117
  280. package/src/core/soul/talon-embedder.ts +0 -80
  281. package/src/core/soul/taps.ts +0 -199
  282. package/src/core/soul/types.ts +0 -298
  283. package/src/core/soul/valence.ts +0 -83
  284. package/src/frontend/telegram/helpers/diagnostics.ts +0 -428
  285. package/src/frontend/telegram/helpers/format.ts +0 -42
  286. package/src/frontend/telegram/helpers/index.ts +0 -13
  287. package/src/util/cleanup-registry.ts +0 -36
  288. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  289. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  290. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  291. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  292. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  293. /package/src/{util/mcp-launcher.ts → core/mcp-hub/launcher.ts} +0 -0
  294. /package/src/{util → core/tools/content}/web-content.ts +0 -0
  295. /package/src/core/tools/{mcp-env.ts → ops/mcp-env.ts} +0 -0
  296. /package/src/{util → core/weaver}/session-name.ts +0 -0
  297. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  298. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  299. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  300. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  301. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  302. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  303. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  304. /package/src/frontend/{shared → presentation}/access.ts +0 -0
  305. /package/src/frontend/{shared → presentation}/plan-usage-report.ts +0 -0
  306. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  307. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  308. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  309. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  310. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
@@ -1,12 +1,14 @@
1
1
  import type { CacheMetricsSupport } from "../../core/types.js";
2
2
  import type { PlanUsage } from "../../core/agent-runtime/capabilities.js";
3
+ import type { CacheVerdict } from "../../storage/metrics.js";
3
4
  import { formatSmartTimestamp, formatRelativeAge } from "../../util/time.js";
5
+ import { formatDuration } from "./format.js";
4
6
 
5
7
  // ── /context breakdown ────────────────────────────────────────────────────────
6
8
 
7
9
  /**
8
10
  * Rough token estimate — ~4 chars/token, the house heuristic used everywhere
9
- * (soul/projector, cache-telemetry). No real tokenizer is wired, so every
11
+ * (cache-telemetry, and the prompt budgets). No real tokenizer is wired, so every
10
12
  * measured figure in the breakdown below is an estimate at this fidelity.
11
13
  */
12
14
  export function estimateContextTokens(text: string): number {
@@ -271,7 +273,7 @@ export function buildCacheDisplay(input: {
271
273
  ? input.cacheWrite
272
274
  : 0;
273
275
 
274
- // Match the canonical formula in `src/backend/shared/usage.ts:cacheHitPercent`:
276
+ // Match the canonical formula in `src/backend/runtime/usage.ts:cacheHitPercent`:
275
277
  // cache_write is tokens being *written to* cache on this call, not served
276
278
  // from it, so it must not dilute the hit ratio. The denominator is
277
279
  // `input + read` ("effective input"), regardless of whether the backend
@@ -287,6 +289,103 @@ export function buildCacheDisplay(input: {
287
289
  };
288
290
  }
289
291
 
292
+ // ── Cache temperature ───────────────────────────────────────────────────────
293
+
294
+ export interface CacheTempDisplay {
295
+ /** Last cross-turn verdict, or "unknown" when this process saw no turn. */
296
+ verdict: CacheVerdict | "unknown";
297
+ /** Time since the last turn ended, or "—" when no turn has finished. */
298
+ idle: string;
299
+ }
300
+
301
+ /**
302
+ * Resolve the "is this chat's prompt prefix still warm?" line.
303
+ *
304
+ * The aggregate `Cache: NN% hit` above it answers a different question — it
305
+ * folds in every request of every turn, and an agentic turn's later requests
306
+ * always read the prefix the first one paid for. What costs money is whether
307
+ * the NEXT turn will find the prefix still cached, and the two inputs to that
308
+ * are the last turn's cross-turn verdict and how long ago that turn ended
309
+ * (docs/cache-economics.md).
310
+ *
311
+ * Returns null when neither is known — a chat that has not completed a turn
312
+ * in this process has nothing to say, and a fabricated "none / 0s" would read
313
+ * as a measurement.
314
+ */
315
+ export function buildCacheTempDisplay(input: {
316
+ verdict?: CacheVerdict;
317
+ lastTurnEndedAt?: number;
318
+ now?: number;
319
+ }): CacheTempDisplay | null {
320
+ const endedAt =
321
+ typeof input.lastTurnEndedAt === "number" &&
322
+ Number.isFinite(input.lastTurnEndedAt) &&
323
+ input.lastTurnEndedAt > 0
324
+ ? input.lastTurnEndedAt
325
+ : undefined;
326
+ if (!input.verdict && endedAt === undefined) return null;
327
+ const now = input.now ?? Date.now();
328
+ return {
329
+ verdict: input.verdict ?? "unknown",
330
+ idle:
331
+ endedAt === undefined ? "—" : formatDuration(Math.max(0, now - endedAt)),
332
+ };
333
+ }
334
+
335
+ /** The one-line rendering every frontend's /status shares. */
336
+ export function formatCacheTempLine(temp: CacheTempDisplay): string {
337
+ return `Cache: ${temp.verdict} last turn · idle ${temp.idle}`;
338
+ }
339
+
340
+ // ── Daemon resources ────────────────────────────────────────────────────────
341
+
342
+ export interface DaemonDisplay {
343
+ rssMb: number;
344
+ heapMb: number;
345
+ /**
346
+ * Mean daemon CPU per turn, in ms, or undefined before any turn has been
347
+ * measured. The metrics store's histograms expose count/avg/min/max only
348
+ * (`storage/metrics.ts`) — there is no percentile to read, so the line
349
+ * says "avg" rather than implying a p50 it cannot compute.
350
+ */
351
+ cpuPerTurnMs: number | undefined;
352
+ /** How many turns the average is over. */
353
+ cpuSamples: number;
354
+ }
355
+
356
+ /**
357
+ * Resolve the "what does the daemon itself cost?" line — resident memory
358
+ * now, and the CPU a turn spends inside Talon rather than waiting on a
359
+ * model (docs/ts-migration-plan.md, Phase 0). Pure: the caller reads
360
+ * `process.memoryUsage()` and the `turn.cpu_ms` histogram.
361
+ */
362
+ export function buildDaemonDisplay(input: {
363
+ rssBytes: number;
364
+ heapBytes: number;
365
+ cpuPerTurn?: { count: number; avg: number };
366
+ }): DaemonDisplay {
367
+ const toMb = (bytes: number): number =>
368
+ Number.isFinite(bytes) && bytes > 0 ? Math.round(bytes / (1024 * 1024)) : 0;
369
+ const cpu = input.cpuPerTurn;
370
+ const measured =
371
+ cpu !== undefined && cpu.count > 0 && Number.isFinite(cpu.avg);
372
+ return {
373
+ rssMb: toMb(input.rssBytes),
374
+ heapMb: toMb(input.heapBytes),
375
+ cpuPerTurnMs: measured ? Math.round(cpu.avg) : undefined,
376
+ cpuSamples: measured ? cpu.count : 0,
377
+ };
378
+ }
379
+
380
+ /** The one-line rendering every frontend's /status shares. */
381
+ export function formatDaemonLine(daemon: DaemonDisplay): string {
382
+ const cpu =
383
+ daemon.cpuPerTurnMs === undefined
384
+ ? "cpu/turn —"
385
+ : `cpu/turn avg ${daemon.cpuPerTurnMs} ms (n=${daemon.cpuSamples})`;
386
+ return `Daemon: rss ${daemon.rssMb} MB · heap ${daemon.heapMb} MB · ${cpu}`;
387
+ }
388
+
290
389
  // ── Plan limits ─────────────────────────────────────────────────────────────
291
390
 
292
391
  /** Figures older than this are labelled with their age in /status. */
@@ -9,8 +9,8 @@ import { resolveModel } from "../../core/models/catalog.js";
9
9
  import { resolveChatBackend } from "../../core/engine/backend-controller/index.js";
10
10
  import { getSessionInfo } from "../../storage/sessions.js";
11
11
  import { getChatSettings } from "../../storage/chat-settings.js";
12
- import { performSessionReset } from "../shared/session-status.js";
13
- import { buildCacheDisplay } from "../shared/status-context.js";
12
+ import { performSessionReset } from "../presentation/session-status.js";
13
+ import { buildCacheDisplay } from "../presentation/status-context.js";
14
14
  import { buildAdaptiveCard } from "./formatting.js";
15
15
  import type { ChatMessage } from "./graph.js";
16
16
  import { postCard } from "./outbound.js";
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { log, logError } from "../../util/log.js";
7
- import { deriveNumericChatId } from "../../util/chat-id.js";
7
+ import { deriveNumericChatId } from "../../core/frontend-runtime/chat-id.js";
8
8
  import {
9
9
  toolInputToRecord,
10
10
  type AgentEvent,
@@ -9,7 +9,7 @@ import {
9
9
  describeSchedule,
10
10
  nextRunAt,
11
11
  } from "../../../storage/cron.js";
12
- import { getPulseStatus } from "../../../core/background/pulse.js";
12
+ import { getPulseStatus } from "../../../core/background/pulse/pulse.js";
13
13
  import { replyHtmlChunked } from "./chunked-reply.js";
14
14
 
15
15
  export async function replyCronJobs(ctx: Context): Promise<void> {
@@ -12,7 +12,7 @@ import { getAllSessions } from "../../../storage/sessions.js";
12
12
  import { todayLogDate } from "../../../storage/daily-log.js";
13
13
  import { getActiveCount } from "../../../core/engine/dispatcher.js";
14
14
  import { getHealthStatus, getRecentErrors } from "../../../util/watchdog.js";
15
- import { formatDuration } from "../helpers/index.js";
15
+ import { formatDuration } from "../../presentation/format.js";
16
16
 
17
17
  export async function replyStats(ctx: Context): Promise<void> {
18
18
  const h = getHealthStatus();
@@ -8,7 +8,7 @@ import { escapeHtml } from "../formatting.js";
8
8
  import { resetSession, getAllSessions } from "../../../storage/sessions.js";
9
9
  import { clearHistory } from "../../../storage/history.js";
10
10
  import { getChatSettings } from "../../../storage/chat-settings.js";
11
- import { formatModelLabel } from "../helpers/index.js";
11
+ import { formatModelLabel } from "../../presentation/format.js";
12
12
  import { replyHtmlChunked } from "./chunked-reply.js";
13
13
 
14
14
  /** `/admin chats` — every active session, newest first, titled via getChat. */
@@ -9,13 +9,13 @@ import {
9
9
  type EffortLevel,
10
10
  } from "../../../storage/chat-settings.js";
11
11
  import { getBackendIdForChat } from "../../../core/engine/backend-controller/index.js";
12
- import { renderEffortRows } from "../helpers/index.js";
12
+ import { renderEffortRows } from "../render/menu.js";
13
13
  import { resolveBackendForChat } from "../model-menu.js";
14
14
  import {
15
15
  displayReasoningEffort,
16
16
  getActiveReasoningLevels,
17
17
  supportsReasoningLevel,
18
- } from "../../shared/reasoning-levels.js";
18
+ } from "../../presentation/reasoning-levels.js";
19
19
  import { answerCallbackQuerySafe, type CallbackDeps } from "./query.js";
20
20
 
21
21
  export async function handleEffortCallback(
@@ -12,7 +12,7 @@ import {
12
12
  renderMetricsKeyboard,
13
13
  renderMetricsPanel,
14
14
  type MetricsView,
15
- } from "../helpers/index.js";
15
+ } from "../render/reports.js";
16
16
  import { isAuthorizedAdmin } from "../commands/state.js";
17
17
  import { answerCallbackQuerySafe, editOrIgnoreSame } from "./query.js";
18
18
 
@@ -14,7 +14,7 @@ import {
14
14
  rebindChat,
15
15
  releaseChat,
16
16
  } from "../../../../core/engine/backend-controller/index.js";
17
- import { resetPulseCheckpoint } from "../../../../core/background/pulse.js";
17
+ import { resetPulseCheckpoint } from "../../../../core/background/pulse/pulse.js";
18
18
  import { resolveActiveModelForChat } from "../../../../core/models/active-model.js";
19
19
  import { logWarn } from "../../../../util/log.js";
20
20
  import type { ModelCallback } from "../../model-callbacks.js";
@@ -11,7 +11,7 @@ import {
11
11
  renderModelBrowseKeyboard,
12
12
  renderBackendMenuKeyboard,
13
13
  renderBackendMenuText,
14
- } from "../../helpers/index.js";
14
+ } from "../../render/menu.js";
15
15
  import {
16
16
  buildModelMenuViewForChat,
17
17
  buildModelBrowseViewForChat,
@@ -8,7 +8,7 @@ import {
8
8
  disablePulse,
9
9
  enablePulse,
10
10
  isPulseEnabled,
11
- } from "../../../core/background/pulse.js";
11
+ } from "../../../core/background/pulse/pulse.js";
12
12
  import { answerCallbackQuerySafe } from "./query.js";
13
13
 
14
14
  export async function handlePulseCallback(
@@ -7,7 +7,7 @@ import type { Context } from "grammy";
7
7
  import type { TalonConfig } from "../../../core/config/index.js";
8
8
  import type { Backend } from "../../../core/agent-runtime/capabilities.js";
9
9
  import { logWarn } from "../../../util/log.js";
10
- import type { SettingsButton } from "../helpers/index.js";
10
+ import type { SettingsButton } from "../render/menu.js";
11
11
 
12
12
  export type CallbackDeps = {
13
13
  config: TalonConfig;
@@ -14,20 +14,20 @@ import {
14
14
  disablePulse,
15
15
  enablePulse,
16
16
  isPulseEnabled,
17
- } from "../../../core/background/pulse.js";
17
+ } from "../../../core/background/pulse/pulse.js";
18
18
  import { getBackendIdForChat } from "../../../core/engine/backend-controller/index.js";
19
19
  import {
20
20
  renderSettingsText,
21
21
  renderSettingsKeyboard,
22
22
  type SettingsButton,
23
- } from "../helpers/index.js";
23
+ } from "../render/menu.js";
24
24
  import { resolveBackendForChat } from "../model-menu.js";
25
25
  import { resolveActiveModelForChat } from "../../../core/models/active-model.js";
26
26
  import {
27
27
  displayReasoningEffort,
28
28
  getActiveReasoningLevels,
29
29
  supportsReasoningLevel,
30
- } from "../../shared/reasoning-levels.js";
30
+ } from "../../presentation/reasoning-levels.js";
31
31
  import { answerCallbackQuerySafe, type CallbackDeps } from "./query.js";
32
32
 
33
33
  export async function handleSettingsCallback(
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Admin + maintenance commands — /admin, /metrics, /doctor, /dream, /soul,
2
+ * Admin + maintenance commands — /admin, /metrics, /doctor, /dream,
3
3
  * /restart, /update — plus the unknown-command "did you mean…?" suggester.
4
4
  *
5
5
  * Most commands here gate on the configured admin user id via
@@ -8,24 +8,23 @@
8
8
 
9
9
  import type { Bot } from "grammy";
10
10
  import type { TalonConfig } from "../../../core/config/index.js";
11
- import { respawnSelf } from "../../../util/respawn.js";
11
+ import { respawnSelf } from "../../../core/daemon/respawn.js";
12
12
  import { isStaleCommand } from "../stale-command.js";
13
13
  import {
14
14
  getRepoRoot,
15
15
  runSelfUpdate,
16
16
  } from "../../../core/update/self-update.js";
17
- import { forceDream } from "../../../core/background/dream.js";
18
- import { getSoul } from "../../../core/soul/service.js";
17
+ import { forceDream } from "../../../core/background/dream/index.js";
19
18
  import { escapeHtml } from "../formatting.js";
20
19
  import { closestMatch } from "../../../native/strsim.js";
20
+ import { formatDuration } from "../../presentation/format.js";
21
21
  import {
22
- formatDuration,
23
22
  renderDoctorMessage,
24
23
  renderMetricsKeyboard,
25
24
  renderMetricsPanel,
26
25
  renderUsageMessage,
27
- } from "../helpers/index.js";
28
- import { collectPlanUsage } from "../../shared/plan-usage-report.js";
26
+ } from "../render/reports.js";
27
+ import { collectPlanUsage } from "../../presentation/plan-usage-report.js";
29
28
  import { collectDoctorReport } from "../../../core/doctor/index.js";
30
29
  import { handleAdminCommand } from "../admin.js";
31
30
  import { getTodayMetrics } from "../../../storage/metrics.js";
@@ -126,39 +125,6 @@ function registerDreamCommand(bot: Bot): void {
126
125
  });
127
126
  }
128
127
 
129
- // /soul — introspect the compiled identity (read-only). `/soul dream`
130
- // (admin) runs the organic maintenance pass. Inert when the soul is
131
- // disabled (TALON_SOUL_ENABLED), so it's safe to ship dormant.
132
- function registerSoulCommand(bot: Bot): void {
133
- bot.command("soul", async (ctx) => {
134
- const soul = getSoul();
135
- if (!soul.enabled) {
136
- await ctx.reply("Soul is disabled (set TALON_SOUL_ENABLED to enable).");
137
- return;
138
- }
139
- const arg = (ctx.match ?? "").trim().toLowerCase();
140
- if (arg === "dream") {
141
- if (!isAuthorizedAdmin(ctx)) {
142
- await ctx.reply("Not authorized.");
143
- return;
144
- }
145
- const sent = await ctx.reply("🧠 Soul dreaming...");
146
- void soul
147
- .dream()
148
- .then(() =>
149
- bot.api.editMessageText(
150
- ctx.chat.id,
151
- sent.message_id,
152
- "🧠 Soul dream complete.",
153
- ),
154
- )
155
- .catch(() => undefined);
156
- return;
157
- }
158
- await ctx.reply(soul.introspect());
159
- });
160
- }
161
-
162
128
  function registerRestartCommand(bot: Bot): void {
163
129
  bot.command("restart", async (ctx) => {
164
130
  if (!isAuthorizedAdmin(ctx)) {
@@ -275,7 +241,6 @@ export function registerAdminCommands(
275
241
  registerUsageCommand(bot, config);
276
242
  registerDoctorCommand(bot, config);
277
243
  registerDreamCommand(bot);
278
- registerSoulCommand(bot);
279
244
  registerRestartCommand(bot);
280
245
  const updateRepoRoot = config.devBuild ? getRepoRoot() : null;
281
246
  if (updateRepoRoot) registerUpdateCommand(bot, config, updateRepoRoot);
@@ -8,7 +8,7 @@
8
8
  * - `memory` — /memory (read-only view of the typed memory store)
9
9
  * - `session` — /reset /status
10
10
  * - `settings` — /model /effort /pulse /settings
11
- * - `admin` — /admin /metrics /doctor /dream /soul /restart /update
11
+ * - `admin` — /admin /metrics /doctor /dream /restart /update
12
12
  * + the unknown-command suggester
13
13
  *
14
14
  * `registerCommands` wires every group onto the bot in an order that ends
@@ -5,12 +5,12 @@
5
5
  import type { Bot, Context } from "grammy";
6
6
  import { isUserClientReady } from "../userbot.js";
7
7
  import { escapeHtml } from "../formatting.js";
8
+ import { formatDuration } from "../../presentation/format.js";
8
9
  import {
9
- formatDuration,
10
10
  renderMeshPairLink,
11
11
  renderMeshReport,
12
12
  type MeshReachability,
13
- } from "../helpers/index.js";
13
+ } from "../render/reports.js";
14
14
  import { isAuthorizedAdmin } from "./state.js";
15
15
  import { getLoadedPlugins } from "../../../core/plugin/index.js";
16
16
  import { getMeshService } from "../../../core/mesh/index.js";
@@ -2,7 +2,7 @@
2
2
  * Session commands — /reset and /status.
3
3
  *
4
4
  * Data gathering and the reset sequence live in
5
- * frontend/shared/session-status.ts; this file only renders Telegram HTML.
5
+ * frontend/presentation/session-status.ts; this file only renders Telegram HTML.
6
6
  */
7
7
 
8
8
  import type { Bot } from "grammy";
@@ -13,13 +13,17 @@ import {
13
13
  formatTokenCount,
14
14
  formatBytes,
15
15
  formatUsd,
16
- } from "../helpers/index.js";
16
+ } from "../../presentation/format.js";
17
17
  import { resolveBackendForChat } from "../model-menu.js";
18
18
  import { getBackendIdForChat } from "../../../core/engine/backend-controller/index.js";
19
19
  import {
20
20
  performSessionReset,
21
21
  collectSessionStatus,
22
- } from "../../shared/session-status.js";
22
+ } from "../../presentation/session-status.js";
23
+ import {
24
+ formatCacheTempLine,
25
+ formatDaemonLine,
26
+ } from "../../presentation/status-context.js";
23
27
  import { stopCurrentTurn } from "../../../core/engine/dispatcher.js";
24
28
  import type { RegisterDeps } from "./state.js";
25
29
 
@@ -74,6 +78,7 @@ export function registerSessionCommands(
74
78
  ` Read ${formatTokenCount(s.cache.read)}${s.cache.showsWrite ? ` Write ${formatTokenCount(s.cache.write)}` : ""}`,
75
79
  ]
76
80
  : []),
81
+ ...(s.cacheTemp ? [` ${formatCacheTempLine(s.cacheTemp)}`] : []),
77
82
  ` Input ${formatTokenCount(s.inputTokens)} Output ${formatTokenCount(s.outputTokens)}${s.costUsd > 0 ? ` Cost ${formatUsd(s.costUsd)}` : ""}`,
78
83
  "",
79
84
  ...(s.plan
@@ -91,6 +96,7 @@ export function registerSessionCommands(
91
96
  `<b>Session</b> ${s.sessionName ? `"${escapeHtml(s.sessionName)}" ` : ""}${s.sessionId ? "<code>" + escapeHtml(s.sessionId.slice(0, 8)) + "...</code>" : "<i>(new)</i>"} · ${s.sessionAge} old`,
92
97
  `<b>Uptime</b> ${s.uptime} · ${s.activeSessionCount} active session${s.activeSessionCount === 1 ? "" : "s"}`,
93
98
  `<b>Runtime</b> ${escapeHtml(s.runtime)} · ${formatBytes(s.rssBytes)} RSS`,
99
+ escapeHtml(formatDaemonLine(s.daemon)),
94
100
  ];
95
101
  await ctx.reply(lines.join("\n"), { parse_mode: "HTML" });
96
102
  });
@@ -17,19 +17,21 @@ import {
17
17
  disablePulse,
18
18
  enablePulse,
19
19
  isPulseEnabled,
20
- } from "../../../core/background/pulse.js";
20
+ } from "../../../core/background/pulse/pulse.js";
21
21
  import { escapeHtml } from "../formatting.js";
22
22
  import {
23
23
  formatModelLabel,
24
24
  formatDuration,
25
25
  parseInterval,
26
+ } from "../../presentation/format.js";
27
+ import {
26
28
  renderSettingsText,
27
29
  renderSettingsKeyboard,
28
30
  renderEffortRows,
29
31
  renderModelMenuText,
30
32
  renderModelMenuKeyboard,
31
33
  type SettingsButton,
32
- } from "../helpers/index.js";
34
+ } from "../render/menu.js";
33
35
  import {
34
36
  buildModelMenuViewForChat,
35
37
  resolveBackendForChat,
@@ -40,7 +42,7 @@ import {
40
42
  displayReasoningEffort,
41
43
  getActiveReasoningLevels,
42
44
  supportsReasoningLevel,
43
- } from "../../shared/reasoning-levels.js";
45
+ } from "../../presentation/reasoning-levels.js";
44
46
  import type { RegisterDeps } from "./state.js";
45
47
 
46
48
  /** `/model <query>` — resolve the query against the per-chat backend and
@@ -13,7 +13,6 @@ import { setMessageFilePath } from "../../../storage/history.js";
13
13
  import { addMedia } from "../../../storage/media-index.js";
14
14
  import { appendDailyLog } from "../../../storage/daily-log.js";
15
15
  import { logError } from "../../../util/log.js";
16
- import { recordMessageSignal } from "../../../core/soul/taps.js";
17
16
  import {
18
17
  getSenderName,
19
18
  getReplyContext,
@@ -178,15 +177,6 @@ export async function handleTextMessage(
178
177
  );
179
178
  const prompt = fwdCtx + replyCtx + replyPhotoCtx + (ctx.message.text ?? "");
180
179
 
181
- // Soul tap: a message reaching here is addressed to Talon (DM, mention, or
182
- // reply). If it reads as a standing directive or a correction, record it as
183
- // evidence. No-op unless the soul is enabled.
184
- recordMessageSignal({
185
- text: ctx.message.text ?? "",
186
- actor: sender,
187
- addressedToBot: true,
188
- });
189
-
190
180
  enqueueMessage(bot, config, chatId, ctx.chat.id, {
191
181
  prompt,
192
182
  replyToId: ctx.message.message_id,
@@ -13,7 +13,7 @@ import {
13
13
  createDmUserTracker,
14
14
  createNoticeCooldown,
15
15
  createRateLimiter,
16
- } from "../../shared/access.js";
16
+ } from "../../presentation/access.js";
17
17
 
18
18
  // ── First-time DM user tracking ──────────────────────────────────────────────
19
19
 
@@ -10,7 +10,6 @@ import { Bot, InputFile, API_CONSTANTS } from "grammy";
10
10
  import { autoRetry } from "@grammyjs/auto-retry";
11
11
  import { apiThrottler } from "@grammyjs/transformer-throttler";
12
12
  import type { TalonConfig } from "../../core/config/index.js";
13
- import { soulEnabled } from "../../core/soul/settings.js";
14
13
  import type { ContextManager } from "../../core/types.js";
15
14
  import type { Gateway } from "../../core/engine/gateway.js";
16
15
  import { createTelegramActionHandler, sendText } from "./actions/index.js";
@@ -127,13 +126,10 @@ export function createTelegramFrontend(
127
126
  });
128
127
  // Beyond grammY's defaults: `chat_join_request` feeds the moderation
129
128
  // tool's pending-join cache (inert unless the bot admins an
130
- // approval-gated chat), and `message_reaction` feeds the soul's
131
- // reaction tap — subscribed only when the soul is enabled, so a
132
- // soulless deployment's reaction behaviour is unchanged.
129
+ // approval-gated chat).
133
130
  const allowedUpdates = [
134
131
  ...API_CONSTANTS.DEFAULT_UPDATE_TYPES,
135
132
  "chat_join_request" as const,
136
- ...(soulEnabled(config.soul) ? ["message_reaction" as const] : []),
137
133
  ];
138
134
  await bot.start({
139
135
  allowed_updates: allowedUpdates,
@@ -12,13 +12,12 @@ import type { TalonConfig } from "../../core/config/index.js";
12
12
  import { pushMessage } from "../../storage/history.js";
13
13
  import type { HistoryMessage } from "../../storage/repositories/history-repo.js";
14
14
  import { allowChat, revokeChat } from "./userbot.js";
15
- import { registerChat } from "../../core/background/pulse.js";
15
+ import { registerChat } from "../../core/background/pulse/pulse.js";
16
16
  import { log } from "../../util/log.js";
17
17
  import { getSenderName } from "./handlers/index.js";
18
18
  import { noteUpdateId } from "./update-offset.js";
19
19
  import { noteInboundThread } from "./topics.js";
20
20
  import { recordJoinRequest } from "./join-requests.js";
21
- import { newlyAddedEmojis, recordReactionToBot } from "../../core/soul/taps.js";
22
21
  import {
23
22
  handleTextMessage,
24
23
  handlePhotoMessage,
@@ -147,18 +146,6 @@ function captureHistory(
147
146
  return next();
148
147
  }
149
148
 
150
- // ── Reaction tap — feed reactions on Talon's own messages to the soul ────
151
- // Telegram only delivers `message_reaction` updates when subscribed via
152
- // allowed_updates (see index.ts) and, in groups, when the bot is an admin.
153
- // The handler is inert unless the soul is enabled.
154
- function tapReaction(ctx: Filter<Context, "message_reaction">): void {
155
- const mr = ctx.messageReaction;
156
- // Ignore the bot reacting to messages itself.
157
- if (mr.user?.id === ctx.me.id) return;
158
- const added = newlyAddedEmojis(mr.old_reaction, mr.new_reaction);
159
- recordReactionToBot(mr.chat.id, mr.message_id, added);
160
- }
161
-
162
149
  // ── Join requests — cache for moderate(op="list_join_requests") ─────────
163
150
  // Delivered only when subscribed via allowed_updates (see index.ts) and
164
151
  // the bot admins a chat whose invite link requires approval. Stored, not
@@ -208,7 +195,6 @@ function registerMessageHandlers(bot: Bot, config: TalonConfig): void {
208
195
  export function registerMiddleware(bot: Bot, config: TalonConfig): void {
209
196
  bot.use(trackUpdateOffset);
210
197
  bot.on("message", captureHistory);
211
- bot.on("message_reaction", tapReaction);
212
198
  bot.on("chat_join_request", cacheJoinRequest);
213
199
  bot.on("my_chat_member", revokeOnRemoval);
214
200
  registerMessageHandlers(bot, config);
@@ -32,7 +32,7 @@ import {
32
32
  buildModelMenuState,
33
33
  type ModelMenuState,
34
34
  type SettingsButton,
35
- } from "./helpers/index.js";
35
+ } from "./render/menu.js";
36
36
  import {
37
37
  hasBackendPool,
38
38
  getBackendIdForChat,
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Telegram's dialect of the shared chat reports.
3
+ *
4
+ * `frontend/presentation/reports.ts` writes each report once and takes
5
+ * the markup spelling from a `ReportFormatter`; this is Telegram's.
6
+ * `escape` is `escapeHtml` because sends use `parse_mode: HTML`, so a
7
+ * literal `<` in a device name, a model id, a metric key or a backend
8
+ * note would be read as a tag and 400 the entire message.
9
+ *
10
+ * Its own module so both `reports.ts` (the report wrappers) and
11
+ * `menu.ts` (the settings body) can read it without a cycle.
12
+ */
13
+
14
+ import { escapeHtml } from "../formatting.js";
15
+ import type { ReportFormatter } from "../../presentation/reports.js";
16
+
17
+ /** Telegram hard-caps a message at 4096 characters; this leaves headroom. */
18
+ export const TELEGRAM_MESSAGE_MAX = 3800;
19
+
20
+ export const TELEGRAM_REPORTS: ReportFormatter = {
21
+ bold: (s) => `<b>${s}</b>`,
22
+ italic: (s) => `<i>${s}</i>`,
23
+ // Telegram has one italic tag; Discord spells this one `*…*`.
24
+ emphasis: (s) => `<i>${s}</i>`,
25
+ code: (s) => `<code>${s}</code>`,
26
+ escape: escapeHtml,
27
+ lineLimit: TELEGRAM_MESSAGE_MAX,
28
+ metricLabelMax: 80,
29
+ pulseLabel: "Pulse:",
30
+ };