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.
- package/package.json +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 +1 -1
- package/src/cli/doctor.ts +1 -1
- package/src/core/agent-runtime/backend-registry.ts +1 -1
- package/src/core/auth/expiry-monitor.ts +1 -1
- package/src/core/{doctor.ts → doctor/index.ts} +11 -11
- package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
- package/src/core/plugin/native-runtimes.ts +1 -1
- package/src/frontend/discord/commands/admin.ts +1 -1
- package/src/frontend/discord/render.ts +1 -1
- package/src/frontend/telegram/actions/index.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/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/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/connection.ts +1 -1
- package/src/frontend/whatsapp/index.ts +1 -1
- 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/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
|
@@ -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
|
+
}
|