@hotmeshio/long-tail 0.2.3 → 0.2.4
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/build/lib/db/migrate.js +31 -19
- package/build/services/iam/bots.js +18 -7
- package/build/start/workers.js +24 -17
- package/package.json +1 -1
package/build/lib/db/migrate.js
CHANGED
|
@@ -43,27 +43,39 @@ const logger_1 = require("../logger");
|
|
|
43
43
|
const SCHEMAS_DIR = path.join(__dirname, 'schemas');
|
|
44
44
|
async function migrate() {
|
|
45
45
|
const pool = (0, index_1.getPool)();
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
// Advisory lock prevents concurrent containers from racing on migrations.
|
|
47
|
+
// Uses a dedicated client so the lock is held for the entire sequence.
|
|
48
|
+
const client = await pool.connect();
|
|
49
|
+
try {
|
|
50
|
+
await client.query('SELECT pg_advisory_lock(8675309)');
|
|
51
|
+
// ensure migration tracking table
|
|
52
|
+
await client.query(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS lt_migrations (
|
|
54
|
+
id SERIAL PRIMARY KEY,
|
|
55
|
+
name TEXT NOT NULL UNIQUE,
|
|
56
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
57
|
+
);
|
|
58
|
+
`);
|
|
59
|
+
// find and sort migration files
|
|
60
|
+
const files = fs.readdirSync(SCHEMAS_DIR)
|
|
61
|
+
.filter(f => f.endsWith('.sql'))
|
|
62
|
+
.sort();
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const { rows } = await client.query('SELECT 1 FROM lt_migrations WHERE name = $1', [file]);
|
|
65
|
+
if (rows.length === 0) {
|
|
66
|
+
const sql = fs.readFileSync(path.join(SCHEMAS_DIR, file), 'utf-8');
|
|
67
|
+
await client.query(sql);
|
|
68
|
+
await client.query('INSERT INTO lt_migrations (name) VALUES ($1)', [file]);
|
|
69
|
+
logger_1.loggerRegistry.info(`[migrate] applied: ${file}`);
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
}
|
|
73
|
+
finally {
|
|
74
|
+
// Advisory lock released when client is released (session-scoped),
|
|
75
|
+
// but release explicitly for clarity
|
|
76
|
+
await client.query('SELECT pg_advisory_unlock(8675309)').catch(() => { });
|
|
77
|
+
client.release();
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
// run directly: npx ts-node lib/db/migrate.ts
|
|
69
81
|
if (require.main === module) {
|
|
@@ -132,13 +132,24 @@ async function ensureSystemBot() {
|
|
|
132
132
|
const { rows } = await pool.query(sql_1.GET_USER_BY_EXTERNAL_ID, [SYSTEM_BOT_NAME]);
|
|
133
133
|
if (rows.length > 0)
|
|
134
134
|
return rows[0].id;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
try {
|
|
136
|
+
const bot = await createBot({
|
|
137
|
+
name: SYSTEM_BOT_NAME,
|
|
138
|
+
display_name: 'System',
|
|
139
|
+
description: 'System bot for cron and system-initiated workflows',
|
|
140
|
+
roles: [{ role: 'system', type: 'superadmin' }],
|
|
141
|
+
});
|
|
142
|
+
return bot.id;
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// Concurrent container already created it — re-read
|
|
146
|
+
if (err.code === '23505') {
|
|
147
|
+
const { rows: retry } = await pool.query(sql_1.GET_USER_BY_EXTERNAL_ID, [SYSTEM_BOT_NAME]);
|
|
148
|
+
if (retry.length > 0)
|
|
149
|
+
return retry[0].id;
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
142
153
|
}
|
|
143
154
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
144
155
|
function toBotRecord(user, description, createdBy) {
|
package/build/start/workers.js
CHANGED
|
@@ -103,6 +103,10 @@ async function startWorkers(startConfig, workers, builtinMcpServerFactories) {
|
|
|
103
103
|
logger_1.loggerRegistry.info('[long-tail] running migrations...');
|
|
104
104
|
await (0, migrate_1.migrate)();
|
|
105
105
|
const connection = buildConnection();
|
|
106
|
+
// Readonly mode: all user-provided workers are observers — skip crons, triggers, and agent seeding.
|
|
107
|
+
// System workers (mcpQuery, etc.) are always added by collectWorkers, so check the original config.
|
|
108
|
+
const userWorkers = startConfig.workers ?? [];
|
|
109
|
+
const isReadonly = userWorkers.length > 0 && userWorkers.every((w) => w.connection?.readonly);
|
|
106
110
|
if (workers.length) {
|
|
107
111
|
// Connect telemetry before HotMesh starts
|
|
108
112
|
if (telemetry_1.telemetryRegistry.hasAdapter) {
|
|
@@ -165,13 +169,15 @@ async function startWorkers(startConfig, workers, builtinMcpServerFactories) {
|
|
|
165
169
|
}
|
|
166
170
|
ltConfig.invalidate();
|
|
167
171
|
}
|
|
168
|
-
// Start maintenance cron
|
|
169
|
-
if (maintenance_1.maintenanceRegistry.hasConfig) {
|
|
172
|
+
// Start maintenance cron (skip in readonly/API mode)
|
|
173
|
+
if (maintenance_1.maintenanceRegistry.hasConfig && !isReadonly) {
|
|
170
174
|
await maintenance_1.maintenanceRegistry.connect();
|
|
171
175
|
logger_1.loggerRegistry.info('[long-tail] maintenance cron started');
|
|
172
176
|
}
|
|
173
|
-
// Start workflow cron schedules
|
|
174
|
-
|
|
177
|
+
// Start workflow cron schedules (skip in readonly/API mode)
|
|
178
|
+
if (!isReadonly) {
|
|
179
|
+
await cron_1.cronRegistry.connect();
|
|
180
|
+
}
|
|
175
181
|
// Connect MCP adapter
|
|
176
182
|
if (mcp_1.mcpRegistry.hasAdapter) {
|
|
177
183
|
await mcp_1.mcpRegistry.connect();
|
|
@@ -281,19 +287,20 @@ async function startWorkers(startConfig, workers, builtinMcpServerFactories) {
|
|
|
281
287
|
await events_1.eventRegistry.connect();
|
|
282
288
|
logger_1.loggerRegistry.info('[long-tail] event adapters connected');
|
|
283
289
|
}
|
|
284
|
-
// Arm agent event subscriptions (
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
// Arm agent event subscriptions and crons (skip in readonly/API mode)
|
|
291
|
+
if (!isReadonly) {
|
|
292
|
+
try {
|
|
293
|
+
await agentTriggerRegistry.connect(callbackAdapter);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
logger_1.loggerRegistry.warn(`[long-tail] agent trigger registry: ${err.message}`);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
await cron_1.cronRegistry.connectAgentCrons();
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
logger_1.loggerRegistry.warn(`[long-tail] agent cron schedules: ${err.message}`);
|
|
303
|
+
}
|
|
297
304
|
}
|
|
298
305
|
// Ensure system bot account exists for cron/system-initiated workflows
|
|
299
306
|
const { ensureSystemBot } = await Promise.resolve().then(() => __importStar(require('../services/iam/bots')));
|
package/package.json
CHANGED