talon-agent 4.3.2 → 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.
Files changed (61) hide show
  1. package/package.json +1 -1
  2. package/src/backend/claude-sdk/doctor.ts +1 -1
  3. package/src/backend/codex/doctor.ts +1 -1
  4. package/src/backend/openai-agents/doctor.ts +1 -1
  5. package/src/bootstrap.ts +1 -1
  6. package/src/cli/doctor.ts +1 -1
  7. package/src/core/agent-runtime/backend-registry.ts +1 -1
  8. package/src/core/auth/expiry-monitor.ts +1 -1
  9. package/src/core/{doctor.ts → doctor/index.ts} +11 -11
  10. package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
  11. package/src/core/plugin/native-runtimes.ts +1 -1
  12. package/src/frontend/discord/commands/admin.ts +1 -1
  13. package/src/frontend/discord/render.ts +1 -1
  14. package/src/frontend/telegram/actions/index.ts +1 -1
  15. package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
  16. package/src/frontend/telegram/actions/moderation/index.ts +74 -0
  17. package/src/frontend/telegram/actions/moderation/members.ts +77 -0
  18. package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
  19. package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
  20. package/src/frontend/telegram/actions/moderation/types.ts +16 -0
  21. package/src/frontend/telegram/admin/background.ts +66 -0
  22. package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
  23. package/src/frontend/telegram/admin/health.ts +81 -0
  24. package/src/frontend/telegram/admin/sessions.ts +105 -0
  25. package/src/frontend/telegram/admin.ts +56 -261
  26. package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
  27. package/src/frontend/telegram/callbacks/model/control.ts +29 -0
  28. package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
  29. package/src/frontend/telegram/callbacks/model/select.ts +100 -0
  30. package/src/frontend/telegram/callbacks/model/types.ts +39 -0
  31. package/src/frontend/telegram/callbacks/model/views.ts +116 -0
  32. package/src/frontend/telegram/callbacks/model.ts +61 -331
  33. package/src/frontend/telegram/commands/admin.ts +108 -77
  34. package/src/frontend/telegram/commands/settings.ts +65 -43
  35. package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
  36. package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
  37. package/src/frontend/telegram/middleware.ts +166 -201
  38. package/src/frontend/terminal/builtins/context.ts +184 -0
  39. package/src/frontend/terminal/builtins/help.ts +35 -0
  40. package/src/frontend/terminal/builtins/index.ts +23 -0
  41. package/src/frontend/terminal/builtins/model.ts +198 -0
  42. package/src/frontend/terminal/builtins/session.ts +101 -0
  43. package/src/frontend/terminal/builtins/status.ts +202 -0
  44. package/src/frontend/terminal/command-registry.ts +80 -0
  45. package/src/frontend/terminal/commands.ts +11 -674
  46. package/src/frontend/terminal/input/history.ts +64 -0
  47. package/src/frontend/terminal/input/keys.ts +143 -0
  48. package/src/frontend/terminal/input/parts.ts +33 -0
  49. package/src/frontend/terminal/input/paste.ts +53 -0
  50. package/src/frontend/terminal/input/render.ts +42 -0
  51. package/src/frontend/terminal/input/state.ts +60 -0
  52. package/src/frontend/terminal/input.ts +27 -342
  53. package/src/frontend/whatsapp/connection.ts +1 -1
  54. package/src/frontend/whatsapp/index.ts +1 -1
  55. package/src/frontend/whatsapp/pairing-service.ts +1 -1
  56. package/src/plugins/github/provision.ts +1 -1
  57. package/src/plugins/mempalace/provision.ts +1 -1
  58. package/src/plugins/playwright/provision.ts +1 -1
  59. package/src/frontend/telegram/actions/moderation.ts +0 -255
  60. /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
  61. /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `model:done` / `model:noop` / unparseable — ack-only callbacks. Nothing
