@inceptionstack/roundhouse 0.5.20 → 0.5.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inceptionstack/roundhouse",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
4
4
  "type": "module",
5
5
  "description": "Multi-platform chat gateway that routes messages through a configured AI agent",
6
6
  "license": "MIT",
@@ -814,34 +814,10 @@ export class Gateway {
814
814
 
815
815
  // Only fire for the primary (first) chat
816
816
  const primaryChatId = chatIds[0];
817
- const threadId = `telegram:${primaryChatId}`;
818
817
  const agentThreadId = "main";
819
818
 
820
- // Create a synthetic Telegram-compatible thread so streaming, HTML conversion,
821
- // message splitting, and progressive edits all work identically to real chat threads.
822
- const token = process.env.TELEGRAM_BOT_TOKEN;
823
- const syntheticThread = {
824
- id: threadId,
825
- adapter: {
826
- telegramFetch: async (method: string, payload: Record<string, unknown>) => {
827
- if (!token) return null;
828
- const res = await fetch(`https://api.telegram.org/bot${token}/${method}`, {
829
- method: "POST",
830
- headers: { "Content-Type": "application/json" },
831
- body: JSON.stringify({ chat_id: primaryChatId, ...payload }),
832
- signal: AbortSignal.timeout(30_000),
833
- });
834
- if (!res.ok) return null;
835
- const json = await res.json() as { result?: unknown };
836
- return json.result ?? null;
837
- },
838
- },
839
- post: async (content: string | { markdown: string }) => {
840
- const text = typeof content === "string" ? content : content.markdown;
841
- await this.transport.notify([primaryChatId], text);
842
- },
843
- startTyping: async () => {},
844
- };
819
+ // Create a thread via the transport adapter no transport-specific logic in gateway
820
+ const syntheticThread = this.transport.createThread(primaryChatId);
845
821
 
846
822
  const bootPrompt = "You just came online after a restart. Say a brief hello in-character (1–2 sentences max). Check your workspace for any pending tasks.";
847
823
 
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, stat } from "node:fs/promises";
2
+ import { mkdir, stat, readFile } from "node:fs/promises";
3
3
  import type { ChildProcess } from "node:child_process";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
@@ -168,10 +168,29 @@ export class SubAgentOrchestratorImpl implements SubAgentOrchestrator, SubAgentL
168
168
 
169
169
  const outcome = current.requestedOutcome
170
170
  ? this.terminationHandler.terminalStatusFor(current)
171
- : (exitCode === 0 ? "complete" : "failed");
171
+ : await this.inferOutcome(runId, exitCode);
172
172
 
173
173
  await this.finalizer.finalizeRun(runId, outcome, { exitCode: exitCode ?? undefined });
174
174
  }
175
+ /**
176
+ * Determine terminal status for a child exit.
177
+ * If exit code is non-zero but the agent produced meaningful stdout output,
178
+ * treat as "complete" — the work succeeded but the process had a teardown error
179
+ * (e.g. pi-hard-no stale context exception on session close).
180
+ */
181
+ private async inferOutcome(runId: string, exitCode: number | null): Promise<"complete" | "failed"> {
182
+ if (exitCode === 0) return "complete";
183
+ if (exitCode === null) return "failed"; // signal-killed, not a teardown error
184
+ try {
185
+ const stdoutPath = join(this.store.getRunDir(runId), "stdout.log");
186
+ const content = await readFile(stdoutPath, "utf-8");
187
+ // If stdout has substantial content (>50 chars), the agent did its job
188
+ if (content.trim().length > 50) return "complete";
189
+ } catch {
190
+ // No stdout file or can't read — fall through to failed
191
+ }
192
+ return "failed";
193
+ }
175
194
  }
176
195
 
177
196
  async function assertDirectoryExists(path: string): Promise<void> {
@@ -50,6 +50,33 @@ export class TelegramAdapter implements TransportAdapter {
50
50
  return isTelegramThread(thread as any);
51
51
  }
52
52
 
53
+ createThread(chatId: number): ChatThread {
54
+ const token = process.env.TELEGRAM_BOT_TOKEN;
55
+ const threadId = `telegram:${chatId}`;
56
+ const telegramFetch = async (method: string, payload: Record<string, unknown>) => {
57
+ if (!token) return null;
58
+ const res = await fetch(`https://api.telegram.org/bot${token}/${method}`, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify({ chat_id: chatId, ...payload }),
62
+ signal: AbortSignal.timeout(30_000),
63
+ });
64
+ if (!res.ok) return null;
65
+ const json = await res.json() as { result?: unknown };
66
+ return json.result ?? null;
67
+ };
68
+ const thread: ChatThread = {
69
+ id: threadId,
70
+ adapter: { telegramFetch },
71
+ post: async (content: string | { markdown: string }) => {
72
+ const text = typeof content === "string" ? content : content.markdown;
73
+ await postTelegramHtml(thread as any, text);
74
+ },
75
+ startTyping: async () => {},
76
+ };
77
+ return thread;
78
+ }
79
+
53
80
  async notify(chatIds: number[], text: string, options?: { parseMode?: string }): Promise<void> {
54
81
  if (!process.env.TELEGRAM_BOT_TOKEN) {
55
82
  console.warn("[roundhouse] TELEGRAM_BOT_TOKEN not set — skipping notification");
@@ -56,6 +56,14 @@ export interface TransportAdapter {
56
56
  /** Send notifications to configured recipients */
57
57
  notify(chatIds: number[], text: string, options?: { parseMode?: string }): Promise<void>;
58
58
 
59
+ /**
60
+ * Create a thread object for a given chat ID.
61
+ * Used by gateway for synthetic turns (boot turn, cron notifications)
62
+ * where no incoming message triggered the interaction.
63
+ * Returns a thread compatible with the streaming system.
64
+ */
65
+ createThread(chatId: number): ChatThread;
66
+
59
67
  /**
60
68
  * Check if a pairing flow is pending.
61
69
  * Gateway uses this to decide whether to attempt pairing on incoming messages.