Skip to content

v0.5.5 — /update command + /reset queue clear#1

Merged
b1rdmania merged 1 commit into
mainfrom
feat/v0.5.5-update-command
Mar 19, 2026
Merged

v0.5.5 — /update command + /reset queue clear#1
b1rdmania merged 1 commit into
mainfrom
feat/v0.5.5-update-command

Conversation

@b1rdmania
Copy link
Copy Markdown
Owner

@b1rdmania b1rdmania commented Mar 18, 2026

What

Two small but important fixes for remote control when you can't SSH in.

/reset now actually resets

Previously /reset killed the current agent but left pendingTasks and pendingMessages in the queue. drainGroup() would immediately start the next item, making it impossible to stop a runaway queue via Telegram. Now /reset calls clearQueue() first, so it's a genuine hard stop.

/update — remote updates over Telegram

Pulls latest code, rebuilds, and restarts via launchd — no SSH needed:

git pull → npm run build → process.exit(0) → launchd restarts

Bootstrap note: requires one manual git pull && npm run build && launchctl kickstart -k gui/501/com.ghostclaw to get onto a running instance. After that, all future updates can be sent via /update from Telegram.

Files changed

  • src/group-queue.ts — added clearQueue() method
  • src/index.tsonReset now calls clearQueue() before killAgent()
  • src/channels/telegram.ts — added /update command, updated /reset reply
  • package.json — bumped to v0.5.5
  • CHANGELOG.md — v0.5.5 entry

Test plan

  • /reset while agent is running — confirm queue doesn't drain
  • /reset with no agent running — confirm "Nothing running" behaviour (note: now always confirms kill+clear regardless)
  • /update on a repo with no new commits — confirm "Already up to date" message
  • /update with new commits — confirm pull, build, restart cycle
  • /ping after restart — confirm bot comes back up

Summary by CodeRabbit

  • New Features

    • Added /update command to deploy code updates remotely via Telegram—automatically fetches latest code, rebuilds, and restarts the service.
  • Bug Fixes

    • Fixed /reset command to fully clear pending tasks and message queues, ensuring complete system reset alongside stopping the agent.

- /update: git pull + npm run build + launchd restart via process.exit(0)
- /reset: now also clears pendingTasks and pendingMessages so the queue
  doesn't keep draining after a kill
- GroupQueue.clearQueue() method added
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This PR introduces a remote update capability via a new /update command and enhances the /reset command to clear both pending tasks and message queues. Version is bumped to v0.5.5, with supporting updates to queue management and command handling logic.

Changes

Cohort / File(s) Summary
Documentation & Version
CHANGELOG.md, package.json
Version bumped to v0.5.5 (2026-03-18). Changelog entry documents new /update command and improved /reset behavior that now clears both pending tasks and message queue.
Queue Management
src/group-queue.ts
New public method clearQueue(groupJid: string) purges all pending tasks and messages for a group, removing it from waitingGroups. Several explanatory comments were removed.
Command & Callback Updates
src/channels/telegram.ts, src/index.ts
Telegram channel adds /update command that executes git pull, npm rebuild, and process restart via launchd with user feedback. Reset command invokes onReset callback and confirms queue clear. Updated onReset callback in index.ts to clear queue before killing agent. Removed non-functional comment blocks throughout both files.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Telegram as Telegram Channel
    participant Shell as Shell (execSync)
    participant Launchd as launchd
    participant Agent as Agent Process

    User->>Telegram: /update
    Telegram->>User: Pulling latest code...
    Telegram->>Shell: git pull
    Shell-->>Telegram: code updated
    Telegram->>User: Building project...
    Telegram->>Shell: npm run build
    Shell-->>Telegram: build complete
    Telegram->>Launchd: restart service
    Launchd->>Agent: terminate
    Launchd->>Agent: spawn new process
    Agent-->>User: Update complete
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A hoppy tale of updates bright,
When /reset clears the queue just right,
And /update pulls from git with care,
Rebuilds and hops through the air!
Remote control, no manual toil,
This v0.5.5 makes things recoil! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: the /update command and /reset queue clearing, directly matching the PR's core objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v0.5.5-update-command
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/channels/telegram.ts (1)

1-447: ⚠️ Potential issue | 🟡 Minor

CI is currently blocked by Prettier in this file.

