talon-agent 4.3.1 → 4.3.3
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/package.json +1 -1
- package/src/app.ts +1 -1
- package/src/backend/claude-sdk/doctor.ts +1 -1
- package/src/backend/codex/doctor.ts +1 -1
- package/src/backend/openai-agents/doctor.ts +1 -1
- package/src/bootstrap.ts +3 -3
- package/src/cli/doctor.ts +1 -1
- package/src/cli/skill.ts +2 -2
- package/src/core/agent-runtime/backend-registry.ts +1 -1
- package/src/core/auth/expiry-monitor.ts +1 -1
- package/src/core/background/cron-spec.ts +1 -1
- package/src/core/background/cron.ts +1 -1
- package/src/core/background/heartbeat/agent.ts +1 -1
- package/src/core/background/triggers/command.ts +1 -1
- package/src/core/background/triggers/exit.ts +1 -1
- package/src/core/background/triggers/output.ts +1 -1
- package/src/core/background/triggers/resume.ts +1 -1
- package/src/core/background/triggers/spawn.ts +1 -1
- package/src/core/{doctor.ts → doctor/index.ts} +11 -11
- package/src/core/engine/gateway-actions/cron.ts +2 -2
- package/src/core/engine/gateway-actions/goals.ts +2 -2
- package/src/core/engine/gateway-actions/scripts.ts +1 -1
- package/src/core/engine/gateway-actions/skills.ts +1 -1
- package/src/core/engine/gateway-actions/triggers.ts +2 -2
- package/src/core/engine/gateway-actions/{shared.ts → validation.ts} +2 -2
- package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
- package/src/core/plugin/native-runtimes.ts +1 -1
- package/src/core/prompt/assemble.ts +2 -2
- package/src/core/scripts/runner.ts +1 -1
- package/src/core/soul/forgetting.ts +2 -2
- package/src/core/soul/salience.ts +8 -4
- package/src/frontend/discord/actions/{shared.ts → buttons.ts} +3 -56
- package/src/frontend/discord/actions/channels.ts +34 -0
- package/src/frontend/discord/actions/chat-info.ts +1 -1
- package/src/frontend/discord/actions/index.ts +1 -1
- package/src/frontend/discord/actions/media.ts +1 -1
- package/src/frontend/discord/actions/messaging.ts +4 -2
- package/src/frontend/discord/actions/try-action.ts +24 -0
- package/src/frontend/discord/admin.ts +2 -2
- package/src/frontend/discord/callbacks/components/settings.ts +1 -1
- package/src/frontend/discord/callbacks/modals.ts +1 -1
- package/src/frontend/discord/commands/admin.ts +3 -3
- package/src/frontend/discord/commands/info.ts +2 -2
- package/src/frontend/discord/commands/{shared.ts → interaction.ts} +2 -1
- package/src/frontend/discord/commands/router.ts +1 -1
- package/src/frontend/discord/commands/session.ts +2 -2
- package/src/frontend/discord/commands/settings.ts +2 -2
- package/src/frontend/discord/{helpers.ts → render.ts} +3 -2
- package/src/frontend/native/extensions.ts +1 -1
- package/src/frontend/teams/chat-discovery.ts +69 -0
- package/src/frontend/teams/commands.ts +141 -0
- package/src/frontend/teams/index.ts +24 -350
- package/src/frontend/teams/outbound.ts +44 -0
- package/src/frontend/teams/poll.ts +101 -0
- package/src/frontend/teams/runtime.ts +51 -0
- package/src/frontend/teams/turn.ts +97 -0
- package/src/frontend/telegram/actions/index.ts +1 -1
- package/src/frontend/telegram/actions/messaging.ts +1 -1
- package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
- package/src/frontend/telegram/actions/moderation/index.ts +74 -0
- package/src/frontend/telegram/actions/moderation/members.ts +77 -0
- package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
- package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
- package/src/frontend/telegram/actions/moderation/types.ts +16 -0
- package/src/frontend/telegram/actions/shared.ts +4 -4
- package/src/frontend/telegram/admin/background.ts +66 -0
- package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
- package/src/frontend/telegram/admin/health.ts +81 -0
- package/src/frontend/telegram/admin/sessions.ts +105 -0
- package/src/frontend/telegram/admin.ts +56 -261
- package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
- package/src/frontend/telegram/callbacks/model/control.ts +29 -0
- package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
- package/src/frontend/telegram/callbacks/model/select.ts +100 -0
- package/src/frontend/telegram/callbacks/model/types.ts +39 -0
- package/src/frontend/telegram/callbacks/model/views.ts +116 -0
- package/src/frontend/telegram/callbacks/model.ts +61 -331
- package/src/frontend/telegram/commands/admin.ts +108 -77
- package/src/frontend/telegram/commands/settings.ts +65 -43
- package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
- package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
- package/src/frontend/telegram/middleware.ts +166 -201
- package/src/frontend/telegram/sticker-library.ts +2 -2
- package/src/frontend/terminal/builtins/context.ts +184 -0
- package/src/frontend/terminal/builtins/help.ts +35 -0
- package/src/frontend/terminal/builtins/index.ts +23 -0
- package/src/frontend/terminal/builtins/model.ts +198 -0
- package/src/frontend/terminal/builtins/session.ts +101 -0
- package/src/frontend/terminal/builtins/status.ts +202 -0
- package/src/frontend/terminal/command-registry.ts +80 -0
- package/src/frontend/terminal/commands.ts +11 -674
- package/src/frontend/terminal/input/history.ts +64 -0
- package/src/frontend/terminal/input/keys.ts +143 -0
- package/src/frontend/terminal/input/parts.ts +33 -0
- package/src/frontend/terminal/input/paste.ts +53 -0
- package/src/frontend/terminal/input/render.ts +42 -0
- package/src/frontend/terminal/input/state.ts +60 -0
- package/src/frontend/terminal/input.ts +27 -342
- package/src/frontend/whatsapp/actions/account.ts +3 -6
- package/src/frontend/whatsapp/actions/chat-info.ts +2 -1
- package/src/frontend/whatsapp/actions/coerce.ts +30 -0
- package/src/frontend/whatsapp/actions/history.ts +1 -1
- package/src/frontend/whatsapp/actions/media-upload.ts +82 -0
- package/src/frontend/whatsapp/actions/media.ts +3 -8
- package/src/frontend/whatsapp/actions/messaging.ts +2 -7
- package/src/frontend/whatsapp/actions/moderation.ts +2 -1
- package/src/frontend/whatsapp/actions/{shared.ts → send.ts} +4 -124
- package/src/frontend/whatsapp/actions/try-action.ts +24 -0
- package/src/frontend/whatsapp/commands.ts +1 -1
- package/src/frontend/whatsapp/connection.ts +1 -1
- package/src/frontend/whatsapp/inbound.ts +1 -1
- package/src/frontend/whatsapp/index.ts +2 -2
- package/src/frontend/whatsapp/pairing-service.ts +1 -1
- package/src/plugins/github/provision.ts +1 -1
- package/src/plugins/mempalace/provision.ts +1 -1
- package/src/plugins/playwright/provision.ts +1 -1
- package/src/storage/repositories/cron-repo.ts +1 -1
- package/src/storage/repositories/goals-repo.ts +1 -1
- package/src/storage/repositories/scripts-repo.ts +1 -1
- package/src/storage/repositories/triggers-repo.ts +1 -1
- package/src/storage/sql/schema.sql +1 -1
- package/src/storage/sql/statements.generated.ts +2 -2
- package/src/storage/sql/triggers.sql +1 -1
- package/src/frontend/telegram/actions/moderation.ts +0 -255
- /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
- /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
- /package/src/storage/{cron-store.ts → cron.ts} +0 -0
- /package/src/storage/{goal-store.ts → goals.ts} +0 -0
- /package/src/storage/{scheduled-store.ts → scheduled.ts} +0 -0
- /package/src/storage/{script-store.ts → scripts.ts} +0 -0
- /package/src/storage/{skill-store.ts → skills.ts} +0 -0
- /package/src/storage/{sticker-store.ts → stickers.ts} +0 -0
- /package/src/storage/{trigger-store.ts → triggers.ts} +0 -0
|
@@ -1,111 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Outbound WhatsApp sends: quoted-reply resolution, the serialized
|
|
3
|
+
* time-bounded socket queue, and the content/text senders that remember
|
|
4
|
+
* what went out so later tool calls and history can address it.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { existsSync, statSync } from "node:fs";
|
|
8
|
-
import { basename, extname } from "node:path";
|
|
9
7
|
import type { AnyMessageContent, WAMessage, WASocket } from "baileys";
|
|
10
|
-
import {
|
|
11
|
-
import { log, logError } from "../../../util/log.js";
|
|
8
|
+
import { log } from "../../../util/log.js";
|
|
12
9
|
import type { ActionResult } from "../../../core/types.js";
|
|
13
10
|
import { toWhatsAppChunks } from "../formatting.js";
|
|
14
11
|
import { lookupMessage, rememberMessage } from "../message-store.js";
|
|
15
12
|
import { pushMessage, type HistoryMessage } from "../../../storage/history.js";
|
|
16
13
|
import type { WhatsAppChatInfo } from "../registry.js";
|
|
17
14
|
|
|
18
|
-
/** WhatsApp's own ceiling for a media upload. */
|
|
19
|
-
const MAX_MEDIA_BYTES = 64 * 1024 * 1024;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Run an action, converting a throw into a structured failure. WhatsApp
|
|
23
|
-
* errors arrive as Boom objects whose message is the useful part; the
|
|
24
|
-
* model gets that text so it can adapt rather than retry blindly.
|
|
25
|
-
*/
|
|
26
|
-
export async function tryAction(
|
|
27
|
-
label: string,
|
|
28
|
-
fn: () => Promise<ActionResult>,
|
|
29
|
-
): Promise<ActionResult> {
|
|
30
|
-
try {
|
|
31
|
-
return await fn();
|
|
32
|
-
} catch (err) {
|
|
33
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
-
logError("whatsapp", `${label} failed: ${msg}`);
|
|
35
|
-
return { ok: false, error: `${label}: ${msg}` };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Baileys accepts a Buffer, a stream, or `{ url }` for local paths and HTTP. */
|
|
40
|
-
export type MediaUpload = { url: string };
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Resolve one media input to something Baileys can upload. Two sources:
|
|
44
|
-
* a public URL (WhatsApp's uploader fetches it) or a workspace file path
|
|
45
|
-
* (streamed from disk). `file_id` is a Telegram concept with no WhatsApp
|
|
46
|
-
* equivalent — say so instead of failing obscurely.
|
|
47
|
-
*/
|
|
48
|
-
export function resolveMediaUpload(
|
|
49
|
-
src: { file_path?: unknown; url?: unknown; file_id?: unknown },
|
|
50
|
-
label: string,
|
|
51
|
-
): { media: MediaUpload; fileName: string } | { error: string } {
|
|
52
|
-
if (src.url) {
|
|
53
|
-
const url = String(src.url);
|
|
54
|
-
return {
|
|
55
|
-
media: { url },
|
|
56
|
-
fileName: basename(new URL(url).pathname) || "file",
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
if (src.file_id) {
|
|
60
|
-
return {
|
|
61
|
-
error:
|
|
62
|
-
`${label}: WhatsApp has no file_id — re-send by url (public) or ` +
|
|
63
|
-
`file_path (workspace file)`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
if (!src.file_path) {
|
|
67
|
-
return {
|
|
68
|
-
error: `${label}: provide file_path (workspace file) or url (public)`,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const filePath = expandFsPath(String(src.file_path));
|
|
72
|
-
if (!existsSync(filePath)) {
|
|
73
|
-
return {
|
|
74
|
-
error: `File not found: ${filePath} — check the workspace path, or send by url instead`,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
if (statSync(filePath).size > MAX_MEDIA_BYTES) {
|
|
78
|
-
return { error: `${label}: file exceeds WhatsApp's 64MB limit` };
|
|
79
|
-
}
|
|
80
|
-
return { media: { url: filePath }, fileName: basename(filePath) };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Extension → mimetype for the document/audio paths that require one. */
|
|
84
|
-
const MIME_BY_EXT: Record<string, string> = {
|
|
85
|
-
".pdf": "application/pdf",
|
|
86
|
-
".txt": "text/plain",
|
|
87
|
-
".md": "text/markdown",
|
|
88
|
-
".json": "application/json",
|
|
89
|
-
".csv": "text/csv",
|
|
90
|
-
".zip": "application/zip",
|
|
91
|
-
".png": "image/png",
|
|
92
|
-
".jpg": "image/jpeg",
|
|
93
|
-
".jpeg": "image/jpeg",
|
|
94
|
-
".gif": "image/gif",
|
|
95
|
-
".webp": "image/webp",
|
|
96
|
-
".mp4": "video/mp4",
|
|
97
|
-
".mov": "video/quicktime",
|
|
98
|
-
".mp3": "audio/mpeg",
|
|
99
|
-
".m4a": "audio/mp4",
|
|
100
|
-
".ogg": "audio/ogg",
|
|
101
|
-
".opus": "audio/ogg; codecs=opus",
|
|
102
|
-
".wav": "audio/wav",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export function guessMimetype(fileName: string, fallback: string): string {
|
|
106
|
-
return MIME_BY_EXT[extname(fileName).toLowerCase()] ?? fallback;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
15
|
/**
|
|
110
16
|
* Resolve a `reply_to` message id into the quoted message Baileys wants.
|
|
111
17
|
* An unknown id quotes nothing rather than failing the send — the reply
|
|
@@ -292,29 +198,3 @@ export async function sendText(
|
|
|
292
198
|
);
|
|
293
199
|
return first ?? { ok: true };
|
|
294
200
|
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Coerce a user reference the model supplied — bare number, JID, or the
|
|
298
|
-
* numeric id from a member listing — into a WhatsApp user JID.
|
|
299
|
-
*/
|
|
300
|
-
export function toUserJid(value: unknown): string | null {
|
|
301
|
-
const raw = String(value ?? "").trim();
|
|
302
|
-
if (!raw) return null;
|
|
303
|
-
if (raw.includes("@")) return raw;
|
|
304
|
-
const digits = raw.replace(/[^0-9]/g, "");
|
|
305
|
-
return digits ? `${digits}@s.whatsapp.net` : null;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Pull the about text out of a `fetchStatus` result. Baileys has
|
|
310
|
-
* returned both a bare object and a one-element array across versions,
|
|
311
|
-
* and the payload itself nests `status` either as a string or as
|
|
312
|
-
* `{ status }` — accept all four shapes rather than guess one.
|
|
313
|
-
*/
|
|
314
|
-
export function readStatusText(result: unknown): string | undefined {
|
|
315
|
-
const entry = (Array.isArray(result) ? result[0] : result) as
|
|
316
|
-
{ status?: string | { status?: string } } | undefined;
|
|
317
|
-
return typeof entry?.status === "string"
|
|
318
|
-
? entry.status
|
|
319
|
-
: entry?.status?.status;
|
|
320
|
-
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a WhatsApp action, converting a throw into a structured failure.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { logError } from "../../../util/log.js";
|
|
6
|
+
import type { ActionResult } from "../../../core/types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run an action, converting a throw into a structured failure. WhatsApp
|
|
10
|
+
* errors arrive as Boom objects whose message is the useful part; the
|
|
11
|
+
* model gets that text so it can adapt rather than retry blindly.
|
|
12
|
+
*/
|
|
13
|
+
export async function tryAction(
|
|
14
|
+
label: string,
|
|
15
|
+
fn: () => Promise<ActionResult>,
|
|
16
|
+
): Promise<ActionResult> {
|
|
17
|
+
try {
|
|
18
|
+
return await fn();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21
|
+
logError("whatsapp", `${label} failed: ${msg}`);
|
|
22
|
+
return { ok: false, error: `${label}: ${msg}` };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
type ModelCommandDeps,
|
|
43
43
|
type ModelOverview,
|
|
44
44
|
} from "../shared/model-commands.js";
|
|
45
|
-
import { sendText } from "./actions/
|
|
45
|
+
import { sendText } from "./actions/send.js";
|
|
46
46
|
import { identityAllowed, type Identity } from "./identity.js";
|
|
47
47
|
import type { WhatsAppChatInfo } from "./registry.js";
|
|
48
48
|
import type { WhatsAppRuntime } from "./runtime.js";
|
|
@@ -11,7 +11,7 @@ import makeWASocket, { type AuthenticationState, type WASocket } from "baileys";
|
|
|
11
11
|
import qrcode from "qrcode-terminal";
|
|
12
12
|
import { log, logError, logWarn } from "../../util/log.js";
|
|
13
13
|
import { dirs } from "../../util/paths.js";
|
|
14
|
-
import { notifyAdmin } from "../../core/notify.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
17
|
import { handleInbound } from "./inbound.js";
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "../../util/watchdog.js";
|
|
19
19
|
import { isAddressedToSelf, isGroupAllowed } from "./access.js";
|
|
20
20
|
import { handleWhatsAppCommand } from "./commands.js";
|
|
21
|
-
import { sendText } from "./actions/
|
|
21
|
+
import { sendText } from "./actions/send.js";
|
|
22
22
|
import {
|
|
23
23
|
bareId,
|
|
24
24
|
canonicalId,
|
|
@@ -25,14 +25,14 @@ import type { Gateway } from "../../core/engine/gateway.js";
|
|
|
25
25
|
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
|
-
import { sendText, setWhatsAppBotName } from "./actions/
|
|
28
|
+
import { sendText, setWhatsAppBotName } from "./actions/send.js";
|
|
29
29
|
import { seedMessageStore } from "./message-store.js";
|
|
30
30
|
import {
|
|
31
31
|
maxWhatsAppMsgId,
|
|
32
32
|
pruneWhatsAppMessages,
|
|
33
33
|
} from "../../storage/whatsapp-messages.js";
|
|
34
34
|
import { flushAuthWrites } from "./auth-state.js";
|
|
35
|
-
import { registerPairingProvider } from "../../core/pairing-broker.js";
|
|
35
|
+
import { registerPairingProvider } from "../../core/frontend-runtime/pairing-broker.js";
|
|
36
36
|
import { beginPairingAttempt } from "./pairing-service.js";
|
|
37
37
|
import { runConnectionLoop } from "./connection.js";
|
|
38
38
|
import { lookupWhatsAppChat, type WhatsAppChatInfo } from "./registry.js";
|
|
@@ -16,7 +16,7 @@ import QRCode from "qrcode";
|
|
|
16
16
|
import type {
|
|
17
17
|
PairingAttempt,
|
|
18
18
|
PairingOutcome,
|
|
19
|
-
} from "../../core/pairing-broker.js";
|
|
19
|
+
} from "../../core/frontend-runtime/pairing-broker.js";
|
|
20
20
|
import { log, logError } from "../../util/log.js";
|
|
21
21
|
import { dirs } from "../../util/paths.js";
|
|
22
22
|
import { useAtomicAuthState, flushAuthWrites } from "./auth-state.js";
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { existsSync } from "node:fs";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { dirname, join, resolve } from "node:path";
|
|
26
|
-
import type { DoctorCheck } from "../../core/doctor.js";
|
|
26
|
+
import type { DoctorCheck } from "../../core/doctor/index.js";
|
|
27
27
|
import {
|
|
28
28
|
compareVersions,
|
|
29
29
|
expandHome,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { dirname, join, resolve } from "node:path";
|
|
19
|
-
import type { DoctorCheck } from "../../core/doctor.js";
|
|
19
|
+
import type { DoctorCheck } from "../../core/doctor/index.js";
|
|
20
20
|
import {
|
|
21
21
|
failDetail,
|
|
22
22
|
loadProvisionState,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cron repository — executes the statements in sql/cron.sql against
|
|
3
3
|
* the `cron_jobs` table; no SQL text lives here. The public store
|
|
4
|
-
* (storage/cron
|
|
4
|
+
* (storage/cron.ts) holds the domain API and validation; this
|
|
5
5
|
* module owns statement execution and the row↔domain mapping.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Goals repository — executes the statements in sql/goals.sql against
|
|
3
3
|
* the `goals` table; no SQL text lives here. The public store
|
|
4
|
-
* (storage/
|
|
4
|
+
* (storage/goals.ts) holds the domain API and validation; this
|
|
5
5
|
* module owns statement execution and the row↔domain mapping.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scripts repository — executes the statements in sql/scripts.sql
|
|
3
3
|
* against the `scripts` table; no SQL text lives here. The public
|
|
4
|
-
* store (storage/
|
|
4
|
+
* store (storage/scripts.ts) holds the domain API, validation,
|
|
5
5
|
* and the on-disk script files; this module owns statement execution
|
|
6
6
|
* and the row↔domain mapping.
|
|
7
7
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Triggers repository — executes the statements in sql/triggers.sql
|
|
3
3
|
* against the `triggers` table; no SQL text lives here. The public
|
|
4
|
-
* store (storage/
|
|
4
|
+
* store (storage/triggers.ts) holds the domain API, validation
|
|
5
5
|
* and the on-disk script/log handling; this module owns statement
|
|
6
6
|
* execution and the row↔domain mapping.
|
|
7
7
|
*/
|
|
@@ -140,7 +140,7 @@ CREATE INDEX IF NOT EXISTS idx_goals_chat_status ON goals(chat_id, status, updat
|
|
|
140
140
|
CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(status, updated_at DESC);
|
|
141
141
|
|
|
142
142
|
-- Agent-authored scripts. Metadata rows only: the script body lives on
|
|
143
|
-
-- disk under ~/.talon/workspace/scripts/ (mirroring the
|
|
143
|
+
-- disk under ~/.talon/workspace/scripts/ (mirroring the triggers store
|
|
144
144
|
-- split) so the agent can also Read/Edit a script as a normal workspace
|
|
145
145
|
-- file. Scripts are global capabilities, not chat data — no chat_id
|
|
146
146
|
-- column. `name` is the lookup key; UNIQUE enforces one per name.
|
|
@@ -145,7 +145,7 @@ CREATE INDEX IF NOT EXISTS idx_goals_chat_status ON goals(chat_id, status, updat
|
|
|
145
145
|
CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(status, updated_at DESC);
|
|
146
146
|
|
|
147
147
|
-- Agent-authored scripts. Metadata rows only: the script body lives on
|
|
148
|
-
-- disk under ~/.talon/workspace/scripts/ (mirroring the
|
|
148
|
+
-- disk under ~/.talon/workspace/scripts/ (mirroring the triggers store
|
|
149
149
|
-- split) so the agent can also Read/Edit a script as a normal workspace
|
|
150
150
|
-- file. Scripts are global capabilities, not chat data — no chat_id
|
|
151
151
|
-- column. \`name\` is the lookup key; UNIQUE enforces one per name.
|
|
@@ -561,7 +561,7 @@ FROM triggers ORDER BY created_at`,
|
|
|
561
561
|
SET status = 'terminated',
|
|
562
562
|
pid = NULL,
|
|
563
563
|
ended_at = COALESCE(ended_at, ?),
|
|
564
|
-
-- Literal must match RESTART_KILL_ERROR in storage/
|
|
564
|
+
-- Literal must match RESTART_KILL_ERROR in storage/triggers.ts.
|
|
565
565
|
last_error = COALESCE(last_error, 'Talon restarted while trigger was running')
|
|
566
566
|
WHERE status IN ('running', 'pending') AND persistent = 0
|
|
567
567
|
|
|
@@ -56,7 +56,7 @@ UPDATE triggers
|
|
|
56
56
|
SET status = 'terminated',
|
|
57
57
|
pid = NULL,
|
|
58
58
|
ended_at = COALESCE(ended_at, ?),
|
|
59
|
-
-- Literal must match RESTART_KILL_ERROR in storage/
|
|
59
|
+
-- Literal must match RESTART_KILL_ERROR in storage/triggers.ts.
|
|
60
60
|
last_error = COALESCE(last_error, 'Talon restarted while trigger was running')
|
|
61
61
|
WHERE status IN ('running', 'pending') AND persistent = 0
|
|
62
62
|
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Moderation actions — the `moderate` tool's member/chat/topic operations,
|
|
3
|
-
* plus profile-photo lookup.
|
|
4
|
-
*
|
|
5
|
-
* Handlers call the Bot API directly and let Telegram's own errors surface
|
|
6
|
-
* (missing admin rights come back as descriptive 400s the model can read);
|
|
7
|
-
* pre-checking rights here would just race the real check.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ChatPermissions } from "grammy/types";
|
|
11
|
-
import { toPositiveId } from "./shared.js";
|
|
12
|
-
import { resolveMediaInput } from "./media.js";
|
|
13
|
-
import { clearJoinRequest, listJoinRequests } from "../join-requests.js";
|
|
14
|
-
import type { TelegramActionHandlers } from "./types.js";
|
|
15
|
-
|
|
16
|
-
/** Seconds-from-now → Telegram until_date, honouring "omit = forever". */
|
|
17
|
-
function untilDate(minutes: unknown): number | undefined {
|
|
18
|
-
const n = Number(minutes);
|
|
19
|
-
if (!Number.isFinite(n) || n <= 0) return undefined;
|
|
20
|
-
return Math.floor(Date.now() / 1000) + Math.round(n * 60);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Friendly permission names → ChatPermissions. `send_media` fans out to all
|
|
25
|
-
* the per-kind media flags; raw `can_*` keys pass through untouched so the
|
|
26
|
-
* full API surface stays reachable.
|
|
27
|
-
*/
|
|
28
|
-
function toChatPermissions(raw: Record<string, boolean>): ChatPermissions {
|
|
29
|
-
const out: Record<string, boolean> = {};
|
|
30
|
-
const alias: Record<string, string[]> = {
|
|
31
|
-
send_messages: ["can_send_messages"],
|
|
32
|
-
send_media: [
|
|
33
|
-
"can_send_audios",
|
|
34
|
-
"can_send_documents",
|
|
35
|
-
"can_send_photos",
|
|
36
|
-
"can_send_videos",
|
|
37
|
-
"can_send_video_notes",
|
|
38
|
-
"can_send_voice_notes",
|
|
39
|
-
],
|
|
40
|
-
send_polls: ["can_send_polls"],
|
|
41
|
-
send_other: ["can_send_other_messages"],
|
|
42
|
-
web_previews: ["can_add_web_page_previews"],
|
|
43
|
-
change_info: ["can_change_info"],
|
|
44
|
-
invite_users: ["can_invite_users"],
|
|
45
|
-
pin_messages: ["can_pin_messages"],
|
|
46
|
-
manage_topics: ["can_manage_topics"],
|
|
47
|
-
};
|
|
48
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
49
|
-
if (typeof value !== "boolean") continue;
|
|
50
|
-
for (const target of alias[key] ?? (key.startsWith("can_") ? [key] : [])) {
|
|
51
|
-
out[target] = value;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Everything a mute takes away; unmute grants it back. */
|
|
58
|
-
const FULL_MEMBER_PERMISSIONS: ChatPermissions = toChatPermissions({
|
|
59
|
-
send_messages: true,
|
|
60
|
-
send_media: true,
|
|
61
|
-
send_polls: true,
|
|
62
|
-
send_other: true,
|
|
63
|
-
web_previews: true,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/** The workaday admin kit `promote` grants; demote sets all of these false. */
|
|
67
|
-
const DEFAULT_ADMIN_RIGHTS = {
|
|
68
|
-
can_manage_chat: true,
|
|
69
|
-
can_delete_messages: true,
|
|
70
|
-
can_restrict_members: true,
|
|
71
|
-
can_pin_messages: true,
|
|
72
|
-
can_invite_users: true,
|
|
73
|
-
can_change_info: true,
|
|
74
|
-
can_manage_topics: true,
|
|
75
|
-
can_manage_video_chats: true,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const moderationHandlers: TelegramActionHandlers = {
|
|
79
|
-
moderate: async (body, chatId, ctx) => {
|
|
80
|
-
const { bot, InputFileClass } = ctx;
|
|
81
|
-
const op = String(body.op ?? "");
|
|
82
|
-
const userId = toPositiveId(body.user_id);
|
|
83
|
-
const needsUser = [
|
|
84
|
-
"ban",
|
|
85
|
-
"unban",
|
|
86
|
-
"mute",
|
|
87
|
-
"unmute",
|
|
88
|
-
"promote",
|
|
89
|
-
"demote",
|
|
90
|
-
"set_admin_title",
|
|
91
|
-
"approve_join_request",
|
|
92
|
-
"decline_join_request",
|
|
93
|
-
];
|
|
94
|
-
if (needsUser.includes(op) && userId === undefined)
|
|
95
|
-
return { ok: false, error: `${op}: user_id is required` };
|
|
96
|
-
|
|
97
|
-
switch (op) {
|
|
98
|
-
case "ban":
|
|
99
|
-
await bot.api.banChatMember(chatId, userId!, {
|
|
100
|
-
until_date: untilDate(body.minutes),
|
|
101
|
-
revoke_messages: body.delete_messages === true || undefined,
|
|
102
|
-
});
|
|
103
|
-
return { ok: true };
|
|
104
|
-
case "unban":
|
|
105
|
-
// only_if_banned keeps this from kicking a current member.
|
|
106
|
-
await bot.api.unbanChatMember(chatId, userId!, {
|
|
107
|
-
only_if_banned: true,
|
|
108
|
-
});
|
|
109
|
-
return { ok: true };
|
|
110
|
-
case "mute":
|
|
111
|
-
await bot.api.restrictChatMember(
|
|
112
|
-
chatId,
|
|
113
|
-
userId!,
|
|
114
|
-
{ can_send_messages: false },
|
|
115
|
-
{ until_date: untilDate(body.minutes) },
|
|
116
|
-
);
|
|
117
|
-
return { ok: true };
|
|
118
|
-
case "unmute":
|
|
119
|
-
await bot.api.restrictChatMember(
|
|
120
|
-
chatId,
|
|
121
|
-
userId!,
|
|
122
|
-
FULL_MEMBER_PERMISSIONS,
|
|
123
|
-
);
|
|
124
|
-
return { ok: true };
|
|
125
|
-
case "promote":
|
|
126
|
-
await bot.api.promoteChatMember(chatId, userId!, DEFAULT_ADMIN_RIGHTS);
|
|
127
|
-
return { ok: true };
|
|
128
|
-
case "demote": {
|
|
129
|
-
const revoked = Object.fromEntries(
|
|
130
|
-
Object.keys(DEFAULT_ADMIN_RIGHTS).map((k) => [k, false]),
|
|
131
|
-
);
|
|
132
|
-
await bot.api.promoteChatMember(chatId, userId!, revoked);
|
|
133
|
-
return { ok: true };
|
|
134
|
-
}
|
|
135
|
-
case "set_admin_title":
|
|
136
|
-
await bot.api.setChatAdministratorCustomTitle(
|
|
137
|
-
chatId,
|
|
138
|
-
userId!,
|
|
139
|
-
String(body.title ?? ""),
|
|
140
|
-
);
|
|
141
|
-
return { ok: true };
|
|
142
|
-
case "set_permissions": {
|
|
143
|
-
const raw = body.permissions;
|
|
144
|
-
if (!raw || typeof raw !== "object")
|
|
145
|
-
return { ok: false, error: "set_permissions: permissions required" };
|
|
146
|
-
await bot.api.setChatPermissions(
|
|
147
|
-
chatId,
|
|
148
|
-
toChatPermissions(raw as Record<string, boolean>),
|
|
149
|
-
);
|
|
150
|
-
return { ok: true };
|
|
151
|
-
}
|
|
152
|
-
case "create_invite_link": {
|
|
153
|
-
const expireMin = Number(body.expire_minutes);
|
|
154
|
-
const link = await bot.api.createChatInviteLink(chatId, {
|
|
155
|
-
name: body.title ? String(body.title) : undefined,
|
|
156
|
-
expire_date:
|
|
157
|
-
Number.isFinite(expireMin) && expireMin > 0
|
|
158
|
-
? Math.floor(Date.now() / 1000) + Math.round(expireMin * 60)
|
|
159
|
-
: undefined,
|
|
160
|
-
member_limit: toPositiveId(body.member_limit),
|
|
161
|
-
});
|
|
162
|
-
return { ok: true, invite_link: link.invite_link };
|
|
163
|
-
}
|
|
164
|
-
case "revoke_invite_link": {
|
|
165
|
-
if (!body.link)
|
|
166
|
-
return { ok: false, error: "revoke_invite_link: link required" };
|
|
167
|
-
await bot.api.revokeChatInviteLink(chatId, String(body.link));
|
|
168
|
-
return { ok: true };
|
|
169
|
-
}
|
|
170
|
-
case "approve_join_request":
|
|
171
|
-
await bot.api.approveChatJoinRequest(chatId, userId!);
|
|
172
|
-
clearJoinRequest(chatId, userId!);
|
|
173
|
-
return { ok: true };
|
|
174
|
-
case "decline_join_request":
|
|
175
|
-
await bot.api.declineChatJoinRequest(chatId, userId!);
|
|
176
|
-
clearJoinRequest(chatId, userId!);
|
|
177
|
-
return { ok: true };
|
|
178
|
-
case "list_join_requests":
|
|
179
|
-
return { ok: true, requests: listJoinRequests(chatId) };
|
|
180
|
-
case "set_chat_photo": {
|
|
181
|
-
const resolved = resolveMediaInput(
|
|
182
|
-
body,
|
|
183
|
-
"set_chat_photo",
|
|
184
|
-
InputFileClass,
|
|
185
|
-
);
|
|
186
|
-
if ("error" in resolved) return { ok: false, error: resolved.error };
|
|
187
|
-
if (typeof resolved.file === "string")
|
|
188
|
-
return {
|
|
189
|
-
ok: false,
|
|
190
|
-
error: "set_chat_photo needs a local file_path (no url/file_id)",
|
|
191
|
-
};
|
|
192
|
-
await bot.api.setChatPhoto(chatId, resolved.file);
|
|
193
|
-
return { ok: true };
|
|
194
|
-
}
|
|
195
|
-
case "delete_chat_photo":
|
|
196
|
-
await bot.api.deleteChatPhoto(chatId);
|
|
197
|
-
return { ok: true };
|
|
198
|
-
case "unpin_all":
|
|
199
|
-
await bot.api.unpinAllChatMessages(chatId);
|
|
200
|
-
return { ok: true };
|
|
201
|
-
case "leave_chat":
|
|
202
|
-
await bot.api.leaveChat(chatId);
|
|
203
|
-
return { ok: true };
|
|
204
|
-
case "create_topic": {
|
|
205
|
-
if (!body.title)
|
|
206
|
-
return { ok: false, error: "create_topic: title required" };
|
|
207
|
-
const topic = await bot.api.createForumTopic(
|
|
208
|
-
chatId,
|
|
209
|
-
String(body.title),
|
|
210
|
-
);
|
|
211
|
-
return { ok: true, thread_id: topic.message_thread_id };
|
|
212
|
-
}
|
|
213
|
-
case "edit_topic": {
|
|
214
|
-
const threadId = toPositiveId(body.thread_id);
|
|
215
|
-
if (threadId === undefined)
|
|
216
|
-
return { ok: false, error: "edit_topic: thread_id required" };
|
|
217
|
-
await bot.api.editForumTopic(chatId, threadId, {
|
|
218
|
-
name: body.title ? String(body.title) : undefined,
|
|
219
|
-
});
|
|
220
|
-
return { ok: true };
|
|
221
|
-
}
|
|
222
|
-
case "close_topic":
|
|
223
|
-
case "reopen_topic":
|
|
224
|
-
case "delete_topic": {
|
|
225
|
-
const threadId = toPositiveId(body.thread_id);
|
|
226
|
-
if (threadId === undefined)
|
|
227
|
-
return { ok: false, error: `${op}: thread_id required` };
|
|
228
|
-
if (op === "close_topic")
|
|
229
|
-
await bot.api.closeForumTopic(chatId, threadId);
|
|
230
|
-
else if (op === "reopen_topic")
|
|
231
|
-
await bot.api.reopenForumTopic(chatId, threadId);
|
|
232
|
-
else await bot.api.deleteForumTopic(chatId, threadId);
|
|
233
|
-
return { ok: true };
|
|
234
|
-
}
|
|
235
|
-
default:
|
|
236
|
-
return { ok: false, error: `Unknown moderation op: ${op}` };
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
get_user_profile_photos: async (body, _chatId, { bot }) => {
|
|
241
|
-
const userId = toPositiveId(body.user_id);
|
|
242
|
-
if (userId === undefined)
|
|
243
|
-
return { ok: false, error: "user_id is required" };
|
|
244
|
-
const limit = toPositiveId(body.limit) ?? 5;
|
|
245
|
-
const photos = await bot.api.getUserProfilePhotos(userId, {
|
|
246
|
-
limit: Math.min(limit, 100),
|
|
247
|
-
});
|
|
248
|
-
return {
|
|
249
|
-
ok: true,
|
|
250
|
-
total: photos.total_count,
|
|
251
|
-
// Largest size of each photo; its file_id sends/downloads like media.
|
|
252
|
-
file_ids: photos.photos.map((sizes) => sizes[sizes.length - 1].file_id),
|
|
253
|
-
};
|
|
254
|
-
},
|
|
255
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|