@c4t4/heyamigo 0.9.20 → 0.9.21
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/config/memory-instructions.md +61 -0
- package/dist/db/identity-sync.js +33 -1
- package/dist/db/schema.js +4 -0
- package/dist/gateway/commands.js +11 -0
- package/dist/memory/digest-flag.js +15 -9
- package/dist/memory/preamble.js +48 -0
- package/dist/queue/crons.js +16 -10
- package/dist/queue/schedule-list.js +61 -0
- package/dist/queue/time-expr.js +192 -0
- package/dist/queue/worker.js +36 -4
- package/migrations/0008_crons_timezone.sql +1 -0
- package/migrations/meta/0008_snapshot.json +931 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/queue/worker.js
CHANGED
|
@@ -4,6 +4,7 @@ import { config } from '../config.js';
|
|
|
4
4
|
import { formatAddress, jidToAddress } from '../db/address.js';
|
|
5
5
|
import { logger } from '../logger.js';
|
|
6
6
|
import { addDailyTokens } from '../store/usage.js';
|
|
7
|
+
import { getTimezoneForSenderNumber } from '../db/identity-sync.js';
|
|
7
8
|
import { estimate as estimateJob } from '../estimates/index.js';
|
|
8
9
|
import { extractFlags, filterFlagsByRole } from '../memory/digest-flag.js';
|
|
9
10
|
import { isValidSlug } from '../memory/journals.js';
|
|
@@ -11,6 +12,7 @@ import { enqueueAsyncTask, enqueueBrowserTask } from './async-tasks.js';
|
|
|
11
12
|
import { enqueueCron } from './crons.js';
|
|
12
13
|
import { enqueueMemoryWrite } from './memory-writes.js';
|
|
13
14
|
import { enqueueOutbound } from './outbound.js';
|
|
15
|
+
import { formatLocalTime, resolveTimeExpression } from './time-expr.js';
|
|
14
16
|
function isStaleSessionError(err) {
|
|
15
17
|
return (err instanceof Error &&
|
|
16
18
|
err.message.includes('No conversation found'));
|
|
@@ -234,8 +236,12 @@ async function callClaude(job) {
|
|
|
234
236
|
}
|
|
235
237
|
// [CRON: @every X — body] and [REMIND: in Nu — body] create cron
|
|
236
238
|
// rows that fire into outbound at their scheduled time. The
|
|
237
|
-
// originating chat (job.jid) is the destination for both.
|
|
239
|
+
// originating chat (job.jid) is the destination for both. Sender's
|
|
240
|
+
// timezone drives "9am" / "today at..." resolution so the schedule
|
|
241
|
+
// lands in their wall-clock time, not the server's.
|
|
238
242
|
const chatAddress = formatAddress(jidToAddress(job.jid));
|
|
243
|
+
const senderTz = getTimezoneForSenderNumber(job.senderNumber);
|
|
244
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
239
245
|
const cronBase = `chat-cron-${job.jid}-${Date.now()}`;
|
|
240
246
|
for (let i = 0; i < crons.length; i++) {
|
|
241
247
|
const c = crons[i];
|
|
@@ -245,24 +251,50 @@ async function callClaude(job) {
|
|
|
245
251
|
enqueueInto: 'outbound',
|
|
246
252
|
payload: { address: chatAddress, kind: 'text', text: c.body },
|
|
247
253
|
recurrence: c.recurrence,
|
|
254
|
+
// Sender's local timezone so @daily HH:MM fires in their
|
|
255
|
+
// wall-clock time, not the server's.
|
|
256
|
+
timezone: senderTz,
|
|
248
257
|
});
|
|
249
|
-
logger.info({
|
|
258
|
+
logger.info({
|
|
259
|
+
jid: job.jid,
|
|
260
|
+
recurrence: c.recurrence,
|
|
261
|
+
tz: senderTz,
|
|
262
|
+
chars: c.body.length,
|
|
263
|
+
}, 'CRON tag scheduled');
|
|
250
264
|
}
|
|
251
265
|
catch (err) {
|
|
252
266
|
logger.warn({ err, jid: job.jid, recurrence: c.recurrence }, 'CRON tag failed (bad recurrence?)');
|
|
253
267
|
}
|
|
254
268
|
}
|
|
269
|
+
// REMIND resolution uses the same senderTz computed above.
|
|
255
270
|
const remindBase = `chat-remind-${job.jid}-${Date.now()}`;
|
|
256
271
|
for (let i = 0; i < reminds.length; i++) {
|
|
257
272
|
const r = reminds[i];
|
|
273
|
+
let firstRunAt;
|
|
274
|
+
try {
|
|
275
|
+
firstRunAt = resolveTimeExpression(r.when, senderTz, nowSec);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
logger.warn({ err, jid: job.jid, when: r.when }, 'REMIND time resolution failed');
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (firstRunAt <= nowSec) {
|
|
282
|
+
logger.warn({ jid: job.jid, when: r.when, firstRunAt }, 'REMIND resolved to past — skipped');
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
258
285
|
enqueueCron({
|
|
259
286
|
name: `${remindBase}-${i}`,
|
|
260
287
|
enqueueInto: 'outbound',
|
|
261
288
|
payload: { address: chatAddress, kind: 'text', text: r.body },
|
|
262
289
|
recurrence: null,
|
|
263
|
-
firstRunAt
|
|
290
|
+
firstRunAt,
|
|
264
291
|
});
|
|
265
|
-
logger.info({
|
|
292
|
+
logger.info({
|
|
293
|
+
jid: job.jid,
|
|
294
|
+
fires: formatLocalTime(firstRunAt, senderTz),
|
|
295
|
+
tz: senderTz,
|
|
296
|
+
chars: r.body.length,
|
|
297
|
+
}, 'REMIND tag scheduled');
|
|
266
298
|
}
|
|
267
299
|
return {
|
|
268
300
|
reply: clean,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `crons` ADD `timezone` text;
|