talon-agent 4.6.1 → 5.0.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 (223) 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 +19 -4
  6. package/src/backend/claude-sdk/handler.ts +28 -7
  7. package/src/backend/claude-sdk/one-shot.ts +1 -1
  8. package/src/backend/claude-sdk/options.ts +7 -6
  9. package/src/backend/claude-sdk/stream.ts +30 -3
  10. package/src/backend/claude-sdk/warm.ts +1 -1
  11. package/src/backend/codex/constants.ts +1 -1
  12. package/src/backend/codex/factory.ts +2 -2
  13. package/src/backend/codex/handler/events.ts +1 -1
  14. package/src/backend/codex/handler/message.ts +26 -12
  15. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  16. package/src/backend/codex/init.ts +1 -1
  17. package/src/backend/codex/mcp-config.ts +1 -1
  18. package/src/backend/codex/one-shot.ts +1 -1
  19. package/src/backend/kilo/handler/message.ts +4 -1
  20. package/src/backend/openai-agents/constants.ts +1 -1
  21. package/src/backend/openai-agents/factory.ts +2 -2
  22. package/src/backend/openai-agents/handler/events.ts +1 -1
  23. package/src/backend/openai-agents/handler/message.ts +8 -4
  24. package/src/backend/openai-agents/init.ts +1 -1
  25. package/src/backend/openai-agents/mcp-pool.ts +1 -1
  26. package/src/backend/opencode/handler/message.ts +4 -1
  27. package/src/backend/remote-server/chat-turn.ts +31 -23
  28. package/src/backend/remote-server/events.ts +3 -3
  29. package/src/backend/remote-server/factory.ts +6 -3
  30. package/src/backend/remote-server/index.ts +1 -1
  31. package/src/backend/remote-server/mcp.ts +1 -1
  32. package/src/backend/remote-server/one-shot.ts +1 -1
  33. package/src/backend/remote-server/server-bindings.ts +1 -1
  34. package/src/backend/remote-server/turn.ts +1 -1
  35. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  36. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  37. package/src/backend/{shared → runtime}/index.ts +32 -23
  38. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  39. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  40. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  41. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  42. package/src/backend/{shared → runtime/turn}/delivery.ts +2 -2
  43. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  44. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  45. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  46. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  47. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  48. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  51. package/src/bootstrap.ts +4 -19
  52. package/src/cli.ts +1 -1
  53. package/src/core/agent-runtime/capabilities.ts +11 -0
  54. package/src/core/agent-runtime/contract-tests.ts +92 -1
  55. package/src/core/agent-runtime/events.ts +1 -1
  56. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  57. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  58. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  59. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  60. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  61. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  62. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  63. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  64. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  65. package/src/core/background/triggers/command.ts +1 -1
  66. package/src/core/config/index.ts +15 -11
  67. package/src/core/daemon/resource-sampler.ts +121 -0
  68. package/src/core/engine/dispatcher.ts +16 -0
  69. package/src/core/engine/gateway-actions/cron.ts +2 -2
  70. package/src/core/engine/gateway-actions/index.ts +3 -0
  71. package/src/core/engine/gateway-actions/memory.ts +335 -0
  72. package/src/core/engine/gateway.ts +0 -6
  73. package/src/core/memory/import.ts +3 -2
  74. package/src/core/memory/taps.ts +199 -0
  75. package/src/core/memory/turn-retrieval.ts +222 -0
  76. package/src/core/prompt/assemble.ts +2 -12
  77. package/src/core/prompt/index.ts +2 -2
  78. package/src/core/prompt/invalidation.ts +1 -1
  79. package/src/core/tasks/index.ts +1 -1
  80. package/src/core/tools/index.ts +2 -0
  81. package/src/core/tools/memory.ts +128 -0
  82. package/src/core/tools/types.ts +1 -0
  83. package/src/core/weaver/turn-cpu.ts +32 -0
  84. package/src/core/weaver/weaver.ts +36 -0
  85. package/src/frontend/discord/admin.ts +1 -1
  86. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  87. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  88. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  89. package/src/frontend/discord/callbacks/modals.ts +4 -1
  90. package/src/frontend/discord/commands/admin.ts +1 -35
  91. package/src/frontend/discord/commands/definitions.ts +0 -11
  92. package/src/frontend/discord/commands/router.ts +0 -3
  93. package/src/frontend/discord/commands/session.ts +6 -0
  94. package/src/frontend/discord/commands/settings.ts +1 -1
  95. package/src/frontend/discord/middleware.ts +1 -25
  96. package/src/frontend/discord/runtime.ts +1 -2
  97. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  98. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  99. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  100. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  101. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  102. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  103. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  104. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  105. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  106. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  107. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  108. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  109. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  110. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  111. package/src/frontend/native/index.ts +13 -10
  112. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  113. package/src/frontend/native/runtime.ts +1 -1
  114. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  115. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  116. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  117. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  118. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  119. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  120. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  121. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  122. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  123. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  124. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  125. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  126. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  127. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  128. package/src/frontend/shared/model-commands.ts +1 -1
  129. package/src/frontend/shared/session-status.ts +35 -8
  130. package/src/frontend/shared/status-context.ts +101 -2
  131. package/src/frontend/telegram/admin/background.ts +1 -1
  132. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  133. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  134. package/src/frontend/telegram/callbacks/settings.ts +1 -1
  135. package/src/frontend/telegram/commands/admin.ts +2 -37
  136. package/src/frontend/telegram/commands/index.ts +1 -1
  137. package/src/frontend/telegram/commands/session.ts +6 -0
  138. package/src/frontend/telegram/commands/settings.ts +1 -1
  139. package/src/frontend/telegram/handlers/messages.ts +0 -10
  140. package/src/frontend/telegram/index.ts +1 -5
  141. package/src/frontend/telegram/middleware.ts +1 -15
  142. package/src/frontend/whatsapp/access.ts +2 -2
  143. package/src/frontend/whatsapp/actions/history.ts +2 -2
  144. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  145. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  146. package/src/frontend/whatsapp/actions/send.ts +1 -1
  147. package/src/frontend/whatsapp/commands.ts +8 -2
  148. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  149. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  150. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  151. package/src/frontend/whatsapp/index.ts +4 -4
  152. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  153. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  154. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  155. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  156. package/src/frontend/whatsapp/registry.ts +1 -1
  157. package/src/frontend/whatsapp/runtime.ts +1 -1
  158. package/src/index.ts +1 -1
  159. package/src/storage/db.ts +6 -1
  160. package/src/storage/memory.ts +59 -8
  161. package/src/storage/metrics.ts +38 -0
  162. package/src/storage/repositories/sessions-repo.ts +6 -0
  163. package/src/storage/session-record.ts +13 -2
  164. package/src/storage/sessions.ts +12 -0
  165. package/src/storage/sql/db.sql +5 -0
  166. package/src/storage/sql/schema.sql +4 -0
  167. package/src/storage/sql/sessions.sql +3 -3
  168. package/src/storage/sql/statements.generated.ts +10 -3
  169. package/src/storage/sql/turn-meta.sql +1 -1
  170. package/src/util/boot-timer.ts +15 -1
  171. package/src/util/chat-id.ts +30 -0
  172. package/src/util/log.ts +1 -1
  173. package/src/util/paths.ts +0 -2
  174. package/src/core/soul/README.md +0 -110
  175. package/src/core/soul/RESEARCH.md +0 -98
  176. package/src/core/soul/associative.ts +0 -98
  177. package/src/core/soul/centrality.ts +0 -98
  178. package/src/core/soul/cluster.ts +0 -83
  179. package/src/core/soul/compiler.ts +0 -207
  180. package/src/core/soul/consolidate.ts +0 -179
  181. package/src/core/soul/critic.ts +0 -162
  182. package/src/core/soul/dag.ts +0 -265
  183. package/src/core/soul/delta.ts +0 -123
  184. package/src/core/soul/drift.ts +0 -99
  185. package/src/core/soul/embedder.ts +0 -129
  186. package/src/core/soul/emergent-critic.ts +0 -96
  187. package/src/core/soul/forgetting.ts +0 -131
  188. package/src/core/soul/governance.ts +0 -93
  189. package/src/core/soul/hash.ts +0 -97
  190. package/src/core/soul/hdc.ts +0 -154
  191. package/src/core/soul/kernel.ts +0 -540
  192. package/src/core/soul/lattice.ts +0 -103
  193. package/src/core/soul/lens.ts +0 -110
  194. package/src/core/soul/projector.ts +0 -240
  195. package/src/core/soul/reflect.ts +0 -170
  196. package/src/core/soul/reflex.ts +0 -164
  197. package/src/core/soul/retrieve.ts +0 -146
  198. package/src/core/soul/salience.ts +0 -146
  199. package/src/core/soul/service.ts +0 -204
  200. package/src/core/soul/settings.ts +0 -47
  201. package/src/core/soul/signals.ts +0 -117
  202. package/src/core/soul/talon-embedder.ts +0 -80
  203. package/src/core/soul/taps.ts +0 -199
  204. package/src/core/soul/types.ts +0 -298
  205. package/src/core/soul/valence.ts +0 -83
  206. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  207. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  208. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  209. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  210. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  211. /package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +0 -0
  212. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  213. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  214. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  215. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  216. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  217. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  218. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  219. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  220. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  221. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  222. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  223. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
