@lovelybunch/api 1.0.57 → 1.0.59
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/lib/git.d.ts +13 -0
- package/dist/lib/git.js +137 -5
- package/dist/lib/jobs/global-job-scheduler.d.ts +2 -0
- package/dist/lib/jobs/global-job-scheduler.js +11 -0
- package/dist/lib/jobs/job-runner.d.ts +17 -0
- package/dist/lib/jobs/job-runner.js +167 -0
- package/dist/lib/jobs/job-scheduler.d.ts +39 -0
- package/dist/lib/jobs/job-scheduler.js +309 -0
- package/dist/lib/jobs/job-store.d.ts +16 -0
- package/dist/lib/jobs/job-store.js +211 -0
- package/dist/lib/storage/file-storage.js +7 -5
- package/dist/lib/terminal/terminal-manager.d.ts +2 -0
- package/dist/lib/terminal/terminal-manager.js +65 -0
- package/dist/routes/api/v1/ai/route.d.ts +1 -7
- package/dist/routes/api/v1/ai/route.js +25 -12
- package/dist/routes/api/v1/git/index.js +63 -1
- package/dist/routes/api/v1/jobs/[id]/route.d.ts +133 -0
- package/dist/routes/api/v1/jobs/[id]/route.js +135 -0
- package/dist/routes/api/v1/jobs/[id]/run/route.d.ts +31 -0
- package/dist/routes/api/v1/jobs/[id]/run/route.js +37 -0
- package/dist/routes/api/v1/jobs/index.d.ts +3 -0
- package/dist/routes/api/v1/jobs/index.js +14 -0
- package/dist/routes/api/v1/jobs/route.d.ts +108 -0
- package/dist/routes/api/v1/jobs/route.js +144 -0
- package/dist/routes/api/v1/jobs/status/route.d.ts +23 -0
- package/dist/routes/api/v1/jobs/status/route.js +21 -0
- package/dist/routes/api/v1/resources/[id]/route.d.ts +2 -44
- package/dist/server-with-static.js +5 -0
- package/dist/server.js +5 -0
- package/package.json +4 -4
- package/static/assets/index-CHq6mL1J.css +33 -0
- package/static/assets/index-QHnHUcsV.js +820 -0
- package/static/index.html +2 -2
- package/static/assets/index-CRg4lVi6.js +0 -779
- package/static/assets/index-VqhUTak4.css +0 -33
|
@@ -117,6 +117,7 @@ export class TerminalManager {
|
|
|
117
117
|
previewSockets: new Set(),
|
|
118
118
|
previewBuffer: '',
|
|
119
119
|
previewFlushTimer: null,
|
|
120
|
+
previewPingIntervals: new Map(),
|
|
120
121
|
};
|
|
121
122
|
this.sessions.set(sessionId, session);
|
|
122
123
|
// Set up PTY event handlers
|
|
@@ -206,6 +207,21 @@ export class TerminalManager {
|
|
|
206
207
|
}
|
|
207
208
|
session.websocket = ws;
|
|
208
209
|
session.lastActivity = new Date();
|
|
210
|
+
// Setup WebSocket keepalive ping (every 30 seconds)
|
|
211
|
+
const pingInterval = setInterval(() => {
|
|
212
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
213
|
+
try {
|
|
214
|
+
ws.ping();
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
clearInterval(pingInterval);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
clearInterval(pingInterval);
|
|
222
|
+
}
|
|
223
|
+
}, 30000);
|
|
224
|
+
session.pingInterval = pingInterval;
|
|
209
225
|
// On attach, send a snapshot of recent backlog so refreshed clients render prior output
|
|
210
226
|
try {
|
|
211
227
|
if (session.backlog && session.backlog.length > 0 && ws.readyState === WebSocket.OPEN) {
|
|
@@ -245,12 +261,20 @@ export class TerminalManager {
|
|
|
245
261
|
}
|
|
246
262
|
});
|
|
247
263
|
ws.on('close', () => {
|
|
264
|
+
if (session.pingInterval) {
|
|
265
|
+
clearInterval(session.pingInterval);
|
|
266
|
+
session.pingInterval = undefined;
|
|
267
|
+
}
|
|
248
268
|
if (session.websocket === ws) {
|
|
249
269
|
session.websocket = undefined;
|
|
250
270
|
}
|
|
251
271
|
});
|
|
252
272
|
ws.on('error', (error) => {
|
|
253
273
|
console.error('WebSocket error:', error);
|
|
274
|
+
if (session.pingInterval) {
|
|
275
|
+
clearInterval(session.pingInterval);
|
|
276
|
+
session.pingInterval = undefined;
|
|
277
|
+
}
|
|
254
278
|
if (session.websocket === ws) {
|
|
255
279
|
session.websocket = undefined;
|
|
256
280
|
}
|
|
@@ -262,10 +286,22 @@ export class TerminalManager {
|
|
|
262
286
|
if (!session) {
|
|
263
287
|
return false;
|
|
264
288
|
}
|
|
289
|
+
// Clear main WebSocket ping interval
|
|
290
|
+
if (session.pingInterval) {
|
|
291
|
+
clearInterval(session.pingInterval);
|
|
292
|
+
session.pingInterval = undefined;
|
|
293
|
+
}
|
|
265
294
|
// Close WebSocket if connected
|
|
266
295
|
if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
|
|
267
296
|
session.websocket.close();
|
|
268
297
|
}
|
|
298
|
+
// Clear all preview ping intervals and close preview sockets
|
|
299
|
+
if (session.previewPingIntervals && session.previewPingIntervals.size > 0) {
|
|
300
|
+
for (const interval of session.previewPingIntervals.values()) {
|
|
301
|
+
clearInterval(interval);
|
|
302
|
+
}
|
|
303
|
+
session.previewPingIntervals.clear();
|
|
304
|
+
}
|
|
269
305
|
// Close all preview sockets
|
|
270
306
|
if (session.previewSockets && session.previewSockets.size > 0) {
|
|
271
307
|
for (const ws of Array.from(session.previewSockets)) {
|
|
@@ -366,8 +402,27 @@ export class TerminalManager {
|
|
|
366
402
|
return false;
|
|
367
403
|
if (!session.previewSockets)
|
|
368
404
|
session.previewSockets = new Set();
|
|
405
|
+
if (!session.previewPingIntervals)
|
|
406
|
+
session.previewPingIntervals = new Map();
|
|
369
407
|
session.previewSockets.add(ws);
|
|
370
408
|
session.lastActivity = new Date();
|
|
409
|
+
// Setup WebSocket keepalive ping for preview socket (every 30 seconds)
|
|
410
|
+
const pingInterval = setInterval(() => {
|
|
411
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
412
|
+
try {
|
|
413
|
+
ws.ping();
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
clearInterval(pingInterval);
|
|
417
|
+
session.previewPingIntervals?.delete(ws);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
clearInterval(pingInterval);
|
|
422
|
+
session.previewPingIntervals?.delete(ws);
|
|
423
|
+
}
|
|
424
|
+
}, 30000);
|
|
425
|
+
session.previewPingIntervals.set(ws, pingInterval);
|
|
371
426
|
// On attach, send a snapshot of recent backlog (last 4KB)
|
|
372
427
|
try {
|
|
373
428
|
const backlog = session.backlog || '';
|
|
@@ -382,9 +437,19 @@ export class TerminalManager {
|
|
|
382
437
|
// Ignore messages: preview sockets are read-only
|
|
383
438
|
});
|
|
384
439
|
ws.on('close', () => {
|
|
440
|
+
const interval = session.previewPingIntervals?.get(ws);
|
|
441
|
+
if (interval) {
|
|
442
|
+
clearInterval(interval);
|
|
443
|
+
session.previewPingIntervals?.delete(ws);
|
|
444
|
+
}
|
|
385
445
|
session.previewSockets?.delete(ws);
|
|
386
446
|
});
|
|
387
447
|
ws.on('error', () => {
|
|
448
|
+
const interval = session.previewPingIntervals?.get(ws);
|
|
449
|
+
if (interval) {
|
|
450
|
+
clearInterval(interval);
|
|
451
|
+
session.previewPingIntervals?.delete(ws);
|
|
452
|
+
}
|
|
388
453
|
session.previewSockets?.delete(ws);
|
|
389
454
|
});
|
|
390
455
|
return true;
|
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
|
-
export declare function POST(c: Context): Promise<
|
|
3
|
-
error: string;
|
|
4
|
-
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
|
-
error: string;
|
|
6
|
-
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
7
|
-
response: any;
|
|
8
|
-
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
2
|
+
export declare function POST(c: Context): Promise<Response>;
|
|
@@ -34,7 +34,7 @@ function getGlobalApiKey(provider) {
|
|
|
34
34
|
}
|
|
35
35
|
export async function POST(c) {
|
|
36
36
|
try {
|
|
37
|
-
const { message: userMessage, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens, enableTools, attachedContextFiles } = await c.req.json();
|
|
37
|
+
const { message: userMessage, history, model, context, contextContent, agentPersona, systemPrompt: systemOverride, maxTokens, enableTools, attachedContextFiles, stream } = await c.req.json();
|
|
38
38
|
if (!userMessage) {
|
|
39
39
|
return c.json({ error: "Message is required" }, 400);
|
|
40
40
|
}
|
|
@@ -95,7 +95,8 @@ export async function POST(c) {
|
|
|
95
95
|
model: model || "openai/gpt-4o",
|
|
96
96
|
messages: messagesPayload,
|
|
97
97
|
temperature: 0.7,
|
|
98
|
-
max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(
|
|
98
|
+
max_tokens: typeof maxTokens === 'number' ? Math.max(256, Math.min(8000, Math.floor(maxTokens))) : 8000,
|
|
99
|
+
stream: stream === true,
|
|
99
100
|
};
|
|
100
101
|
// Add tools if enabled
|
|
101
102
|
if (tools) {
|
|
@@ -124,6 +125,17 @@ export async function POST(c) {
|
|
|
124
125
|
console.error("OpenRouter API error:", error);
|
|
125
126
|
return c.json({ error: "Failed to get AI response" }, 500);
|
|
126
127
|
}
|
|
128
|
+
// Handle streaming response
|
|
129
|
+
if (stream === true && response.body) {
|
|
130
|
+
return c.newResponse(response.body, {
|
|
131
|
+
status: 200,
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'text/event-stream',
|
|
134
|
+
'Cache-Control': 'no-cache',
|
|
135
|
+
'Connection': 'keep-alive',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
127
139
|
const data = await response.json();
|
|
128
140
|
const aiMessage = data.choices?.[0]?.message;
|
|
129
141
|
if (!aiMessage) {
|
|
@@ -143,6 +155,7 @@ export async function POST(c) {
|
|
|
143
155
|
}))
|
|
144
156
|
];
|
|
145
157
|
// Get final response from AI with tool results
|
|
158
|
+
// Use lower max_tokens for tool result synthesis (typically just needs to summarize)
|
|
146
159
|
const finalResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
147
160
|
method: "POST",
|
|
148
161
|
headers: {
|
|
@@ -154,8 +167,8 @@ export async function POST(c) {
|
|
|
154
167
|
body: JSON.stringify({
|
|
155
168
|
model: model || "openai/gpt-4o",
|
|
156
169
|
messages: messagesWithTools,
|
|
157
|
-
temperature: 0.
|
|
158
|
-
max_tokens:
|
|
170
|
+
temperature: 0.5, // Lower temperature for more consistent tool result summaries
|
|
171
|
+
max_tokens: 2000, // Reduced from 8000 - tool summaries don't need to be as long
|
|
159
172
|
}),
|
|
160
173
|
});
|
|
161
174
|
if (!finalResponse.ok) {
|
|
@@ -179,9 +192,9 @@ export async function POST(c) {
|
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
async function executeToolCalls(toolCalls) {
|
|
182
|
-
const results = [];
|
|
183
195
|
const storage = new FileStorageAdapter();
|
|
184
|
-
|
|
196
|
+
// Execute all tool calls in parallel for better performance
|
|
197
|
+
const resultPromises = toolCalls.map(async (toolCall) => {
|
|
185
198
|
try {
|
|
186
199
|
// Handle both function calling formats
|
|
187
200
|
const functionName = toolCall.function?.name || toolCall.name;
|
|
@@ -199,7 +212,7 @@ async function executeToolCalls(toolCalls) {
|
|
|
199
212
|
else if (functionName === 'knowledge_documents') {
|
|
200
213
|
result = await executeKnowledgeToolDirect(functionArgs);
|
|
201
214
|
}
|
|
202
|
-
|
|
215
|
+
return {
|
|
203
216
|
tool_call_id: toolCall.id,
|
|
204
217
|
content: JSON.stringify({
|
|
205
218
|
success: result.success,
|
|
@@ -207,19 +220,19 @@ async function executeToolCalls(toolCalls) {
|
|
|
207
220
|
message: result.message,
|
|
208
221
|
error: result.error
|
|
209
222
|
})
|
|
210
|
-
}
|
|
223
|
+
};
|
|
211
224
|
}
|
|
212
225
|
catch (error) {
|
|
213
|
-
|
|
226
|
+
return {
|
|
214
227
|
tool_call_id: toolCall.id,
|
|
215
228
|
content: JSON.stringify({
|
|
216
229
|
success: false,
|
|
217
230
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
218
231
|
})
|
|
219
|
-
}
|
|
232
|
+
};
|
|
220
233
|
}
|
|
221
|
-
}
|
|
222
|
-
return
|
|
234
|
+
});
|
|
235
|
+
return Promise.all(resultPromises);
|
|
223
236
|
}
|
|
224
237
|
async function executeProposalsToolDirect(args, storage) {
|
|
225
238
|
const { operation, id, filters, proposal } = args;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { getRepoStatus, listBranches, createBranch, deleteBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, } from '../../../../lib/git.js';
|
|
2
|
+
import { getRepoStatus, listBranches, createBranch, deleteBranch, switchBranch, mergeBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, checkRemoteAuth, getCredentialConfig, storeCredentials, } from '../../../../lib/git.js';
|
|
3
3
|
import { loadGitSettings, setDefaultWorktreePath } from '../../../../lib/git-settings.js';
|
|
4
4
|
const app = new Hono();
|
|
5
5
|
// Settings
|
|
@@ -38,6 +38,42 @@ app.get('/status', async (c) => {
|
|
|
38
38
|
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
+
// Auth Status
|
|
42
|
+
app.get('/auth-status', async (c) => {
|
|
43
|
+
try {
|
|
44
|
+
const authStatus = await checkRemoteAuth();
|
|
45
|
+
return c.json({ success: true, data: authStatus });
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// Credential Config
|
|
52
|
+
app.get('/credential-config', async (c) => {
|
|
53
|
+
try {
|
|
54
|
+
const config = await getCredentialConfig();
|
|
55
|
+
return c.json({ success: true, data: config });
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
// Store Credentials
|
|
62
|
+
app.post('/credentials', async (c) => {
|
|
63
|
+
try {
|
|
64
|
+
const body = await c.req.json();
|
|
65
|
+
const username = String(body?.username || '');
|
|
66
|
+
const password = String(body?.password || '');
|
|
67
|
+
if (!username || !password) {
|
|
68
|
+
return c.json({ success: false, error: { message: 'Username and password are required' } }, 400);
|
|
69
|
+
}
|
|
70
|
+
await storeCredentials(username, password);
|
|
71
|
+
return c.json({ success: true, data: { message: 'Credentials stored successfully' } });
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
41
77
|
// Branches
|
|
42
78
|
app.get('/branches', async (c) => {
|
|
43
79
|
try {
|
|
@@ -72,6 +108,32 @@ app.delete('/branches/:branch', async (c) => {
|
|
|
72
108
|
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
73
109
|
}
|
|
74
110
|
});
|
|
111
|
+
app.post('/branches/:branch/switch', async (c) => {
|
|
112
|
+
try {
|
|
113
|
+
const name = c.req.param('branch');
|
|
114
|
+
await switchBranch(name);
|
|
115
|
+
return c.json({ success: true, data: { switched: name } });
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
app.post('/branches/:branch/merge', async (c) => {
|
|
122
|
+
try {
|
|
123
|
+
const name = c.req.param('branch');
|
|
124
|
+
let strategy;
|
|
125
|
+
try {
|
|
126
|
+
strategy = await c.req.json();
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
const mergeStrategy = strategy?.strategy === 'squash' || strategy?.strategy === 'rebase' ? strategy.strategy : 'merge';
|
|
130
|
+
const result = await mergeBranch(name, mergeStrategy);
|
|
131
|
+
return c.json({ success: true, data: { branch: name, strategy: mergeStrategy, result } });
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
75
137
|
// Commits (in current repo root)
|
|
76
138
|
app.post('/commits', async (c) => {
|
|
77
139
|
try {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { ScheduledJobStatus } from '@lovelybunch/types';
|
|
3
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
4
|
+
success: false;
|
|
5
|
+
error: {
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
10
|
+
success: true;
|
|
11
|
+
data: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
prompt: string;
|
|
16
|
+
model: string;
|
|
17
|
+
status: ScheduledJobStatus;
|
|
18
|
+
schedule: {
|
|
19
|
+
type: "cron";
|
|
20
|
+
expression: string;
|
|
21
|
+
timezone?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: "interval";
|
|
25
|
+
hours: number;
|
|
26
|
+
daysOfWeek: import("@lovelybunch/types").DayOfWeek[];
|
|
27
|
+
timezone?: string;
|
|
28
|
+
anchorHour?: number;
|
|
29
|
+
};
|
|
30
|
+
metadata: {
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
lastRunAt?: string;
|
|
34
|
+
nextRunAt?: string;
|
|
35
|
+
};
|
|
36
|
+
runs: {
|
|
37
|
+
id: string;
|
|
38
|
+
jobId: string;
|
|
39
|
+
trigger: import("@lovelybunch/types").ScheduledJobTrigger;
|
|
40
|
+
status: import("@lovelybunch/types").ScheduledJobRunStatus;
|
|
41
|
+
startedAt: string;
|
|
42
|
+
finishedAt?: string;
|
|
43
|
+
outputPath?: string;
|
|
44
|
+
summary?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
cliCommand?: string;
|
|
47
|
+
}[];
|
|
48
|
+
tags?: string[];
|
|
49
|
+
contextPaths?: string[];
|
|
50
|
+
};
|
|
51
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
52
|
+
success: false;
|
|
53
|
+
error: {
|
|
54
|
+
code: string;
|
|
55
|
+
message: any;
|
|
56
|
+
};
|
|
57
|
+
}, 500, "json">)>;
|
|
58
|
+
export declare function PATCH(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
59
|
+
success: false;
|
|
60
|
+
error: {
|
|
61
|
+
code: string;
|
|
62
|
+
message: string;
|
|
63
|
+
};
|
|
64
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
65
|
+
success: false;
|
|
66
|
+
error: {
|
|
67
|
+
code: string;
|
|
68
|
+
message: any;
|
|
69
|
+
};
|
|
70
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
71
|
+
success: true;
|
|
72
|
+
data: {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
prompt: string;
|
|
77
|
+
model: string;
|
|
78
|
+
status: ScheduledJobStatus;
|
|
79
|
+
schedule: {
|
|
80
|
+
type: "cron";
|
|
81
|
+
expression: string;
|
|
82
|
+
timezone?: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: "interval";
|
|
86
|
+
hours: number;
|
|
87
|
+
daysOfWeek: import("@lovelybunch/types").DayOfWeek[];
|
|
88
|
+
timezone?: string;
|
|
89
|
+
anchorHour?: number;
|
|
90
|
+
};
|
|
91
|
+
metadata: {
|
|
92
|
+
createdAt: string;
|
|
93
|
+
updatedAt: string;
|
|
94
|
+
lastRunAt?: string;
|
|
95
|
+
nextRunAt?: string;
|
|
96
|
+
};
|
|
97
|
+
runs: {
|
|
98
|
+
id: string;
|
|
99
|
+
jobId: string;
|
|
100
|
+
trigger: import("@lovelybunch/types").ScheduledJobTrigger;
|
|
101
|
+
status: import("@lovelybunch/types").ScheduledJobRunStatus;
|
|
102
|
+
startedAt: string;
|
|
103
|
+
finishedAt?: string;
|
|
104
|
+
outputPath?: string;
|
|
105
|
+
summary?: string;
|
|
106
|
+
error?: string;
|
|
107
|
+
cliCommand?: string;
|
|
108
|
+
}[];
|
|
109
|
+
tags?: string[];
|
|
110
|
+
contextPaths?: string[];
|
|
111
|
+
};
|
|
112
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
113
|
+
success: false;
|
|
114
|
+
error: {
|
|
115
|
+
code: string;
|
|
116
|
+
message: any;
|
|
117
|
+
};
|
|
118
|
+
}, 500, "json">)>;
|
|
119
|
+
export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
120
|
+
success: false;
|
|
121
|
+
error: {
|
|
122
|
+
code: string;
|
|
123
|
+
message: string;
|
|
124
|
+
};
|
|
125
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
126
|
+
success: true;
|
|
127
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
128
|
+
success: false;
|
|
129
|
+
error: {
|
|
130
|
+
code: string;
|
|
131
|
+
message: any;
|
|
132
|
+
};
|
|
133
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { JobStore } from '../../../../../lib/jobs/job-store.js';
|
|
2
|
+
import { getGlobalJobScheduler } from '../../../../../lib/jobs/global-job-scheduler.js';
|
|
3
|
+
import { normalizeSchedule, normalizeStatus, } from '../route.js';
|
|
4
|
+
const store = new JobStore();
|
|
5
|
+
const scheduler = getGlobalJobScheduler();
|
|
6
|
+
function normalizeScheduleUpdate(current, incoming) {
|
|
7
|
+
if (!incoming)
|
|
8
|
+
return current;
|
|
9
|
+
const scheduleInput = {
|
|
10
|
+
...(current.type === 'interval' ? current : {}),
|
|
11
|
+
...(current.type === 'cron' ? current : {}),
|
|
12
|
+
...incoming,
|
|
13
|
+
type: incoming?.type ?? current.type
|
|
14
|
+
};
|
|
15
|
+
return normalizeSchedule(scheduleInput);
|
|
16
|
+
}
|
|
17
|
+
function normalizeStatusUpdate(current, status) {
|
|
18
|
+
if (status === undefined)
|
|
19
|
+
return current;
|
|
20
|
+
return normalizeStatus(status);
|
|
21
|
+
}
|
|
22
|
+
export async function GET(c) {
|
|
23
|
+
try {
|
|
24
|
+
const id = c.req.param('id');
|
|
25
|
+
const job = await store.getJob(id);
|
|
26
|
+
if (!job) {
|
|
27
|
+
return c.json({
|
|
28
|
+
success: false,
|
|
29
|
+
error: {
|
|
30
|
+
code: 'JOB_NOT_FOUND',
|
|
31
|
+
message: `Job ${id} not found`
|
|
32
|
+
}
|
|
33
|
+
}, 404);
|
|
34
|
+
}
|
|
35
|
+
return c.json({ success: true, data: job });
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('Failed to fetch job:', error);
|
|
39
|
+
return c.json({
|
|
40
|
+
success: false,
|
|
41
|
+
error: {
|
|
42
|
+
code: 'GET_JOB_ERROR',
|
|
43
|
+
message: error?.message ?? 'Unknown error retrieving job'
|
|
44
|
+
}
|
|
45
|
+
}, 500);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function PATCH(c) {
|
|
49
|
+
try {
|
|
50
|
+
const id = c.req.param('id');
|
|
51
|
+
const existing = await store.getJob(id);
|
|
52
|
+
if (!existing) {
|
|
53
|
+
return c.json({
|
|
54
|
+
success: false,
|
|
55
|
+
error: {
|
|
56
|
+
code: 'JOB_NOT_FOUND',
|
|
57
|
+
message: `Job ${id} not found`
|
|
58
|
+
}
|
|
59
|
+
}, 404);
|
|
60
|
+
}
|
|
61
|
+
const body = await c.req.json();
|
|
62
|
+
let schedule;
|
|
63
|
+
try {
|
|
64
|
+
schedule = normalizeScheduleUpdate(existing.schedule, body.schedule);
|
|
65
|
+
}
|
|
66
|
+
catch (validationError) {
|
|
67
|
+
return c.json({
|
|
68
|
+
success: false,
|
|
69
|
+
error: {
|
|
70
|
+
code: 'INVALID_SCHEDULE',
|
|
71
|
+
message: validationError?.message || 'Invalid schedule definition.'
|
|
72
|
+
}
|
|
73
|
+
}, 400);
|
|
74
|
+
}
|
|
75
|
+
const updated = {
|
|
76
|
+
...existing,
|
|
77
|
+
name: typeof body.name === 'string' ? body.name : existing.name,
|
|
78
|
+
description: typeof body.description === 'string' ? body.description : existing.description,
|
|
79
|
+
prompt: typeof body.prompt === 'string' ? body.prompt : existing.prompt,
|
|
80
|
+
model: typeof body.model === 'string' ? body.model : existing.model,
|
|
81
|
+
status: normalizeStatusUpdate(existing.status, body.status),
|
|
82
|
+
schedule,
|
|
83
|
+
tags: Array.isArray(body.tags) ? body.tags : existing.tags,
|
|
84
|
+
contextPaths: Array.isArray(body.contextPaths) ? body.contextPaths : existing.contextPaths,
|
|
85
|
+
metadata: {
|
|
86
|
+
...existing.metadata,
|
|
87
|
+
updatedAt: new Date()
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
await store.saveJob(updated);
|
|
91
|
+
await scheduler.refresh(updated.id);
|
|
92
|
+
const reloaded = await store.getJob(updated.id);
|
|
93
|
+
return c.json({
|
|
94
|
+
success: true,
|
|
95
|
+
data: reloaded ?? updated
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Failed to update job:', error);
|
|
100
|
+
return c.json({
|
|
101
|
+
success: false,
|
|
102
|
+
error: {
|
|
103
|
+
code: 'UPDATE_JOB_ERROR',
|
|
104
|
+
message: error?.message ?? 'Unknown error updating job'
|
|
105
|
+
}
|
|
106
|
+
}, 500);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function DELETE(c) {
|
|
110
|
+
try {
|
|
111
|
+
const id = c.req.param('id');
|
|
112
|
+
const deleted = await store.deleteJob(id);
|
|
113
|
+
if (!deleted) {
|
|
114
|
+
return c.json({
|
|
115
|
+
success: false,
|
|
116
|
+
error: {
|
|
117
|
+
code: 'JOB_NOT_FOUND',
|
|
118
|
+
message: `Job ${id} not found`
|
|
119
|
+
}
|
|
120
|
+
}, 404);
|
|
121
|
+
}
|
|
122
|
+
await scheduler.unregister(id);
|
|
123
|
+
return c.json({ success: true });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error('Failed to delete job:', error);
|
|
127
|
+
return c.json({
|
|
128
|
+
success: false,
|
|
129
|
+
error: {
|
|
130
|
+
code: 'DELETE_JOB_ERROR',
|
|
131
|
+
message: error?.message ?? 'Unknown error deleting job'
|
|
132
|
+
}
|
|
133
|
+
}, 500);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
success: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
|
+
success: true;
|
|
10
|
+
data: {
|
|
11
|
+
jobId: string;
|
|
12
|
+
run: {
|
|
13
|
+
id: string;
|
|
14
|
+
jobId: string;
|
|
15
|
+
trigger: import("@lovelybunch/types").ScheduledJobTrigger;
|
|
16
|
+
status: import("@lovelybunch/types").ScheduledJobRunStatus;
|
|
17
|
+
startedAt: string;
|
|
18
|
+
finishedAt?: string;
|
|
19
|
+
outputPath?: string;
|
|
20
|
+
summary?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
cliCommand?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
26
|
+
success: false;
|
|
27
|
+
error: {
|
|
28
|
+
code: string;
|
|
29
|
+
message: any;
|
|
30
|
+
};
|
|
31
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { JobStore } from '../../../../../../lib/jobs/job-store.js';
|
|
2
|
+
import { getGlobalJobScheduler } from '../../../../../../lib/jobs/global-job-scheduler.js';
|
|
3
|
+
const store = new JobStore();
|
|
4
|
+
const scheduler = getGlobalJobScheduler();
|
|
5
|
+
export async function POST(c) {
|
|
6
|
+
try {
|
|
7
|
+
const id = c.req.param('id');
|
|
8
|
+
const job = await store.getJob(id);
|
|
9
|
+
if (!job) {
|
|
10
|
+
return c.json({
|
|
11
|
+
success: false,
|
|
12
|
+
error: {
|
|
13
|
+
code: 'JOB_NOT_FOUND',
|
|
14
|
+
message: `Job ${id} not found`
|
|
15
|
+
}
|
|
16
|
+
}, 404);
|
|
17
|
+
}
|
|
18
|
+
const run = await scheduler.runNow(id, 'manual');
|
|
19
|
+
return c.json({
|
|
20
|
+
success: true,
|
|
21
|
+
data: {
|
|
22
|
+
jobId: id,
|
|
23
|
+
run
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error('Failed to trigger job run:', error);
|
|
29
|
+
return c.json({
|
|
30
|
+
success: false,
|
|
31
|
+
error: {
|
|
32
|
+
code: 'RUN_JOB_ERROR',
|
|
33
|
+
message: error?.message ?? 'Unknown error running job'
|
|
34
|
+
}
|
|
35
|
+
}, 500);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { GET, POST } from './route.js';
|
|
3
|
+
import { GET as getJob, PATCH as patchJob, DELETE as deleteJob } from './[id]/route.js';
|
|
4
|
+
import { POST as runJob } from './[id]/run/route.js';
|
|
5
|
+
import { GET as getStatus } from './status/route.js';
|
|
6
|
+
const jobs = new Hono();
|
|
7
|
+
jobs.get('/', GET);
|
|
8
|
+
jobs.post('/', POST);
|
|
9
|
+
jobs.get('/status', getStatus);
|
|
10
|
+
jobs.get('/:id', getJob);
|
|
11
|
+
jobs.patch('/:id', patchJob);
|
|
12
|
+
jobs.delete('/:id', deleteJob);
|
|
13
|
+
jobs.post('/:id/run', runJob);
|
|
14
|
+
export default jobs;
|