@coffer-org/plugin-telegram 1.3.2 → 2.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/dist/index.js CHANGED
@@ -12,6 +12,16 @@ export default definePlugin({
12
12
  field.password({ key: 'api_hash', label: 'telegram.settings.api_hash' }),
13
13
  field.password({ key: 'bot_token', label: 'telegram.settings.bot_token' }),
14
14
  field.tel({ key: 'phone', label: 'telegram.settings.phone' }),
15
+ field.select({
16
+ key: 'reasoning_display',
17
+ label: 'telegram.settings.reasoning_display',
18
+ default: 'collapsible',
19
+ options: [
20
+ { value: 'off', title: 'telegram.settings.reasoning_display_off' },
21
+ { value: 'collapsible', title: 'telegram.settings.reasoning_display_collapsible' },
22
+ { value: 'separate', title: 'telegram.settings.reasoning_display_separate' },
23
+ ],
24
+ }),
15
25
  ],
16
26
  }),
17
27
  });
@@ -1,4 +1,11 @@
1
- import type { LoadOpts } from './config.ts';
1
+ import type { LiveChannelOps, RenderFn, ReplyChannel, ReplyContext } from '@coffer-org/plugin-orchestrator/runtime';
2
+ import type { LoadOpts, ReasoningDisplay } from './config.ts';
3
+ export declare function stripHtml(s: string): string;
4
+ export declare function escHtml(s: string): string;
5
+ export declare function renderWithReasoning(mode: ReasoningDisplay, max: number): RenderFn;
6
+ export declare const TG_MAX = 4096;
7
+ export declare const TG_THROTTLE_MS = 2000;
8
+ export declare function makeReplyFactory(t: LiveChannelOps, display: ReasoningDisplay): (_chatId: string, _ctx: ReplyContext) => ReplyChannel;
2
9
  export type StartOpts = LoadOpts;
3
10
  export declare function startBot(opts?: StartOpts): Promise<boolean>;
4
11
  export declare function stopBot(): Promise<void>;
@@ -1,13 +1,82 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { TelegramClient, Api } from 'telegram';
4
- import { LogLevel } from 'telegram/extensions/Logger.js';
5
- import { StringSession } from 'telegram/sessions/index.js';
6
- import { NewMessage } from 'telegram/events/index.js';
7
- import { handleIncoming } from '@coffer-org/plugin-orchestrator/runtime';
8
- import { loadBotConfig, hasCredentials } from "./config.js";
3
+ import { TelegramClient, Api } from 'teleproto';
4
+ import { LogLevel } from 'teleproto/extensions/Logger.js';
5
+ import { StringSession } from 'teleproto/sessions/index.js';
6
+ import { NewMessage } from 'teleproto/events/index.js';
7
+ import { handleIncoming, loadGatePolicy, makeLiveChannel, chunk } from '@coffer-org/plugin-orchestrator/runtime';
8
+ import { recordUser, recordAssistant, buildChain } from "./chain-store.js";
9
+ import { loadBotConfig, hasCredentials, loadReasoningDisplay } from "./config.js";
9
10
  import { getLogger } from '@coffer-org/sdk/logger';
10
11
  const log = getLogger('telegram');