@@ -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
@@ -14,8 +14,7 @@ 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";
21
20
  import {
@@ -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
@@ -20,6 +20,10 @@ import {
20
20
  performSessionReset,
21
21
  collectSessionStatus,
22
22
  } from "../../shared/session-status.js";
23
+ import {
24
+ formatCacheTempLine,
25
+ formatDaemonLine,
26
+ } from "../../shared/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,7 +17,7 @@ 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,
@@ -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,
@@ -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);
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Access gates — the allow-lists are the entire permission model. DMs
3
- * match `allowedJids` (see identity.ts); groups go through `groupPolicy`
3
+ * match `allowedJids` (see connection/identity.ts); groups go through `groupPolicy`
4
4
  * and, in mention mode, must address the bot.
5
5
  */
6
6
 
7
7
  import type { WAMessage } from "baileys";
8
8
  import { logWarn } from "../../util/log.js";
9
- import { bareId } from "./identity.js";
9
+ import { bareId } from "./connection/identity.js";
10
10
  import type { WhatsAppRuntime } from "./runtime.js";
11
11
 
12
12
  /**
@@ -17,8 +17,8 @@ import {
17
17
  getMessageById,
18
18
  setMessageFilePath,
19
19
  } from "../../../storage/history.js";
20
- import { saveInboundMedia } from "../media-store.js";
21
- import { lookupMessage, resolveKey } from "../message-store.js";
20
+ import { saveInboundMedia } from "../messages/media-store.js";
21
+ import { lookupMessage, resolveKey } from "../messages/message-store.js";
22
22
  import { tryAction } from "./try-action.js";
23
23
  import type { WhatsAppActionHandlers } from "./types.js";
24
24
 
@@ -11,8 +11,8 @@ import { randomUUID } from "node:crypto";
11
11
  import { proto } from "baileys";
12
12
  import { log } from "../../../util/log.js";
13
13
  import { toWhatsAppText } from "../formatting.js";
14
- import { lookupMessage, resolveKey } from "../message-store.js";
15
- import { recordPin, listPins, forgetPin } from "../pins.js";
14
+ import { lookupMessage, resolveKey } from "../messages/message-store.js";
15
+ import { recordPin, listPins, forgetPin } from "../messages/pins.js";
16
16
  import { tryAction } from "./try-action.js";
17
17
  import { resolveQuoted, sendContent, sendText, boundedSend } from "./send.js";
18
18
  import type { WhatsAppActionHandlers } from "./types.js";
@@ -194,7 +194,7 @@ export const moderationHandlers: WhatsAppActionHandlers = {
194
194
 
195
195
  case "unpin_all": {
196
196
  // WhatsApp unpins individually; clear the ones Talon placed.
197
- const { listPins } = await import("../pins.js");
197
+ const { listPins } = await import("../messages/pins.js");
198
198
  const pins = listPins(chat.chatId);
199
199
  return {
200
200
  ok: true,
@@ -8,7 +8,7 @@ import type { AnyMessageContent, WAMessage, WASocket } from "baileys";
8
8
  import { log } from "../../../util/log.js";
9
9
  import type { ActionResult } from "../../../core/types.js";
10
10
  import { toWhatsAppChunks } from "../formatting.js";
11
- import { lookupMessage, rememberMessage } from "../message-store.js";
11
+ import { lookupMessage, rememberMessage } from "../messages/message-store.js";
12
12
  import { pushMessage, type HistoryMessage } from "../../../storage/history.js";
13
13
  import type { WhatsAppChatInfo } from "../registry.js";
14
14
 
@@ -15,7 +15,7 @@
15
15
  import { log } from "../../util/log.js";
16
16
  import { recordMessageProcessed } from "../../util/watchdog.js";
17
17
  import { getChatSettings } from "../../storage/chat-settings.js";
18
- import { isPulseEnabled } from "../../core/background/pulse.js";
18
+ import { isPulseEnabled } from "../../core/background/pulse/pulse.js";
19
19
  import {
20
20
  DEFAULT_PULSE_INTERVAL_MS,
21
21
  formatBytes,
@@ -29,6 +29,10 @@ import {
29
29
  performSessionReset,
30
30
  type SessionStatusData,
31
31
  } from "../shared/session-status.js";
32
+ import {
33
+ formatCacheTempLine,
34
+ formatDaemonLine,
35
+ } from "../shared/status-context.js";
32
36
  import {
33
37
  describeChatEffort,
34
38
  describeChatModels,
@@ -43,7 +47,7 @@ import {
43
47
  type ModelOverview,
44
48
  } from "../shared/model-commands.js";
45
49
  import { sendText } from "./actions/send.js";
46
- import { identityAllowed, type Identity } from "./identity.js";
50
+ import { identityAllowed, type Identity } from "./connection/identity.js";
47
51
  import type { WhatsAppChatInfo } from "./registry.js";
48
52
  import type { WhatsAppRuntime } from "./runtime.js";
49
53
 
@@ -199,6 +203,7 @@ function renderStatus(s: SessionStatusData): string {
199
203
  `Cache: ${s.cache.hitPct}% hit · read ${formatTokenCount(s.cache.read)}${s.cache.showsWrite ? ` · write ${formatTokenCount(s.cache.write)}` : ""}`,
200
204
  );
201
205
  }
206
+ if (s.cacheTemp) lines.push(formatCacheTempLine(s.cacheTemp));
202
207
  if (s.plan) {
203
208
  lines.push(
204
209
  "",
@@ -216,6 +221,7 @@ function renderStatus(s: SessionStatusData): string {
216
221
  `**Session** ${s.sessionName ? `"${s.sessionName}" ` : ""}${s.sessionId ? `\`${s.sessionId.slice(0, 8)}…\`` : "(new)"} · ${s.sessionAge} old`,
217
222
  `**Uptime** ${s.uptime} · ${s.activeSessionCount} active session${s.activeSessionCount === 1 ? "" : "s"}`,
218
223
  `**Runtime** ${s.runtime} · ${formatBytes(s.rssBytes)} RSS`,
224
+ formatDaemonLine(s.daemon),
219
225
  );
220
226
  return lines.join("\n");
221
227
  }
@@ -9,19 +9,19 @@ import { readFile } from "node:fs/promises";
9
9
  import { resolve as resolvePath } from "node:path";
10
10
  import makeWASocket, { type AuthenticationState, type WASocket } from "baileys";
11
11
  import qrcode from "qrcode-terminal";
12
- import { log, logError, logWarn } from "../../util/log.js";
13
- import { dirs } from "../../util/paths.js";
14
- import { notifyAdmin } from "../../core/frontend-runtime/admin-notify.js";
12
+ import { log, logError, logWarn } from "../../../util/log.js";
13
+ import { dirs } from "../../../util/paths.js";
14
+ import { notifyAdmin } from "../../../core/frontend-runtime/admin-notify.js";
15
15
  import { useAtomicAuthState } from "./auth-state.js";
16
16
  import { bareId } from "./identity.js";
17
- import { handleInbound } from "./inbound.js";
17
+ import { handleInbound } from "../messages/inbound.js";
18
18
  import { classifyClose, isPaired, REPLACED_BACKOFF_MS } from "./pairing.js";
19
19
  import { isManualPairingActive, onPairingComplete } from "./pairing-lock.js";
20
20
  import {
21
21
  RECONNECT_BASE_MS,
22
22
  RECONNECT_MAX_MS,
23
23
  type WhatsAppRuntime,
24
- } from "./runtime.js";
24
+ } from "../runtime.js";
25
25
  import { makeWaLogger } from "./wa-logger.js";
26
26
 
27
27
  /** What the connection loop should do after a socket ends. */
