teleton 0.7.0 → 0.7.1

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.
@@ -42,7 +42,7 @@ import {
42
42
  writePluginSecret,
43
43
  writeRawConfig,
44
44
  writeSummaryToDailyLog
45
- } from "./chunk-TCD4NZDA.js";
45
+ } from "./chunk-HZNZT4TG.js";
46
46
  import {
47
47
  getKeyPair,
48
48
  getTonPrice,
@@ -83,7 +83,7 @@ import {
83
83
  closeDatabase,
84
84
  getDatabase,
85
85
  initializeMemory
86
- } from "./chunk-N3F7E7DR.js";
86
+ } from "./chunk-RBU6JXD3.js";
87
87
  import {
88
88
  JOURNAL_SCHEMA,
89
89
  USED_TRANSACTIONS_SCHEMA,
@@ -20984,6 +20984,7 @@ var PluginWatcher = class {
20984
20984
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
20985
20985
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
20986
20986
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
20987
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
20987
20988
  var log123 = createLogger("MCP");
20988
20989
  var MCP_CONNECT_TIMEOUT_MS = 3e4;
20989
20990
  function parseCommand(config) {
@@ -21031,21 +21032,48 @@ async function loadMcpServers(config) {
21031
21032
  stderr: "pipe"
21032
21033
  });
21033
21034
  } else if (serverConfig.url) {
21034
- transport = new SSEClientTransport(new URL(serverConfig.url));
21035
+ transport = new StreamableHTTPClientTransport(new URL(serverConfig.url));
21035
21036
  } else {
21036
21037
  throw new Error(`MCP server "${name}": needs 'command' or 'url'`);
21037
21038
  }
21038
21039
  const client = new Client({ name: `teleton-${name}`, version: "1.0.0" });
21039
21040
  let timeoutHandle;
21040
- await Promise.race([
21041
- client.connect(transport),
21042
- new Promise((_, reject) => {
21043
- timeoutHandle = setTimeout(
21044
- () => reject(new Error(`Connection timed out after ${MCP_CONNECT_TIMEOUT_MS / 1e3}s`)),
21045
- MCP_CONNECT_TIMEOUT_MS
21046
- );
21047
- })
21048
- ]).finally(() => clearTimeout(timeoutHandle));
21041
+ try {
21042
+ await Promise.race([
21043
+ client.connect(transport),
21044
+ new Promise((_, reject) => {
21045
+ timeoutHandle = setTimeout(
21046
+ () => reject(new Error(`Connection timed out after ${MCP_CONNECT_TIMEOUT_MS / 1e3}s`)),
21047
+ MCP_CONNECT_TIMEOUT_MS
21048
+ );
21049
+ })
21050
+ ]).finally(() => clearTimeout(timeoutHandle));
21051
+ } catch (err) {
21052
+ if (serverConfig.url && transport instanceof StreamableHTTPClientTransport) {
21053
+ await client.close().catch(() => {
21054
+ });
21055
+ log123.info({ server: name }, "Streamable HTTP failed, falling back to SSE");
21056
+ transport = new SSEClientTransport(new URL(serverConfig.url));
21057
+ const fallbackClient = new Client({ name: `teleton-${name}`, version: "1.0.0" });
21058
+ await Promise.race([
21059
+ fallbackClient.connect(transport),
21060
+ new Promise((_, reject) => {
21061
+ timeoutHandle = setTimeout(
21062
+ () => reject(
21063
+ new Error(`SSE fallback timed out after ${MCP_CONNECT_TIMEOUT_MS / 1e3}s`)
21064
+ ),
21065
+ MCP_CONNECT_TIMEOUT_MS
21066
+ );
21067
+ })
21068
+ ]).finally(() => clearTimeout(timeoutHandle));
21069
+ return {
21070
+ serverName: name,
21071
+ client: fallbackClient,
21072
+ scope: serverConfig.scope ?? "always"
21073
+ };
21074
+ }
21075
+ throw err;
21076
+ }
21049
21077
  return { serverName: name, client, scope: serverConfig.scope ?? "always" };
21050
21078
  })
21051
21079
  );
@@ -21056,9 +21084,8 @@ async function loadMcpServers(config) {
21056
21084
  if (result.status === "fulfilled") {
21057
21085
  connections.push(result.value);
21058
21086
  } else {
21059
- log123.warn(
21060
- `MCP server "${name}" failed to connect: ${result.reason instanceof Error ? result.reason.message : result.reason}`
21061
- );
21087
+ const reason = result.reason instanceof Error ? result.reason.stack ?? result.reason.message : result.reason;
21088
+ log123.warn({ server: name, reason }, `MCP server "${name}" failed to connect`);
21062
21089
  }
21063
21090
  }
21064
21091
  return connections;
@@ -21141,8 +21168,129 @@ async function closeMcpServers(connections) {
21141
21168
  );
21142
21169
  }
21143
21170
 
