@inceptionstack/roundhouse 0.5.13 → 0.5.14
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/CHANGELOG.md +235 -0
- package/package.json +2 -1
- package/src/gateway/commands.ts +21 -15
- package/src/gateway/gateway.ts +28 -36
- package/src/gateway/whats-new.ts +59 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@inceptionstack/roundhouse` are documented here.
|
|
4
|
+
|
|
5
|
+
## [0.5.10] — 2026-05-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Cron notifications actually delivered** — replaced IPC socket loopback with direct callback injection from gateway
|
|
9
|
+
- `shouldNotify` onlyOn filter now applies to all notification routes (was only explicit Telegram)
|
|
10
|
+
|
|
11
|
+
## [0.5.9] — 2026-05-09
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Cron IPC broadcast** — cron jobs without explicit `notify.telegram.chatIds` now broadcast results via IPC socket to all active transports
|
|
15
|
+
- Expanded tools.md: mcporter, playwright-cli, codex exec, AWS CLI, memory management docs
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Completed one-shot jobs hidden from `/crons` and `roundhouse cron list` (use `--all` to see them)
|
|
19
|
+
- playwright-cli command names corrected (requests, cookie-list, eval)
|
|
20
|
+
- mcporter examples use actual configured server names (aws-mcp, aws-documentation)
|
|
21
|
+
|
|
22
|
+
## [0.5.8] — 2026-05-09
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **IPC unix socket** — `roundhouse message "text"` sends messages to active transports via `~/.roundhouse/gateway.sock`
|
|
26
|
+
- **Session routing** — `--session main` targets primary chat, numeric ID targets specific chat
|
|
27
|
+
- **Provision tools.md** — bundled tools.md auto-copied to `~/.roundhouse/` on setup/update (never overwrites)
|
|
28
|
+
- 13 IPC integration tests
|
|
29
|
+
- IPC barrel exports for consistent import paths
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
- Socket mode 0600 (owner-only access)
|
|
33
|
+
- Stale socket cleanup with liveness probe (500ms timeout)
|
|
34
|
+
- 64KB payload guard, 5s request timeout
|
|
35
|
+
|
|
36
|
+
## [0.5.7] — 2026-05-09
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **`<tools>` section injection** — bundled `tools.md` injected into every agent prompt so agent knows it can schedule cron jobs (PR #50)
|
|
40
|
+
- **Extension updates in `/update`** — pi-hard-no and pi-branch-enforcer updated alongside roundhouse (PR #51)
|
|
41
|
+
- User-customizable `~/.roundhouse/tools.md` overrides bundled tools documentation
|
|
42
|
+
- Per-extension progress messages shown to user during update
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- Tools injection runs after STT enrichment so voice-only messages also get tools context
|
|
46
|
+
- XML tag sanitization prevents prompt injection from user-customized tools.md
|
|
47
|
+
- `/update` version-check failure returns distinct error (not misleading "already-latest")
|
|
48
|
+
- Error messages truncated to 200 chars for Telegram safety
|
|
49
|
+
|
|
50
|
+
## [0.5.6] — 2026-05-09
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- **STT agent prompt injection** — when whisper/ffmpeg are missing, injects install prompt into agent turn instead of complex auto-install chains (PR #45)
|
|
54
|
+
- **getMissingDeps()** on whisper provider + SttService — reports what’s missing so gateway can act
|
|
55
|
+
- **Duration-exceeded “skipped” status** — audio too long gets status “skipped” (not “failed”), preventing false install prompts
|
|
56
|
+
- **Text+audio handling** — when user sends caption + voice, install prompt appends to existing text
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
- **Removed `autoInstall` config** — no longer needed; agent handles installation autonomously
|
|
60
|
+
- **Simplified whisper.ts** — removed installWhisperWithPip, installWhisperWithUv, ensureFfmpeg (~150 lines deleted)
|
|
61
|
+
- **User notification** — “Asking agent to install...” (accurate) replaces “Setting up...” (misleading)
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **systemd: TimeoutStopSec=15 + KillMode=mixed** (PR #43) — hung whisper subprocesses no longer block shutdown for 90s
|
|
65
|
+
- **Stale autoInstall references** in CLI setup wizard, doctor checks, and usability report (PR #46)
|
|
66
|
+
|
|
67
|
+
## [0.5.5] — 2026-05-09
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **TransportAdapter interface** — enrichPrompt, postMessage, registerCommands, ownsThread, notify, isPairingPending, handlePairing (PR #37)
|
|
71
|
+
- **TelegramAdapter** — implements TransportAdapter, pairing logic moved from gateway (PR #39)
|
|
72
|
+
- **Bundled skills** — roundhouse-cron + pr-merge-discipline ship with package
|
|
73
|
+
- **STT typing indicator** — Telegram shows “typing” during voice transcription (PR #42)
|
|
74
|
+
- **PairingResult widened** to `string | number` for future Slack/Discord support (PR #41)
|
|
75
|
+
- **Agent chooser** — interactive numbered menu in setup wizard (PR #37)
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
- **Gateway imports** — 9 broken `./` → `../` paths after module reorg (PR #40)
|
|
79
|
+
- **Naming conventions** — adapter.ts → telegram-adapter.ts, TelegramTransportAdapter → TelegramAdapter (PR #38)
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
- **Module reorganization** (PR #36) — gateway/, transports/telegram/, cli/setup/, provisioning/
|
|
83
|
+
- 376 tests passing
|
|
84
|
+
|
|
85
|
+
## [0.5.0–0.5.4] — 2026-05-08
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
- **Shared "main" session** — all direct messages route to a single `main` agent thread
|
|
89
|
+
- Telegram DMs, CLI TUI, CLI agent, future Slack/Discord all share one conversation
|
|
90
|
+
- Sessions stored in `~/.roundhouse/sessions/main/`
|
|
91
|
+
- `SESSIONS_DIR` exported from config
|
|
92
|
+
- `resolveAgentThreadId()` routes DMs → `main`, groups → `group:<chatId>`
|
|
93
|
+
- `roundhouse tui` with no args opens `main` session directly (no scanning/prompting)
|
|
94
|
+
- `roundhouse agent` defaults to `main` thread; `--ephemeral` for one-off behavior
|
|
95
|
+
- **Silent agent failure detection** — model_error event, pi-telegram conflict warning, safety net posts "no response" if turn silent (v0.5.4)
|
|
96
|
+
- **macOS LaunchAgent support** — auto-start, Plist generation (v0.5.2)
|
|
97
|
+
- **Phase 2 refactoring** — cron dispatcher, pi-adapter extraction, setup.ts split (v0.5.3)
|
|
98
|
+
|
|
99
|
+
### Fixed
|
|
100
|
+
- **Pairing userId extraction** — reads `author.userId` matching Telegram adapter shape
|
|
101
|
+
- **Session reaper race** — tracks `inFlight` counter, skips busy sessions during reap
|
|
102
|
+
- **/compact concurrency** — now acquires per-thread lock like normal prompts
|
|
103
|
+
- **Attachment permissions** — dirs created with 0700, files with 0600
|
|
104
|
+
- **Memory state permissions** — writes with mode 0600, dirs 0700
|
|
105
|
+
- **Cron template cwd** — uses `agentCfg.cwd` instead of `process.cwd()`
|
|
106
|
+
- **Cron TDZ crash** — `agentCfg` was referenced before declaration
|
|
107
|
+
- **cmdRun shell injection** — uses `execFileSync` instead of shell string interpolation
|
|
108
|
+
|
|
109
|
+
### Removed
|
|
110
|
+
- Legacy `threadIdToDirLegacy()` and all backward-compat fallback code
|
|
111
|
+
- Per-platform session directories (old `telegram_c*` dirs no longer used)
|
|
112
|
+
|
|
113
|
+
## [0.3.18] — 2026-04-30
|
|
114
|
+
|
|
115
|
+
### Added
|
|
116
|
+
- **`--agent` flag** for `roundhouse setup` — agent-aware setup with `AgentDefinition` registry
|
|
117
|
+
- Pi as default agent, extensible to future agent types
|
|
118
|
+
- `stepInstallPackages`, `stepPreflight`, `stepConfigure` all driven by agent definition
|
|
119
|
+
- `resolveAgentForSetup()` wires Pi-specific configure/installExtension
|
|
120
|
+
- Unknown agent types rejected with available list
|
|
121
|
+
|
|
122
|
+
## [0.3.17] — 2026-04-29
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
- **`setup --telegram`** — interactive wizard + headless automation
|
|
126
|
+
- Interactive: BotFather guide, masked token prompt, QR pairing link, 10-step guided flow
|
|
127
|
+
- Headless: `--headless` with structured JSON logging, persistent pairing file
|
|
128
|
+
- Gateway completes pairing on `/start <nonce>` — `handlePendingPairing()` method
|
|
129
|
+
- `--bot-token` rejected in headless mode (argv visible in process listings)
|
|
130
|
+
- `qrcode-terminal` dependency for pairing QR codes
|
|
131
|
+
- Seed `~/.roundhouse/.env` with commented-out example template (mode 0600)
|
|
132
|
+
|
|
133
|
+
## [0.3.16] — 2026-04-29
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
- Show Telegram checks in CLI doctor, deduplicate token resolution
|
|
137
|
+
- Flip psst default to off
|
|
138
|
+
|
|
139
|
+
## [0.3.15] — 2026-04-29
|
|
140
|
+
|
|
141
|
+
### Fixed
|
|
142
|
+
- Validate pathValue and guard against non-string unit values in systemd generator
|
|
143
|
+
- Newline-injection guard in `generateUnit`, harden `whichSync`
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
- Consolidate systemd/shell helpers, add systemd tests
|
|
147
|
+
- Extract shared env-file and systemd modules (DRY/SRP)
|
|
148
|
+
|
|
149
|
+
## [0.3.14] — 2026-04-28
|
|
150
|
+
|
|
151
|
+
### Changed
|
|
152
|
+
- Refactor: extract shared env-file and systemd modules
|
|
153
|
+
|
|
154
|
+
## [0.3.13] — 2026-04-28
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
- `cmdInstall` tsx fallback now uses 'run' subcommand
|
|
158
|
+
- Split start/run: 'start' launches daemon, 'run' runs foreground
|
|
159
|
+
|
|
160
|
+
## [0.3.12] — 2026-04-28
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
- Rename env file to `.env` with legacy fallback + deprecation warning
|
|
164
|
+
|
|
165
|
+
## [0.3.11] — 2026-04-28
|
|
166
|
+
|
|
167
|
+
### Fixed
|
|
168
|
+
- E2E findings: show step ⑥ skip message, doctor checks global npm for Pi SDK
|
|
169
|
+
- Allow `--dry-run` without bot token
|
|
170
|
+
|
|
171
|
+
## [0.3.10] — 2026-04-27
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
- 6 findings from Codex full-codebase review (2 HIGH, 4 MEDIUM)
|
|
175
|
+
- Attachment size: `Blob.size` check before Buffer materialization
|
|
176
|
+
- Shell injection in `runSudo`: `execFileSync` with arg arrays
|
|
177
|
+
- `threadIdToDir` collision: injective `_xNNNN` encoding
|
|
178
|
+
- `/restart` checks both allowlists
|
|
179
|
+
- `isCommand` validates @bot suffix
|
|
180
|
+
- Cron `lastScheduledAt` pre-advance documented
|
|
181
|
+
|
|
182
|
+
## [0.3.9] — 2026-04-27
|
|
183
|
+
|
|
184
|
+
### Fixed
|
|
185
|
+
- Setup fixes from E2E test findings on fresh EC2 instances
|
|
186
|
+
|
|
187
|
+
## [0.3.8] — 2026-04-27
|
|
188
|
+
|
|
189
|
+
### Added
|
|
190
|
+
- **`roundhouse setup`** — one-command install & configure with psst integration
|
|
191
|
+
- **`roundhouse pair`** — standalone Telegram pairing command
|
|
192
|
+
- **`roundhouse agent`** — CLI command to send messages to configured agent
|
|
193
|
+
- `roundhouse --version` / `-v` flag
|
|
194
|
+
- Shared `BOT_COMMANDS` constant
|
|
195
|
+
- `allowedUserIds` (immutable numeric IDs) in gateway auth
|
|
196
|
+
|
|
197
|
+
## [0.3.6] — 2026-04-26
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
- **Memory system** (Option B) — roundhouse-managed by default
|
|
201
|
+
- MEMORY.md, daily notes, newspaper-style injection
|
|
202
|
+
- Proactive compaction: soft/hard/emergency thresholds
|
|
203
|
+
- Pre-compact flush in all modes
|
|
204
|
+
- `/status` shows memory mode and system CPU/RAM
|
|
205
|
+
- Rich startup notification with version, model, cron counts
|
|
206
|
+
|
|
207
|
+
## [0.3.5] — 2026-04-25
|
|
208
|
+
|
|
209
|
+
### Added
|
|
210
|
+
- **Cron system** — internal scheduler with `p-queue` and `croner`
|
|
211
|
+
- `roundhouse cron add/list/show/trigger/runs/edit/pause/resume/delete`
|
|
212
|
+
- Standard cron, interval, and one-shot schedule types
|
|
213
|
+
- Fresh agent per run, timeout with abort, template variables
|
|
214
|
+
- `/crons` and `/jobs` Telegram commands
|
|
215
|
+
- Built-in heartbeat job (reads HEARTBEAT.md every 30min)
|
|
216
|
+
|
|
217
|
+
## [0.3.2] — 2026-04-24
|
|
218
|
+
|
|
219
|
+
### Added
|
|
220
|
+
- **Voice support** — download attachments, STT via whisper
|
|
221
|
+
- **Doctor** — 21 checks across 7 categories
|
|
222
|
+
- Config migration `~/.config/roundhouse/` → `~/.roundhouse/`
|
|
223
|
+
|
|
224
|
+
## [0.3.1] — 2026-04-23
|
|
225
|
+
|
|
226
|
+
### Added
|
|
227
|
+
- Gateway with Telegram adapter, per-thread agent sessions
|
|
228
|
+
- `/new`, `/restart`, `/status`, `/compact`, `/verbose`, `/stop`, `/doctor` commands
|
|
229
|
+
- Auto-register bot commands with Telegram on startup
|
|
230
|
+
- Draining/drain_complete notification system
|
|
231
|
+
- Context token usage with progress bar
|
|
232
|
+
|
|
233
|
+
## [0.2.0] — 2026-04-22
|
|
234
|
+
|
|
235
|
+
- Initial release
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inceptionstack/roundhouse",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-platform chat gateway that routes messages through a configured AI agent",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"src/",
|
|
33
33
|
"bin/",
|
|
34
34
|
"skills/",
|
|
35
|
+
"CHANGELOG.md",
|
|
35
36
|
"LICENSE",
|
|
36
37
|
"README.md",
|
|
37
38
|
"architecture.md",
|
package/src/gateway/commands.ts
CHANGED
|
@@ -151,20 +151,15 @@ export async function handleCompact(ctx: CommandContext): Promise<void> {
|
|
|
151
151
|
|
|
152
152
|
// ── /status ──────────────────────────────────────────
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
const { thread, agent, agentThreadId, config, allowedUsers, verboseThreads, postWithFallback } = ctx;
|
|
154
|
+
// ── Status helpers ────────────────────────────────────────
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
? `${Math.floor(
|
|
160
|
-
: `${Math.floor(
|
|
161
|
-
|
|
162
|
-
const debugStream = process.env.ROUNDHOUSE_DEBUG_STREAM === "1";
|
|
163
|
-
const nodeVer = process.version;
|
|
164
|
-
const memMB = (process.memoryUsage.rss() / 1024 / 1024).toFixed(1);
|
|
156
|
+
function formatUptime(sec: number): string {
|
|
157
|
+
return sec < 3600
|
|
158
|
+
? `${Math.floor(sec / 60)}m ${Math.floor(sec % 60)}s`
|
|
159
|
+
: `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`;
|
|
160
|
+
}
|
|
165
161
|
|
|
166
|
-
|
|
167
|
-
let updateAvailable = "";
|
|
162
|
+
async function checkAvailableUpdate(): Promise<string> {
|
|
168
163
|
try {
|
|
169
164
|
const { exec } = await import("node:child_process");
|
|
170
165
|
const { promisify } = await import("node:util");
|
|
@@ -172,14 +167,25 @@ export async function handleStatus(ctx: CommandContext): Promise<void> {
|
|
|
172
167
|
const { stdout } = await execAsync("npm view @inceptionstack/roundhouse version 2>/dev/null", { timeout: 10_000 });
|
|
173
168
|
const latest = stdout.trim().split("\n").pop()!.trim();
|
|
174
169
|
if (latest && /^\d+\.\d+\.\d+$/.test(latest) && latest !== ROUNDHOUSE_VERSION) {
|
|
175
|
-
// Simple semver comparison: split and compare numerically
|
|
176
170
|
const [lM, lm, lp] = latest.split(".").map(Number);
|
|
177
171
|
const [cM, cm, cp] = ROUNDHOUSE_VERSION.split(".").map(Number);
|
|
178
172
|
if (lM > cM || (lM === cM && lm > cm) || (lM === cM && lm === cm && lp > cp)) {
|
|
179
|
-
|
|
173
|
+
return latest;
|
|
180
174
|
}
|
|
181
175
|
}
|
|
182
|
-
} catch { /* network unavailable
|
|
176
|
+
} catch { /* network unavailable */ }
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function handleStatus(ctx: CommandContext): Promise<void> {
|
|
181
|
+
const { thread, agent, agentThreadId, config, allowedUsers, verboseThreads, postWithFallback } = ctx;
|
|
182
|
+
|
|
183
|
+
const uptimeStr = formatUptime(process.uptime());
|
|
184
|
+
const platforms = Object.keys(config.chat.adapters).join(", ");
|
|
185
|
+
const debugStream = process.env.ROUNDHOUSE_DEBUG_STREAM === "1";
|
|
186
|
+
const nodeVer = process.version;
|
|
187
|
+
const memMB = (process.memoryUsage.rss() / 1024 / 1024).toFixed(1);
|
|
188
|
+
const updateAvailable = await checkAvailableUpdate();
|
|
183
189
|
|
|
184
190
|
const info = agent.getInfo ? agent.getInfo(agentThreadId) : {};
|
|
185
191
|
const agentVersion = info.version ? `v${info.version}` : "";
|
package/src/gateway/gateway.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { hostname } from "node:os";
|
|
|
31
31
|
import { join } from "node:path";
|
|
32
32
|
import { injectToolsSection } from "./tools-inject";
|
|
33
33
|
import { injectPersonaSection, loadPersona } from "./persona-inject";
|
|
34
|
+
import { checkVersionChange } from "./whats-new";
|
|
34
35
|
|
|
35
36
|
/** Bot username for command suffix validation (set during gateway init) */
|
|
36
37
|
let _botUsername = "";
|
|
@@ -237,45 +238,32 @@ export class Gateway {
|
|
|
237
238
|
if (isCommand(userText, "/start")) return;
|
|
238
239
|
if (!userText.trim() && !rawAttachments.length) return;
|
|
239
240
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// Handle /compact command
|
|
259
|
-
if (isCommand(userText.trim(), "/compact")) {
|
|
260
|
-
await handleCompact(this.buildCommandContext(thread, message, agentThreadId, authorName, allowedUsers, allowedUserIds, verboseThreads, threadLocks));
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Handle /status command
|
|
265
|
-
if (isCommand(userText.trim(), "/status")) {
|
|
266
|
-
await handleStatus(this.buildCommandContext(thread, message, agentThreadId, authorName, allowedUsers, allowedUserIds, verboseThreads, threadLocks));
|
|
267
|
-
return;
|
|
241
|
+
// ── Command dispatch (registry-based) ───
|
|
242
|
+
const trimmed = userText.trim();
|
|
243
|
+
|
|
244
|
+
// Commands using standard CommandContext
|
|
245
|
+
const COMMAND_REGISTRY: Record<string, (ctx: CommandContext) => Promise<void>> = {
|
|
246
|
+
"/new": handleNew,
|
|
247
|
+
"/restart": handleRestart,
|
|
248
|
+
"/update": handleUpdate,
|
|
249
|
+
"/compact": handleCompact,
|
|
250
|
+
"/status": handleStatus,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
for (const [cmd, handler] of Object.entries(COMMAND_REGISTRY)) {
|
|
254
|
+
if (isCommand(trimmed, cmd)) {
|
|
255
|
+
await handler(this.buildCommandContext(thread, message, agentThreadId, authorName, allowedUsers, allowedUserIds, verboseThreads, threadLocks));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
268
258
|
}
|
|
269
259
|
|
|
270
|
-
//
|
|
271
|
-
if (isCommandWithArgs(
|
|
272
|
-
await handleModel({ thread, text:
|
|
260
|
+
// Commands with custom context (accept args)
|
|
261
|
+
if (isCommandWithArgs(trimmed, "/model") || isCommand(trimmed, "/model")) {
|
|
262
|
+
await handleModel({ thread, text: trimmed, postWithFallback: (t, txt) => this.postWithFallback(t, txt) });
|
|
273
263
|
return;
|
|
274
264
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (isCommandWithArgs(userText.trim(), "/later") || isCommand(userText.trim(), "/later")) {
|
|
278
|
-
await handleLater({ thread, text: userText.trim(), postWithFallback: (t, txt) => this.postWithFallback(t, txt) });
|
|
265
|
+
if (isCommandWithArgs(trimmed, "/later") || isCommand(trimmed, "/later")) {
|
|
266
|
+
await handleLater({ thread, text: trimmed, postWithFallback: (t, txt) => this.postWithFallback(t, txt) });
|
|
279
267
|
return;
|
|
280
268
|
}
|
|
281
269
|
|
|
@@ -759,6 +747,9 @@ export class Gateway {
|
|
|
759
747
|
cronInfo = `Cron jobs: ${cs.enabledCount}/${cs.jobCount} enabled`;
|
|
760
748
|
}
|
|
761
749
|
|
|
750
|
+
// Check if this is a fresh update (call once, before loop)
|
|
751
|
+
const whatsNew = checkVersionChange();
|
|
752
|
+
|
|
762
753
|
for (const chatId of chatIds) {
|
|
763
754
|
const sessionId = Number(chatId) < 0 ? `group:${chatId}` : "main";
|
|
764
755
|
const perChatText = [
|
|
@@ -780,7 +771,8 @@ export class Gateway {
|
|
|
780
771
|
` Process: ${memMB} MB RSS`,
|
|
781
772
|
].filter(line => line != null).join("\n");
|
|
782
773
|
|
|
783
|
-
|
|
774
|
+
const fullText = whatsNew ? `${perChatText}\n\n${whatsNew}` : perChatText;
|
|
775
|
+
await this.transport.notify([chatId], fullText);
|
|
784
776
|
}
|
|
785
777
|
}
|
|
786
778
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway/whats-new.ts — Detect version changes and format "what's new" text
|
|
3
|
+
*
|
|
4
|
+
* On startup, compares current ROUNDHOUSE_VERSION against the last-known
|
|
5
|
+
* version stored in ~/.roundhouse/.last-version. If different, reads the
|
|
6
|
+
* latest CHANGELOG entry and formats it for the startup notification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { ROUNDHOUSE_DIR, ROUNDHOUSE_VERSION } from "../config";
|
|
13
|
+
|
|
14
|
+
const VERSION_FILE = join(ROUNDHOUSE_DIR, ".last-version");
|
|
15
|
+
|
|
16
|
+
/** Read the bundled CHANGELOG.md and extract the latest version's entry. */
|
|
17
|
+
function getLatestChangelog(): string {
|
|
18
|
+
const changelogPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "CHANGELOG.md");
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(changelogPath, "utf8");
|
|
21
|
+
// Find first ## [x.y.z] section and extract until next ## or end
|
|
22
|
+
const match = content.match(/^## \[[\d.]+\].*?\n([\s\S]*?)(?=\n## \[|$)/m);
|
|
23
|
+
if (!match) return "";
|
|
24
|
+
// Clean up: take first 5 meaningful lines (skip blank)
|
|
25
|
+
const lines = match[1].trim().split("\n")
|
|
26
|
+
.filter(l => l.trim())
|
|
27
|
+
.slice(0, 6)
|
|
28
|
+
.map(l => l.replace(/^### /, "").replace(/^\*\*/, "• ").replace(/\*\*$/, "").replace(/^- /, "• "));
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
} catch {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Check if version changed since last startup. Returns "what's new" text or null. */
|
|
36
|
+
export function checkVersionChange(): string | null {
|
|
37
|
+
let lastVersion = "";
|
|
38
|
+
try {
|
|
39
|
+
lastVersion = readFileSync(VERSION_FILE, "utf8").trim();
|
|
40
|
+
} catch { /* first run or file missing */ }
|
|
41
|
+
|
|
42
|
+
// Always update the version file
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(ROUNDHOUSE_DIR, { recursive: true });
|
|
45
|
+
writeFileSync(VERSION_FILE, ROUNDHOUSE_VERSION + "\n");
|
|
46
|
+
} catch {}
|
|
47
|
+
|
|
48
|
+
// No change
|
|
49
|
+
if (lastVersion === ROUNDHOUSE_VERSION) return null;
|
|
50
|
+
|
|
51
|
+
// First run (no previous version)
|
|
52
|
+
if (!lastVersion) return null;
|
|
53
|
+
|
|
54
|
+
// Version changed — this is an update
|
|
55
|
+
const changelog = getLatestChangelog();
|
|
56
|
+
const header = `🆕 Updated: v${lastVersion} → v${ROUNDHOUSE_VERSION}`;
|
|
57
|
+
if (!changelog) return header;
|
|
58
|
+
return `${header}\n\n${changelog}`;
|
|
59
|
+
}
|