@@ -16,9 +16,9 @@ import QRCode from "qrcode";
16
16
  import type {
17
17
  PairingAttempt,
18
18
  PairingOutcome,
19
- } from "../../core/frontend-runtime/pairing-broker.js";
20
- import { log, logError } from "../../util/log.js";
21
- import { dirs } from "../../util/paths.js";
19
+ } from "../../../core/frontend-runtime/pairing-broker.js";
20
+ import { log, logError } from "../../../util/log.js";
21
+ import { dirs } from "../../../util/paths.js";
22
22
  import { useAtomicAuthState, flushAuthWrites } from "./auth-state.js";
23
23
  import { classifyClose } from "./pairing.js";
24
24
  import { acquireManualPairing, releaseManualPairing } from "./pairing-lock.js";
@@ -3,11 +3,11 @@
3
3
  * that already has structured logging. Bridge warn+ into Talon's log and
4
4
  * drop the rest. The shape is pino's minimal logger contract.
5
5
  *
6
- * Shared by the frontend socket (index.ts) and the manual pairing socket
6
+ * Shared by the frontend socket (../index.ts) and the manual pairing socket
7
7
  * (pairing-service.ts).
8
8
  */
9
9
 
10
- import { logError, logWarn } from "../../util/log.js";
10
+ import { logError, logWarn } from "../../../util/log.js";
11
11
 
