@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,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware Factory — Edge Runtime Compatible
|
|
3
|
+
*
|
|
4
|
+
* Creates a Next.js middleware function that handles authentication,
|
|
5
|
+
* rate limiting, and security for AgentFactory API routes.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This module runs in the Edge Runtime. It MUST NOT import
|
|
8
|
+
* from @renseiai/agentfactory-server (which uses Node.js crypto/ioredis).
|
|
9
|
+
* All utilities are inlined for Edge compatibility.
|
|
10
|
+
*/
|
|
11
|
+
import { NextResponse } from 'next/server';
|
|
12
|
+
const RATE_LIMITS = {
|
|
13
|
+
public: { limit: 60, windowMs: 60_000 },
|
|
14
|
+
webhook: { limit: 10, windowMs: 1_000 },
|
|
15
|
+
dashboard: { limit: 30, windowMs: 60_000 },
|
|
16
|
+
};
|
|
17
|
+
const caches = new Map();
|
|
18
|
+
function checkRateLimit(type, key) {
|
|
19
|
+
const config = RATE_LIMITS[type];
|
|
20
|
+
let cache = caches.get(type);
|
|
21
|
+
if (!cache) {
|
|
22
|
+
cache = new Map();
|
|
23
|
+
caches.set(type, cache);
|
|
24
|
+
}
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const windowStart = now - config.windowMs;
|
|
27
|
+
let entry = cache.get(key);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
entry = { timestamps: [], lastAccess: now };
|
|
30
|
+
}
|
|
31
|
+
entry.timestamps = entry.timestamps.filter((ts) => ts > windowStart);
|
|
32
|
+
entry.lastAccess = now;
|
|
33
|
+
const allowed = entry.timestamps.length < config.limit;
|
|
34
|
+
if (allowed)
|
|
35
|
+
entry.timestamps.push(now);
|
|
36
|
+
cache.set(key, entry);
|
|
37
|
+
// LRU eviction at 10k entries
|
|
38
|
+
if (cache.size > 10_000) {
|
|
39
|
+
const entries = Array.from(cache.entries()).sort((a, b) => a[1].lastAccess - b[1].lastAccess);
|
|
40
|
+
const toRemove = Math.ceil(10_000 * 0.1);
|
|
41
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
42
|
+
cache.delete(entries[i][0]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const remaining = Math.max(0, config.limit - entry.timestamps.length);
|
|
46
|
+
const oldestTs = entry.timestamps[0];
|
|
47
|
+
const resetIn = oldestTs ? Math.max(0, oldestTs + config.windowMs - now) : 0;
|
|
48
|
+
return { allowed, remaining, resetIn, limit: config.limit };
|
|
49
|
+
}
|
|
50
|
+
// === Edge-compatible utilities ===
|
|
51
|
+
function getClientIP(headers) {
|
|
52
|
+
return (headers.get('x-forwarded-for')?.split(',')[0].trim() ||
|
|
53
|
+
headers.get('cf-connecting-ip') ||
|
|
54
|
+
headers.get('x-real-ip') ||
|
|
55
|
+
'unknown');
|
|
56
|
+
}
|
|
57
|
+
function buildRateLimitHeaders(result) {
|
|
58
|
+
return {
|
|
59
|
+
'X-RateLimit-Limit': result.limit.toString(),
|
|
60
|
+
'X-RateLimit-Remaining': result.remaining.toString(),
|
|
61
|
+
'X-RateLimit-Reset': Math.ceil(result.resetIn / 1000).toString(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Timing-safe string comparison using XOR (Edge-compatible).
|
|
66
|
+
* Does NOT use Node.js crypto.timingSafeEqual.
|
|
67
|
+
*/
|
|
68
|
+
function timingSafeEqual(a, b) {
|
|
69
|
+
if (a.length !== b.length)
|
|
70
|
+
return false;
|
|
71
|
+
let result = 0;
|
|
72
|
+
for (let i = 0; i < a.length; i++) {
|
|
73
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
74
|
+
}
|
|
75
|
+
return result === 0;
|
|
76
|
+
}
|
|
77
|
+
// === Route defaults ===
|
|
78
|
+
const DEFAULT_PUBLIC_ROUTES = ['/api/public/', '/dashboard', '/pipeline', '/settings', '/'];
|
|
79
|
+
const DEFAULT_PROTECTED_ROUTES = ['/api/sessions', '/api/workers'];
|
|
80
|
+
const DEFAULT_SESSION_PAGES = ['/sessions/'];
|
|
81
|
+
const DEFAULT_WEBHOOK_ROUTE = '/webhook';
|
|
82
|
+
const DEFAULT_PASSTHROUGH_ROUTES = ['/api/cleanup'];
|
|
83
|
+
/**
|
|
84
|
+
* Create an AgentFactory middleware function with configurable routes
|
|
85
|
+
* and rate limiting.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* // In middleware.ts:
|
|
90
|
+
* import { createAgentFactoryMiddleware } from '@renseiai/agentfactory-nextjs'
|
|
91
|
+
*
|
|
92
|
+
* const { middleware, matcherConfig } = createAgentFactoryMiddleware()
|
|
93
|
+
* export { middleware }
|
|
94
|
+
* export const config = matcherConfig
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function createAgentFactoryMiddleware(userConfig) {
|
|
98
|
+
const publicRoutes = userConfig?.routes?.public ?? DEFAULT_PUBLIC_ROUTES;
|
|
99
|
+
const protectedRoutes = userConfig?.routes?.protected ?? DEFAULT_PROTECTED_ROUTES;
|
|
100
|
+
const sessionPages = userConfig?.routes?.sessionPages ?? DEFAULT_SESSION_PAGES;
|
|
101
|
+
const webhookRoute = userConfig?.routes?.webhook ?? DEFAULT_WEBHOOK_ROUTE;
|
|
102
|
+
const passthroughRoutes = userConfig?.routes?.passthrough ?? DEFAULT_PASSTHROUGH_ROUTES;
|
|
103
|
+
function middleware(request) {
|
|
104
|
+
const { pathname } = request.nextUrl;
|
|
105
|
+
const clientIP = getClientIP(request.headers);
|
|
106
|
+
// === PROTECTED INTERNAL APIS (checked first — no rate limiting) ===
|
|
107
|
+
// Must come before public routes because '/' in publicRoutes would
|
|
108
|
+
// match all pathnames via startsWith, rate-limiting authenticated workers.
|
|
109
|
+
if (protectedRoutes.some(route => pathname.startsWith(route))) {
|
|
110
|
+
const workerApiKey = process.env.WORKER_API_KEY;
|
|
111
|
+
// In development without key, allow access
|
|
112
|
+
if (!workerApiKey && process.env.NODE_ENV !== 'production') {
|
|
113
|
+
return NextResponse.next();
|
|
114
|
+
}
|
|
115
|
+
if (!workerApiKey) {
|
|
116
|
+
console.error('WORKER_API_KEY not configured - blocking protected API access');
|
|
117
|
+
return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
118
|
+
}
|
|
119
|
+
const authHeader = request.headers.get('authorization');
|
|
120
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
121
|
+
return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
122
|
+
}
|
|
123
|
+
const token = authHeader.slice(7);
|
|
124
|
+
if (!timingSafeEqual(token, workerApiKey)) {
|
|
125
|
+
return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
126
|
+
}
|
|
127
|
+
return NextResponse.next();
|
|
128
|
+
}
|
|
129
|
+
// === WEBHOOK ROUTE ===
|
|
130
|
+
if (pathname === webhookRoute) {
|
|
131
|
+
const result = checkRateLimit('webhook', clientIP);
|
|
132
|
+
if (!result.allowed) {
|
|
133
|
+
return new NextResponse(JSON.stringify({ error: 'Too many requests' }), {
|
|
134
|
+
status: 429,
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
...buildRateLimitHeaders(result),
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return NextResponse.next();
|
|
142
|
+
}
|
|
143
|
+
// === SESSION DETAIL PAGES ===
|
|
144
|
+
if (sessionPages.some(route => pathname.startsWith(route))) {
|
|
145
|
+
return NextResponse.next();
|
|
146
|
+
}
|
|
147
|
+
// === PASSTHROUGH ROUTES ===
|
|
148
|
+
if (passthroughRoutes.some(route => pathname === route || pathname.startsWith(route))) {
|
|
149
|
+
return NextResponse.next();
|
|
150
|
+
}
|
|
151
|
+
// === PUBLIC ROUTES (rate limited) ===
|
|
152
|
+
if (publicRoutes.some(route => pathname === route || pathname.startsWith(route))) {
|
|
153
|
+
const result = checkRateLimit('public', clientIP);
|
|
154
|
+
if (!result.allowed) {
|
|
155
|
+
return new NextResponse(JSON.stringify({ error: 'Too many requests' }), {
|
|
156
|
+
status: 429,
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
...buildRateLimitHeaders(result),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const response = NextResponse.next();
|
|
164
|
+
const rateLimitHeaders = buildRateLimitHeaders(result);
|
|
165
|
+
for (const [key, value] of Object.entries(rateLimitHeaders)) {
|
|
166
|
+
response.headers.set(key, value);
|
|
167
|
+
}
|
|
168
|
+
return response;
|
|
169
|
+
}
|
|
170
|
+
// === ALL OTHER ROUTES ===
|
|
171
|
+
return NextResponse.next();
|
|
172
|
+
}
|
|
173
|
+
const matcherConfig = {
|
|
174
|
+
matcher: [
|
|
175
|
+
'/api/:path*',
|
|
176
|
+
webhookRoute,
|
|
177
|
+
'/dashboard',
|
|
178
|
+
'/pipeline',
|
|
179
|
+
'/settings',
|
|
180
|
+
'/sessions/:path*',
|
|
181
|
+
'/',
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
return { middleware, matcherConfig };
|
|
185
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Runtime-safe middleware exports.
|
|
3
|
+
*
|
|
4
|
+
* This barrel ONLY re-exports modules that are compatible with Next.js
|
|
5
|
+
* Edge Runtime. It deliberately excludes worker-auth and cron-auth
|
|
6
|
+
* which depend on @renseiai/agentfactory-server (Node.js crypto/ioredis).
|
|
7
|
+
*
|
|
8
|
+
* Use this subpath import in middleware.ts:
|
|
9
|
+
* import { createAgentFactoryMiddleware } from '@renseiai/agentfactory-nextjs/middleware'
|
|
10
|
+
*
|
|
11
|
+
* Do NOT import from the main barrel ('@renseiai/agentfactory-nextjs')
|
|
12
|
+
* in Edge Runtime — it pulls in Node.js-only modules.
|
|
13
|
+
*/
|
|
14
|
+
export { createAgentFactoryMiddleware } from './factory.js';
|
|
15
|
+
export type { MiddlewareConfig } from './types.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAA;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Runtime-safe middleware exports.
|
|
3
|
+
*
|
|
4
|
+
* This barrel ONLY re-exports modules that are compatible with Next.js
|
|
5
|
+
* Edge Runtime. It deliberately excludes worker-auth and cron-auth
|
|
6
|
+
* which depend on @renseiai/agentfactory-server (Node.js crypto/ioredis).
|
|
7
|
+
*
|
|
8
|
+
* Use this subpath import in middleware.ts:
|
|
9
|
+
* import { createAgentFactoryMiddleware } from '@renseiai/agentfactory-nextjs/middleware'
|
|
10
|
+
*
|
|
11
|
+
* Do NOT import from the main barrel ('@renseiai/agentfactory-nextjs')
|
|
12
|
+
* in Edge Runtime — it pulls in Node.js-only modules.
|
|
13
|
+
*/
|
|
14
|
+
export { createAgentFactoryMiddleware } from './factory.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the middleware factory.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the AgentFactory middleware.
|
|
6
|
+
*/
|
|
7
|
+
export interface MiddlewareConfig {
|
|
8
|
+
/** Route path configuration */
|
|
9
|
+
routes?: {
|
|
10
|
+
/** Public routes - no auth, with rate limiting (default: ['/api/public/', '/dashboard', '/']) */
|
|
11
|
+
public?: string[];
|
|
12
|
+
/** Protected routes - require WORKER_API_KEY (default: ['/api/sessions', '/api/workers']) */
|
|
13
|
+
protected?: string[];
|
|
14
|
+
/** Session detail pages - allow public access (default: ['/sessions/']) */
|
|
15
|
+
sessionPages?: string[];
|
|
16
|
+
/** Webhook route (default: '/webhook') */
|
|
17
|
+
webhook?: string;
|
|
18
|
+
/** Routes with custom auth in handler (default: ['/api/cleanup']) */
|
|
19
|
+
passthrough?: string[];
|
|
20
|
+
};
|
|
21
|
+
/** Rate limit configurations */
|
|
22
|
+
rateLimits?: {
|
|
23
|
+
/** Public endpoint rate limit (default: 60/min) */
|
|
24
|
+
public?: {
|
|
25
|
+
max: number;
|
|
26
|
+
windowMs: number;
|
|
27
|
+
};
|
|
28
|
+
/** Webhook endpoint rate limit (default: 10/sec) */
|
|
29
|
+
webhook?: {
|
|
30
|
+
max: number;
|
|
31
|
+
windowMs: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/middleware/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,MAAM,CAAC,EAAE;QACP,iGAAiG;QACjG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;QACjB,6FAA6F;QAC7F,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;QACpB,2EAA2E;QAC3E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;QACvB,0CAA0C;QAC1C,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,qEAAqE;QACrE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KACvB,CAAA;IACD,gCAAgC;IAChC,UAAU,CAAC,EAAE;QACX,mDAAmD;QACnD,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;QAC1C,oDAAoD;QACpD,OAAO,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;KAC5C,CAAA;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Authentication — Next.js Adapter
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around @renseiai/agentfactory-server's framework-agnostic
|
|
5
|
+
* auth functions, adapted for Next.js request/response types.
|
|
6
|
+
*/
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import { isWorkerAuthConfigured } from '@renseiai/agentfactory-server';
|
|
9
|
+
export { isWorkerAuthConfigured };
|
|
10
|
+
/**
|
|
11
|
+
* Verify worker API key from request
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyWorkerAuth(request: NextRequest): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Create an unauthorized response
|
|
16
|
+
*/
|
|
17
|
+
export declare function unauthorizedResponse(): NextResponse;
|
|
18
|
+
/**
|
|
19
|
+
* Middleware helper for protected routes
|
|
20
|
+
*
|
|
21
|
+
* @param request - Next.js request object
|
|
22
|
+
* @returns NextResponse if unauthorized, null if authorized
|
|
23
|
+
*/
|
|
24
|
+
export declare function requireWorkerAuth(request: NextRequest): NextResponse | null;
|
|
25
|
+
//# sourceMappingURL=worker-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-auth.d.ts","sourceRoot":"","sources":["../../../src/middleware/worker-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAGL,sBAAsB,EAEvB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EAAE,sBAAsB,EAAE,CAAA;AAIjC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAO9D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,YAAY,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,GAAG,IAAI,CAS3E"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Authentication — Next.js Adapter
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around @renseiai/agentfactory-server's framework-agnostic
|
|
5
|
+
* auth functions, adapted for Next.js request/response types.
|
|
6
|
+
*/
|
|
7
|
+
import { NextResponse } from 'next/server';
|
|
8
|
+
import { extractBearerToken, verifyApiKey, isWorkerAuthConfigured, createLogger, } from '@renseiai/agentfactory-server';
|
|
9
|
+
export { isWorkerAuthConfigured };
|
|
10
|
+
const log = createLogger('worker-auth');
|
|
11
|
+
/**
|
|
12
|
+
* Verify worker API key from request
|
|
13
|
+
*/
|
|
14
|
+
export function verifyWorkerAuth(request) {
|
|
15
|
+
const token = extractBearerToken(request.headers.get('authorization'));
|
|
16
|
+
if (!token) {
|
|
17
|
+
log.debug('Missing or invalid Authorization header');
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return verifyApiKey(token);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create an unauthorized response
|
|
24
|
+
*/
|
|
25
|
+
export function unauthorizedResponse() {
|
|
26
|
+
return NextResponse.json({ error: 'Unauthorized', message: 'Invalid or missing API key' }, { status: 401 });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Middleware helper for protected routes
|
|
30
|
+
*
|
|
31
|
+
* @param request - Next.js request object
|
|
32
|
+
* @returns NextResponse if unauthorized, null if authorized
|
|
33
|
+
*/
|
|
34
|
+
export function requireWorkerAuth(request) {
|
|
35
|
+
if (!verifyWorkerAuth(request)) {
|
|
36
|
+
log.warn('Unauthorized worker API request', {
|
|
37
|
+
path: request.nextUrl.pathname,
|
|
38
|
+
ip: request.headers.get('x-forwarded-for') || 'unknown',
|
|
39
|
+
});
|
|
40
|
+
return unauthorizedResponse();
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-formatting.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/error-formatting.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CA+B1D"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic error formatting for Linear comments.
|
|
3
|
+
*/
|
|
4
|
+
import { AgentSpawnError } from '@renseiai/agentfactory-linear';
|
|
5
|
+
/**
|
|
6
|
+
* Format an error for posting as a Linear comment with markdown.
|
|
7
|
+
*/
|
|
8
|
+
export function formatErrorForComment(error) {
|
|
9
|
+
const lines = ['## Agent Error', '', `**${error.name}**: ${error.message}`];
|
|
10
|
+
if (error instanceof AgentSpawnError) {
|
|
11
|
+
lines.push('');
|
|
12
|
+
lines.push(`- Issue ID: ${error.issueId}`);
|
|
13
|
+
if (error.sessionId) {
|
|
14
|
+
lines.push(`- Session ID: ${error.sessionId}`);
|
|
15
|
+
}
|
|
16
|
+
lines.push(`- Retryable: ${error.isRetryable ? 'Yes' : 'No'}`);
|
|
17
|
+
if (error.cause) {
|
|
18
|
+
lines.push(`- Cause: ${error.cause.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (error.stack) {
|
|
22
|
+
lines.push('');
|
|
23
|
+
lines.push('<details>');
|
|
24
|
+
lines.push('<summary>Stack Trace</summary>');
|
|
25
|
+
lines.push('');
|
|
26
|
+
lines.push('```');
|
|
27
|
+
lines.push(error.stack);
|
|
28
|
+
lines.push('```');
|
|
29
|
+
lines.push('</details>');
|
|
30
|
+
}
|
|
31
|
+
lines.push('');
|
|
32
|
+
lines.push(`---`);
|
|
33
|
+
lines.push(`*Error occurred at ${new Date().toISOString()}*`);
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createWebhookOrchestrator } from './webhook-orchestrator.js';
|
|
2
|
+
export { formatErrorForComment } from './error-formatting.js';
|
|
3
|
+
export type { WebhookOrchestratorConfig, WebhookOrchestratorHooks, WebhookOrchestratorInstance, } from './types.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,YAAY,EACV,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the webhook orchestrator factory.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentProcess, StopAgentResult, ForwardPromptResult } from '@renseiai/agentfactory';
|
|
5
|
+
import type { RetryConfig } from '@renseiai/agentfactory-linear';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the webhook orchestrator.
|
|
8
|
+
*/
|
|
9
|
+
export interface WebhookOrchestratorConfig {
|
|
10
|
+
/** Maximum concurrent agents (default: 10) */
|
|
11
|
+
maxConcurrent?: number;
|
|
12
|
+
/** Enable auto-transition of Linear statuses (default: true) */
|
|
13
|
+
autoTransition?: boolean;
|
|
14
|
+
/** Retry configuration for agent spawning */
|
|
15
|
+
retryConfig?: Required<RetryConfig>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Lifecycle hooks for agent events.
|
|
19
|
+
* Consumers use these to add custom behavior (e.g., marking agent-worked).
|
|
20
|
+
*/
|
|
21
|
+
export interface WebhookOrchestratorHooks {
|
|
22
|
+
/** Called when an agent starts successfully */
|
|
23
|
+
onAgentComplete?: (agent: AgentProcess) => Promise<void> | void;
|
|
24
|
+
/** Called when an agent fails with an error */
|
|
25
|
+
onAgentError?: (agent: AgentProcess, error: Error) => Promise<void> | void;
|
|
26
|
+
/** Called when an agent is stopped (e.g., by user) */
|
|
27
|
+
onAgentStopped?: (agent: AgentProcess) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The webhook orchestrator instance provides methods to spawn, stop,
|
|
31
|
+
* and forward prompts to agents.
|
|
32
|
+
*/
|
|
33
|
+
export interface WebhookOrchestratorInstance {
|
|
34
|
+
/**
|
|
35
|
+
* Spawn an agent for an issue with idempotency and retry logic.
|
|
36
|
+
* Returns immediately after spawning — does not wait for completion.
|
|
37
|
+
*/
|
|
38
|
+
spawnAgentAsync(issueId: string, sessionId: string, webhookId?: string): Promise<{
|
|
39
|
+
spawned: boolean;
|
|
40
|
+
reason?: string;
|
|
41
|
+
agent?: AgentProcess;
|
|
42
|
+
error?: Error;
|
|
43
|
+
}>;
|
|
44
|
+
/** Stop an agent by session ID */
|
|
45
|
+
stopAgentBySession(sessionId: string, cleanupWorktree?: boolean): Promise<StopAgentResult>;
|
|
46
|
+
/** Get an active agent by session ID */
|
|
47
|
+
getAgentBySession(sessionId: string): AgentProcess | undefined;
|
|
48
|
+
/** Check if an agent is already running for an issue */
|
|
49
|
+
isAgentRunningForIssue(issueId: string): boolean;
|
|
50
|
+
/** Forward a follow-up prompt to an existing or new agent */
|
|
51
|
+
forwardPromptAsync(issueId: string, sessionId: string, promptText: string): Promise<ForwardPromptResult>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAChG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAEhE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gEAAgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAA;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,+CAA+C;IAC/C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/D,+CAA+C;IAC/C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC1E,sDAAsD;IACtD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAC/C;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,YAAY,CAAA;QACpB,KAAK,CAAC,EAAE,KAAK,CAAA;KACd,CAAC,CAAA;IAEF,kCAAkC;IAClC,kBAAkB,CAChB,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,CAAC,eAAe,CAAC,CAAA;IAE3B,wCAAwC;IACxC,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAA;IAE9D,wDAAwD;IACxD,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;IAEhD,6DAA6D;IAC7D,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAAA;CAChC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook Orchestrator Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates a singleton orchestrator instance configured for webhook-triggered
|
|
5
|
+
* agent spawning. Includes retry logic, idempotency, session state persistence,
|
|
6
|
+
* and error activity emission to Linear.
|
|
7
|
+
*
|
|
8
|
+
* Consumers provide lifecycle hooks (e.g., onAgentComplete) for custom behavior
|
|
9
|
+
* like marking issues as "agent-worked" for automated QA.
|
|
10
|
+
*/
|
|
11
|
+
import type { WebhookOrchestratorConfig, WebhookOrchestratorHooks, WebhookOrchestratorInstance } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Create a webhook orchestrator instance.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Orchestrator configuration
|
|
16
|
+
* @param hooks - Lifecycle hooks for custom behavior
|
|
17
|
+
* @returns A webhook orchestrator instance
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const orchestrator = createWebhookOrchestrator(
|
|
22
|
+
* { maxConcurrent: 10 },
|
|
23
|
+
* {
|
|
24
|
+
* onAgentComplete: async (agent) => {
|
|
25
|
+
* await markAgentWorked(agent.issueId, { ... })
|
|
26
|
+
* },
|
|
27
|
+
* }
|
|
28
|
+
* )
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createWebhookOrchestrator(config?: WebhookOrchestratorConfig, hooks?: WebhookOrchestratorHooks): WebhookOrchestratorInstance;
|
|
32
|
+
//# sourceMappingURL=webhook-orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-orchestrator.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/webhook-orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqCH,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC5B,MAAM,YAAY,CAAA;AAyFnB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,CAAC,EAAE,yBAAyB,EAClC,KAAK,CAAC,EAAE,wBAAwB,GAC/B,2BAA2B,CAsT7B"}
|