Please run formatting on src/channels/telegram.ts before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/channels/telegram.ts` around lines 1 - 447, The file fails CI due to
Prettier formatting; run your project's formatter (e.g. npx prettier --write
src/channels/telegram.ts or npm run format) to apply consistent formatting
across the TelegramChannel class and its methods (connect, sendMessage,
sendDocument, setTyping, etc.), then re-stage and push the formatted file so CI
can pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/channels/telegram.ts`:
- Around line 68-70: The /reset handler always replies "Agent killed..."
regardless of whether anything was running; change it to inspect the return
value of this.opts.onReset?.(chatJid) (e.g., store it in a variable like const
result = await this.opts.onReset?.(chatJid)) and then send a conditional reply
via ctx.reply: if result indicates an agent was killed, keep "Agent killed and
queue cleared — send me something to start fresh.", otherwise reply with "No
agent was running; queue cleared — send me something to start fresh." Ensure you
await the onReset call if it returns a promise and only call ctx.reply once with
the appropriate message.
- Around line 73-97: The /update handler (this.bot.command('update')) currently
allows any registered chat (using registeredGroups() and chatJid) to
pull/build/restart; restrict it by checking the caller against an
admin/allowlist before performing actions. Add an explicit authorization check
at the top of the handler that verifies the chatJid is in a configured admin set
(e.g., this.opts.adminGroups or a single this.opts.adminGroup/mainGroup value)
or that group has an admin flag (e.g., group.isAdmin); if the check fails reply
with an "unauthorized" message and return. Ensure you reference the same symbols
(chatJid, registeredGroups(), this.opts) so the check integrates with existing
config and log unauthorized attempts via logger before returning.

In `@src/group-queue.ts`:
- Around line 205-219: The clearQueue method currently empties pendingTasks and
removes the group from waitingGroups but does not cancel any scheduled
retry/backoff for that group; update clearQueue (and related state on the group
object returned by getGroup) to cancel and clear any retry timer or backoff
state so retries cannot fire after reset — specifically: if the group state has
a timer id (e.g., state.retryTimer or state.retryTimeoutId) call
clearTimeout/clearInterval and delete/null that property, and if there is a
centralized backoff map (e.g., this.retryBackoffs or this.backoffTimers keyed by
groupJid) clear and delete its entry and reset any backoff counters (e.g.,
state.backoffCount) to 0 so no retry will be scheduled after clearQueue
completes.

---

Outside diff comments:
In `@src/channels/telegram.ts`:
- Around line 1-447: The file fails CI due to Prettier formatting; run your
project's formatter (e.g. npx prettier --write src/channels/telegram.ts or npm
run format) to apply consistent formatting across the TelegramChannel class and
its methods (connect, sendMessage, sendDocument, setTyping, etc.), then re-stage
and push the formatted file so CI can pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 943a136d-af66-4f20-92db-6fb5ecb51264

📥 Commits

Reviewing files that changed from the base of the PR and between eee604e and 90cbd4f.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • package.json
  • src/channels/telegram.ts
  • src/group-queue.ts
  • src/index.ts

Comment thread src/channels/telegram.ts
Comment on lines +68 to +70
this.opts.onReset?.(chatJid);
ctx.reply('Reset. Agent killed and queue cleared — send me something to start fresh.');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

/reset response is misleading when nothing was running.

onReset returns kill status, but the reply always says “Agent killed”. This can misreport state.

💡 Proposed fix
-      this.opts.onReset?.(chatJid);
-      ctx.reply('Reset. Agent killed and queue cleared — send me something to start fresh.');
+      const killed = this.opts.onReset?.(chatJid) ?? false;
+      ctx.reply(
+        killed
+          ? 'Reset. Agent killed and queue cleared — send me something to start fresh.'
+          : 'Reset. Queue cleared. No running agent found — send me something to start fresh.',
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.opts.onReset?.(chatJid);
ctx.reply('Reset. Agent killed and queue cleared — send me something to start fresh.');
});
const killed = this.opts.onReset?.(chatJid) ?? false;
ctx.reply(
killed
? 'Reset. Agent killed and queue cleared — send me something to start fresh.'
: 'Reset. Queue cleared. No running agent found — send me something to start fresh.',
);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/channels/telegram.ts` around lines 68 - 70, The /reset handler always
replies "Agent killed..." regardless of whether anything was running; change it
to inspect the return value of this.opts.onReset?.(chatJid) (e.g., store it in a
variable like const result = await this.opts.onReset?.(chatJid)) and then send a
conditional reply via ctx.reply: if result indicates an agent was killed, keep
"Agent killed and queue cleared — send me something to start fresh.", otherwise
reply with "No agent was running; queue cleared — send me something to start
fresh." Ensure you await the onReset call if it returns a promise and only call
ctx.reply once with the appropriate message.

Comment thread src/channels/telegram.ts
Comment on lines +73 to 97
this.bot.command('update', async (ctx) => {
const chatJid = `tg:${ctx.chat.id}`;
const group = this.opts.registeredGroups()[chatJid];
if (!group) {
ctx.reply('Not a registered chat.');
return;
}

await ctx.reply('Pulling latest code...');
const cwd = process.cwd();

try {
const pullOut = execSync('git pull', { cwd, encoding: 'utf-8' });
await ctx.reply(`git pull: ${pullOut.trim()}`);

await ctx.reply('Building...');
execSync('npm run build', { cwd, encoding: 'utf-8', timeout: 120_000 });

await ctx.reply('Done. Restarting — back in a moment.');
setTimeout(() => process.exit(0), 500);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
await ctx.reply(`Update failed:\n${msg.slice(0, 500)}`);
logger.error({ err }, '/update command failed');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

/update needs stricter authorization.

This command can execute repo update/build/restart, but it’s currently available to any registered chat. That’s a high-impact control path and should be restricted to an admin channel/allowlist.

🔐 Proposed fix (restrict to main/admin group)
-import { ASSISTANT_NAME, TRIGGER_PATTERN } from '../config.js';
+import { ASSISTANT_NAME, MAIN_GROUP_FOLDER, TRIGGER_PATTERN } from '../config.js';

     this.bot.command('update', async (ctx) => {
       const chatJid = `tg:${ctx.chat.id}`;
       const group = this.opts.registeredGroups()[chatJid];
       if (!group) {
         ctx.reply('Not a registered chat.');
         return;
       }
+      if (group.folder !== MAIN_GROUP_FOLDER) {
+        await ctx.reply('Not authorized to run /update from this chat.');
+        return;
+      }

       await ctx.reply('Pulling latest code...');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/channels/telegram.ts` around lines 73 - 97, The /update handler