3
+ * to redraw; each answers the query itself.
4
+ */
5
+
6
+ import type { Context } from "grammy";
7
+ import { answerCallbackQuerySafe } from "../shared.js";
8
+ import type { ModelOutcome } from "./types.js";
9
+
10
+ // Acknowledge fast (within Telegram's 30s window) so the user
11
+ // doesn't see a perpetual loading spinner.
12
+ export async function handleDone(
13
+ ctx: Context,
14
+ ): Promise<ModelOutcome | undefined> {
15
+ await answerCallbackQuerySafe(ctx, { text: "Done" });
16
+ try {
17
+ await ctx.deleteMessage();
18
+ } catch {
19
+ /* might lack delete permission */
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ export async function handleNoop(
25
+ ctx: Context,
26
+ ): Promise<ModelOutcome | undefined> {
27
+ await answerCallbackQuerySafe(ctx);
28
+ return undefined;
29
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * View navigation — `model:menu`, `model:browse`, `model:nav:*`,
3
+ * `model:backends`. No state changes; each just names the view to redraw.
4
+ */
5
+
6
+ import type { ModelCallback } from "../../model-callbacks.js";
7
+ import type { ModelOutcome } from "./types.js";
8
+
9
+ export async function showMenu(): Promise<ModelOutcome> {
10
+ return { view: { kind: "menu" } };
11
+ }
12
+
13
+ export async function showBrowse(): Promise<ModelOutcome> {
14
+ return { view: { kind: "browse" } };
15
+ }
16
+
17
+ export async function navBackToProviders(): Promise<ModelOutcome> {
18
+ return { view: { kind: "browse", backToGroups: true, page: 1 } };
19
+ }
20
+
21
+ export async function navProvider(
22
+ _ctx: unknown,
23
+ action: Extract<ModelCallback, { kind: "nav-provider" }>,
24
+ ): Promise<ModelOutcome> {
25
+ return { view: { kind: "browse", provider: action.provider, page: 1 } };
26
+ }
27
+
28
+ export async function navPage(
29
+ _ctx: unknown,
30
+ action: Extract<ModelCallback, { kind: "nav-page" }>,
31
+ ): Promise<ModelOutcome> {
32
+ return {
33
+ view: {
34
+ kind: "browse",
35
+ page: action.page,
36
+ filter: action.filter,
37
+ provider: action.provider,
38
+ },
39
+ };
40
+ }
41
+
42
+ // Legacy support — current UX promotes free-toggle on the main
43
+ // menu, but a `model:nav:filter:*` payload from an old message
44
+ // still routes to the browse view with that filter applied.
45
+ export async function navFilter(
46
+ _ctx: unknown,
47
+ action: Extract<ModelCallback, { kind: "nav-filter" }>,
48
+ ): Promise<ModelOutcome> {
49
+ return { view: { kind: "browse", filter: action.filter, page: 1 } };
50
+ }
51
+
52
+ export async function showBackends(): Promise<ModelOutcome> {
53
+ return { view: { kind: "backends" } };
54
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Model selection — `model:<id>`, `model:reset`, `model:toggle-free`.
3
+ *
4
+ * Every branch resolves the *per-chat* backend so the pick is validated
5
+ * against, and stored under, the backend the chat actually runs on.
6
+ */
7
+
8
+ import type { Context } from "grammy";
9
+ import {
10
+ setChatModelForBackend,
11
+ setChatBackend,
12
+ } from "../../../../storage/chat-settings.js";
13
+ import { resolveModelId as resolveModelName } from "../../../../core/models/catalog.js";
14
+ import { getBackendIdForChat } from "../../../../core/engine/backend-controller/index.js";
15
+ import { resolveActiveModelForChat } from "../../../../core/models/active-model.js";
16
+ import type { ModelCallback } from "../../model-callbacks.js";
17
+ import { resolveBackendForChat, toggleChatFreeOnly } from "../../model-menu.js";
18
+ import { answerCallbackQuerySafe, type CallbackDeps } from "../shared.js";
19
+ import type { ModelOutcome } from "./types.js";
20
+
21
+ export async function handleSelect(
22
+ ctx: Context,
23
+ action: Extract<ModelCallback, { kind: "select" }>,
24
+ cid: string,
25
+ { config, gateway }: CallbackDeps,
26
+ ): Promise<ModelOutcome | undefined> {
27
+ // Resolve selection against the *per-chat* backend — if the
28
+ // chat has switched to openai-agents we must validate the id
29
+ // against that catalog, not the global default's.
30
+ const be = resolveBackendForChat(cid, gateway);
31
+ const beId = getBackendIdForChat(cid);
32
+ if (be?.models?.resolveModelInfo) {
33
+ const resolution = await be.models?.resolveModelInfo(action.modelId);
34
+ if (resolution.kind !== "exact" || !resolution.model.selectable) {
35
+ await answerCallbackQuerySafe(ctx, {
36
+ text:
37
+ resolution.kind === "exact"
38
+ ? (resolution.model.unavailableReason ?? "Unavailable")
39
+ : "Model is unavailable",
40
+ });
41
+ return undefined;
42
+ }
43
+ // Persist the pick into the chat's *backend-specific* slot.
44
+ // Switching backends later restores each side's prior choice
45
+ // automatically — the slot isn't shared across backends.
46
+ setChatModelForBackend(cid, beId, resolution.storedValue);
47
+ // Also pin the chat to this backend so a restart doesn't
48
+ // unbind to the role-default and orphan the model id.
49
+ setChatBackend(cid, beId);
50
+ return {
51
+ toast: `Model: ${resolution.model.displayName}`,
52
+ view: { kind: "menu" },
53
+ };
54
+ }
55
+ setChatModelForBackend(cid, beId, resolveModelName(action.modelId));
56
+ setChatBackend(cid, beId);
57
+ const { model: resolved } = await resolveActiveModelForChat(
58
+ cid,
59
+ be,
60
+ beId,
61
+ config,
62
+ );
63
+ return { toast: `Model: ${resolved ?? "(unset)"}`, view: { kind: "menu" } };
64
+ }
65
+
66
+ export async function handleReset(
67
+ _ctx: Context,
68
+ _action: Extract<ModelCallback, { kind: "reset" }>,
69
+ cid: string,
70
+ { config, gateway }: CallbackDeps,
71
+ ): Promise<ModelOutcome> {
72
+ // Clear this backend's slot only — other backends' picks stay.
73
+ // Compute the toast through the resolver so the label names the
74
+ // backend's actual default (or "No model selected" when there
75
+ // isn't one — catalog-driven backend, no operator config).
76
+ const be = resolveBackendForChat(cid, gateway);
77
+ const beId = getBackendIdForChat(cid);
78
+ setChatModelForBackend(cid, beId, undefined);
79
+ const { model: resolvedDefault } = await resolveActiveModelForChat(
80
+ cid,
81
+ be,
82
+ beId,
83
+ config,
84
+ );
85
+ return {
86
+ toast: resolvedDefault
87
+ ? `Model reset to default (${resolvedDefault})`
88
+ : `Model reset — no default available, pick one`,
89
+ view: { kind: "menu" },
90
+ };
91
+ }
92
+
93
+ export async function handleToggleFree(
94
+ _ctx: Context,
95
+ _action: Extract<ModelCallback, { kind: "toggle-free" }>,
96
+ cid: string,
97
+ ): Promise<ModelOutcome> {
98
+ const next = toggleChatFreeOnly(cid);
99
+ return { toast: `Free only: ${next ? "on" : "off"}`, view: { kind: "menu" } };
100
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Types for the `model:*` callback handlers.
3
+ *
4
+ * A handler mutates at most one piece of chat-settings (or pool) state and
5
+ * hands the router what to do next: a toast to answer the query with (or a
6
+ * plain ack) and the view to redraw. It returns `undefined` when it already
7
+ * answered the query itself — an error toast, or the "done" close — and
8
+ * there is nothing to redraw.
9
+ */
10
+
11
+ import type { Context } from "grammy";
12
+ import type { ModelCallback } from "../../model-callbacks.js";
13
+ import type { CallbackDeps } from "../shared.js";
14
+
15
+ export type ModelView =
16
+ | { kind: "menu" }
17
+ | { kind: "backends" }
18
+ | {
19
+ kind: "browse";
20
+ page?: number;
21
+ filter?: "all" | "free";
22
+ provider?: string;
23
+ backToGroups?: boolean;
24
+ };
25
+
26
+ export type ModelOutcome = { toast?: string; view: ModelView };
27
+
28
+ export type ModelCallbackHandler<A extends ModelCallback = ModelCallback> = (
29
+ ctx: Context,
30
+ action: A,
31
+ cid: string,
32
+ deps: CallbackDeps,
33
+ ) => Promise<ModelOutcome | undefined>;
34
+
35
+ export type ModelCallbackHandlers = {
36
+ [K in ModelCallback["kind"]]: ModelCallbackHandler<
37
+ Extract<ModelCallback, { kind: K }>
38
+ >;
39
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Re-render the /model message in one of its three views: main menu,
3
+ * backend submenu, or browse.
4
+ */
5
+
6
+ import type { Context } from "grammy";
7
+ import { escapeHtml } from "../../formatting.js";
8
+ import {
9
+ renderModelMenuText,
10
+ renderModelMenuKeyboard,
11
+ renderModelBrowseKeyboard,
12
+ renderBackendMenuKeyboard,
13
+ renderBackendMenuText,
14
+ } from "../../helpers/index.js";
15
+ import {
16
+ buildModelMenuViewForChat,
17
+ buildModelBrowseViewForChat,
18
+ buildBackendMenuViewForChat,
19
+ } from "../../model-menu.js";
20
+ import { editOrIgnoreSame, type CallbackDeps } from "../shared.js";
21
+ import type { ModelView } from "./types.js";
22
+
23
+ export async function renderModelView(
24
+ ctx: Context,
25
+ view: ModelView,
26
+ cid: string,
27
+ deps: CallbackDeps,
28
+ ): Promise<void> {
29
+ if (view.kind === "menu") return renderMenu(ctx, cid, deps);
30
+ if (view.kind === "backends") return renderBackends(ctx, cid, deps);
31
+ return renderBrowse(ctx, view, cid, deps);
32
+ }
33
+
34
+ async function renderMenu(
35
+ ctx: Context,
36
+ cid: string,
37
+ { config, gateway }: CallbackDeps,
38
+ ): Promise<void> {
39
+ const view = await buildModelMenuViewForChat(cid, config, gateway);
40
+ if (!view) return;
41
+ await editOrIgnoreSame(
42
+ ctx,
43
+ renderModelMenuText(view.state),
44
+ renderModelMenuKeyboard(view.state),
45
+ );
46
+ }
47
+
48
+ async function renderBackends(
49
+ ctx: Context,
50
+ cid: string,
51
+ { config }: CallbackDeps,
52
+ ): Promise<void> {
53
+ const menu = buildBackendMenuViewForChat(cid, config);
54
+ await editOrIgnoreSame(
55
+ ctx,
56
+ renderBackendMenuText({
57
+ activeBackend: menu.activeBackend,
58
+ hasBackendOverride: menu.hasBackendOverride,
59
+ defaultBackendLabel: menu.defaultBackendLabel,
60
+ }),
61
+ renderBackendMenuKeyboard({
62
+ available: menu.available,
63
+ activeBackendId: menu.activeBackend.id,
64
+ hasBackendOverride: menu.hasBackendOverride,
65
+ }),
66
+ );
67
+ }
68
+
69
+ // browse view — controller picks the per-chat backend and
70
+ // applies the chat's freeOnly preference when the caller
71
+ // doesn't override the filter explicitly. `backToGroups`
72
+ // means "drop any cached provider drill" so we omit `provider`.
73
+ async function renderBrowse(
74
+ ctx: Context,
75
+ view: Extract<ModelView, { kind: "browse" }>,
76
+ cid: string,
77
+ { config, gateway }: CallbackDeps,
78
+ ): Promise<void> {
79
+ const browse = await buildModelBrowseViewForChat(
80
+ cid,
81
+ config,
82
+ {
83
+ ...(view.filter !== undefined ? { filter: view.filter } : {}),
84
+ ...(view.page !== undefined ? { page: view.page } : {}),
85
+ ...(view.provider !== undefined && !view.backToGroups
86
+ ? { provider: view.provider }
87
+ : {}),
88
+ },
89
+ gateway,
90
+ );
91
+ if (!browse) return;
92
+ const lines = [
93
+ `<b>Model:</b> <code>${escapeHtml(browse.activeDisplay)}</code>`,
94
+ ...browse.modelDetails.map(escapeHtml),
95
+ ...(browse.filter === "free" && browse.freeCount > 0
96
+ ? ["<i>Filter: free-tier only.</i>"]
97
+ : []),
98
+ ];
99
+ await editOrIgnoreSame(
100
+ ctx,
101
+ lines.join("\n"),
102
+ renderModelBrowseKeyboard(
103
+ browse.modelButtons,
104
+ {
105
+ page: browse.page,
106
+ totalPages: browse.totalPages,
107
+ filter: browse.filter,
108
+ freeCount: browse.freeCount,
109
+ totalCount: browse.totalCount,
110
+ },
111
+ browse.view,
112
+ browse.provider,
113
+ "model:menu",
114
+ ),
115
+ );
116
+ }