thepopebot 1.2.76-beta.27 → 1.2.76-beta.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/api/index.js CHANGED
@@ -7,7 +7,7 @@ import { dispatchCommand, dispatchPreAuthCommand } from '../lib/channels/command
7
7
  import { getByChannelChatId, setActiveThread } from '../lib/db/user-channels.js';
8
8
  import { chat, chatStream, summarizeAgentJob } from '../lib/ai/index.js';
9
9
  import { createNotification } from '../lib/db/notifications.js';
10
- import { loadTriggers } from '../lib/triggers.js';
10
+ import { getFireTriggers } from '../lib/triggers.js';
11
11
  import { verifyApiKey } from '../lib/db/api-keys.js';
12
12
  import { getConfig } from '../lib/config.js';
13
13
  import { parseOAuthState, exchangeCodeForToken } from '../lib/oauth/helper.js';
@@ -19,8 +19,6 @@ const _refreshLocks = new Map();
19
19
  // Bot token — resolved from DB/env, can be overridden by /telegram/register
20
20
  let telegramBotToken = null;
21
21
 
22
- // Cached trigger firing function (initialized on first request)
23
- let _fireTriggers = null;
24
22
 
25
23
  function getTelegramBotToken() {
26
24
  if (!telegramBotToken) {
@@ -29,13 +27,6 @@ function getTelegramBotToken() {
29
27
  return telegramBotToken;
30
28
  }
31
29
 
32
- function getFireTriggers() {
33
- if (!_fireTriggers) {
34
- const result = loadTriggers();
35
- _fireTriggers = result.fireTriggers;
36
- }
37
- return _fireTriggers;
38
- }
39
30
 
40
31
  // Routes that have their own authentication
41
32
  const PUBLIC_ROUTES = ['/telegram/webhook', '/github/webhook', '/ping', '/oauth/callback'];
@@ -52,7 +52,7 @@ export async function register() {
52
52
  }
53
53
 
54
54
  // Start cron scheduler
55
- const { loadCrons } = await import('../lib/cron.js');
55
+ const { loadCrons, reloadCrons } = await import('../lib/cron.js');
56
56
  loadCrons();
57
57
 
58
58
  // Start built-in crons (version check)
@@ -74,5 +74,33 @@ export async function register() {
74
74
  const { startMaintenanceCron } = await import('../lib/maintenance.js');
75
75
  startMaintenanceCron();
76
76
 
77
+ // Watch config files for hot reload (avoids full pm2 restart on git pull)
78
+ const { reloadTriggers } = await import('../lib/triggers.js');
79
+ const { default: chokidar } = await import('chokidar');
80
+ const { PROJECT_ROOT } = await import('../lib/paths.js');
81
+ const path = await import('path');
82
+
83
+ const cronsPath = path.join(PROJECT_ROOT, 'agent-job/CRONS.json');
84
+ const triggersPath = path.join(PROJECT_ROOT, 'event-handler/TRIGGERS.json');
85
+
86
+ const watcher = chokidar.watch([cronsPath, triggersPath], {
87
+ ignoreInitial: true,
88
+ awaitWriteFinish: { stabilityThreshold: 500 },
89
+ });
90
+
91
+ watcher.on('change', (filePath) => {
92
+ if (filePath.endsWith('CRONS.json')) {
93
+ console.log('[config watch] CRONS.json changed, reloading...');
94
+ reloadCrons();
95
+ } else if (filePath.endsWith('TRIGGERS.json')) {
96
+ console.log('[config watch] TRIGGERS.json changed, reloading...');
97
+ reloadTriggers();
98
+ }
99
+ });
100
+
101
+ watcher.on('error', (err) => {
102
+ console.error('[config watch] Watcher error:', err.message);
103
+ });
104
+
77
105
  console.log('thepopebot initialized');
78
106
  }
package/lib/cron.js CHANGED
@@ -4,6 +4,9 @@ import path from 'path';
4
4
  import { PROJECT_ROOT } from './paths.js';
5
5
  import { executeAction } from './actions.js';
6
6
 
7
+ // Active user cron tasks (module-scoped so reloadCrons can stop them)
8
+ let _cronTasks = [];
9
+
7
10
  function getInstalledVersion() {
8
11
  const pkgPath = path.join(process.cwd(), 'node_modules', 'thepopebot', 'package.json');
9
12
  return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
@@ -228,7 +231,6 @@ function startBuiltinCrons() {
228
231
 
229
232
  /**
230
233
  * Load and schedule crons from CRONS.json
231
- * @returns {Array} - Array of scheduled cron tasks
232
234
  */
233
235
  function loadCrons() {
234
236
  const cronFile = path.join(PROJECT_ROOT, 'agent-job/CRONS.json');
@@ -238,7 +240,8 @@ function loadCrons() {
238
240
  if (!fs.existsSync(cronFile)) {
239
241
  console.log('No CRONS.json found');
240
242
  console.log('-----------------\n');
241
- return [];
243
+ _cronTasks = [];
244
+ return;
242
245
  }
243
246
 
244
247
  const crons = JSON.parse(fs.readFileSync(cronFile, 'utf8'));
@@ -276,7 +279,32 @@ function loadCrons() {
276
279
 
277
280
  console.log('-----------------\n');
278
281
 
279
- return tasks;
282
+ _cronTasks = tasks;
283
+ }
284
+
285
+ /**
286
+ * Stop existing cron tasks and re-load from CRONS.json.
287
+ * If the new file is invalid, keeps existing tasks running.
288
+ */
289
+ function reloadCrons() {
290
+ const cronFile = path.join(PROJECT_ROOT, 'agent-job/CRONS.json');
291
+
292
+ // Pre-validate before stopping anything
293
+ if (fs.existsSync(cronFile)) {
294
+ try {
295
+ JSON.parse(fs.readFileSync(cronFile, 'utf8'));
296
+ } catch (err) {
297
+ console.error(`[cron reload] Invalid JSON in CRONS.json, keeping existing schedule: ${err.message}`);
298
+ return;
299
+ }
300
+ }
301
+
302
+ for (const { task } of _cronTasks) {
303
+ task.stop();
304
+ }
305
+
306
+ loadCrons();
307
+ console.log('[cron reload] Cron schedule reloaded');
280
308
  }
281
309
 
282
- export { loadCrons, startBuiltinCrons, getUpdateAvailable, setUpdateAvailable, getInstalledVersion, isPrerelease, runVersionCheck };
310
+ export { loadCrons, reloadCrons, startBuiltinCrons, getUpdateAvailable, setUpdateAvailable, getInstalledVersion, isPrerelease, runVersionCheck };
package/lib/triggers.js CHANGED
@@ -3,6 +3,9 @@ import path from 'path';
3
3
  import { PROJECT_ROOT } from './paths.js';
4
4
  import { executeAction } from './actions.js';
5
5
 
6
+ // Cached fire function (module-scoped so reloadTriggers can reset it)
7
+ let _fireTriggers = null;
8
+
6
9
  /**
7
10
  * Replace {{body.field}} templates with values from request context
8
11
  * @param {string} template - String with {{body.field}} placeholders
@@ -102,4 +105,38 @@ function loadTriggers() {
102
105
  return { triggerMap, fireTriggers };
103
106
  }
104
107
 
105
- export { loadTriggers };
108
+ /**
109
+ * Get the cached fire function, loading on first call.
110
+ */
111
+ function getFireTriggers() {
112
+ if (!_fireTriggers) {
113
+ const result = loadTriggers();
114
+ _fireTriggers = result.fireTriggers;
115
+ }
116
+ return _fireTriggers;
117
+ }
118
+
119
+ /**
120
+ * Re-load triggers from TRIGGERS.json.
121
+ * If the new file is invalid, keeps existing triggers.
122
+ */
123
+ function reloadTriggers() {
124
+ const triggerFile = path.join(PROJECT_ROOT, 'event-handler/TRIGGERS.json');
125
+
126
+ // Pre-validate before discarding anything
127
+ if (fs.existsSync(triggerFile)) {
128
+ try {
129
+ JSON.parse(fs.readFileSync(triggerFile, 'utf8'));
130
+ } catch (err) {
131
+ console.error(`[trigger reload] Invalid JSON in TRIGGERS.json, keeping existing triggers: ${err.message}`);
132
+ return;
133
+ }
134
+ }
135
+
136
+ _fireTriggers = null;
137
+ // Force re-load immediately so the log output shows
138
+ getFireTriggers();
139
+ console.log('[trigger reload] Triggers reloaded');
140
+ }
141
+
142
+ export { loadTriggers, getFireTriggers, reloadTriggers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.76-beta.27",
3
+ "version": "1.2.76-beta.28",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -68,11 +68,6 @@ jobs:
68
68
  docker exec thepopebot-event-handler rm -f /tmp/.rebuild-status
69
69
  echo "status=$STATUS" >> $GITHUB_OUTPUT
70
70
 
71
- - name: Reload (no version change)
72
- if: steps.pull.outputs.status == 'RELOAD'
73
- run: |
74
- docker exec thepopebot-event-handler gosu coding-agent pm2 reload all
75
-
76
71
  - name: Pull new image and restart container
77
72
  if: steps.pull.outputs.status == 'VERSION_CHANGED'
78
73
  run: |