teleton 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -26
- package/dist/{chunk-D5I7GBV7.js → chunk-FNV5FF35.js} +22 -13
- package/dist/chunk-LRCPA7SC.js +149 -0
- package/dist/{chunk-ADCMUNYU.js → chunk-N3F7E7DR.js} +58 -53
- package/dist/chunk-ND2X5FWB.js +368 -0
- package/dist/chunk-NERLQY2H.js +421 -0
- package/dist/{chunk-YBA6IBGT.js → chunk-OCLG5GKI.js} +24 -24
- package/dist/{chunk-6L6KGATM.js → chunk-OGIG552S.js} +1397 -1688
- package/dist/chunk-RCMD3U65.js +141 -0
- package/dist/{chunk-4IPJ25HE.js → chunk-TCD4NZDA.js} +1045 -658
- package/dist/{chunk-ECSCVEQQ.js → chunk-UCN6TI25.js} +7 -3
- package/dist/{chunk-WL2Q3VRD.js → chunk-UDD7FYOU.js} +12 -4
- package/dist/chunk-VAUJSSD3.js +20 -0
- package/dist/chunk-XBE4JB7C.js +8 -0
- package/dist/{chunk-GDCODBNO.js → chunk-XBKSS6DM.js} +2 -16
- package/dist/cli/index.js +878 -433
- package/dist/client-3VWE7NC4.js +29 -0
- package/dist/{get-my-gifts-KVULMBJ3.js → get-my-gifts-RI7FAXAL.js} +3 -1
- package/dist/index.js +17 -11
- package/dist/{memory-TVDOGQXS.js → memory-RD7ZSTRV.js} +7 -5
- package/dist/{migrate-QIEMPOMT.js → migrate-GO4NOBT7.js} +14 -9
- package/dist/{server-RSWVCVY3.js → server-OWVEZTR3.js} +81 -84
- package/dist/setup-server-C7ZTPHD5.js +934 -0
- package/dist/{task-dependency-resolver-72DLY2HV.js → task-dependency-resolver-WKZWJLLM.js} +19 -15
- package/dist/{task-executor-VXB6DAV2.js → task-executor-PD3H4MLO.js} +4 -1
- package/dist/tool-adapter-Y3TCEQOC.js +145 -0
- package/dist/{tool-index-DKI2ZNOU.js → tool-index-MIVK3D7H.js} +14 -9
- package/dist/{transcript-7V4UNID4.js → transcript-UDJZP6NK.js} +2 -1
- package/dist/web/assets/complete-fZLnb5Ot.js +1 -0
- package/dist/web/assets/index-B_FcaX5D.css +1 -0
- package/dist/web/assets/index-CbeAP4_n.js +67 -0
- package/dist/web/assets/index.es-oXiZF7Hc.js +11 -0
- package/dist/web/assets/login-telegram-BP7CJDmx.js +1 -0
- package/dist/web/assets/run-DOrDowjK.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +7 -3
- package/dist/chunk-2QUJLHCZ.js +0 -362
- package/dist/web/assets/index-BNhrx9S1.js +0 -67
- package/dist/web/assets/index-CqrrRLOh.css +0 -1
|
@@ -1,13 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigSchema,
|
|
3
|
+
getKeyPair,
|
|
4
|
+
getTonPrice,
|
|
5
|
+
getWalletAddress,
|
|
6
|
+
getWalletBalance,
|
|
7
|
+
loadWallet
|
|
8
|
+
} from "./chunk-NERLQY2H.js";
|
|
9
|
+
import {
|
|
10
|
+
getCachedHttpEndpoint
|
|
11
|
+
} from "./chunk-QUAPFI2N.js";
|
|
12
|
+
import {
|
|
13
|
+
require_BigInteger
|
|
14
|
+
} from "./chunk-TSKJCWQQ.js";
|
|
15
|
+
import {
|
|
16
|
+
getErrorMessage
|
|
17
|
+
} from "./chunk-XBE4JB7C.js";
|
|
18
|
+
import {
|
|
19
|
+
getProviderMetadata
|
|
20
|
+
} from "./chunk-LRCPA7SC.js";
|
|
1
21
|
import {
|
|
2
22
|
createDbWrapper,
|
|
3
23
|
migrateFromMainDb,
|
|
4
24
|
openModuleDb
|
|
5
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UCN6TI25.js";
|
|
6
26
|
import {
|
|
7
|
-
COINGECKO_API_URL,
|
|
8
|
-
fetchWithTimeout,
|
|
9
27
|
tonapiFetch
|
|
10
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-XBKSS6DM.js";
|
|
29
|
+
import {
|
|
30
|
+
PAYMENT_TOLERANCE_RATIO
|
|
31
|
+
} from "./chunk-RO62LO6Z.js";
|
|
11
32
|
import {
|
|
12
33
|
RETRY_BLOCKCHAIN_BASE_DELAY_MS,
|
|
13
34
|
RETRY_BLOCKCHAIN_MAX_DELAY_MS,
|
|
@@ -17,10 +38,6 @@ import {
|
|
|
17
38
|
RETRY_DEFAULT_MAX_DELAY_MS,
|
|
18
39
|
RETRY_DEFAULT_TIMEOUT_MS
|
|
19
40
|
} from "./chunk-4DU3C27M.js";
|
|
20
|
-
import {
|
|
21
|
-
PAYMENT_TOLERANCE_RATIO,
|
|
22
|
-
TELEGRAM_MAX_MESSAGE_LENGTH
|
|
23
|
-
} from "./chunk-RO62LO6Z.js";
|
|
24
41
|
import {
|
|
25
42
|
ALLOWED_EXTENSIONS,
|
|
26
43
|
TELETON_ROOT,
|
|
@@ -28,244 +45,19 @@ import {
|
|
|
28
45
|
WORKSPACE_ROOT
|
|
29
46
|
} from "./chunk-EYWNOHMJ.js";
|
|
30
47
|
import {
|
|
31
|
-
|
|
32
|
-
} from "./chunk-
|
|
48
|
+
createLogger
|
|
49
|
+
} from "./chunk-RCMD3U65.js";
|
|
33
50
|
import {
|
|
34
|
-
__require
|
|
51
|
+
__require,
|
|
52
|
+
__toESM
|
|
35
53
|
} from "./chunk-QGM4M3NI.js";
|
|
36
54
|
|
|
37
|
-
// src/config/providers.ts
|
|
38
|
-
var PROVIDER_REGISTRY = {
|
|
39
|
-
anthropic: {
|
|
40
|
-
id: "anthropic",
|
|
41
|
-
displayName: "Anthropic (Claude)",
|
|
42
|
-
envVar: "ANTHROPIC_API_KEY",
|
|
43
|
-
keyPrefix: "sk-ant-",
|
|
44
|
-
keyHint: "sk-ant-api03-...",
|
|
45
|
-
consoleUrl: "https://console.anthropic.com/",
|
|
46
|
-
defaultModel: "claude-opus-4-5-20251101",
|
|
47
|
-
utilityModel: "claude-3-5-haiku-20241022",
|
|
48
|
-
toolLimit: null,
|
|
49
|
-
piAiProvider: "anthropic"
|
|
50
|
-
},
|
|
51
|
-
openai: {
|
|
52
|
-
id: "openai",
|
|
53
|
-
displayName: "OpenAI (GPT-4o)",
|
|
54
|
-
envVar: "OPENAI_API_KEY",
|
|
55
|
-
keyPrefix: "sk-",
|
|
56
|
-
keyHint: "sk-proj-...",
|
|
57
|
-
consoleUrl: "https://platform.openai.com/api-keys",
|
|
58
|
-
defaultModel: "gpt-4o",
|
|
59
|
-
utilityModel: "gpt-4o-mini",
|
|
60
|
-
toolLimit: 128,
|
|
61
|
-
piAiProvider: "openai"
|
|
62
|
-
},
|
|
63
|
-
google: {
|
|
64
|
-
id: "google",
|
|
65
|
-
displayName: "Google (Gemini)",
|
|
66
|
-
envVar: "GOOGLE_API_KEY",
|
|
67
|
-
keyPrefix: null,
|
|
68
|
-
keyHint: "AIza...",
|
|
69
|
-
consoleUrl: "https://aistudio.google.com/apikey",
|
|
70
|
-
defaultModel: "gemini-2.5-flash",
|
|
71
|
-
utilityModel: "gemini-2.0-flash-lite",
|
|
72
|
-
toolLimit: 128,
|
|
73
|
-
piAiProvider: "google"
|
|
74
|
-
},
|
|
75
|
-
xai: {
|
|
76
|
-
id: "xai",
|
|
77
|
-
displayName: "xAI (Grok)",
|
|
78
|
-
envVar: "XAI_API_KEY",
|
|
79
|
-
keyPrefix: "xai-",
|
|
80
|
-
keyHint: "xai-...",
|
|
81
|
-
consoleUrl: "https://console.x.ai/",
|
|
82
|
-
defaultModel: "grok-3",
|
|
83
|
-
utilityModel: "grok-3-mini-fast",
|
|
84
|
-
toolLimit: 128,
|
|
85
|
-
piAiProvider: "xai"
|
|
86
|
-
},
|
|
87
|
-
groq: {
|
|
88
|
-
id: "groq",
|
|
89
|
-
displayName: "Groq",
|
|
90
|
-
envVar: "GROQ_API_KEY",
|
|
91
|
-
keyPrefix: "gsk_",
|
|
92
|
-
keyHint: "gsk_...",
|
|
93
|
-
consoleUrl: "https://console.groq.com/keys",
|
|
94
|
-
defaultModel: "llama-3.3-70b-versatile",
|
|
95
|
-
utilityModel: "llama-3.1-8b-instant",
|
|
96
|
-
toolLimit: 128,
|
|
97
|
-
piAiProvider: "groq"
|
|
98
|
-
},
|
|
99
|
-
openrouter: {
|
|
100
|
-
id: "openrouter",
|
|
101
|
-
displayName: "OpenRouter",
|
|
102
|
-
envVar: "OPENROUTER_API_KEY",
|
|
103
|
-
keyPrefix: "sk-or-",
|
|
104
|
-
keyHint: "sk-or-v1-...",
|
|
105
|
-
consoleUrl: "https://openrouter.ai/keys",
|
|
106
|
-
defaultModel: "anthropic/claude-opus-4.5",
|
|
107
|
-
utilityModel: "google/gemini-2.5-flash-lite",
|
|
108
|
-
toolLimit: 128,
|
|
109
|
-
piAiProvider: "openrouter"
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
function getProviderMetadata(provider) {
|
|
113
|
-
const meta = PROVIDER_REGISTRY[provider];
|
|
114
|
-
if (!meta) {
|
|
115
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
116
|
-
}
|
|
117
|
-
return meta;
|
|
118
|
-
}
|
|
119
|
-
function getSupportedProviders() {
|
|
120
|
-
return Object.values(PROVIDER_REGISTRY);
|
|
121
|
-
}
|
|
122
|
-
function validateApiKeyFormat(provider, key) {
|
|
123
|
-
const meta = PROVIDER_REGISTRY[provider];
|
|
124
|
-
if (!meta) return `Unknown provider: ${provider}`;
|
|
125
|
-
if (!key || key.trim().length === 0) return "API key is required";
|
|
126
|
-
if (meta.keyPrefix && !key.startsWith(meta.keyPrefix)) {
|
|
127
|
-
return `Invalid format (should start with ${meta.keyPrefix})`;
|
|
128
|
-
}
|
|
129
|
-
return void 0;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
55
|
// src/config/loader.ts
|
|
133
|
-
import { readFileSync, existsSync, writeFileSync, mkdirSync
|
|
56
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
134
57
|
import { parse, stringify } from "yaml";
|
|
135
58
|
import { homedir } from "os";
|
|
136
59
|
import { dirname, join } from "path";
|
|
137
|
-
|
|
138
|
-
// src/config/schema.ts
|
|
139
|
-
import { z } from "zod";
|
|
140
|
-
var DMPolicy = z.enum(["pairing", "allowlist", "open", "disabled"]);
|
|
141
|
-
var GroupPolicy = z.enum(["open", "allowlist", "disabled"]);
|
|
142
|
-
var SessionResetPolicySchema = z.object({
|
|
143
|
-
daily_reset_enabled: z.boolean().default(true).describe("Enable daily session reset"),
|
|
144
|
-
daily_reset_hour: z.number().min(0).max(23).default(4).describe("Hour of day (0-23) to reset sessions"),
|
|
145
|
-
idle_expiry_enabled: z.boolean().default(true).describe("Enable session reset after idle period"),
|
|
146
|
-
idle_expiry_minutes: z.number().default(1440).describe("Minutes of inactivity before session reset (default: 24h)")
|
|
147
|
-
});
|
|
148
|
-
var AgentConfigSchema = z.object({
|
|
149
|
-
provider: z.enum(["anthropic", "openai", "google", "xai", "groq", "openrouter"]).default("anthropic"),
|
|
150
|
-
api_key: z.string(),
|
|
151
|
-
model: z.string().default("claude-opus-4-5-20251101"),
|
|
152
|
-
utility_model: z.string().optional().describe("Cheap model for summarization (auto-detected if omitted)"),
|
|
153
|
-
max_tokens: z.number().default(4096),
|
|
154
|
-
temperature: z.number().default(0.7),
|
|
155
|
-
system_prompt: z.string().nullable().default(null),
|
|
156
|
-
max_agentic_iterations: z.number().default(5).describe("Maximum number of agentic loop iterations (tool call \u2192 result \u2192 tool call cycles)"),
|
|
157
|
-
session_reset_policy: SessionResetPolicySchema.default(SessionResetPolicySchema.parse({}))
|
|
158
|
-
});
|
|
159
|
-
var TelegramConfigSchema = z.object({
|
|
160
|
-
api_id: z.number(),
|
|
161
|
-
api_hash: z.string(),
|
|
162
|
-
phone: z.string(),
|
|
163
|
-
session_name: z.string().default("teleton_session"),
|
|
164
|
-
session_path: z.string().default("~/.teleton"),
|
|
165
|
-
dm_policy: DMPolicy.default("pairing"),
|
|
166
|
-
allow_from: z.array(z.number()).default([]),
|
|
167
|
-
group_policy: GroupPolicy.default("open"),
|
|
168
|
-
group_allow_from: z.array(z.number()).default([]),
|
|
169
|
-
require_mention: z.boolean().default(true),
|
|
170
|
-
max_message_length: z.number().default(TELEGRAM_MAX_MESSAGE_LENGTH),
|
|
171
|
-
typing_simulation: z.boolean().default(true),
|
|
172
|
-
rate_limit_messages_per_second: z.number().default(1),
|
|
173
|
-
rate_limit_groups_per_minute: z.number().default(20),
|
|
174
|
-
admin_ids: z.array(z.number()).default([]),
|
|
175
|
-
agent_channel: z.string().nullable().default(null),
|
|
176
|
-
owner_name: z.string().optional().describe("Owner's first name (e.g., 'Alex')"),
|
|
177
|
-
owner_username: z.string().optional().describe("Owner's Telegram username (without @)"),
|
|
178
|
-
owner_id: z.number().optional().describe("Owner's Telegram user ID"),
|
|
179
|
-
debounce_ms: z.number().default(1500).describe("Debounce delay in milliseconds for group messages (0 = disabled)"),
|
|
180
|
-
bot_token: z.string().optional().describe("Telegram Bot token from @BotFather for inline deal buttons"),
|
|
181
|
-
bot_username: z.string().optional().describe("Bot username without @ (e.g., 'teleton_deals_bot')")
|
|
182
|
-
});
|
|
183
|
-
var StorageConfigSchema = z.object({
|
|
184
|
-
sessions_file: z.string().default("~/.teleton/sessions.json"),
|
|
185
|
-
pairing_file: z.string().default("~/.teleton/pairing.json"),
|
|
186
|
-
memory_file: z.string().default("~/.teleton/memory.json"),
|
|
187
|
-
history_limit: z.number().default(100)
|
|
188
|
-
});
|
|
189
|
-
var MetaConfigSchema = z.object({
|
|
190
|
-
version: z.string().default("1.0.0"),
|
|
191
|
-
created_at: z.string().optional(),
|
|
192
|
-
last_modified_at: z.string().optional(),
|
|
193
|
-
onboard_command: z.string().default("teleton setup")
|
|
194
|
-
});
|
|
195
|
-
var _DealsObject = z.object({
|
|
196
|
-
enabled: z.boolean().default(true),
|
|
197
|
-
expiry_seconds: z.number().default(120),
|
|
198
|
-
buy_max_floor_percent: z.number().default(100),
|
|
199
|
-
sell_min_floor_percent: z.number().default(105),
|
|
200
|
-
poll_interval_ms: z.number().default(5e3),
|
|
201
|
-
max_verification_retries: z.number().default(12),
|
|
202
|
-
expiry_check_interval_ms: z.number().default(6e4)
|
|
203
|
-
});
|
|
204
|
-
var DealsConfigSchema = _DealsObject.default(_DealsObject.parse({}));
|
|
205
|
-
var _WebUIObject = z.object({
|
|
206
|
-
enabled: z.boolean().default(false).describe("Enable WebUI server"),
|
|
207
|
-
port: z.number().default(7777).describe("HTTP server port"),
|
|
208
|
-
host: z.string().default("127.0.0.1").describe("Bind address (localhost only for security)"),
|
|
209
|
-
auth_token: z.string().optional().describe("Bearer token for API auth (auto-generated if omitted)"),
|
|
210
|
-
cors_origins: z.array(z.string()).default(["http://localhost:5173", "http://localhost:7777"]).describe("Allowed CORS origins for development"),
|
|
211
|
-
log_requests: z.boolean().default(false).describe("Log all HTTP requests")
|
|
212
|
-
});
|
|
213
|
-
var WebUIConfigSchema = _WebUIObject.default(_WebUIObject.parse({}));
|
|
214
|
-
var _EmbeddingObject = z.object({
|
|
215
|
-
provider: z.enum(["local", "anthropic", "none"]).default("local").describe("Embedding provider: local (ONNX), anthropic (API), or none (FTS5-only)"),
|
|
216
|
-
model: z.string().optional().describe("Model override (default: Xenova/all-MiniLM-L6-v2 for local)")
|
|
217
|
-
});
|
|
218
|
-
var EmbeddingConfigSchema = _EmbeddingObject.default(_EmbeddingObject.parse({}));
|
|
219
|
-
var _DevObject = z.object({
|
|
220
|
-
hot_reload: z.boolean().default(false).describe("Enable plugin hot-reload (watches ~/.teleton/plugins/ for changes)")
|
|
221
|
-
});
|
|
222
|
-
var DevConfigSchema = _DevObject.default(_DevObject.parse({}));
|
|
223
|
-
var McpServerSchema = z.object({
|
|
224
|
-
command: z.string().optional().describe("Stdio command (e.g. 'npx @modelcontextprotocol/server-filesystem /tmp')"),
|
|
225
|
-
args: z.array(z.string()).optional().describe("Explicit args array (overrides command splitting)"),
|
|
226
|
-
env: z.record(z.string(), z.string()).optional().describe("Environment variables for stdio server"),
|
|
227
|
-
url: z.string().url().optional().describe("SSE/HTTP endpoint URL (alternative to command)"),
|
|
228
|
-
scope: z.enum(["always", "dm-only", "group-only", "admin-only"]).default("always").describe("Tool scope"),
|
|
229
|
-
enabled: z.boolean().default(true).describe("Enable/disable this server")
|
|
230
|
-
}).refine((s) => s.command || s.url, {
|
|
231
|
-
message: "Each MCP server needs either 'command' (stdio) or 'url' (SSE/HTTP)"
|
|
232
|
-
});
|
|
233
|
-
var _McpObject = z.object({
|
|
234
|
-
servers: z.record(z.string(), McpServerSchema).default({})
|
|
235
|
-
});
|
|
236
|
-
var McpConfigSchema = _McpObject.default(_McpObject.parse({}));
|
|
237
|
-
var _ToolRagObject = z.object({
|
|
238
|
-
enabled: z.boolean().default(false).describe("Enable semantic tool retrieval (Tool RAG)"),
|
|
239
|
-
top_k: z.number().default(25).describe("Max tools to retrieve per LLM call"),
|
|
240
|
-
always_include: z.array(z.string()).default([
|
|
241
|
-
"telegram_send_message",
|
|
242
|
-
"telegram_reply_message",
|
|
243
|
-
"telegram_send_photo",
|
|
244
|
-
"telegram_send_document",
|
|
245
|
-
"journal_*",
|
|
246
|
-
"workspace_*",
|
|
247
|
-
"web_*"
|
|
248
|
-
]).describe("Tool name patterns always included (prefix glob with *)"),
|
|
249
|
-
skip_unlimited_providers: z.boolean().default(false).describe("Skip Tool RAG for providers with no tool limit (e.g. Anthropic)")
|
|
250
|
-
});
|
|
251
|
-
var ToolRagConfigSchema = _ToolRagObject.default(_ToolRagObject.parse({}));
|
|
252
|
-
var ConfigSchema = z.object({
|
|
253
|
-
meta: MetaConfigSchema.default(MetaConfigSchema.parse({})),
|
|
254
|
-
agent: AgentConfigSchema,
|
|
255
|
-
telegram: TelegramConfigSchema,
|
|
256
|
-
storage: StorageConfigSchema.default(StorageConfigSchema.parse({})),
|
|
257
|
-
embedding: EmbeddingConfigSchema,
|
|
258
|
-
deals: DealsConfigSchema,
|
|
259
|
-
webui: WebUIConfigSchema,
|
|
260
|
-
dev: DevConfigSchema,
|
|
261
|
-
tool_rag: ToolRagConfigSchema,
|
|
262
|
-
mcp: McpConfigSchema,
|
|
263
|
-
plugins: z.record(z.string(), z.unknown()).default({}).describe("Per-plugin config (key = plugin name with underscores)"),
|
|
264
|
-
tonapi_key: z.string().optional().describe("TonAPI key for higher rate limits (from @tonapi_bot)"),
|
|
265
|
-
tavily_api_key: z.string().optional().describe("Tavily API key for web search & extract (free at https://tavily.com)")
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// src/config/loader.ts
|
|
60
|
+
var log = createLogger("Config");
|
|
269
61
|
var DEFAULT_CONFIG_PATH = join(TELETON_ROOT, "config.yaml");
|
|
270
62
|
function expandPath(path) {
|
|
271
63
|
if (path.startsWith("~")) {
|
|
@@ -292,7 +84,7 @@ Run 'teleton setup' to create one.`);
|
|
|
292
84
|
throw new Error(`Invalid YAML in ${fullPath}: ${error.message}`);
|
|
293
85
|
}
|
|
294
86
|
if (raw && typeof raw === "object" && "market" in raw) {
|
|
295
|
-
|
|
87
|
+
log.warn("config.market is deprecated and ignored. Use market-api plugin instead.");
|
|
296
88
|
delete raw.market;
|
|
297
89
|
}
|
|
298
90
|
const result = ConfigSchema.safeParse(raw);
|
|
@@ -332,12 +124,28 @@ Run 'teleton setup' to create one.`);
|
|
|
332
124
|
}
|
|
333
125
|
if (process.env.TELETON_WEBUI_PORT) {
|
|
334
126
|
const port = parseInt(process.env.TELETON_WEBUI_PORT, 10);
|
|
335
|
-
if (!isNaN(port)) {
|
|
127
|
+
if (!isNaN(port) && port >= 1024 && port <= 65535) {
|
|
336
128
|
config.webui.port = port;
|
|
337
129
|
}
|
|
338
130
|
}
|
|
339
131
|
if (process.env.TELETON_WEBUI_HOST) {
|
|
340
132
|
config.webui.host = process.env.TELETON_WEBUI_HOST;
|
|
133
|
+
if (!["127.0.0.1", "localhost", "::1"].includes(config.webui.host)) {
|
|
134
|
+
log.warn(
|
|
135
|
+
{ host: config.webui.host },
|
|
136
|
+
"WebUI bound to non-loopback address \u2014 ensure auth_token is set"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (process.env.TELETON_BASE_URL) {
|
|
141
|
+
try {
|
|
142
|
+
new URL(process.env.TELETON_BASE_URL);
|
|
143
|
+
config.agent.base_url = process.env.TELETON_BASE_URL;
|
|
144
|
+
} catch {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Invalid TELETON_BASE_URL: "${process.env.TELETON_BASE_URL}" is not a valid URL`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
341
149
|
}
|
|
342
150
|
if (process.env.TELETON_TAVILY_API_KEY) {
|
|
343
151
|
config.tavily_api_key = process.env.TELETON_TAVILY_API_KEY;
|
|
@@ -354,169 +162,447 @@ function getDefaultConfigPath() {
|
|
|
354
162
|
return DEFAULT_CONFIG_PATH;
|
|
355
163
|
}
|
|
356
164
|
|
|
357
|
-
// src/
|
|
358
|
-
import {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
import {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
165
|
+
// src/soul/loader.ts
|
|
166
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
|
|
167
|
+
|
|
168
|
+
// src/memory/daily-logs.ts
|
|
169
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, appendFileSync, readFileSync as readFileSync2 } from "fs";
|
|
170
|
+
import { join as join2 } from "path";
|
|
171
|
+
|
|
172
|
+
// src/workspace/validator.ts
|
|
173
|
+
import { existsSync as existsSync2, lstatSync, readdirSync } from "fs";
|
|
174
|
+
import { resolve, normalize, relative, extname, basename } from "path";
|
|
175
|
+
import { homedir as homedir2 } from "os";
|
|
176
|
+
var WorkspaceSecurityError = class extends Error {
|
|
177
|
+
constructor(message, attemptedPath) {
|
|
178
|
+
super(message);
|
|
179
|
+
this.attemptedPath = attemptedPath;
|
|
180
|
+
this.name = "WorkspaceSecurityError";
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function decodeRecursive(str) {
|
|
184
|
+
let decoded = str;
|
|
185
|
+
let prev = "";
|
|
186
|
+
let iterations = 0;
|
|
187
|
+
const maxIterations = 10;
|
|
188
|
+
while (decoded !== prev && iterations < maxIterations) {
|
|
189
|
+
prev = decoded;
|
|
190
|
+
try {
|
|
191
|
+
decoded = decodeURIComponent(decoded);
|
|
192
|
+
} catch {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
iterations++;
|
|
196
|
+
}
|
|
197
|
+
return decoded;
|
|
198
|
+
}
|
|
199
|
+
function validatePath(inputPath, allowCreate = false) {
|
|
200
|
+
if (!inputPath || inputPath.trim() === "") {
|
|
201
|
+
throw new WorkspaceSecurityError("Path cannot be empty.", inputPath);
|
|
202
|
+
}
|
|
203
|
+
const trimmedPath = inputPath.trim().replace(/\\/g, "/");
|
|
204
|
+
const decodedPath = decodeRecursive(trimmedPath);
|
|
205
|
+
let absolutePath;
|
|
206
|
+
if (decodedPath.startsWith("/")) {
|
|
207
|
+
absolutePath = resolve(normalize(decodedPath));
|
|
208
|
+
} else if (decodedPath.startsWith("~/")) {
|
|
209
|
+
const expanded = decodedPath.replace(/^~(?=$|[\\/])/, homedir2());
|
|
210
|
+
absolutePath = resolve(expanded);
|
|
211
|
+
} else {
|
|
212
|
+
absolutePath = resolve(WORKSPACE_ROOT, normalize(decodedPath));
|
|
213
|
+
}
|
|
214
|
+
const relativePath = relative(WORKSPACE_ROOT, absolutePath);
|
|
215
|
+
if (relativePath.startsWith("..") || relativePath.startsWith("/")) {
|
|
216
|
+
throw new WorkspaceSecurityError(
|
|
217
|
+
`Access denied: Path '${inputPath}' is outside the workspace. Only files in ~/.teleton/workspace/ are accessible.`,
|
|
218
|
+
inputPath
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const exists = existsSync2(absolutePath);
|
|
222
|
+
if (!exists && !allowCreate) {
|
|
223
|
+
throw new WorkspaceSecurityError(
|
|
224
|
+
`File not found: '${inputPath}' does not exist in workspace.`,
|
|
225
|
+
inputPath
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (exists) {
|
|
229
|
+
const stats = lstatSync(absolutePath);
|
|
230
|
+
if (stats.isSymbolicLink()) {
|
|
231
|
+
throw new WorkspaceSecurityError(
|
|
232
|
+
`Access denied: Symbolic links are not allowed for security reasons.`,
|
|
233
|
+
inputPath
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
373
237
|
return {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
238
|
+
absolutePath,
|
|
239
|
+
relativePath,
|
|
240
|
+
exists,
|
|
241
|
+
isDirectory: exists ? lstatSync(absolutePath).isDirectory() : false,
|
|
242
|
+
extension: extname(absolutePath).toLowerCase(),
|
|
243
|
+
filename: basename(absolutePath)
|
|
379
244
|
};
|
|
380
245
|
}
|
|
381
|
-
function
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
|
|
246
|
+
function validateReadPath(inputPath) {
|
|
247
|
+
const validated = validatePath(inputPath, false);
|
|
248
|
+
if (validated.isDirectory) {
|
|
249
|
+
throw new WorkspaceSecurityError(`Cannot read directory as file: '${inputPath}'`, inputPath);
|
|
385
250
|
}
|
|
386
|
-
|
|
387
|
-
chmodSync2(WALLET_FILE, 384);
|
|
388
|
-
_walletCache = void 0;
|
|
389
|
-
_keyPairCache = null;
|
|
251
|
+
return validated;
|
|
390
252
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
253
|
+
var IMMUTABLE_FILES = ["SOUL.md", "STRATEGY.md", "SECURITY.md"];
|
|
254
|
+
function validateWritePath(inputPath, fileType) {
|
|
255
|
+
const validated = validatePath(inputPath, true);
|
|
256
|
+
if (IMMUTABLE_FILES.includes(validated.filename)) {
|
|
257
|
+
throw new WorkspaceSecurityError(
|
|
258
|
+
`Cannot write to ${validated.filename}. This file is configured by the owner. Use memory_write instead.`,
|
|
259
|
+
inputPath
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (fileType && ALLOWED_EXTENSIONS[fileType]) {
|
|
263
|
+
const allowedExts = ALLOWED_EXTENSIONS[fileType];
|
|
264
|
+
if (!allowedExts.includes(validated.extension)) {
|
|
265
|
+
throw new WorkspaceSecurityError(
|
|
266
|
+
`Invalid file type: '${validated.extension}' is not allowed for ${fileType}. Allowed: ${allowedExts.join(", ")}`,
|
|
267
|
+
inputPath
|
|
268
|
+
);
|
|
269
|
+
}
|
|
396
270
|
}
|
|
271
|
+
return validated;
|
|
272
|
+
}
|
|
273
|
+
function validateDirectory(inputPath) {
|
|
274
|
+
const validated = validatePath(inputPath, true);
|
|
275
|
+
if (validated.exists && !validated.isDirectory) {
|
|
276
|
+
throw new WorkspaceSecurityError(
|
|
277
|
+
`Path exists but is not a directory: '${inputPath}'`,
|
|
278
|
+
inputPath
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return validated;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/memory/daily-logs.ts
|
|
285
|
+
var log2 = createLogger("Memory");
|
|
286
|
+
var MEMORY_DIR = WORKSPACE_PATHS.MEMORY_DIR;
|
|
287
|
+
function formatDate(date) {
|
|
288
|
+
const year = date.getFullYear();
|
|
289
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
290
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
291
|
+
return `${year}-${month}-${day}`;
|
|
292
|
+
}
|
|
293
|
+
function getDailyLogPath(date = /* @__PURE__ */ new Date()) {
|
|
294
|
+
return join2(MEMORY_DIR, `${formatDate(date)}.md`);
|
|
295
|
+
}
|
|
296
|
+
function ensureMemoryDir() {
|
|
297
|
+
if (!existsSync3(MEMORY_DIR)) {
|
|
298
|
+
mkdirSync2(MEMORY_DIR, { recursive: true });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function appendToDailyLog(content, date = /* @__PURE__ */ new Date()) {
|
|
397
302
|
try {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
303
|
+
ensureMemoryDir();
|
|
304
|
+
const logPath = getDailyLogPath(date);
|
|
305
|
+
const timestamp = date.toLocaleTimeString("en-US", { hour12: false });
|
|
306
|
+
if (!existsSync3(logPath)) {
|
|
307
|
+
const header = `# Daily Log - ${formatDate(date)}
|
|
308
|
+
|
|
309
|
+
`;
|
|
310
|
+
appendFileSync(logPath, header, "utf-8");
|
|
311
|
+
}
|
|
312
|
+
const entry = `## ${timestamp}
|
|
313
|
+
|
|
314
|
+
${content}
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
`;
|
|
319
|
+
appendFileSync(logPath, entry, "utf-8");
|
|
320
|
+
log2.info(`Daily log updated: ${logPath}`);
|
|
401
321
|
} catch (error) {
|
|
402
|
-
|
|
403
|
-
|
|
322
|
+
log2.error({ err: error }, "Failed to write daily log");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function readDailyLog(date = /* @__PURE__ */ new Date()) {
|
|
326
|
+
try {
|
|
327
|
+
const logPath = getDailyLogPath(date);
|
|
328
|
+
if (!existsSync3(logPath)) return null;
|
|
329
|
+
return readFileSync2(logPath, "utf-8");
|
|
330
|
+
} catch {
|
|
404
331
|
return null;
|
|
405
332
|
}
|
|
406
333
|
}
|
|
407
|
-
|
|
408
|
-
|
|
334
|
+
var DAILY_LOG_LINE_LIMIT = 100;
|
|
335
|
+
function truncateDailyLog(content) {
|
|
336
|
+
const lines = content.split("\n");
|
|
337
|
+
if (lines.length <= DAILY_LOG_LINE_LIMIT) return content;
|
|
338
|
+
const truncated = lines.slice(-DAILY_LOG_LINE_LIMIT).join("\n");
|
|
339
|
+
const dropped = lines.length - DAILY_LOG_LINE_LIMIT;
|
|
340
|
+
return `_[... ${dropped} earlier lines omitted]_
|
|
341
|
+
|
|
342
|
+
${truncated}`;
|
|
409
343
|
}
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
344
|
+
function readRecentMemory() {
|
|
345
|
+
const today = /* @__PURE__ */ new Date();
|
|
346
|
+
const yesterday = new Date(today);
|
|
347
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
348
|
+
const parts = [];
|
|
349
|
+
const yesterdayLog = readDailyLog(yesterday);
|
|
350
|
+
if (yesterdayLog) {
|
|
351
|
+
parts.push(`## Yesterday (${formatDate(yesterday)})
|
|
352
|
+
|
|
353
|
+
${truncateDailyLog(yesterdayLog)}`);
|
|
414
354
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
};
|
|
355
|
+
const todayLog = readDailyLog(today);
|
|
356
|
+
if (todayLog) {
|
|
357
|
+
parts.push(`## Today (${formatDate(today)})
|
|
358
|
+
|
|
359
|
+
${truncateDailyLog(todayLog)}`);
|
|
360
|
+
}
|
|
361
|
+
if (parts.length === 0) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
return `# Recent Memory
|
|
365
|
+
|
|
366
|
+
${parts.join("\n\n---\n\n")}`;
|
|
428
367
|
}
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
368
|
+
function writeSummaryToDailyLog(summary) {
|
|
369
|
+
appendToDailyLog(`### Memory Flush (Pre-Compaction)
|
|
370
|
+
|
|
371
|
+
${summary}`);
|
|
432
372
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
373
|
+
|
|
374
|
+
// src/utils/sanitize.ts
|
|
375
|
+
function sanitizeForPrompt(text) {
|
|
376
|
+
return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").replace(/[\u00AD\u034F\u061C\u180E\u200B-\u200F\u2060-\u2064\uFEFF]/g, "").replace(/[\u202A-\u202E\u2066-\u2069]/g, "").replace(/[\r\n\u2028\u2029]+/g, " ").replace(/#{1,6}\s/g, "").replace(/<\/?[a-zA-Z_][^>]*>/g, "").replace(/`{3,}/g, "`").trim().slice(0, 128);
|
|
377
|
+
}
|
|
378
|
+
function sanitizeForContext(text) {
|
|
379
|
+
return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").replace(/[\u00AD\u034F\u061C\u180E\u200B-\u200F\u2060-\u2064\uFEFF]/g, "").replace(/[\u202A-\u202E\u2066-\u2069]/g, "").replace(/[\u2028\u2029]/g, "\n").replace(/<\/?[a-zA-Z_][^>]*>/g, "").replace(/`{3,}/g, "``").trim();
|
|
439
380
|
}
|
|
440
|
-
|
|
381
|
+
|
|
382
|
+
// src/soul/loader.ts
|
|
383
|
+
var SOUL_PATHS = [WORKSPACE_PATHS.SOUL];
|
|
384
|
+
var STRATEGY_PATHS = [WORKSPACE_PATHS.STRATEGY];
|
|
385
|
+
var SECURITY_PATHS = [WORKSPACE_PATHS.SECURITY];
|
|
386
|
+
var MEMORY_PATH = WORKSPACE_PATHS.MEMORY;
|
|
387
|
+
var DEFAULT_SOUL = `# Teleton AI
|
|
388
|
+
|
|
389
|
+
You are Teleton, a personal AI assistant that operates through Telegram.
|
|
390
|
+
|
|
391
|
+
## Personality
|
|
392
|
+
- Helpful and concise
|
|
393
|
+
- Direct and honest
|
|
394
|
+
- Friendly but professional
|
|
395
|
+
|
|
396
|
+
## Guidelines
|
|
397
|
+
- Keep responses short and actionable
|
|
398
|
+
- Use markdown when appropriate
|
|
399
|
+
- Respect user privacy
|
|
400
|
+
- Be transparent about capabilities and limitations
|
|
401
|
+
`;
|
|
402
|
+
var fileCache = /* @__PURE__ */ new Map();
|
|
403
|
+
var FILE_CACHE_TTL = 6e4;
|
|
404
|
+
function cachedReadFile(path) {
|
|
405
|
+
const now = Date.now();
|
|
406
|
+
const cached = fileCache.get(path);
|
|
407
|
+
if (cached && now < cached.expiry) return cached.content;
|
|
408
|
+
let content = null;
|
|
441
409
|
try {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const { Address: Address2 } = await import("@ton/core");
|
|
445
|
-
const addressObj = Address2.parse(address);
|
|
446
|
-
const balance = await client.getBalance(addressObj);
|
|
447
|
-
const balanceFormatted = fromNano(balance);
|
|
448
|
-
return {
|
|
449
|
-
balance: balanceFormatted,
|
|
450
|
-
balanceNano: balance.toString()
|
|
451
|
-
};
|
|
452
|
-
} catch (error) {
|
|
453
|
-
console.error("Failed to get balance:", error);
|
|
454
|
-
return null;
|
|
410
|
+
if (existsSync4(path)) content = readFileSync3(path, "utf-8");
|
|
411
|
+
} catch {
|
|
455
412
|
}
|
|
413
|
+
fileCache.set(path, { content, expiry: now + FILE_CACHE_TTL });
|
|
414
|
+
return content;
|
|
456
415
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
416
|
+
function clearPromptCache() {
|
|
417
|
+
fileCache.clear();
|
|
418
|
+
}
|
|
419
|
+
function loadSoul() {
|
|
420
|
+
for (const path of SOUL_PATHS) {
|
|
421
|
+
const content = cachedReadFile(path);
|
|
422
|
+
if (content) return content;
|
|
462
423
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
424
|
+
return DEFAULT_SOUL;
|
|
425
|
+
}
|
|
426
|
+
function loadStrategy() {
|
|
427
|
+
for (const path of STRATEGY_PATHS) {
|
|
428
|
+
const content = cachedReadFile(path);
|
|
429
|
+
if (content) return content;
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
function loadSecurity() {
|
|
434
|
+
for (const path of SECURITY_PATHS) {
|
|
435
|
+
const content = cachedReadFile(path);
|
|
436
|
+
if (content) return content;
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
var MEMORY_HARD_LIMIT = 150;
|
|
441
|
+
function loadPersistentMemory() {
|
|
442
|
+
const content = cachedReadFile(MEMORY_PATH);
|
|
443
|
+
if (!content) return null;
|
|
444
|
+
const lines = content.split("\n");
|
|
445
|
+
if (lines.length <= MEMORY_HARD_LIMIT) {
|
|
446
|
+
return content;
|
|
447
|
+
}
|
|
448
|
+
const truncated = lines.slice(0, MEMORY_HARD_LIMIT).join("\n");
|
|
449
|
+
const remaining = lines.length - MEMORY_HARD_LIMIT;
|
|
450
|
+
return `${truncated}
|
|
451
|
+
|
|
452
|
+
_[... ${remaining} more lines not loaded. Consider consolidating MEMORY.md to keep it under ${MEMORY_HARD_LIMIT} lines.]_`;
|
|
453
|
+
}
|
|
454
|
+
function loadMemoryContext() {
|
|
455
|
+
const parts = [];
|
|
456
|
+
const persistentMemory = loadPersistentMemory();
|
|
457
|
+
if (persistentMemory) {
|
|
458
|
+
parts.push(`## Persistent Memory
|
|
459
|
+
|
|
460
|
+
${sanitizeForContext(persistentMemory)}`);
|
|
461
|
+
}
|
|
462
|
+
const recentMemory = readRecentMemory();
|
|
463
|
+
if (recentMemory) {
|
|
464
|
+
parts.push(sanitizeForContext(recentMemory));
|
|
465
|
+
}
|
|
466
|
+
if (parts.length === 0) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
return parts.join("\n\n---\n\n");
|
|
470
|
+
}
|
|
471
|
+
function buildSystemPrompt(options) {
|
|
472
|
+
const soul = options.soul ?? loadSoul();
|
|
473
|
+
const parts = [soul];
|
|
474
|
+
const security = loadSecurity();
|
|
475
|
+
if (security) {
|
|
476
|
+
parts.push(`
|
|
477
|
+
${security}`);
|
|
478
|
+
}
|
|
479
|
+
const includeStrategy = options.includeStrategy ?? true;
|
|
480
|
+
if (includeStrategy) {
|
|
481
|
+
const strategy = options.strategy ?? loadStrategy();
|
|
482
|
+
if (strategy) {
|
|
483
|
+
parts.push(`
|
|
484
|
+
${strategy}`);
|
|
472
485
|
}
|
|
473
|
-
} catch {
|
|
474
486
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
487
|
+
parts.push(`
|
|
488
|
+
## Your Workspace
|
|
489
|
+
|
|
490
|
+
You have a personal workspace at \`~/.teleton/workspace/\` where you can store and manage files.
|
|
491
|
+
|
|
492
|
+
**Structure:**
|
|
493
|
+
- \`SOUL.md\` - Your personality and behavior guidelines
|
|
494
|
+
- \`MEMORY.md\` - Persistent memory (long-term facts you've learned)
|
|
495
|
+
- \`STRATEGY.md\` - Business strategy and trading rules
|
|
496
|
+
- \`memory/\` - Daily logs (auto-created per day)
|
|
497
|
+
- \`downloads/\` - Media downloaded from Telegram
|
|
498
|
+
- \`uploads/\` - Files ready to send
|
|
499
|
+
- \`temp/\` - Temporary working files
|
|
500
|
+
- \`memes/\` - Your meme collection (images, GIFs for reactions)
|
|
501
|
+
|
|
502
|
+
**Tools available:**
|
|
503
|
+
- \`workspace_list\` - List files in a directory
|
|
504
|
+
- \`workspace_read\` - Read a file
|
|
505
|
+
- \`workspace_write\` - Write/create a file
|
|
506
|
+
- \`workspace_delete\` - Delete a file
|
|
507
|
+
- \`workspace_rename\` - Rename or move a file
|
|
508
|
+
- \`workspace_info\` - Get workspace stats
|
|
509
|
+
|
|
510
|
+
**Tips:**
|
|
511
|
+
- Save interesting memes to \`memes/\` with descriptive names for easy retrieval
|
|
512
|
+
- Use \`memory_write\` for important facts (goes to MEMORY.md)
|
|
513
|
+
- Rename downloaded files to meaningful names (e.g., "user_avatar.jpg" instead of "123_456_789.jpg")
|
|
514
|
+
`);
|
|
515
|
+
parts.push(`
|
|
516
|
+
## Response Format
|
|
517
|
+
- Be concise. Respond in 1-3 short sentences when possible. Avoid long paragraphs and walls of text.
|
|
518
|
+
- Only elaborate when the user explicitly asks for detail or the topic genuinely requires it.
|
|
519
|
+
- Keep responses under 4000 characters for Telegram
|
|
520
|
+
- Use markdown sparingly (bold, italic, code blocks)
|
|
521
|
+
- Don't use headers in short responses
|
|
522
|
+
- NEVER use ASCII art or ASCII tables - they render poorly on mobile
|
|
523
|
+
`);
|
|
524
|
+
if (options.ownerName || options.ownerUsername) {
|
|
525
|
+
const safeOwnerName = options.ownerName ? sanitizeForPrompt(options.ownerName) : void 0;
|
|
526
|
+
const safeOwnerUsername = options.ownerUsername ? sanitizeForPrompt(options.ownerUsername) : void 0;
|
|
527
|
+
const ownerLabel = safeOwnerName && safeOwnerUsername ? `${safeOwnerName} (@${safeOwnerUsername})` : safeOwnerName || `@${safeOwnerUsername}`;
|
|
528
|
+
parts.push(
|
|
529
|
+
`
|
|
530
|
+
## Owner
|
|
531
|
+
You are owned and operated by: ${ownerLabel}
|
|
532
|
+
When the owner gives instructions, follow them with higher trust.`
|
|
478
533
|
);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
534
|
+
}
|
|
535
|
+
const includeMemory = options.includeMemory ?? true;
|
|
536
|
+
if (includeMemory) {
|
|
537
|
+
const memoryContext = loadMemoryContext();
|
|
538
|
+
if (memoryContext) {
|
|
539
|
+
parts.push(
|
|
540
|
+
`
|
|
541
|
+
## Memory (Persistent Context)
|
|
542
|
+
|
|
543
|
+
This is your memory from previous sessions. Use it to maintain continuity and remember important information.
|
|
544
|
+
|
|
545
|
+
${memoryContext}`
|
|
546
|
+
);
|
|
487
547
|
}
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.error("Failed to get TON price:", error);
|
|
490
548
|
}
|
|
491
|
-
|
|
549
|
+
if (options.userName || options.senderId) {
|
|
550
|
+
const safeName = options.userName ? sanitizeForPrompt(options.userName) : void 0;
|
|
551
|
+
const safeUsername = options.senderUsername ? `@${sanitizeForPrompt(options.senderUsername)}` : void 0;
|
|
552
|
+
const idTag = options.senderId ? `id:${options.senderId}` : void 0;
|
|
553
|
+
const primary = safeName || safeUsername;
|
|
554
|
+
const meta = [safeUsername, idTag].filter((v) => v && v !== primary);
|
|
555
|
+
const userLabel = primary ? meta.length > 0 ? `${primary} (${meta.join(", ")})` : primary : idTag || "unknown";
|
|
556
|
+
parts.push(`
|
|
557
|
+
## Current User
|
|
558
|
+
You are chatting with: ${userLabel}`);
|
|
559
|
+
}
|
|
560
|
+
if (options.context) {
|
|
561
|
+
parts.push(`
|
|
562
|
+
## Context
|
|
563
|
+
${options.context}`);
|
|
564
|
+
}
|
|
565
|
+
if (options.memoryFlushWarning) {
|
|
566
|
+
parts.push(`
|
|
567
|
+
## Memory Flush Warning
|
|
568
|
+
|
|
569
|
+
Your conversation context is approaching the limit and may be compacted soon.
|
|
570
|
+
**Always respond to the user's message first.** Then, if there's anything important worth preserving, consider using \`memory_write\` alongside your response:
|
|
571
|
+
|
|
572
|
+
- \`target: "persistent"\` for facts, lessons, contacts, decisions
|
|
573
|
+
- \`target: "daily"\` for session notes, events, temporary context
|
|
574
|
+
`);
|
|
575
|
+
}
|
|
576
|
+
return parts.join("\n");
|
|
492
577
|
}
|
|
493
578
|
|
|
494
579
|
// src/agent/tools/plugin-loader.ts
|
|
495
|
-
import { readdirSync, readFileSync as
|
|
580
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync6, statSync } from "fs";
|
|
496
581
|
import { join as join4 } from "path";
|
|
497
582
|
import { pathToFileURL } from "url";
|
|
498
583
|
import { execFile } from "child_process";
|
|
499
584
|
import { promisify } from "util";
|
|
500
585
|
|
|
501
586
|
// src/agent/tools/plugin-validator.ts
|
|
502
|
-
import { z
|
|
503
|
-
var
|
|
504
|
-
|
|
587
|
+
import { z } from "zod";
|
|
588
|
+
var log3 = createLogger("PluginValidator");
|
|
589
|
+
var ManifestSchema = z.object({
|
|
590
|
+
name: z.string().min(1).max(64).regex(
|
|
505
591
|
/^[a-z0-9][a-z0-9-]*$/,
|
|
506
592
|
"Must be lowercase alphanumeric with hyphens, starting with a letter or number"
|
|
507
593
|
),
|
|
508
|
-
version:
|
|
509
|
-
author:
|
|
510
|
-
description:
|
|
511
|
-
dependencies:
|
|
512
|
-
defaultConfig:
|
|
513
|
-
sdkVersion:
|
|
514
|
-
secrets:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
required:
|
|
518
|
-
description:
|
|
519
|
-
env:
|
|
594
|
+
version: z.string().regex(/^\d+\.\d+\.\d+$/, "Must be semver (e.g., 1.0.0)"),
|
|
595
|
+
author: z.string().max(128).optional(),
|
|
596
|
+
description: z.string().max(256).optional(),
|
|
597
|
+
dependencies: z.array(z.string()).optional(),
|
|
598
|
+
defaultConfig: z.record(z.string(), z.unknown()).optional(),
|
|
599
|
+
sdkVersion: z.string().max(32).optional(),
|
|
600
|
+
secrets: z.record(
|
|
601
|
+
z.string(),
|
|
602
|
+
z.object({
|
|
603
|
+
required: z.boolean(),
|
|
604
|
+
description: z.string().max(256),
|
|
605
|
+
env: z.string().max(128).optional()
|
|
520
606
|
})
|
|
521
607
|
).optional()
|
|
522
608
|
});
|
|
@@ -527,20 +613,20 @@ function validateToolDefs(defs, pluginName) {
|
|
|
527
613
|
const valid = [];
|
|
528
614
|
for (const def of defs) {
|
|
529
615
|
if (!def || typeof def !== "object") {
|
|
530
|
-
|
|
616
|
+
log3.warn(`[${pluginName}] tool is not an object, skipping`);
|
|
531
617
|
continue;
|
|
532
618
|
}
|
|
533
619
|
const t = def;
|
|
534
620
|
if (!t.name || typeof t.name !== "string") {
|
|
535
|
-
|
|
621
|
+
log3.warn(`[${pluginName}] tool missing 'name', skipping`);
|
|
536
622
|
continue;
|
|
537
623
|
}
|
|
538
624
|
if (!t.description || typeof t.description !== "string") {
|
|
539
|
-
|
|
625
|
+
log3.warn(`[${pluginName}] tool "${t.name}" missing 'description', skipping`);
|
|
540
626
|
continue;
|
|
541
627
|
}
|
|
542
628
|
if (!t.execute || typeof t.execute !== "function") {
|
|
543
|
-
|
|
629
|
+
log3.warn(`[${pluginName}] tool "${t.name}" missing 'execute' function, skipping`);
|
|
544
630
|
continue;
|
|
545
631
|
}
|
|
546
632
|
valid.push(t);
|
|
@@ -572,35 +658,40 @@ var PluginSDKError = class extends Error {
|
|
|
572
658
|
var SDK_VERSION = "1.0.0";
|
|
573
659
|
|
|
574
660
|
// src/ton/transfer.ts
|
|
575
|
-
import { WalletContractV5R1
|
|
661
|
+
import { WalletContractV5R1, TonClient, toNano, internal } from "@ton/ton";
|
|
576
662
|
import { Address, SendMode } from "@ton/core";
|
|
663
|
+
var log4 = createLogger("TON");
|
|
577
664
|
async function sendTon(params) {
|
|
578
665
|
try {
|
|
579
666
|
const { toAddress, amount, comment = "", bounce = false } = params;
|
|
667
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
668
|
+
log4.error({ amount }, "Invalid transfer amount");
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
580
671
|
let recipientAddress;
|
|
581
672
|
try {
|
|
582
673
|
recipientAddress = Address.parse(toAddress);
|
|
583
674
|
} catch (e) {
|
|
584
|
-
|
|
675
|
+
log4.error({ err: e }, `Invalid recipient address: ${toAddress}`);
|
|
585
676
|
return null;
|
|
586
677
|
}
|
|
587
678
|
const keyPair = await getKeyPair();
|
|
588
679
|
if (!keyPair) {
|
|
589
|
-
|
|
680
|
+
log4.error("Wallet not initialized");
|
|
590
681
|
return null;
|
|
591
682
|
}
|
|
592
|
-
const wallet =
|
|
683
|
+
const wallet = WalletContractV5R1.create({
|
|
593
684
|
workchain: 0,
|
|
594
685
|
publicKey: keyPair.publicKey
|
|
595
686
|
});
|
|
596
687
|
const endpoint = await getCachedHttpEndpoint();
|
|
597
|
-
const client = new
|
|
688
|
+
const client = new TonClient({ endpoint });
|
|
598
689
|
const contract = client.open(wallet);
|
|
599
690
|
const seqno = await contract.getSeqno();
|
|
600
691
|
await contract.sendTransfer({
|
|
601
692
|
seqno,
|
|
602
693
|
secretKey: keyPair.secretKey,
|
|
603
|
-
sendMode: SendMode.PAY_GAS_SEPARATELY
|
|
694
|
+
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
|
604
695
|
messages: [
|
|
605
696
|
internal({
|
|
606
697
|
to: recipientAddress,
|
|
@@ -611,15 +702,16 @@ async function sendTon(params) {
|
|
|
611
702
|
]
|
|
612
703
|
});
|
|
613
704
|
const pseudoHash = `${seqno}_${Date.now()}_${amount.toFixed(2)}`;
|
|
614
|
-
|
|
705
|
+
log4.info(`Sent ${amount} TON to ${toAddress.slice(0, 8)}... - seqno: ${seqno}`);
|
|
615
706
|
return pseudoHash;
|
|
616
707
|
} catch (error) {
|
|
617
|
-
|
|
708
|
+
log4.error({ err: error }, "Error sending TON");
|
|
618
709
|
return null;
|
|
619
710
|
}
|
|
620
711
|
}
|
|
621
712
|
|
|
622
713
|
// src/utils/retry.ts
|
|
714
|
+
var log5 = createLogger("Utils");
|
|
623
715
|
var DEFAULT_OPTIONS = {
|
|
624
716
|
maxAttempts: RETRY_DEFAULT_MAX_ATTEMPTS,
|
|
625
717
|
baseDelayMs: RETRY_DEFAULT_BASE_DELAY_MS,
|
|
@@ -643,7 +735,7 @@ async function withRetry(fn, options = {}) {
|
|
|
643
735
|
return result;
|
|
644
736
|
} catch (error) {
|
|
645
737
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
646
|
-
|
|
738
|
+
log5.warn(`Retry attempt ${attempt}/${opts.maxAttempts} failed: ${lastError.message}`);
|
|
647
739
|
if (attempt < opts.maxAttempts) {
|
|
648
740
|
const delay = Math.min(opts.baseDelayMs * Math.pow(2, attempt - 1), opts.maxDelayMs);
|
|
649
741
|
await sleep(delay);
|
|
@@ -661,7 +753,7 @@ async function withBlockchainRetry(fn, operation = "blockchain operation") {
|
|
|
661
753
|
timeout: RETRY_BLOCKCHAIN_TIMEOUT_MS
|
|
662
754
|
});
|
|
663
755
|
} catch (error) {
|
|
664
|
-
const message =
|
|
756
|
+
const message = getErrorMessage(error);
|
|
665
757
|
throw new Error(`${operation} failed after retries: ${message}`);
|
|
666
758
|
}
|
|
667
759
|
}
|
|
@@ -670,25 +762,25 @@ async function withBlockchainRetry(fn, operation = "blockchain operation") {
|
|
|
670
762
|
var DEFAULT_MAX_AGE_MINUTES = 10;
|
|
671
763
|
var DEFAULT_TX_RETENTION_DAYS = 30;
|
|
672
764
|
var CLEANUP_PROBABILITY = 0.1;
|
|
673
|
-
function cleanupOldTransactions(db, retentionDays,
|
|
765
|
+
function cleanupOldTransactions(db, retentionDays, log7) {
|
|
674
766
|
if (Math.random() > CLEANUP_PROBABILITY) return;
|
|
675
767
|
try {
|
|
676
768
|
const cutoff = Math.floor(Date.now() / 1e3) - retentionDays * 24 * 60 * 60;
|
|
677
769
|
const result = db.prepare("DELETE FROM used_transactions WHERE used_at < ?").run(cutoff);
|
|
678
770
|
if (result.changes > 0) {
|
|
679
|
-
|
|
771
|
+
log7.debug(`Cleaned up ${result.changes} old transaction records (>${retentionDays}d)`);
|
|
680
772
|
}
|
|
681
773
|
} catch (err) {
|
|
682
|
-
|
|
774
|
+
log7.error("Transaction cleanup failed:", err);
|
|
683
775
|
}
|
|
684
776
|
}
|
|
685
|
-
function createTonSDK(
|
|
777
|
+
function createTonSDK(log7, db) {
|
|
686
778
|
return {
|
|
687
779
|
getAddress() {
|
|
688
780
|
try {
|
|
689
781
|
return getWalletAddress();
|
|
690
782
|
} catch (err) {
|
|
691
|
-
|
|
783
|
+
log7.error("ton.getAddress() failed:", err);
|
|
692
784
|
return null;
|
|
693
785
|
}
|
|
694
786
|
},
|
|
@@ -698,7 +790,7 @@ function createTonSDK(log, db) {
|
|
|
698
790
|
if (!addr) return null;
|
|
699
791
|
return await getWalletBalance(addr);
|
|
700
792
|
} catch (err) {
|
|
701
|
-
|
|
793
|
+
log7.error("ton.getBalance() failed:", err);
|
|
702
794
|
return null;
|
|
703
795
|
}
|
|
704
796
|
},
|
|
@@ -706,7 +798,7 @@ function createTonSDK(log, db) {
|
|
|
706
798
|
try {
|
|
707
799
|
return await getTonPrice();
|
|
708
800
|
} catch (err) {
|
|
709
|
-
|
|
801
|
+
log7.error("ton.getPrice() failed:", err);
|
|
710
802
|
return null;
|
|
711
803
|
}
|
|
712
804
|
},
|
|
@@ -748,13 +840,13 @@ function createTonSDK(log, db) {
|
|
|
748
840
|
},
|
|
749
841
|
async getTransactions(address, limit) {
|
|
750
842
|
try {
|
|
751
|
-
const { TonClient:
|
|
843
|
+
const { TonClient: TonClient2 } = await import("@ton/ton");
|
|
752
844
|
const { Address: Address2 } = await import("@ton/core");
|
|
753
845
|
const { getCachedHttpEndpoint: getCachedHttpEndpoint2 } = await import("./endpoint-FLYNEZ2F.js");
|
|
754
846
|
const { formatTransactions } = await import("./format-transactions-FD74HI5N.js");
|
|
755
847
|
const addressObj = Address2.parse(address);
|
|
756
848
|
const endpoint = await getCachedHttpEndpoint2();
|
|
757
|
-
const client = new
|
|
849
|
+
const client = new TonClient2({ endpoint });
|
|
758
850
|
const transactions = await withBlockchainRetry(
|
|
759
851
|
() => client.getTransactions(addressObj, {
|
|
760
852
|
limit: Math.min(limit ?? 10, 50)
|
|
@@ -763,7 +855,7 @@ function createTonSDK(log, db) {
|
|
|
763
855
|
);
|
|
764
856
|
return formatTransactions(transactions);
|
|
765
857
|
} catch (err) {
|
|
766
|
-
|
|
858
|
+
log7.error("ton.getTransactions() failed:", err);
|
|
767
859
|
return [];
|
|
768
860
|
}
|
|
769
861
|
},
|
|
@@ -779,7 +871,7 @@ function createTonSDK(log, db) {
|
|
|
779
871
|
throw new PluginSDKError("Wallet not initialized", "WALLET_NOT_INITIALIZED");
|
|
780
872
|
}
|
|
781
873
|
const maxAgeMinutes = params.maxAgeMinutes ?? DEFAULT_MAX_AGE_MINUTES;
|
|
782
|
-
cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS,
|
|
874
|
+
cleanupOldTransactions(db, DEFAULT_TX_RETENTION_DAYS, log7);
|
|
783
875
|
try {
|
|
784
876
|
const txs = await this.getTransactions(address, 20);
|
|
785
877
|
for (const tx of txs) {
|
|
@@ -813,7 +905,7 @@ function createTonSDK(log, db) {
|
|
|
813
905
|
};
|
|
814
906
|
} catch (err) {
|
|
815
907
|
if (err instanceof PluginSDKError) throw err;
|
|
816
|
-
|
|
908
|
+
log7.error("ton.verifyPayment() failed:", err);
|
|
817
909
|
return {
|
|
818
910
|
verified: false,
|
|
819
911
|
error: `Verification failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -827,7 +919,7 @@ function createTonSDK(log, db) {
|
|
|
827
919
|
if (!addr) return [];
|
|
828
920
|
const response = await tonapiFetch(`/accounts/${addr}/jettons`);
|
|
829
921
|
if (!response.ok) {
|
|
830
|
-
|
|
922
|
+
log7.error(`ton.getJettonBalances() TonAPI error: ${response.status}`);
|
|
831
923
|
return [];
|
|
832
924
|
}
|
|
833
925
|
const data = await response.json();
|
|
@@ -855,7 +947,7 @@ function createTonSDK(log, db) {
|
|
|
855
947
|
}
|
|
856
948
|
return balances;
|
|
857
949
|
} catch (err) {
|
|
858
|
-
|
|
950
|
+
log7.error("ton.getJettonBalances() failed:", err);
|
|
859
951
|
return [];
|
|
860
952
|
}
|
|
861
953
|
},
|
|
@@ -864,7 +956,7 @@ function createTonSDK(log, db) {
|
|
|
864
956
|
const response = await tonapiFetch(`/jettons/${jettonAddress}`);
|
|
865
957
|
if (response.status === 404) return null;
|
|
866
958
|
if (!response.ok) {
|
|
867
|
-
|
|
959
|
+
log7.error(`ton.getJettonInfo() TonAPI error: ${response.status}`);
|
|
868
960
|
return null;
|
|
869
961
|
}
|
|
870
962
|
const data = await response.json();
|
|
@@ -882,13 +974,13 @@ function createTonSDK(log, db) {
|
|
|
882
974
|
image: data.preview || metadata.image || void 0
|
|
883
975
|
};
|
|
884
976
|
} catch (err) {
|
|
885
|
-
|
|
977
|
+
log7.error("ton.getJettonInfo() failed:", err);
|
|
886
978
|
return null;
|
|
887
979
|
}
|
|
888
980
|
},
|
|
889
981
|
async sendJetton(jettonAddress, to, amount, opts) {
|
|
890
982
|
const { Address: Address2, beginCell, SendMode: SendMode2 } = await import("@ton/core");
|
|
891
|
-
const { WalletContractV5R1:
|
|
983
|
+
const { WalletContractV5R1: WalletContractV5R12, TonClient: TonClient2, toNano: toNano2, internal: internal2 } = await import("@ton/ton");
|
|
892
984
|
const { getCachedHttpEndpoint: getCachedHttpEndpoint2 } = await import("./endpoint-FLYNEZ2F.js");
|
|
893
985
|
const walletData = loadWallet();
|
|
894
986
|
if (!walletData) {
|
|
@@ -922,7 +1014,9 @@ function createTonSDK(log, db) {
|
|
|
922
1014
|
const senderJettonWallet = jettonBalance.wallet_address.address;
|
|
923
1015
|
const decimals = jettonBalance.jetton.decimals || 9;
|
|
924
1016
|
const currentBalance = BigInt(jettonBalance.balance);
|
|
925
|
-
const
|
|
1017
|
+
const amountStr = amount.toFixed(decimals);
|
|
1018
|
+
const [whole, frac = ""] = amountStr.split(".");
|
|
1019
|
+
const amountInUnits = BigInt(whole + (frac + "0".repeat(decimals)).slice(0, decimals));
|
|
926
1020
|
if (amountInUnits > currentBalance) {
|
|
927
1021
|
throw new PluginSDKError(
|
|
928
1022
|
`Insufficient balance. Have ${Number(currentBalance) / 10 ** decimals}, need ${amount}`,
|
|
@@ -940,12 +1034,12 @@ function createTonSDK(log, db) {
|
|
|
940
1034
|
if (!keyPair) {
|
|
941
1035
|
throw new PluginSDKError("Wallet key derivation failed", "OPERATION_FAILED");
|
|
942
1036
|
}
|
|
943
|
-
const wallet =
|
|
1037
|
+
const wallet = WalletContractV5R12.create({
|
|
944
1038
|
workchain: 0,
|
|
945
1039
|
publicKey: keyPair.publicKey
|
|
946
1040
|
});
|
|
947
1041
|
const endpoint = await getCachedHttpEndpoint2();
|
|
948
|
-
const client = new
|
|
1042
|
+
const client = new TonClient2({ endpoint });
|
|
949
1043
|
const walletContract = client.open(wallet);
|
|
950
1044
|
const seqno = await walletContract.getSeqno();
|
|
951
1045
|
await walletContract.sendTransfer({
|
|
@@ -967,7 +1061,7 @@ function createTonSDK(log, db) {
|
|
|
967
1061
|
try {
|
|
968
1062
|
const response = await tonapiFetch(`/accounts/${ownerAddress}/jettons`);
|
|
969
1063
|
if (!response.ok) {
|
|
970
|
-
|
|
1064
|
+
log7.error(`ton.getJettonWalletAddress() TonAPI error: ${response.status}`);
|
|
971
1065
|
return null;
|
|
972
1066
|
}
|
|
973
1067
|
const { Address: Address2 } = await import("@ton/core");
|
|
@@ -977,7 +1071,7 @@ function createTonSDK(log, db) {
|
|
|
977
1071
|
);
|
|
978
1072
|
return match ? match.wallet_address.address : null;
|
|
979
1073
|
} catch (err) {
|
|
980
|
-
|
|
1074
|
+
log7.error("ton.getJettonWalletAddress() failed:", err);
|
|
981
1075
|
return null;
|
|
982
1076
|
}
|
|
983
1077
|
},
|
|
@@ -990,14 +1084,14 @@ function createTonSDK(log, db) {
|
|
|
990
1084
|
`/accounts/${encodeURIComponent(addr)}/nfts?limit=100&indirect_ownership=true`
|
|
991
1085
|
);
|
|
992
1086
|
if (!response.ok) {
|
|
993
|
-
|
|
1087
|
+
log7.error(`ton.getNftItems() TonAPI error: ${response.status}`);
|
|
994
1088
|
return [];
|
|
995
1089
|
}
|
|
996
1090
|
const data = await response.json();
|
|
997
1091
|
if (!Array.isArray(data.nft_items)) return [];
|
|
998
1092
|
return data.nft_items.filter((item) => item.trust !== "blacklist").map((item) => mapNftItem(item));
|
|
999
1093
|
} catch (err) {
|
|
1000
|
-
|
|
1094
|
+
log7.error("ton.getNftItems() failed:", err);
|
|
1001
1095
|
return [];
|
|
1002
1096
|
}
|
|
1003
1097
|
},
|
|
@@ -1006,13 +1100,13 @@ function createTonSDK(log, db) {
|
|
|
1006
1100
|
const response = await tonapiFetch(`/nfts/${nftAddress}`);
|
|
1007
1101
|
if (response.status === 404) return null;
|
|
1008
1102
|
if (!response.ok) {
|
|
1009
|
-
|
|
1103
|
+
log7.error(`ton.getNftInfo() TonAPI error: ${response.status}`);
|
|
1010
1104
|
return null;
|
|
1011
1105
|
}
|
|
1012
1106
|
const item = await response.json();
|
|
1013
1107
|
return mapNftItem(item);
|
|
1014
1108
|
} catch (err) {
|
|
1015
|
-
|
|
1109
|
+
log7.error("ton.getNftInfo() failed:", err);
|
|
1016
1110
|
return null;
|
|
1017
1111
|
}
|
|
1018
1112
|
},
|
|
@@ -1061,12 +1155,18 @@ function mapNftItem(item) {
|
|
|
1061
1155
|
};
|
|
1062
1156
|
}
|
|
1063
1157
|
|
|
1064
|
-
// src/
|
|
1065
|
-
|
|
1158
|
+
// src/utils/gramjs-bigint.ts
|
|
1159
|
+
var import_big_integer = __toESM(require_BigInteger(), 1);
|
|
1160
|
+
import { randomBytes } from "crypto";
|
|
1161
|
+
function toLong(value) {
|
|
1162
|
+
return (0, import_big_integer.default)(String(value));
|
|
1163
|
+
}
|
|
1164
|
+
function randomLong() {
|
|
1165
|
+
return toLong(randomBytes(8).readBigUInt64BE());
|
|
1166
|
+
}
|
|
1066
1167
|
|
|
1067
1168
|
// src/sdk/telegram-messages.ts
|
|
1068
|
-
|
|
1069
|
-
function createTelegramMessagesSDK(bridge, log) {
|
|
1169
|
+
function createTelegramMessagesSDK(bridge, log7) {
|
|
1070
1170
|
function requireBridge() {
|
|
1071
1171
|
if (!bridge.isAvailable()) {
|
|
1072
1172
|
throw new PluginSDKError(
|
|
@@ -1128,12 +1228,11 @@ function createTelegramMessagesSDK(bridge, log) {
|
|
|
1128
1228
|
fromPeer: fromChatId,
|
|
1129
1229
|
toPeer: toChatId,
|
|
1130
1230
|
id: [messageId],
|
|
1131
|
-
randomId: [
|
|
1231
|
+
randomId: [randomLong()]
|
|
1132
1232
|
})
|
|
1133
1233
|
);
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
for (const update of updates.updates) {
|
|
1234
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
1235
|
+
for (const update of result.updates) {
|
|
1137
1236
|
if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
|
|
1138
1237
|
return update.message.id;
|
|
1139
1238
|
}
|
|
@@ -1187,7 +1286,7 @@ function createTelegramMessagesSDK(bridge, log) {
|
|
|
1187
1286
|
return (resultData.messages ?? []).map(toSimpleMessage);
|
|
1188
1287
|
} catch (err) {
|
|
1189
1288
|
if (err instanceof PluginSDKError) throw err;
|
|
1190
|
-
|
|
1289
|
+
log7.error("telegram.searchMessages() failed:", err);
|
|
1191
1290
|
return [];
|
|
1192
1291
|
}
|
|
1193
1292
|
},
|
|
@@ -1213,7 +1312,7 @@ function createTelegramMessagesSDK(bridge, log) {
|
|
|
1213
1312
|
try {
|
|
1214
1313
|
const gramJsClient = getClient();
|
|
1215
1314
|
const { Api } = await import("telegram");
|
|
1216
|
-
const
|
|
1315
|
+
const bigInt2 = (await import("./BigInteger-DQ33LTTE.js")).default;
|
|
1217
1316
|
const peer = await gramJsClient.getInputEntity(chatId);
|
|
1218
1317
|
const result = await gramJsClient.invoke(
|
|
1219
1318
|
new Api.messages.GetReplies({
|
|
@@ -1225,7 +1324,7 @@ function createTelegramMessagesSDK(bridge, log) {
|
|
|
1225
1324
|
limit,
|
|
1226
1325
|
maxId: 0,
|
|
1227
1326
|
minId: 0,
|
|
1228
|
-
hash:
|
|
1327
|
+
hash: bigInt2(0)
|
|
1229
1328
|
})
|
|
1230
1329
|
);
|
|
1231
1330
|
const messages = [];
|
|
@@ -1412,8 +1511,7 @@ function createTelegramMessagesSDK(bridge, log) {
|
|
|
1412
1511
|
}
|
|
1413
1512
|
|
|
1414
1513
|
// src/sdk/telegram-social.ts
|
|
1415
|
-
|
|
1416
|
-
function createTelegramSocialSDK(bridge, log) {
|
|
1514
|
+
function createTelegramSocialSDK(bridge, log7) {
|
|
1417
1515
|
function requireBridge() {
|
|
1418
1516
|
if (!bridge.isAvailable()) {
|
|
1419
1517
|
throw new PluginSDKError(
|
|
@@ -1455,9 +1553,7 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1455
1553
|
let description;
|
|
1456
1554
|
let membersCount;
|
|
1457
1555
|
try {
|
|
1458
|
-
const fullChannel = await client.invoke(
|
|
1459
|
-
new Api.channels.GetFullChannel({ channel: entity })
|
|
1460
|
-
);
|
|
1556
|
+
const fullChannel = await client.invoke(new Api.channels.GetFullChannel({ channel }));
|
|
1461
1557
|
const fullChat = fullChannel.fullChat;
|
|
1462
1558
|
description = fullChat.about || void 0;
|
|
1463
1559
|
membersCount = fullChat.participantsCount || void 0;
|
|
@@ -1495,7 +1591,7 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1495
1591
|
return null;
|
|
1496
1592
|
} catch (err) {
|
|
1497
1593
|
if (err instanceof PluginSDKError) throw err;
|
|
1498
|
-
|
|
1594
|
+
log7.error("telegram.getChatInfo() failed:", err);
|
|
1499
1595
|
return null;
|
|
1500
1596
|
}
|
|
1501
1597
|
},
|
|
@@ -1548,12 +1644,14 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1548
1644
|
}
|
|
1549
1645
|
if (result.users && result.users.length > 0) {
|
|
1550
1646
|
const user = result.users[0];
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1647
|
+
if (user instanceof Api.User) {
|
|
1648
|
+
return {
|
|
1649
|
+
id: Number(user.id),
|
|
1650
|
+
type: "user",
|
|
1651
|
+
username: user.username || void 0,
|
|
1652
|
+
title: user.firstName || void 0
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1557
1655
|
}
|
|
1558
1656
|
if (result.chats && result.chats.length > 0) {
|
|
1559
1657
|
const chat = result.chats[0];
|
|
@@ -1561,8 +1659,8 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1561
1659
|
return {
|
|
1562
1660
|
id: Number(chat.id),
|
|
1563
1661
|
type,
|
|
1564
|
-
username: chat.username || void 0,
|
|
1565
|
-
title: chat.title || void 0
|
|
1662
|
+
username: chat instanceof Api.Channel ? chat.username || void 0 : void 0,
|
|
1663
|
+
title: chat instanceof Api.Channel || chat instanceof Api.Chat ? chat.title || void 0 : void 0
|
|
1566
1664
|
};
|
|
1567
1665
|
}
|
|
1568
1666
|
return null;
|
|
@@ -1586,20 +1684,23 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1586
1684
|
filter: new Api.ChannelParticipantsRecent(),
|
|
1587
1685
|
offset: 0,
|
|
1588
1686
|
limit: limit ?? 100,
|
|
1589
|
-
hash: 0
|
|
1687
|
+
hash: toLong(0)
|
|
1590
1688
|
})
|
|
1591
1689
|
);
|
|
1592
1690
|
const resultData = result;
|
|
1593
|
-
return (resultData.users || []).map((user) =>
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1691
|
+
return (resultData.users || []).map((user) => {
|
|
1692
|
+
const u = user;
|
|
1693
|
+
return {
|
|
1694
|
+
id: Number(u.id),
|
|
1695
|
+
firstName: u.firstName || "",
|
|
1696
|
+
lastName: u.lastName || void 0,
|
|
1697
|
+
username: u.username || void 0,
|
|
1698
|
+
isBot: u.bot || false
|
|
1699
|
+
};
|
|
1700
|
+
});
|
|
1600
1701
|
} catch (err) {
|
|
1601
1702
|
if (err instanceof PluginSDKError) throw err;
|
|
1602
|
-
|
|
1703
|
+
log7.error("telegram.getParticipants() failed:", err);
|
|
1603
1704
|
return [];
|
|
1604
1705
|
}
|
|
1605
1706
|
},
|
|
@@ -1618,7 +1719,7 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1618
1719
|
const anonymous = opts?.isAnonymous ?? true;
|
|
1619
1720
|
const multipleChoice = opts?.multipleChoice ?? false;
|
|
1620
1721
|
const poll = new Api.Poll({
|
|
1621
|
-
id:
|
|
1722
|
+
id: randomLong(),
|
|
1622
1723
|
question: new Api.TextWithEntities({ text: question, entities: [] }),
|
|
1623
1724
|
answers: answers.map(
|
|
1624
1725
|
(opt, idx) => new Api.PollAnswer({
|
|
@@ -1634,10 +1735,10 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1634
1735
|
peer: chatId,
|
|
1635
1736
|
media: new Api.InputMediaPoll({ poll }),
|
|
1636
1737
|
message: "",
|
|
1637
|
-
randomId:
|
|
1738
|
+
randomId: randomLong()
|
|
1638
1739
|
})
|
|
1639
1740
|
);
|
|
1640
|
-
if (result
|
|
1741
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
1641
1742
|
for (const update of result.updates) {
|
|
1642
1743
|
if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
|
|
1643
1744
|
return update.message?.id ?? 0;
|
|
@@ -1671,7 +1772,7 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1671
1772
|
const client = getClient();
|
|
1672
1773
|
const { Api } = await import("telegram");
|
|
1673
1774
|
const poll = new Api.Poll({
|
|
1674
|
-
id:
|
|
1775
|
+
id: randomLong(),
|
|
1675
1776
|
question: new Api.TextWithEntities({ text: question, entities: [] }),
|
|
1676
1777
|
answers: answers.map(
|
|
1677
1778
|
(opt, idx) => new Api.PollAnswer({
|
|
@@ -1693,10 +1794,10 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1693
1794
|
solutionEntities: []
|
|
1694
1795
|
}),
|
|
1695
1796
|
message: "",
|
|
1696
|
-
randomId:
|
|
1797
|
+
randomId: randomLong()
|
|
1697
1798
|
})
|
|
1698
1799
|
);
|
|
1699
|
-
if (result
|
|
1800
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
1700
1801
|
for (const update of result.updates) {
|
|
1701
1802
|
if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
|
|
1702
1803
|
return update.message?.id ?? 0;
|
|
@@ -1962,12 +2063,32 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
1962
2063
|
const client = getClient();
|
|
1963
2064
|
const { Api, helpers } = await import("telegram");
|
|
1964
2065
|
const { CustomFile } = await import("telegram/client/uploads.js");
|
|
1965
|
-
const { readFileSync:
|
|
2066
|
+
const { readFileSync: readFileSync7, statSync: statSync2 } = await import("fs");
|
|
1966
2067
|
const { basename: basename2 } = await import("path");
|
|
1967
|
-
const
|
|
2068
|
+
const { resolve: resolve2, normalize: normalize2 } = await import("path");
|
|
2069
|
+
const { homedir: homedir3 } = await import("os");
|
|
2070
|
+
const { realpathSync } = await import("fs");
|
|
2071
|
+
const filePath = realpathSync(resolve2(normalize2(mediaPath)));
|
|
2072
|
+
const home = homedir3();
|
|
2073
|
+
const teletonWorkspace = `${home}/.teleton/workspace/`;
|
|
2074
|
+
const allowedPrefixes = [
|
|
2075
|
+
"/tmp/",
|
|
2076
|
+
`${home}/Downloads/`,
|
|
2077
|
+
`${home}/Pictures/`,
|
|
2078
|
+
`${home}/Videos/`,
|
|
2079
|
+
`${teletonWorkspace}uploads/`,
|
|
2080
|
+
`${teletonWorkspace}downloads/`,
|
|
2081
|
+
`${teletonWorkspace}memes/`
|
|
2082
|
+
];
|
|
2083
|
+
if (!allowedPrefixes.some((p) => filePath.startsWith(p))) {
|
|
2084
|
+
throw new PluginSDKError(
|
|
2085
|
+
"sendStory: media path must be within /tmp, Downloads, Pictures, or Videos",
|
|
2086
|
+
"OPERATION_FAILED"
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
1968
2089
|
const fileName = basename2(filePath);
|
|
1969
2090
|
const fileSize = statSync2(filePath).size;
|
|
1970
|
-
const fileBuffer =
|
|
2091
|
+
const fileBuffer = readFileSync7(filePath);
|
|
1971
2092
|
const isVideo = filePath.toLowerCase().match(/\.(mp4|mov|avi)$/);
|
|
1972
2093
|
const customFile = new CustomFile(fileName, fileSize, filePath, fileBuffer);
|
|
1973
2094
|
const uploadedFile = await client.uploadFile({
|
|
@@ -2004,7 +2125,8 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
2004
2125
|
randomId: helpers.generateRandomBigInt()
|
|
2005
2126
|
})
|
|
2006
2127
|
);
|
|
2007
|
-
|
|
2128
|
+
const storyUpdate = result instanceof Api.Updates ? result.updates.find((u) => u.className === "UpdateStory") : void 0;
|
|
2129
|
+
return storyUpdate?.story?.id ?? 0;
|
|
2008
2130
|
} catch (err) {
|
|
2009
2131
|
if (err instanceof PluginSDKError) throw err;
|
|
2010
2132
|
throw new PluginSDKError(
|
|
@@ -2017,7 +2139,7 @@ function createTelegramSocialSDK(bridge, log) {
|
|
|
2017
2139
|
}
|
|
2018
2140
|
|
|
2019
2141
|
// src/sdk/telegram.ts
|
|
2020
|
-
function createTelegramSDK(bridge,
|
|
2142
|
+
function createTelegramSDK(bridge, log7) {
|
|
2021
2143
|
function requireBridge() {
|
|
2022
2144
|
if (!bridge.isAvailable()) {
|
|
2023
2145
|
throw new PluginSDKError(
|
|
@@ -2073,17 +2195,17 @@ function createTelegramSDK(bridge, log) {
|
|
|
2073
2195
|
peer: chatId,
|
|
2074
2196
|
media: new Api.InputMediaDice({ emoticon }),
|
|
2075
2197
|
message: "",
|
|
2076
|
-
randomId:
|
|
2198
|
+
randomId: randomLong(),
|
|
2077
2199
|
replyTo: replyToId ? new Api.InputReplyToMessage({ replyToMsgId: replyToId }) : void 0
|
|
2078
2200
|
})
|
|
2079
2201
|
);
|
|
2080
2202
|
let value;
|
|
2081
2203
|
let messageId;
|
|
2082
|
-
if (result
|
|
2204
|
+
if (result instanceof Api.Updates || result instanceof Api.UpdatesCombined) {
|
|
2083
2205
|
for (const update of result.updates) {
|
|
2084
2206
|
if (update.className === "UpdateNewMessage" || update.className === "UpdateNewChannelMessage") {
|
|
2085
2207
|
const msg = update.message;
|
|
2086
|
-
if (msg
|
|
2208
|
+
if (msg instanceof Api.Message && msg.media?.className === "MessageMediaDice") {
|
|
2087
2209
|
value = msg.media.value;
|
|
2088
2210
|
messageId = msg.id;
|
|
2089
2211
|
break;
|
|
@@ -2127,7 +2249,7 @@ function createTelegramSDK(bridge, log) {
|
|
|
2127
2249
|
timestamp: m.timestamp
|
|
2128
2250
|
}));
|
|
2129
2251
|
} catch (err) {
|
|
2130
|
-
|
|
2252
|
+
log7.error("telegram.getMessages() failed:", err);
|
|
2131
2253
|
return [];
|
|
2132
2254
|
}
|
|
2133
2255
|
},
|
|
@@ -2149,6 +2271,7 @@ function createTelegramSDK(bridge, log) {
|
|
|
2149
2271
|
return bridge.isAvailable();
|
|
2150
2272
|
},
|
|
2151
2273
|
getRawClient() {
|
|
2274
|
+
log7.warn("getRawClient() called \u2014 this bypasses SDK sandbox guarantees");
|
|
2152
2275
|
if (!bridge.isAvailable()) return null;
|
|
2153
2276
|
try {
|
|
2154
2277
|
return bridge.getClient().getClient();
|
|
@@ -2157,13 +2280,13 @@ function createTelegramSDK(bridge, log) {
|
|
|
2157
2280
|
}
|
|
2158
2281
|
},
|
|
2159
2282
|
// Spread extended methods from sub-modules
|
|
2160
|
-
...createTelegramMessagesSDK(bridge,
|
|
2161
|
-
...createTelegramSocialSDK(bridge,
|
|
2283
|
+
...createTelegramMessagesSDK(bridge, log7),
|
|
2284
|
+
...createTelegramSocialSDK(bridge, log7)
|
|
2162
2285
|
};
|
|
2163
2286
|
}
|
|
2164
2287
|
|
|
2165
2288
|
// src/sdk/secrets.ts
|
|
2166
|
-
import { readFileSync as
|
|
2289
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
2167
2290
|
import { join as join3 } from "path";
|
|
2168
2291
|
var SECRETS_DIR = join3(TELETON_ROOT, "plugins", "data");
|
|
2169
2292
|
function getSecretsPath(pluginName) {
|
|
@@ -2172,8 +2295,8 @@ function getSecretsPath(pluginName) {
|
|
|
2172
2295
|
function readSecretsFile(pluginName) {
|
|
2173
2296
|
const filePath = getSecretsPath(pluginName);
|
|
2174
2297
|
try {
|
|
2175
|
-
if (!
|
|
2176
|
-
const raw =
|
|
2298
|
+
if (!existsSync5(filePath)) return {};
|
|
2299
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2177
2300
|
const parsed = JSON.parse(raw);
|
|
2178
2301
|
if (typeof parsed !== "object" || parsed === null) return {};
|
|
2179
2302
|
return parsed;
|
|
@@ -2182,40 +2305,40 @@ function readSecretsFile(pluginName) {
|
|
|
2182
2305
|
}
|
|
2183
2306
|
}
|
|
2184
2307
|
function writePluginSecret(pluginName, key, value) {
|
|
2185
|
-
mkdirSync3(SECRETS_DIR, { recursive: true });
|
|
2308
|
+
mkdirSync3(SECRETS_DIR, { recursive: true, mode: 448 });
|
|
2186
2309
|
const filePath = getSecretsPath(pluginName);
|
|
2187
2310
|
const existing = readSecretsFile(pluginName);
|
|
2188
2311
|
existing[key] = value;
|
|
2189
|
-
|
|
2312
|
+
writeFileSync2(filePath, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
2190
2313
|
}
|
|
2191
2314
|
function deletePluginSecret(pluginName, key) {
|
|
2192
2315
|
const existing = readSecretsFile(pluginName);
|
|
2193
2316
|
if (!(key in existing)) return false;
|
|
2194
2317
|
delete existing[key];
|
|
2195
2318
|
const filePath = getSecretsPath(pluginName);
|
|
2196
|
-
|
|
2319
|
+
writeFileSync2(filePath, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
2197
2320
|
return true;
|
|
2198
2321
|
}
|
|
2199
2322
|
function listPluginSecretKeys(pluginName) {
|
|
2200
2323
|
return Object.keys(readSecretsFile(pluginName));
|
|
2201
2324
|
}
|
|
2202
|
-
function createSecretsSDK(pluginName, pluginConfig,
|
|
2325
|
+
function createSecretsSDK(pluginName, pluginConfig, log7) {
|
|
2203
2326
|
const envPrefix = pluginName.replace(/-/g, "_").toUpperCase();
|
|
2204
2327
|
function get(key) {
|
|
2205
2328
|
const envKey = `${envPrefix}_${key.toUpperCase()}`;
|
|
2206
2329
|
const envValue = process.env[envKey];
|
|
2207
2330
|
if (envValue) {
|
|
2208
|
-
|
|
2331
|
+
log7.debug(`Secret "${key}" resolved from env var ${envKey}`);
|
|
2209
2332
|
return envValue;
|
|
2210
2333
|
}
|
|
2211
2334
|
const stored = readSecretsFile(pluginName);
|
|
2212
2335
|
if (key in stored && stored[key]) {
|
|
2213
|
-
|
|
2336
|
+
log7.debug(`Secret "${key}" resolved from secrets store`);
|
|
2214
2337
|
return stored[key];
|
|
2215
2338
|
}
|
|
2216
2339
|
const configValue = pluginConfig[key];
|
|
2217
2340
|
if (configValue !== void 0 && configValue !== null) {
|
|
2218
|
-
|
|
2341
|
+
log7.debug(`Secret "${key}" resolved from pluginConfig`);
|
|
2219
2342
|
return String(configValue);
|
|
2220
2343
|
}
|
|
2221
2344
|
return void 0;
|
|
@@ -2307,38 +2430,61 @@ function createStorageSDK(db) {
|
|
|
2307
2430
|
}
|
|
2308
2431
|
|
|
2309
2432
|
// src/sdk/index.ts
|
|
2433
|
+
var sdkLog = createLogger("SDK");
|
|
2434
|
+
var BLOCKED_SQL_RE = /\b(ATTACH|DETACH)\s+DATABASE\b/i;
|
|
2435
|
+
function createSafeDb(db) {
|
|
2436
|
+
return new Proxy(db, {
|
|
2437
|
+
get(target, prop, receiver) {
|
|
2438
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2439
|
+
if (prop === "exec") {
|
|
2440
|
+
return (sql) => {
|
|
2441
|
+
if (BLOCKED_SQL_RE.test(sql)) {
|
|
2442
|
+
throw new Error("ATTACH/DETACH DATABASE is not allowed in plugin context");
|
|
2443
|
+
}
|
|
2444
|
+
return target.exec(sql);
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
if (prop === "prepare") {
|
|
2448
|
+
return (sql) => {
|
|
2449
|
+
if (BLOCKED_SQL_RE.test(sql)) {
|
|
2450
|
+
throw new Error("ATTACH/DETACH DATABASE is not allowed in plugin context");
|
|
2451
|
+
}
|
|
2452
|
+
return target.prepare(sql);
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2310
2459
|
function createPluginSDK(deps, opts) {
|
|
2311
|
-
const
|
|
2312
|
-
const
|
|
2313
|
-
const
|
|
2314
|
-
const
|
|
2315
|
-
const
|
|
2316
|
-
const
|
|
2460
|
+
const log7 = createLogger2(opts.pluginName);
|
|
2461
|
+
const safeDb = opts.db ? createSafeDb(opts.db) : null;
|
|
2462
|
+
const ton = Object.freeze(createTonSDK(log7, safeDb));
|
|
2463
|
+
const telegram = Object.freeze(createTelegramSDK(deps.bridge, log7));
|
|
2464
|
+
const secrets = Object.freeze(createSecretsSDK(opts.pluginName, opts.pluginConfig, log7));
|
|
2465
|
+
const storage = safeDb ? Object.freeze(createStorageSDK(safeDb)) : null;
|
|
2466
|
+
const frozenLog = Object.freeze(log7);
|
|
2317
2467
|
const frozenConfig = Object.freeze(opts.sanitizedConfig);
|
|
2318
|
-
const frozenPluginConfig = Object.freeze(opts.pluginConfig);
|
|
2468
|
+
const frozenPluginConfig = Object.freeze(JSON.parse(JSON.stringify(opts.pluginConfig ?? {})));
|
|
2319
2469
|
return Object.freeze({
|
|
2320
2470
|
version: SDK_VERSION,
|
|
2321
2471
|
ton,
|
|
2322
2472
|
telegram,
|
|
2323
2473
|
secrets,
|
|
2324
2474
|
storage,
|
|
2325
|
-
db:
|
|
2475
|
+
db: safeDb,
|
|
2326
2476
|
config: frozenConfig,
|
|
2327
2477
|
pluginConfig: frozenPluginConfig,
|
|
2328
2478
|
log: frozenLog
|
|
2329
2479
|
});
|
|
2330
2480
|
}
|
|
2331
|
-
function
|
|
2332
|
-
const
|
|
2481
|
+
function createLogger2(pluginName) {
|
|
2482
|
+
const pinoChild = createLogger(`plugin:${pluginName}`);
|
|
2333
2483
|
return {
|
|
2334
|
-
info: (...args) =>
|
|
2335
|
-
warn: (...args) =>
|
|
2336
|
-
error: (...args) =>
|
|
2337
|
-
debug: (...args) =>
|
|
2338
|
-
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
2339
|
-
console.log(`\u{1F50D} ${prefix}`, ...args);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2484
|
+
info: (...args) => pinoChild.info(args.map(String).join(" ")),
|
|
2485
|
+
warn: (...args) => pinoChild.warn(args.map(String).join(" ")),
|
|
2486
|
+
error: (...args) => pinoChild.error(args.map(String).join(" ")),
|
|
2487
|
+
debug: (...args) => pinoChild.debug(args.map(String).join(" "))
|
|
2342
2488
|
};
|
|
2343
2489
|
}
|
|
2344
2490
|
function parseSemver(v) {
|
|
@@ -2358,22 +2504,22 @@ function semverGte(a, b) {
|
|
|
2358
2504
|
function semverSatisfies(current, range) {
|
|
2359
2505
|
const cur = parseSemver(current);
|
|
2360
2506
|
if (!cur) {
|
|
2361
|
-
|
|
2362
|
-
return
|
|
2507
|
+
sdkLog.warn(`[SDK] Could not parse current version "${current}", rejecting`);
|
|
2508
|
+
return false;
|
|
2363
2509
|
}
|
|
2364
2510
|
if (range.startsWith(">=")) {
|
|
2365
2511
|
const req2 = parseSemver(range.slice(2));
|
|
2366
2512
|
if (!req2) {
|
|
2367
|
-
|
|
2368
|
-
return
|
|
2513
|
+
sdkLog.warn(`[SDK] Malformed sdkVersion range "${range}", rejecting`);
|
|
2514
|
+
return false;
|
|
2369
2515
|
}
|
|
2370
2516
|
return semverGte(cur, req2);
|
|
2371
2517
|
}
|
|
2372
2518
|
if (range.startsWith("^")) {
|
|
2373
2519
|
const req2 = parseSemver(range.slice(1));
|
|
2374
2520
|
if (!req2) {
|
|
2375
|
-
|
|
2376
|
-
return
|
|
2521
|
+
sdkLog.warn(`[SDK] Malformed sdkVersion range "${range}", rejecting`);
|
|
2522
|
+
return false;
|
|
2377
2523
|
}
|
|
2378
2524
|
if (req2.major === 0) {
|
|
2379
2525
|
return cur.major === 0 && cur.minor === req2.minor && semverGte(cur, req2);
|
|
@@ -2382,14 +2528,15 @@ function semverSatisfies(current, range) {
|
|
|
2382
2528
|
}
|
|
2383
2529
|
const req = parseSemver(range);
|
|
2384
2530
|
if (!req) {
|
|
2385
|
-
|
|
2386
|
-
return
|
|
2531
|
+
sdkLog.warn(`[SDK] Malformed sdkVersion "${range}", rejecting`);
|
|
2532
|
+
return false;
|
|
2387
2533
|
}
|
|
2388
2534
|
return cur.major === req.major && cur.minor === req.minor && cur.patch === req.patch;
|
|
2389
2535
|
}
|
|
2390
2536
|
|
|
2391
2537
|
// src/agent/tools/plugin-loader.ts
|
|
2392
2538
|
var execFileAsync = promisify(execFile);
|
|
2539
|
+
var log6 = createLogger("PluginLoader");
|
|
2393
2540
|
var PLUGIN_DATA_DIR = join4(TELETON_ROOT, "plugins", "data");
|
|
2394
2541
|
function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
2395
2542
|
let manifest = null;
|
|
@@ -2397,17 +2544,16 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2397
2544
|
try {
|
|
2398
2545
|
manifest = validateManifest(raw.manifest);
|
|
2399
2546
|
} catch (err) {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
err instanceof Error ? err.message : err
|
|
2547
|
+
log6.warn(
|
|
2548
|
+
`[${entryName}] invalid manifest, ignoring: ${err instanceof Error ? err.message : err}`
|
|
2403
2549
|
);
|
|
2404
2550
|
}
|
|
2405
2551
|
}
|
|
2406
2552
|
if (!manifest) {
|
|
2407
2553
|
const manifestPath = join4(WORKSPACE_PATHS.PLUGINS_DIR, entryName, "manifest.json");
|
|
2408
2554
|
try {
|
|
2409
|
-
if (
|
|
2410
|
-
const diskManifest = JSON.parse(
|
|
2555
|
+
if (existsSync6(manifestPath)) {
|
|
2556
|
+
const diskManifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
2411
2557
|
if (diskManifest && typeof diskManifest.version === "string") {
|
|
2412
2558
|
manifest = {
|
|
2413
2559
|
name: entryName,
|
|
@@ -2439,12 +2585,13 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2439
2585
|
const pluginConfigKey = pluginName.replace(/-/g, "_");
|
|
2440
2586
|
const rawPluginConfig = config.plugins?.[pluginConfigKey] ?? {};
|
|
2441
2587
|
const pluginConfig = { ...manifest?.defaultConfig, ...rawPluginConfig };
|
|
2442
|
-
const
|
|
2588
|
+
const pluginLog = createLogger(`Plugin:${pluginName}`);
|
|
2589
|
+
const logFn = (...args) => pluginLog.info(args.map(String).join(" "));
|
|
2443
2590
|
if (manifest?.secrets) {
|
|
2444
2591
|
const dummyLogger = {
|
|
2445
|
-
info:
|
|
2446
|
-
warn: (...a) =>
|
|
2447
|
-
error: (...a) =>
|
|
2592
|
+
info: (...a) => pluginLog.info(a.map(String).join(" ")),
|
|
2593
|
+
warn: (...a) => pluginLog.warn(a.map(String).join(" ")),
|
|
2594
|
+
error: (...a) => pluginLog.error(a.map(String).join(" ")),
|
|
2448
2595
|
debug: () => {
|
|
2449
2596
|
}
|
|
2450
2597
|
};
|
|
@@ -2458,8 +2605,8 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2458
2605
|
}
|
|
2459
2606
|
}
|
|
2460
2607
|
if (missing.length > 0) {
|
|
2461
|
-
|
|
2462
|
-
|
|
2608
|
+
pluginLog.warn(
|
|
2609
|
+
`Missing required secrets:
|
|
2463
2610
|
` + missing.map((m) => ` \u2022 ${m}`).join("\n") + `
|
|
2464
2611
|
Set via: /plugin set ${pluginName} <key> <value>`
|
|
2465
2612
|
);
|
|
@@ -2492,10 +2639,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2492
2639
|
}
|
|
2493
2640
|
}
|
|
2494
2641
|
} catch (err) {
|
|
2495
|
-
|
|
2496
|
-
`\u274C [${pluginName}] migrate() failed:`,
|
|
2497
|
-
err instanceof Error ? err.message : err
|
|
2498
|
-
);
|
|
2642
|
+
pluginLog.error(`migrate() failed: ${err instanceof Error ? err.message : err}`);
|
|
2499
2643
|
if (pluginDb) {
|
|
2500
2644
|
try {
|
|
2501
2645
|
pluginDb.close();
|
|
@@ -2546,10 +2690,7 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2546
2690
|
};
|
|
2547
2691
|
});
|
|
2548
2692
|
} catch (err) {
|
|
2549
|
-
|
|
2550
|
-
`\u274C [${pluginName}] tools() failed:`,
|
|
2551
|
-
err instanceof Error ? err.message : err
|
|
2552
|
-
);
|
|
2693
|
+
pluginLog.error(`tools() failed: ${err instanceof Error ? err.message : err}`);
|
|
2553
2694
|
return [];
|
|
2554
2695
|
}
|
|
2555
2696
|
},
|
|
@@ -2561,24 +2702,18 @@ function adaptPlugin(raw, entryName, config, loadedModuleNames, sdkDeps) {
|
|
|
2561
2702
|
db: pluginDb ?? null,
|
|
2562
2703
|
config: sanitizedConfig,
|
|
2563
2704
|
pluginConfig,
|
|
2564
|
-
log
|
|
2705
|
+
log: logFn
|
|
2565
2706
|
};
|
|
2566
2707
|
await raw.start(enhancedContext);
|
|
2567
2708
|
} catch (err) {
|
|
2568
|
-
|
|
2569
|
-
`\u274C [${pluginName}] start() failed:`,
|
|
2570
|
-
err instanceof Error ? err.message : err
|
|
2571
|
-
);
|
|
2709
|
+
pluginLog.error(`start() failed: ${err instanceof Error ? err.message : err}`);
|
|
2572
2710
|
}
|
|
2573
2711
|
},
|
|
2574
2712
|
async stop() {
|
|
2575
2713
|
try {
|
|
2576
2714
|
await raw.stop?.();
|
|
2577
2715
|
} catch (err) {
|
|
2578
|
-
|
|
2579
|
-
`\u274C [${pluginName}] stop() failed:`,
|
|
2580
|
-
err instanceof Error ? err.message : err
|
|
2581
|
-
);
|
|
2716
|
+
pluginLog.error(`stop() failed: ${err instanceof Error ? err.message : err}`);
|
|
2582
2717
|
} finally {
|
|
2583
2718
|
if (pluginDb) {
|
|
2584
2719
|
try {
|
|
@@ -2596,35 +2731,35 @@ async function ensurePluginDeps(pluginDir, pluginEntry) {
|
|
|
2596
2731
|
const pkgJson = join4(pluginDir, "package.json");
|
|
2597
2732
|
const lockfile = join4(pluginDir, "package-lock.json");
|
|
2598
2733
|
const nodeModules = join4(pluginDir, "node_modules");
|
|
2599
|
-
if (!
|
|
2600
|
-
if (!
|
|
2601
|
-
|
|
2602
|
-
|
|
2734
|
+
if (!existsSync6(pkgJson)) return;
|
|
2735
|
+
if (!existsSync6(lockfile)) {
|
|
2736
|
+
log6.warn(
|
|
2737
|
+
`[${pluginEntry}] package.json without package-lock.json \u2014 skipping (lockfile required)`
|
|
2603
2738
|
);
|
|
2604
2739
|
return;
|
|
2605
2740
|
}
|
|
2606
|
-
if (
|
|
2741
|
+
if (existsSync6(nodeModules)) {
|
|
2607
2742
|
const marker = join4(nodeModules, ".package-lock.json");
|
|
2608
|
-
if (
|
|
2743
|
+
if (existsSync6(marker) && statSync(marker).mtimeMs >= statSync(lockfile).mtimeMs) return;
|
|
2609
2744
|
}
|
|
2610
|
-
|
|
2745
|
+
log6.info(`[${pluginEntry}] Installing dependencies...`);
|
|
2611
2746
|
try {
|
|
2612
2747
|
await execFileAsync("npm", ["ci", "--ignore-scripts", "--no-audit", "--no-fund"], {
|
|
2613
2748
|
cwd: pluginDir,
|
|
2614
2749
|
timeout: 6e4,
|
|
2615
2750
|
env: { ...process.env, NODE_ENV: "production" }
|
|
2616
2751
|
});
|
|
2617
|
-
|
|
2752
|
+
log6.info(`[${pluginEntry}] Dependencies installed`);
|
|
2618
2753
|
} catch (err) {
|
|
2619
|
-
|
|
2754
|
+
log6.error(`[${pluginEntry}] Failed to install deps: ${String(err).slice(0, 300)}`);
|
|
2620
2755
|
}
|
|
2621
2756
|
}
|
|
2622
2757
|
async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
2623
2758
|
const pluginsDir = WORKSPACE_PATHS.PLUGINS_DIR;
|
|
2624
|
-
if (!
|
|
2759
|
+
if (!existsSync6(pluginsDir)) {
|
|
2625
2760
|
return [];
|
|
2626
2761
|
}
|
|
2627
|
-
const entries =
|
|
2762
|
+
const entries = readdirSync2(pluginsDir);
|
|
2628
2763
|
const modules = [];
|
|
2629
2764
|
const loadedNames = /* @__PURE__ */ new Set();
|
|
2630
2765
|
const pluginPaths = [];
|
|
@@ -2638,7 +2773,7 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
|
2638
2773
|
modulePath = entryPath;
|
|
2639
2774
|
} else if (stat.isDirectory()) {
|
|
2640
2775
|
const indexPath = join4(entryPath, "index.js");
|
|
2641
|
-
if (
|
|
2776
|
+
if (existsSync6(indexPath)) {
|
|
2642
2777
|
modulePath = indexPath;
|
|
2643
2778
|
}
|
|
2644
2779
|
}
|
|
@@ -2661,156 +2796,402 @@ async function loadEnhancedPlugins(config, loadedModuleNames, sdkDeps) {
|
|
|
2661
2796
|
);
|
|
2662
2797
|
for (const result of loadResults) {
|
|
2663
2798
|
if (result.status === "rejected") {
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
result.reason instanceof Error ? result.reason.message : result.reason
|
|
2799
|
+
log6.error(
|
|
2800
|
+
`Plugin failed to load: ${result.reason instanceof Error ? result.reason.message : result.reason}`
|
|
2667
2801
|
);
|
|
2668
2802
|
continue;
|
|
2669
2803
|
}
|
|
2670
2804
|
const { entry, mod } = result.value;
|
|
2671
2805
|
try {
|
|
2672
2806
|
if (!mod.tools || typeof mod.tools !== "function" && !Array.isArray(mod.tools)) {
|
|
2673
|
-
|
|
2807
|
+
log6.warn(`Plugin "${entry}": no 'tools' array or function exported, skipping`);
|
|
2674
2808
|
continue;
|
|
2675
2809
|
}
|
|
2676
2810
|
const adapted = adaptPlugin(mod, entry, config, loadedModuleNames, sdkDeps);
|
|
2677
2811
|
if (loadedNames.has(adapted.name)) {
|
|
2678
|
-
|
|
2679
|
-
`\u26A0\uFE0F Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`
|
|
2680
|
-
);
|
|
2812
|
+
log6.warn(`Plugin "${adapted.name}" already loaded, skipping duplicate from "${entry}"`);
|
|
2681
2813
|
continue;
|
|
2682
2814
|
}
|
|
2683
2815
|
loadedNames.add(adapted.name);
|
|
2684
2816
|
modules.push(adapted);
|
|
2685
2817
|
} catch (err) {
|
|
2686
|
-
|
|
2687
|
-
`\u274C Plugin "${entry}" failed to adapt:`,
|
|
2688
|
-
err instanceof Error ? err.message : err
|
|
2689
|
-
);
|
|
2818
|
+
log6.error(`Plugin "${entry}" failed to adapt: ${err instanceof Error ? err.message : err}`);
|
|
2690
2819
|
}
|
|
2691
2820
|
}
|
|
2692
2821
|
return modules;
|
|
2693
2822
|
}
|
|
2694
2823
|
|
|
2695
|
-
// src/
|
|
2696
|
-
import {
|
|
2697
|
-
import {
|
|
2698
|
-
|
|
2699
|
-
var
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2824
|
+
// src/config/configurable-keys.ts
|
|
2825
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "fs";
|
|
2826
|
+
import { parse as parse2, stringify as stringify2 } from "yaml";
|
|
2827
|
+
var noValidation = () => void 0;
|
|
2828
|
+
var identity = (v) => v;
|
|
2829
|
+
var nonEmpty = (v) => v.length > 0 ? void 0 : "Must not be empty";
|
|
2830
|
+
function numberInRange(min, max) {
|
|
2831
|
+
return (v) => {
|
|
2832
|
+
const n = Number(v);
|
|
2833
|
+
if (isNaN(n)) return "Must be a number";
|
|
2834
|
+
if (n < min || n > max) return `Must be between ${min} and ${max}`;
|
|
2835
|
+
return void 0;
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
function enumValidator(options) {
|
|
2839
|
+
return (v) => options.includes(v) ? void 0 : `Must be one of: ${options.join(", ")}`;
|
|
2840
|
+
}
|
|
2841
|
+
var CONFIGURABLE_KEYS = {
|
|
2842
|
+
// ─── API Keys ──────────────────────────────────────────────────────
|
|
2843
|
+
"agent.api_key": {
|
|
2844
|
+
type: "string",
|
|
2845
|
+
category: "API Keys",
|
|
2846
|
+
description: "LLM provider API key",
|
|
2847
|
+
sensitive: true,
|
|
2848
|
+
validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
|
|
2849
|
+
mask: (v) => v.slice(0, 8) + "****",
|
|
2850
|
+
parse: identity
|
|
2851
|
+
},
|
|
2852
|
+
tavily_api_key: {
|
|
2853
|
+
type: "string",
|
|
2854
|
+
category: "API Keys",
|
|
2855
|
+
description: "Tavily API key for web search",
|
|
2856
|
+
sensitive: true,
|
|
2857
|
+
validate: (v) => v.startsWith("tvly-") ? void 0 : "Must start with 'tvly-'",
|
|
2858
|
+
mask: (v) => v.slice(0, 9) + "****",
|
|
2859
|
+
parse: identity
|
|
2860
|
+
},
|
|
2861
|
+
tonapi_key: {
|
|
2862
|
+
type: "string",
|
|
2863
|
+
category: "API Keys",
|
|
2864
|
+
description: "TonAPI key for higher rate limits",
|
|
2865
|
+
sensitive: true,
|
|
2866
|
+
validate: (v) => v.length >= 10 ? void 0 : "Must be at least 10 characters",
|
|
2867
|
+
mask: (v) => v.slice(0, 10) + "****",
|
|
2868
|
+
parse: identity
|
|
2869
|
+
},
|
|
2870
|
+
"telegram.bot_token": {
|
|
2871
|
+
type: "string",
|
|
2872
|
+
category: "API Keys",
|
|
2873
|
+
description: "Bot token from @BotFather",
|
|
2874
|
+
sensitive: true,
|
|
2875
|
+
validate: (v) => v.includes(":") ? void 0 : "Must contain ':' (e.g., 123456:ABC...)",
|
|
2876
|
+
mask: (v) => v.split(":")[0] + ":****",
|
|
2877
|
+
parse: identity
|
|
2878
|
+
},
|
|
2879
|
+
// ─── Agent ─────────────────────────────────────────────────────────
|
|
2880
|
+
"agent.provider": {
|
|
2881
|
+
type: "enum",
|
|
2882
|
+
category: "Agent",
|
|
2883
|
+
description: "LLM provider",
|
|
2884
|
+
sensitive: false,
|
|
2885
|
+
options: [
|
|
2886
|
+
"anthropic",
|
|
2887
|
+
"openai",
|
|
2888
|
+
"google",
|
|
2889
|
+
"xai",
|
|
2890
|
+
"groq",
|
|
2891
|
+
"openrouter",
|
|
2892
|
+
"moonshot",
|
|
2893
|
+
"mistral",
|
|
2894
|
+
"cocoon",
|
|
2895
|
+
"local"
|
|
2896
|
+
],
|
|
2897
|
+
validate: enumValidator([
|
|
2898
|
+
"anthropic",
|
|
2899
|
+
"openai",
|
|
2900
|
+
"google",
|
|
2901
|
+
"xai",
|
|
2902
|
+
"groq",
|
|
2903
|
+
"openrouter",
|
|
2904
|
+
"moonshot",
|
|
2905
|
+
"mistral",
|
|
2906
|
+
"cocoon",
|
|
2907
|
+
"local"
|
|
2908
|
+
]),
|
|
2909
|
+
mask: identity,
|
|
2910
|
+
parse: identity
|
|
2911
|
+
},
|
|
2912
|
+
"agent.model": {
|
|
2913
|
+
type: "string",
|
|
2914
|
+
category: "Agent",
|
|
2915
|
+
description: "Main LLM model ID",
|
|
2916
|
+
sensitive: false,
|
|
2917
|
+
validate: nonEmpty,
|
|
2918
|
+
mask: identity,
|
|
2919
|
+
parse: identity
|
|
2920
|
+
},
|
|
2921
|
+
"agent.utility_model": {
|
|
2922
|
+
type: "string",
|
|
2923
|
+
category: "Agent",
|
|
2924
|
+
description: "Cheap model for summarization (auto-detected if empty)",
|
|
2925
|
+
sensitive: false,
|
|
2926
|
+
validate: noValidation,
|
|
2927
|
+
mask: identity,
|
|
2928
|
+
parse: identity
|
|
2929
|
+
},
|
|
2930
|
+
"agent.temperature": {
|
|
2931
|
+
type: "number",
|
|
2932
|
+
category: "Agent",
|
|
2933
|
+
description: "Response creativity (0.0 = deterministic, 2.0 = max)",
|
|
2934
|
+
sensitive: false,
|
|
2935
|
+
validate: numberInRange(0, 2),
|
|
2936
|
+
mask: identity,
|
|
2937
|
+
parse: (v) => Number(v)
|
|
2938
|
+
},
|
|
2939
|
+
"agent.max_tokens": {
|
|
2940
|
+
type: "number",
|
|
2941
|
+
category: "Agent",
|
|
2942
|
+
description: "Maximum response length in tokens",
|
|
2943
|
+
sensitive: false,
|
|
2944
|
+
validate: numberInRange(256, 128e3),
|
|
2945
|
+
mask: identity,
|
|
2946
|
+
parse: (v) => Number(v)
|
|
2947
|
+
},
|
|
2948
|
+
"agent.max_agentic_iterations": {
|
|
2949
|
+
type: "number",
|
|
2950
|
+
category: "Agent",
|
|
2951
|
+
description: "Max tool-call loop iterations per message",
|
|
2952
|
+
sensitive: false,
|
|
2953
|
+
validate: numberInRange(1, 20),
|
|
2954
|
+
mask: identity,
|
|
2955
|
+
parse: (v) => Number(v)
|
|
2956
|
+
},
|
|
2957
|
+
// ─── Session ───────────────────────────────────────────────────
|
|
2958
|
+
"agent.session_reset_policy.daily_reset_enabled": {
|
|
2959
|
+
type: "boolean",
|
|
2960
|
+
category: "Session",
|
|
2961
|
+
description: "Enable daily session reset at specified hour",
|
|
2962
|
+
sensitive: false,
|
|
2963
|
+
validate: enumValidator(["true", "false"]),
|
|
2964
|
+
mask: identity,
|
|
2965
|
+
parse: (v) => v === "true"
|
|
2966
|
+
},
|
|
2967
|
+
"agent.session_reset_policy.daily_reset_hour": {
|
|
2968
|
+
type: "number",
|
|
2969
|
+
category: "Session",
|
|
2970
|
+
description: "Hour (0-23 UTC) for daily session reset",
|
|
2971
|
+
sensitive: false,
|
|
2972
|
+
validate: numberInRange(0, 23),
|
|
2973
|
+
mask: identity,
|
|
2974
|
+
parse: (v) => Number(v)
|
|
2975
|
+
},
|
|
2976
|
+
"agent.session_reset_policy.idle_expiry_enabled": {
|
|
2977
|
+
type: "boolean",
|
|
2978
|
+
category: "Session",
|
|
2979
|
+
description: "Enable automatic session expiry after idle period",
|
|
2980
|
+
sensitive: false,
|
|
2981
|
+
validate: enumValidator(["true", "false"]),
|
|
2982
|
+
mask: identity,
|
|
2983
|
+
parse: (v) => v === "true"
|
|
2984
|
+
},
|
|
2985
|
+
"agent.session_reset_policy.idle_expiry_minutes": {
|
|
2986
|
+
type: "number",
|
|
2987
|
+
category: "Session",
|
|
2988
|
+
description: "Idle minutes before session expires (minimum 1)",
|
|
2989
|
+
sensitive: false,
|
|
2990
|
+
validate: numberInRange(1, Number.MAX_SAFE_INTEGER),
|
|
2991
|
+
mask: identity,
|
|
2992
|
+
parse: (v) => Number(v)
|
|
2993
|
+
},
|
|
2994
|
+
// ─── Telegram ──────────────────────────────────────────────────────
|
|
2995
|
+
"telegram.bot_username": {
|
|
2996
|
+
type: "string",
|
|
2997
|
+
category: "Telegram",
|
|
2998
|
+
description: "Bot username without @",
|
|
2999
|
+
sensitive: false,
|
|
3000
|
+
validate: (v) => v.length >= 3 ? void 0 : "Must be at least 3 characters",
|
|
3001
|
+
mask: identity,
|
|
3002
|
+
parse: identity
|
|
3003
|
+
},
|
|
3004
|
+
"telegram.dm_policy": {
|
|
3005
|
+
type: "enum",
|
|
3006
|
+
category: "Telegram",
|
|
3007
|
+
description: "DM access policy",
|
|
3008
|
+
sensitive: false,
|
|
3009
|
+
options: ["pairing", "allowlist", "open", "disabled"],
|
|
3010
|
+
validate: enumValidator(["pairing", "allowlist", "open", "disabled"]),
|
|
3011
|
+
mask: identity,
|
|
3012
|
+
parse: identity
|
|
3013
|
+
},
|
|
3014
|
+
"telegram.group_policy": {
|
|
3015
|
+
type: "enum",
|
|
3016
|
+
category: "Telegram",
|
|
3017
|
+
description: "Group access policy",
|
|
3018
|
+
sensitive: false,
|
|
3019
|
+
options: ["open", "allowlist", "disabled"],
|
|
3020
|
+
validate: enumValidator(["open", "allowlist", "disabled"]),
|
|
3021
|
+
mask: identity,
|
|
3022
|
+
parse: identity
|
|
3023
|
+
},
|
|
3024
|
+
"telegram.require_mention": {
|
|
3025
|
+
type: "boolean",
|
|
3026
|
+
category: "Telegram",
|
|
3027
|
+
description: "Require @mention in groups to respond",
|
|
3028
|
+
sensitive: false,
|
|
3029
|
+
validate: enumValidator(["true", "false"]),
|
|
3030
|
+
mask: identity,
|
|
3031
|
+
parse: (v) => v === "true"
|
|
3032
|
+
},
|
|
3033
|
+
"telegram.owner_name": {
|
|
3034
|
+
type: "string",
|
|
3035
|
+
category: "Telegram",
|
|
3036
|
+
description: "Owner's first name (used in system prompt)",
|
|
3037
|
+
sensitive: false,
|
|
3038
|
+
validate: noValidation,
|
|
3039
|
+
mask: identity,
|
|
3040
|
+
parse: identity
|
|
3041
|
+
},
|
|
3042
|
+
"telegram.owner_username": {
|
|
3043
|
+
type: "string",
|
|
3044
|
+
category: "Telegram",
|
|
3045
|
+
description: "Owner's Telegram username (without @)",
|
|
3046
|
+
sensitive: false,
|
|
3047
|
+
validate: noValidation,
|
|
3048
|
+
mask: identity,
|
|
3049
|
+
parse: identity
|
|
3050
|
+
},
|
|
3051
|
+
"telegram.debounce_ms": {
|
|
3052
|
+
type: "number",
|
|
3053
|
+
category: "Telegram",
|
|
3054
|
+
description: "Group message debounce delay in ms (0 = disabled)",
|
|
3055
|
+
sensitive: false,
|
|
3056
|
+
validate: numberInRange(0, 1e4),
|
|
3057
|
+
mask: identity,
|
|
3058
|
+
parse: (v) => Number(v)
|
|
3059
|
+
},
|
|
3060
|
+
"telegram.agent_channel": {
|
|
3061
|
+
type: "string",
|
|
3062
|
+
category: "Telegram",
|
|
3063
|
+
description: "Channel username for auto-publishing",
|
|
3064
|
+
sensitive: false,
|
|
3065
|
+
validate: noValidation,
|
|
3066
|
+
mask: identity,
|
|
3067
|
+
parse: identity
|
|
3068
|
+
},
|
|
3069
|
+
"telegram.typing_simulation": {
|
|
3070
|
+
type: "boolean",
|
|
3071
|
+
category: "Telegram",
|
|
3072
|
+
description: "Simulate typing indicator before sending replies",
|
|
3073
|
+
sensitive: false,
|
|
3074
|
+
validate: enumValidator(["true", "false"]),
|
|
3075
|
+
mask: identity,
|
|
3076
|
+
parse: (v) => v === "true"
|
|
3077
|
+
},
|
|
3078
|
+
// ─── Embedding ─────────────────────────────────────────────────────
|
|
3079
|
+
"embedding.provider": {
|
|
3080
|
+
type: "enum",
|
|
3081
|
+
category: "Embedding",
|
|
3082
|
+
description: "Embedding provider for RAG",
|
|
3083
|
+
sensitive: false,
|
|
3084
|
+
options: ["local", "anthropic", "none"],
|
|
3085
|
+
validate: enumValidator(["local", "anthropic", "none"]),
|
|
3086
|
+
mask: identity,
|
|
3087
|
+
parse: identity
|
|
3088
|
+
},
|
|
3089
|
+
// ─── WebUI ─────────────────────────────────────────────────────────
|
|
3090
|
+
"webui.port": {
|
|
3091
|
+
type: "number",
|
|
3092
|
+
category: "WebUI",
|
|
3093
|
+
description: "HTTP server port (requires restart)",
|
|
3094
|
+
sensitive: false,
|
|
3095
|
+
validate: numberInRange(1024, 65535),
|
|
3096
|
+
mask: identity,
|
|
3097
|
+
parse: (v) => Number(v)
|
|
3098
|
+
},
|
|
3099
|
+
"webui.log_requests": {
|
|
3100
|
+
type: "boolean",
|
|
3101
|
+
category: "WebUI",
|
|
3102
|
+
description: "Log all HTTP requests to console",
|
|
3103
|
+
sensitive: false,
|
|
3104
|
+
validate: enumValidator(["true", "false"]),
|
|
3105
|
+
mask: identity,
|
|
3106
|
+
parse: (v) => v === "true"
|
|
3107
|
+
},
|
|
3108
|
+
// ─── Deals ─────────────────────────────────────────────────────────
|
|
3109
|
+
"deals.enabled": {
|
|
3110
|
+
type: "boolean",
|
|
3111
|
+
category: "Deals",
|
|
3112
|
+
description: "Enable the deals/escrow module",
|
|
3113
|
+
sensitive: false,
|
|
3114
|
+
validate: enumValidator(["true", "false"]),
|
|
3115
|
+
mask: identity,
|
|
3116
|
+
parse: (v) => v === "true"
|
|
3117
|
+
},
|
|
3118
|
+
// ─── Developer ─────────────────────────────────────────────────────
|
|
3119
|
+
"dev.hot_reload": {
|
|
3120
|
+
type: "boolean",
|
|
3121
|
+
category: "Developer",
|
|
3122
|
+
description: "Watch ~/.teleton/plugins/ for live changes",
|
|
3123
|
+
sensitive: false,
|
|
3124
|
+
validate: enumValidator(["true", "false"]),
|
|
3125
|
+
mask: identity,
|
|
3126
|
+
parse: (v) => v === "true"
|
|
2704
3127
|
}
|
|
2705
3128
|
};
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
const maxIterations = 10;
|
|
2711
|
-
while (decoded !== prev && iterations < maxIterations) {
|
|
2712
|
-
prev = decoded;
|
|
2713
|
-
try {
|
|
2714
|
-
decoded = decodeURIComponent(decoded);
|
|
2715
|
-
} catch {
|
|
2716
|
-
break;
|
|
2717
|
-
}
|
|
2718
|
-
iterations++;
|
|
3129
|
+
var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3130
|
+
function assertSafePath(parts) {
|
|
3131
|
+
if (parts.some((p) => FORBIDDEN_SEGMENTS.has(p))) {
|
|
3132
|
+
throw new Error("Invalid config path: forbidden segment");
|
|
2719
3133
|
}
|
|
2720
|
-
return decoded;
|
|
2721
3134
|
}
|
|
2722
|
-
function
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
throw new WorkspaceSecurityError(
|
|
2740
|
-
`Access denied: Path '${inputPath}' is outside the workspace. Only files in ~/.teleton/workspace/ are accessible.`,
|
|
2741
|
-
inputPath
|
|
2742
|
-
);
|
|
2743
|
-
}
|
|
2744
|
-
const exists = existsSync5(absolutePath);
|
|
2745
|
-
if (!exists && !allowCreate) {
|
|
2746
|
-
throw new WorkspaceSecurityError(
|
|
2747
|
-
`File not found: '${inputPath}' does not exist in workspace.`,
|
|
2748
|
-
inputPath
|
|
2749
|
-
);
|
|
2750
|
-
}
|
|
2751
|
-
if (exists) {
|
|
2752
|
-
const stats = lstatSync(absolutePath);
|
|
2753
|
-
if (stats.isSymbolicLink()) {
|
|
2754
|
-
throw new WorkspaceSecurityError(
|
|
2755
|
-
`Access denied: Symbolic links are not allowed for security reasons.`,
|
|
2756
|
-
inputPath
|
|
2757
|
-
);
|
|
3135
|
+
function getNestedValue(obj, path) {
|
|
3136
|
+
const parts = path.split(".");
|
|
3137
|
+
assertSafePath(parts);
|
|
3138
|
+
let current = obj;
|
|
3139
|
+
for (const part of parts) {
|
|
3140
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
3141
|
+
current = current[part];
|
|
3142
|
+
}
|
|
3143
|
+
return current;
|
|
3144
|
+
}
|
|
3145
|
+
function setNestedValue(obj, path, value) {
|
|
3146
|
+
const parts = path.split(".");
|
|
3147
|
+
assertSafePath(parts);
|
|
3148
|
+
let current = obj;
|
|
3149
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3150
|
+
if (current[parts[i]] == null || typeof current[parts[i]] !== "object") {
|
|
3151
|
+
current[parts[i]] = {};
|
|
2758
3152
|
}
|
|
3153
|
+
current = current[parts[i]];
|
|
2759
3154
|
}
|
|
2760
|
-
|
|
2761
|
-
absolutePath,
|
|
2762
|
-
relativePath,
|
|
2763
|
-
exists,
|
|
2764
|
-
isDirectory: exists ? lstatSync(absolutePath).isDirectory() : false,
|
|
2765
|
-
extension: extname(absolutePath).toLowerCase(),
|
|
2766
|
-
filename: basename(absolutePath)
|
|
2767
|
-
};
|
|
3155
|
+
current[parts[parts.length - 1]] = value;
|
|
2768
3156
|
}
|
|
2769
|
-
function
|
|
2770
|
-
const
|
|
2771
|
-
|
|
2772
|
-
|
|
3157
|
+
function deleteNestedValue(obj, path) {
|
|
3158
|
+
const parts = path.split(".");
|
|
3159
|
+
assertSafePath(parts);
|
|
3160
|
+
let current = obj;
|
|
3161
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3162
|
+
if (current == null || typeof current !== "object") return;
|
|
3163
|
+
current = current[parts[i]];
|
|
3164
|
+
}
|
|
3165
|
+
if (current != null && typeof current === "object") {
|
|
3166
|
+
delete current[parts[parts.length - 1]];
|
|
2773
3167
|
}
|
|
2774
|
-
return validated;
|
|
2775
3168
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
`Cannot write to ${validated.filename}. This file is configured by the owner. Use memory_write instead.`,
|
|
2782
|
-
inputPath
|
|
2783
|
-
);
|
|
3169
|
+
function readRawConfig(configPath) {
|
|
3170
|
+
const fullPath = expandPath(configPath);
|
|
3171
|
+
if (!existsSync7(fullPath)) {
|
|
3172
|
+
throw new Error(`Config file not found: ${fullPath}
|
|
3173
|
+
Run 'teleton setup' to create one.`);
|
|
2784
3174
|
}
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
throw new WorkspaceSecurityError(
|
|
2789
|
-
`Invalid file type: '${validated.extension}' is not allowed for ${fileType}. Allowed: ${allowedExts.join(", ")}`,
|
|
2790
|
-
inputPath
|
|
2791
|
-
);
|
|
2792
|
-
}
|
|
3175
|
+
const raw = parse2(readFileSync6(fullPath, "utf-8"));
|
|
3176
|
+
if (!raw || typeof raw !== "object") {
|
|
3177
|
+
throw new Error(`Invalid config file: ${fullPath}`);
|
|
2793
3178
|
}
|
|
2794
|
-
return
|
|
3179
|
+
return raw;
|
|
2795
3180
|
}
|
|
2796
|
-
function
|
|
2797
|
-
const
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
);
|
|
3181
|
+
function writeRawConfig(raw, configPath) {
|
|
3182
|
+
const clone = { ...raw };
|
|
3183
|
+
delete clone.market;
|
|
3184
|
+
const result = ConfigSchema.safeParse(clone);
|
|
3185
|
+
if (!result.success) {
|
|
3186
|
+
throw new Error(`Refusing to save invalid config: ${result.error.message}`);
|
|
2803
3187
|
}
|
|
2804
|
-
|
|
3188
|
+
raw.meta = raw.meta ?? {};
|
|
3189
|
+
raw.meta.last_modified_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3190
|
+
const fullPath = expandPath(configPath);
|
|
3191
|
+
writeFileSync3(fullPath, stringify2(raw), { encoding: "utf-8", mode: 384 });
|
|
2805
3192
|
}
|
|
2806
3193
|
|
|
2807
3194
|
export {
|
|
2808
|
-
DealsConfigSchema,
|
|
2809
|
-
ConfigSchema,
|
|
2810
|
-
getProviderMetadata,
|
|
2811
|
-
getSupportedProviders,
|
|
2812
|
-
validateApiKeyFormat,
|
|
2813
|
-
expandPath,
|
|
2814
3195
|
loadConfig,
|
|
2815
3196
|
configExists,
|
|
2816
3197
|
getDefaultConfigPath,
|
|
@@ -2819,21 +3200,27 @@ export {
|
|
|
2819
3200
|
validateReadPath,
|
|
2820
3201
|
validateWritePath,
|
|
2821
3202
|
validateDirectory,
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
getWalletBalance,
|
|
2830
|
-
getTonPrice,
|
|
3203
|
+
appendToDailyLog,
|
|
3204
|
+
writeSummaryToDailyLog,
|
|
3205
|
+
sanitizeForPrompt,
|
|
3206
|
+
sanitizeForContext,
|
|
3207
|
+
clearPromptCache,
|
|
3208
|
+
loadSoul,
|
|
3209
|
+
buildSystemPrompt,
|
|
2831
3210
|
writePluginSecret,
|
|
2832
3211
|
deletePluginSecret,
|
|
2833
3212
|
listPluginSecretKeys,
|
|
3213
|
+
toLong,
|
|
3214
|
+
randomLong,
|
|
2834
3215
|
withBlockchainRetry,
|
|
2835
3216
|
sendTon,
|
|
2836
3217
|
adaptPlugin,
|
|
2837
3218
|
ensurePluginDeps,
|
|
2838
|
-
loadEnhancedPlugins
|
|
3219
|
+
loadEnhancedPlugins,
|
|
3220
|
+
CONFIGURABLE_KEYS,
|
|
3221
|
+
getNestedValue,
|
|
3222
|
+
setNestedValue,
|
|
3223
|
+
deleteNestedValue,
|
|
3224
|
+
readRawConfig,
|
|
3225
|
+
writeRawConfig
|
|
2839
3226
|
};
|