talon-agent 4.6.0 → 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.
- package/README.md +3 -3
- package/package.json +3 -2
- package/prompts/README.md +2 -2
- package/prompts/system/memory-recall.md +16 -0
- package/src/app.ts +19 -4
- package/src/backend/claude-sdk/handler.ts +28 -7
- package/src/backend/claude-sdk/one-shot.ts +1 -1
- package/src/backend/claude-sdk/options.ts +7 -6
- package/src/backend/claude-sdk/stream.ts +30 -3
- package/src/backend/claude-sdk/warm.ts +1 -1
- package/src/backend/codex/constants.ts +1 -1
- package/src/backend/codex/factory.ts +2 -2
- package/src/backend/codex/handler/events.ts +1 -1
- package/src/backend/codex/handler/message.ts +26 -12
- package/src/backend/codex/handler/rollout-accounting.ts +1 -1
- package/src/backend/codex/init.ts +1 -1
- package/src/backend/codex/mcp-config.ts +1 -1
- package/src/backend/codex/one-shot.ts +1 -1
- package/src/backend/kilo/handler/message.ts +4 -1
- package/src/backend/openai-agents/constants.ts +1 -1
- package/src/backend/openai-agents/factory.ts +2 -2
- package/src/backend/openai-agents/handler/events.ts +1 -1
- package/src/backend/openai-agents/handler/message.ts +8 -4
- package/src/backend/openai-agents/init.ts +1 -1
- package/src/backend/openai-agents/mcp-pool.ts +1 -1
- package/src/backend/opencode/handler/message.ts +4 -1
- package/src/backend/remote-server/chat-turn.ts +31 -24
- package/src/backend/remote-server/events.ts +3 -3
- package/src/backend/remote-server/factory.ts +6 -3
- package/src/backend/remote-server/index.ts +1 -1
- package/src/backend/remote-server/mcp.ts +1 -1
- package/src/backend/remote-server/one-shot.ts +1 -1
- package/src/backend/remote-server/server-bindings.ts +1 -1
- package/src/backend/remote-server/turn.ts +1 -1
- package/src/backend/runtime/cache/cache-metrics.ts +126 -0
- package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
- package/src/backend/{shared → runtime}/index.ts +32 -23
- package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
- package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
- package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
- package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
- package/src/backend/{shared → runtime/turn}/delivery.ts +2 -2
- package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
- package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
- package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
- package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
- package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
- package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
- package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
- package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
- package/src/bootstrap.ts +4 -19
- package/src/cli.ts +1 -1
- package/src/core/agent-runtime/capabilities.ts +11 -0
- package/src/core/agent-runtime/contract-tests.ts +92 -1
- package/src/core/agent-runtime/events.ts +1 -1
- package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
- package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
- package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
- package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
- package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
- package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
- package/src/core/background/{dream.ts → dream/index.ts} +10 -15
- package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
- package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
- package/src/core/background/triggers/command.ts +1 -1
- package/src/core/config/index.ts +15 -11
- package/src/core/daemon/resource-sampler.ts +121 -0
- package/src/core/engine/dispatcher.ts +16 -0
- package/src/core/engine/gateway-actions/cron.ts +2 -2
- package/src/core/engine/gateway-actions/index.ts +3 -0
- package/src/core/engine/gateway-actions/memory.ts +335 -0
- package/src/core/engine/gateway.ts +0 -6
- package/src/core/memory/import.ts +3 -2
- package/src/core/memory/taps.ts +199 -0
- package/src/core/memory/turn-retrieval.ts +222 -0
- package/src/core/prompt/assemble.ts +2 -12
- package/src/core/prompt/index.ts +2 -2
- package/src/core/prompt/invalidation.ts +1 -1
- package/src/core/tasks/index.ts +1 -1
- package/src/core/tools/index.ts +2 -0
- package/src/core/tools/memory.ts +128 -0
- package/src/core/tools/types.ts +1 -0
- package/src/core/weaver/turn-cpu.ts +32 -0
- package/src/core/weaver/weaver.ts +36 -0
- package/src/frontend/discord/admin.ts +1 -1
- package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
- package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
- package/src/frontend/discord/callbacks/components/settings.ts +1 -1
- package/src/frontend/discord/callbacks/modals.ts +4 -1
- package/src/frontend/discord/commands/admin.ts +1 -35
- package/src/frontend/discord/commands/definitions.ts +0 -11
- package/src/frontend/discord/commands/router.ts +0 -3
- package/src/frontend/discord/commands/session.ts +6 -0
- package/src/frontend/discord/commands/settings.ts +1 -1
- package/src/frontend/discord/middleware.ts +1 -25
- package/src/frontend/discord/runtime.ts +1 -2
- package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
- package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
- package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
- package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
- package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
- package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
- package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
- package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
- package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
- package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
- package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
- package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
- package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
- package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
- package/src/frontend/native/index.ts +13 -10
- package/src/frontend/native/{media.ts → media/media.ts} +3 -3
- package/src/frontend/native/runtime.ts +1 -1
- package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
- package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
- package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
- package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
- package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
- package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
- package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
- package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
- package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
- package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
- package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
- package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
- package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
- package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
- package/src/frontend/shared/model-commands.ts +1 -1
- package/src/frontend/shared/session-status.ts +35 -8
- package/src/frontend/shared/status-context.ts +101 -2
- package/src/frontend/telegram/admin/background.ts +1 -1
- package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
- package/src/frontend/telegram/callbacks/pulse.ts +1 -1
- package/src/frontend/telegram/callbacks/settings.ts +1 -1
- package/src/frontend/telegram/commands/admin.ts +2 -37
- package/src/frontend/telegram/commands/index.ts +1 -1
- package/src/frontend/telegram/commands/session.ts +6 -0
- package/src/frontend/telegram/commands/settings.ts +1 -1
- package/src/frontend/telegram/handlers/messages.ts +0 -10
- package/src/frontend/telegram/index.ts +1 -5
- package/src/frontend/telegram/middleware.ts +1 -15
- package/src/frontend/whatsapp/access.ts +2 -2
- package/src/frontend/whatsapp/actions/history.ts +2 -2
- package/src/frontend/whatsapp/actions/messaging.ts +2 -2
- package/src/frontend/whatsapp/actions/moderation.ts +1 -1
- package/src/frontend/whatsapp/actions/send.ts +1 -1
- package/src/frontend/whatsapp/commands.ts +8 -2
- package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
- package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
- package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
- package/src/frontend/whatsapp/index.ts +4 -4
- package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
- package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
- package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
- package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
- package/src/frontend/whatsapp/registry.ts +1 -1
- package/src/frontend/whatsapp/runtime.ts +1 -1
- package/src/index.ts +1 -1
- package/src/storage/db.ts +6 -1
- package/src/storage/memory.ts +59 -8
- package/src/storage/metrics.ts +38 -0
- package/src/storage/repositories/goals-repo.ts +2 -1
- package/src/storage/repositories/sessions-repo.ts +6 -0
- package/src/storage/session-record.ts +13 -2
- package/src/storage/sessions.ts +12 -0
- package/src/storage/sql/db.sql +5 -0
- package/src/storage/sql/schema.sql +4 -0
- package/src/storage/sql/sessions.sql +3 -3
- package/src/storage/sql/statements.generated.ts +10 -3
- package/src/storage/sql/turn-meta.sql +1 -1
- package/src/util/boot-timer.ts +15 -1
- package/src/util/chat-id.ts +30 -0
- package/src/util/concurrency.ts +1 -1
- package/src/util/log.ts +1 -1
- package/src/util/paths.ts +0 -2
- package/src/core/soul/README.md +0 -110
- package/src/core/soul/RESEARCH.md +0 -98
- package/src/core/soul/associative.ts +0 -98
- package/src/core/soul/centrality.ts +0 -98
- package/src/core/soul/cluster.ts +0 -83
- package/src/core/soul/compiler.ts +0 -207
- package/src/core/soul/consolidate.ts +0 -179
- package/src/core/soul/critic.ts +0 -162
- package/src/core/soul/dag.ts +0 -265
- package/src/core/soul/delta.ts +0 -123
- package/src/core/soul/drift.ts +0 -99
- package/src/core/soul/embedder.ts +0 -129
- package/src/core/soul/emergent-critic.ts +0 -96
- package/src/core/soul/forgetting.ts +0 -131
- package/src/core/soul/governance.ts +0 -93
- package/src/core/soul/hash.ts +0 -97
- package/src/core/soul/hdc.ts +0 -154
- package/src/core/soul/kernel.ts +0 -540
- package/src/core/soul/lattice.ts +0 -103
- package/src/core/soul/lens.ts +0 -110
- package/src/core/soul/projector.ts +0 -240
- package/src/core/soul/reflect.ts +0 -170
- package/src/core/soul/reflex.ts +0 -164
- package/src/core/soul/retrieve.ts +0 -146
- package/src/core/soul/salience.ts +0 -146
- package/src/core/soul/service.ts +0 -204
- package/src/core/soul/settings.ts +0 -47
- package/src/core/soul/signals.ts +0 -117
- package/src/core/soul/talon-embedder.ts +0 -80
- package/src/core/soul/taps.ts +0 -199
- package/src/core/soul/types.ts +0 -298
- package/src/core/soul/valence.ts +0 -83
- /package/src/backend/{shared → runtime}/frontends.ts +0 -0
- /package/src/backend/{shared → runtime}/metrics.ts +0 -0
- /package/src/backend/{shared → runtime}/sleep.ts +0 -0
- /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
- /package/src/backend/{shared → runtime}/usage.ts +0 -0
- /package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
- /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
- /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
- /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
- /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
- /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
- /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,
|
|
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 /
|
|
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
|
});
|
|
@@ -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)
|
|
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 "
|
|
13
|
-
import { dirs } from "
|
|
14
|
-
import { notifyAdmin } from "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
20
|
-
import { log, logError } from "
|
|
21
|
-
import { dirs } from "
|
|
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 "
|
|
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 "
|
|
9
|
-
import { log, logError } from "
|
|
10
|
-
import { execute } from "
|
|
11
|
-
import { toolInputToRecord } from "
|
|
12
|
-
import { appendDailyLog } from "
|
|
13
|
-
import { pushMessage } from "
|
|
14
|
-
import { relayInbound } from "
|
|
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 "
|
|
19
|
-
import { isAddressedToSelf, isGroupAllowed } from "
|
|
20
|
-
import { handleWhatsAppCommand } from "
|
|
21
|
-
import { sendText } from "
|
|
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 "
|
|
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 "
|
|
32
|
-
import type { WhatsAppRuntime } from "
|
|
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 "
|
|
15
|
-
import { addMedia } from "
|
|
16
|
-
import { logWarn } from "
|
|
17
|
-
import { dirs } from "
|
|
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 = [
|
|
@@ -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 "
|
|
18
|
-
import { log, logError } from "
|
|
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 "
|
|
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/
|
|
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);
|
package/src/storage/memory.ts
CHANGED
|
@@ -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
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
487
|
-
if (!
|
|
488
|
-
return repo.searchFts(
|
|
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. */
|