@renseiai/agentfactory-nextjs 0.8.0
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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/src/__tests__/middleware-edge-safety.test.d.ts +2 -0
- package/dist/src/__tests__/middleware-edge-safety.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware-edge-safety.test.js +74 -0
- package/dist/src/__tests__/poll-project-filter.test.d.ts +2 -0
- package/dist/src/__tests__/poll-project-filter.test.d.ts.map +1 -0
- package/dist/src/__tests__/poll-project-filter.test.js +83 -0
- package/dist/src/__tests__/subpath-exports.test.d.ts +2 -0
- package/dist/src/__tests__/subpath-exports.test.d.ts.map +1 -0
- package/dist/src/__tests__/subpath-exports.test.js +35 -0
- package/dist/src/__tests__/webhook-project-filter.test.d.ts +2 -0
- package/dist/src/__tests__/webhook-project-filter.test.d.ts.map +1 -0
- package/dist/src/__tests__/webhook-project-filter.test.js +48 -0
- package/dist/src/factory.d.ts +140 -0
- package/dist/src/factory.d.ts.map +1 -0
- package/dist/src/factory.js +127 -0
- package/dist/src/handlers/cleanup.d.ts +44 -0
- package/dist/src/handlers/cleanup.d.ts.map +1 -0
- package/dist/src/handlers/cleanup.js +34 -0
- package/dist/src/handlers/config.d.ts +11 -0
- package/dist/src/handlers/config.d.ts.map +1 -0
- package/dist/src/handlers/config.js +20 -0
- package/dist/src/handlers/issue-tracker-proxy/index.d.ts +34 -0
- package/dist/src/handlers/issue-tracker-proxy/index.d.ts.map +1 -0
- package/dist/src/handlers/issue-tracker-proxy/index.js +230 -0
- package/dist/src/handlers/issue-tracker-proxy/serializer.d.ts +28 -0
- package/dist/src/handlers/issue-tracker-proxy/serializer.d.ts.map +1 -0
- package/dist/src/handlers/issue-tracker-proxy/serializer.js +95 -0
- package/dist/src/handlers/issue-tracker-proxy/types.d.ts +9 -0
- package/dist/src/handlers/issue-tracker-proxy/types.d.ts.map +1 -0
- package/dist/src/handlers/issue-tracker-proxy/types.js +4 -0
- package/dist/src/handlers/oauth/callback.d.ts +36 -0
- package/dist/src/handlers/oauth/callback.d.ts.map +1 -0
- package/dist/src/handlers/oauth/callback.js +96 -0
- package/dist/src/handlers/public/session-detail.d.ts +31 -0
- package/dist/src/handlers/public/session-detail.d.ts.map +1 -0
- package/dist/src/handlers/public/session-detail.js +91 -0
- package/dist/src/handlers/public/sessions-list.d.ts +22 -0
- package/dist/src/handlers/public/sessions-list.d.ts.map +1 -0
- package/dist/src/handlers/public/sessions-list.js +75 -0
- package/dist/src/handlers/public/stats.d.ts +28 -0
- package/dist/src/handlers/public/stats.d.ts.map +1 -0
- package/dist/src/handlers/public/stats.js +66 -0
- package/dist/src/handlers/sessions/activity.d.ts +15 -0
- package/dist/src/handlers/sessions/activity.d.ts.map +1 -0
- package/dist/src/handlers/sessions/activity.js +93 -0
- package/dist/src/handlers/sessions/claim.d.ts +15 -0
- package/dist/src/handlers/sessions/claim.d.ts.map +1 -0
- package/dist/src/handlers/sessions/claim.js +139 -0
- package/dist/src/handlers/sessions/completion.d.ts +16 -0
- package/dist/src/handlers/sessions/completion.d.ts.map +1 -0
- package/dist/src/handlers/sessions/completion.js +82 -0
- package/dist/src/handlers/sessions/external-urls.d.ts +15 -0
- package/dist/src/handlers/sessions/external-urls.d.ts.map +1 -0
- package/dist/src/handlers/sessions/external-urls.js +70 -0
- package/dist/src/handlers/sessions/get.d.ts +19 -0
- package/dist/src/handlers/sessions/get.d.ts.map +1 -0
- package/dist/src/handlers/sessions/get.js +47 -0
- package/dist/src/handlers/sessions/list.d.ts +27 -0
- package/dist/src/handlers/sessions/list.d.ts.map +1 -0
- package/dist/src/handlers/sessions/list.js +51 -0
- package/dist/src/handlers/sessions/lock-refresh.d.ts +14 -0
- package/dist/src/handlers/sessions/lock-refresh.d.ts.map +1 -0
- package/dist/src/handlers/sessions/lock-refresh.js +38 -0
- package/dist/src/handlers/sessions/progress.d.ts +15 -0
- package/dist/src/handlers/sessions/progress.d.ts.map +1 -0
- package/dist/src/handlers/sessions/progress.js +94 -0
- package/dist/src/handlers/sessions/prompts.d.ts +15 -0
- package/dist/src/handlers/sessions/prompts.d.ts.map +1 -0
- package/dist/src/handlers/sessions/prompts.js +91 -0
- package/dist/src/handlers/sessions/status.d.ts +19 -0
- package/dist/src/handlers/sessions/status.d.ts.map +1 -0
- package/dist/src/handlers/sessions/status.js +187 -0
- package/dist/src/handlers/sessions/tool-error.d.ts +15 -0
- package/dist/src/handlers/sessions/tool-error.d.ts.map +1 -0
- package/dist/src/handlers/sessions/tool-error.js +103 -0
- package/dist/src/handlers/sessions/transfer-ownership.d.ts +14 -0
- package/dist/src/handlers/sessions/transfer-ownership.d.ts.map +1 -0
- package/dist/src/handlers/sessions/transfer-ownership.js +56 -0
- package/dist/src/handlers/workers/get-delete.d.ts +15 -0
- package/dist/src/handlers/workers/get-delete.d.ts.map +1 -0
- package/dist/src/handlers/workers/get-delete.js +58 -0
- package/dist/src/handlers/workers/heartbeat.d.ts +14 -0
- package/dist/src/handlers/workers/heartbeat.d.ts.map +1 -0
- package/dist/src/handlers/workers/heartbeat.js +42 -0
- package/dist/src/handlers/workers/list.d.ts +22 -0
- package/dist/src/handlers/workers/list.d.ts.map +1 -0
- package/dist/src/handlers/workers/list.js +33 -0
- package/dist/src/handlers/workers/poll.d.ts +14 -0
- package/dist/src/handlers/workers/poll.d.ts.map +1 -0
- package/dist/src/handlers/workers/poll.js +96 -0
- package/dist/src/handlers/workers/register.d.ts +9 -0
- package/dist/src/handlers/workers/register.d.ts.map +1 -0
- package/dist/src/handlers/workers/register.js +45 -0
- package/dist/src/index.d.ts +52 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +56 -0
- package/dist/src/linear-client-resolver.d.ts +59 -0
- package/dist/src/linear-client-resolver.d.ts.map +1 -0
- package/dist/src/linear-client-resolver.js +104 -0
- package/dist/src/middleware/cron-auth.d.ts +21 -0
- package/dist/src/middleware/cron-auth.d.ts.map +1 -0
- package/dist/src/middleware/cron-auth.js +46 -0
- package/dist/src/middleware/factory.d.ts +33 -0
- package/dist/src/middleware/factory.d.ts.map +1 -0
- package/dist/src/middleware/factory.js +185 -0
- package/dist/src/middleware/index.d.ts +16 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +14 -0
- package/dist/src/middleware/types.d.ts +35 -0
- package/dist/src/middleware/types.d.ts.map +1 -0
- package/dist/src/middleware/types.js +4 -0
- package/dist/src/middleware/worker-auth.d.ts +25 -0
- package/dist/src/middleware/worker-auth.d.ts.map +1 -0
- package/dist/src/middleware/worker-auth.js +43 -0
- package/dist/src/orchestrator/error-formatting.d.ts +8 -0
- package/dist/src/orchestrator/error-formatting.d.ts.map +1 -0
- package/dist/src/orchestrator/error-formatting.js +35 -0
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -0
- package/dist/src/orchestrator/index.js +2 -0
- package/dist/src/orchestrator/types.d.ts +53 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +4 -0
- package/dist/src/orchestrator/webhook-orchestrator.d.ts +32 -0
- package/dist/src/orchestrator/webhook-orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator/webhook-orchestrator.js +373 -0
- package/dist/src/types.d.ts +101 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +7 -0
- package/dist/src/webhook/governor-bridge.d.ts +23 -0
- package/dist/src/webhook/governor-bridge.d.ts.map +1 -0
- package/dist/src/webhook/governor-bridge.js +36 -0
- package/dist/src/webhook/handlers/issue-updated.d.ts +15 -0
- package/dist/src/webhook/handlers/issue-updated.d.ts.map +1 -0
- package/dist/src/webhook/handlers/issue-updated.js +771 -0
- package/dist/src/webhook/handlers/session-created.d.ts +9 -0
- package/dist/src/webhook/handlers/session-created.d.ts.map +1 -0
- package/dist/src/webhook/handlers/session-created.js +337 -0
- package/dist/src/webhook/handlers/session-prompted.d.ts +9 -0
- package/dist/src/webhook/handlers/session-prompted.d.ts.map +1 -0
- package/dist/src/webhook/handlers/session-prompted.js +199 -0
- package/dist/src/webhook/handlers/session-updated.d.ts +9 -0
- package/dist/src/webhook/handlers/session-updated.d.ts.map +1 -0
- package/dist/src/webhook/handlers/session-updated.js +29 -0
- package/dist/src/webhook/processor.d.ts +22 -0
- package/dist/src/webhook/processor.d.ts.map +1 -0
- package/dist/src/webhook/processor.js +98 -0
- package/dist/src/webhook/signature.d.ts +16 -0
- package/dist/src/webhook/signature.d.ts.map +1 -0
- package/dist/src/webhook/signature.js +23 -0
- package/dist/src/webhook/utils.d.ts +61 -0
- package/dist/src/webhook/utils.d.ts.map +1 -0
- package/dist/src/webhook/utils.js +166 -0
- package/package.json +86 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, POST /api/sessions/[id]/prompts
|
|
3
|
+
*
|
|
4
|
+
* Get pending prompts or claim a specific prompt.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
interface RouteParams {
|
|
8
|
+
params: Promise<{
|
|
9
|
+
id: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createSessionPromptsGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
13
|
+
export declare function createSessionPromptsPostHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAYvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,8BAA8B,KAClB,SAAS,WAAW,EAAE,YAAY,WAAW,oCAwDxE;AAED,wBAAgB,+BAA+B,KAClB,SAAS,WAAW,EAAE,YAAY,WAAW,oCAoDzE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, POST /api/sessions/[id]/prompts
|
|
3
|
+
*
|
|
4
|
+
* Get pending prompts or claim a specific prompt.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { getSessionState, getPendingPrompts, popPendingPrompt, claimPendingPrompt, createLogger, } from '@renseiai/agentfactory-server';
|
|
9
|
+
const log = createLogger('api:sessions:prompts');
|
|
10
|
+
export function createSessionPromptsGetHandler() {
|
|
11
|
+
return async function GET(request, { params }) {
|
|
12
|
+
const authError = requireWorkerAuth(request);
|
|
13
|
+
if (authError)
|
|
14
|
+
return authError;
|
|
15
|
+
const { id: sessionId } = await params;
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
const shouldPop = url.searchParams.get('pop') === 'true';
|
|
18
|
+
try {
|
|
19
|
+
const session = await getSessionState(sessionId);
|
|
20
|
+
if (!session) {
|
|
21
|
+
return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
|
|
22
|
+
}
|
|
23
|
+
if (shouldPop) {
|
|
24
|
+
const prompt = await popPendingPrompt(sessionId);
|
|
25
|
+
if (prompt) {
|
|
26
|
+
log.info('Prompt popped successfully', {
|
|
27
|
+
sessionId,
|
|
28
|
+
promptId: prompt.id,
|
|
29
|
+
promptLength: prompt.prompt.length,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return NextResponse.json({
|
|
33
|
+
prompt,
|
|
34
|
+
hasMore: prompt ? true : false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const prompts = await getPendingPrompts(sessionId);
|
|
38
|
+
if (prompts.length > 0) {
|
|
39
|
+
log.info('Prompts retrieved', {
|
|
40
|
+
sessionId,
|
|
41
|
+
promptCount: prompts.length,
|
|
42
|
+
promptIds: prompts.map((p) => p.id),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return NextResponse.json({
|
|
46
|
+
prompts,
|
|
47
|
+
count: prompts.length,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
log.error('Failed to get pending prompts', { error, sessionId });
|
|
52
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get pending prompts' }, { status: 500 });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createSessionPromptsPostHandler() {
|
|
57
|
+
return async function POST(request, { params }) {
|
|
58
|
+
const authError = requireWorkerAuth(request);
|
|
59
|
+
if (authError)
|
|
60
|
+
return authError;
|
|
61
|
+
const { id: sessionId } = await params;
|
|
62
|
+
try {
|
|
63
|
+
const body = await request.json();
|
|
64
|
+
const { promptId } = body;
|
|
65
|
+
if (!promptId || typeof promptId !== 'string') {
|
|
66
|
+
return NextResponse.json({ error: 'Bad Request', message: 'promptId is required' }, { status: 400 });
|
|
67
|
+
}
|
|
68
|
+
const session = await getSessionState(sessionId);
|
|
69
|
+
if (!session) {
|
|
70
|
+
return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
|
|
71
|
+
}
|
|
72
|
+
const prompt = await claimPendingPrompt(sessionId, promptId);
|
|
73
|
+
if (!prompt) {
|
|
74
|
+
return NextResponse.json({ error: 'Not Found', message: 'Prompt not found or already claimed' }, { status: 404 });
|
|
75
|
+
}
|
|
76
|
+
log.info('Prompt claimed', {
|
|
77
|
+
sessionId,
|
|
78
|
+
promptId,
|
|
79
|
+
promptLength: prompt.prompt.length,
|
|
80
|
+
});
|
|
81
|
+
return NextResponse.json({
|
|
82
|
+
claimed: true,
|
|
83
|
+
prompt,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
log.error('Failed to claim prompt', { error, sessionId });
|
|
88
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to claim prompt' }, { status: 500 });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, POST /api/sessions/[id]/status
|
|
3
|
+
*
|
|
4
|
+
* Report status update from worker or get current status.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
import type { RouteConfig } from '../../types.js';
|
|
8
|
+
interface RouteParams {
|
|
9
|
+
params: Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createSessionStatusPostHandler(config?: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
14
|
+
export declare function createSessionStatusGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<{
|
|
15
|
+
error: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}> | NextResponse<import("@renseiai/agentfactory-server").AgentSessionState>>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAiBvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAYD,wBAAgB,8BAA8B,CAAC,MAAM,CAAC,EAAE,WAAW,IACtC,SAAS,WAAW,EAAE,YAAY,WAAW,oCA0LzE;AAED,wBAAgB,6BAA6B,KACjB,SAAS,WAAW,EAAE,YAAY,WAAW;;;8EAsBxE"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, POST /api/sessions/[id]/status
|
|
3
|
+
*
|
|
4
|
+
* Report status update from worker or get current status.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { getSessionState, updateSessionStatus, updateSessionCostData, updateProviderSessionId, startSession, removeWorkerSession, releaseClaim, markAgentWorked, releaseIssueLock, promoteNextPendingWork, RedisProcessingStateStorage, createLogger, } from '@renseiai/agentfactory-server';
|
|
9
|
+
const log = createLogger('api:sessions:status');
|
|
10
|
+
const VALID_STATUSES = [
|
|
11
|
+
'running',
|
|
12
|
+
'finalizing',
|
|
13
|
+
'completed',
|
|
14
|
+
'failed',
|
|
15
|
+
'stopped',
|
|
16
|
+
];
|
|
17
|
+
const TERMINAL_STATUSES = ['completed', 'failed', 'stopped'];
|
|
18
|
+
export function createSessionStatusPostHandler(config) {
|
|
19
|
+
return async function POST(request, { params }) {
|
|
20
|
+
const authError = requireWorkerAuth(request);
|
|
21
|
+
if (authError)
|
|
22
|
+
return authError;
|
|
23
|
+
const { id: sessionId } = await params;
|
|
24
|
+
try {
|
|
25
|
+
const body = await request.json();
|
|
26
|
+
const { workerId, status, providerSessionId, worktreePath, error: errorInfo, totalCostUsd, inputTokens, outputTokens } = body;
|
|
27
|
+
if (!workerId || typeof workerId !== 'string') {
|
|
28
|
+
return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
if (!status || !VALID_STATUSES.includes(status)) {
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
error: 'Bad Request',
|
|
33
|
+
message: `status must be one of: ${VALID_STATUSES.join(', ')}`,
|
|
34
|
+
}, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
const session = await getSessionState(sessionId);
|
|
37
|
+
if (!session) {
|
|
38
|
+
return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
|
|
39
|
+
}
|
|
40
|
+
if (session.workerId && session.workerId !== workerId) {
|
|
41
|
+
return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
|
|
42
|
+
}
|
|
43
|
+
if (status === 'running') {
|
|
44
|
+
if (worktreePath) {
|
|
45
|
+
await startSession(sessionId, workerId, worktreePath);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await updateSessionStatus(sessionId, 'running');
|
|
49
|
+
}
|
|
50
|
+
if (providerSessionId) {
|
|
51
|
+
await updateProviderSessionId(sessionId, providerSessionId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (status === 'finalizing') {
|
|
55
|
+
await updateSessionStatus(sessionId, 'finalizing');
|
|
56
|
+
}
|
|
57
|
+
else if (TERMINAL_STATUSES.includes(status)) {
|
|
58
|
+
await updateSessionStatus(sessionId, status);
|
|
59
|
+
if (totalCostUsd != null || inputTokens != null || outputTokens != null) {
|
|
60
|
+
await updateSessionCostData(sessionId, { totalCostUsd, inputTokens, outputTokens }).catch((err) => log.error('Failed to persist cost data', { sessionId, error: err }));
|
|
61
|
+
}
|
|
62
|
+
if (status === 'completed' && session.issueId) {
|
|
63
|
+
try {
|
|
64
|
+
await markAgentWorked(session.issueId, {
|
|
65
|
+
issueIdentifier: session.issueIdentifier || 'unknown',
|
|
66
|
+
sessionId: sessionId,
|
|
67
|
+
});
|
|
68
|
+
log.info('Issue marked as agent-worked', {
|
|
69
|
+
issueId: session.issueId,
|
|
70
|
+
sessionId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
log.error('Failed to mark agent-worked', { sessionId, error: err });
|
|
75
|
+
}
|
|
76
|
+
// Mark research/backlog-creation phases as completed so the
|
|
77
|
+
// governor does not re-dispatch the same top-of-funnel work.
|
|
78
|
+
const phase = session.workType === 'research' ? 'research'
|
|
79
|
+
: session.workType === 'backlog-creation' ? 'backlog-creation'
|
|
80
|
+
: null;
|
|
81
|
+
if (phase) {
|
|
82
|
+
try {
|
|
83
|
+
const processingState = new RedisProcessingStateStorage();
|
|
84
|
+
await processingState.markPhaseCompleted(session.issueId, phase, sessionId);
|
|
85
|
+
log.info('Processing phase marked complete', {
|
|
86
|
+
issueId: session.issueId,
|
|
87
|
+
phase,
|
|
88
|
+
sessionId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
log.error('Failed to mark processing phase complete', {
|
|
93
|
+
sessionId,
|
|
94
|
+
issueId: session.issueId,
|
|
95
|
+
phase,
|
|
96
|
+
error: err,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Emit a response activity to close the Linear agent session.
|
|
102
|
+
// Linear auto-transitions sessions to "complete" based on response activities.
|
|
103
|
+
if (config && session.linearSessionId && session.organizationId) {
|
|
104
|
+
try {
|
|
105
|
+
const linearClient = await config.linearClient.getClient(session.organizationId);
|
|
106
|
+
const message = status === 'completed'
|
|
107
|
+
? 'Work completed successfully.'
|
|
108
|
+
: status === 'failed'
|
|
109
|
+
? 'Agent encountered an error during execution.'
|
|
110
|
+
: 'Agent was stopped.';
|
|
111
|
+
await linearClient.createAgentActivity({
|
|
112
|
+
agentSessionId: session.linearSessionId,
|
|
113
|
+
content: { type: 'response', body: message },
|
|
114
|
+
ephemeral: false,
|
|
115
|
+
});
|
|
116
|
+
log.info('Emitted completion response activity to close Linear session', {
|
|
117
|
+
sessionId,
|
|
118
|
+
linearSessionId: session.linearSessionId,
|
|
119
|
+
status,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
log.warn('Failed to emit completion response activity', {
|
|
124
|
+
sessionId,
|
|
125
|
+
linearSessionId: session.linearSessionId,
|
|
126
|
+
error: err,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await releaseClaim(sessionId);
|
|
131
|
+
await removeWorkerSession(workerId, sessionId);
|
|
132
|
+
if (session.issueId) {
|
|
133
|
+
try {
|
|
134
|
+
await releaseIssueLock(session.issueId);
|
|
135
|
+
const promoted = await promoteNextPendingWork(session.issueId);
|
|
136
|
+
if (promoted) {
|
|
137
|
+
log.info('Promoted pending work after lock release', {
|
|
138
|
+
issueId: session.issueId,
|
|
139
|
+
promotedSessionId: promoted.sessionId,
|
|
140
|
+
promotedWorkType: promoted.workType,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
log.error('Failed to release issue lock or promote pending work', {
|
|
146
|
+
sessionId,
|
|
147
|
+
issueId: session.issueId,
|
|
148
|
+
error: err,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
log.info('Session status updated', {
|
|
154
|
+
sessionId,
|
|
155
|
+
workerId,
|
|
156
|
+
status,
|
|
157
|
+
hasProviderSessionId: !!providerSessionId,
|
|
158
|
+
hasError: !!errorInfo,
|
|
159
|
+
});
|
|
160
|
+
const updatedSession = await getSessionState(sessionId);
|
|
161
|
+
return NextResponse.json({
|
|
162
|
+
updated: true,
|
|
163
|
+
session: updatedSession,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
log.error('Failed to update session status', { error, sessionId });
|
|
168
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to update session status' }, { status: 500 });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function createSessionStatusGetHandler() {
|
|
173
|
+
return async function GET(request, { params }) {
|
|
174
|
+
const { id: sessionId } = await params;
|
|
175
|
+
try {
|
|
176
|
+
const session = await getSessionState(sessionId);
|
|
177
|
+
if (!session) {
|
|
178
|
+
return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
|
|
179
|
+
}
|
|
180
|
+
return NextResponse.json(session);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
log.error('Failed to get session status', { error, sessionId });
|
|
184
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get session status' }, { status: 500 });
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/sessions/[id]/tool-error
|
|
3
|
+
*
|
|
4
|
+
* Report a tool error as a Linear issue for tracking.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
import type { RouteConfig } from '../../types.js';
|
|
8
|
+
interface RouteParams {
|
|
9
|
+
params: Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createSessionToolErrorHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=tool-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-error.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/tool-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAIvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAYD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,WAAW,IACpC,SAAS,WAAW,EAAE,YAAY,WAAW,oCA4HzE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/sessions/[id]/tool-error
|
|
3
|
+
*
|
|
4
|
+
* Report a tool error as a Linear issue for tracking.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { getSessionState, createLogger } from '@renseiai/agentfactory-server';
|
|
9
|
+
import { createAgentSession, ENVIRONMENT_ISSUE_TYPES } from '@renseiai/agentfactory-linear';
|
|
10
|
+
const log = createLogger('api:sessions:tool-error');
|
|
11
|
+
export function createSessionToolErrorHandler(config) {
|
|
12
|
+
return async function POST(request, { params }) {
|
|
13
|
+
const authError = requireWorkerAuth(request);
|
|
14
|
+
if (authError)
|
|
15
|
+
return authError;
|
|
16
|
+
const { id: sessionId } = await params;
|
|
17
|
+
try {
|
|
18
|
+
const body = (await request.json());
|
|
19
|
+
const { workerId, toolName, errorMessage, context } = body;
|
|
20
|
+
if (!workerId || typeof workerId !== 'string') {
|
|
21
|
+
return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
if (!toolName || typeof toolName !== 'string') {
|
|
24
|
+
return NextResponse.json({ error: 'Bad Request', message: 'toolName is required' }, { status: 400 });
|
|
25
|
+
}
|
|
26
|
+
if (!errorMessage || typeof errorMessage !== 'string') {
|
|
27
|
+
return NextResponse.json({ error: 'Bad Request', message: 'errorMessage is required' }, { status: 400 });
|
|
28
|
+
}
|
|
29
|
+
const session = await getSessionState(sessionId);
|
|
30
|
+
if (!session) {
|
|
31
|
+
return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
|
|
32
|
+
}
|
|
33
|
+
if (session.workerId && session.workerId !== workerId) {
|
|
34
|
+
return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
|
|
35
|
+
}
|
|
36
|
+
// Skip Linear forwarding for governor-generated fake session IDs.
|
|
37
|
+
if (sessionId.startsWith('governor-')) {
|
|
38
|
+
log.debug('Skipping Linear tool error report for governor-generated session', {
|
|
39
|
+
sessionId,
|
|
40
|
+
toolName,
|
|
41
|
+
});
|
|
42
|
+
return NextResponse.json({
|
|
43
|
+
created: false,
|
|
44
|
+
reason: 'Governor-generated session — no Linear agent session exists',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const linearClient = await config.linearClient.getClient(session.organizationId);
|
|
49
|
+
const agentSession = createAgentSession({
|
|
50
|
+
client: linearClient.linearClient,
|
|
51
|
+
issueId: session.issueId,
|
|
52
|
+
sessionId,
|
|
53
|
+
autoTransition: false,
|
|
54
|
+
});
|
|
55
|
+
const issue = await agentSession.reportEnvironmentIssue(`Tool error: ${toolName}`, `The agent encountered an error while using the **${toolName}** tool.\n\n**Error:**\n\`\`\`\n${errorMessage}\n\`\`\``, {
|
|
56
|
+
issueType: ENVIRONMENT_ISSUE_TYPES.TOOL,
|
|
57
|
+
sourceIssueId: context?.issueIdentifier ?? session.issueId,
|
|
58
|
+
additionalContext: {
|
|
59
|
+
toolName,
|
|
60
|
+
sessionId,
|
|
61
|
+
workerId,
|
|
62
|
+
...context?.additionalContext,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (issue) {
|
|
66
|
+
log.info('Tool error reported to Linear', {
|
|
67
|
+
sessionId,
|
|
68
|
+
toolName,
|
|
69
|
+
issueIdentifier: issue.identifier,
|
|
70
|
+
});
|
|
71
|
+
return NextResponse.json({
|
|
72
|
+
created: true,
|
|
73
|
+
issue: {
|
|
74
|
+
id: issue.id,
|
|
75
|
+
identifier: issue.identifier,
|
|
76
|
+
url: issue.url,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return NextResponse.json({
|
|
81
|
+
created: false,
|
|
82
|
+
reason: 'Failed to create issue in Linear',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (linearError) {
|
|
86
|
+
const errorMessage2 = linearError instanceof Error ? linearError.message : String(linearError);
|
|
87
|
+
log.error('Failed to report tool error to Linear', {
|
|
88
|
+
error: errorMessage2,
|
|
89
|
+
sessionId,
|
|
90
|
+
toolName,
|
|
91
|
+
});
|
|
92
|
+
return NextResponse.json({
|
|
93
|
+
created: false,
|
|
94
|
+
reason: `Failed to report to Linear: ${errorMessage2}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
log.error('Failed to process tool error report', { error, sessionId });
|
|
100
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process tool error report' }, { status: 500 });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/sessions/[id]/transfer-ownership
|
|
3
|
+
*
|
|
4
|
+
* Transfer session ownership to a new worker.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
interface RouteParams {
|
|
8
|
+
params: Promise<{
|
|
9
|
+
id: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createSessionTransferOwnershipHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=transfer-ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer-ownership.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/transfer-ownership.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAYvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,qCAAqC,KACxB,SAAS,WAAW,EAAE,YAAY,WAAW,oCA8DzE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/sessions/[id]/transfer-ownership
|
|
3
|
+
*
|
|
4
|
+
* Transfer session ownership to a new worker.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { transferSessionOwnership, getSessionState, removeWorkerSession, addWorkerSession, createLogger, } from '@renseiai/agentfactory-server';
|
|
9
|
+
const log = createLogger('api:sessions:transfer-ownership');
|
|
10
|
+
export function createSessionTransferOwnershipHandler() {
|
|
11
|
+
return async function POST(request, { params }) {
|
|
12
|
+
const authError = requireWorkerAuth(request);
|
|
13
|
+
if (authError)
|
|
14
|
+
return authError;
|
|
15
|
+
const { id: sessionId } = await params;
|
|
16
|
+
try {
|
|
17
|
+
const body = await request.json();
|
|
18
|
+
const { newWorkerId, oldWorkerId } = body;
|
|
19
|
+
if (!newWorkerId || typeof newWorkerId !== 'string') {
|
|
20
|
+
return NextResponse.json({ error: 'Bad Request', message: 'newWorkerId is required' }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
if (!oldWorkerId || typeof oldWorkerId !== 'string') {
|
|
23
|
+
return NextResponse.json({ error: 'Bad Request', message: 'oldWorkerId is required' }, { status: 400 });
|
|
24
|
+
}
|
|
25
|
+
const result = await transferSessionOwnership(sessionId, newWorkerId, oldWorkerId);
|
|
26
|
+
if (!result.transferred) {
|
|
27
|
+
log.warn('Session ownership transfer failed', {
|
|
28
|
+
sessionId,
|
|
29
|
+
newWorkerId,
|
|
30
|
+
oldWorkerId,
|
|
31
|
+
reason: result.reason,
|
|
32
|
+
});
|
|
33
|
+
return NextResponse.json({
|
|
34
|
+
transferred: false,
|
|
35
|
+
reason: result.reason,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
await removeWorkerSession(oldWorkerId, sessionId);
|
|
39
|
+
await addWorkerSession(newWorkerId, sessionId);
|
|
40
|
+
log.info('Session ownership transferred', {
|
|
41
|
+
sessionId,
|
|
42
|
+
oldWorkerId,
|
|
43
|
+
newWorkerId,
|
|
44
|
+
});
|
|
45
|
+
const session = await getSessionState(sessionId);
|
|
46
|
+
return NextResponse.json({
|
|
47
|
+
transferred: true,
|
|
48
|
+
session,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
log.error('Failed to transfer session ownership', { error, sessionId });
|
|
53
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to transfer session ownership' }, { status: 500 });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, DELETE /api/workers/[id]
|
|
3
|
+
*
|
|
4
|
+
* Get worker details or deregister a worker.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
interface RouteParams {
|
|
8
|
+
params: Promise<{
|
|
9
|
+
id: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createWorkerDeleteHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
13
|
+
export declare function createWorkerGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=get-delete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-delete.d.ts","sourceRoot":"","sources":["../../../../src/handlers/workers/get-delete.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAMvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,yBAAyB,KACV,SAAS,WAAW,EAAE,YAAY,WAAW,oCAyC3E;AAED,wBAAgB,sBAAsB,KACV,SAAS,WAAW,EAAE,YAAY,WAAW,oCAyBxE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET, DELETE /api/workers/[id]
|
|
3
|
+
*
|
|
4
|
+
* Get worker details or deregister a worker.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { deregisterWorker, getWorker, createLogger } from '@renseiai/agentfactory-server';
|
|
9
|
+
const log = createLogger('api:workers:detail');
|
|
10
|
+
export function createWorkerDeleteHandler() {
|
|
11
|
+
return async function DELETE(request, { params }) {
|
|
12
|
+
const authError = requireWorkerAuth(request);
|
|
13
|
+
if (authError)
|
|
14
|
+
return authError;
|
|
15
|
+
const { id: workerId } = await params;
|
|
16
|
+
try {
|
|
17
|
+
const worker = await getWorker(workerId);
|
|
18
|
+
if (!worker) {
|
|
19
|
+
return NextResponse.json({ error: 'Not Found', message: 'Worker not found' }, { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
const result = await deregisterWorker(workerId);
|
|
22
|
+
if (!result.deregistered) {
|
|
23
|
+
return NextResponse.json({ error: 'Service Unavailable', message: 'Failed to deregister worker' }, { status: 503 });
|
|
24
|
+
}
|
|
25
|
+
log.info('Worker deregistered via API', {
|
|
26
|
+
workerId,
|
|
27
|
+
unclaimedSessions: result.unclaimedSessions.length,
|
|
28
|
+
});
|
|
29
|
+
return NextResponse.json({
|
|
30
|
+
deregistered: true,
|
|
31
|
+
unclaimedSessions: result.unclaimedSessions,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
log.error('Failed to deregister worker', { error, workerId });
|
|
36
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to deregister worker' }, { status: 500 });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createWorkerGetHandler() {
|
|
41
|
+
return async function GET(request, { params }) {
|
|
42
|
+
const authError = requireWorkerAuth(request);
|
|
43
|
+
if (authError)
|
|
44
|
+
return authError;
|
|
45
|
+
const { id: workerId } = await params;
|
|
46
|
+
try {
|
|
47
|
+
const worker = await getWorker(workerId);
|
|
48
|
+
if (!worker) {
|
|
49
|
+
return NextResponse.json({ error: 'Not Found', message: 'Worker not found' }, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
return NextResponse.json(worker);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
log.error('Failed to get worker', { error, workerId });
|
|
55
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get worker' }, { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workers/[id]/heartbeat
|
|
3
|
+
*
|
|
4
|
+
* Send a heartbeat from a worker to keep registration active.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
interface RouteParams {
|
|
8
|
+
params: Promise<{
|
|
9
|
+
id: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createWorkerHeartbeatHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../../../src/handlers/workers/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAMvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,4BAA4B,KACf,SAAS,WAAW,EAAE,YAAY,WAAW,oCAiDzE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workers/[id]/heartbeat
|
|
3
|
+
*
|
|
4
|
+
* Send a heartbeat from a worker to keep registration active.
|
|
5
|
+
*/
|
|
6
|
+
import { NextResponse } from 'next/server';
|
|
7
|
+
import { requireWorkerAuth } from '../../middleware/worker-auth.js';
|
|
8
|
+
import { updateHeartbeat, getWorker, createLogger } from '@renseiai/agentfactory-server';
|
|
9
|
+
const log = createLogger('api:workers:heartbeat');
|
|
10
|
+
export function createWorkerHeartbeatHandler() {
|
|
11
|
+
return async function POST(request, { params }) {
|
|
12
|
+
const authError = requireWorkerAuth(request);
|
|
13
|
+
if (authError)
|
|
14
|
+
return authError;
|
|
15
|
+
const { id: workerId } = await params;
|
|
16
|
+
try {
|
|
17
|
+
const body = await request.json();
|
|
18
|
+
const { activeCount, load } = body;
|
|
19
|
+
if (typeof activeCount !== 'number' || activeCount < 0) {
|
|
20
|
+
return NextResponse.json({ error: 'Bad Request', message: 'activeCount must be a non-negative number' }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
const worker = await getWorker(workerId);
|
|
23
|
+
if (!worker) {
|
|
24
|
+
return NextResponse.json({ error: 'Not Found', message: 'Worker not found' }, { status: 404 });
|
|
25
|
+
}
|
|
26
|
+
const result = await updateHeartbeat(workerId, activeCount, load);
|
|
27
|
+
if (!result) {
|
|
28
|
+
return NextResponse.json({ error: 'Service Unavailable', message: 'Failed to update heartbeat' }, { status: 503 });
|
|
29
|
+
}
|
|
30
|
+
log.debug('Heartbeat received', {
|
|
31
|
+
workerId,
|
|
32
|
+
activeCount,
|
|
33
|
+
pendingWorkCount: result.pendingWorkCount,
|
|
34
|
+
});
|
|
35
|
+
return NextResponse.json(result);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
log.error('Failed to process heartbeat', { error, workerId });
|
|
39
|
+
return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process heartbeat' }, { status: 500 });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|