@inceptionstack/roundhouse 0.4.4 → 0.4.5

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
@@ -178,7 +178,7 @@ Without a config file, defaults are used with env vars (`TELEGRAM_BOT_TOKEN`, `B
178
178
 
179
179
  | Field | Description |
180
180
  |-------|-------------|
181
- | `agent.type` | Agent backend: `"pi"` (more coming) |
181
+ | `agent.type` | Agent backend: `"pi"`, `"kiro"` |
182
182
  | `agent.cwd` | Working directory for the agent |
183
183
  | `agent.sessionDir` | Override session storage path |
184
184
  | `chat.botUsername` | Bot display name for Chat SDK |
@@ -492,7 +492,7 @@ No other changes needed — the gateway's unified handler covers all platforms.
492
492
  | `src/agents/base-adapter.ts` | Abstract base class — adapter interface contract |
493
493
  | `src/agents/registry.ts` | Agent type → factory registry |
494
494
  | `src/config.ts` | Shared config loading, defaults, env overrides |
495
- | `test/` | Unit tests (vitest, 75 passing) |
495
+ | `test/` | Unit + integration tests (vitest, 311 passing) |
496
496
 
497
497
  ## CI/CD
498
498
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inceptionstack/roundhouse",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "description": "Multi-platform chat gateway that routes messages through a configured AI agent",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { Type } from "typebox";
2
+ import { Type } from "@sinclair/typebox";
3
3
 
4
4
  export default function (pi: ExtensionAPI) {
5
5
  pi.registerTool({
@@ -46,6 +46,13 @@ export default function (pi: ExtensionAPI) {
46
46
  }),
47
47
  signal: controller.signal,
48
48
  });
49
+ } catch (err: any) {
50
+ clearTimeout(timeout);
51
+ const msg = err.name === "AbortError" ? "Request timed out (30s)" : `Network error: ${err.message}`;
52
+ return {
53
+ content: [{ type: "text", text: msg }],
54
+ details: { query: params.query, error: err.name },
55
+ };
49
56
  } finally {
50
57
  clearTimeout(timeout);
51
58
  }
@@ -13,6 +13,7 @@
13
13
  import { homedir } from "node:os";
14
14
  import { resolve } from "node:path";
15
15
  import type { AgentAdapterFactory, AgentMessage, AgentResponse, AgentStreamEvent, AdapterInfo } from "../../types.js";
16
+ import { ROUNDHOUSE_VERSION } from "../../config.js";
16
17
  import { BaseAdapter } from "../base-adapter.js";
17
18
  import { spawnKiroCli, shutdownProcess, getKiroCliVersion, type AcpProcess, type InitializeResult, type SessionNewResult } from "./acp/index.js";
18
19
  import { SessionStore, type SessionEntry } from "./session.js";
@@ -188,7 +189,7 @@ class KiroAdapter extends BaseAdapter {
188
189
 
189
190
  await this.mainProcess.client.call<InitializeResult>("initialize", {
190
191
  protocolVersion: "1.0",
191
- clientInfo: { name: "roundhouse", version: "0.4.3" },
192
+ clientInfo: { name: "roundhouse", version: ROUNDHOUSE_VERSION },
192
193
  });
193
194
 
194
195
  if (!this.reaperInterval) {
package/src/cli/cli.ts CHANGED
@@ -72,7 +72,12 @@ async function cmdStart() {
72
72
  return;
73
73
  }
74
74
 
75
- // No systemd service — fall back to foreground
75
+ // No systemd service — fall back to foreground. Check config before launching.
76
+ if (!(await fileExists(CONFIG_PATH))) {
77
+ console.error("No config found. Run 'roundhouse setup --telegram' first.");
78
+ process.exit(1);
79
+ }
80
+
76
81
  console.log("No systemd service found. Running in foreground (use Ctrl+C to stop)...");
77
82
  if (process.platform !== "darwin") {
78
83
  console.log(" Tip: run 'roundhouse install' to set up the systemd daemon.\n");
@@ -83,6 +88,12 @@ async function cmdStart() {
83
88
  }
84
89
 
85
90
  async function cmdRun() {
91
+ // Guard: check config exists before launching gateway
92
+ if (!(await fileExists(CONFIG_PATH))) {
93
+ console.error("No config found. Run 'roundhouse setup --telegram' first.");
94
+ process.exit(1);
95
+ }
96
+
86
97
  process.env.ROUNDHOUSE_CONFIG = CONFIG_PATH;
87
98
 
88
99
  // Load .env file so secrets (TELEGRAM_BOT_TOKEN, etc.) are available
package/src/cli/setup.ts CHANGED
@@ -443,7 +443,7 @@ async function stepValidateToken(opts: SetupOptions): Promise<BotInfo> {
443
443
  }
444
444
 
445
445
  async function stepStopGateway(): Promise<void> {
446
- step("", "Checking for running gateway...");
446
+ step("", "Checking for running gateway...");
447
447
 
448
448
  if (platform() !== "linux") {
449
449
  ok("Not Linux — skipping service check");
@@ -464,7 +464,7 @@ async function stepStopGateway(): Promise<void> {
464
464
  }
465
465
 
466
466
  async function stepInstallPackages(opts: SetupOptions, agent: AgentDefinition): Promise<void> {
467
- step("", "Installing packages...");
467
+ step("", "Installing packages...");
468
468
 
469
469
  // Roundhouse
470
470
  const rhInstalled = whichSync("roundhouse");
@@ -596,12 +596,12 @@ async function stepInstallPackages(opts: SetupOptions, agent: AgentDefinition):
596
596
 
597
597
  async function stepStoreSecrets(opts: SetupOptions, botInfo: BotInfo): Promise<void> {
598
598
  if (!opts.psst) {
599
- step("", "Storing secrets...");
599
+ step("", "Storing secrets...");
600
600
  ok("Skipped (default — use --with-psst to enable)");
601
601
  return;
602
602
  }
603
603
 
604
- step("", "Storing secrets in psst...");
604
+ step("", "Storing secrets in psst...");
605
605
 
606
606
  const secrets: [string, string][] = [
607
607
  ["TELEGRAM_BOT_TOKEN", opts.botToken],
@@ -640,7 +640,7 @@ async function stepStoreSecrets(opts: SetupOptions, botInfo: BotInfo): Promise<v
640
640
  // ── Bundle install ──────────────────────────────────────────────────
641
641
 
642
642
  async function stepInstallBundle(opts: SetupOptions): Promise<void> {
643
- step("⑥b", "Installing bundle (skills + CLI tools)...");
643
+ step("⑥", "Installing bundle (skills + CLI tools)...");
644
644
 
645
645
  const bundleLog: ProvisionLog = {
646
646
  info: (msg) => log(` ${msg}`),
@@ -657,7 +657,7 @@ async function stepConfigure(
657
657
  pairResult: PairResult | null,
658
658
  agent: AgentDefinition,
659
659
  ): Promise<void> {
660
- step("", "Configuring...");
660
+ step("", "Configuring...");
661
661
 
662
662
  await mkdir(ROUNDHOUSE_DIR, { recursive: true });
663
663
 
@@ -766,7 +766,7 @@ async function stepConfigure(
766
766
  }
767
767
 
768
768
  async function stepPair(opts: SetupOptions, botInfo: BotInfo): Promise<PairResult | null> {
769
- step("", "Pairing with Telegram...");
769
+ step("", "Pairing with Telegram...");
770
770
 
771
771
  // Skip if chat IDs already known
772
772
  if (opts.notifyChatIds.length > 0) {
@@ -822,13 +822,13 @@ async function stepPair(opts: SetupOptions, botInfo: BotInfo): Promise<PairResul
822
822
  }
823
823
 
824
824
  async function stepRegisterCommands(opts: SetupOptions): Promise<void> {
825
- step("", "Registering bot commands...");
825
+ step("", "Registering bot commands...");
826
826
  await registerBotCommands(opts.botToken);
827
827
  ok(`${BOT_COMMANDS.length} commands registered with Telegram`);
828
828
  }
829
829
 
830
830
  async function stepInstallSystemd(opts: SetupOptions): Promise<void> {
831
- step("", "Installing systemd service...");
831
+ step("⑩b", "Installing systemd service...");
832
832
 
833
833
  if (!opts.systemd) {
834
834
  ok("Skipped (--no-systemd)");
@@ -873,7 +873,7 @@ async function stepInstallSystemd(opts: SetupOptions): Promise<void> {
873
873
  }
874
874
 
875
875
  async function stepPostflight(): Promise<void> {
876
- step("", "Postflight checks...");
876
+ step("", "Postflight checks...");
877
877
 
878
878
  if (platform() === "linux") {
879
879
  if (isServiceActive()) {
@@ -894,6 +894,11 @@ async function stepPostflight(): Promise<void> {
894
894
  if (!whichSync("ffmpeg")) {
895
895
  warn("ffmpeg not found (install for voice support)");
896
896
  }
897
+
898
+ if (!process.env.TAVILY_API_KEY) {
899
+ warn("TAVILY_API_KEY not set — web search extension won't work");
900
+ log(" Get a free key at https://tavily.com and add to ~/.roundhouse/.env");
901
+ }
897
902
  }
898
903
 
899
904
  // ── BotFather Guide ──────────────────────────────────
@@ -951,11 +956,11 @@ async function runInteractiveTelegramSetup(opts: SetupOptions): Promise<void> {
951
956
  // Step 5: Install packages
952
957
  await stepInstallPackages(opts, agent);
953
958
 
954
- // Step 5b: Install bundle (skills + CLI tools)
959
+ // Step 6: Install bundle (skills + CLI tools)
955
960
  await stepInstallBundle(opts);
956
961
 
957
- // Step 6: Pair via Telegram
958
- step("", "Pairing with Telegram...");
962
+ // Step 7: Pair via Telegram
963
+ step("", "Pairing with Telegram...");
959
964
  const nonce = createPairingNonce();
960
965
  const pairingLink = createPairingLink(botInfo.username, nonce);
961
966
  log(`\n Open this link to pair:\n`);
@@ -964,6 +969,16 @@ async function runInteractiveTelegramSetup(opts: SetupOptions): Promise<void> {
964
969
  log(` Or send /start ${nonce} to @${botInfo.username}`);
965
970
  log("");
966
971
 
972
+ // Auto-open the pairing link on macOS
973
+ if (process.platform === "darwin") {
974
+ try {
975
+ execFileSync("open", [pairingLink], { stdio: "ignore" });
976
+ log(" (Opened in Telegram — switch to the app to complete pairing)");
977
+ } catch { /* ignore if open fails */ }
978
+ }
979
+
980
+ log(" Waiting for you to tap the link in Telegram...");
981
+
967
982
  const pairResult = await pairTelegram(
968
983
  opts.botToken, botInfo.username, opts.users,
969
984
  300_000, log, { nonce, showLink: false },
@@ -977,17 +992,17 @@ async function runInteractiveTelegramSetup(opts: SetupOptions): Promise<void> {
977
992
  }
978
993
  }
979
994
 
980
- // Step 7: Store secrets
995
+ // Step 8: Store secrets
981
996
  await stepStoreSecrets(opts, botInfo);
982
997
 
983
- // Step 8: Write config
998
+ // Step 9: Write config
984
999
  await stepConfigure(opts, botInfo, pairResult, agent);
985
1000
 
986
- // Step 9: Register commands + install service
1001
+ // Step 10: Register commands + install service
987
1002
  await stepRegisterCommands(opts);
988
1003
  await stepInstallSystemd(opts);
989
1004
 
990
- // Step 10: Verify
1005
+ // Step 11: Verify
991
1006
  await stepPostflight();
992
1007
 
993
1008
  // Done!
package/src/config.ts CHANGED
@@ -8,8 +8,21 @@
8
8
  import { homedir } from "node:os";
9
9
  import { resolve } from "node:path";
10
10
  import { readFile, access } from "node:fs/promises";
11
+ import { readFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { fileURLToPath } from "node:url";
11
14
  import type { GatewayConfig } from "./types";
12
15
 
16
+ // ── Version ──────────────────────────────────────────
17
+
18
+ const __configDir = dirname(fileURLToPath(import.meta.url));
19
+
20
+ /** Roundhouse package version (read from package.json at startup) */
21
+ export const ROUNDHOUSE_VERSION: string = (() => {
22
+ try { return JSON.parse(readFileSync(join(__configDir, "..", "package.json"), "utf8")).version; }
23
+ catch { return "unknown"; }
24
+ })();
25
+
13
26
  // ── Path constants ───────────────────────────────────
14
27
 
15
28
  /** New canonical config root */
package/src/gateway.ts CHANGED
@@ -13,7 +13,7 @@ import { isTelegramThread, postTelegramHtml, handleTelegramHtmlStream } from "./
13
13
  import { SttService, enrichAttachmentsWithTranscripts, DEFAULT_STT_CONFIG } from "./voice/stt-service";
14
14
  import { sendTelegramToMany } from "./notify/telegram";
15
15
  import { runDoctor, formatDoctorTelegram, createDoctorContext } from "./cli/doctor/runner";
16
- import { ROUNDHOUSE_DIR } from "./config";
16
+ import { ROUNDHOUSE_DIR, ROUNDHOUSE_VERSION } from "./config";
17
17
  import { CronSchedulerService } from "./cron/scheduler";
18
18
  import { isBuiltinJob } from "./cron/helpers";
19
19
  import { formatSchedule, formatRunCounts, jobEnabledIcon } from "./cron/format";
@@ -65,10 +65,6 @@ import { join, dirname, basename } from "node:path";
65
65
  import { fileURLToPath } from "node:url";
66
66
 
67
67
  const __gatewayDir = dirname(fileURLToPath(import.meta.url));
68
- const ROUNDHOUSE_VERSION: string = (() => {
69
- try { return JSON.parse(readFileSync(join(__gatewayDir, "..", "package.json"), "utf8")).version; }
70
- catch { return "unknown"; }
71
- })();
72
68
 
73
69
  function telegramChatIdFromThreadId(threadId: unknown): number | null {
74
70
  if (typeof threadId !== "string") return null;