@c4t4/heyamigo 0.9.4 → 0.9.5
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/dist/memory/scheduler.js +24 -12
- package/dist/queue/cron-dispatch.js +32 -0
- package/dist/queue/cron-handlers.js +25 -0
- package/package.json +1 -1
package/dist/memory/scheduler.js
CHANGED
|
@@ -2,6 +2,8 @@ import fastq from 'fastq';
|
|
|
2
2
|
import { config } from '../config.js';
|
|
3
3
|
import { logger } from '../logger.js';
|
|
4
4
|
import { prunePrompts } from '../promptlog.js';
|
|
5
|
+
import { registerInternalCronHandler } from '../queue/cron-handlers.js';
|
|
6
|
+
import { deleteCron, enqueueCron } from '../queue/crons.js';
|
|
5
7
|
import { pruneMedia } from '../store/media.js';
|
|
6
8
|
import { runDigest } from './digest.js';
|
|
7
9
|
import { ensureScaffold, getLastDigestedAt, jsonlMtimeFor, loadDigestState, } from './store.js';
|
|
@@ -74,7 +76,6 @@ async function sweep() {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
let sweepTimer = null;
|
|
77
|
-
let nudgeTimer = null;
|
|
78
79
|
const NUDGE_TICK_MS = 5 * 60 * 1000; // 5 minutes
|
|
79
80
|
export function startScheduler() {
|
|
80
81
|
if (sweepTimer)
|
|
@@ -95,13 +96,17 @@ export function startScheduler() {
|
|
|
95
96
|
sweepTimer = setInterval(() => {
|
|
96
97
|
void sweep().catch((err) => logger.error({ err }, 'sweep failed'));
|
|
97
98
|
}, config.memory.sweepIntervalMs);
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
// Proactive journal nudges (check-ins, silent-nudges). Migrated from
|
|
100
|
+
// setInterval to a cron row → orchestrator. Same cadence, same body;
|
|
101
|
+
// benefits are: survives restarts, visible in `crons` table, can be
|
|
102
|
+
// paused via control row without code change.
|
|
103
|
+
registerInternalCronHandler('journal-nudge-tick', runNudgeTickSafe);
|
|
104
|
+
enqueueCron({
|
|
105
|
+
name: 'journal-nudge-tick',
|
|
106
|
+
enqueueInto: 'internal',
|
|
107
|
+
payload: { handler: 'journal-nudge-tick' },
|
|
108
|
+
recurrence: `@every ${Math.floor(NUDGE_TICK_MS / 1000)}s`,
|
|
109
|
+
});
|
|
105
110
|
logger.info({
|
|
106
111
|
intervalMs: config.memory.sweepIntervalMs,
|
|
107
112
|
nudgeTickMs: NUDGE_TICK_MS,
|
|
@@ -121,11 +126,18 @@ export function stopScheduler() {
|
|
|
121
126
|
clearInterval(sweepTimer);
|
|
122
127
|
sweepTimer = null;
|
|
123
128
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
// Nudge cron is owned by the crons table; orchestrator stops on its
|
|
130
|
+
// own. Deleting the cron row here would re-arm itself on next boot,
|
|
131
|
+
// so leave it alone — disabling via the `enabled` column is the
|
|
132
|
+
// user-facing knob.
|
|
128
133
|
for (const t of pendingTimers.values())
|
|
129
134
|
clearTimeout(t);
|
|
130
135
|
pendingTimers.clear();
|
|
131
136
|
}
|
|
137
|
+
// Exported for callers (CLI, /nudge command) that want to surgically
|
|
138
|
+
// disable nudges without editing config. Use `setCronEnabled` from
|
|
139
|
+
// crons.ts for the on/off switch; this is a hard delete (regenerated
|
|
140
|
+
// on next startScheduler call).
|
|
141
|
+
export function deleteNudgeCron() {
|
|
142
|
+
return deleteCron('journal-nudge-tick');
|
|
143
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// in on later phases — when those queues exist, add their dispatch
|
|
6
6
|
// here and a cron can fire into them with no other changes.
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
|
+
import { getInternalCronHandler } from './cron-handlers.js';
|
|
8
9
|
import { enqueueOutbound } from './outbound.js';
|
|
9
10
|
export function dispatchCron(row) {
|
|
10
11
|
let payload;
|
|
@@ -19,6 +20,9 @@ export function dispatchCron(row) {
|
|
|
19
20
|
case 'outbound':
|
|
20
21
|
dispatchOutbound(row, payload);
|
|
21
22
|
return;
|
|
23
|
+
case 'internal':
|
|
24
|
+
dispatchInternal(row, payload);
|
|
25
|
+
return;
|
|
22
26
|
case 'inbound':
|
|
23
27
|
case 'async':
|
|
24
28
|
case 'memory_writes':
|
|
@@ -28,6 +32,34 @@ export function dispatchCron(row) {
|
|
|
28
32
|
logger.error({ name: row.name, target: row.enqueueInto }, 'cron has unknown target queue');
|
|
29
33
|
}
|
|
30
34
|
}
|
|
35
|
+
function dispatchInternal(row, payload) {
|
|
36
|
+
if (!payload || typeof payload !== 'object' || !('handler' in payload)) {
|
|
37
|
+
logger.error({ id: row.id, name: row.name }, "internal cron missing 'handler' in payload");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const handlerName = payload.handler;
|
|
41
|
+
if (typeof handlerName !== 'string') {
|
|
42
|
+
logger.error({ id: row.id, name: row.name }, 'internal cron handler name not a string');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const handler = getInternalCronHandler(handlerName);
|
|
46
|
+
if (!handler) {
|
|
47
|
+
logger.error({ id: row.id, name: row.name, handler: handlerName }, 'internal cron handler not registered');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Fire-and-forget. Handler errors are caught and logged but the
|
|
51
|
+
// cron row still gets marked fired so we don't stack up retries.
|
|
52
|
+
try {
|
|
53
|
+
const result = handler();
|
|
54
|
+
if (result && typeof result.catch === 'function') {
|
|
55
|
+
;
|
|
56
|
+
result.catch((err) => logger.error({ err, handler: handlerName }, 'internal cron handler rejected'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.error({ err, handler: handlerName }, 'internal cron handler threw');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
31
63
|
function dispatchOutbound(row, payload) {
|
|
32
64
|
if (!isOutboundPayload(payload)) {
|
|
33
65
|
logger.error({ id: row.id, payload }, 'cron outbound payload malformed');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// In-process handler registry for the 'internal' cron target.
|
|
2
|
+
//
|
|
3
|
+
// Not every periodic task fits the queue→worker model. Things like
|
|
4
|
+
// "run the journal observer sweep" or "regenerate compressed memory"
|
|
5
|
+
// are pure in-process work — there's no queue to enqueue into, the
|
|
6
|
+
// orchestrator just needs to call a function.
|
|
7
|
+
//
|
|
8
|
+
// Mechanism: cron rows with enqueue_into='internal' carry a payload
|
|
9
|
+
// with `handler: <name>`. The dispatcher looks up the name in this
|
|
10
|
+
// registry and invokes the function. Registry is populated at boot,
|
|
11
|
+
// before the orchestrator starts polling.
|
|
12
|
+
import { logger } from '../logger.js';
|
|
13
|
+
const registry = new Map();
|
|
14
|
+
export function registerInternalCronHandler(name, handler) {
|
|
15
|
+
if (registry.has(name)) {
|
|
16
|
+
logger.warn({ name }, 'internal cron handler already registered; overwriting');
|
|
17
|
+
}
|
|
18
|
+
registry.set(name, handler);
|
|
19
|
+
}
|
|
20
|
+
export function getInternalCronHandler(name) {
|
|
21
|
+
return registry.get(name);
|
|
22
|
+
}
|
|
23
|
+
export function listInternalCronHandlers() {
|
|
24
|
+
return [...registry.keys()];
|
|
25
|
+
}
|