@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.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/src/__tests__/middleware-edge-safety.test.d.ts +2 -0
  4. package/dist/src/__tests__/middleware-edge-safety.test.d.ts.map +1 -0
  5. package/dist/src/__tests__/middleware-edge-safety.test.js +74 -0
  6. package/dist/src/__tests__/poll-project-filter.test.d.ts +2 -0
  7. package/dist/src/__tests__/poll-project-filter.test.d.ts.map +1 -0
  8. package/dist/src/__tests__/poll-project-filter.test.js +83 -0
  9. package/dist/src/__tests__/subpath-exports.test.d.ts +2 -0
  10. package/dist/src/__tests__/subpath-exports.test.d.ts.map +1 -0
  11. package/dist/src/__tests__/subpath-exports.test.js +35 -0
  12. package/dist/src/__tests__/webhook-project-filter.test.d.ts +2 -0
  13. package/dist/src/__tests__/webhook-project-filter.test.d.ts.map +1 -0
  14. package/dist/src/__tests__/webhook-project-filter.test.js +48 -0
  15. package/dist/src/factory.d.ts +140 -0
  16. package/dist/src/factory.d.ts.map +1 -0
  17. package/dist/src/factory.js +127 -0
  18. package/dist/src/handlers/cleanup.d.ts +44 -0
  19. package/dist/src/handlers/cleanup.d.ts.map +1 -0
  20. package/dist/src/handlers/cleanup.js +34 -0
  21. package/dist/src/handlers/config.d.ts +11 -0
  22. package/dist/src/handlers/config.d.ts.map +1 -0
  23. package/dist/src/handlers/config.js +20 -0
  24. package/dist/src/handlers/issue-tracker-proxy/index.d.ts +34 -0
  25. package/dist/src/handlers/issue-tracker-proxy/index.d.ts.map +1 -0
  26. package/dist/src/handlers/issue-tracker-proxy/index.js +230 -0
  27. package/dist/src/handlers/issue-tracker-proxy/serializer.d.ts +28 -0
  28. package/dist/src/handlers/issue-tracker-proxy/serializer.d.ts.map +1 -0
  29. package/dist/src/handlers/issue-tracker-proxy/serializer.js +95 -0
  30. package/dist/src/handlers/issue-tracker-proxy/types.d.ts +9 -0
  31. package/dist/src/handlers/issue-tracker-proxy/types.d.ts.map +1 -0
  32. package/dist/src/handlers/issue-tracker-proxy/types.js +4 -0
  33. package/dist/src/handlers/oauth/callback.d.ts +36 -0
  34. package/dist/src/handlers/oauth/callback.d.ts.map +1 -0
  35. package/dist/src/handlers/oauth/callback.js +96 -0
  36. package/dist/src/handlers/public/session-detail.d.ts +31 -0
  37. package/dist/src/handlers/public/session-detail.d.ts.map +1 -0
  38. package/dist/src/handlers/public/session-detail.js +91 -0
  39. package/dist/src/handlers/public/sessions-list.d.ts +22 -0
  40. package/dist/src/handlers/public/sessions-list.d.ts.map +1 -0
  41. package/dist/src/handlers/public/sessions-list.js +75 -0
  42. package/dist/src/handlers/public/stats.d.ts +28 -0
  43. package/dist/src/handlers/public/stats.d.ts.map +1 -0
  44. package/dist/src/handlers/public/stats.js +66 -0
  45. package/dist/src/handlers/sessions/activity.d.ts +15 -0
  46. package/dist/src/handlers/sessions/activity.d.ts.map +1 -0
  47. package/dist/src/handlers/sessions/activity.js +93 -0
  48. package/dist/src/handlers/sessions/claim.d.ts +15 -0
  49. package/dist/src/handlers/sessions/claim.d.ts.map +1 -0
  50. package/dist/src/handlers/sessions/claim.js +139 -0
  51. package/dist/src/handlers/sessions/completion.d.ts +16 -0
  52. package/dist/src/handlers/sessions/completion.d.ts.map +1 -0
  53. package/dist/src/handlers/sessions/completion.js +82 -0
  54. package/dist/src/handlers/sessions/external-urls.d.ts +15 -0
  55. package/dist/src/handlers/sessions/external-urls.d.ts.map +1 -0
  56. package/dist/src/handlers/sessions/external-urls.js +70 -0
  57. package/dist/src/handlers/sessions/get.d.ts +19 -0
  58. package/dist/src/handlers/sessions/get.d.ts.map +1 -0
  59. package/dist/src/handlers/sessions/get.js +47 -0
  60. package/dist/src/handlers/sessions/list.d.ts +27 -0
  61. package/dist/src/handlers/sessions/list.d.ts.map +1 -0
  62. package/dist/src/handlers/sessions/list.js +51 -0
  63. package/dist/src/handlers/sessions/lock-refresh.d.ts +14 -0
  64. package/dist/src/handlers/sessions/lock-refresh.d.ts.map +1 -0
  65. package/dist/src/handlers/sessions/lock-refresh.js +38 -0
  66. package/dist/src/handlers/sessions/progress.d.ts +15 -0
  67. package/dist/src/handlers/sessions/progress.d.ts.map +1 -0
  68. package/dist/src/handlers/sessions/progress.js +94 -0
  69. package/dist/src/handlers/sessions/prompts.d.ts +15 -0
  70. package/dist/src/handlers/sessions/prompts.d.ts.map +1 -0
  71. package/dist/src/handlers/sessions/prompts.js +91 -0
  72. package/dist/src/handlers/sessions/status.d.ts +19 -0
  73. package/dist/src/handlers/sessions/status.d.ts.map +1 -0
  74. package/dist/src/handlers/sessions/status.js +187 -0
  75. package/dist/src/handlers/sessions/tool-error.d.ts +15 -0
  76. package/dist/src/handlers/sessions/tool-error.d.ts.map +1 -0
  77. package/dist/src/handlers/sessions/tool-error.js +103 -0
  78. package/dist/src/handlers/sessions/transfer-ownership.d.ts +14 -0
  79. package/dist/src/handlers/sessions/transfer-ownership.d.ts.map +1 -0
  80. package/dist/src/handlers/sessions/transfer-ownership.js +56 -0
  81. package/dist/src/handlers/workers/get-delete.d.ts +15 -0
  82. package/dist/src/handlers/workers/get-delete.d.ts.map +1 -0
  83. package/dist/src/handlers/workers/get-delete.js +58 -0
  84. package/dist/src/handlers/workers/heartbeat.d.ts +14 -0
  85. package/dist/src/handlers/workers/heartbeat.d.ts.map +1 -0
  86. package/dist/src/handlers/workers/heartbeat.js +42 -0
  87. package/dist/src/handlers/workers/list.d.ts +22 -0
  88. package/dist/src/handlers/workers/list.d.ts.map +1 -0
  89. package/dist/src/handlers/workers/list.js +33 -0
  90. package/dist/src/handlers/workers/poll.d.ts +14 -0
  91. package/dist/src/handlers/workers/poll.d.ts.map +1 -0
  92. package/dist/src/handlers/workers/poll.js +96 -0
  93. package/dist/src/handlers/workers/register.d.ts +9 -0
  94. package/dist/src/handlers/workers/register.d.ts.map +1 -0
  95. package/dist/src/handlers/workers/register.js +45 -0
  96. package/dist/src/index.d.ts +52 -0
  97. package/dist/src/index.d.ts.map +1 -0
  98. package/dist/src/index.js +56 -0
  99. package/dist/src/linear-client-resolver.d.ts +59 -0
  100. package/dist/src/linear-client-resolver.d.ts.map +1 -0
  101. package/dist/src/linear-client-resolver.js +104 -0
  102. package/dist/src/middleware/cron-auth.d.ts +21 -0
  103. package/dist/src/middleware/cron-auth.d.ts.map +1 -0
  104. package/dist/src/middleware/cron-auth.js +46 -0
  105. package/dist/src/middleware/factory.d.ts +33 -0
  106. package/dist/src/middleware/factory.d.ts.map +1 -0
  107. package/dist/src/middleware/factory.js +185 -0
  108. package/dist/src/middleware/index.d.ts +16 -0
  109. package/dist/src/middleware/index.d.ts.map +1 -0
  110. package/dist/src/middleware/index.js +14 -0
  111. package/dist/src/middleware/types.d.ts +35 -0
  112. package/dist/src/middleware/types.d.ts.map +1 -0
  113. package/dist/src/middleware/types.js +4 -0
  114. package/dist/src/middleware/worker-auth.d.ts +25 -0
  115. package/dist/src/middleware/worker-auth.d.ts.map +1 -0
  116. package/dist/src/middleware/worker-auth.js +43 -0
  117. package/dist/src/orchestrator/error-formatting.d.ts +8 -0
  118. package/dist/src/orchestrator/error-formatting.d.ts.map +1 -0
  119. package/dist/src/orchestrator/error-formatting.js +35 -0
  120. package/dist/src/orchestrator/index.d.ts +4 -0
  121. package/dist/src/orchestrator/index.d.ts.map +1 -0
  122. package/dist/src/orchestrator/index.js +2 -0
  123. package/dist/src/orchestrator/types.d.ts +53 -0
  124. package/dist/src/orchestrator/types.d.ts.map +1 -0
  125. package/dist/src/orchestrator/types.js +4 -0
  126. package/dist/src/orchestrator/webhook-orchestrator.d.ts +32 -0
  127. package/dist/src/orchestrator/webhook-orchestrator.d.ts.map +1 -0
  128. package/dist/src/orchestrator/webhook-orchestrator.js +373 -0
  129. package/dist/src/types.d.ts +101 -0
  130. package/dist/src/types.d.ts.map +1 -0
  131. package/dist/src/types.js +7 -0
  132. package/dist/src/webhook/governor-bridge.d.ts +23 -0
  133. package/dist/src/webhook/governor-bridge.d.ts.map +1 -0
  134. package/dist/src/webhook/governor-bridge.js +36 -0
  135. package/dist/src/webhook/handlers/issue-updated.d.ts +15 -0
  136. package/dist/src/webhook/handlers/issue-updated.d.ts.map +1 -0
  137. package/dist/src/webhook/handlers/issue-updated.js +771 -0
  138. package/dist/src/webhook/handlers/session-created.d.ts +9 -0
  139. package/dist/src/webhook/handlers/session-created.d.ts.map +1 -0
  140. package/dist/src/webhook/handlers/session-created.js +337 -0
  141. package/dist/src/webhook/handlers/session-prompted.d.ts +9 -0
  142. package/dist/src/webhook/handlers/session-prompted.d.ts.map +1 -0
  143. package/dist/src/webhook/handlers/session-prompted.js +199 -0
  144. package/dist/src/webhook/handlers/session-updated.d.ts +9 -0
  145. package/dist/src/webhook/handlers/session-updated.d.ts.map +1 -0
  146. package/dist/src/webhook/handlers/session-updated.js +29 -0
  147. package/dist/src/webhook/processor.d.ts +22 -0
  148. package/dist/src/webhook/processor.d.ts.map +1 -0
  149. package/dist/src/webhook/processor.js +98 -0
  150. package/dist/src/webhook/signature.d.ts +16 -0
  151. package/dist/src/webhook/signature.d.ts.map +1 -0
  152. package/dist/src/webhook/signature.js +23 -0
  153. package/dist/src/webhook/utils.d.ts +61 -0
  154. package/dist/src/webhook/utils.d.ts.map +1 -0
  155. package/dist/src/webhook/utils.js +166 -0
  156. package/package.json +86 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * OAuth Callback Handler for Linear
