@cmdop/bot 2026.2.26 → 2026.2.28

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 CHANGED
@@ -279,6 +279,12 @@ All errors extend `BotError` and expose a `code` string:
279
279
  - `@cmdop/node` (peer dependency, installed automatically)
280
280
  - Platform libraries are **optional peer dependencies** — install only what you use
281
281
 
282
+ ## Links
283
+
284
+ - [Homepage](https://cmdop.com/sdk/python/bot)
285
+ - [Documentation](https://cmdop.com/docs/sdk/node/bot)
286
+ - [npm](https://www.npmjs.com/package/@cmdop/bot)
287
+
282
288
  ## License
283
289
 
284
290
  MIT
package/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ var grammy = require('grammy');
5
5
  var discord_js = require('discord.js');
6
6
  var bolt = require('@slack/bolt');
7
7
  var node = require('@cmdop/node');
8
+ var consola = require('consola');
8
9
 
9
10
  var __create = Object.create;
10
11
  var __defProp = Object.defineProperty;
@@ -161,6 +162,95 @@ var init_errors = __esm({
161
162
  }
162
163
  });
163
164
 
165
+ // src/models/command.ts
166
+ var command_exports = {};
167
+ __export(command_exports, {
168
+ ParsedCommandSchema: () => exports.ParsedCommandSchema,
169
+ parseCommand: () => parseCommand
170
+ });
171
+ function parseCommand(text) {
172
+ const trimmed = text.trim();
173
+ const match = /^[/!](\w+)(?:\s+(.*))?$/s.exec(trimmed);
174
+ if (!match) return null;
175
+ const name = (match[1] ?? "").toLowerCase();
176
+ const rest = (match[2] ?? "").trim();
177
+ const args = rest ? rest.split(/\s+/) : [];
178
+ return exports.ParsedCommandSchema.parse({ name, args, rawText: trimmed });
179
+ }
180
+ exports.ParsedCommandSchema = void 0;
181
+ var init_command = __esm({
182
+ "src/models/command.ts"() {
183
+ exports.ParsedCommandSchema = v4.z.object({
184
+ name: v4.z.string().min(1),
185
+ args: v4.z.array(v4.z.string()),
186
+ rawText: v4.z.string()
187
+ });
188
+ }
189
+ });
190
+
191
+ // src/core/base-channel.ts
192
+ exports.BaseChannel = void 0;
193
+ var init_base_channel = __esm({
194
+ "src/core/base-channel.ts"() {
195
+ init_command();
196
+ init_errors();
197
+ exports.BaseChannel = class {
198
+ constructor(id, name, permissions, dispatcher, logger) {
199
+ this.id = id;
200
+ this.name = name;
201
+ this.permissions = permissions;
202
+ this.dispatcher = dispatcher;
203
+ this.logger = logger;
204
+ }
205
+ /**
206
+ * Process an incoming message: parse command → check permission → dispatch → send result.
207
+ * Plain text (non-command) is routed to the agent handler automatically.
208
+ * Channels call this from their platform event handler.
209
+ */
210
+ async processMessage(msg) {
211
+ const parsed = parseCommand(msg.text);
212
+ const ctx = parsed ? {
213
+ userId: msg.userId,
214
+ command: parsed.name,
215
+ args: parsed.args,
216
+ channelId: msg.channelId,
217
+ message: msg
218
+ } : {
219
+ userId: msg.userId,
220
+ command: "agent",
221
+ args: [msg.text],
222
+ channelId: msg.channelId,
223
+ message: msg
224
+ };
225
+ let result;
226
+ try {
227
+ result = await this.dispatcher.dispatch(ctx);
228
+ } catch (err2) {
229
+ const botErr = err2 instanceof exports.BotError ? err2 : new exports.BotError("Unexpected error", { cause: err2 instanceof Error ? err2 : void 0 });
230
+ result = { ok: false, error: botErr };
231
+ }
232
+ if (result.ok) {
233
+ await this.send(msg.userId, result.value);
234
+ } else {
235
+ await this.send(msg.userId, {
236
+ type: "error",
237
+ message: this.formatErrorMessage(result.error)
238
+ });
239
+ }
240
+ }
241
+ formatErrorMessage(error) {
242
+ if (error instanceof exports.CommandNotFoundError) {
243
+ return `Unknown command. Type /help for available commands.`;
244
+ }
245
+ return error.message;
246
+ }
247
+ logEvent(event, meta = {}) {
248
+ this.logger.info(`[${this.name}] ${event}`, { channel: this.id, ...meta });
249
+ }
250
+ };
251
+ }
252
+ });
253
+
164
254
  // ../../node_modules/.pnpm/bottleneck@2.19.5/node_modules/bottleneck/lib/parser.js
165
255
  var require_parser = __commonJS({
166
256
  "../../node_modules/.pnpm/bottleneck@2.19.5/node_modules/bottleneck/lib/parser.js"(exports2) {
@@ -3011,9 +3101,9 @@ var require_lib = __commonJS({
3011
3101
  }
3012
3102
  });
3013
3103
 
3014
- // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js
3104
+ // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js
3015
3105
  var require_deps_node = __commonJS({
3016
- "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js"(exports2) {
3106
+ "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/deps.node.js"(exports2) {
3017
3107
  var __importDefault = exports2 && exports2.__importDefault || function(mod2) {
3018
3108
  return mod2 && mod2.__esModule ? mod2 : { "default": mod2 };
3019
3109
  };
@@ -3026,9 +3116,9 @@ var require_deps_node = __commonJS({
3026
3116
  }
3027
3117
  });
3028
3118
 
3029
- // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/mod.js
3119
+ // ../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/mod.js
3030
3120
  var require_mod = __commonJS({
3031
- "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.0/node_modules/@grammyjs/transformer-throttler/dist/mod.js"(exports2) {
3121
+ "../../node_modules/.pnpm/@grammyjs+transformer-throttler@1.2.1_grammy@1.40.1/node_modules/@grammyjs/transformer-throttler/dist/mod.js"(exports2) {
3032
3122
  Object.defineProperty(exports2, "__esModule", { value: true });
3033
3123
  exports2.bypassThrottler = exports2.apiThrottler = exports2.BottleneckStrategy = void 0;
3034
3124
  var deps_node_js_1 = require_deps_node();
@@ -3091,90 +3181,10 @@ var require_mod = __commonJS({
3091
3181
  }
3092
3182
  });
3093
3183
 
3094
- // src/models/command.ts
3095
- var command_exports = {};
3096
- __export(command_exports, {
3097
- ParsedCommandSchema: () => exports.ParsedCommandSchema,
3098
- parseCommand: () => parseCommand
3099
- });
3100
- function parseCommand(text) {
3101
- const trimmed = text.trim();
3102
- const match = /^[/!](\w+)(?:\s+(.*))?$/s.exec(trimmed);
3103
- if (!match) return null;
3104
- const name = (match[1] ?? "").toLowerCase();
3105
- const rest = (match[2] ?? "").trim();
3106
- const args = rest ? rest.split(/\s+/) : [];
3107
- return exports.ParsedCommandSchema.parse({ name, args, rawText: trimmed });
3108
- }
3109
- exports.ParsedCommandSchema = void 0;
3110
- var init_command = __esm({
3111
- "src/models/command.ts"() {
3112
- exports.ParsedCommandSchema = v4.z.object({
3113
- name: v4.z.string().min(1),
3114
- args: v4.z.array(v4.z.string()),
3115
- rawText: v4.z.string()
3116
- });
3117
- }
3118
- });
3119
-
3120
- // src/core/base-channel.ts
3121
- exports.BaseChannel = void 0;
3122
- var init_base_channel = __esm({
3123
- "src/core/base-channel.ts"() {
3124
- init_command();
3125
- init_errors();
3126
- exports.BaseChannel = class {
3127
- constructor(id, name, permissions, dispatcher, logger) {
3128
- this.id = id;
3129
- this.name = name;
3130
- this.permissions = permissions;
3131
- this.dispatcher = dispatcher;
3132
- this.logger = logger;
3133
- }
3134
- /**
3135
- * Process an incoming message: parse command → check permission → dispatch → send result.
3136
- * Channels call this from their platform event handler.
3137
- */
3138
- async processMessage(msg) {
3139
- const parsed = parseCommand(msg.text);
3140
- if (!parsed) return;
3141
- const ctx = {
3142
- userId: msg.userId,
3143
- command: parsed.name,
3144
- args: parsed.args,
3145
- channelId: msg.channelId,
3146
- message: msg
3147
- };
3148
- let result;
3149
- try {
3150
- result = await this.dispatcher.dispatch(ctx);
3151
- } catch (err2) {
3152
- const botErr = err2 instanceof exports.BotError ? err2 : new exports.BotError("Unexpected error", { cause: err2 instanceof Error ? err2 : void 0 });
3153
- result = { ok: false, error: botErr };
3154
- }
3155
- if (result.ok) {
3156
- await this.send(msg.userId, result.value);
3157
- } else {
3158
- await this.send(msg.userId, {
3159
- type: "error",
3160
- message: this.formatErrorMessage(result.error)
3161
- });
3162
- }
3163
- }
3164
- formatErrorMessage(error) {
3165
- if (error instanceof exports.CommandNotFoundError) {
3166
- return `Unknown command. Type /help for available commands.`;
3167
- }
3168
- return error.message;
3169
- }
3170
- logEvent(event, meta = {}) {
3171
- this.logger.info(`[${this.name}] ${event}`, { channel: this.id, ...meta });
3172
- }
3173
- };
3174
- }
3175
- });
3176
-
3177
3184
  // src/channels/telegram/formatter.ts
3185
+ function escapeHtml(text) {
3186
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3187
+ }
3178
3188
  function formatBytes(bytes) {
3179
3189
  if (bytes < 1024) return `${bytes}B`;
3180
3190
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
@@ -3244,6 +3254,51 @@ ${this.formatText(error.message)}`;
3244
3254
  const sizeStr = size !== void 0 && !isDir ? ` \\(${this.formatText(formatBytes(size))}\\)` : "";
3245
3255
  return `${icon} \`${this.escapeInline(name)}\`${sizeStr}`;
3246
3256
  }
3257
+ // ─── HTML formatting (preferred — avoids MarkdownV2 escaping hell) ────────
3258
+ /**
3259
+ * Escape HTML special chars, then convert basic markdown to HTML tags.
3260
+ * Handles: **bold**, `code`, ```code blocks```, _italic_
3261
+ */
3262
+ formatTextHtml(text) {
3263
+ let html = escapeHtml(text);
3264
+ html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang, code) => {
3265
+ const cls = lang ? ` class="language-${lang}"` : "";
3266
+ return `<pre><code${cls}>${code}</code></pre>`;
3267
+ });
3268
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
3269
+ html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
3270
+ html = html.replace(/__(.+?)__/g, "<b>$1</b>");
3271
+ html = html.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, "<i>$1</i>");
3272
+ html = html.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, "<i>$1</i>");
3273
+ return html;
3274
+ }
3275
+ /**
3276
+ * Format code block as HTML <pre>.
3277
+ */
3278
+ formatCodeHtml(code, language) {
3279
+ const escaped = escapeHtml(code);
3280
+ const cls = language ? ` class="language-${language}"` : "";
3281
+ const wrapped = `<pre><code${cls}>${escaped}</code></pre>`;
3282
+ if (wrapped.length <= TELEGRAM_MAX_MESSAGE_LENGTH) return wrapped;
3283
+ const overhead = 40 + (language?.length ?? 0);
3284
+ const maxCode = TELEGRAM_MAX_MESSAGE_LENGTH - overhead;
3285
+ return `<pre><code${cls}>${escaped.slice(0, maxCode)}
3286
+ \u2026(truncated)</code></pre>`;
3287
+ }
3288
+ /**
3289
+ * Format error message as HTML.
3290
+ */
3291
+ formatErrorHtml(message) {
3292
+ return `\u274C <b>Error:</b> ${escapeHtml(message)}`;
3293
+ }
3294
+ /**
3295
+ * Format file entry as HTML.
3296
+ */
3297
+ formatFileEntryHtml(name, isDir, size) {
3298
+ const icon = isDir ? "\u{1F4C1}" : "\u{1F4C4}";
3299
+ const sizeStr = size !== void 0 && !isDir ? ` (${formatBytes(size)})` : "";
3300
+ return `${icon} <code>${escapeHtml(name)}</code>${sizeStr}`;
3301
+ }
3247
3302
  // Escape text that appears inside inline code (backtick context)
3248
3303
  escapeInline(text) {
3249
3304
  return text.replace(/`/g, "'");
@@ -3282,8 +3337,18 @@ var init_channel = __esm({
3282
3337
  bot.on("message:text", async (ctx) => {
3283
3338
  const msg = this.normalizeContext(ctx);
3284
3339
  if (!msg) return;
3285
- for (const h3 of this.messageHandlers) await h3(msg);
3286
- await this.processMessage(msg);
3340
+ const typingInterval = setInterval(() => {
3341
+ ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => {
3342
+ });
3343
+ }, 4e3);
3344
+ await ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => {
3345
+ });
3346
+ try {
3347
+ for (const h3 of this.messageHandlers) await h3(msg);
3348
+ await this.processMessage(msg);
3349
+ } finally {
3350
+ clearInterval(typingInterval);
3351
+ }
3287
3352
  });
3288
3353
  bot.on("callback_query:data", async (ctx) => {
3289
3354
  await ctx.answerCallbackQuery();
@@ -3331,20 +3396,18 @@ var init_channel = __esm({
3331
3396
  try {
3332
3397
  switch (message.type) {
3333
3398
  case "text": {
3334
- const text = this.truncate(this.formatter.formatText(message.text));
3335
- await this.bot.api.sendMessage(chatId, text, { parse_mode: "MarkdownV2" });
3399
+ const text = this.truncate(this.formatter.formatTextHtml(message.text));
3400
+ await this.bot.api.sendMessage(chatId, text, { parse_mode: "HTML" });
3336
3401
  break;
3337
3402
  }
3338
3403
  case "code": {
3339
- const code = this.formatter.formatCode(message.code, message.language);
3340
- await this.bot.api.sendMessage(chatId, code, { parse_mode: "MarkdownV2" });
3404
+ const code = this.formatter.formatCodeHtml(message.code, message.language);
3405
+ await this.bot.api.sendMessage(chatId, code, { parse_mode: "HTML" });
3341
3406
  break;
3342
3407
  }
3343
3408
  case "error": {
3344
- const errText = this.formatter.formatError(
3345
- Object.assign(new Error(message.message), { code: "BOT_ERROR", context: {}, toLog: () => ({}) })
3346
- );
3347
- await this.bot.api.sendMessage(chatId, errText, { parse_mode: "MarkdownV2" });
3409
+ const errText = this.formatter.formatErrorHtml(message.message);
3410
+ await this.bot.api.sendMessage(chatId, errText, { parse_mode: "HTML" });
3348
3411
  break;
3349
3412
  }
3350
3413
  }
@@ -3371,7 +3434,7 @@ var init_channel = __esm({
3371
3434
  }
3372
3435
  truncate(text) {
3373
3436
  if (text.length <= this.maxLength) return text;
3374
- return text.slice(0, this.maxLength - 20) + "\n\\.\\.\\. *\\(truncated\\)*";
3437
+ return text.slice(0, this.maxLength - 20) + "\n... <i>(truncated)</i>";
3375
3438
  }
3376
3439
  };
3377
3440
  }
@@ -16580,21 +16643,34 @@ var PermissionManager = class {
16580
16643
  return this.identityMap;
16581
16644
  }
16582
16645
  };
16583
-
16584
- // src/core/logger.ts
16585
- var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
16646
+ var CONSOLA_LEVELS = {
16647
+ debug: 4,
16648
+ info: 3,
16649
+ warn: 2,
16650
+ error: 1
16651
+ };
16586
16652
  function createLogger(level = "info") {
16587
- const minLevel = LEVELS[level];
16588
- function log(lvl, msg, meta = {}) {
16589
- if (LEVELS[lvl] < minLevel) return;
16590
- const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, msg, ...meta };
16591
- process.stderr.write(JSON.stringify(entry) + "\n");
16592
- }
16653
+ const consola$1 = consola.createConsola({
16654
+ level: CONSOLA_LEVELS[level],
16655
+ formatOptions: {
16656
+ date: true,
16657
+ colors: true
16658
+ }
16659
+ }).withTag("bot");
16593
16660
  return {
16594
- debug: (msg, meta) => log("debug", msg, meta ?? {}),
16595
- info: (msg, meta) => log("info", msg, meta ?? {}),
16596
- warn: (msg, meta) => log("warn", msg, meta ?? {}),
16597
- error: (msg, meta) => log("error", msg, meta ?? {})
16661
+ consola: consola$1,
16662
+ debug(msg, meta) {
16663
+ meta ? consola$1.debug(msg, meta) : consola$1.debug(msg);
16664
+ },
16665
+ info(msg, meta) {
16666
+ meta ? consola$1.info(msg, meta) : consola$1.info(msg);
16667
+ },
16668
+ warn(msg, meta) {
16669
+ meta ? consola$1.warn(msg, meta) : consola$1.warn(msg);
16670
+ },
16671
+ error(msg, meta) {
16672
+ meta ? consola$1.error(msg, meta) : consola$1.error(msg);
16673
+ }
16598
16674
  };
16599
16675
  }
16600
16676
 
@@ -16701,7 +16777,21 @@ var AgentHandler = class extends BaseHandler {
16701
16777
  const text = result.text.length > this.maxOutput ? result.text.slice(0, this.maxOutput) + "\n...(truncated)" : result.text;
16702
16778
  return ok({ type: "text", text });
16703
16779
  } catch (e3) {
16704
- return err(new exports.CMDOPError("Agent execution failed", e3 instanceof Error ? e3 : void 0));
16780
+ const errMsg = e3 instanceof Error ? e3.message : String(e3);
16781
+ this.logger.error("Agent execution failed", { error: errMsg });
16782
+ if (errMsg.includes("session_id") || errMsg.includes("No active session")) {
16783
+ return err(new exports.CMDOPError("Machine is offline or CMDOP agent is not running.\nhttps://cmdop.com/downloads/"));
16784
+ }
16785
+ if (errMsg.includes("context canceled") || errMsg.includes("CANCELLED")) {
16786
+ return err(new exports.CMDOPError("Request was interrupted. Please try again."));
16787
+ }
16788
+ if (errMsg.includes("DEADLINE_EXCEEDED") || errMsg.includes("timeout")) {
16789
+ return err(new exports.CMDOPError("Request timed out. The task may be too complex \u2014 try a simpler prompt."));
16790
+ }
16791
+ if (errMsg.includes("UNAVAILABLE") || errMsg.includes("Connection refused")) {
16792
+ return err(new exports.CMDOPError("Server unavailable. Check your connection and try again."));
16793
+ }
16794
+ return err(new exports.CMDOPError(`Agent error: ${errMsg}`, e3 instanceof Error ? e3 : void 0));
16705
16795
  }
16706
16796
  }
16707
16797
  };
@@ -16822,6 +16912,63 @@ function loadSettings(env = process.env) {
16822
16912
  return result.data;
16823
16913
  }
16824
16914
 
16915
+ // src/channels/demo/channel.ts
16916
+ init_base_channel();
16917
+ var DemoChannel = class extends exports.BaseChannel {
16918
+ messageHandlers = [];
16919
+ onOutput;
16920
+ constructor(permissions, dispatcher, logger, options2 = {}) {
16921
+ super("demo", "Demo", permissions, dispatcher, logger);
16922
+ this.onOutput = options2.onOutput ?? ((text) => process.stdout.write(text + "\n"));
16923
+ }
16924
+ async start() {
16925
+ this.logEvent("started");
16926
+ }
16927
+ async stop() {
16928
+ this.messageHandlers = [];
16929
+ this.logEvent("stopped");
16930
+ }
16931
+ send(_userId, message) {
16932
+ const text = formatOutgoing(message);
16933
+ this.onOutput(text, message);
16934
+ return Promise.resolve();
16935
+ }
16936
+ onMessage(handler) {
16937
+ this.messageHandlers.push(handler);
16938
+ }
16939
+ /**
16940
+ * Inject a message as if it came from a real platform user.
16941
+ * Primarily used in tests and CLI demos.
16942
+ */
16943
+ async injectMessage(input) {
16944
+ const msg = {
16945
+ id: `demo-${Date.now()}`,
16946
+ userId: input.userId ?? "demo-user",
16947
+ channelId: this.id,
16948
+ text: input.text,
16949
+ timestamp: /* @__PURE__ */ new Date(),
16950
+ attachments: []
16951
+ };
16952
+ for (const handler of this.messageHandlers) {
16953
+ await handler(msg);
16954
+ }
16955
+ await this.processMessage(msg);
16956
+ }
16957
+ };
16958
+ function formatOutgoing(message) {
16959
+ switch (message.type) {
16960
+ case "text":
16961
+ return message.text;
16962
+ case "code":
16963
+ return `\`\`\`${message.language ?? ""}
16964
+ ${message.code}
16965
+ \`\`\``;
16966
+ case "error":
16967
+ return `\u274C ${message.message}${message.hint ? `
16968
+ \u{1F4A1} ${message.hint}` : ""}`;
16969
+ }
16970
+ }
16971
+
16825
16972
  // src/hub.ts
16826
16973
  var IntegrationHub = class _IntegrationHub {
16827
16974
  constructor(_client, _logger, _settings, permissions, dispatcher, channelStartMode) {
@@ -16847,11 +16994,12 @@ var IntegrationHub = class _IntegrationHub {
16847
16994
  Object.assign(settings2, options2.settings ?? {});
16848
16995
  if (options2.defaultMachine) settings2.defaultMachine = options2.defaultMachine;
16849
16996
  const logger = options2.logger ?? createLogger(settings2.logLevel);
16850
- const client = options2.apiKey ? await node.CMDOPClient.remote(options2.apiKey) : await node.CMDOPClient.local();
16997
+ const client = options2.apiKey ? node.CMDOPClient.remote(options2.apiKey) : node.CMDOPClient.local();
16851
16998
  const store = options2.permissionStore ?? new InMemoryPermissionStore();
16852
16999
  const identityMap = new IdentityMap();
16853
17000
  const permissions = new PermissionManager(store, {
16854
17001
  adminUsers: [...options2.adminUsers ?? [], ...settings2.allowedUsers],
17002
+ defaultLevel: options2.defaultPermission,
16855
17003
  identityMap
16856
17004
  });
16857
17005
  const dispatcher = new MessageDispatcher(permissions, logger);
@@ -16898,6 +17046,15 @@ var IntegrationHub = class _IntegrationHub {
16898
17046
  const channel = new SlackChannel2(options2, this._permissions, this._dispatcher, this._logger);
16899
17047
  return this.registerChannel(channel);
16900
17048
  }
17049
+ /**
17050
+ * Create and register a DemoChannel for CLI testing.
17051
+ * No external dependencies — messages are injected via the returned channel's `injectMessage()`.
17052
+ */
17053
+ addDemo(options2) {
17054
+ const channel = new DemoChannel(this._permissions, this._dispatcher, this._logger, options2);
17055
+ this.registerChannel(channel);
17056
+ return channel;
17057
+ }
16901
17058
  registerHandler(handler) {
16902
17059
  this._dispatcher.register(handler);
16903
17060
  return this;
@@ -16906,6 +17063,17 @@ var IntegrationHub = class _IntegrationHub {
16906
17063
  async start() {
16907
17064
  if (this.started) return;
16908
17065
  this.started = true;
17066
+ if (this._settings.defaultMachine) {
17067
+ try {
17068
+ await this._client.setMachine(this._settings.defaultMachine);
17069
+ this._logger.info("Machine routing initialized", { machine: this._settings.defaultMachine });
17070
+ } catch (err2) {
17071
+ this._logger.error("Failed to initialize machine routing", {
17072
+ machine: this._settings.defaultMachine,
17073
+ err: err2 instanceof Error ? err2.message : String(err2)
17074
+ });
17075
+ }
17076
+ }
16909
17077
  if (this.channelStartMode === "strict") {
16910
17078
  await Promise.all([...this.channels.values()].map(async (ch) => {
16911
17079
  await ch.start();
@@ -17168,63 +17336,6 @@ var SlackStream = class _SlackStream {
17168
17336
  }
17169
17337
  };
17170
17338
 
17171
- // src/channels/demo/channel.ts
17172
- init_base_channel();
17173
- var DemoChannel = class extends exports.BaseChannel {
17174
- messageHandlers = [];
17175
- onOutput;
17176
- constructor(permissions, dispatcher, logger, options2 = {}) {
17177
- super("demo", "Demo", permissions, dispatcher, logger);
17178
- this.onOutput = options2.onOutput ?? ((text) => process.stdout.write(text + "\n"));
17179
- }
17180
- async start() {
17181
- this.logEvent("started");
17182
- }
17183
- async stop() {
17184
- this.messageHandlers = [];
17185
- this.logEvent("stopped");
17186
- }
17187
- send(_userId, message) {
17188
- const text = formatOutgoing(message);
17189
- this.onOutput(text, message);
17190
- return Promise.resolve();
17191
- }
17192
- onMessage(handler) {
17193
- this.messageHandlers.push(handler);
17194
- }
17195
- /**
17196
- * Inject a message as if it came from a real platform user.
17197
- * Primarily used in tests and CLI demos.
17198
- */
17199
- async injectMessage(input) {
17200
- const msg = {
17201
- id: `demo-${Date.now()}`,
17202
- userId: input.userId ?? "demo-user",
17203
- channelId: this.id,
17204
- text: input.text,
17205
- timestamp: /* @__PURE__ */ new Date(),
17206
- attachments: []
17207
- };
17208
- for (const handler of this.messageHandlers) {
17209
- await handler(msg);
17210
- }
17211
- await this.processMessage(msg);
17212
- }
17213
- };
17214
- function formatOutgoing(message) {
17215
- switch (message.type) {
17216
- case "text":
17217
- return message.text;
17218
- case "code":
17219
- return `\`\`\`${message.language ?? ""}
17220
- ${message.code}
17221
- \`\`\``;
17222
- case "error":
17223
- return `\u274C ${message.message}${message.hint ? `
17224
- \u{1F4A1} ${message.hint}` : ""}`;
17225
- }
17226
- }
17227
-
17228
17339
  // src/channels/telegram/index.ts
17229
17340
  init_channel();
17230
17341
  init_formatter();