12
12
  export function makeWaLogger(): never {
13
13
  const fmt = (args: unknown[]): string =>
@@ -26,15 +26,15 @@ import { log, logError, logWarn } from "../../util/log.js";
26
26
  import { maxMsgIdForChatPrefix } from "../../storage/history.js";
27
27
  import { createWhatsAppActionHandler } from "./actions/index.js";
28
28
  import { sendText, setWhatsAppBotName } from "./actions/send.js";
29
- import { seedMessageStore } from "./message-store.js";
29
+ import { seedMessageStore } from "./messages/message-store.js";
30
30
  import {
31
31
  maxWhatsAppMsgId,
32
32
  pruneWhatsAppMessages,
33
33
  } from "../../storage/whatsapp-messages.js";
34
- import { flushAuthWrites } from "./auth-state.js";
34
+ import { flushAuthWrites } from "./connection/auth-state.js";
35
35
  import { registerPairingProvider } from "../../core/frontend-runtime/pairing-broker.js";
36
- import { beginPairingAttempt } from "./pairing-service.js";
37
- import { runConnectionLoop } from "./connection.js";
36
+ import { beginPairingAttempt } from "./connection/pairing-service.js";
37
+ import { runConnectionLoop } from "./connection/connection.js";
38
38
  import { lookupWhatsAppChat, type WhatsAppChatInfo } from "./registry.js";
39
39
  import { createWhatsAppRuntime } from "./runtime.js";
40
40
 
@@ -1,35 +1,35 @@
1
1
  /**
2
2
  * One inbound message, from `messages.upsert` to the model's turn:
3
3
  * access gates → media saved to the workspace → history recorded →
4
- * slash commands (commands.ts) → catch-up policy → `execute()`.
4
+ * slash commands (../commands.ts) → catch-up policy → `execute()`.
5
5
  */
6
6
 
7
7
  import { isJidGroup, type WAMessage } from "baileys";
8
- import type { AgentEvent } from "../../core/agent-runtime/events.js";
9
- import { log, logError } from "../../util/log.js";
10
- import { execute } from "../../core/engine/dispatcher.js";
11
- import { toolInputToRecord } from "../../core/agent-runtime/events.js";
12
- import { appendDailyLog } from "../../storage/daily-log.js";
13
- import { pushMessage } from "../../storage/history.js";
14
- import { relayInbound } from "../../core/engine/cross-chat-relay.js";
8
+ import type { AgentEvent } from "../../../core/agent-runtime/events.js";
9
+ import { log, logError } from "../../../util/log.js";
10
+ import { execute } from "../../../core/engine/dispatcher.js";
11
+ import { toolInputToRecord } from "../../../core/agent-runtime/events.js";
12
+ import { appendDailyLog } from "../../../storage/daily-log.js";
13
+ import { pushMessage } from "../../../storage/history.js";
14
+ import { relayInbound } from "../../../core/engine/cross-chat-relay.js";
15
15
  import {
16
16
  recordMessageProcessed,
17
17
  recordMessageReceived,
18
- } from "../../util/watchdog.js";
19
- import { isAddressedToSelf, isGroupAllowed } from "./access.js";
20
- import { handleWhatsAppCommand } from "./commands.js";
21
- import { sendText } from "./actions/send.js";
18
+ } from "../../../util/watchdog.js";
19
+ import { isAddressedToSelf, isGroupAllowed } from "../access.js";
20
+ import { handleWhatsAppCommand } from "../commands.js";
21
+ import { sendText } from "../actions/send.js";
22
22
  import {
23
23
  bareId,
24
24
  canonicalId,
25
25
  identityAllowed,
26
26
  resolveIdentity,
27
27
  type Identity,
28
- } from "./identity.js";
28
+ } from "../connection/identity.js";
29
29
  import { saveInboundMedia, type SavedMedia } from "./media-store.js";