3
+ *
4
+ * Exchanges an authorization code for an access token and stores it
5
+ * in Redis for workspace-specific token resolution.
6
+ *
7
+ * @see https://developers.linear.app/docs/oauth/authentication
8
+ */
9
+ import type { RouteHandler } from '../../types.js';
10
+ /**
11
+ * Configuration for the OAuth callback handler.
12
+ */
13
+ export interface OAuthConfig {
14
+ /** Linear OAuth client ID (defaults to LINEAR_CLIENT_ID env) */
15
+ clientId?: string;
16
+ /** Linear OAuth client secret (defaults to LINEAR_CLIENT_SECRET env) */
17
+ clientSecret?: string;
18
+ /** Application URL for redirect URI (defaults to NEXT_PUBLIC_APP_URL env) */
19
+ appUrl?: string;
20
+ /** Redirect path on success (defaults to '/?oauth=success') */
21
+ successRedirect?: string;
22
+ }
23
+ /**
24
+ * Create an OAuth callback route handler for Linear.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // In app/callback/route.ts:
29
+ * import { createOAuthCallbackHandler } from '@renseiai/agentfactory-nextjs'
30
+ * export const { GET } = createOAuthCallbackHandler()
31
+ * ```
32
+ */
33
+ export declare function createOAuthCallbackHandler(config?: OAuthConfig): {
34
+ GET: RouteHandler;
35
+ };
36
+ //# sourceMappingURL=callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../../../src/handlers/oauth/callback.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAID;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG;IAAE,GAAG,EAAE,YAAY,CAAA;CAAE,CAkGtF"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * OAuth Callback Handler for Linear
3
+ *
4
+ * Exchanges an authorization code for an access token and stores it
5
+ * in Redis for workspace-specific token resolution.
6
+ *
7
+ * @see https://developers.linear.app/docs/oauth/authentication
8
+ */
9
+ import { NextResponse } from 'next/server';
10
+ import { storeToken, fetchOrganization, isRedisConfigured, createLogger, generateRequestId, } from '@renseiai/agentfactory-server';
11
+ const baseLogger = createLogger('oauth-callback');
12
+ /**
13
+ * Create an OAuth callback route handler for Linear.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In app/callback/route.ts:
18
+ * import { createOAuthCallbackHandler } from '@renseiai/agentfactory-nextjs'
19
+ * export const { GET } = createOAuthCallbackHandler()
20
+ * ```
21
+ */
22
+ export function createOAuthCallbackHandler(config) {
23
+ return {
24
+ GET: async (request) => {
25
+ const requestId = generateRequestId();
26
+ const log = baseLogger.child({ requestId });
27
+ const clientId = config?.clientId ?? process.env.LINEAR_CLIENT_ID;
28
+ const clientSecret = config?.clientSecret ?? process.env.LINEAR_CLIENT_SECRET;
29
+ const appUrl = config?.appUrl
30
+ ?? process.env.NEXT_PUBLIC_APP_URL
31
+ ?? (process.env.VERCEL_PROJECT_PRODUCTION_URL && `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`)
32
+ ?? (process.env.VERCEL_URL && `https://${process.env.VERCEL_URL}`)
33
+ ?? 'http://localhost:3002';
34
+ const successRedirect = config?.successRedirect ?? '/?oauth=success';
35
+ const code = request.nextUrl.searchParams.get('code');
36
+ const error = request.nextUrl.searchParams.get('error');
37
+ const errorDescription = request.nextUrl.searchParams.get('error_description');
38
+ if (error) {
39
+ log.error('OAuth error from Linear', { oauthError: error, errorDescription });
40
+ return NextResponse.json({ error, description: errorDescription, requestId }, { status: 400 });
41
+ }
42
+ if (!code) {
43
+ log.warn('Missing authorization code');
44
+ return NextResponse.json({ error: 'Missing authorization code', requestId }, { status: 400 });
45
+ }
46
+ if (!clientId || !clientSecret) {
47
+ log.error('OAuth credentials not configured');
48
+ return NextResponse.json({ error: 'OAuth not configured', requestId }, { status: 500 });
49
+ }
50
+ try {
51
+ log.debug('Exchanging authorization code for token');
52
+ const tokenResponse = await fetch('https://api.linear.app/oauth/token', {
53
+ method: 'POST',
54
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
55
+ body: new URLSearchParams({
56
+ grant_type: 'authorization_code',
57
+ client_id: clientId,
58
+ client_secret: clientSecret,
59
+ redirect_uri: `${appUrl}/callback`,
60
+ code,
61
+ }),
62
+ });
63
+ if (!tokenResponse.ok) {
64
+ const errorText = await tokenResponse.text();
65
+ log.error('Token exchange failed', {
66
+ statusCode: tokenResponse.status,
67
+ errorDetails: errorText,
68
+ });
69
+ return NextResponse.json({ error: 'Token exchange failed', details: errorText, requestId }, { status: 400 });
70
+ }
71
+ const tokenData = (await tokenResponse.json());
72
+ if (isRedisConfigured()) {
73
+ const organization = await fetchOrganization(tokenData.access_token);
74
+ if (organization) {
75
+ await storeToken(organization.id, tokenData, organization.name);
76
+ log.info('OAuth successful, token stored', {
77
+ workspaceId: organization.id,
78
+ workspaceName: organization.name,
79
+ });
80
+ }
81
+ else {
82
+ log.warn('OAuth successful but could not fetch organization info - token not stored');
83
+ }
84
+ }
85
+ else {
86
+ log.info('OAuth successful, token received (Redis not configured for storage)');
87
+ }
88
+ return NextResponse.redirect(new URL(successRedirect, appUrl));
89
+ }
90
+ catch (err) {
91
+ log.error('OAuth callback error', { error: err });
92
+ return NextResponse.json({ error: 'Internal server error', requestId }, { status: 500 });
93
+ }
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * GET /api/public/sessions/:id
3
+ *
4
+ * Returns sanitized session detail for a single session.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ export interface PublicSessionDetailResponse {
8
+ id: string;
9
+ identifier: string;
10
+ status: 'queued' | 'parked' | 'working' | 'completed' | 'failed';
11
+ workType: string;
12
+ startedAt: string;
13
+ duration: number;
14
+ timeline: {
15
+ created: string;
16
+ queued?: string;
17
+ started?: string;
18
+ completed?: string;
19
+ };
20
+ }
21
+ export declare function createPublicSessionDetailHandler(): (_request: NextRequest, { params }: {
22
+ params: Promise<{
23
+ id: string;
24
+ }>;
25
+ }) => Promise<NextResponse<{
26
+ error: string;
27
+ }> | NextResponse<{
28
+ session: PublicSessionDetailResponse;
29
+ timestamp: string;
30
+ }>>;
31
+ //# sourceMappingURL=session-detail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-detail.d.ts","sourceRoot":"","sources":["../../../../src/handlers/public/session-detail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAYvD,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AA6ED,wBAAgB,gCAAgC,KAE5C,UAAU,WAAW,EACrB,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;;IAoClD"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * GET /api/public/sessions/:id
3
+ *
4
+ * Returns sanitized session detail for a single session.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { getAllSessions, isSessionInQueue, hashSessionId, isValidPublicId, createLogger, } from '@renseiai/agentfactory-server';
8
+ const log = createLogger('api/public/sessions/[id]');
9
+ function toPublicStatus(status, isParked = false) {
10
+ switch (status) {
11
+ case 'pending':
12
+ return isParked ? 'parked' : 'queued';
13
+ case 'claimed':
14
+ case 'running':
15
+ return 'working';
16
+ case 'completed':
17
+ return 'completed';
18
+ case 'failed':
19
+ case 'stopped':
20
+ return 'failed';
21
+ default:
22
+ return 'queued';
23
+ }
24
+ }
25
+ async function toPublicSessionDetail(session) {
26
+ const now = Math.floor(Date.now() / 1000);
27
+ const startTime = session.queuedAt
28
+ ? Math.floor(session.queuedAt / 1000)
29
+ : session.createdAt;
30
+ const endTime = ['completed', 'failed', 'stopped'].includes(session.status)
31
+ ? session.updatedAt
32
+ : now;
33
+ const isComplete = ['completed', 'failed', 'stopped'].includes(session.status);
34
+ let isParked = false;
35
+ if (session.status === 'pending') {
36
+ const inQueue = await isSessionInQueue(session.linearSessionId);
37
+ isParked = !inQueue;
38
+ }
39
+ return {
40
+ id: hashSessionId(session.linearSessionId),
41
+ identifier: session.issueIdentifier || 'Unknown',
42
+ status: toPublicStatus(session.status, isParked),
43
+ workType: session.workType || 'development',
44
+ startedAt: new Date(startTime * 1000).toISOString(),
45
+ duration: endTime - startTime,
46
+ timeline: {
47
+ created: new Date(session.createdAt * 1000).toISOString(),
48
+ queued: session.queuedAt
49
+ ? new Date(session.queuedAt).toISOString()
50
+ : undefined,
51
+ started: session.claimedAt
52
+ ? new Date(session.claimedAt * 1000).toISOString()
53
+ : undefined,
54
+ completed: isComplete
55
+ ? new Date(session.updatedAt * 1000).toISOString()
56
+ : undefined,
57
+ },
58
+ };
59
+ }
60
+ async function findSessionByPublicId(publicId) {
61
+ const allSessions = await getAllSessions();
62
+ for (const session of allSessions) {
63
+ if (hashSessionId(session.linearSessionId) === publicId) {
64
+ return session;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ export function createPublicSessionDetailHandler() {
70
+ return async function GET(_request, { params }) {
71
+ const { id: publicId } = await params;
72
+ if (!isValidPublicId(publicId)) {
73
+ return NextResponse.json({ error: 'Invalid session ID format' }, { status: 400 });
74
+ }
75
+ try {
76
+ const session = await findSessionByPublicId(publicId);
77
+ if (!session) {
78
+ return NextResponse.json({ error: 'Session not found' }, { status: 404 });
79
+ }
80
+ const publicSession = await toPublicSessionDetail(session);
81
+ return NextResponse.json({
82
+ session: publicSession,
83
+ timestamp: new Date().toISOString(),
84
+ });
85
+ }
86
+ catch (error) {
87
+ log.error('Failed to fetch public session detail', { error, publicId });
88
+ return NextResponse.json({ error: 'Failed to fetch session' }, { status: 500 });
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * GET /api/public/sessions
3
+ *
4
+ * Returns sanitized session data for public display.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ export interface PublicSessionResponse {
8
+ id: string;
9
+ identifier: string;
10
+ status: 'queued' | 'parked' | 'working' | 'completed' | 'failed' | 'stopped';
11
+ workType: string;
12
+ startedAt: string;
13
+ duration: number;
14
+ costUsd?: number;
15
+ provider?: string;
16
+ }
17
+ export declare function createPublicSessionsListHandler(): () => Promise<NextResponse<{
18
+ sessions: PublicSessionResponse[];
19
+ count: number;
20
+ timestamp: string;
21
+ }>>;
22
+ //# sourceMappingURL=sessions-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions-list.d.ts","sourceRoot":"","sources":["../../../../src/handlers/public/sessions-list.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAW1C,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC5E,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAqDD,wBAAgB,+BAA+B;;;;IA6B9C"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * GET /api/public/sessions
3
+ *
4
+ * Returns sanitized session data for public display.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { getAllSessions, isSessionInQueue, hashSessionId, createLogger, } from '@renseiai/agentfactory-server';
8
+ const log = createLogger('api/public/sessions');
9
+ function toPublicStatus(status, isParked = false) {
10
+ switch (status) {
11
+ case 'pending':
12
+ return isParked ? 'parked' : 'queued';
13
+ case 'claimed':
14
+ case 'running':
15
+ case 'finalizing':
16
+ return 'working';
17
+ case 'completed':
18
+ return 'completed';
19
+ case 'failed':
20
+ return 'failed';
21
+ case 'stopped':
22
+ return 'stopped';
23
+ default:
24
+ return 'queued';
25
+ }
26
+ }
27
+ async function toPublicSession(session) {
28
+ const now = Math.floor(Date.now() / 1000);
29
+ const startTime = session.queuedAt
30
+ ? Math.floor(session.queuedAt / 1000)
31
+ : session.createdAt;
32
+ const endTime = ['completed', 'failed', 'stopped'].includes(session.status)
33
+ ? session.updatedAt
34
+ : now;
35
+ let isParked = false;
36
+ if (session.status === 'pending') {
37
+ const inQueue = await isSessionInQueue(session.linearSessionId);
38
+ isParked = !inQueue;
39
+ }
40
+ return {
41
+ id: hashSessionId(session.linearSessionId),
42
+ identifier: session.issueIdentifier || 'Unknown',
43
+ status: toPublicStatus(session.status, isParked),
44
+ workType: session.workType || 'development',
45
+ startedAt: new Date(startTime * 1000).toISOString(),
46
+ duration: endTime - startTime,
47
+ costUsd: session.totalCostUsd,
48
+ provider: session.provider,
49
+ };
50
+ }
51
+ export function createPublicSessionsListHandler() {
52
+ return async function GET() {
53
+ try {
54
+ const allSessions = await getAllSessions();
55
+ const publicSessions = await Promise.all(allSessions.map(toPublicSession));
56
+ publicSessions.sort((a, b) => {
57
+ return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime();
58
+ });
59
+ return NextResponse.json({
60
+ sessions: publicSessions,
61
+ count: publicSessions.length,
62
+ timestamp: new Date().toISOString(),
63
+ });
64
+ }
65
+ catch (error) {
66
+ log.error('Failed to fetch public sessions', { error });
67
+ return NextResponse.json({
68
+ sessions: [],
69
+ count: 0,
70
+ timestamp: new Date().toISOString(),
71
+ error: 'Failed to fetch sessions',
72
+ }, { status: 500 });
73
+ }
74
+ };
75
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * GET /api/public/stats
3
+ *
4
+ * Returns aggregate statistics only - no sensitive data.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ export interface PublicStatsResponse {
8
+ workersOnline: number;
9
+ agentsWorking: number;
10
+ queueDepth: number;
11
+ completedToday: number;
12
+ availableCapacity: number;
13
+ totalCostToday: number;
14
+ totalCostAllTime: number;
15
+ sessionCountToday: number;
16
+ }
17
+ export declare function createPublicStatsHandler(): () => Promise<NextResponse<{
18
+ timestamp: string;
19
+ workersOnline: number;
20
+ agentsWorking: number;
21
+ queueDepth: number;
22
+ completedToday: number;
23
+ availableCapacity: number;
24
+ totalCostToday: number;
25
+ totalCostAllTime: number;
26
+ sessionCountToday: number;
27
+ }>>;
28
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../../../src/handlers/public/stats.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAUD,wBAAgB,wBAAwB;;mBAlBvB,MAAM;mBACN,MAAM;gBACT,MAAM;oBACF,MAAM;uBACH,MAAM;oBACT,MAAM;sBACJ,MAAM;uBACL,MAAM;IAiF1B"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * GET /api/public/stats
3
+ *
4
+ * Returns aggregate statistics only - no sensitive data.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { getAllSessions, listWorkers, getTotalCapacity, getQueueLength, createLogger } from '@renseiai/agentfactory-server';
8
+ const log = createLogger('api/public/stats');
9
+ function getTodayStart() {
10
+ const now = new Date();
11
+ const todayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
12
+ return Math.floor(todayStart.getTime() / 1000);
13
+ }
14
+ export function createPublicStatsHandler() {
15
+ return async function GET() {
16
+ try {
17
+ // Fetch workers once and pass to getTotalCapacity to avoid
18
+ // redundant listWorkers() calls that can return different snapshots
19
+ const [allSessions, workers, queueLength] = await Promise.all([
20
+ getAllSessions(),
21
+ listWorkers(),
22
+ getQueueLength(),
23
+ ]);
24
+ const capacity = await getTotalCapacity(workers);
25
+ const todayStart = getTodayStart();
26
+ const agentsWorking = allSessions.filter((s) => s.status === 'running' || s.status === 'claimed').length;
27
+ const completedToday = allSessions.filter((s) => s.status === 'completed' &&
28
+ s.updatedAt >= todayStart).length;
29
+ const todaySessions = allSessions.filter((s) => s.updatedAt >= todayStart);
30
+ const totalCostToday = todaySessions
31
+ .reduce((sum, s) => sum + (s.totalCostUsd ?? 0), 0);
32
+ const totalCostAllTime = allSessions
33
+ .reduce((sum, s) => sum + (s.totalCostUsd ?? 0), 0);
34
+ const workersOnline = workers.filter((w) => w.status === 'active').length;
35
+ const stats = {
36
+ workersOnline,
37
+ agentsWorking,
38
+ queueDepth: queueLength,
39
+ completedToday,
40
+ availableCapacity: capacity.availableCapacity,
41
+ totalCostToday: Math.round(totalCostToday * 10000) / 10000,
42
+ totalCostAllTime: Math.round(totalCostAllTime * 10000) / 10000,
43
+ sessionCountToday: todaySessions.length,
44
+ };
45
+ return NextResponse.json({
46
+ ...stats,
47
+ timestamp: new Date().toISOString(),
48
+ });
49
+ }
50
+ catch (error) {
51
+ log.error('Failed to get public stats', { error });
52
+ return NextResponse.json({
53
+ workersOnline: 0,
54
+ agentsWorking: 0,
55
+ queueDepth: 0,
56
+ completedToday: 0,
57
+ availableCapacity: 0,
58
+ totalCostToday: 0,
59
+ totalCostAllTime: 0,
60
+ sessionCountToday: 0,
61
+ error: 'Failed to fetch stats',
62
+ timestamp: new Date().toISOString(),
63
+ }, { status: 500 });
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/sessions/[id]/activity
3
+ *
4
+ * Report agent activity to be forwarded to Linear.
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 createSessionActivityHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/activity.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;AAUD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,WAAW,IACnC,SAAS,WAAW,EAAE,YAAY,WAAW,oCA+GzE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * POST /api/sessions/[id]/activity
3
+ *
4
+ * Report agent activity to be forwarded to Linear.
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 } from '@renseiai/agentfactory-linear';
10
+ const log = createLogger('api:sessions:activity');
11
+ export function createSessionActivityHandler(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, activity } = body;
20
+ if (!workerId || typeof workerId !== 'string') {
21
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
22
+ }
23
+ if (!activity || !activity.type || !activity.content) {
24
+ return NextResponse.json({ error: 'Bad Request', message: 'activity with type and content is required' }, { status: 400 });
25
+ }
26
+ const session = await getSessionState(sessionId);
27
+ if (!session) {
28
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
29
+ }
30
+ if (session.workerId && session.workerId !== workerId) {
31
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
32
+ }
33
+ // Skip Linear forwarding for governor-generated fake session IDs.
34
+ // When the governor can't create a real agent session on Linear (e.g., OAuth
35
+ // token issue), it generates a local ID prefixed with "governor-". There's no
36
+ // corresponding session on Linear's side, so forwarding would always fail.
37
+ if (sessionId.startsWith('governor-')) {
38
+ log.debug('Skipping Linear forwarding for governor-generated session', {
39
+ sessionId,
40
+ activityType: activity.type,
41
+ });
42
+ return NextResponse.json({
43
+ forwarded: 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
+ switch (activity.type) {
56
+ case 'thought':
57
+ await agentSession.emitThought(activity.content);
58
+ break;
59
+ case 'action':
60
+ await agentSession.emitAction(activity.toolName || 'Tool', activity.toolInput || {});
61
+ break;
62
+ case 'response':
63
+ await agentSession.emitResponse(activity.content);
64
+ break;
65
+ }
66
+ log.debug('Activity forwarded to Linear', {
67
+ sessionId,
68
+ activityType: activity.type,
69
+ issueId: session.issueId,
70
+ });
71
+ return NextResponse.json({
72
+ forwarded: true,
73
+ });
74
+ }
75
+ catch (linearError) {
76
+ const errorMessage = linearError instanceof Error ? linearError.message : String(linearError);
77
+ log.error('Failed to forward activity to Linear', {
78
+ error: errorMessage,
79
+ sessionId,
80
+ issueId: session.issueId,
81
+ });
82
+ return NextResponse.json({
83
+ forwarded: false,
84
+ reason: `Failed to forward to Linear: ${errorMessage}`,
85
+ });
86
+ }
87
+ }
88
+ catch (error) {
89
+ log.error('Failed to process activity', { error, sessionId });
90
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process activity' }, { status: 500 });
91
+ }
92
+ };
93
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/sessions/[id]/claim
3
+ *
4
+ * Claim a session for processing.
5
+ * Uses atomic operations to prevent race conditions.
6
+ */
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ interface RouteParams {
9
+ params: Promise<{
10
+ id: string;
11
+ }>;
12
+ }
13
+ export declare function createSessionClaimHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=claim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/claim.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAevD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,yBAAyB,KACZ,SAAS,WAAW,EAAE,YAAY,WAAW,oCAoJzE"}