21171
+ // src/agent/lifecycle.ts
21172
+ import { EventEmitter } from "events";
21173
+ var log124 = createLogger("Lifecycle");
21174
+ var AgentLifecycle = class extends EventEmitter {
21175
+ state = "stopped";
21176
+ error;
21177
+ startPromise = null;
21178
+ stopPromise = null;
21179
+ runningSince = null;
21180
+ registeredStartFn = null;
21181
+ registeredStopFn = null;
21182
+ getState() {
21183
+ return this.state;
21184
+ }
21185
+ getError() {
21186
+ return this.error;
21187
+ }
21188
+ getUptime() {
21189
+ if (this.state !== "running" || this.runningSince === null) {
21190
+ return null;
21191
+ }
21192
+ return Math.floor((Date.now() - this.runningSince) / 1e3);
21193
+ }
21194
+ /**
21195
+ * Register the start/stop callbacks so start()/stop() can be called without args.
21196
+ */
21197
+ registerCallbacks(startFn, stopFn) {
21198
+ this.registeredStartFn = startFn;
21199
+ this.registeredStopFn = stopFn;
21200
+ }
21201
+ /**
21202
+ * Start the agent. Uses the provided callback or falls back to registered one.
21203
+ * - No-op if already running
21204
+ * - Returns existing promise if already starting
21205
+ * - Throws if currently stopping
21206
+ */
21207
+ async start(startFn) {
21208
+ const fn = startFn ?? this.registeredStartFn;
21209
+ if (!fn) {
21210
+ throw new Error("No start function provided or registered");
21211
+ }
21212
+ if (this.state === "running") {
21213
+ return;
21214
+ }
21215
+ if (this.state === "starting") {
21216
+ return this.startPromise;
21217
+ }
21218
+ if (this.state === "stopping") {
21219
+ throw new Error("Cannot start while agent is stopping");
21220
+ }
21221
+ this.transition("starting");
21222
+ this.startPromise = (async () => {
21223
+ try {
21224
+ await fn();
21225
+ this.error = void 0;
21226
+ this.runningSince = Date.now();
21227
+ this.transition("running");
21228
+ } catch (err) {
21229
+ const message = err instanceof Error ? err.message : String(err);
21230
+ this.error = message;
21231
+ this.runningSince = null;
21232
+ this.transition("stopped", message);
21233
+ throw err;
21234
+ } finally {
21235
+ this.startPromise = null;
21236
+ }
21237
+ })();
21238
+ return this.startPromise;
21239
+ }
21240
+ /**
21241
+ * Stop the agent. Uses the provided callback or falls back to registered one.
21242
+ * - No-op if already stopped
21243
+ * - Returns existing promise if already stopping
21244
+ * - If starting, waits for start to complete then stops
21245
+ */
21246
+ async stop(stopFn) {
21247
+ const fn = stopFn ?? this.registeredStopFn;
21248
+ if (!fn) {
21249
+ throw new Error("No stop function provided or registered");
21250
+ }
21251
+ if (this.state === "stopped") {
21252
+ return;
21253
+ }
21254
+ if (this.state === "stopping") {
21255
+ return this.stopPromise;
21256
+ }
21257
+ if (this.state === "starting" && this.startPromise) {
21258
+ try {
21259
+ await this.startPromise;
21260
+ } catch {
21261
+ return;
21262
+ }
21263
+ }
21264
+ this.transition("stopping");
21265
+ this.stopPromise = (async () => {
21266
+ try {
21267
+ await fn();
21268
+ } catch (err) {
21269
+ log124.error({ err }, "Error during agent stop");
21270
+ } finally {
21271
+ this.runningSince = null;
21272
+ this.transition("stopped");
21273
+ this.stopPromise = null;
21274
+ }
21275
+ })();
21276
+ return this.stopPromise;
21277
+ }
21278
+ transition(newState, error) {
21279
+ this.state = newState;
21280
+ const event = {
21281
+ state: newState,
21282
+ timestamp: Date.now()
21283
+ };
21284
+ if (error !== void 0) {
21285
+ event.error = error;
21286
+ }
21287
+ log124.info(`Agent state: ${newState}${error ? ` (${error})` : ""}`);
21288
+ this.emit("stateChange", event);
21289
+ }
21290
+ };
21291
+
21144
21292
  // src/index.ts