30
30
  import { lookupByWaId, rememberMessage } from "./message-store.js";
31
- import { registerWhatsAppChat, type WhatsAppChatInfo } from "./registry.js";
32
- import type { WhatsAppRuntime } from "./runtime.js";
31
+ import { registerWhatsAppChat, type WhatsAppChatInfo } from "../registry.js";
32
+ import type { WhatsAppRuntime } from "../runtime.js";
33
33
  import { runTurnWithRecovery, shouldReplyToCatchUp } from "./turn-recovery.js";
34
34
 
35
35
  export type InboundOptions = { catchUp?: boolean };
@@ -11,10 +11,10 @@
11
11
  import { mkdirSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { downloadMediaMessage, type WAMessage } from "baileys";
14
- import { makeWaLogger } from "./wa-logger.js";
15
- import { addMedia } from "../../storage/media-index.js";
16
- import { logWarn } from "../../util/log.js";
17
- import { dirs } from "../../util/paths.js";
14
+ import { makeWaLogger } from "../connection/wa-logger.js";
15
+ import { addMedia } from "../../../storage/media-index.js";
16
+ import { logWarn } from "../../../util/log.js";
17
+ import { dirs } from "../../../util/paths.js";
18
18
 
19
19
  /** Media kinds Talon's index understands, keyed by WhatsApp message field. */
20
20
  const MEDIA_KINDS = [
@@ -24,7 +24,7 @@ import {
24
24
  saveWhatsAppMessage,
25
25
  clearWhatsAppMessages,
26
26
  type WhatsAppMessageRecord,
27
- } from "../../storage/whatsapp-messages.js";
27
+ } from "../../../storage/whatsapp-messages.js";
28
28
 
29
29
  export type StoredMessage = {
30
30
  /** Talon-facing numeric id. */
@@ -6,7 +6,7 @@
6
6
  * the log — the WhatsApp user's message was marked read and then nothing
7
7
  * ever came back, which reads as being ignored.
8
8
  *
9
- * Split from index.ts so the policy is unit-testable without a Baileys
9
+ * Split from ../index.ts so the policy is unit-testable without a Baileys
10
10
  * socket: the caller injects the turn and the delivery.
11
11
  */
12
12
 
@@ -14,13 +14,13 @@ import {
14
14
  classify,
15
15
  friendlyMessage,
16
16
  RETRY_ELAPSED_CAP_MS,
17
- } from "../../core/errors.js";
18
- import { log, logError } from "../../util/log.js";
17
+ } from "../../../core/errors.js";
18
+ import { log, logError } from "../../../util/log.js";
19
19
  import {
20
20
  recordError,
21
21
  recordMessageProcessed,
22
22
  recordMessageSettled,
23
- } from "../../util/watchdog.js";
23
+ } from "../../../util/watchdog.js";
24
24
 
