omnikey-cli 1.0.25 → 1.0.26
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/backend-dist/agent/agentAuth.js +4 -0
- package/backend-dist/agent/agentServer.js +252 -8
- package/backend-dist/agent/utils.js +3 -2
- package/backend-dist/authMiddleware.js +4 -0
- package/backend-dist/config.js +1 -0
- package/backend-dist/db.js +7 -0
- package/backend-dist/index.js +5 -24
- package/backend-dist/models/agentSession.js +80 -0
- package/backend-dist/subscriptionRoutes.js +4 -0
- package/backend-dist/taskInstructionRoutes.js +11 -8
- package/package.json +1 -1
|
@@ -21,6 +21,10 @@ const authMiddleware_1 = require("../authMiddleware");
|
|
|
21
21
|
* @returns The authenticated `Subscription`, or `null` if authentication fails for any reason.
|
|
22
22
|
*/
|
|
23
23
|
async function authenticateFromAuthHeader(authHeader, log) {
|
|
24
|
+
if (config_1.config.blockSaas) {
|
|
25
|
+
log.warn('Blocking SaaS access: rejecting agent WebSocket connection due to BLOCK_SAAS=true');
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
24
28
|
if (config_1.config.isSelfHosted) {
|
|
25
29
|
log.info('Self-hosted mode: skipping JWT authentication for agent WebSocket connection.');
|
|
26
30
|
try {
|
|
@@ -37,16 +37,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.attachAgentWebSocketServer = attachAgentWebSocketServer;
|
|
40
|
+
exports.createAgentRouter = createAgentRouter;
|
|
41
|
+
const express_1 = __importDefault(require("express"));
|
|
40
42
|
const ws_1 = __importStar(require("ws"));
|
|
41
43
|
const cuid_1 = __importDefault(require("cuid"));
|
|
42
44
|
const config_1 = require("../config");
|
|
43
45
|
const logger_1 = require("../logger");
|
|
44
46
|
const subscription_1 = require("../models/subscription");
|
|
45
47
|
const subscriptionUsage_1 = require("../models/subscriptionUsage");
|
|
48
|
+
const agentSession_1 = require("../models/agentSession");
|
|
46
49
|
const agentPrompts_1 = require("./agentPrompts");
|
|
47
50
|
const featureRoutes_1 = require("../featureRoutes");
|
|
48
51
|
const web_search_provider_1 = require("../web-search/web-search-provider");
|
|
49
52
|
const agentAuth_1 = require("./agentAuth");
|
|
53
|
+
const authMiddleware_1 = require("../authMiddleware");
|
|
50
54
|
const utils_1 = require("./utils");
|
|
51
55
|
const ai_client_1 = require("../ai-client");
|
|
52
56
|
async function runToolLoop(initialResult, session, sessionId, send, log, tools, onUsage) {
|
|
@@ -137,12 +141,55 @@ async function runToolLoop(initialResult, session, sessionId, send, log, tools,
|
|
|
137
141
|
return result;
|
|
138
142
|
}
|
|
139
143
|
const aiModel = (0, ai_client_1.getDefaultModel)(config_1.config.aiProvider, 'smart');
|
|
144
|
+
// In-memory cache: sessionId -> live SessionState. Hydrated from DB on first
|
|
145
|
+
// access and written back after each turn so restarts resume correctly.
|
|
140
146
|
const sessionMessages = new Map();
|
|
141
147
|
const MAX_TURNS = 10;
|
|
148
|
+
// ─── DB helpers ───────────────────────────────────────────────────────────────
|
|
149
|
+
async function persistSessionToDB(sessionId, state) {
|
|
150
|
+
try {
|
|
151
|
+
const historyJson = JSON.stringify(state.history);
|
|
152
|
+
await agentSession_1.AgentSession.update({
|
|
153
|
+
historyJson,
|
|
154
|
+
turns: state.turns,
|
|
155
|
+
lastActiveAt: new Date(),
|
|
156
|
+
}, { where: { id: sessionId } });
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
logger_1.logger.error('Failed to persist agent session to DB', { sessionId, error: err });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Maximum number of sessions stored per subscription. When this limit is
|
|
163
|
+
// exceeded the oldest sessions (by lastActiveAt) are pruned automatically.
|
|
164
|
+
const SESSION_CAP = 50;
|
|
165
|
+
async function enforceSessionCap(subscriptionId, logger) {
|
|
166
|
+
try {
|
|
167
|
+
const count = await agentSession_1.AgentSession.count({ where: { subscriptionId } });
|
|
168
|
+
if (count <= SESSION_CAP)
|
|
169
|
+
return;
|
|
170
|
+
const excess = count - SESSION_CAP;
|
|
171
|
+
const oldest = await agentSession_1.AgentSession.findAll({
|
|
172
|
+
where: { subscriptionId },
|
|
173
|
+
order: [['last_active_at', 'ASC']],
|
|
174
|
+
limit: excess,
|
|
175
|
+
attributes: ['id'],
|
|
176
|
+
});
|
|
177
|
+
const ids = oldest.map((s) => s.id);
|
|
178
|
+
await agentSession_1.AgentSession.destroy({ where: { id: ids } });
|
|
179
|
+
logger.info('Pruned oldest agent sessions to enforce cap', {
|
|
180
|
+
subscriptionId,
|
|
181
|
+
pruned: ids.length,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
logger.error('Failed to enforce agent session cap', { subscriptionId, error: err });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
142
188
|
async function getOrCreateSession(sessionId, subscription, platform, log) {
|
|
189
|
+
// 1. Return the live in-memory entry if already loaded this process lifetime.
|
|
143
190
|
const existing = sessionMessages.get(sessionId);
|
|
144
191
|
if (existing) {
|
|
145
|
-
log.debug('Reusing existing agent session', {
|
|
192
|
+
log.debug('Reusing existing agent session (in-memory)', {
|
|
146
193
|
sessionId,
|
|
147
194
|
subscriptionId: existing.subscription.id,
|
|
148
195
|
turns: existing.turns,
|
|
@@ -151,10 +198,42 @@ async function getOrCreateSession(sessionId, subscription, platform, log) {
|
|
|
151
198
|
sessionState: existing,
|
|
152
199
|
hasStoredPrompt: existing.history
|
|
153
200
|
.filter((h) => h.role === 'user')
|
|
154
|
-
.some((h) => h.content.includes('<stored_instructions>')),
|
|
201
|
+
.some((h) => typeof h.content === 'string' && h.content.includes('<stored_instructions>')),
|
|
155
202
|
};
|
|
156
203
|
}
|
|
157
|
-
//
|
|
204
|
+
// 2. Try to resume from a persisted DB record.
|
|
205
|
+
try {
|
|
206
|
+
const dbSession = await agentSession_1.AgentSession.findOne({
|
|
207
|
+
where: { id: sessionId, subscriptionId: subscription.id },
|
|
208
|
+
});
|
|
209
|
+
if (dbSession) {
|
|
210
|
+
const history = JSON.parse(dbSession.historyJson);
|
|
211
|
+
const entry = {
|
|
212
|
+
subscription,
|
|
213
|
+
history,
|
|
214
|
+
turns: dbSession.turns,
|
|
215
|
+
};
|
|
216
|
+
sessionMessages.set(sessionId, entry);
|
|
217
|
+
log.info('Resumed agent session from DB', {
|
|
218
|
+
sessionId,
|
|
219
|
+
subscriptionId: subscription.id,
|
|
220
|
+
turns: entry.turns,
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
sessionState: entry,
|
|
224
|
+
hasStoredPrompt: history
|
|
225
|
+
.filter((h) => h.role === 'user')
|
|
226
|
+
.some((h) => typeof h.content === 'string' && h.content.includes('<stored_instructions>')),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
log.error('Failed to load agent session from DB; creating a fresh one', {
|
|
232
|
+
sessionId,
|
|
233
|
+
error: err,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// 3. Create a brand-new session in-memory and persist it to the DB.
|
|
158
237
|
const prompt = await (0, featureRoutes_1.getPromptForCommand)(log, 'task', subscription).catch((err) => {
|
|
159
238
|
log.error('Failed to get system prompt for new agent session', { error: err });
|
|
160
239
|
return '';
|
|
@@ -185,6 +264,23 @@ ${prompt}
|
|
|
185
264
|
turns: 0,
|
|
186
265
|
};
|
|
187
266
|
sessionMessages.set(sessionId, entry);
|
|
267
|
+
// Persist immediately so that GET /sessions picks it up right away.
|
|
268
|
+
try {
|
|
269
|
+
await agentSession_1.AgentSession.create({
|
|
270
|
+
id: sessionId,
|
|
271
|
+
subscriptionId: subscription.id,
|
|
272
|
+
title: 'New Session',
|
|
273
|
+
platform: platform ?? null,
|
|
274
|
+
historyJson: JSON.stringify(entry.history),
|
|
275
|
+
turns: 0,
|
|
276
|
+
lastActiveAt: new Date(),
|
|
277
|
+
});
|
|
278
|
+
// Prune oldest sessions after each creation so the cap is always respected.
|
|
279
|
+
void enforceSessionCap(subscription.id, log);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
log.error('Failed to create agent session in DB', { sessionId, error: err });
|
|
283
|
+
}
|
|
188
284
|
log.info('Created new agent session', {
|
|
189
285
|
sessionId,
|
|
190
286
|
subscriptionId: subscription.id,
|
|
@@ -243,6 +339,16 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
243
339
|
? userContent
|
|
244
340
|
: `<user_input>${(0, utils_1.createUserContent)(userContent, hasStoredPrompt)}</user_input>`,
|
|
245
341
|
});
|
|
342
|
+
// Use the first real user message (turn 1) as the session title.
|
|
343
|
+
if (session.turns === 1 && !isAssistance) {
|
|
344
|
+
const rawInput = clientMessage.content || '';
|
|
345
|
+
const titleSlug = rawInput.trim().slice(0, 60).replace(/\s+/g, ' ');
|
|
346
|
+
if (titleSlug) {
|
|
347
|
+
agentSession_1.AgentSession.update({ title: titleSlug }, { where: { id: sessionId } }).catch((err) => {
|
|
348
|
+
log.error('Failed to update agent session title', { sessionId, error: err });
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
246
352
|
}
|
|
247
353
|
// On the final turn we omit tools so the model is forced to emit a
|
|
248
354
|
// plain text <final_answer> rather than issuing another tool call.
|
|
@@ -250,7 +356,20 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
250
356
|
const tools = isFinalTurn ? undefined : (0, utils_1.buildAvailableTools)();
|
|
251
357
|
const recordUsage = async (result) => {
|
|
252
358
|
const usage = result.usage;
|
|
253
|
-
if (!usage
|
|
359
|
+
if (!usage)
|
|
360
|
+
return;
|
|
361
|
+
// Always update the per-session token counters in the DB.
|
|
362
|
+
try {
|
|
363
|
+
await agentSession_1.AgentSession.increment({
|
|
364
|
+
promptTokensUsed: usage.prompt_tokens,
|
|
365
|
+
completionTokensUsed: usage.completion_tokens,
|
|
366
|
+
totalTokensUsed: usage.total_tokens,
|
|
367
|
+
}, { where: { id: sessionId } });
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
log.error('Failed to update agent session token usage', { sessionId, error: err });
|
|
371
|
+
}
|
|
372
|
+
if (!subscription.id || config_1.config.isSelfHosted)
|
|
254
373
|
return;
|
|
255
374
|
try {
|
|
256
375
|
await subscriptionUsage_1.SubscriptionUsage.create({
|
|
@@ -290,8 +409,8 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
290
409
|
log.warn('Agent LLM returned empty content; sending generic error to client.');
|
|
291
410
|
const errorMessage = 'The agent returned an empty response. Please try again.';
|
|
292
411
|
(0, utils_1.sendFinalAnswer)(send, sessionId, errorMessage, true);
|
|
293
|
-
//
|
|
294
|
-
//
|
|
412
|
+
// Evict from the in-memory cache; the DB record is kept so the session
|
|
413
|
+
// appears in the list and can be retried or deleted by the user.
|
|
295
414
|
sessionMessages.delete(sessionId);
|
|
296
415
|
return;
|
|
297
416
|
}
|
|
@@ -398,6 +517,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
398
517
|
sender: 'agent',
|
|
399
518
|
content: hasFinalAnswerTag ? content : `<final_answer>\n${content}\n</final_answer>`,
|
|
400
519
|
});
|
|
520
|
+
await persistSessionToDB(sessionId, session);
|
|
401
521
|
sessionMessages.delete(sessionId);
|
|
402
522
|
}
|
|
403
523
|
else if (content) {
|
|
@@ -416,6 +536,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
416
536
|
sender: 'agent',
|
|
417
537
|
content: `<final_answer>\n${content}\n</final_answer>`,
|
|
418
538
|
});
|
|
539
|
+
await persistSessionToDB(sessionId, session);
|
|
419
540
|
sessionMessages.delete(sessionId);
|
|
420
541
|
}
|
|
421
542
|
else {
|
|
@@ -423,6 +544,7 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
423
544
|
sessionId,
|
|
424
545
|
});
|
|
425
546
|
(0, utils_1.sendFinalAnswer)(send, sessionId, 'The agent returned an empty response. Please try again.', true);
|
|
547
|
+
// Evict from in-memory cache; DB record is preserved.
|
|
426
548
|
sessionMessages.delete(sessionId);
|
|
427
549
|
}
|
|
428
550
|
}
|
|
@@ -430,8 +552,8 @@ async function runAgentTurn(sessionId, subscription, clientMessage, send, log) {
|
|
|
430
552
|
log.error('Agent LLM call failed', { error: err });
|
|
431
553
|
const errorMessage = 'Agent failed to call language model. Please try again later.';
|
|
432
554
|
(0, utils_1.sendFinalAnswer)(send, sessionId, errorMessage, true);
|
|
433
|
-
//
|
|
434
|
-
//
|
|
555
|
+
// Evict from in-memory cache; DB record is preserved so the user can
|
|
556
|
+
// review or delete the session from the client.
|
|
435
557
|
sessionMessages.delete(sessionId);
|
|
436
558
|
}
|
|
437
559
|
}
|
|
@@ -504,3 +626,125 @@ function attachAgentWebSocketServer(server) {
|
|
|
504
626
|
logger_1.logger.info('Agent WebSocket server attached at path /ws/omni-agent');
|
|
505
627
|
return wss;
|
|
506
628
|
}
|
|
629
|
+
// ─── REST router ─────────────────────────────────────────────────────────────
|
|
630
|
+
// Exposes agent session management endpoints that the macOS (and Windows)
|
|
631
|
+
// clients can call over plain HTTP before/during a session.
|
|
632
|
+
function createAgentRouter() {
|
|
633
|
+
const router = express_1.default.Router();
|
|
634
|
+
// Apply auth to every route in this router.
|
|
635
|
+
router.use(authMiddleware_1.authMiddleware);
|
|
636
|
+
// GET /api/agent/sessions
|
|
637
|
+
// Returns the most recent 50 sessions for the authenticated subscription,
|
|
638
|
+
// ordered by last activity descending.
|
|
639
|
+
router.get('/sessions', async (req, res) => {
|
|
640
|
+
const { subscription, logger: log } = res.locals;
|
|
641
|
+
try {
|
|
642
|
+
const sessions = await agentSession_1.AgentSession.findAll({
|
|
643
|
+
where: { subscriptionId: subscription.id },
|
|
644
|
+
order: [['last_active_at', 'DESC']],
|
|
645
|
+
limit: 50,
|
|
646
|
+
attributes: [
|
|
647
|
+
'id',
|
|
648
|
+
'title',
|
|
649
|
+
'platform',
|
|
650
|
+
'turns',
|
|
651
|
+
'totalTokensUsed',
|
|
652
|
+
'promptTokensUsed',
|
|
653
|
+
'completionTokensUsed',
|
|
654
|
+
'lastActiveAt',
|
|
655
|
+
'createdAt',
|
|
656
|
+
'updatedAt',
|
|
657
|
+
],
|
|
658
|
+
});
|
|
659
|
+
res.json(sessions.map((s) => ({
|
|
660
|
+
id: s.id,
|
|
661
|
+
title: s.title,
|
|
662
|
+
platform: s.platform,
|
|
663
|
+
turns: s.turns,
|
|
664
|
+
totalTokensUsed: Number(s.totalTokensUsed),
|
|
665
|
+
promptTokensUsed: Number(s.promptTokensUsed),
|
|
666
|
+
completionTokensUsed: Number(s.completionTokensUsed),
|
|
667
|
+
remainingContextTokens: Math.max(0, utils_1.MAX_HISTORY_TOTAL - Number(s.totalTokensUsed)),
|
|
668
|
+
contextBudget: utils_1.MAX_HISTORY_TOTAL,
|
|
669
|
+
lastActiveAt: s.lastActiveAt,
|
|
670
|
+
createdAt: s.createdAt,
|
|
671
|
+
updatedAt: s.updatedAt,
|
|
672
|
+
})));
|
|
673
|
+
}
|
|
674
|
+
catch (err) {
|
|
675
|
+
log.error('Failed to list agent sessions', { error: err });
|
|
676
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
// DELETE /api/agent/sessions/:sessionId
|
|
680
|
+
// Allows the client to explicitly delete a session and its stored history.
|
|
681
|
+
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
682
|
+
const { subscription, logger: log } = res.locals;
|
|
683
|
+
const { sessionId } = req.params;
|
|
684
|
+
if (!sessionId || typeof sessionId !== 'string' || sessionId.length > 128) {
|
|
685
|
+
res.status(400).json({ error: 'Invalid session ID' });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const deleted = await agentSession_1.AgentSession.destroy({
|
|
690
|
+
where: { id: sessionId, subscriptionId: subscription.id },
|
|
691
|
+
});
|
|
692
|
+
if (deleted === 0) {
|
|
693
|
+
res.status(404).json({ error: 'Session not found' });
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
// Also remove from the in-memory cache if it was loaded.
|
|
697
|
+
sessionMessages.delete(sessionId);
|
|
698
|
+
res.status(200).json({ deleted: true });
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
log.error('Failed to delete agent session', { sessionId, error: err });
|
|
702
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
// GET /api/agent/sessions/:sessionId/context
|
|
706
|
+
// Returns token usage and remaining context budget for a single session.
|
|
707
|
+
router.get('/sessions/:sessionId/context', async (req, res) => {
|
|
708
|
+
const { subscription, logger: log } = res.locals;
|
|
709
|
+
const { sessionId } = req.params;
|
|
710
|
+
// Validate that sessionId is a well-formed non-empty string (no path traversal).
|
|
711
|
+
if (!sessionId || typeof sessionId !== 'string' || sessionId.length > 128) {
|
|
712
|
+
res.status(400).json({ error: 'Invalid session ID' });
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
const session = await agentSession_1.AgentSession.findOne({
|
|
717
|
+
where: { id: sessionId, subscriptionId: subscription.id },
|
|
718
|
+
attributes: [
|
|
719
|
+
'id',
|
|
720
|
+
'title',
|
|
721
|
+
'turns',
|
|
722
|
+
'totalTokensUsed',
|
|
723
|
+
'promptTokensUsed',
|
|
724
|
+
'completionTokensUsed',
|
|
725
|
+
'lastActiveAt',
|
|
726
|
+
],
|
|
727
|
+
});
|
|
728
|
+
if (!session) {
|
|
729
|
+
res.status(404).json({ error: 'Session not found' });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
res.json({
|
|
733
|
+
id: session.id,
|
|
734
|
+
title: session.title,
|
|
735
|
+
turns: session.turns,
|
|
736
|
+
totalTokensUsed: Number(session.totalTokensUsed),
|
|
737
|
+
promptTokensUsed: Number(session.promptTokensUsed),
|
|
738
|
+
completionTokensUsed: Number(session.completionTokensUsed),
|
|
739
|
+
remainingContextTokens: Math.max(0, utils_1.MAX_HISTORY_TOTAL - Number(session.totalTokensUsed)),
|
|
740
|
+
contextBudget: utils_1.MAX_HISTORY_TOTAL,
|
|
741
|
+
lastActiveAt: session.lastActiveAt,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
log.error('Failed to fetch agent session context', { error: err });
|
|
746
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
return router;
|
|
750
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_HISTORY_TOTAL = void 0;
|
|
3
4
|
exports.buildAvailableTools = buildAvailableTools;
|
|
4
5
|
exports.createUserContent = createUserContent;
|
|
5
6
|
exports.sendFinalAnswer = sendFinalAnswer;
|
|
@@ -60,7 +61,7 @@ function sendFinalAnswer(send, sessionId, message, isError) {
|
|
|
60
61
|
const MAX_MESSAGE_CONTENT = (0, ai_client_1.getMaxMessageContentLength)(config_1.config.aiProvider);
|
|
61
62
|
// Total character budget across all history messages (derived from the
|
|
62
63
|
// provider's context-window size minus headroom for output + system prompt).
|
|
63
|
-
|
|
64
|
+
exports.MAX_HISTORY_TOTAL = (0, ai_client_1.getMaxHistoryLength)(config_1.config.aiProvider);
|
|
64
65
|
const FINAL_ANSWER_REQUEST = {
|
|
65
66
|
role: 'user',
|
|
66
67
|
content: 'Content was truncated because a length limit was reached. ' +
|
|
@@ -91,7 +92,7 @@ function pushToSessionHistory(logger, session, message) {
|
|
|
91
92
|
}
|
|
92
93
|
// 2. Total history length limit.
|
|
93
94
|
const currentTotal = session.history.reduce((acc, msg) => acc + (typeof msg.content === 'string' ? msg.content.length : 0), 0);
|
|
94
|
-
const remaining = MAX_HISTORY_TOTAL - currentTotal;
|
|
95
|
+
const remaining = exports.MAX_HISTORY_TOTAL - currentTotal;
|
|
95
96
|
if (content.length > remaining) {
|
|
96
97
|
content = content.slice(0, Math.max(0, remaining - FINAL_ANSWER_REQUEST.content.length));
|
|
97
98
|
limitHit = true;
|
|
@@ -32,6 +32,10 @@ async function selfHostedSubscription() {
|
|
|
32
32
|
async function authMiddleware(req, res, next) {
|
|
33
33
|
const authHeader = req.headers.authorization;
|
|
34
34
|
logger_1.logger.defaultMeta = { traceId: (0, crypto_1.randomUUID)() };
|
|
35
|
+
if (config_1.config.blockSaas) {
|
|
36
|
+
logger_1.logger.warn('Blocking SaaS access: rejecting request due to BLOCK_SAAS=true');
|
|
37
|
+
return res.status(403).json({ error: 'SaaS access is blocked.' });
|
|
38
|
+
}
|
|
35
39
|
if (config_1.config.isSelfHosted || !config_1.config.jwtSecret) {
|
|
36
40
|
logger_1.logger.info('Self-hosted mode: skipping auth middleware.');
|
|
37
41
|
if (config_1.config.isSelfHosted) {
|
package/backend-dist/config.js
CHANGED
package/backend-dist/db.js
CHANGED
|
@@ -14,6 +14,13 @@ if (config_1.config.isSelfHosted) {
|
|
|
14
14
|
logging: config_1.config.dbLogging ? console.log : false,
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
+
else if (config_1.config.blockSaas) {
|
|
18
|
+
exports.sequelize = sequelize = new sequelize_1.Sequelize({
|
|
19
|
+
dialect: 'sqlite',
|
|
20
|
+
storage: ':memory:',
|
|
21
|
+
logging: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
17
24
|
else if (config_1.config.databaseUrl) {
|
|
18
25
|
// Use Postgres for cloud/hosted
|
|
19
26
|
exports.sequelize = sequelize = new sequelize_1.Sequelize(config_1.config.databaseUrl, {
|
package/backend-dist/index.js
CHANGED
|
@@ -15,7 +15,8 @@ const logger_1 = require("./logger");
|
|
|
15
15
|
const taskInstructionRoutes_1 = require("./taskInstructionRoutes");
|
|
16
16
|
const config_1 = require("./config");
|
|
17
17
|
const agentServer_1 = require("./agent/agentServer");
|
|
18
|
-
|
|
18
|
+
// Importing AgentSession ensures the model is registered with Sequelize before initDatabase().
|
|
19
|
+
require("./models/agentSession");
|
|
19
20
|
const app = (0, express_1.default)();
|
|
20
21
|
const PORT = Number(config_1.config.port);
|
|
21
22
|
app.set('trust proxy', 1);
|
|
@@ -26,20 +27,13 @@ app.use(express_1.default.static(path_1.default.join(process.cwd(), 'public')));
|
|
|
26
27
|
app.use('/api/subscription', (0, subscriptionRoutes_1.createSubscriptionRouter)(logger_1.logger));
|
|
27
28
|
app.use('/api/feature', (0, featureRoutes_1.createFeatureRouter)());
|
|
28
29
|
app.use('/api/instructions', (0, taskInstructionRoutes_1.taskInstructionRouter)());
|
|
30
|
+
app.use('/api/agent', (0, agentServer_1.createAgentRouter)());
|
|
29
31
|
app.get('/macos/download', (_req, res) => {
|
|
30
32
|
const dmgPath = path_1.default.join(process.cwd(), 'macOS', 'OmniKeyAI.dmg');
|
|
31
33
|
if (!fs_1.default.existsSync(dmgPath)) {
|
|
32
34
|
res.status(404).send('File not found.');
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
if (!config_1.config.isSelfHosted) {
|
|
36
|
-
appDownload_1.AppDownload.findOrCreate({
|
|
37
|
-
where: { platform: 'macos' },
|
|
38
|
-
defaults: { platform: 'macos', count: 0 },
|
|
39
|
-
})
|
|
40
|
-
.then(([record]) => record.increment('count'))
|
|
41
|
-
.catch((err) => logger_1.logger.error('Failed to increment macOS download count.', { error: err }));
|
|
42
|
-
}
|
|
43
37
|
res.set({
|
|
44
38
|
'Content-Type': 'application/octet-stream',
|
|
45
39
|
'Content-Disposition': 'attachment; filename="OmniKeyAI.dmg"',
|
|
@@ -74,8 +68,8 @@ app.get('/macos/appcast', (req, res) => {
|
|
|
74
68
|
const appcastUrl = `${baseUrl}/macos/appcast`;
|
|
75
69
|
// These should match the values embedded into the macOS app
|
|
76
70
|
// Info.plist in macOS/build_release_dmg.sh.
|
|
77
|
-
const bundleVersion = '
|
|
78
|
-
const shortVersion = '1.0.
|
|
71
|
+
const bundleVersion = '20';
|
|
72
|
+
const shortVersion = '1.0.19';
|
|
79
73
|
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
|
80
74
|
<rss version="2.0"
|
|
81
75
|
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
|
|
@@ -113,14 +107,6 @@ app.get('/windows/download', (_req, res) => {
|
|
|
113
107
|
res.status(404).send('File not found.');
|
|
114
108
|
return;
|
|
115
109
|
}
|
|
116
|
-
if (!config_1.config.isSelfHosted) {
|
|
117
|
-
appDownload_1.AppDownload.findOrCreate({
|
|
118
|
-
where: { platform: 'windows' },
|
|
119
|
-
defaults: { platform: 'windows', count: 0 },
|
|
120
|
-
})
|
|
121
|
-
.then(([record]) => record.increment('count'))
|
|
122
|
-
.catch((err) => logger_1.logger.error('Failed to increment Windows download count.', { error: err }));
|
|
123
|
-
}
|
|
124
110
|
res.set({
|
|
125
111
|
'Content-Type': 'application/zip',
|
|
126
112
|
'Content-Disposition': `attachment; filename="${WIN_ZIP_FILENAME}"`,
|
|
@@ -155,11 +141,6 @@ app.get('/windows/update', (req, res) => {
|
|
|
155
141
|
releaseNotes: '',
|
|
156
142
|
});
|
|
157
143
|
});
|
|
158
|
-
app.get('/api/downloads', async (_req, res) => {
|
|
159
|
-
const rows = await appDownload_1.AppDownload.findAll({ where: { platform: ['macos', 'windows'] } });
|
|
160
|
-
const find = (p) => Number(rows.find((r) => r.platform === p)?.count ?? 0);
|
|
161
|
-
res.json({ macos: find('macos'), windows: find('windows') });
|
|
162
|
-
});
|
|
163
144
|
app.get('/health', (_req, res) => {
|
|
164
145
|
res.json({ status: 'ok' });
|
|
165
146
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AgentSession = void 0;
|
|
7
|
+
const sequelize_1 = require("sequelize");
|
|
8
|
+
const cuid_1 = __importDefault(require("cuid"));
|
|
9
|
+
const db_1 = require("../db");
|
|
10
|
+
const subscription_1 = require("./subscription");
|
|
11
|
+
class AgentSession extends sequelize_1.Model {
|
|
12
|
+
}
|
|
13
|
+
exports.AgentSession = AgentSession;
|
|
14
|
+
AgentSession.init({
|
|
15
|
+
id: {
|
|
16
|
+
type: sequelize_1.DataTypes.STRING,
|
|
17
|
+
primaryKey: true,
|
|
18
|
+
allowNull: false,
|
|
19
|
+
defaultValue: () => (0, cuid_1.default)(),
|
|
20
|
+
},
|
|
21
|
+
subscriptionId: {
|
|
22
|
+
type: sequelize_1.DataTypes.STRING,
|
|
23
|
+
allowNull: false,
|
|
24
|
+
field: 'subscription_id',
|
|
25
|
+
references: {
|
|
26
|
+
model: subscription_1.Subscription,
|
|
27
|
+
key: 'id',
|
|
28
|
+
},
|
|
29
|
+
onUpdate: 'CASCADE',
|
|
30
|
+
onDelete: 'CASCADE',
|
|
31
|
+
},
|
|
32
|
+
title: {
|
|
33
|
+
type: sequelize_1.DataTypes.STRING,
|
|
34
|
+
allowNull: false,
|
|
35
|
+
defaultValue: 'New Session',
|
|
36
|
+
},
|
|
37
|
+
platform: {
|
|
38
|
+
type: sequelize_1.DataTypes.STRING,
|
|
39
|
+
allowNull: true,
|
|
40
|
+
},
|
|
41
|
+
historyJson: {
|
|
42
|
+
type: sequelize_1.DataTypes.TEXT,
|
|
43
|
+
allowNull: false,
|
|
44
|
+
defaultValue: '[]',
|
|
45
|
+
field: 'history_json',
|
|
46
|
+
},
|
|
47
|
+
turns: {
|
|
48
|
+
type: sequelize_1.DataTypes.INTEGER,
|
|
49
|
+
allowNull: false,
|
|
50
|
+
defaultValue: 0,
|
|
51
|
+
},
|
|
52
|
+
promptTokensUsed: {
|
|
53
|
+
type: sequelize_1.DataTypes.BIGINT,
|
|
54
|
+
allowNull: false,
|
|
55
|
+
defaultValue: 0,
|
|
56
|
+
field: 'prompt_tokens_used',
|
|
57
|
+
},
|
|
58
|
+
completionTokensUsed: {
|
|
59
|
+
type: sequelize_1.DataTypes.BIGINT,
|
|
60
|
+
allowNull: false,
|
|
61
|
+
defaultValue: 0,
|
|
62
|
+
field: 'completion_tokens_used',
|
|
63
|
+
},
|
|
64
|
+
totalTokensUsed: {
|
|
65
|
+
type: sequelize_1.DataTypes.BIGINT,
|
|
66
|
+
allowNull: false,
|
|
67
|
+
defaultValue: 0,
|
|
68
|
+
field: 'total_tokens_used',
|
|
69
|
+
},
|
|
70
|
+
lastActiveAt: {
|
|
71
|
+
type: sequelize_1.DataTypes.DATE,
|
|
72
|
+
allowNull: false,
|
|
73
|
+
defaultValue: sequelize_1.DataTypes.NOW,
|
|
74
|
+
field: 'last_active_at',
|
|
75
|
+
},
|
|
76
|
+
}, {
|
|
77
|
+
sequelize: db_1.sequelize,
|
|
78
|
+
tableName: 'agent_sessions',
|
|
79
|
+
indexes: [{ fields: ['subscription_id'] }],
|
|
80
|
+
});
|
|
@@ -77,6 +77,10 @@ function createSubscriptionRouter(logger) {
|
|
|
77
77
|
router.post('/activate', async (req, res) => {
|
|
78
78
|
logger.defaultMeta = { traceId: (0, crypto_1.randomUUID)() };
|
|
79
79
|
logger.info('Handling subscription activation request using user key.');
|
|
80
|
+
if (config_1.config.blockSaas) {
|
|
81
|
+
logger.warn('Blocking SaaS access: rejecting subscription activation due to BLOCK_SAAS=true');
|
|
82
|
+
return res.status(403).json({ error: 'SaaS access is blocked.' });
|
|
83
|
+
}
|
|
80
84
|
try {
|
|
81
85
|
const body = zod_1.default.custom().parse(req.body);
|
|
82
86
|
const subscription = config_1.config.isSelfHosted
|
|
@@ -39,14 +39,6 @@ function taskInstructionRouter() {
|
|
|
39
39
|
router.post('/templates', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
40
40
|
const { logger, subscription } = res.locals;
|
|
41
41
|
try {
|
|
42
|
-
const existingCount = await subscriptionTaskTemplate_1.SubscriptionTaskTemplate.count({
|
|
43
|
-
where: { subscriptionId: subscription.id },
|
|
44
|
-
});
|
|
45
|
-
if (existingCount >= 5) {
|
|
46
|
-
return res
|
|
47
|
-
.status(400)
|
|
48
|
-
.json({ error: 'You can save up to 5 task templates per subscription.' });
|
|
49
|
-
}
|
|
50
42
|
const parseResult = taskTemplateSchema.parse(req.body);
|
|
51
43
|
const template = await subscriptionTaskTemplate_1.SubscriptionTaskTemplate.create({
|
|
52
44
|
subscriptionId: subscription.id,
|
|
@@ -153,5 +145,16 @@ function taskInstructionRouter() {
|
|
|
153
145
|
res.status(500).json({ error: 'Failed to set default task template.' });
|
|
154
146
|
}
|
|
155
147
|
});
|
|
148
|
+
router.post('/templates/clear-default', authMiddleware_1.authMiddleware, async (req, res) => {
|
|
149
|
+
const { logger, subscription } = res.locals;
|
|
150
|
+
try {
|
|
151
|
+
await subscriptionTaskTemplate_1.SubscriptionTaskTemplate.update({ isDefault: false }, { where: { subscriptionId: subscription.id, isDefault: true } });
|
|
152
|
+
res.status(204).send();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.error('Error clearing default task template.', { error: err });
|
|
156
|
+
res.status(500).json({ error: 'Failed to clear default task template.' });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
156
159
|
return router;
|
|
157
160
|
}
|
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.26",
|
|
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",
|