@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,98 @@
1
+ /**
2
+ * Webhook Processor — Main Entry Point
3
+ *
4
+ * Receives Linear webhook events, verifies signatures, runs idempotency
5
+ * checks, and dispatches to sub-handlers.
6
+ */
7
+ import { NextResponse } from 'next/server';
8
+ import { isAgentSessionCreated, isAgentSessionPrompted, isAgentSessionUpdated, isIssueUpdate, } from '@renseiai/agentfactory-linear';
9
+ import { createLogger, generateRequestId } from '@renseiai/agentfactory-server';
10
+ import { verifyWebhookSignature } from './signature.js';
11
+ import { handleSessionCreated } from './handlers/session-created.js';
12
+ import { handleSessionUpdated } from './handlers/session-updated.js';
13
+ import { handleSessionPrompted } from './handlers/session-prompted.js';
14
+ import { handleIssueUpdated } from './handlers/issue-updated.js';
15
+ const baseLogger = createLogger('webhook');
16
+ /**
17
+ * Create webhook route handlers from config.
18
+ *
19
+ * Returns { POST, GET } for use as Next.js App Router exports.
20
+ */
21
+ export function createWebhookHandler(config) {
22
+ async function POST(request) {
23
+ const startTime = Date.now();
24
+ const requestId = generateRequestId();
25
+ const log = baseLogger.child({ requestId });
26
+ try {
27
+ const body = await request.text();
28
+ const signature = request.headers.get('linear-signature');
29
+ // Verify webhook signature
30
+ const webhookSecret = config.webhookSecret ?? process.env.LINEAR_WEBHOOK_SECRET;
31
+ if (!webhookSecret) {
32
+ const isProduction = process.env.NODE_ENV === 'production' ||
33
+ process.env.VERCEL_ENV === 'production';
34
+ if (isProduction) {
35
+ log.error('LINEAR_WEBHOOK_SECRET not configured - rejecting webhook in production');
36
+ return NextResponse.json({ error: 'Service unavailable', message: 'Webhook signature verification not configured' }, { status: 503 });
37
+ }
38
+ else {
39
+ log.warn('LINEAR_WEBHOOK_SECRET not configured - skipping signature verification in development');
40
+ }
41
+ }
42
+ else {
43
+ if (!verifyWebhookSignature(body, signature, webhookSecret)) {
44
+ log.warn('Invalid webhook signature');
45
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
46
+ }
47
+ }
48
+ const payload = JSON.parse(body);
49
+ log.info('Webhook received', {
50
+ webhookType: payload.type,
51
+ webhookAction: payload.action,
52
+ workspaceId: payload.organizationId,
53
+ });
54
+ const rawPayload = payload;
55
+ // Handle agent session 'create' events
56
+ if (isAgentSessionCreated(payload)) {
57
+ const result = await handleSessionCreated(config, payload, rawPayload, log);
58
+ if (result)
59
+ return result;
60
+ }
61
+ // Handle agent session 'update' events (stop signal)
62
+ if (isAgentSessionUpdated(payload)) {
63
+ const result = await handleSessionUpdated(config, payload, rawPayload, log);
64
+ if (result)
65
+ return result;
66
+ }
67
+ // Handle agent session 'prompted' events (follow-up messages)
68
+ if (isAgentSessionPrompted(payload)) {
69
+ const result = await handleSessionPrompted(config, payload, rawPayload, log);
70
+ if (result)
71
+ return result;
72
+ }
73
+ // Handle Issue update events (status transitions)
74
+ if (isIssueUpdate(payload)) {
75
+ const result = await handleIssueUpdated(config, payload, log);
76
+ if (result)
77
+ return result;
78
+ }
79
+ const durationMs = Date.now() - startTime;
80
+ log.info('Webhook processed', { durationMs });
81
+ return NextResponse.json({ success: true, duration: durationMs, requestId });
82
+ }
83
+ catch (err) {
84
+ const durationMs = Date.now() - startTime;
85
+ log.error('Webhook error', { error: err, durationMs });
86
+ return NextResponse.json({ error: 'Internal server error', requestId }, { status: 500 });
87
+ }
88
+ }
89
+ async function GET() {
90
+ baseLogger.debug('Health check requested');
91
+ return NextResponse.json({
92
+ status: 'ok',
93
+ endpoint: '/webhook',
94
+ description: 'Linear webhook receiver',
95
+ });
96
+ }
97
+ return { POST, GET };
98
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Webhook Signature Verification
3
+ *
4
+ * HMAC-SHA256 verification for Linear webhook signatures.
5
+ * @see https://developers.linear.app/docs/webhooks#validating-webhooks
6
+ */
7
+ /**
8
+ * Verify a Linear webhook signature using HMAC-SHA256.
9
+ *
10
+ * @param body - Raw request body string
11
+ * @param signature - Value of the `linear-signature` header
12
+ * @param secret - The webhook signing secret
13
+ * @returns true if the signature is valid
14
+ */
15
+ export declare function verifyWebhookSignature(body: string, signature: string | null, secret: string): boolean;
16
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../../src/webhook/signature.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAQT"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Webhook Signature Verification
3
+ *
4
+ * HMAC-SHA256 verification for Linear webhook signatures.
5
+ * @see https://developers.linear.app/docs/webhooks#validating-webhooks
6
+ */
7
+ import crypto from 'crypto';
8
+ /**
9
+ * Verify a Linear webhook signature using HMAC-SHA256.
10
+ *
11
+ * @param body - Raw request body string
12
+ * @param signature - Value of the `linear-signature` header
13
+ * @param secret - The webhook signing secret
14
+ * @returns true if the signature is valid
15
+ */
16
+ export function verifyWebhookSignature(body, signature, secret) {
17
+ if (!signature)
18
+ return false;
19
+ const hmac = crypto.createHmac('sha256', secret);
20
+ hmac.update(body);
21
+ const digest = hmac.digest('hex');
22
+ return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
23
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Webhook Utility Functions
3
+ *
4
+ * Shared helpers used by webhook sub-handlers.
5
+ */
6
+ import type { LinearAgentClient, AgentWorkType } from '@renseiai/agentfactory-linear';
7
+ import type { ResolvedWebhookConfig } from '../types.js';
8
+ /**
9
+ * Activity types for Linear Agent API
10
+ */
11
+ type ActivityType = 'thought' | 'response' | 'error';
12
+ /**
13
+ * Emit an activity to the agent session.
14
+ * Uses Linear's native Agent API.
15
+ */
16
+ export declare function emitActivity(client: LinearAgentClient, sessionId: string, type: ActivityType, body: string, ephemeral?: boolean): Promise<void>;
17
+ /**
18
+ * Consolidated stop signal handler.
19
+ *
20
+ * Handles stops for sessions in any non-terminal state:
21
+ * - running/claimed: Worker will detect the status change and stop the agent
22
+ * - pending: Removes from global work queue and/or issue-pending queue
23
+ * - Any other non-terminal state: Updates status and cleans up
24
+ */
25
+ export declare function handleStopSignal(config: ResolvedWebhookConfig, sessionId: string, issueId: string, organizationId?: string): Promise<void>;
26
+ /**
27
+ * Resolve a state ID to its name using the Linear API.
28
+ */
29
+ export declare function resolveStateName(config: ResolvedWebhookConfig, organizationId: string | undefined, issueId: string, stateId: string): Promise<string | undefined>;
30
+ /**
31
+ * Check if a project is allowed for auto-trigger.
32
+ */
33
+ export declare function isProjectAllowed(projectName: string | undefined, allowedProjects: string[]): boolean;
34
+ /**
35
+ * Check if an issue has any excluded labels.
36
+ */
37
+ export declare function hasExcludedLabel(labels: Array<{
38
+ name: string;
39
+ }> | undefined, excludedLabels: string[]): boolean;
40
+ /**
41
+ * Determine work type from issue status.
42
+ */
43
+ export declare function determineWorkType(status: string | undefined): AgentWorkType;
44
+ /**
45
+ * Default priority for work types (lower = higher priority).
46
+ */
47
+ export declare function defaultGetPriority(workType: AgentWorkType): number;
48
+ /**
49
+ * Get the app URL for constructing dashboard links.
50
+ */
51
+ export declare function getAppUrl(config: ResolvedWebhookConfig): string;
52
+ /**
53
+ * Get the priority for a work type, using config override if available.
54
+ */
55
+ export declare function getPriority(config: ResolvedWebhookConfig, workType: AgentWorkType): number;
56
+ /**
57
+ * Work type messages for queuing acknowledgment.
58
+ */
59
+ export declare const WORK_TYPE_MESSAGES: Record<AgentWorkType, string>;
60
+ export {};
61
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/webhook/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAarF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAIxD;;GAEG;AACH,KAAK,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAA;AAEpD;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,qBAAqB,EAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,qBAAqB,EAC7B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAIpG;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAI/G;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAG3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAelE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAM/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,aAAa,GAAG,MAAM,CAE1F;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAY5D,CAAA"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Webhook Utility Functions
3
+ *
4
+ * Shared helpers used by webhook sub-handlers.
5
+ */
6
+ import { STATUS_WORK_TYPE_MAP } from '@renseiai/agentfactory-linear';
7
+ import { getSessionState, updateSessionStatus, removeFromQueue, removeParkedWorkBySessionId, releaseClaim, getIssueLock, releaseIssueLock, promoteNextPendingWork, createLogger, } from '@renseiai/agentfactory-server';
8
+ const baseLogger = createLogger('webhook');
9
+ /**
10
+ * Emit an activity to the agent session.
11
+ * Uses Linear's native Agent API.
12
+ */
13
+ export async function emitActivity(client, sessionId, type, body, ephemeral) {
14
+ const isEphemeral = ephemeral ?? type === 'thought';
15
+ await client.createAgentActivity({
16
+ agentSessionId: sessionId,
17
+ content: { type, body },
18
+ ephemeral: isEphemeral,
19
+ });
20
+ }
21
+ /**
22
+ * Consolidated stop signal handler.
23
+ *
24
+ * Handles stops for sessions in any non-terminal state:
25
+ * - running/claimed: Worker will detect the status change and stop the agent
26
+ * - pending: Removes from global work queue and/or issue-pending queue
27
+ * - Any other non-terminal state: Updates status and cleans up
28
+ */
29
+ export async function handleStopSignal(config, sessionId, issueId, organizationId) {
30
+ const stopLog = baseLogger.child({ sessionId, issueId, handler: 'handleStopSignal' });
31
+ const session = await getSessionState(sessionId);
32
+ if (session && ['completed', 'failed', 'stopped'].includes(session.status)) {
33
+ stopLog.info('Session already in terminal state, skipping stop', {
34
+ status: session.status,
35
+ });
36
+ return;
37
+ }
38
+ if (session) {
39
+ stopLog.info('Stopping session', {
40
+ previousStatus: session.status,
41
+ workerId: session.workerId,
42
+ });
43
+ await updateSessionStatus(sessionId, 'stopped');
44
+ }
45
+ else {
46
+ stopLog.info('No session state found, cleaning up queues only');
47
+ }
48
+ await removeFromQueue(sessionId);
49
+ await removeParkedWorkBySessionId(issueId, sessionId);
50
+ await releaseClaim(sessionId);
51
+ const lock = await getIssueLock(issueId);
52
+ if (lock && lock.sessionId === sessionId) {
53
+ await releaseIssueLock(issueId);
54
+ await promoteNextPendingWork(issueId);
55
+ stopLog.info('Released issue lock and promoted next pending work');
56
+ }
57
+ try {
58
+ const linearClient = await config.linearClient.getClient(organizationId);
59
+ const statusMsg = session?.status === 'pending'
60
+ ? 'Stop signal received. Queued work has been cancelled.'
61
+ : 'Stop signal received. Agent will stop shortly.';
62
+ await emitActivity(linearClient, sessionId, 'response', statusMsg);
63
+ }
64
+ catch (err) {
65
+ stopLog.error('Failed to emit stop activity', { error: err });
66
+ }
67
+ }
68
+ /**
69
+ * Resolve a state ID to its name using the Linear API.
70
+ */
71
+ export async function resolveStateName(config, organizationId, issueId, stateId) {
72
+ try {
73
+ const linearClient = await config.linearClient.getClient(organizationId);
74
+ const issue = await linearClient.getIssue(issueId);
75
+ const team = await issue.team;
76
+ if (team) {
77
+ const statuses = await linearClient.getTeamStatuses(team.id);
78
+ const entry = Object.entries(statuses).find(([, id]) => id === stateId);
79
+ return entry?.[0];
80
+ }
81
+ return undefined;
82
+ }
83
+ catch (err) {
84
+ baseLogger.warn('Failed to resolve state name', { issueId, stateId, error: err });
85
+ return undefined;
86
+ }
87
+ }
88
+ /**
89
+ * Check if a project is allowed for auto-trigger.
90
+ */
91
+ export function isProjectAllowed(projectName, allowedProjects) {
92
+ if (allowedProjects.length === 0)
93
+ return true;
94
+ if (!projectName)
95
+ return false;
96
+ return allowedProjects.includes(projectName);
97
+ }
98
+ /**
99
+ * Check if an issue has any excluded labels.
100
+ */
101
+ export function hasExcludedLabel(labels, excludedLabels) {
102
+ if (excludedLabels.length === 0)
103
+ return false;
104
+ if (!labels || labels.length === 0)
105
+ return false;
106
+ return labels.some(label => excludedLabels.includes(label.name));
107
+ }
108
+ /**
109
+ * Determine work type from issue status.
110
+ */
111
+ export function determineWorkType(status) {
112
+ if (!status)
113
+ return 'development';
114
+ return STATUS_WORK_TYPE_MAP[status] ?? 'development';
115
+ }
116
+ /**
117
+ * Default priority for work types (lower = higher priority).
118
+ */
119
+ export function defaultGetPriority(workType) {
120
+ switch (workType) {
121
+ case 'qa': return 2;
122
+ case 'acceptance': return 2;
123
+ case 'refinement': return 2;
124
+ case 'refinement-coordination': return 2;
125
+ case 'inflight': return 2;
126
+ case 'backlog-creation': return 3;
127
+ case 'development': return 3;
128
+ case 'research': return 4;
129
+ case 'coordination': return 2;
130
+ case 'qa-coordination': return 2;
131
+ case 'acceptance-coordination': return 2;
132
+ default: return 3;
133
+ }
134
+ }
135
+ /**
136
+ * Get the app URL for constructing dashboard links.
137
+ */
138
+ export function getAppUrl(config) {
139
+ return config.appUrl
140
+ ?? process.env.NEXT_PUBLIC_APP_URL
141
+ ?? (process.env.VERCEL_PROJECT_PRODUCTION_URL && `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`)
142
+ ?? (process.env.VERCEL_URL && `https://${process.env.VERCEL_URL}`)
143
+ ?? 'https://agent.rensei.dev';
144
+ }
145
+ /**
146
+ * Get the priority for a work type, using config override if available.
147
+ */
148
+ export function getPriority(config, workType) {
149
+ return config.getPriority?.(workType) ?? defaultGetPriority(workType);
150
+ }
151
+ /**
152
+ * Work type messages for queuing acknowledgment.
153
+ */
154
+ export const WORK_TYPE_MESSAGES = {
155
+ research: 'Research work queued. Agent will analyze and flesh out story requirements...',
156
+ 'backlog-creation': 'Backlog creation queued. Agent will break down the story into separate issues...',
157
+ development: 'Development work queued. Waiting for an available worker...',
158
+ inflight: 'Resuming in-flight work. Agent will continue where it left off...',
159
+ qa: 'QA work queued. Waiting for an available worker to validate the implementation...',
160
+ acceptance: 'Acceptance testing queued. Agent will verify the deployed preview...',
161
+ refinement: 'Refinement work queued. Agent will address rejection feedback...',
162
+ coordination: 'Coordination work queued. Agent will orchestrate sub-issue execution...',
163
+ 'qa-coordination': 'QA coordination queued. Agent will validate all sub-issues in parallel...',
164
+ 'acceptance-coordination': 'Acceptance coordination queued. Agent will verify sub-issues and merge PR...',
165
+ 'refinement-coordination': 'Refinement coordination queued. Agent will triage QA/acceptance failures to sub-issues...',
166
+ };
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@renseiai/agentfactory-nextjs",
3
+ "version": "0.8.0",
4
+ "type": "module",
5
+ "description": "Next.js API route handlers for AgentFactory — webhook processor, worker/session management, public stats",
6
+ "author": "Rensei AI (https://rensei.ai)",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=22.0.0"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/renseiai/agentfactory",
14
+ "directory": "packages/nextjs"
15
+ },
16
+ "homepage": "https://github.com/renseiai/agentfactory/tree/main/packages/nextjs",
17
+ "bugs": {
18
+ "url": "https://github.com/renseiai/agentfactory/issues"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/src/index.d.ts",
25
+ "import": "./dist/src/index.js",
26
+ "default": "./dist/src/index.js"
27
+ },
28
+ "./middleware": {
29
+ "types": "./dist/src/middleware/index.d.ts",
30
+ "import": "./dist/src/middleware/index.js",
31
+ "default": "./dist/src/middleware/index.js"
32
+ }
33
+ },
34
+ "main": "./dist/src/index.js",
35
+ "types": "./dist/src/index.d.ts"
36
+ },
37
+ "keywords": [
38
+ "nextjs",
39
+ "api-routes",
40
+ "webhook",
41
+ "agent-server",
42
+ "linear"
43
+ ],
44
+ "main": "./dist/src/index.js",
45
+ "module": "./dist/src/index.js",
46
+ "types": "./dist/src/index.d.ts",
47
+ "exports": {
48
+ ".": {
49
+ "types": "./src/index.ts",
50
+ "import": "./dist/src/index.js",
51
+ "default": "./dist/src/index.js"
52
+ },
53
+ "./middleware": {
54
+ "types": "./src/middleware/index.ts",
55
+ "import": "./dist/src/middleware/index.js",
56
+ "default": "./dist/src/middleware/index.js"
57
+ }
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "README.md",
62
+ "LICENSE"
63
+ ],
64
+ "dependencies": {
65
+ "@renseiai/agentfactory": "workspace:*",
66
+ "@renseiai/agentfactory-linear": "workspace:*",
67
+ "@renseiai/agentfactory-server": "workspace:*"
68
+ },
69
+ "peerDependencies": {
70
+ "next": ">=14.0.0"
71
+ },
72
+ "devDependencies": {
73
+ "@types/node": "^22.5.4",
74
+ "next": "^15.3.3",
75
+ "typescript": "^5.7.3",
76
+ "vitest": "^3.2.3"
77
+ },
78
+ "scripts": {
79
+ "build": "tsc",
80
+ "typecheck": "tsc --noEmit",
81
+ "test": "vitest run",
82
+ "test:watch": "vitest",
83
+ "clean": "rm -rf dist",
84
+ "prepublishOnly": "pnpm clean && pnpm build"
85
+ }
86
+ }