25
25
  export type TurnRecoveryDeps = {
26
26
  chatId: string;
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { isJidGroup, jidNormalizedUser } from "baileys";
12
12
  import { deriveNumericChatId } from "../../util/chat-id.js";
13
- import { bareId } from "./identity.js";
13
+ import { bareId } from "./connection/identity.js";
14
14
 
15
15
  export type WhatsAppChatInfo = {
16
16
  /** Talon chat id: wa_dm_<number> or wa_group_<id>. */
@@ -11,7 +11,7 @@
11
11
  import type { WASocket } from "baileys";
12
12
  import type { TalonConfig } from "../../core/config/index.js";
13
13
  import type { Gateway } from "../../core/engine/gateway.js";
14
- import { bareId } from "./identity.js";
14
+ import { bareId } from "./connection/identity.js";
15
15
 
16
16
  type WhatsAppSettings = {
17
17
  allowedJids: string[];
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { MCP_LAUNCH_SUBCOMMAND, runSupervisor } from "./util/mcp-launcher.js";
12
- import { LUA_RUN_SUBCOMMAND, runLuaMain } from "./core/scripting/lua-runner.js";
12
+ import { LUA_RUN_SUBCOMMAND, runLuaMain } from "./core/scripts/lua.js";
13
13
 
14
14
  if (process.argv[2] === MCP_LAUNCH_SUBCOMMAND) {
15
15
  await runSupervisor(process.argv.slice(3));
package/src/storage/db.ts CHANGED
@@ -69,7 +69,7 @@ let db: SqlDatabase | null = null;
69
69
  * Column reconciliation runs first: `ALTER TABLE … ADD COLUMN` has no
70
70
  * IF NOT EXISTS form, so columns added to already-shipped tables
71
71
  * (media_index.content_hash, sessions.metrics,
72
- * history_messages.attachments) are ensured by attempting the ALTER and
72
+ * history_messages.attachments, sessions.last_turn_ended_at) are ensured by attempting the ALTER and
73
73
  * swallowing the two expected failures — "duplicate column name"
74
74
  * (column already there) and "no such table" (fresh database; the
75
75
  * CREATE TABLE in schema.sql includes the column).
@@ -100,6 +100,11 @@ function ensureSchema(database: SqlDatabase): void {
100
100
  } catch {
101
101
  /* duplicate column or no such table — both mean nothing to do */
102
102
  }
103
+ try {
104
+ database.exec(dbSql.addSessionsLastTurnEndedAtColumn);
105
+ } catch {
106
+ /* duplicate column or no such table — both mean nothing to do */
107
+ }
103
108
  database.exec("BEGIN");
104
109
  try {
105
110
  database.exec(SCHEMA);
@@ -206,6 +206,25 @@ function similarityQuery(text: string): string {
206
206
  .join(" OR ");
207
207
  }
208
208
 
209
+ /**
210
+ * The near-duplicate probe, in one place: live rows of the same
211
+ * kind + subject whose text best matches `text` (bm25 order), with
212
+ * `excludeId` left out. `assertMemory` excludes the row it just wrote;
213
+ * a caller probing *before* writing has nothing to exclude and passes
214
+ * `0`, which is not a valid rowid.
215
+ */
216
+ function similarRows(
217
+ kind: MemoryKind,
218
+ subject: string,
219
+ text: string,
220
+ excludeId: number,
221
+ limit: number,
222
+ ): MemoryRow[] {
223
+ const match = similarityQuery(text);
224
+ if (!match) return [];
225
+ return repo.similar(match, kind, subject, excludeId, limit);
226
+ }
227
+
209
228
  /**
210
229
  * Load a row that a mutation is about to change, or explain why it
211
230
  * can't. Only a live row is mutable: a dropped one is in the graveyard
@@ -280,10 +299,13 @@ export function assertMemory(input: MemoryInput): {
280
299
  const row = normalize(input);
281
300
  return inTransaction(() => {
282
301
  const id = insertWithHistory(row, "assert");
283
- const match = similarityQuery(row.text);
284
- const similar = match
285
- ? repo.similar(match, row.kind, row.subject, id, SIMILAR_LIMIT)
286
- : [];
302
+ const similar = similarRows(
303
+ row.kind,
304
+ row.subject,
305
+ row.text,
306
+ id,
307
+ SIMILAR_LIMIT,
308
+ );
287
309
  return { id, similar };
288
310
  });
289
311
  }
@@ -478,14 +500,43 @@ export function listMemories(opts: MemoryListOptions = {}): MemoryRow[] {
478
500
  * input is quoted into a literal FTS5 expression by the shared
479
501
  * `ftsQuote` core, so operators and punctuation in a query are matched
480
502
  * as text rather than parsed as syntax.
503
+ *
504
+ * `match` picks how the query's terms combine:
505
+ *
506
+ * - `"all"` (default) — every term must appear. Right for a search
507
+ * someone typed on purpose (`talon memory search`, `/memory`).
508
+ * - `"any"` — terms are OR-ed and bm25 ranks, the same expression
509
+ * near-duplicate detection already uses. Right for a query that is
510
+ * a whole sentence nobody wrote as a query — a chat turn's inbound
511
+ * message (`core/memory/turn-retrieval.ts`), where AND-ing every
512
+ * word means matching nothing.
481
513
  */
482
514
  export function searchMemories(
483
515
  query: string,
484
- opts: { kind?: MemoryKind; limit?: number } = {},
516
+ opts: { kind?: MemoryKind; limit?: number; match?: "all" | "any" } = {},
485
517
  ): MemoryRow[] {
486
- const match = ftsQuote(query);
487
- if (!match) return [];
488
- return repo.searchFts(match, opts.kind, opts.limit ?? DEFAULT_SEARCH_LIMIT);
518
+ const expr = opts.match === "any" ? similarityQuery(query) : ftsQuote(query);
519
+ if (!expr) return [];
520
+ return repo.searchFts(expr, opts.kind, opts.limit ?? DEFAULT_SEARCH_LIMIT);
521
+ }
522
+
523
+ /**
524
+ * The near-duplicate candidates a claim *would* report if it were
525
+ * asserted now — the same FTS probe `assertMemory` runs, without
526
+ * writing anything.
527
+ *
528
+ * This is what lets the write path offer supersede-instead-of-append
529
+ * before a second row exists (plan §3.2): `remember` probes first, and
530
+ * only inserts once the caller has either picked a row to supersede or
531
+ * said the claim really is separate.
532
+ */
533
+ export function findSimilarMemories(
534
+ kind: MemoryKind,
535
+ subject: string,
536
+ text: string,
537
+ limit: number = SIMILAR_LIMIT,
538
+ ): MemoryRow[] {
539
+ return similarRows(kind, subject, text, 0, limit);
489
540
  }
490
541
 
491
542
  /** The audit trail for one row, oldest entry first. */