21145
- var log124 = createLogger("App");
21293
+ var log125 = createLogger("App");
21146
21294
  var TeletonApp = class {
21147
21295
  config;
21148
21296
  agent;
@@ -21162,6 +21310,7 @@ var TeletonApp = class {
21162
21310
  pluginWatcher = null;
21163
21311
  mcpConnections = [];
21164
21312
  callbackHandlerRegistered = false;
21313
+ lifecycle = new AgentLifecycle();
21165
21314
  configPath;
21166
21315
  constructor(configPath) {
21167
21316
  this.configPath = configPath ?? getDefaultConfigPath();
@@ -21220,13 +21369,19 @@ var TeletonApp = class {
21220
21369
  this.toolRegistry
21221
21370
  );
21222
21371
  }
21372
+ /**
21373
+ * Get the lifecycle state machine for WebUI integration
21374
+ */
21375
+ getLifecycle() {
21376
+ return this.lifecycle;
21377
+ }
21223
21378
  /**
21224
21379
  * Start the agent
21225
21380
  */
21226
21381
  async start() {
21227
21382
  const blue = "\x1B[34m";
21228
21383
  const reset = "\x1B[0m";
21229
- log124.info(`
21384
+ log125.info(`
21230
21385
  ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
21231
21386
  \u2502 \u2502
21232
21387
  \u2502 ______________ ________________ _ __ ___ _____________ ________ \u2502
@@ -21237,6 +21392,71 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21237
21392
  \u2502 \u2502
21238
21393
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 DEV: ZKPROOF.T.ME \u2500\u2500\u2518${reset}
21239
21394
  `);
21395
+ this.lifecycle.registerCallbacks(
21396
+ () => this.startAgent(),
21397
+ () => this.stopAgent()
21398
+ );
21399
+ if (this.config.webui.enabled) {
21400
+ try {
21401
+ const { WebUIServer } = await import("./server-DS5OARW6.js");
21402
+ const mcpServers = () => Object.entries(this.config.mcp.servers).map(([name, serverConfig]) => {
21403
+ const type = serverConfig.command ? "stdio" : serverConfig.url ? "streamable-http" : "sse";
21404
+ const target = serverConfig.command ?? serverConfig.url ?? "";
21405
+ const connected = this.mcpConnections.some((c2) => c2.serverName === name);
21406
+ const moduleName = `mcp_${name}`;
21407
+ const moduleTools = this.toolRegistry.getModuleTools(moduleName);
21408
+ return {
21409
+ name,
21410
+ type,
21411
+ target,
21412
+ scope: serverConfig.scope ?? "always",
21413
+ enabled: serverConfig.enabled ?? true,
21414
+ connected,
21415
+ toolCount: moduleTools.length,
21416
+ tools: moduleTools.map((t) => t.name),
21417
+ envKeys: Object.keys(serverConfig.env ?? {})
21418
+ };
21419
+ });
21420
+ const builtinNames = this.modules.map((m) => m.name);
21421
+ const pluginContext = {
21422
+ bridge: this.bridge,
21423
+ db: getDatabase().getDb(),
21424
+ config: this.config
21425
+ };
21426
+ this.webuiServer = new WebUIServer({
21427
+ agent: this.agent,
21428
+ bridge: this.bridge,
21429
+ memory: this.memory,
21430
+ toolRegistry: this.toolRegistry,
21431
+ plugins: this.modules.filter((m) => this.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" })),
21432
+ mcpServers,
21433
+ config: this.config.webui,
21434
+ configPath: this.configPath,
21435
+ lifecycle: this.lifecycle,
21436
+ marketplace: {
21437
+ modules: this.modules,
21438
+ config: this.config,
21439
+ sdkDeps: this.sdkDeps,
21440
+ pluginContext,
21441
+ loadedModuleNames: builtinNames,
21442
+ rewireHooks: () => this.wirePluginEventHooks()
21443
+ }
21444
+ });
21445
+ await this.webuiServer.start();
21446
+ } catch (error) {
21447
+ log125.error({ err: error }, "\u274C Failed to start WebUI server");
21448
+ log125.warn("\u26A0\uFE0F Continuing without WebUI...");
21449
+ }
21450
+ }
21451
+ await this.lifecycle.start(() => this.startAgent());
21452
+ await new Promise(() => {
21453
+ });
21454
+ }
21455
+ /**
21456
+ * Start agent subsystems (Telegram, plugins, MCP, modules, debouncer, handler).
21457
+ * Called by lifecycle.start() — do NOT call directly.
21458
+ */
21459
+ async startAgent() {
21240
21460
  const moduleNames = this.modules.filter((m) => m.tools(this.config).length > 0).map((m) => m.name);
21241
21461
  const builtinNames = this.modules.map((m) => m.name);
21242
21462
  const externalModules = await loadEnhancedPlugins(this.config, builtinNames, this.sdkDeps);
@@ -21253,7 +21473,7 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21253
21473
  }
21254
21474
  this.modules.push(mod);
21255
21475
  } catch (error) {
21256
- log124.error(
21476
+ log125.error(
21257
21477
  `\u274C Plugin "${mod.name}" failed to load: ${error instanceof Error ? error.message : error}`
21258
21478
  );
21259
21479
  }
@@ -21269,7 +21489,7 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21269
21489
  if (mcp.count > 0) {
21270
21490
  this.toolCount = this.toolRegistry.count;
21271
21491
  mcpServerNames.push(...mcp.names);
21272
- log124.info(
21492
+ log125.info(
21273
21493
  `\u{1F50C} MCP: ${mcp.count} tools from ${mcp.names.length} server(s) (${mcp.names.join(", ")})`
21274
21494
  );
21275
21495
  }
@@ -21297,15 +21517,15 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21297
21517
  const provider = this.config.agent.provider || "anthropic";
21298
21518
  const providerMeta = getProviderMetadata(provider);
21299
21519
  const allNames = [...moduleNames, ...pluginNames, ...mcpServerNames];
21300
- log124.info(
21520
+ log125.info(
21301
21521
  `\u{1F50C} ${this.toolCount} tools loaded (${allNames.join(", ")})${pluginToolCount > 0 ? ` \u2014 ${pluginToolCount} from plugins` : ""}`
21302
21522
  );
21303
21523
  if (providerMeta.toolLimit !== null && this.toolCount > providerMeta.toolLimit) {
21304
- log124.warn(
21524
+ log125.warn(
21305
21525
  `\u26A0\uFE0F Tool count (${this.toolCount}) exceeds ${providerMeta.displayName} limit (${providerMeta.toolLimit})`
21306
21526
  );
21307
21527
  }
21308
- const { migrateSessionsToDb } = await import("./migrate-GO4NOBT7.js");
21528
+ const { migrateSessionsToDb } = await import("./migrate-M7SJMDOL.js");
21309
21529
  migrateSessionsToDb();
21310
21530
  const { cleanupOldTranscripts } = await import("./transcript-UDJZP6NK.js");
21311
21531
  cleanupOldTranscripts(30);
@@ -21319,7 +21539,7 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21319
21539
  if (toolIndex) {
21320
21540
  const t0 = Date.now();
21321
21541
  const indexedCount = await toolIndex.indexAll(this.toolRegistry.getAll());
21322
- log124.info(`\u{1F50D} Tool RAG: ${indexedCount} tools indexed (${Date.now() - t0}ms)`);
21542
+ log125.info(`\u{1F50D} Tool RAG: ${indexedCount} tools indexed (${Date.now() - t0}ms)`);
21323
21543
  }
21324
21544
  this.agent.initializeContextBuilder(this.memory.embedder, db2.isVectorSearchReady());
21325
21545
  if (this.config.agent.provider === "cocoon") {
@@ -21330,44 +21550,44 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21330
21550
  if (models.length === 0) {
21331
21551
  throw new Error(`No models found on port ${port}`);
21332
21552
  }
21333
- log124.info(`Cocoon Network ready \u2014 ${models.length} model(s) on port ${port}`);
21553
+ log125.info(`Cocoon Network ready \u2014 ${models.length} model(s) on port ${port}`);
21334
21554
  } catch (err) {
21335
- log124.error(
21555
+ log125.error(
21336
21556
  `Cocoon Network unavailable on port ${this.config.cocoon?.port ?? 1e4}: ${getErrorMessage(err)}`
21337
21557
  );
21338
- log124.error("Start the Cocoon client first: cocoon start");
21339
- process.exit(1);
21558
+ log125.error("Start the Cocoon client first: cocoon start");
21559
+ throw new Error(`Cocoon Network unavailable: ${getErrorMessage(err)}`);
21340
21560
  }
21341
21561
  }
21342
21562
  if (this.config.agent.provider === "local" && !this.config.agent.base_url) {
21343
- log124.error("Local provider requires base_url in config (e.g. http://localhost:11434/v1)");
21344
- process.exit(1);
21563
+ throw new Error(
21564
+ "Local provider requires base_url in config (e.g. http://localhost:11434/v1)"
21565
+ );
21345
21566
  }
21346
21567
  if (this.config.agent.provider === "local" && this.config.agent.base_url) {
21347
21568
  try {
21348
21569
  const { registerLocalModels } = await import("./client-3VWE7NC4.js");
21349
21570
  const models = await registerLocalModels(this.config.agent.base_url);
21350
21571
  if (models.length > 0) {
21351
- log124.info(`Discovered ${models.length} local model(s): ${models.join(", ")}`);
21572
+ log125.info(`Discovered ${models.length} local model(s): ${models.join(", ")}`);
21352
21573
  if (!this.config.agent.model || this.config.agent.model === "auto") {
21353
21574
  this.config.agent.model = models[0];
21354
- log124.info(`Using local model: ${models[0]}`);
21575
+ log125.info(`Using local model: ${models[0]}`);
21355
21576
  }
21356
21577
  } else {
21357
- log124.warn("No models found on local LLM server \u2014 is it running?");
21578
+ log125.warn("No models found on local LLM server \u2014 is it running?");
21358
21579
  }
21359
21580
  } catch (err) {
21360
- log124.error(
21581
+ log125.error(
21361
21582
  `Local LLM server unavailable at ${this.config.agent.base_url}: ${getErrorMessage(err)}`
21362
21583
  );
21363
- log124.error("Start the LLM server first (e.g. ollama serve)");
21364
- process.exit(1);
21584
+ log125.error("Start the LLM server first (e.g. ollama serve)");
21585
+ throw new Error(`Local LLM server unavailable: ${getErrorMessage(err)}`);
21365
21586
  }
21366
21587
  }
21367
21588
  await this.bridge.connect();
21368
21589
  if (!this.bridge.isAvailable()) {
21369
- log124.error("\u274C Failed to connect to Telegram");
21370
- process.exit(1);
21590
+ throw new Error("Failed to connect to Telegram");
21371
21591
  }
21372
21592
  await this.resolveOwnerInfo();
21373
21593
  const ownUserId = this.bridge.getOwnUserId();
@@ -21389,12 +21609,12 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21389
21609
  startedModules.push(mod);
21390
21610
  }
21391
21611
  } catch (error) {
21392
- log124.error({ err: error }, "\u274C Module start failed, cleaning up started modules");
21612
+ log125.error({ err: error }, "\u274C Module start failed, cleaning up started modules");
21393
21613
  for (const mod of startedModules.reverse()) {
21394
21614
  try {
21395
21615
  await mod.stop?.();
21396
21616
  } catch (e) {
21397
- log124.error({ err: e }, `\u26A0\uFE0F Module "${mod.name}" cleanup failed`);
21617
+ log125.error({ err: e }, `\u26A0\uFE0F Module "${mod.name}" cleanup failed`);
21398
21618
  }
21399
21619
  }
21400
21620
  throw error;
@@ -21411,67 +21631,22 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21411
21631
  });
21412
21632
  this.pluginWatcher.start();
21413
21633
  }
21414
- log124.info(`\u2705 SOUL.md loaded`);
21415
- log124.info(`\u2705 Knowledge: ${indexResult.indexed} files, ${ftsResult.knowledge} chunks indexed`);
21416
- log124.info(`\u2705 Telegram: @${username} connected`);
21417
- log124.info(`\u2705 TON Blockchain: connected`);
21634
+ log125.info(`\u2705 SOUL.md loaded`);
21635
+ log125.info(`\u2705 Knowledge: ${indexResult.indexed} files, ${ftsResult.knowledge} chunks indexed`);
21636
+ log125.info(`\u2705 Telegram: @${username} connected`);
21637
+ log125.info(`\u2705 TON Blockchain: connected`);
21418
21638
  if (this.config.tonapi_key) {
21419
- log124.info(`\u{1F511} TonAPI key configured`);
21639
+ log125.info(`\u{1F511} TonAPI key configured`);
21420
21640
  }
21421
- log124.info(`\u2705 DEXs: STON.fi, DeDust connected`);
21422
- log124.info(`\u2705 Wallet: ${walletAddress || "not configured"}`);
21423
- log124.info(`\u2705 Model: ${provider}/${this.config.agent.model}`);
21424
- log124.info(`\u2705 Admins: ${this.config.telegram.admin_ids.join(", ")}`);
21425
- log124.info(
21641
+ log125.info(`\u2705 DEXs: STON.fi, DeDust connected`);
21642
+ log125.info(`\u2705 Wallet: ${walletAddress || "not configured"}`);
21643
+ log125.info(`\u2705 Model: ${provider}/${this.config.agent.model}`);
21644
+ log125.info(`\u2705 Admins: ${this.config.telegram.admin_ids.join(", ")}`);
21645
+ log125.info(
21426
21646
  `\u2705 Policy: DM ${this.config.telegram.dm_policy}, Groups ${this.config.telegram.group_policy}, Debounce ${this.config.telegram.debounce_ms}ms
21427
21647
  `
21428
21648
  );
21429
- log124.info("Teleton Agent is running! Press Ctrl+C to stop.");
21430
- if (this.config.webui.enabled) {
21431
- try {
21432
- const { WebUIServer } = await import("./server-OWVEZTR3.js");
21433
- const mcpServers = Object.entries(this.config.mcp.servers).map(([name, serverConfig]) => {
21434
- const type = serverConfig.command ? "stdio" : "sse";
21435
- const target = serverConfig.command ?? serverConfig.url ?? "";
21436
- const connected = this.mcpConnections.some((c2) => c2.serverName === name);
21437
- const moduleName = `mcp_${name}`;
21438
- const moduleTools = this.toolRegistry.getModuleTools(moduleName);
21439
- return {
21440
- name,
21441
- type,
21442
- target,
21443
- scope: serverConfig.scope ?? "always",
21444
- enabled: serverConfig.enabled ?? true,
21445
- connected,
21446
- toolCount: moduleTools.length,
21447
- tools: moduleTools.map((t) => t.name),
21448
- envKeys: Object.keys(serverConfig.env ?? {})
21449
- };
21450
- });
21451
- this.webuiServer = new WebUIServer({
21452
- agent: this.agent,
21453
- bridge: this.bridge,
21454
- memory: this.memory,
21455
- toolRegistry: this.toolRegistry,
21456
- plugins: this.modules.filter((m) => this.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" })),
21457
- mcpServers,
21458
- config: this.config.webui,
21459
- configPath: this.configPath,
21460
- marketplace: {
21461
- modules: this.modules,
21462
- config: this.config,
21463
- sdkDeps: this.sdkDeps,
21464
- pluginContext,
21465
- loadedModuleNames: builtinNames,
21466
- rewireHooks: () => this.wirePluginEventHooks()
21467
- }
21468
- });
21469
- await this.webuiServer.start();
21470
- } catch (error) {
21471
- log124.error({ err: error }, "\u274C Failed to start WebUI server");
21472
- log124.warn("\u26A0\uFE0F Continuing without WebUI...");
21473
- }
21474
- }
21649
+ log125.info("Teleton Agent is running! Press Ctrl+C to stop.");
21475
21650
  this.debouncer = new MessageDebouncer(
21476
21651
  {
21477
21652
  debounceMs: this.config.telegram.debounce_ms
@@ -21492,18 +21667,16 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21492
21667
  }
21493
21668
  },
21494
21669
  (error, messages) => {
21495
- log124.error({ err: error }, `Error processing batch of ${messages.length} messages`);
21670
+ log125.error({ err: error }, `Error processing batch of ${messages.length} messages`);
21496
21671
  }
21497
21672
  );
21498
21673
  this.bridge.onNewMessage(async (message) => {
21499
21674
  try {
21500
21675
  await this.debouncer.enqueue(message);
21501
21676
  } catch (error) {
21502
- log124.error({ err: error }, "Error enqueueing message");
21677
+ log125.error({ err: error }, "Error enqueueing message");
21503
21678
  }
21504
21679
  });
21505
- await new Promise(() => {
21506
- });
21507
21680
  }
21508
21681
  /**
21509
21682
  * Resolve owner name and username from Telegram API if not already configured.
@@ -21546,10 +21719,10 @@ ${blue} \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
21546
21719
  writeRawConfig(raw, this.configPath);
21547
21720
  const displayName = this.config.telegram.owner_name || "Unknown";
21548
21721
  const displayUsername = this.config.telegram.owner_username ? ` (@${this.config.telegram.owner_username})` : "";
21549
- log124.info(`\u{1F464} Owner resolved: ${displayName}${displayUsername}`);
21722
+ log125.info(`\u{1F464} Owner resolved: ${displayName}${displayUsername}`);
21550
21723
  }
21551
21724
  } catch (error) {
21552
- log124.warn(
21725
+ log125.warn(
21553
21726
  `\u26A0\uFE0F Could not resolve owner info: ${error instanceof Error ? error.message : error}`
21554
21727
  );
21555
21728
  }
@@ -21617,7 +21790,7 @@ Task: "${taskDescription}"`;
21617
21790
  if (this.adminHandler.isPaused()) return;
21618
21791
  await this.messageHandler.handleMessage(message);
21619
21792
  } catch (error) {
21620
- log124.error({ err: error }, "Error handling message");
21793
+ log125.error({ err: error }, "Error handling message");
21621
21794
  }
21622
21795
  }
21623
21796
  /**
@@ -21627,19 +21800,19 @@ Task: "${taskDescription}"`;
21627
21800
  const { getTaskStore } = await import("./tasks-QSCWSMPS.js");
21628
21801
  const { executeScheduledTask } = await import("./task-executor-PD3H4MLO.js");
21629
21802
  const { TaskDependencyResolver } = await import("./task-dependency-resolver-WKZWJLLM.js");
21630
- const { getDatabase: getDatabase2 } = await import("./memory-RD7ZSTRV.js");
21803
+ const { getDatabase: getDatabase2 } = await import("./memory-5SS3Q5EA.js");
21631
21804
  const db2 = getDatabase2().getDb();
21632
21805
  const taskStore = getTaskStore(db2);
21633
21806
  const match = message.text.match(/^\[TASK:([^\]]+)\]/);
21634
21807
  if (!match) {
21635
- log124.warn(`Invalid task format: ${message.text}`);
21808
+ log125.warn(`Invalid task format: ${message.text}`);
21636
21809
  return;
21637
21810
  }
21638
21811
  const taskId = match[1];
21639
21812
  try {
21640
21813
  const task = taskStore.getTask(taskId);
21641
21814
  if (!task) {
21642
- log124.warn(`Task ${taskId} not found in database`);
21815
+ log125.warn(`Task ${taskId} not found in database`);
21643
21816
  await this.bridge.sendMessage({
21644
21817
  chatId: message.chatId,
21645
21818
  text: `\u26A0\uFE0F Task ${taskId} not found. It may have been deleted.`,
@@ -21648,11 +21821,11 @@ Task: "${taskDescription}"`;
21648
21821
  return;
21649
21822
  }
21650
21823
  if (task.status === "cancelled" || task.status === "done" || task.status === "failed") {
21651
- log124.info(`\u23ED\uFE0F Task ${taskId} already ${task.status}, skipping`);
21824
+ log125.info(`\u23ED\uFE0F Task ${taskId} already ${task.status}, skipping`);
21652
21825
  return;
21653
21826
  }
21654
21827
  if (!taskStore.canExecute(taskId)) {
21655
- log124.warn(`Task ${taskId} cannot execute yet - dependencies not satisfied`);
21828
+ log125.warn(`Task ${taskId} cannot execute yet - dependencies not satisfied`);
21656
21829
  await this.bridge.sendMessage({
21657
21830
  chatId: message.chatId,
21658
21831
  text: `\u23F3 Task "${task.description}" is waiting for parent tasks to complete.`,
@@ -21701,13 +21874,13 @@ Task: "${taskDescription}"`;
21701
21874
  });
21702
21875
  }
21703
21876
  taskStore.completeTask(taskId, response.content);
21704
- log124.info(`\u2705 Executed scheduled task ${taskId}: ${task.description}`);
21877
+ log125.info(`\u2705 Executed scheduled task ${taskId}: ${task.description}`);
21705
21878
  if (!this.dependencyResolver) {
21706
21879
  this.dependencyResolver = new TaskDependencyResolver(taskStore, this.bridge);
21707
21880
  }
21708
21881
  await this.dependencyResolver.onTaskComplete(taskId);
21709
21882
  } catch (error) {
21710
- log124.error({ err: error }, "Error handling scheduled task");
21883
+ log125.error({ err: error }, "Error handling scheduled task");
21711
21884
  try {
21712
21885
  taskStore.failTask(taskId, getErrorMessage(error));
21713
21886
  if (!this.dependencyResolver) {
@@ -21732,7 +21905,7 @@ Task: "${taskDescription}"`;
21732
21905
  try {
21733
21906
  await withHooks.onMessage(event);
21734
21907
  } catch (err) {
21735
- log124.error(
21908
+ log125.error(
21736
21909
  `\u274C [${mod.name}] onMessage error: ${err instanceof Error ? err.message : err}`
21737
21910
  );
21738
21911
  }
@@ -21742,7 +21915,7 @@ Task: "${taskDescription}"`;
21742
21915
  ]);
21743
21916
  const hookCount = this.modules.filter((m) => m.onMessage).length;
21744
21917
  if (hookCount > 0) {
21745
- log124.info(`\u{1F517} ${hookCount} plugin onMessage hook(s) registered`);
21918
+ log125.info(`\u{1F517} ${hookCount} plugin onMessage hook(s) registered`);
21746
21919
  }
21747
21920
  if (!this.callbackHandlerRegistered) {
21748
21921
  this.bridge.getClient().addCallbackQueryHandler(async (update) => {
@@ -21758,7 +21931,7 @@ Task: "${taskDescription}"`;
21758
21931
  try {
21759
21932
  await this.bridge.getClient().answerCallbackQuery(queryId, { message: text, alert });
21760
21933
  } catch (err) {
21761
- log124.error(
21934
+ log125.error(
21762
21935
  `\u274C Failed to answer callback query: ${err instanceof Error ? err.message : err}`
21763
21936
  );
21764
21937
  }
@@ -21778,7 +21951,7 @@ Task: "${taskDescription}"`;
21778
21951
  try {
21779
21952
  await withHooks.onCallbackQuery(event);
21780
21953
  } catch (err) {
21781
- log124.error(
21954
+ log125.error(
21782
21955
  `\u274C [${mod.name}] onCallbackQuery error: ${err instanceof Error ? err.message : err}`
21783
21956
  );
21784
21957
  }
@@ -21790,7 +21963,7 @@ Task: "${taskDescription}"`;
21790
21963
  (m) => m.onCallbackQuery
21791
21964
  ).length;
21792
21965
  if (cbCount > 0) {
21793
- log124.info(`\u{1F517} ${cbCount} plugin onCallbackQuery hook(s) registered`);
21966
+ log125.info(`\u{1F517} ${cbCount} plugin onCallbackQuery hook(s) registered`);
21794
21967
  }
21795
21968
  }
21796
21969
  }
@@ -21798,56 +21971,63 @@ Task: "${taskDescription}"`;
21798
21971
  * Stop the agent
21799
21972
  */
21800
21973
  async stop() {
21801
- log124.info("\u{1F44B} Stopping Teleton AI...");
21974
+ log125.info("\u{1F44B} Stopping Teleton AI...");
21975
+ await this.lifecycle.stop(() => this.stopAgent());
21802
21976
  if (this.webuiServer) {
21803
21977
  try {
21804
21978
  await this.webuiServer.stop();
21805
21979
  } catch (e) {
21806
- log124.error({ err: e }, "\u26A0\uFE0F WebUI stop failed");
21980
+ log125.error({ err: e }, "\u26A0\uFE0F WebUI stop failed");
21807
21981
  }
21808
21982
  }
21983
+ try {
21984
+ closeDatabase();
21985
+ } catch (e) {
21986
+ log125.error({ err: e }, "\u26A0\uFE0F Database close failed");
21987
+ }
21988
+ }
21989
+ /**
21990
+ * Stop agent subsystems (watcher, MCP, debouncer, handler, modules, bridge).
21991
+ * Called by lifecycle.stop() — do NOT call directly.
21992
+ */
21993
+ async stopAgent() {
21809
21994
  if (this.pluginWatcher) {
21810
21995
  try {
21811
21996
  await this.pluginWatcher.stop();
21812
21997
  } catch (e) {
21813
- log124.error({ err: e }, "\u26A0\uFE0F Plugin watcher stop failed");
21998
+ log125.error({ err: e }, "\u26A0\uFE0F Plugin watcher stop failed");
21814
21999
  }
21815
22000
  }
21816
22001
  if (this.mcpConnections.length > 0) {
21817
22002
  try {
21818
22003
  await closeMcpServers(this.mcpConnections);
21819
22004
  } catch (e) {
21820
- log124.error({ err: e }, "\u26A0\uFE0F MCP close failed");
22005
+ log125.error({ err: e }, "\u26A0\uFE0F MCP close failed");
21821
22006
  }
21822
22007
  }
21823
22008
  if (this.debouncer) {
21824
22009
  try {
21825
22010
  await this.debouncer.flushAll();
21826
22011
  } catch (e) {
21827
- log124.error({ err: e }, "\u26A0\uFE0F Debouncer flush failed");
22012
+ log125.error({ err: e }, "\u26A0\uFE0F Debouncer flush failed");
21828
22013
  }
21829
22014
  }
21830
22015
  try {
21831
22016
  await this.messageHandler.drain();
21832
22017
  } catch (e) {
21833
- log124.error({ err: e }, "\u26A0\uFE0F Message queue drain failed");
22018
+ log125.error({ err: e }, "\u26A0\uFE0F Message queue drain failed");
21834
22019
  }
21835
22020
  for (const mod of this.modules) {
21836
22021
  try {
21837
22022
  await mod.stop?.();
21838
22023
  } catch (e) {
21839
- log124.error({ err: e }, `\u26A0\uFE0F Module "${mod.name}" stop failed`);
22024
+ log125.error({ err: e }, `\u26A0\uFE0F Module "${mod.name}" stop failed`);
21840
22025
  }
21841
22026
  }
21842
22027
  try {
21843
22028
  await this.bridge.disconnect();
21844
22029
  } catch (e) {
21845
- log124.error({ err: e }, "\u26A0\uFE0F Bridge disconnect failed");
21846
- }
21847
- try {
21848
- closeDatabase();
21849
- } catch (e) {
21850
- log124.error({ err: e }, "\u26A0\uFE0F Database close failed");
22030
+ log125.error({ err: e }, "\u26A0\uFE0F Bridge disconnect failed");
21851
22031
  }
21852
22032
  }
21853
22033
  };
@@ -21856,14 +22036,14 @@ async function main(configPath) {
21856
22036
  try {
21857
22037
  app = new TeletonApp(configPath);
21858
22038
  } catch (error) {
21859
- log124.error(`Failed to initialize: ${error instanceof Error ? error.message : error}`);
22039
+ log125.error(`Failed to initialize: ${error instanceof Error ? error.message : error}`);
21860
22040
  process.exit(1);
21861
22041
  }
21862
22042
  process.on("unhandledRejection", (reason) => {
21863
- log124.error({ err: reason }, "\u26A0\uFE0F Unhandled promise rejection");
22043
+ log125.error({ err: reason }, "\u26A0\uFE0F Unhandled promise rejection");
21864
22044
  });
21865
22045
  process.on("uncaughtException", (error) => {
21866
- log124.error({ err: error }, "\u{1F4A5} Uncaught exception");
22046
+ log125.error({ err: error }, "\u{1F4A5} Uncaught exception");
21867
22047
  process.exit(1);
21868
22048
  });
21869
22049
  let shutdownInProgress = false;
@@ -21871,7 +22051,7 @@ async function main(configPath) {
21871
22051
  if (shutdownInProgress) return;
21872
22052
  shutdownInProgress = true;
21873
22053
  const forceExit = setTimeout(() => {
21874
- log124.error("\u26A0\uFE0F Shutdown timed out, forcing exit");
22054
+ log125.error("\u26A0\uFE0F Shutdown timed out, forcing exit");
21875
22055
  process.exit(1);
21876
22056
  }, SHUTDOWN_TIMEOUT_MS);
21877
22057
  forceExit.unref();
@@ -21885,7 +22065,7 @@ async function main(configPath) {
21885
22065
  }
21886
22066
  if (import.meta.url === `file://${process.argv[1]}`) {
21887
22067
  main().catch((error) => {
21888
- log124.fatal({ err: error }, "Fatal error");
22068
+ log125.fatal({ err: error }, "Fatal error");
21889
22069
  process.exit(1);
21890
22070
  });
21891
22071
  }