talon-agent 5.25.0 → 5.26.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 +1 -0
- package/package.json +1 -1
- package/src/backend/agy/one-shot.ts +34 -3
- package/src/backend/codex/auth.ts +31 -2
- package/src/backend/codex/constants.ts +21 -7
- package/src/backend/codex/discovery.ts +32 -1
- package/src/backend/codex/factory.ts +3 -5
- package/src/backend/codex/handler/message.ts +7 -7
- package/src/backend/codex/models.ts +53 -11
- package/src/backend/codex/one-shot.ts +139 -37
- package/src/backend/codex/state.ts +11 -0
- package/src/core/agents/abort-reason.ts +46 -0
- package/src/core/agents/registry.ts +3 -2
- package/src/core/background/dream/index.ts +2 -2
- package/src/core/background/heartbeat/agent.ts +1 -1
- package/src/core/background/isolated-agent.ts +1 -1
- package/src/core/backup/index.ts +1 -0
- package/src/core/backup/status.ts +30 -1
- package/src/core/config/index.ts +8 -0
- package/src/core/engine/gateway.ts +5 -0
- package/src/core/mesh/credentials/store.ts +60 -9
- package/src/core/tools/bridge.ts +58 -4
- package/src/frontend/discord/callbacks/components/effort.ts +4 -4
- package/src/frontend/discord/callbacks/components/index.ts +2 -0
- package/src/frontend/discord/commands/backup-panel.ts +219 -0
- package/src/frontend/discord/commands/backup.ts +25 -34
- package/src/frontend/discord/commands/info.ts +17 -5
- package/src/frontend/discord/commands/settings.ts +12 -9
- package/src/frontend/discord/render.ts +1 -1
- package/src/frontend/native/bridge/credentials/claims.ts +5 -5
- package/src/frontend/native/bridge/routes/chats.ts +40 -20
- package/src/frontend/native/bridge/routes/host.ts +15 -2
- package/src/frontend/native/bridge/routes/table.ts +4 -0
- package/src/frontend/native/commands/admin.ts +64 -0
- package/src/frontend/native/commands/backup.ts +191 -0
- package/src/frontend/native/commands/definitions.ts +113 -0
- package/src/frontend/native/commands/format.ts +24 -0
- package/src/frontend/native/commands/index.ts +106 -0
- package/src/frontend/native/commands/info.ts +97 -0
- package/src/frontend/native/commands/session.ts +130 -0
- package/src/frontend/native/commands/types.ts +26 -0
- package/src/frontend/native/protocol.ts +18 -0
- package/src/frontend/native/surface/handlers.ts +14 -1
- package/src/frontend/native/surface/status.ts +9 -1
- package/src/frontend/native/turn/emit.ts +32 -1
- package/src/frontend/presentation/backup-panel.ts +425 -0
- package/src/frontend/presentation/memory-report.ts +109 -0
- package/src/frontend/presentation/text-commands.ts +243 -0
- package/src/frontend/telegram/admin/sessions.ts +20 -5
- package/src/frontend/telegram/admin.ts +9 -2
- package/src/frontend/telegram/callbacks/backup.ts +153 -22
- package/src/frontend/telegram/callbacks/effort.ts +5 -5
- package/src/frontend/telegram/callbacks/index.ts +2 -2
- package/src/frontend/telegram/callbacks/settings.ts +3 -40
- package/src/frontend/telegram/commands/admin.ts +8 -7
- package/src/frontend/telegram/commands/backup.ts +56 -46
- package/src/frontend/telegram/commands/index.ts +3 -2
- package/src/frontend/telegram/commands/info.ts +45 -26
- package/src/frontend/telegram/commands/memory.ts +8 -92
- package/src/frontend/telegram/commands/settings.ts +13 -45
- package/src/frontend/telegram/commands/whatsapp-pairing.ts +19 -15
- package/src/frontend/telegram/render/backup-panel.ts +47 -0
- package/src/frontend/telegram/render/menu.ts +21 -30
- package/src/frontend/terminal/builtins/model.ts +20 -11
- package/src/frontend/whatsapp/commands.ts +16 -216
- package/src/frontend/whatsapp/messages/inbound.ts +33 -2
|
@@ -5,8 +5,11 @@ import { resolveModelId as resolveModelName } from "../../../core/models/catalog
|
|
|
5
5
|
import {
|
|
6
6
|
getChatSettings,
|
|
7
7
|
setChatModel,
|
|
8
|
-
setChatEffort,
|
|
9
8
|
} from "../../../storage/chat-settings.js";
|
|
9
|
+
import {
|
|
10
|
+
describeChatEffort,
|
|
11
|
+
setChatEffortLevel,
|
|
12
|
+
} from "../../presentation/model-commands.js";
|
|
10
13
|
import type { Command, CommandContext } from "../command-registry.js";
|
|
11
14
|
|
|
12
15
|
type BackendRef = CommandContext["backend"];
|
|
@@ -178,20 +181,26 @@ export const modelCommand: Command = {
|
|
|
178
181
|
export const effortCommand: Command = {
|
|
179
182
|
name: "effort",
|
|
180
183
|
argHint: "[lvl]",
|
|
181
|
-
description: "Thinking effort
|
|
184
|
+
description: "Thinking effort for the active model",
|
|
182
185
|
async handler(args, ctx) {
|
|
183
|
-
|
|
186
|
+
// Same resolution and validation as every chat frontend: the levels
|
|
187
|
+
// the active model registers, plus adaptive/reset/default.
|
|
188
|
+
const deps = {
|
|
189
|
+
config: ctx.config,
|
|
190
|
+
gateway: { backend: ctx.backend ?? null },
|
|
191
|
+
};
|
|
192
|
+
const arg = args.trim();
|
|
193
|
+
if (!arg) {
|
|
194
|
+
const { current, levels } = await describeChatEffort(ctx.chatId(), deps);
|
|
184
195
|
ctx.renderer.writeSystem(
|
|
185
|
-
|
|
196
|
+
levels.length
|
|
197
|
+
? `Effort: ${current} (${levels.join(", ")}, adaptive)`
|
|
198
|
+
: `Effort: ${current}`,
|
|
186
199
|
);
|
|
187
200
|
} else {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
? undefined
|
|
192
|
-
: (args as "off" | "low" | "medium" | "high" | "max"),
|
|
193
|
-
);
|
|
194
|
-
ctx.renderer.writeSystem(`Effort → ${args}`);
|
|
201
|
+
const outcome = await setChatEffortLevel(ctx.chatId(), arg, deps);
|
|
202
|
+
if (outcome.ok) ctx.renderer.writeSystem(outcome.text);
|
|
203
|
+
else ctx.renderer.writeError(outcome.text);
|
|
195
204
|
}
|
|
196
205
|
ctx.reprompt();
|
|
197
206
|
},
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* WhatsApp has no inline buttons, so everything Telegram does with a
|
|
6
6
|
* picker is done here with typed arguments: `/model` prints a numbered
|
|
7
7
|
* catalog, `/model 3` (or `/model <id>`, `/model <backend>`) picks from
|
|
8
|
-
* it. The
|
|
9
|
-
* this module only parses,
|
|
8
|
+
* it. The replies are shared with the native bridge
|
|
9
|
+
* (frontend/presentation/text-commands.ts); this module only parses,
|
|
10
|
+
* authorises, and delivers.
|
|
10
11
|
*
|
|
11
12
|
* Replies are written in Markdown — `sendText` translates them into
|
|
12
13
|
* WhatsApp's dialect and splits long listings into bubbles.
|
|
@@ -14,38 +15,17 @@
|
|
|
14
15
|
|
|
15
16
|
import { log } from "../../util/log.js";
|
|
16
17
|
import { recordMessageProcessed } from "../../util/watchdog.js";
|
|
17
|
-
import {
|
|
18
|
-
import { isPulseEnabled } from "../../core/background/pulse/pulse.js";
|
|
18
|
+
import { performSessionReset } from "../presentation/session-status.js";
|
|
19
19
|
import {
|
|
20
|
-
DEFAULT_PULSE_INTERVAL_MS,
|
|
21
|
-
formatBytes,
|
|
22
|
-
formatDuration,
|
|
23
|
-
formatModelLabel,
|
|
24
|
-
formatTokenCount,
|
|
25
|
-
formatUsd,
|
|
26
|
-
} from "../presentation/format.js";
|
|
27
|
-
import {
|
|
28
|
-
collectSessionStatus,
|
|
29
|
-
performSessionReset,
|
|
30
|
-
type SessionStatusData,
|
|
31
|
-
} from "../presentation/session-status.js";
|
|
32
|
-
import {
|
|
33
|
-
formatCacheTempLine,
|
|
34
|
-
formatDaemonLine,
|
|
35
|
-
} from "../presentation/status-context.js";
|
|
36
|
-
import {
|
|
37
|
-
describeChatEffort,
|
|
38
|
-
describeChatModels,
|
|
39
|
-
matchBackendArg,
|
|
40
|
-
resetChatBackend,
|
|
41
|
-
resetChatModel,
|
|
42
20
|
resolveChatBackendPair,
|
|
43
|
-
selectChatModel,
|
|
44
|
-
setChatEffortLevel,
|
|
45
|
-
switchChatBackend,
|
|
46
21
|
type ModelCommandDeps,
|
|
47
|
-
type ModelOverview,
|
|
48
22
|
} from "../presentation/model-commands.js";
|
|
23
|
+
import {
|
|
24
|
+
effortCommandReply,
|
|
25
|
+
modelCommandReply,
|
|
26
|
+
settingsCommandReply,
|
|
27
|
+
statusCommandReply,
|
|
28
|
+
} from "../presentation/text-commands.js";
|
|
49
29
|
import { sendText } from "./actions/send.js";
|
|
50
30
|
import { identityAllowed, type Identity } from "./connection/identity.js";
|
|
51
31
|
import type { WhatsAppChatInfo } from "./registry.js";
|
|
@@ -73,9 +53,6 @@ export type CommandInbound = {
|
|
|
73
53
|
isGroup: boolean;
|
|
74
54
|
};
|
|
75
55
|
|
|
76
|
-
/** How many catalog entries `/model` prints before pointing at `/model <id>`. */
|
|
77
|
-
const MAX_LISTED_MODELS = 40;
|
|
78
|
-
|
|
79
56
|
/**
|
|
80
57
|
* Parse a slash command out of message text. Group messages in mention
|
|
81
58
|
* mode arrive as "@<number> /model", so leading @mentions are skipped.
|
|
@@ -112,120 +89,6 @@ export function canChangeSettings(
|
|
|
112
89
|
return identityAllowed(inbound.identity, runtime.allowedDms);
|
|
113
90
|
}
|
|
114
91
|
|
|
115
|
-
// ── Renderers ───────────────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
function renderModelOverview(view: ModelOverview): string {
|
|
118
|
-
const lines: string[] = [];
|
|
119
|
-
lines.push(
|
|
120
|
-
view.activeModel
|
|
121
|
-
? `**Model:** \`${view.activeModel}\`${
|
|
122
|
-
view.activeDisplay && view.activeDisplay !== view.activeModel
|
|
123
|
-
? ` (${view.activeDisplay})`
|
|
124
|
-
: ""
|
|
125
|
-
}${view.hasModelOverride ? " — chat override" : ""}`
|
|
126
|
-
: "**Model:** none selected — messages will be refused until you pick one",
|
|
127
|
-
);
|
|
128
|
-
lines.push(
|
|
129
|
-
`**Backend:** ${view.backendLabel} (\`${view.backendId}\`)${view.hasBackendOverride ? " — chat override" : ""}`,
|
|
130
|
-
);
|
|
131
|
-
if (view.backends.length > 1) {
|
|
132
|
-
lines.push(
|
|
133
|
-
`Backends: ${view.backends.map((b) => `\`${b.id}\``).join(", ")} — \`/model <backend>\` switches (session restarts).`,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
if (view.choices.length === 0) {
|
|
137
|
-
lines.push(
|
|
138
|
-
"",
|
|
139
|
-
"This backend has no browsable catalog; `/model <id>` sets one directly.",
|
|
140
|
-
);
|
|
141
|
-
} else {
|
|
142
|
-
lines.push("", `**Models** (${view.choices.length}):`);
|
|
143
|
-
for (const c of view.choices.slice(0, MAX_LISTED_MODELS)) {
|
|
144
|
-
const marker = c.id === view.activeModel ? " ✓" : "";
|
|
145
|
-
const free = c.free ? " · free" : "";
|
|
146
|
-
lines.push(`${c.index}. ${c.displayName} — \`${c.id}\`${free}${marker}`);
|
|
147
|
-
}
|
|
148
|
-
if (view.choices.length > MAX_LISTED_MODELS) {
|
|
149
|
-
lines.push(
|
|
150
|
-
`… ${view.choices.length - MAX_LISTED_MODELS} more — \`/model <id>\` picks any of them.`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
lines.push(
|
|
155
|
-
"",
|
|
156
|
-
"`/model <number>` or `/model <id>` picks; `/model default` clears the chat's pick" +
|
|
157
|
-
(view.hasBackendOverride
|
|
158
|
-
? "; `/model backend default` reverts the backend."
|
|
159
|
-
: "."),
|
|
160
|
-
);
|
|
161
|
-
return lines.join("\n");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function renderSettings(
|
|
165
|
-
view: ModelOverview,
|
|
166
|
-
effort: { current: string; levels: string[] },
|
|
167
|
-
pulseOn: boolean,
|
|
168
|
-
pulseIntervalMs: number | undefined,
|
|
169
|
-
freeOnly: boolean,
|
|
170
|
-
): string {
|
|
171
|
-
const lines = [
|
|
172
|
-
"**Settings**",
|
|
173
|
-
`Model: \`${view.activeModel ?? "none selected"}\`${view.hasModelOverride ? " (chat override)" : ""}`,
|
|
174
|
-
`Backend: ${view.backendLabel} (\`${view.backendId}\`)${view.hasBackendOverride ? " (chat override)" : ""}`,
|
|
175
|
-
`Effort: ${effort.current}${effort.levels.length ? ` — levels: ${effort.levels.join(", ")}` : ""}`,
|
|
176
|
-
`Pulse: ${pulseOn ? "on" : "off"} (every ${formatDuration(pulseIntervalMs ?? DEFAULT_PULSE_INTERVAL_MS)})`,
|
|
177
|
-
];
|
|
178
|
-
if (freeOnly) lines.push("Free-only models: on");
|
|
179
|
-
lines.push(
|
|
180
|
-
"",
|
|
181
|
-
"Change with `/model`, `/effort`; `/reset` starts a fresh session.",
|
|
182
|
-
);
|
|
183
|
-
return lines.join("\n");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function renderStatus(s: SessionStatusData): string {
|
|
187
|
-
const used = s.context.known ? formatTokenCount(s.context.used) : "unknown";
|
|
188
|
-
const max = s.context.max > 0 ? formatTokenCount(s.context.max) : "unknown";
|
|
189
|
-
const pct = s.context.known ? `${s.context.pct}%` : "unknown";
|
|
190
|
-
const lines = [
|
|
191
|
-
`**Talon** · \`${formatModelLabel(s.activeModel)}\`${s.backendLabel ? ` · ${s.backendLabel}` : ""} · effort: ${s.effortName}${s.turnInProgress ? " · ⏳ turn running" : ""}`,
|
|
192
|
-
"",
|
|
193
|
-
`**Context** ${used} / ${max} (${pct})${s.context.warn ? " ⚠️ consider /reset" : ""}`,
|
|
194
|
-
`\`${s.context.bar}\``,
|
|
195
|
-
"",
|
|
196
|
-
"**Session stats**",
|
|
197
|
-
`Response: last ${s.lastResponseMs ? formatDuration(s.lastResponseMs) : "—"} · avg ${s.avgResponseMs ? formatDuration(s.avgResponseMs) : "—"} · best ${s.fastestMs ? formatDuration(s.fastestMs) : "—"}`,
|
|
198
|
-
`Turns: ${s.turns}${s.turnsModelLabel ? ` (${formatModelLabel(s.turnsModelLabel)})` : ""}`,
|
|
199
|
-
`Tokens: in ${formatTokenCount(s.inputTokens)} · out ${formatTokenCount(s.outputTokens)}${s.costUsd > 0 ? ` · cost ${formatUsd(s.costUsd)}` : ""}`,
|
|
200
|
-
];
|
|
201
|
-
if (s.cache) {
|
|
202
|
-
lines.push(
|
|
203
|
-
`Cache: ${s.cache.hitPct}% hit · read ${formatTokenCount(s.cache.read)}${s.cache.showsWrite ? ` · write ${formatTokenCount(s.cache.write)}` : ""}`,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
if (s.cacheTemp) lines.push(formatCacheTempLine(s.cacheTemp));
|
|
207
|
-
if (s.plan) {
|
|
208
|
-
lines.push(
|
|
209
|
-
"",
|
|
210
|
-
`**Plan**${s.plan.plan ? ` ${s.plan.plan}` : ""}${s.plan.ageLabel ? ` (${s.plan.ageLabel})` : ""}`,
|
|
211
|
-
...s.plan.windows.map(
|
|
212
|
-
(w) =>
|
|
213
|
-
`\`${w.label.padEnd(6)}${w.bar} ${String(w.percent).padStart(3)}%\`${w.resetLabel ? ` reset ${w.resetLabel}` : ""}`,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
lines.push(
|
|
218
|
-
"",
|
|
219
|
-
`**Pulse** ${s.pulseOn ? "on" : "off"}`,
|
|
220
|
-
`**Workspace** ${formatBytes(s.diskBytes)}`,
|
|
221
|
-
`**Session** ${s.sessionName ? `"${s.sessionName}" ` : ""}${s.sessionId ? `\`${s.sessionId.slice(0, 8)}…\`` : "(new)"} · ${s.sessionAge} old`,
|
|
222
|
-
`**Uptime** ${s.uptime} · ${s.activeSessionCount} active session${s.activeSessionCount === 1 ? "" : "s"}`,
|
|
223
|
-
`**Runtime** ${s.runtime} · ${formatBytes(s.rssBytes)} RSS`,
|
|
224
|
-
formatDaemonLine(s.daemon),
|
|
225
|
-
);
|
|
226
|
-
return lines.join("\n");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
92
|
const HELP_TEXT = [
|
|
230
93
|
"**Commands**",
|
|
231
94
|
"/model — list backends and models; `/model <n|id>` picks, `/model <backend>` switches",
|
|
@@ -238,71 +101,6 @@ const HELP_TEXT = [
|
|
|
238
101
|
|
|
239
102
|
// ── Handlers ────────────────────────────────────────────────────────────────
|
|
240
103
|
|
|
241
|
-
async function runModelCommand(
|
|
242
|
-
chatId: string,
|
|
243
|
-
arg: string,
|
|
244
|
-
deps: ModelCommandDeps,
|
|
245
|
-
): Promise<string> {
|
|
246
|
-
if (!arg) return renderModelOverview(await describeChatModels(chatId, deps));
|
|
247
|
-
const lower = arg.toLowerCase();
|
|
248
|
-
// `/reset` keeps history on WhatsApp because the local store is the only
|
|
249
|
-
// chat record; a backend switch keeps it for the same reason.
|
|
250
|
-
if (lower === "backend default" || lower === "backend reset") {
|
|
251
|
-
return (await resetChatBackend(chatId, deps, { keepHistory: true })).text;
|
|
252
|
-
}
|
|
253
|
-
if (lower === "reset" || lower === "default") {
|
|
254
|
-
return (await resetChatModel(chatId, deps)).text;
|
|
255
|
-
}
|
|
256
|
-
const backend = matchBackendArg(arg, deps.config);
|
|
257
|
-
if (backend) {
|
|
258
|
-
return (
|
|
259
|
-
await switchChatBackend(chatId, backend, deps, { keepHistory: true })
|
|
260
|
-
).text;
|
|
261
|
-
}
|
|
262
|
-
return (await selectChatModel(chatId, arg, deps)).text;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async function runEffortCommand(
|
|
266
|
-
chatId: string,
|
|
267
|
-
arg: string,
|
|
268
|
-
deps: ModelCommandDeps,
|
|
269
|
-
): Promise<string> {
|
|
270
|
-
if (arg) return (await setChatEffortLevel(chatId, arg, deps)).text;
|
|
271
|
-
const effort = await describeChatEffort(chatId, deps);
|
|
272
|
-
if (effort.levels.length === 0) {
|
|
273
|
-
return `No reasoning levels available for ${effort.activeModel ?? "the active model"} on backend ${effort.backendId}.`;
|
|
274
|
-
}
|
|
275
|
-
return `**Effort:** ${effort.current}\nLevels: ${effort.levels.join(", ")}, or adaptive — \`/effort <level>\` sets one.`;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async function runSettingsCommand(
|
|
279
|
-
chatId: string,
|
|
280
|
-
deps: ModelCommandDeps,
|
|
281
|
-
): Promise<string> {
|
|
282
|
-
const [view, effort] = await Promise.all([
|
|
283
|
-
describeChatModels(chatId, deps),
|
|
284
|
-
describeChatEffort(chatId, deps),
|
|
285
|
-
]);
|
|
286
|
-
const sets = getChatSettings(chatId);
|
|
287
|
-
return renderSettings(
|
|
288
|
-
view,
|
|
289
|
-
effort,
|
|
290
|
-
isPulseEnabled(chatId),
|
|
291
|
-
sets.pulseIntervalMs,
|
|
292
|
-
sets.freeOnly === true,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function runStatusCommand(
|
|
297
|
-
chatId: string,
|
|
298
|
-
deps: ModelCommandDeps,
|
|
299
|
-
): Promise<string> {
|
|
300
|
-
const { backend, backendId } = resolveChatBackendPair(chatId, deps);
|
|
301
|
-
return renderStatus(
|
|
302
|
-
await collectSessionStatus(chatId, deps.config, backend, backendId),
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
104
|
async function runResetCommand(
|
|
307
105
|
chatId: string,
|
|
308
106
|
senderName: string,
|
|
@@ -337,13 +135,15 @@ export async function executeWhatsAppCommand(
|
|
|
337
135
|
}
|
|
338
136
|
switch (cmd.name) {
|
|
339
137
|
case "model":
|
|
340
|
-
|
|
138
|
+
// `/reset` keeps history on WhatsApp because the local store is the
|
|
139
|
+
// only chat record; a backend switch keeps it for the same reason.
|
|
140
|
+
return modelCommandReply(chatId, cmd.arg, deps, { keepHistory: true });
|
|
341
141
|
case "effort":
|
|
342
|
-
return
|
|
142
|
+
return effortCommandReply(chatId, cmd.arg, deps);
|
|
343
143
|
case "settings":
|
|
344
|
-
return
|
|
144
|
+
return settingsCommandReply(chatId, deps);
|
|
345
145
|
case "status":
|
|
346
|
-
return
|
|
146
|
+
return statusCommandReply(chatId, deps);
|
|
347
147
|
case "reset":
|
|
348
148
|
return runResetCommand(chatId, inbound.senderName, deps);
|
|
349
149
|
case "help":
|
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
4
|
+
* slash commands (../commands.ts; skipped in the catch-up backlog) →
|
|
5
|
+
* catch-up policy → `execute()`.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { isJidGroup, type WAMessage } from "baileys";
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
recordMessageReceived,
|
|
18
19
|
} from "../../../util/watchdog.js";
|
|
19
20
|
import { isAddressedToSelf, isGroupAllowed } from "../access.js";
|
|
20
|
-
import { handleWhatsAppCommand } from "../commands.js";
|
|
21
|
+
import { handleWhatsAppCommand, parseWhatsAppCommand } from "../commands.js";
|
|
21
22
|
import { sendText } from "../actions/send.js";
|
|
22
23
|
import {
|
|
23
24
|
bareId,
|
|
@@ -207,6 +208,32 @@ function catchUpDeservesReply(inbound: RecordedMessage): boolean {
|
|
|
207
208
|
return true;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
/**
|
|
212
|
+
* A slash command in the catch-up backlog was issued against an earlier
|
|
213
|
+
* state of the chat — replaying `/reset` or `/model x` after a restart
|
|
214
|
+
* would silently undo whatever happened since. It stays recorded but does
|
|
215
|
+
* not run; while fresh, the sender is told so they can resend it.
|
|
216
|
+
*/
|
|
217
|
+
async function skipCatchUpCommand(
|
|
218
|
+
runtime: WhatsAppRuntime,
|
|
219
|
+
inbound: RecordedMessage,
|
|
220
|
+
name: string,
|
|
221
|
+
): Promise<void> {
|
|
222
|
+
const { chat, senderName } = inbound;
|
|
223
|
+
log(
|
|
224
|
+
"whatsapp",
|
|
225
|
+
`[${chat.chatId}] Skipped /${name} from ${senderName} (sent while offline)`,
|
|
226
|
+
);
|
|
227
|
+
recordMessageProcessed();
|
|
228
|
+
const sock = runtime.sock;
|
|
229
|
+
if (!sock || !shouldReplyToCatchUp(inbound.platformTs)) return;
|
|
230
|
+
await sendText(
|
|
231
|
+
{ sock, gateway: runtime.gateway },
|
|
232
|
+
chat,
|
|
233
|
+
`Not run: /${name} was sent while offline. Send it again to run it.`,
|
|
234
|
+
).catch(() => {});
|
|
235
|
+
}
|
|
236
|
+
|
|
210
237
|
async function onTurnEvent(
|
|
211
238
|
runtime: WhatsAppRuntime,
|
|
212
239
|
chat: WhatsAppChatInfo,
|
|
@@ -307,6 +334,10 @@ export async function handleInbound(
|
|
|
307
334
|
if (!admitted) return;
|
|
308
335
|
const inbound = await recordInbound(runtime, msg, admitted);
|
|
309
336
|
if (!inbound) return;
|
|
337
|
+
if (options.catchUp) {
|
|
338
|
+
const cmd = parseWhatsAppCommand(inbound.text);
|
|
339
|
+
if (cmd) return skipCatchUpCommand(runtime, inbound, cmd.name);
|
|
340
|
+
}
|
|
310
341
|
if (await handleWhatsAppCommand(runtime, inbound)) return;
|
|
311
342
|
if (options.catchUp && !catchUpDeservesReply(inbound)) return;
|
|
312
343
|
await runInboundTurn(runtime, inbound);
|