(this.bot.command('update')) currently allows any registered chat (using
registeredGroups() and chatJid) to pull/build/restart; restrict it by checking
the caller against an admin/allowlist before performing actions. Add an explicit
authorization check at the top of the handler that verifies the chatJid is in a
configured admin set (e.g., this.opts.adminGroups or a single
this.opts.adminGroup/mainGroup value) or that group has an admin flag (e.g.,
group.isAdmin); if the check fails reply with an "unauthorized" message and
return. Ensure you reference the same symbols (chatJid, registeredGroups(),
this.opts) so the check integrates with existing config and log unauthorized
attempts via logger before returning.

Comment thread src/group-queue.ts
Comment on lines +205 to +219
/**
* Clear all pending tasks and messages for a group.
* Call alongside killAgent() on /reset so the queue doesn't keep draining.
*/
clearQueue(groupJid: string): void {
const state = this.getGroup(groupJid);
const taskCount = state.pendingTasks.length;
state.pendingTasks = [];
state.pendingMessages = false;
// Remove from waiting list too
this.waitingGroups = this.waitingGroups.filter((jid) => jid !== groupJid);
if (taskCount > 0) {
logger.info({ groupJid, taskCount }, 'Queue cleared on reset');
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

clearQueue() should also cancel retry backoff state.

A previously scheduled retry can still fire after reset and restart processing for the same group. That makes /reset less deterministic as a hard stop.

🛠️ Proposed fix
 interface GroupState {
   active: boolean;
   idleWaiting: boolean;
   isTaskContainer: boolean;
   pendingMessages: boolean;
   pendingTasks: QueuedTask[];
   process: ChildProcess | null;
   containerName: string | null;
   groupFolder: string | null;
   retryCount: number;
+  retryTimer: NodeJS.Timeout | null;
 }

 // in getGroup() initializer
       state = {
         active: false,
         idleWaiting: false,
         isTaskContainer: false,
         pendingMessages: false,
         pendingTasks: [],
         process: null,
         containerName: null,
         groupFolder: null,
         retryCount: 0,
+        retryTimer: null,
       };

   clearQueue(groupJid: string): void {
     const state = this.getGroup(groupJid);
     const taskCount = state.pendingTasks.length;
     state.pendingTasks = [];
     state.pendingMessages = false;
+    state.retryCount = 0;
+    if (state.retryTimer) {
+      clearTimeout(state.retryTimer);
+      state.retryTimer = null;
+    }
     this.waitingGroups = this.waitingGroups.filter((jid) => jid !== groupJid);
     if (taskCount > 0) {
       logger.info({ groupJid, taskCount }, 'Queue cleared on reset');
     }
   }

   private scheduleRetry(groupJid: string, state: GroupState): void {
     state.retryCount++;
     if (state.retryCount > MAX_RETRIES) {
       ...
       return;
     }

     const delayMs = BASE_RETRY_MS * Math.pow(2, state.retryCount - 1);
+    if (state.retryTimer) clearTimeout(state.retryTimer);
-    setTimeout(() => {
+    state.retryTimer = setTimeout(() => {
+      state.retryTimer = null;
       if (!this.shuttingDown) {
         this.enqueueMessageCheck(groupJid);
       }
     }, delayMs);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/group-queue.ts` around lines 205 - 219, The clearQueue method currently
empties pendingTasks and removes the group from waitingGroups but does not
cancel any scheduled retry/backoff for that group; update clearQueue (and
related state on the group object returned by getGroup) to cancel and clear any
retry timer or backoff state so retries cannot fire after reset — specifically:
if the group state has a timer id (e.g., state.retryTimer or
state.retryTimeoutId) call clearTimeout/clearInterval and delete/null that
property, and if there is a centralized backoff map (e.g., this.retryBackoffs or
this.backoffTimers keyed by groupJid) clear and delete its entry and reset any
backoff counters (e.g., state.backoffCount) to 0 so no retry will be scheduled
after clearQueue completes.

@b1rdmania b1rdmania merged commit 0492f03 into main Mar 19, 2026
1 of 2 checks passed
@b1rdmania b1rdmania deleted the feat/v0.5.5-update-command branch March 23, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant