omnikey-cli 1.0.34 → 1.0.36
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.
|
@@ -171,9 +171,6 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
|
|
|
171
171
|
return result;
|
|
172
172
|
}
|
|
173
173
|
const aiModel = (0, ai_client_1.getDefaultModel)(config_1.config.aiProvider, 'smart');
|
|
174
|
-
// In-memory cache: sessionId -> live SessionState. Hydrated from DB on first
|
|
175
|
-
// access and written back after each turn so restarts resume correctly.
|
|
176
|
-
const sessionMessages = new Map();
|
|
177
174
|
const MAX_TURNS = 20;
|
|
178
175
|
// ─── DB helpers ───────────────────────────────────────────────────────────────
|
|
179
176
|
async function persistSessionToDB(sessionId, state) {
|
|
@@ -216,22 +213,7 @@ async function enforceSessionCap(subscriptionId, logger) {
|
|
|
216
213
|
}
|
|
217
214
|
}
|
|
218
215
|
async function getOrCreateSession(sessionId, subscription, platform, log, isCronJob = false) {
|
|
219
|
-
// 1.
|
|
220
|
-
const existing = sessionMessages.get(sessionId);
|
|
221
|
-
if (existing) {
|
|
222
|
-
log.debug('Reusing existing agent session (in-memory)', {
|
|
223
|
-
sessionId,
|
|
224
|
-
subscriptionId: existing.subscription.id,
|
|
225
|
-
turns: existing.turns,
|
|
226
|
-
});
|
|
227
|
-
return {
|
|
228
|
-
sessionState: existing,
|
|
229
|
-
hasStoredPrompt: existing.history
|
|
230
|
-
.filter((h) => h.role === 'user')
|
|
231
|
-
.some((h) => typeof h.content === 'string' && h.content.includes('<stored_instructions>')),
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
// 2. Try to resume from a persisted DB record.
|
|
216
|
+
// 1. Try to resume from a persisted DB record.
|
|
235
217
|
try {
|
|
236
218
|
const dbSession = await agentSession_1.AgentSession.findOne({
|
|
237
219
|
where: { id: sessionId, subscriptionId: subscription.id },
|
|
@@ -243,7 +225,6 @@ async function getOrCreateSession(sessionId, subscription, platform, log, isCron
|
|
|
243
225
|
history,
|
|
244
226
|
turns: dbSession.turns,
|
|
245
227
|
};
|
|
246
|
-
sessionMessages.set(sessionId, entry);
|
|
247
228
|
log.info('Resumed agent session from DB', {
|
|
248
229
|
sessionId,
|
|
249
230
|
subscriptionId: subscription.id,
|
|
@@ -263,7 +244,7 @@ async function getOrCreateSession(sessionId, subscription, platform, log, isCron
|
|
|
263
244
|
error: err,
|
|
264
245
|
});
|
|
265
246
|
}
|
|
266
|
-
//
|
|
247
|
+
// 2. Create a brand-new session and persist it to the DB.
|
|
267
248
|
const prompt = await (0, featureRoutes_1.getPromptForCommand)(log, 'task', subscription).catch((err) => {
|
|
268
249
|
log.error('Failed to get system prompt for new agent session', { error: err });
|
|
269
250
|
return '';
|
|
@@ -293,17 +274,19 @@ ${prompt}
|
|
|
293
274
|
],
|
|
294
275
|
turns: 0,
|
|
295
276
|
};
|
|
296
|
-
sessionMessages.set(sessionId, entry);
|
|
297
277
|
// Persist immediately so that GET /sessions picks it up right away.
|
|
298
278
|
try {
|
|
299
|
-
await agentSession_1.AgentSession.
|
|
300
|
-
id: sessionId,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
279
|
+
await agentSession_1.AgentSession.findOrCreate({
|
|
280
|
+
where: { id: sessionId, subscriptionId: subscription.id },
|
|
281
|
+
defaults: {
|
|
282
|
+
id: sessionId,
|
|
283
|
+
subscriptionId: subscription.id,
|
|
284
|
+
title: 'New Session',
|
|
285
|
+
platform: platform ?? null,
|
|
286
|
+
historyJson: JSON.stringify(entry.history),
|
|
287
|
+
turns: 0,
|
|
288
|
+
lastActiveAt: new Date(),
|
|
289
|
+
},
|
|
307
290
|
});
|
|
308
291
|
// Prune oldest sessions after each creation so the cap is always respected.
|
|
309
292
|
void enforceSessionCap(subscription.id, log);
|
|
@@ -321,7 +304,7 @@ ${prompt}
|
|
|
321
304
|
hasStoredPrompt: !!prompt,
|
|
322
305
|
};
|
|
323
306
|
}
|
|
324
|
-
async function
|
|
307
|
+
async function runAgentTurnInternal(sessionId, subscription, clientMessage, send, log, options) {
|
|
325
308
|
const { sessionState: session, hasStoredPrompt } = await getOrCreateSession(sessionId, subscription, clientMessage.platform, log, options?.isCronJob);
|
|
326
309
|
// Count this call as one agent iteration.
|
|
327
310
|
session.turns += 1;
|
|
@@ -444,10 +427,8 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log, o
|
|
|
444
427
|
if (!content && result.finish_reason !== 'tool_calls') {
|
|
445
428
|
log.warn('Agent LLM returned empty content; sending generic error to client.');
|
|
446
429
|
const errorMessage = 'The agent returned an empty response. Please try again.';
|
|
430
|
+
await persistSessionToDB(sessionId, session);
|
|
447
431
|
(0, utils_1.sendFinalAnswer)(send, sessionId, errorMessage, true);
|
|
448
|
-
// Evict from the in-memory cache; the DB record is kept so the session
|
|
449
|
-
// appears in the list and can be retried or deleted by the user.
|
|
450
|
-
sessionMessages.delete(sessionId);
|
|
451
432
|
return;
|
|
452
433
|
}
|
|
453
434
|
// If the model requested web tool calls, execute them and get a follow-up
|
|
@@ -507,7 +488,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log, o
|
|
|
507
488
|
'No plain text. No other format.',
|
|
508
489
|
].join('\n'),
|
|
509
490
|
});
|
|
510
|
-
await
|
|
491
|
+
await runAgentTurnInternal(sessionId, subscription, {
|
|
511
492
|
sender: 'agent',
|
|
512
493
|
session_id: sessionId,
|
|
513
494
|
content: '',
|
|
@@ -553,7 +534,6 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log, o
|
|
|
553
534
|
});
|
|
554
535
|
(0, utils_1.pushToSessionHistory)(logger_1.logger, session, { role: 'assistant', content });
|
|
555
536
|
await persistSessionToDB(sessionId, session);
|
|
556
|
-
sessionMessages.delete(sessionId);
|
|
557
537
|
send({
|
|
558
538
|
session_id: sessionId,
|
|
559
539
|
sender: 'agent',
|
|
@@ -572,7 +552,6 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log, o
|
|
|
572
552
|
});
|
|
573
553
|
(0, utils_1.pushToSessionHistory)(log, session, { role: 'assistant', content });
|
|
574
554
|
await persistSessionToDB(sessionId, session);
|
|
575
|
-
sessionMessages.delete(sessionId);
|
|
576
555
|
send({
|
|
577
556
|
session_id: sessionId,
|
|
578
557
|
sender: 'agent',
|
|
@@ -583,20 +562,20 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log, o
|
|
|
583
562
|
log.warn('Agent returned empty content with no recognized tags; sending error', {
|
|
584
563
|
sessionId,
|
|
585
564
|
});
|
|
565
|
+
await persistSessionToDB(sessionId, session);
|
|
586
566
|
(0, utils_1.sendFinalAnswer)(send, sessionId, 'The agent returned an empty response. Please try again.', true);
|
|
587
|
-
// Evict from in-memory cache; DB record is preserved.
|
|
588
|
-
sessionMessages.delete(sessionId);
|
|
589
567
|
}
|
|
590
568
|
}
|
|
591
569
|
catch (err) {
|
|
592
570
|
log.error('Agent LLM call failed', { error: err });
|
|
593
571
|
const errorMessage = 'Agent failed to call language model. Please try again later.';
|
|
572
|
+
await persistSessionToDB(sessionId, session);
|
|
594
573
|
(0, utils_1.sendFinalAnswer)(send, sessionId, errorMessage, true);
|
|
595
|
-
// Evict from in-memory cache; DB record is preserved so the user can
|
|
596
|
-
// review or delete the session from the client.
|
|
597
|
-
sessionMessages.delete(sessionId);
|
|
598
574
|
}
|
|
599
575
|
}
|
|
576
|
+
async function runAgentTurn(sessionId, subscription, clientMessage, send, log, options) {
|
|
577
|
+
await runAgentTurnInternal(sessionId, subscription, clientMessage, send, log, options);
|
|
578
|
+
}
|
|
600
579
|
function attachAgentWebSocketServer(server) {
|
|
601
580
|
const wss = new ws_1.WebSocketServer({ server, path: '/ws/omni-agent' });
|
|
602
581
|
wss.on('connection', (ws, req) => {
|
|
@@ -733,8 +712,6 @@ function createAgentRouter() {
|
|
|
733
712
|
res.status(404).json({ error: 'Session not found' });
|
|
734
713
|
return;
|
|
735
714
|
}
|
|
736
|
-
// Also remove from the in-memory cache if it was loaded.
|
|
737
|
-
sessionMessages.delete(sessionId);
|
|
738
715
|
res.status(200).json({ deleted: true });
|
|
739
716
|
}
|
|
740
717
|
catch (err) {
|
|
@@ -10,18 +10,33 @@ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
|
10
10
|
const logger_1 = require("./logger");
|
|
11
11
|
const config_1 = require("./config");
|
|
12
12
|
const subscription_1 = require("./models/subscription");
|
|
13
|
+
const SELF_HOSTED_SUBSCRIPTION_ID = 'self-hosted-local-subscription';
|
|
13
14
|
async function selfHostedSubscription() {
|
|
14
15
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Reuse any existing self-hosted record (including legacy IDs) first.
|
|
17
|
+
const existing = await subscription_1.Subscription.findOne({ where: { isSelfHosted: true } });
|
|
18
|
+
if (existing)
|
|
19
|
+
return existing;
|
|
20
|
+
// Use a deterministic primary key so concurrent first-time requests do not
|
|
21
|
+
// create duplicate rows.
|
|
22
|
+
const [subscription, created] = await subscription_1.Subscription.findOrCreate({
|
|
23
|
+
where: { id: SELF_HOSTED_SUBSCRIPTION_ID },
|
|
24
|
+
defaults: {
|
|
25
|
+
id: SELF_HOSTED_SUBSCRIPTION_ID,
|
|
18
26
|
email: 'local-user@omnikey.ai',
|
|
19
27
|
licenseKey: 'self-hosted',
|
|
20
28
|
subscriptionStatus: 'active',
|
|
21
29
|
isSelfHosted: true,
|
|
22
|
-
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
if (created) {
|
|
23
33
|
logger_1.logger.info('Created self-hosted subscription record in database.');
|
|
24
34
|
}
|
|
35
|
+
// Ensure deterministic row remains flagged for self-hosted mode.
|
|
36
|
+
if (!subscription.isSelfHosted) {
|
|
37
|
+
subscription.isSelfHosted = true;
|
|
38
|
+
await subscription.save();
|
|
39
|
+
}
|
|
25
40
|
return subscription;
|
|
26
41
|
}
|
|
27
42
|
catch (err) {
|
|
@@ -31,37 +46,37 @@ async function selfHostedSubscription() {
|
|
|
31
46
|
}
|
|
32
47
|
async function authMiddleware(req, res, next) {
|
|
33
48
|
const authHeader = req.headers.authorization;
|
|
34
|
-
logger_1.logger.
|
|
49
|
+
const requestLogger = logger_1.logger.child({ traceId: (0, crypto_1.randomUUID)() });
|
|
50
|
+
res.locals.logger = requestLogger;
|
|
35
51
|
if (config_1.config.blockSaas) {
|
|
36
|
-
|
|
52
|
+
requestLogger.warn('Blocking SaaS access: rejecting request due to BLOCK_SAAS=true');
|
|
37
53
|
return res.status(403).json({ error: 'SaaS access is blocked.' });
|
|
38
54
|
}
|
|
39
55
|
if (config_1.config.isSelfHosted || !config_1.config.jwtSecret) {
|
|
40
|
-
|
|
56
|
+
requestLogger.info('Self-hosted mode: skipping auth middleware.');
|
|
41
57
|
if (config_1.config.isSelfHosted) {
|
|
42
58
|
res.locals.subscription = await selfHostedSubscription();
|
|
43
|
-
res.locals.logger = logger_1.logger;
|
|
44
59
|
}
|
|
45
60
|
return next();
|
|
46
61
|
}
|
|
47
62
|
if (!authHeader) {
|
|
48
|
-
|
|
63
|
+
requestLogger.warn('Missing Authorization header on feature route.');
|
|
49
64
|
return res.status(401).json({ error: 'Missing bearer token.' });
|
|
50
65
|
}
|
|
51
66
|
const [scheme, token] = authHeader.split(' ');
|
|
52
67
|
if (scheme !== 'Bearer' || !token) {
|
|
53
|
-
|
|
68
|
+
requestLogger.warn('Malformed Authorization header on feature route.');
|
|
54
69
|
return res.status(401).json({ error: 'Invalid authorization header.' });
|
|
55
70
|
}
|
|
56
71
|
try {
|
|
57
72
|
const decoded = jsonwebtoken_1.default.verify(token, config_1.config.jwtSecret);
|
|
58
73
|
const subscription = await subscription_1.Subscription.findByPk(decoded.sid);
|
|
59
74
|
if (!subscription) {
|
|
60
|
-
|
|
75
|
+
requestLogger.warn('Subscription not found for JWT.', { sid: decoded.sid });
|
|
61
76
|
return res.status(403).json({ error: 'Invalid or expired token.' });
|
|
62
77
|
}
|
|
63
78
|
if (subscription.subscriptionStatus == 'expired') {
|
|
64
|
-
|
|
79
|
+
requestLogger.warn('Inactive subscription for JWT.', {
|
|
65
80
|
sid: decoded.sid,
|
|
66
81
|
status: subscription.subscriptionStatus,
|
|
67
82
|
});
|
|
@@ -71,19 +86,18 @@ async function authMiddleware(req, res, next) {
|
|
|
71
86
|
if (subscription.licenseKeyExpiresAt && subscription.licenseKeyExpiresAt <= now) {
|
|
72
87
|
subscription.subscriptionStatus = 'expired';
|
|
73
88
|
await subscription.save();
|
|
74
|
-
|
|
89
|
+
requestLogger.info('Subscription key has expired during activation.', {
|
|
75
90
|
subscriptionId: subscription.id,
|
|
76
91
|
});
|
|
77
92
|
return res
|
|
78
93
|
.status(403)
|
|
79
94
|
.json({ error: 'Subscription has expired.', subscriptionStatus: 'expired' });
|
|
80
95
|
}
|
|
81
|
-
res.locals.logger = logger_1.logger;
|
|
82
96
|
res.locals.subscription = subscription;
|
|
83
97
|
next();
|
|
84
98
|
}
|
|
85
99
|
catch (err) {
|
|
86
|
-
|
|
100
|
+
requestLogger.warn('Invalid or expired JWT on feature route.', { error: err });
|
|
87
101
|
return res.status(403).json({ error: 'Invalid or expired token.' });
|
|
88
102
|
}
|
|
89
103
|
}
|
|
@@ -3,9 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getDownloadCounts = getDownloadCounts;
|
|
4
4
|
exports.incrementDownloadCount = incrementDownloadCount;
|
|
5
5
|
const storage_1 = require("@google-cloud/storage");
|
|
6
|
+
const zod_1 = require("zod");
|
|
6
7
|
const logger_1 = require("../logger");
|
|
7
8
|
const config_1 = require("../config");
|
|
8
9
|
const DEFAULT_COUNTS = { macos: 0, windows: 0 };
|
|
10
|
+
const downloadCountsSchema = zod_1.z.object({
|
|
11
|
+
macos: zod_1.z.number().nonnegative().optional(),
|
|
12
|
+
windows: zod_1.z.number().nonnegative().optional(),
|
|
13
|
+
});
|
|
14
|
+
function parseDownloadCounts(raw) {
|
|
15
|
+
const json = JSON.parse(raw);
|
|
16
|
+
const parsed = downloadCountsSchema.safeParse(json);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
return { ...DEFAULT_COUNTS };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
macos: parsed.data.macos ?? 0,
|
|
22
|
+
windows: parsed.data.windows ?? 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
9
25
|
// Initialised once at module load — uses Application Default Credentials when
|
|
10
26
|
// running on Cloud Run (or any GCP environment), and falls back to ADC from
|
|
11
27
|
// the local environment during development.
|
|
@@ -30,28 +46,57 @@ async function readCounts(bucketName, objectPath) {
|
|
|
30
46
|
return { ...DEFAULT_COUNTS };
|
|
31
47
|
}
|
|
32
48
|
const [contents] = await file.download();
|
|
33
|
-
|
|
49
|
+
return parseDownloadCounts(contents.toString('utf8'));
|
|
50
|
+
}
|
|
51
|
+
async function readCountsWithGeneration(bucketName, objectPath) {
|
|
52
|
+
const file = storage.bucket(bucketName).file(objectPath);
|
|
53
|
+
const [exists] = await file.exists();
|
|
54
|
+
if (!exists) {
|
|
55
|
+
return { counts: { ...DEFAULT_COUNTS }, generation: null, exists: false };
|
|
56
|
+
}
|
|
57
|
+
const [[metadata], [contents]] = await Promise.all([file.getMetadata(), file.download()]);
|
|
58
|
+
const counts = parseDownloadCounts(contents.toString('utf8'));
|
|
34
59
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
counts,
|
|
61
|
+
generation: metadata.generation ?? null,
|
|
62
|
+
exists: true,
|
|
37
63
|
};
|
|
38
64
|
}
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
65
|
+
function isGcsPreconditionError(err) {
|
|
66
|
+
const maybe = err;
|
|
67
|
+
return (maybe?.code === 412 ||
|
|
68
|
+
maybe?.message?.includes('conditionNotMet') === true ||
|
|
69
|
+
maybe?.message?.includes('Precondition Failed') === true);
|
|
45
70
|
}
|
|
46
71
|
async function incrementDownloadCount(platform) {
|
|
47
72
|
const gcs = getGcsConfig();
|
|
48
73
|
if (!gcs)
|
|
49
74
|
return;
|
|
75
|
+
const file = storage.bucket(gcs.bucketName).file(gcs.objectPath);
|
|
76
|
+
const MAX_RETRIES = 6;
|
|
50
77
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
79
|
+
const { counts, generation, exists } = await readCountsWithGeneration(gcs.bucketName, gcs.objectPath);
|
|
80
|
+
counts[platform] += 1;
|
|
81
|
+
try {
|
|
82
|
+
await file.save(JSON.stringify(counts), {
|
|
83
|
+
contentType: 'application/json',
|
|
84
|
+
resumable: false,
|
|
85
|
+
preconditionOpts: exists
|
|
86
|
+
? { ifGenerationMatch: Number(generation) }
|
|
87
|
+
: { ifGenerationMatch: 0 },
|
|
88
|
+
});
|
|
89
|
+
logger_1.logger.info(`Download count incremented for ${platform}.`, { counts, attempt });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (isGcsPreconditionError(err) && attempt < MAX_RETRIES) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
logger_1.logger.warn(`Download count increment exhausted retries for ${platform}.`);
|
|
55
100
|
}
|
|
56
101
|
catch (err) {
|
|
57
102
|
logger_1.logger.error(`Failed to increment download count for ${platform}.`, { error: err });
|
package/backend-dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const zlib_1 = __importDefault(require("zlib"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
11
12
|
const subscriptionRoutes_1 = require("./subscriptionRoutes");
|
|
12
13
|
const featureRoutes_1 = require("./featureRoutes");
|
|
13
14
|
const db_1 = require("./db");
|
|
@@ -23,6 +24,7 @@ require("./models/scheduledJob");
|
|
|
23
24
|
const bucket_adapter_1 = require("./bucket-adapter");
|
|
24
25
|
const app = (0, express_1.default)();
|
|
25
26
|
const PORT = Number(config_1.config.port);
|
|
27
|
+
const IS_CRON_CHILD = process.env.OMNIKEY_CRON_CHILD === '1';
|
|
26
28
|
app.set('trust proxy', 1);
|
|
27
29
|
app.use((0, cors_1.default)());
|
|
28
30
|
app.use(express_1.default.json());
|
|
@@ -74,8 +76,8 @@ app.get('/macos/appcast', (req, res) => {
|
|
|
74
76
|
const appcastUrl = `${baseUrl}/macos/appcast`;
|
|
75
77
|
// These should match the values embedded into the macOS app
|
|
76
78
|
// Info.plist in macOS/build_release_dmg.sh.
|
|
77
|
-
const bundleVersion = '
|
|
78
|
-
const shortVersion = '1.0.
|
|
79
|
+
const bundleVersion = '25';
|
|
80
|
+
const shortVersion = '1.0.24';
|
|
79
81
|
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
80
82
|
<rss version="2.0"
|
|
81
83
|
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
|
|
@@ -165,9 +167,38 @@ app.get('*', (_req, res) => {
|
|
|
165
167
|
res.sendFile(path_1.default.join(process.cwd(), 'public', 'index.html'));
|
|
166
168
|
});
|
|
167
169
|
let server = null;
|
|
170
|
+
let cronChildProcess = null;
|
|
171
|
+
function startCronChildProcess() {
|
|
172
|
+
if (IS_CRON_CHILD || cronChildProcess)
|
|
173
|
+
return;
|
|
174
|
+
const childPort = PORT + 1;
|
|
175
|
+
const entry = process.argv[1] || __filename;
|
|
176
|
+
cronChildProcess = (0, child_process_1.fork)(entry, [], {
|
|
177
|
+
env: {
|
|
178
|
+
...process.env,
|
|
179
|
+
OMNIKEY_CRON_CHILD: '1',
|
|
180
|
+
OMNIKEY_PORT: String(childPort),
|
|
181
|
+
},
|
|
182
|
+
execArgv: process.execArgv,
|
|
183
|
+
stdio: 'inherit',
|
|
184
|
+
});
|
|
185
|
+
logger_1.logger.info('Spawned cron child process.', {
|
|
186
|
+
pid: cronChildProcess.pid,
|
|
187
|
+
port: childPort,
|
|
188
|
+
});
|
|
189
|
+
cronChildProcess.on('exit', (code, signal) => {
|
|
190
|
+
logger_1.logger.warn('Cron child process exited.', { code, signal });
|
|
191
|
+
cronChildProcess = null;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
168
194
|
async function start() {
|
|
169
195
|
try {
|
|
170
196
|
await (0, db_1.initDatabase)(logger_1.logger);
|
|
197
|
+
if (IS_CRON_CHILD) {
|
|
198
|
+
logger_1.logger.info('Starting cron child process mode.', { port: PORT });
|
|
199
|
+
(0, scheduledJobExecutor_1.startScheduledJobExecutor)();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
171
202
|
server = app.listen(PORT, () => {
|
|
172
203
|
logger_1.logger.info(`Enhancer API listening on http://localhost:${PORT}`, {
|
|
173
204
|
isSelfHosted: config_1.config.isSelfHosted,
|
|
@@ -181,7 +212,7 @@ async function start() {
|
|
|
181
212
|
(0, agentServer_1.attachAgentWebSocketServer)(server);
|
|
182
213
|
}
|
|
183
214
|
if (config_1.config.isSelfHosted) {
|
|
184
|
-
(
|
|
215
|
+
startCronChildProcess();
|
|
185
216
|
}
|
|
186
217
|
}
|
|
187
218
|
catch (err) {
|
|
@@ -192,6 +223,15 @@ async function start() {
|
|
|
192
223
|
start();
|
|
193
224
|
function gracefulShutdown(signal) {
|
|
194
225
|
logger_1.logger.info(`Received ${signal}. Starting graceful shutdown...`);
|
|
226
|
+
if (cronChildProcess) {
|
|
227
|
+
cronChildProcess.kill('SIGTERM');
|
|
228
|
+
cronChildProcess = null;
|
|
229
|
+
}
|
|
230
|
+
if (IS_CRON_CHILD) {
|
|
231
|
+
logger_1.logger.info('Cron child process exiting.');
|
|
232
|
+
process.exit(0);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
195
235
|
if (!server) {
|
|
196
236
|
logger_1.logger.info('Server was not started or already closed. Exiting process.');
|
|
197
237
|
process.exit(0);
|
|
@@ -144,7 +144,7 @@ function runCronJob(job, subscription, sessionId) {
|
|
|
144
144
|
content: output,
|
|
145
145
|
is_terminal_output: true,
|
|
146
146
|
is_error: isError,
|
|
147
|
-
}, send, logger_1.logger, { maxTurns: MAX_CRON_TURNS }).catch((err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
147
|
+
}, send, logger_1.logger, { maxTurns: MAX_CRON_TURNS, isCronJob: true }).catch((err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
if (FINAL_ANSWER_RE.test(content)) {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"registry": "https://registry.npmjs.org/"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.0.
|
|
7
|
+
"version": "1.0.36",
|
|
8
8
|
"description": "CLI for onboarding users to Omnikey AI and configuring OPENAI_API_KEY. Use Yarn for install/build.",
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=14.0.0",
|