12
+ const FALLBACK_REPLY_WINDOW_MS = 1_800_000;
13
+ const lastByChat = new Map();
14
+ async function replyWindowMs() {
15
+ try {
16
+ return (await loadGatePolicy()).replyWindow * 1000;
17
+ }
18
+ catch (err) {
19
+ log.warn(`gate policy load: ${err instanceof Error ? err.message : String(err)} — using default reply window`);
20
+ return FALLBACK_REPLY_WINDOW_MS;
21
+ }
22
+ }
23
+ export function stripHtml(s) {
24
+ return s
25
+ .replace(/<[^>]*>/g, '')
26
+ .replace(/&lt;/g, '<')
27
+ .replace(/&gt;/g, '>')
28
+ .replace(/&quot;/g, '"')
29
+ .replace(/&#39;/g, "'")
30
+ .replace(/&amp;/g, '&');
31
+ }
32
+ export function escHtml(s) {
33
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
34
+ }
35
+ const OPEN = '<blockquote expandable>';
36
+ const CLOSE = '</blockquote>\n\n';
37
+ const MIN_REASONING = 100;
38
+ export function renderWithReasoning(mode, max) {
39
+ return ({ text, reasoning }) => {
40
+ const answer = (text ?? '').trim() || '⚠️ the agent returned no response';
41
+ if (mode !== 'collapsible')
42
+ return chunk(answer, max);
43
+ const raw = (reasoning ?? '').trim();
44
+ if (!raw)
45
+ return chunk(answer, max);
46
+ const parts = chunk(answer, max);
47
+ const head = parts[0];
48
+ const room = max - head.length - OPEN.length - CLOSE.length;
49
+ if (room < MIN_REASONING)
50
+ return parts;
51
+ let body = escHtml(raw);
52
+ if (body.length > room)
53
+ body = `…${body.slice(-(room - 1))}`;
54
+ return [`${OPEN}${body}${CLOSE}${head}`, ...parts.slice(1)];
55
+ };
56
+ }
57
+ const TELEGRAM_FORMAT = [
58
+ 'CRITICAL OUTPUT FORMAT — Telegram HTML ONLY. Format the reply strictly with Telegram\'s HTML subset (<b>, <i>, <code>, <a>, …) and NOTHING else. NEVER use Markdown — no *bold*, _italic_, **bold**, `code`, ```blocks```, # headings, - bullets, or | tables. Markdown does not render here; it shows up as literal characters and breaks the message. When in doubt, use an HTML tag or plain text.',
59
+ 'Inline styling (tags may nest): <b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strike</s>, <code>inline code</code>, <spoiler>spoiler</spoiler>. Links: <a href="https://…">text</a> (also <a href="tg://user?id=123">mention</a>).',
60
+ 'Blocks: <pre>code block</pre> or, for syntax highlight, <pre><code class="language-python">…</code></pre>. Quotes: <blockquote>quote</blockquote>, or <blockquote expandable>long collapsible quote</blockquote>. Bullet lists: start plain lines with "• ".',
61
+ 'Only those tags are parsed. NOT supported here (do not use): <tg-spoiler>, <span>, <ins>, <strike>, <tg-time> — use <spoiler>, <u>, <s> instead. Avoid <tg-emoji> (needs a custom-emoji id + Premium); use ordinary emoji.',
62
+ 'DO NOT use markdown (*, _, #, ```), tables, or images. Escape literal <, >, and & in prose as &lt;, &gt;, and &amp; so they are not read as tags.',
63
+ 'Render any TABLE as a <pre> block with space-aligned columns — real tables are unsupported.',
64
+ 'Beyond the tags, use Unicode symbols and emoji to structure and enrich replies — they render everywhere: bullets (•, ▪, –), separators (─────), arrows (→), status marks (✅ ⚠️ ❌ ⏳), and topic emoji (📅 💰 🏥 🛒 🌱 …). They make messages scannable and friendly.',
65
+ 'Keep replies short and skimmable (messages cap at 4096 chars): prefer short paragraphs and "• " bullet lists over long prose.',
66
+ ].join('\n');
67
+ export const TG_MAX = 4096;
68
+ export const TG_THROTTLE_MS = 2000;
69
+ export function makeReplyFactory(t, display) {
70
+ return (_chatId, _ctx) => {
71
+ const ch = makeLiveChannel({
72
+ ops: t,
73
+ throttleMs: TG_THROTTLE_MS,
74
+ maxLength: TG_MAX,
75
+ render: renderWithReasoning(display, TG_MAX),
76
+ });
77
+ return display === 'separate' ? ch : { ...ch, segment() { } };
78
+ };
79
+ }
11
80
  function loadSessionString(file) {
12
81
  try {
13
82
  return fs.readFileSync(file, 'utf-8');
@@ -46,36 +115,62 @@ export async function startBot(opts = {}) {
46
115
  const me = (await tg.getMe());
47
116
  const meId = me?.id?.toString();
48
117
  log.info(`connected as @${me?.username || me?.firstName || meId}`);
49
- async function sendMd(peer, text) {
118
+ async function sendHtml(peer, text, replyTo) {
50
119
  try {
51
- return await tg.sendMessage(peer, { message: text, parseMode: 'md' });
120
+ return await tg.sendMessage(peer, { message: text, parseMode: 'html', replyTo });
52
121
  }
53
122
  catch {
54
123
  try {
55
- return await tg.sendMessage(peer, { message: text });
124
+ return await tg.sendMessage(peer, { message: stripHtml(text), replyTo });
56
125
  }
57
126
  catch {
58
127
  return undefined;
59
128
  }
60
129
  }
61
130
  }
62
- async function editMd(peer, msgId, text) {
131
+ async function editHtml(peer, msgId, text) {
63
132
  try {
64
- await tg.editMessage(peer, { message: msgId, text, parseMode: 'md' });
133
+ await tg.editMessage(peer, { message: msgId, text, parseMode: 'html' });
65
134
  }
66
135
  catch {
67
136
  try {
68
- await tg.editMessage(peer, { message: msgId, text });
137
+ await tg.editMessage(peer, { message: msgId, text: stripHtml(text) });
69
138
  }
70
139
  catch {
71
140
  }
72
141
  }
73
142
  }
74
143
  function startTypingPeer(peer) {
75
- const ping = () => tg.invoke(new Api.messages.SetTyping({ peer, action: new Api.SendMessageTypingAction() })).catch(() => { });
76
- void ping();
77
- const timer = setInterval(() => void ping(), 5000);
78
- return () => clearInterval(timer);
144
+ let logged = false;
145
+ let stopped = false;
146
+ let timer;
147
+ const warn = (err) => {
148
+ if (logged)
149
+ return;
150
+ logged = true;
151
+ log.warn(`typing indicator: ${err instanceof Error ? err.message : String(err)}`);
152
+ };
153
+ void (async () => {
154
+ let input;
155
+ try {
156
+ input = await tg.getInputEntity(peer);
157
+ }
158
+ catch (err) {
159
+ warn(err);
160
+ return;
161
+ }
162
+ if (stopped)
163
+ return;
164
+ const ping = () => tg
165
+ .invoke(new Api.messages.SetTyping({ peer: input, action: new Api.SendMessageTypingAction() }))
166
+ .catch(warn);
167
+ void ping();
168
+ timer = setInterval(() => void ping(), 5000);
169
+ })();
170
+ return () => {
171
+ stopped = true;
172
+ clearInterval(timer);
173
+ };
79
174
  }
80
175
  tg.addEventHandler(async (event) => {
81
176
  try {
@@ -100,31 +195,75 @@ export async function startBot(opts = {}) {
100
195
  catch {
101
196
  }
102
197
  log.info(`msg [${chatLabel}]: ${text.slice(0, 80)}`);
103
- const chatId = String(event.chatId ?? '?');
104
- const userId = message.senderId?.toString() ?? '?';
105
- const replyTo = message.replyTo?.replyToMsgId ?? null;
106
- const connector = {
107
- id: 'telegram',
108
- capabilities: { streaming: true, typing: true, maxMessageLength: 4096 },
109
- async sendMessage(_chatId, msgText) {
110
- const sent = await sendMd(peer, msgText);
111
- if (!sent)
198
+ const stopTyping = startTypingPeer(peer);
199
+ try {
200
+ const chatId = String(event.chatId ?? '?');
201
+ const userId = message.senderId?.toString() ?? '?';
202
+ const incomingMsgId = String(message.id);
203
+ const replyTo = message.replyTo?.replyToMsgId ?? null;
204
+ let replyToId = replyTo != null ? String(replyTo) : null;
205
+ const nowMs = Date.now();
206
+ if (!replyToId) {
207
+ const prev = lastByChat.get(chatId);
208
+ if (prev && nowMs - prev.ts < (await replyWindowMs()))
209
+ replyToId = prev.msgId;
210
+ }
211
+ lastByChat.set(chatId, { msgId: incomingMsgId, ts: nowMs });
212
+ let displayName;
213
+ try {
214
+ const s = (await message.getSender());
215
+ displayName = s?.firstName || s?.username || undefined;
216
+ }
217
+ catch {
218
+ }
219
+ await recordUser({ chatId, msgId: incomingMsgId, sender: displayName, text, ts: message.date ?? 0, replyToId });
220
+ const fetch = async (id) => {
221
+ try {
222
+ const arr = (await tg.getMessages(peer, { ids: [Number(id)] }));
223
+ const m = arr?.[0];
224
+ if (!m)
225
+ return null;
226
+ return {
227
+ text: m.message ?? '',
228
+ senderId: m.senderId?.toString() ?? '',
229
+ replyToId: m.replyTo?.replyToMsgId?.toString() ?? null,
230
+ date: m.date ?? 0,
231
+ };
232
+ }
233
+ catch {
112
234
  return null;
113
- return { msgId: String(sent.id) };
114
- },
115
- async editMessage(_chatId, msgId, msgText) {
116
- await editMd(peer, Number(msgId), msgText);
117
- },
118
- startTyping(_chatId) {
119
- return startTypingPeer(peer);
120
- },
121
- };
122
- await handleIncoming(connector, {
123
- chatId,
124
- userId,
125
- text,
126
- replyToId: replyTo != null ? String(replyTo) : null,
127
- });
235
+ }
236
+ };
237
+ const messages = await buildChain(incomingMsgId, { chatId, fetch, botId: meId ?? '' });
238
+ const reasoningDisplay = await loadReasoningDisplay();
239
+ const connector = {
240
+ id: 'telegram',
241
+ reply: makeReplyFactory({
242
+ send: async (text) => {
243
+ const sent = await sendHtml(peer, text, Number(incomingMsgId));
244
+ return sent ? String(sent.id) : null;
245
+ },
246
+ edit: async (msgId, text) => {
247
+ await editHtml(peer, Number(msgId), text);
248
+ },
249
+ }, reasoningDisplay),
250
+ async recordAssistant(m) {
251
+ await recordAssistant({ ...m, chatId, now: Math.floor(Date.now() / 1000) });
252
+ if (m.botMsgId)
253
+ lastByChat.set(chatId, { msgId: m.botMsgId, ts: Date.now() });
254
+ },
255
+ };
256
+ await handleIncoming(connector, {
257
+ connectorId: 'telegram',
258
+ chatId,
259
+ channelSystem: TELEGRAM_FORMAT,
260
+ sender: { id: userId, displayName },
261
+ messages,
262
+ });
263
+ }
264
+ finally {
265
+ stopTyping();
266
+ }
128
267
  }
129
268
  catch (err) {
130
269
  log.error(`message handling: ${err instanceof Error ? err.message : String(err)}`);
@@ -0,0 +1,28 @@
1
+ import type { ConvMessage } from '@coffer-org/plugin-orchestrator/runtime';
2
+ export type FetchMsg = (msgId: string) => Promise<{
3
+ text: string;
4
+ senderId: string;
5
+ replyToId: string | null;
6
+ date: number;
7
+ } | null>;
8
+ export declare function recordUser(m: {
9
+ chatId: string;
10
+ msgId: string;
11
+ sender?: string | null;
12
+ text: string;
13
+ ts: number;
14
+ replyToId: string | null;
15
+ }): Promise<void>;
16
+ export declare function recordAssistant(m: {
17
+ chatId: string;
18
+ parentMsgId: string | null;
19
+ botMsgId: string | null;
20
+ text: string;
21
+ now: number;
22
+ }): Promise<void>;
23
+ export declare function buildChain(headMsgId: string, opts: {
24
+ chatId: string;
25
+ fetch: FetchMsg;
26
+ botId: string;
27
+ maxDepth?: number;
28
+ }): Promise<ConvMessage[]>;
@@ -0,0 +1,48 @@
1
+ import { getThreadMessage, putThreadMessage } from '@coffer-org/server/thread-store';
2
+ import { getLogger } from '@coffer-org/sdk/logger';
3
+ const log = getLogger('telegram');
4
+ const CONNECTOR = 'telegram';
5
+ const DEFAULT_MAX_DEPTH = 30;
6
+ export async function recordUser(m) {
7
+ await putThreadMessage({ connector: CONNECTOR, chatId: m.chatId, msgId: m.msgId, role: 'user', sender: m.sender ?? null, text: m.text, ts: m.ts, replyToId: m.replyToId });
8
+ }
9
+ export async function recordAssistant(m) {
10
+ if (m.botMsgId == null)
11
+ return;
12
+ await putThreadMessage({
13
+ connector: CONNECTOR,
14
+ chatId: m.chatId,
15
+ msgId: m.botMsgId,
16
+ role: 'assistant',
17
+ text: m.text,
18
+ ts: m.now,
19
+ replyToId: m.parentMsgId,
20
+ });
21
+ }
22
+ export async function buildChain(headMsgId, opts) {
23
+ const max = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
24
+ const acc = [];
25
+ let cur = headMsgId;
26
+ const seen = new Set();
27
+ while (cur && acc.length < max && !seen.has(cur)) {
28
+ seen.add(cur);
29
+ let stored = await getThreadMessage(CONNECTOR, opts.chatId, cur);
30
+ if (!stored) {
31
+ const fetched = await opts.fetch(cur);
32
+ if (!fetched)
33
+ break;
34
+ const role = fetched.senderId === opts.botId ? 'assistant' : 'user';
35
+ await putThreadMessage({ connector: CONNECTOR, chatId: opts.chatId, msgId: cur, role, sender: null, text: fetched.text, ts: fetched.date, replyToId: fetched.replyToId });
36
+ stored = { msgId: cur, role, sender: null, text: fetched.text, ts: fetched.date, replyToId: fetched.replyToId };
37
+ }
38
+ if (stored.role !== 'reasoning') {
39
+ acc.push({ role: stored.role, content: stored.text, sender: stored.sender, msgId: stored.msgId, ts: stored.ts });
40
+ }
41
+ cur = stored.replyToId;
42
+ }
43
+ if (acc.length >= max)
44
+ log.info(`chain capped at ${max} for ${headMsgId}`);
45
+ while (acc.length && acc.at(-1)?.role !== 'user')
46
+ acc.pop();
47
+ return acc.reverse();
48
+ }
@@ -12,3 +12,6 @@ export interface LoadOpts {
12
12
  }
13
13
  export declare function loadBotConfig(opts?: LoadOpts): BotConfig;
14
14
  export declare function hasCredentials(cfg: Pick<BotConfig, 'apiId' | 'apiHash' | 'botToken' | 'phone'>): boolean;
15
+ export type ReasoningDisplay = 'off' | 'collapsible' | 'separate';
16
+ export declare function toReasoningDisplay(v: unknown): ReasoningDisplay;
17
+ export declare function loadReasoningDisplay(): Promise<ReasoningDisplay>;
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
+ import { getPluginSettings } from '@coffer-org/server/plugin-runtime';
3
4
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
4
5
  export const PLUGIN_ROOT = path.resolve(__dirname, '../..');
5
6
  export function loadBotConfig(opts = {}) {
@@ -16,3 +17,9 @@ export function loadBotConfig(opts = {}) {
16
17
  export function hasCredentials(cfg) {
17
18
  return Boolean(cfg.apiId && cfg.apiHash && (cfg.botToken || cfg.phone));
18
19
  }
20
+ export function toReasoningDisplay(v) {
21
+ return v === 'off' || v === 'separate' ? v : 'collapsible';
22
+ }
23
+ export async function loadReasoningDisplay() {
24
+ return toReasoningDisplay((await getPluginSettings('telegram'))['reasoning_display']);
25
+ }
@@ -1,7 +1,9 @@
1
1
  import { getPluginSettings } from '@coffer-org/server/plugin-runtime';
2
+ import { pruneThreadMessages } from '@coffer-org/server/thread-store';
2
3
  import { startBot, stopBot } from "./bot.js";
3
4
  import { getLogger } from '@coffer-org/sdk/logger';
4
5
  const log = getLogger('telegram');
6
+ const THREAD_TTL_MS = Number(process.env['TELEGRAM_THREAD_TTL_MS'] ?? 7 * 86_400_000);
5
7
  export { startBot, stopBot };
6
8
  export { loadBotConfig, hasCredentials } from "./config.js";
7
9
  export const serverHooks = {
@@ -12,4 +14,13 @@ export const serverHooks = {
12
14
  teardown: async () => {
13
15
  await stopBot();
14
16
  },
17
+ backgroundTasks: [
18
+ {
19
+ name: 'telegram-thread-prune',
20
+ intervalMs: 86_400_000,
21
+ run: async () => {
22
+ await pruneThreadMessages('telegram', Math.floor((Date.now() - THREAD_TTL_MS) / 1000));
23
+ },
24
+ },
25
+ ],
15
26
  };
package/dist/schema.js CHANGED
@@ -21,14 +21,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- //#region ../sdk/dist/plugin.js
24
+ //#region ../sdk/src/plugin.ts
25
25
  function definePlugin(p) {
26
26
  if (!p.id) throw new Error("[plugin] missing id");
27
27
  if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
28
28
  return p;
29
29
  }
30
30
  //#endregion
31
- //#region ../sdk/dist/settings.js
31
+ //#region ../sdk/src/settings.ts
32
32
  function defineSettings(s) {
33
33
  return s;
34
34
  }
@@ -154,7 +154,10 @@ function assignProp(target, prop, value) {
154
154
  }
155
155
  function mergeDefs(...defs) {
156
156
  const mergedDescriptors = {};
157
- for (const def of defs) Object.assign(mergedDescriptors, Object.getOwnPropertyDescriptors(def));
157
+ for (const def of defs) {
158
+ const descriptors = Object.getOwnPropertyDescriptors(def);
159
+ Object.assign(mergedDescriptors, descriptors);
160
+ }
158
161
  return Object.defineProperties({}, mergedDescriptors);
159
162
  }
160
163
  function esc(str) {
@@ -476,7 +479,7 @@ var _parse = (_Err) => (schema, value, _ctx, _params) => {
476
479
  }, ctx);
477
480
  if (result instanceof Promise) throw new $ZodAsyncError();
478
481
  if (result.issues.length) {
479
- const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
482
+ const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
480
483
  captureStackTrace(e, _params?.callee);
481
484
  throw e;
482
485
  }
@@ -493,7 +496,7 @@ var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
493
496
  }, ctx);
494
497
  if (result instanceof Promise) result = await result;
495
498
  if (result.issues.length) {
496
- const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
499
+ const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
497
500
  captureStackTrace(e, params?.callee);
498
501
  throw e;
499
502
  }
@@ -1589,13 +1592,13 @@ var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
1589
1592
  }
1590
1593
  return propValues;
1591
1594
  });
1592
- const isObject$1 = isObject;
1595
+ const isObject$2 = isObject;
1593
1596
  const catchall = def.catchall;
1594
1597
  let value;
1595
1598
  inst._zod.parse = (payload, ctx) => {
1596
1599
  value ?? (value = _normalized.value);
1597
1600
  const input = payload.value;
1598
- if (!isObject$1(input)) {
1601
+ if (!isObject$2(input)) {
1599
1602
  payload.issues.push({
1600
1603
  expected: "object",
1601
1604
  code: "invalid_type",
@@ -1718,7 +1721,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1718
1721
  return (payload, ctx) => fn(shape, payload, ctx);
1719
1722
  };
1720
1723
  let fastpass;
1721
- const isObject$2 = isObject;
1724
+ const isObject$1 = isObject;
1722
1725
  const jit = !globalConfig.jitless;
1723
1726
  const fastEnabled = jit && allowsEval.value;
1724
1727
  const catchall = def.catchall;
@@ -1726,7 +1729,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1726
1729
  inst._zod.parse = (payload, ctx) => {
1727
1730
  value ?? (value = _normalized.value);
1728
1731
  const input = payload.value;
1729
- if (!isObject$2(input)) {
1732
+ if (!isObject$1(input)) {
1730
1733
  payload.issues.push({
1731
1734
  expected: "object",
1732
1735
  code: "invalid_type",
@@ -1945,7 +1948,7 @@ var $ZodOptional = /*@__PURE__*/ $constructor("$ZodOptional", (inst, def) => {
1945
1948
  inst._zod.optin = "optional";
1946
1949
  inst._zod.optout = "optional";
1947
1950
  defineLazy(inst._zod, "values", () => {
1948
- return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;
1951
+ return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, void 0]) : void 0;
1949
1952
  });
1950
1953
  defineLazy(inst._zod, "pattern", () => {
1951
1954
  const pattern = def.innerType._zod.pattern;
@@ -1979,7 +1982,7 @@ var $ZodNullable = /*@__PURE__*/ $constructor("$ZodNullable", (inst, def) => {
1979
1982
  return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;
1980
1983
  });
1981
1984
  defineLazy(inst._zod, "values", () => {
1982
- return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;
1985
+ return def.innerType._zod.values ? /* @__PURE__ */ new Set([...def.innerType._zod.values, null]) : void 0;
1983
1986
  });
1984
1987
  inst._zod.parse = (payload, ctx) => {
1985
1988
  if (payload.value === null) return payload;
@@ -4078,7 +4081,7 @@ function number(params) {
4078
4081
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
4079
4082
  }
4080
4083
  //#endregion
4081
- //#region ../sdk/dist/units.js
4084
+ //#region ../sdk/src/units.ts
4082
4085
  var UNITS_CURRENCY = [
4083
4086
  {
4084
4087
  value: "UAH",
@@ -4386,7 +4389,13 @@ function resolveUnits(u) {
4386
4389
  return typeof u === "string" ? UNITS_MAP[u] : u;
4387
4390
  }
4388
4391
  //#endregion
4389
- //#region ../sdk/dist/currencies.js
4392
+ //#region ../sdk/src/currencies.ts
4393
+ /**
4394
+ * Валюти з вбудованого `Intl` (ISO 4217) — БЕЗ hand-list і без npm-залежності.
4395
+ * Коди — `Intl.supportedValuesOf('currency')`; символи/назви локалізовані через
4396
+ * `Intl.NumberFormat`/`Intl.DisplayNames`.
4397
+ */
4398
+ /** Усі ISO 4217 коди. Fallback — кілька основних (на випадок старого рантайму). */
4390
4399
  var CURRENCY_CODES = (() => {
4391
4400
  try {
4392
4401
  return Intl.supportedValuesOf("currency");
@@ -4401,24 +4410,34 @@ var CURRENCY_CODES = (() => {
4401
4410
  }
4402
4411
  })();
4403
4412
  var CODE_SET = new Set(CURRENCY_CODES);
4413
+ /** Чи валідний ISO 4217 код. */
4404
4414
  var isCurrencyCode = (c) => CODE_SET.has(c);
4405
4415
  //#endregion
4406
- //#region ../sdk/dist/fields/validation.js
4416
+ //#region ../sdk/src/fields/validation.ts
4417
+ /** Структуроване повідомлення для zod: JSON {code, params}. Декодує mutate.ts. */
4407
4418
  function vmsg(code, params) {
4408
4419
  return JSON.stringify(params ? {
4409
4420
  code,
4410
4421
  params
4411
4422
  } : { code });
4412
4423
  }
4424
+ /** v4 error-map: повідомлення для відсутнього значення (колишній required_error). */
4413
4425
  function reqErr(code = "required") {
4414
4426
  return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
4415
4427
  }
4428
+ /** v4 error-map: повідомлення для невалідного типу (колишній invalid_type_error). */
4416
4429
  function typeErr(code = "invalid_type") {
4417
4430
  return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
4418
4431
  }
4432
+ /** v4 error-map: required + invalid_type разом (колишні required_error + invalid_type_error). */
4419
4433
  function reqTypeErr() {
4420
4434
  return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
4421
4435
  }
4436
+ /** Parse a JSON string, else return the native value (object/array) as-is.
4437
+ * Tolerant input for native form-state AND legacy JSON-string payloads.
4438
+ * A string that fails JSON.parse is returned unchanged — callers detect
4439
+ * parse failure via `typeof result === 'string'` (inners here are object/array,
4440
+ * never a bare string). */
4422
4441
  function jsonValue(raw) {
4423
4442
  if (typeof raw === "string") try {
4424
4443
  return JSON.parse(raw);
@@ -4427,6 +4446,12 @@ function jsonValue(raw) {
4427
4446
  }
4428
4447
  return raw;
4429
4448
  }
4449
+ /**
4450
+ * Фабрика для полів, що зберігають JSON і валідуються вкладеною zod-схемою.
4451
+ * Приймає нативне object/array (native form-state) АБО JSON-рядок (legacy).
4452
+ * Single-parse через jsonValue → inner.safeParse → issue `code`; рядок, що не
4453
+ * розпарсився, лишається рядком → код 'json'.
4454
+ */
4430
4455
  function jsonRefined(inner, code) {
4431
4456
  return unknown().superRefine((raw, ctx) => {
4432
4457
  const parsed = jsonValue(raw);
@@ -4455,7 +4480,7 @@ function optionalize(schema, required) {
4455
4480
  }).pipe(schema));
4456
4481
  }
4457
4482
  //#endregion
4458
- //#region ../sdk/dist/fields/normalize.js
4483
+ //#region ../sdk/src/fields/normalize.ts
4459
4484
  function normalizeOpts(rawIn) {
4460
4485
  const raw = rawIn;
4461
4486
  const r = raw.rules ?? {};
@@ -4495,7 +4520,19 @@ function normalizeOpts(rawIn) {
4495
4520
  return Object.fromEntries(Object.entries(result).filter(([, v]) => v !== void 0));
4496
4521
  }
4497
4522
  //#endregion
4498
- //#region ../sdk/dist/field-presets.js
4523
+ //#region ../sdk/src/field-presets.ts
4524
+ /**
4525
+ * Пресети полів — тонкі обгортки над базовими примітивами з fields.ts.
4526
+ *
4527
+ * Кожен пресет = один kind (один віджет). Пресети не приймають `format` —
4528
+ * вони самі є семантичними типами. min/max/step — через `config`.
4529
+ *
4530
+ * Пресети додаються до `f` через `composeF`, який гарантує, що жоден пресет
4531
+ * не перебиває примітив.
4532
+ *
4533
+ * Циклічний імпорт з fields.ts безпечний: фабрики/хелпери — hoisted-декларації,
4534
+ * а пресети викликають їх лише в тілі своїх функцій.
4535
+ */
4499
4536
  function email(raw) {
4500
4537
  const o = normalizeOpts(raw);
4501
4538
  const required = o.required ?? false;
@@ -4549,6 +4586,14 @@ function password(raw) {
4549
4586
  zod: optionalize(s, required)
4550
4587
  });
4551
4588
  }
4589
+ /**
4590
+ * Internal API-token list — keyValue-shaped collection (name + write-only
4591
+ * token + timestamps), custom renderer (`kind:'internalApiToken'`, own
4592
+ * create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
4593
+ * general-purpose field type for plugin shelves, only for the core account
4594
+ * settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
4595
+ * already mask/preserve it for free.
4596
+ */
4552
4597
  function internalApiToken(o) {
4553
4598
  return group({
4554
4599
  key: o.key,
@@ -4564,6 +4609,25 @@ function internalApiToken(o) {
4564
4609
  ]
4565
4610
  });
4566
4611
  }
4612
+ /**
4613
+ * Internal "connected apps" list — the OAuth clients (claude.ai & co) the user
4614
+ * has authorized for MCP access. Same "internal" caveat and custom-renderer
4615
+ * trick as internalApiToken(): read-only rows + a revoke action, never part of
4616
+ * the parent form's save.
4617
+ */
4618
+ function internalOauthGrants(o) {
4619
+ return group({
4620
+ key: o.key,
4621
+ label: o.label ?? o.key,
4622
+ multiple: true,
4623
+ view: { kind: "internalOauthGrants" },
4624
+ fields: [
4625
+ string({ key: "clientName" }),
4626
+ string({ key: "createdAt" }),
4627
+ string({ key: "lastUsedAt" })
4628
+ ]
4629
+ });
4630
+ }
4567
4631
  var SLUG_RE = /^[a-z0-9-]+$/;
4568
4632
  function slug(raw) {
4569
4633
  const o = normalizeOpts(raw);
@@ -4641,8 +4705,10 @@ function link(raw) {
4641
4705
  }, o.multiple ?? false));
4642
4706
  }
4643
4707
  var TEL_RE = /^\+?[\d\s()-]{4,}$/;
4708
+ /** Loose URL: будь-який scheme:// АБО host-з-крапкою (+опц. порт/шлях). Без пробілів. */
4644
4709
  var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
4645
- var CSS_COLOR_NAMES = new Set([
4710
+ /** CSS named colors (CSS Color Module L4) — для f.colorname. */
4711
+ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
4646
4712
  "aliceblue",
4647
4713
  "antiquewhite",
4648
4714
  "aqua",
@@ -4900,6 +4966,7 @@ function reminder(raw) {
4900
4966
  }
4901
4967
  var _real = real;
4902
4968
  var _int = int;
4969
+ /** Відсоток 0..100 — real з rules:{min:0,max:100}. */
4903
4970
  function percent(o) {
4904
4971
  return _real({
4905
4972
  ...o,
@@ -4910,6 +4977,7 @@ function percent(o) {
4910
4977
  }
4911
4978
  });
4912
4979
  }
4980
+ /** Рік — int з rules:{min:1900,max:2100}; межі можна перекрити через rules.min/max. */
4913
4981
  function year(o) {
4914
4982
  return _int({
4915
4983
  ...o,
@@ -4989,7 +5057,8 @@ var presets = {
4989
5057
  weight,
4990
5058
  dimensions,
4991
5059
  country,
4992
- internalApiToken
5060
+ internalApiToken,
5061
+ internalOauthGrants
4993
5062
  };
4994
5063
  //#endregion
4995
5064
  //#region ../../node_modules/iso-639-1/src/data.js
@@ -5101,7 +5170,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5101
5170
  },
5102
5171
  cs: {
5103
5172
  name: "Czech",
5104
- nativeName: "Čeština"
5173
+ nativeName: "čeština"
5105
5174
  },
5106
5175
  cu: {
5107
5176
  name: "Old Church Slavonic",
@@ -5565,7 +5634,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5565
5634
  },
5566
5635
  sk: {
5567
5636
  name: "Slovak",
5568
- nativeName: "Slovenčina"
5637
+ nativeName: "slovenčtina"
5569
5638
  },
5570
5639
  sl: {
5571
5640
  name: "Slovenian",
@@ -5730,7 +5799,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
5730
5799
  };
5731
5800
  }));
5732
5801
  //#endregion
5733
- //#region ../sdk/dist/fields/constants.js
5802
+ //#region ../sdk/src/fields/constants.ts
5734
5803
  var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
5735
5804
  var LANGUAGES_LIST = require_data();
5736
5805
  var LANGUAGES = {};
@@ -5786,7 +5855,21 @@ import_src.default.getAllCodes().map((code) => ({
5786
5855
  name: import_src.default.getNativeName(code)
5787
5856
  })).sort((a, b) => a.name.localeCompare(b.name));
5788
5857
  //#endregion
5789
- //#region ../sdk/dist/fields.js
5858
+ //#region ../sdk/src/fields.ts
5859
+ /**
5860
+ * The field type system — the heart of the platform. A pure, isomorphic module.
5861
+ * One field description → three consumers:
5862
+ * 1. `zod` — validation (identical on server and client)
5863
+ * 2. `column` — the DB column type
5864
+ * 3. `prim`/`kind` — keys for resolving the renderer (see web/render/registry)
5865
+ */
5866
+ /**
5867
+ * A visual or storage group of fields (one level of nesting — no groups inside groups).
5868
+ * Without `key` → a layout group (not stored). With `key` → storage:
5869
+ * - embedded (key, no multiple): stored as a column or a nested object.
5870
+ * - collection (key + multiple): a child table (array of rows).
5871
+ * A storage group requires a `label` and only `FieldItem` children (with a storage key).
5872
+ */
5790
5873
  function group(o) {
5791
5874
  const r = o.rules ?? {};
5792
5875
  const v = o.view ?? {};
@@ -5802,37 +5885,40 @@ function group(o) {
5802
5885
  required: o.required,
5803
5886
  unique: r.unique,
5804
5887
  label: o.label,
5805
- icon: v.icon,
5888
+ icon: o.icon,
5806
5889
  display: v.display ?? "wrap",
5807
5890
  kind: v.kind,
5808
5891
  fixed: r.fixed,
5809
5892
  fields: o.fields
5810
5893
  };
5811
5894
  }
5895
+ /** Layout group, flow nowrap + horizontal scroll (single line). Sugar over group(). */
5812
5896
  function row(o) {
5813
5897
  return group({
5814
5898
  label: o.label,
5899
+ icon: o.icon,
5815
5900
  fields: o.fields,
5816
- view: {
5817
- ...o.view,
5818
- display: "scroll"
5819
- }
5901
+ view: { display: "scroll" }
5820
5902
  });
5821
5903
  }
5904
+ /** Tabular collection (array of rows → <table>, aligned by columns). Sugar. */
5822
5905
  function table(o) {
5823
5906
  return group({
5824
5907
  key: o.key,
5825
5908
  label: o.label,
5909
+ icon: o.icon,
5826
5910
  fields: o.fields,
5827
5911
  multiple: true,
5828
5912
  required: o.required,
5829
5913
  rules: o.rules,
5830
- view: {
5831
- ...o.view,
5832
- display: "table"
5833
- }
5914
+ view: { display: "table" }
5834
5915
  });
5835
5916
  }
5917
+ /**
5918
+ * Layout group with a 2D layout (rows×columns → aligned CSS grid).
5919
+ * Keyless (flat parent fields). Rows are glued together with brk() separators
5920
+ * into a flat fields; the renderer reconstructs the 2D by splitting on brk. Sugar over group().
5921
+ */
5836
5922
  function sheet(o) {
5837
5923
  const flat = [];
5838
5924
  o.fields.forEach((rowFields, i) => {
@@ -5841,13 +5927,17 @@ function sheet(o) {
5841
5927
  });
5842
5928
  return group({
5843
5929
  label: o.label,
5930
+ icon: o.icon,
5844
5931
  fields: flat,
5845
- view: {
5846
- ...o.view,
5847
- display: "sheet"
5848
- }
5932
+ view: { display: "sheet" }
5849
5933
  });
5850
5934
  }
5935
+ /**
5936
+ * Composite URL — embedded-group sugar. Storage: separate nested columns
5937
+ * (`key__scheme`, `key__host`, `key__port`, …) in the same table. The renderer
5938
+ * is custom (a single link/input, `kind:'url'`) — string↔parts via
5939
+ * parseUrl/buildUrl. Partial/invalid URLs are stored by parts.
5940
+ */
5851
5941
  function url(o) {
5852
5942
  return group({
5853
5943
  key: o.key,
@@ -5871,8 +5961,8 @@ function keyed(o) {
5871
5961
  return {
5872
5962
  ...(o.container ?? group)({
5873
5963
  label: o.label,
5874
- fields: [o.by, ...o.fields],
5875
- view: o.view
5964
+ icon: o.icon,
5965
+ fields: [o.by, ...o.fields]
5876
5966
  }),
5877
5967
  key: o.key,
5878
5968
  multiple: true,
@@ -5881,18 +5971,22 @@ function keyed(o) {
5881
5971
  fixed: o.fixed
5882
5972
  };
5883
5973
  }
5974
+ /** Horizontal divider. */
5884
5975
  function divider() {
5885
5976
  return { el: "divider" };
5886
5977
  }
5978
+ /** Forced flow-row wrap (subsequent fields start on a new row). No <hr>. */
5887
5979
  function brk() {
5888
5980
  return { el: "break" };
5889
5981
  }
5982
+ /** Reference markdown block by i18n key. */
5890
5983
  function info(textKey) {
5891
5984
  return {
5892
5985
  el: "info",
5893
5986
  text: textKey
5894
5987
  };
5895
5988
  }
5989
+ /** Action button: invokes the handler registered in actionRegistry under the key `value`. */
5896
5990
  function button(o) {
5897
5991
  return {
5898
5992
  el: "button",
@@ -5902,6 +5996,7 @@ function button(o) {
5902
5996
  variant: o.variant
5903
5997
  };
5904
5998
  }
5999
+ /** Wraps FieldMeta into FieldItem/StaticEl depending on opts.key/opts.value. */
5905
6000
  function wrapKey(opts, meta) {
5906
6001
  let m = meta;
5907
6002
  if (opts.noEditControl) m = {
@@ -5911,6 +6006,34 @@ function wrapKey(opts, meta) {
5911
6006
  noEditControl: true
5912
6007
  }
5913
6008
  };
6009
+ if (opts.emphasis) m = {
6010
+ ...m,
6011
+ hints: {
6012
+ ...m.hints,
6013
+ emphasis: opts.emphasis
6014
+ }
6015
+ };
6016
+ if (opts.noLabel) m = {
6017
+ ...m,
6018
+ hints: {
6019
+ ...m.hints,
6020
+ noLabel: true
6021
+ }
6022
+ };
6023
+ if (opts.role) m = {
6024
+ ...m,
6025
+ hints: {
6026
+ ...m.hints,
6027
+ role: opts.role
6028
+ }
6029
+ };
6030
+ if (opts.pinned) m = {
6031
+ ...m,
6032
+ hints: {
6033
+ ...m.hints,
6034
+ pinned: true
6035
+ }
6036
+ };
5914
6037
  if (opts.default !== void 0) m = {
5915
6038
  ...m,
5916
6039
  default: opts.default
@@ -5940,6 +6063,14 @@ function safeJsonParse(s) {
5940
6063
  return;
5941
6064
  }
5942
6065
  }
6066
+ /**
6067
+ * Wraps a field into a JSON array of values (storage: TEXT). The inner zod is `base.zod`.
6068
+ * Modifier order: base → applyMultiple.
6069
+ *
6070
+ * Exception: kind 'image'/'media' handle multiple themselves in the renderer (gallery) —
6071
+ * the MultipleField* wrappers on the web skip them.
6072
+ */
6073
+ /** Zod "JSON array of inner values" — shared by applyMultiple and bindSelectSourceZod. */
5943
6074
  function multipleZod(inner) {
5944
6075
  return unknown().superRefine((raw, ctx) => {
5945
6076
  let arr;
@@ -6266,8 +6397,8 @@ function relation(raw) {
6266
6397
  displayKey: o.displayKey ?? "name"
6267
6398
  },
6268
6399
  relation: {
6269
- vault: raw.options.vault,
6270
- type: raw.options.module
6400
+ library: raw.options.library,
6401
+ shelf: raw.options.shelf
6271
6402
  },
6272
6403
  ...multi && { json: true },
6273
6404
  zod: optionalize(s, required)
@@ -6532,6 +6663,11 @@ function geo(raw) {
6532
6663
  zod: optionalize(s, required)
6533
6664
  });
6534
6665
  }
6666
+ /**
6667
+ * 7 values, Mon–Sun. A thin wrapper over keyed({fixed}): a fixed collection,
6668
+ * by=select('weekdays'), one real value per day. Its own kind 'per-weekday'
6669
+ * dispatches a compact renderer. Storage: a grandchild table of {day, value} rows.
6670
+ */
6535
6671
  function perWeekday(o) {
6536
6672
  return {
6537
6673
  ...keyed({
@@ -6642,15 +6778,44 @@ function period(raw) {
6642
6778
  zod: optionalize(s, required)
6643
6779
  });
6644
6780
  }
6781
+ /** Keys a file entry may carry. Everything else is rejected — see fileEntryIssue. */
6782
+ var FILE_KEYS = /* @__PURE__ */ new Set([
6783
+ "name",
6784
+ "mime",
6785
+ "size"
6786
+ ]);
6787
+ /** Keys that mean "the author pasted a remote address" — the one mistake worth naming. */
6788
+ var FILE_URL_KEYS = [
6789
+ "url",
6790
+ "src",
6791
+ "href",
6792
+ "link"
6793
+ ];
6794
+ /**
6795
+ * A file entry points at a file already uploaded to the server: `{ name }`, where
6796
+ * name is the bare filename returned by POST /api/upload. Anything else — a remote
6797
+ * URL, an extra key, a path — is refused here, so a record can never hold a
6798
+ * reference the server cannot serve. `mime`/`size` are accepted (legacy payloads
6799
+ * and the web uploader send them) but the server overwrites them from disk.
6800
+ * Returns a vmsg code, or null when the entry is well-formed.
6801
+ */
6802
+ function fileEntryIssue(it) {
6803
+ if (typeof it !== "object" || it === null || Array.isArray(it)) return "file_structure";
6804
+ const rec = it;
6805
+ if (FILE_URL_KEYS.some((k) => k in rec)) return "file_remote_url";
6806
+ const name = rec["name"];
6807
+ if (typeof name !== "string" || name === "") return "file_structure";
6808
+ if (name.includes("://") || name.startsWith("//")) return "file_remote_url";
6809
+ if (/[\\/]/.test(name) || name.includes("..")) return "file_name";
6810
+ for (const k of Object.keys(rec)) if (!FILE_KEYS.has(k)) return "file_unknown_key";
6811
+ if (rec["mime"] !== void 0 && typeof rec["mime"] !== "string") return "file_structure";
6812
+ if (rec["size"] !== void 0 && typeof rec["size"] !== "number") return "file_structure";
6813
+ return null;
6814
+ }
6645
6815
  function makeFile(kind, raw) {
6646
6816
  const o = normalizeOpts(raw);
6647
6817
  const required = o.required ?? false;
6648
6818
  const multiple = o.multiple ?? false;
6649
- const rowSchema = object({
6650
- name: string$1().min(1),
6651
- mime: string$1().optional(),
6652
- size: number$1().optional()
6653
- });
6654
6819
  const s = unknown().superRefine((raw, ctx) => {
6655
6820
  if (typeof raw === "string") try {
6656
6821
  JSON.parse(raw);
@@ -6663,12 +6828,15 @@ function makeFile(kind, raw) {
6663
6828
  }
6664
6829
  const parsed = jsonValue(raw);
6665
6830
  const items = Array.isArray(parsed) ? parsed : [parsed];
6666
- for (const it of items) if (!rowSchema.safeParse(it).success) {
6667
- ctx.addIssue({
6668
- code: ZodIssueCode.custom,
6669
- message: vmsg("file_structure")
6670
- });
6671
- return;
6831
+ for (const it of items) {
6832
+ const code = fileEntryIssue(it);
6833
+ if (code) {
6834
+ ctx.addIssue({
6835
+ code: ZodIssueCode.custom,
6836
+ message: vmsg(code)
6837
+ });
6838
+ return;
6839
+ }
6672
6840
  }
6673
6841
  });
6674
6842
  return wrapKey(o, {
@@ -6824,6 +6992,11 @@ function embed(raw) {
6824
6992
  zod: optionalize(s, required)
6825
6993
  });
6826
6994
  }
6995
+ /**
6996
+ * Base primitives (storage-aligned) + structural types. Presets (thin wrappers
6997
+ * over these primitives) are added below from field-presets.ts and CANNOT override
6998
+ * any key from here (guard in composeF).
6999
+ */
6827
7000
  var PRIMITIVES = {
6828
7001
  string,
6829
7002
  text,
@@ -6873,6 +7046,7 @@ var PRIMITIVES = {
6873
7046
  info,
6874
7047
  button
6875
7048
  };
7049
+ /** Assembles `f`, guaranteeing no preset shadows a primitive. */
6876
7050
  function composeF(presets) {
6877
7051
  for (const k of Object.keys(presets)) if (k in PRIMITIVES) throw new Error(`[f] preset '${k}' overrides a primitive`);
6878
7052
  return {
@@ -6930,6 +7104,25 @@ var src_default = definePlugin({
6930
7104
  field.tel({
6931
7105
  key: "phone",
6932
7106
  label: "telegram.settings.phone"
7107
+ }),
7108
+ field.select({
7109
+ key: "reasoning_display",
7110
+ label: "telegram.settings.reasoning_display",
7111
+ default: "collapsible",
7112
+ options: [
7113
+ {
7114
+ value: "off",
7115
+ title: "telegram.settings.reasoning_display_off"
7116
+ },
7117
+ {
7118
+ value: "collapsible",
7119
+ title: "telegram.settings.reasoning_display_collapsible"
7120
+ },
7121
+ {
7122
+ value: "separate",
7123
+ title: "telegram.settings.reasoning_display_separate"
7124
+ }
7125
+ ]
6933
7126
  })
6934
7127
  ]
6935
7128
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-telegram",
3
- "version": "1.3.2",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -25,11 +25,11 @@
25
25
  "test": "node --import tsx --test \"src/runtime/*.test.ts\""
26
26
  },
27
27
  "dependencies": {
28
- "@coffer-org/plugin-orchestrator": "^1.3.1",
29
- "@coffer-org/sdk": "^1.4.0",
30
- "@coffer-org/server": "^1.4.0",
28
+ "@coffer-org/plugin-orchestrator": "^2.0.0",
29
+ "@coffer-org/sdk": "^2.0.0",
30
+ "@coffer-org/server": "^2.0.1",
31
31
  "input": "^1.0.1",
32
- "telegram": "^2.26.0"
32
+ "teleproto": "^1.228.1"
33
33
  },
34
34
  "devDependencies": {},
35
35
  "coffer": {