@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rensei AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # @renseiai/agentfactory-nextjs
2
+
3
+ Next.js route handlers, webhook processor, middleware, and OAuth for [AgentFactory](https://github.com/renseiai/agentfactory). Drop-in API routes that turn a Next.js app into a full agent fleet server.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @renseiai/agentfactory-nextjs
9
+ ```
10
+
11
+ Or scaffold a complete project:
12
+
13
+ ```bash
14
+ npx @renseiai/create-agentfactory-app my-agent
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Configure routes
20
+
21
+ ```typescript
22
+ // src/lib/config.ts
23
+ import { createAllRoutes, createDefaultLinearClientResolver } from '@renseiai/agentfactory-nextjs'
24
+
25
+ export const routes = createAllRoutes({
26
+ linearClient: createDefaultLinearClientResolver(),
27
+ })
28
+ ```
29
+
30
+ ### 2. Add webhook route
31
+
32
+ ```typescript
33
+ // src/app/webhook/route.ts
34
+ import { routes } from '@/lib/config'
35
+ export const POST = routes.webhook.POST
36
+ export const GET = routes.webhook.GET
37
+ ```
38
+
39
+ ### 3. Add middleware
40
+
41
+ ```typescript
42
+ // src/middleware.ts
43
+ import { createAgentFactoryMiddleware } from '@renseiai/agentfactory-nextjs'
44
+
45
+ const { middleware } = createAgentFactoryMiddleware()
46
+ export { middleware }
47
+
48
+ export const config = {
49
+ matcher: ['/api/:path*', '/webhook', '/dashboard', '/sessions/:path*', '/'],
50
+ }
51
+ ```
52
+
53
+ ## What's Included
54
+
55
+ `createAllRoutes()` generates 21+ route handlers from a single config:
56
+
57
+ | Route Group | Endpoints | Purpose |
58
+ |-------------|-----------|---------|
59
+ | **Webhook** | `POST /webhook` | Receive Linear events, dispatch agents |
60
+ | **Workers** | `/api/workers/*` | Worker registration, heartbeat, polling |
61
+ | **Sessions** | `/api/sessions/*` | Session management, status, activity |
62
+ | **Public** | `/api/public/*` | Public stats, session list |
63
+ | **Cleanup** | `/api/cleanup` | Orphaned resource cleanup |
64
+ | **OAuth** | `/callback` | Linear OAuth callback |
65
+
66
+ Each route file is a 2-line re-export:
67
+
68
+ ```typescript
69
+ import { routes } from '@/lib/config'
70
+ export const GET = routes.sessions.list.GET
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ ```typescript
76
+ const routes = createAllRoutes({
77
+ // Required: how to resolve a Linear API client
78
+ linearClient: createDefaultLinearClientResolver(),
79
+
80
+ // Optional: customize prompts, detection, priority
81
+ generatePrompt: (identifier, workType, mentionContext) => string,
82
+ detectWorkTypeFromPrompt: (prompt, validWorkTypes) => AgentWorkType | undefined,
83
+ getPriority: (workType) => number,
84
+
85
+ // Optional: auto-trigger QA/acceptance
86
+ autoTrigger: {
87
+ enableAutoQA: true,
88
+ enableAutoAcceptance: false,
89
+ autoQARequireAgentWorked: true,
90
+ autoAcceptanceRequireAgentWorked: true,
91
+ autoQAProjects: [],
92
+ autoAcceptanceProjects: [],
93
+ autoQAExcludeLabels: [],
94
+ autoAcceptanceExcludeLabels: [],
95
+ },
96
+
97
+ // Optional: governor integration
98
+ // 'direct' (default) — webhooks dispatch work directly
99
+ // 'event-bridge' — dispatch AND publish governor events (dual-write)
100
+ // 'governor-only' — only publish events, governor handles all dispatch
101
+ governorMode: 'event-bridge',
102
+
103
+ // Optional: OAuth
104
+ oauth: { clientId: '...', clientSecret: '...' },
105
+ })
106
+ ```
107
+
108
+ ### Governor Event Bridge
109
+
110
+ When `governorMode` is `event-bridge` or `governor-only`, webhook handlers publish events to a `GovernorEventBus`. Wire the bus at server startup:
111
+
112
+ ```typescript
113
+ import { RedisEventBus } from '@renseiai/agentfactory-server'
114
+ import { setGovernorEventBus } from '@renseiai/agentfactory-nextjs'
115
+
116
+ const eventBus = new RedisEventBus()
117
+ setGovernorEventBus(eventBus)
118
+ ```
119
+
120
+ The governor process (running `af-governor`) consumes from the same Redis Stream and dispatches work through the shared Redis queue.
121
+
122
+ ## Middleware
123
+
124
+ Handles API key auth, rate limiting, and webhook signature verification:
125
+
126
+ ```typescript
127
+ const { middleware } = createAgentFactoryMiddleware({
128
+ routes: {
129
+ public: ['/api/public/', '/dashboard', '/'],
130
+ protected: ['/api/sessions', '/api/workers'],
131
+ webhook: '/webhook',
132
+ },
133
+ rateLimits: {
134
+ public: { max: 60, windowMs: 60_000 },
135
+ webhook: { max: 10, windowMs: 1_000 },
136
+ },
137
+ })
138
+ ```
139
+
140
+ ## Environment Variables
141
+
142
+ | Variable | Required | Description |
143
+ |----------|----------|-------------|
144
+ | `LINEAR_ACCESS_TOKEN` | Yes | Linear API key |
145
+ | `LINEAR_WEBHOOK_SECRET` | For webhooks | Webhook signature verification |
146
+ | `REDIS_URL` | For distributed | Redis connection URL |
147
+
148
+ ## Related Packages
149
+
150
+ | Package | Description |
151
+ |---------|-------------|
152
+ | [@renseiai/agentfactory](https://www.npmjs.com/package/@renseiai/agentfactory) | Core orchestrator |
153
+ | [@renseiai/agentfactory-linear](https://www.npmjs.com/package/@renseiai/agentfactory-linear) | Linear integration |
154
+ | [@renseiai/agentfactory-server](https://www.npmjs.com/package/@renseiai/agentfactory-server) | Redis infrastructure |
155
+ | [@renseiai/agentfactory-cli](https://www.npmjs.com/package/@renseiai/agentfactory-cli) | CLI tools |
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=middleware-edge-safety.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-edge-safety.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/middleware-edge-safety.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ /**
7
+ * Edge Runtime safety tests for the /middleware subpath.
8
+ *
9
+ * Next.js middleware runs in Edge Runtime which cannot load Node.js
10
+ * built-in modules (crypto, ioredis, etc.). The /middleware subpath
11
+ * must NEVER transitively import these modules.
12
+ *
13
+ * These tests inspect the source code of the middleware barrel and
14
+ * its transitive imports to verify no Node.js-only modules are pulled in.
15
+ */
16
+ const FORBIDDEN_NODE_MODULES = [
17
+ 'crypto',
18
+ 'ioredis',
19
+ 'fs',
20
+ 'child_process',
21
+ 'net',
22
+ 'tls',
23
+ 'dns',
24
+ ];
25
+ const FORBIDDEN_PACKAGE_IMPORTS = [
26
+ '@renseiai/agentfactory-server',
27
+ ];
28
+ function readSource(relativePath) {
29
+ return readFileSync(resolve(__dirname, '..', relativePath), 'utf-8');
30
+ }
31
+ describe('middleware Edge Runtime safety', () => {
32
+ it('middleware barrel (index.ts) only imports from factory and types', () => {
33
+ const source = readSource('middleware/index.ts');
34
+ const importLines = source.split('\n').filter(line => line.match(/^(?:export|import)\s/) && line.includes('from'));
35
+ for (const line of importLines) {
36
+ // Allow imports from ./factory.js and ./types.js only
37
+ expect(line).toMatch(/from\s+['"]\.\/(?:factory|types)\.js['"]/);
38
+ }
39
+ });
40
+ it('middleware factory (factory.ts) does not import Node.js modules', () => {
41
+ const source = readSource('middleware/factory.ts');
42
+ for (const mod of FORBIDDEN_NODE_MODULES) {
43
+ expect(source).not.toMatch(new RegExp(`from\\s+['"]${mod}['"]`));
44
+ expect(source).not.toMatch(new RegExp(`require\\s*\\(\\s*['"]${mod}['"]`));
45
+ }
46
+ });
47
+ it('middleware factory (factory.ts) does not import @renseiai/agentfactory-server', () => {
48
+ const source = readSource('middleware/factory.ts');
49
+ for (const pkg of FORBIDDEN_PACKAGE_IMPORTS) {
50
+ expect(source).not.toMatch(new RegExp(`from\\s+['"]${pkg}`));
51
+ expect(source).not.toMatch(new RegExp(`require\\s*\\(\\s*['"]${pkg}`));
52
+ }
53
+ });
54
+ it('middleware types (types.ts) has no runtime imports', () => {
55
+ const source = readSource('middleware/types.ts');
56
+ // types.ts should only have type-level imports (import type) or no imports at all
57
+ const runtimeImports = source.split('\n').filter(line => line.match(/^import\s+(?!type\s)/) && !line.includes('import type'));
58
+ expect(runtimeImports).toEqual([]);
59
+ });
60
+ it('middleware factory only imports from next/server and local types', () => {
61
+ const source = readSource('middleware/factory.ts');
62
+ const importLines = source.split('\n').filter(line => line.match(/^import\s/) && line.includes('from'));
63
+ for (const line of importLines) {
64
+ const fromMatch = line.match(/from\s+['"]([^'"]+)['"]/);
65
+ if (!fromMatch)
66
+ continue;
67
+ const specifier = fromMatch[1];
68
+ // Allow: next/server, ./types.js (relative local imports)
69
+ const isAllowed = specifier === 'next/server' ||
70
+ specifier.startsWith('./');
71
+ expect(isAllowed, `Unexpected import: ${specifier}`).toBe(true);
72
+ }
73
+ });
74
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=poll-project-filter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poll-project-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/poll-project-filter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ /**
3
+ * Tests for project-based work filtering in the poll handler.
4
+ *
5
+ * The poll handler filters work items based on the worker's `projects` field.
6
+ * This test validates the filtering logic independently of Redis/HTTP.
7
+ */
8
+ function filterWorkForProjects(allWork, workerProjects, desiredCount) {
9
+ const hasProjectFilter = workerProjects && workerProjects.length > 0;
10
+ if (hasProjectFilter) {
11
+ // Accept: matching project OR untagged items (backward compat)
12
+ return allWork
13
+ .filter(w => !w.projectName || workerProjects.includes(w.projectName))
14
+ .slice(0, desiredCount);
15
+ }
16
+ return allWork.slice(0, desiredCount);
17
+ }
18
+ function makeWork(overrides = {}) {
19
+ return {
20
+ sessionId: `session-${Math.random().toString(36).slice(2, 8)}`,
21
+ issueId: 'issue-1',
22
+ issueIdentifier: 'TEST-1',
23
+ priority: 3,
24
+ queuedAt: Date.now(),
25
+ ...overrides,
26
+ };
27
+ }
28
+ describe('poll handler project filtering', () => {
29
+ it('worker with projects: ["Social"] receives only Social + untagged work', () => {
30
+ const allWork = [
31
+ makeWork({ projectName: 'Social', issueIdentifier: 'SUP-1' }),
32
+ makeWork({ projectName: 'Agent', issueIdentifier: 'SUP-2' }),
33
+ makeWork({ projectName: undefined, issueIdentifier: 'SUP-3' }),
34
+ makeWork({ projectName: 'Art', issueIdentifier: 'SUP-4' }),
35
+ ];
36
+ const result = filterWorkForProjects(allWork, ['Social'], 5);
37
+ expect(result).toHaveLength(2);
38
+ expect(result[0].issueIdentifier).toBe('SUP-1');
39
+ expect(result[1].issueIdentifier).toBe('SUP-3');
40
+ });
41
+ it('worker with projects: undefined receives all work', () => {
42
+ const allWork = [
43
+ makeWork({ projectName: 'Social' }),
44
+ makeWork({ projectName: 'Agent' }),
45
+ makeWork({ projectName: undefined }),
46
+ ];
47
+ const result = filterWorkForProjects(allWork, undefined, 5);
48
+ expect(result).toHaveLength(3);
49
+ });
50
+ it('worker with projects: ["Social", "Agent"] receives both + untagged', () => {
51
+ const allWork = [
52
+ makeWork({ projectName: 'Social', issueIdentifier: 'SUP-1' }),
53
+ makeWork({ projectName: 'Agent', issueIdentifier: 'SUP-2' }),
54
+ makeWork({ projectName: 'Art', issueIdentifier: 'SUP-3' }),
55
+ makeWork({ projectName: undefined, issueIdentifier: 'SUP-4' }),
56
+ ];
57
+ const result = filterWorkForProjects(allWork, ['Social', 'Agent'], 5);
58
+ expect(result).toHaveLength(3);
59
+ expect(result.map(w => w.issueIdentifier)).toEqual(['SUP-1', 'SUP-2', 'SUP-4']);
60
+ });
61
+ it('empty queue returns empty regardless of filter', () => {
62
+ expect(filterWorkForProjects([], ['Social'], 5)).toEqual([]);
63
+ expect(filterWorkForProjects([], undefined, 5)).toEqual([]);
64
+ });
65
+ it('respects desiredCount limit', () => {
66
+ const allWork = [
67
+ makeWork({ projectName: 'Social' }),
68
+ makeWork({ projectName: 'Social' }),
69
+ makeWork({ projectName: 'Social' }),
70
+ makeWork({ projectName: 'Social' }),
71
+ ];
72
+ const result = filterWorkForProjects(allWork, ['Social'], 2);
73
+ expect(result).toHaveLength(2);
74
+ });
75
+ it('empty projects array is treated as no filter', () => {
76
+ const allWork = [
77
+ makeWork({ projectName: 'Social' }),
78
+ makeWork({ projectName: 'Agent' }),
79
+ ];
80
+ const result = filterWorkForProjects(allWork, [], 5);
81
+ expect(result).toHaveLength(2);
82
+ });
83
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=subpath-exports.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subpath-exports.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/subpath-exports.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ /**
3
+ * Subpath export resolution tests for @renseiai/agentfactory-nextjs.
4
+ *
5
+ * Verifies that the /middleware subpath exports the expected factory
6
+ * function and does NOT transitively import Node.js-only modules
7
+ * (crypto, ioredis) that break Edge Runtime.
8
+ */
9
+ describe('@renseiai/agentfactory-nextjs subpath exports', () => {
10
+ it('exports createAgentFactoryMiddleware from ./middleware', async () => {
11
+ const mod = await import('../middleware/index.js');
12
+ expect(mod.createAgentFactoryMiddleware).toBeDefined();
13
+ expect(typeof mod.createAgentFactoryMiddleware).toBe('function');
14
+ });
15
+ it('exports createAllRoutes from main barrel', async () => {
16
+ const mod = await import('../index.js');
17
+ expect(mod.createAllRoutes).toBeDefined();
18
+ expect(typeof mod.createAllRoutes).toBe('function');
19
+ });
20
+ it('exports createDefaultLinearClientResolver from main barrel', async () => {
21
+ const mod = await import('../index.js');
22
+ expect(mod.createDefaultLinearClientResolver).toBeDefined();
23
+ expect(typeof mod.createDefaultLinearClientResolver).toBe('function');
24
+ });
25
+ it('exports createWebhookOrchestrator from main barrel', async () => {
26
+ const mod = await import('../index.js');
27
+ expect(mod.createWebhookOrchestrator).toBeDefined();
28
+ expect(typeof mod.createWebhookOrchestrator).toBe('function');
29
+ });
30
+ it('exports createOAuthCallbackHandler from main barrel', async () => {
31
+ const mod = await import('../index.js');
32
+ expect(mod.createOAuthCallbackHandler).toBeDefined();
33
+ expect(typeof mod.createOAuthCallbackHandler).toBe('function');
34
+ });
35
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=webhook-project-filter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-project-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/webhook-project-filter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isProjectAllowed } from '../webhook/utils.js';
3
+ /**
4
+ * Tests for server-level project filtering in webhook handlers.
5
+ *
6
+ * The `projects` config field on `WebhookConfig` gates webhook processing
7
+ * so that a server deployed for specific Linear projects only handles
8
+ * webhooks for those projects.
9
+ */
10
+ describe('webhook server-level project filtering', () => {
11
+ it('no projects config (undefined) → all webhooks pass', () => {
12
+ const allowedProjects = []; // empty = no filter
13
+ expect(isProjectAllowed('Social', allowedProjects)).toBe(true);
14
+ expect(isProjectAllowed('Agent', allowedProjects)).toBe(true);
15
+ expect(isProjectAllowed(undefined, allowedProjects)).toBe(true);
16
+ });
17
+ it('empty projects array → all webhooks pass', () => {
18
+ expect(isProjectAllowed('Social', [])).toBe(true);
19
+ expect(isProjectAllowed('Agent', [])).toBe(true);
20
+ expect(isProjectAllowed(undefined, [])).toBe(true);
21
+ });
22
+ it('projects: ["Social"] → only Social issues pass', () => {
23
+ const allowedProjects = ['Social'];
24
+ expect(isProjectAllowed('Social', allowedProjects)).toBe(true);
25
+ expect(isProjectAllowed('Agent', allowedProjects)).toBe(false);
26
+ expect(isProjectAllowed('Art', allowedProjects)).toBe(false);
27
+ });
28
+ it('issue with no project + server has projects → rejected', () => {
29
+ const allowedProjects = ['Social'];
30
+ expect(isProjectAllowed(undefined, allowedProjects)).toBe(false);
31
+ });
32
+ it('non-matching project → rejected', () => {
33
+ const allowedProjects = ['Social', 'Agent'];
34
+ expect(isProjectAllowed('Art', allowedProjects)).toBe(false);
35
+ });
36
+ it('multiple allowed projects → matching ones pass', () => {
37
+ const allowedProjects = ['Social', 'Agent'];
38
+ expect(isProjectAllowed('Social', allowedProjects)).toBe(true);
39
+ expect(isProjectAllowed('Agent', allowedProjects)).toBe(true);
40
+ expect(isProjectAllowed('Art', allowedProjects)).toBe(false);
41
+ });
42
+ it('project name matching is exact (case-sensitive)', () => {
43
+ const allowedProjects = ['Social'];
44
+ expect(isProjectAllowed('social', allowedProjects)).toBe(false);
45
+ expect(isProjectAllowed('SOCIAL', allowedProjects)).toBe(false);
46
+ expect(isProjectAllowed('Social', allowedProjects)).toBe(true);
47
+ });
48
+ });
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Factory — @renseiai/agentfactory-nextjs
3
+ *
4
+ * Wires all handlers together from a single configuration object.
5
+ * Consumers call `createAllRoutes(config)` and get back a nested
6
+ * route tree that maps 1:1 onto Next.js App Router exports.
7
+ *
8
+ * When optional config fields are omitted, sensible defaults from
9
+ * @renseiai/agentfactory-linear are used (defaultGeneratePrompt, etc.).
10
+ */
11
+ import type { RouteHandler, WebhookConfig, CronConfig } from './types.js';
12
+ import { type OAuthConfig } from './handlers/oauth/callback.js';
13
+ export interface AllRoutes {
14
+ workers: {
15
+ register: {
16
+ POST: RouteHandler;
17
+ };
18
+ list: {
19
+ GET: RouteHandler;
20
+ };
21
+ detail: {
22
+ GET: RouteHandler;
23
+ DELETE: RouteHandler;
24
+ };
25
+ heartbeat: {
26
+ POST: RouteHandler;
27
+ };
28
+ poll: {
29
+ GET: RouteHandler;
30
+ };
31
+ };
32
+ sessions: {
33
+ list: {
34
+ GET: RouteHandler;
35
+ };
36
+ detail: {
37
+ GET: RouteHandler;
38
+ };
39
+ claim: {
40
+ POST: RouteHandler;
41
+ };
42
+ status: {
43
+ GET: RouteHandler;
44
+ POST: RouteHandler;
45
+ };
46
+ lockRefresh: {
47
+ POST: RouteHandler;
48
+ };
49
+ prompts: {
50
+ GET: RouteHandler;
51
+ POST: RouteHandler;
52
+ };
53
+ transferOwnership: {
54
+ POST: RouteHandler;
55
+ };
56
+ activity: {
57
+ POST: RouteHandler;
58
+ };
59
+ completion: {
60
+ POST: RouteHandler;
61
+ };
62
+ externalUrls: {
63
+ POST: RouteHandler;
64
+ };
65
+ progress: {
66
+ POST: RouteHandler;
67
+ };
68
+ toolError: {
69
+ POST: RouteHandler;
70
+ };
71
+ };
72
+ public: {
73
+ stats: {
74
+ GET: RouteHandler;
75
+ };
76
+ sessions: {
77
+ GET: RouteHandler;
78
+ };
79
+ sessionDetail: {
80
+ GET: RouteHandler;
81
+ };
82
+ };
83
+ config: {
84
+ GET: RouteHandler;
85
+ };
86
+ cleanup: {
87
+ POST: RouteHandler;
88
+ GET: RouteHandler;
89
+ };
90
+ webhook: {
91
+ POST: RouteHandler;
92
+ GET: RouteHandler;
93
+ };
94
+ oauth: {
95
+ callback: {
96
+ GET: RouteHandler;
97
+ };
98
+ };
99
+ issueTrackerProxy: {
100
+ POST: RouteHandler;
101
+ GET: RouteHandler;
102
+ };
103
+ }
104
+ /**
105
+ * Configuration for createAllRoutes.
106
+ * Extends WebhookConfig & CronConfig with optional OAuth config.
107
+ */
108
+ export interface AllRoutesConfig extends WebhookConfig, CronConfig {
109
+ /** OAuth configuration for the callback handler */
110
+ oauth?: OAuthConfig;
111
+ }
112
+ /**
113
+ * Create all route handlers from a single config object.
114
+ *
115
+ * Optional fields fall back to sensible defaults from @renseiai/agentfactory-linear:
116
+ * - `generatePrompt` → `defaultGeneratePrompt`
117
+ * - `detectWorkTypeFromPrompt` → `defaultDetectWorkTypeFromPrompt`
118
+ * - `getPriority` → `defaultGetPriority`
119
+ * - `buildParentQAContext` → `defaultBuildParentQAContext`
120
+ * - `buildParentAcceptanceContext` → `defaultBuildParentAcceptanceContext`
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * import { createAllRoutes, createDefaultLinearClientResolver } from '@renseiai/agentfactory-nextjs'
125
+ *
126
+ * // Minimal — everything uses defaults
127
+ * const routes = createAllRoutes({
128
+ * linearClient: createDefaultLinearClientResolver(),
129
+ * })
130
+ *
131
+ * // Custom — override specific callbacks
132
+ * const routes = createAllRoutes({
133
+ * linearClient: createDefaultLinearClientResolver(),
134
+ * generatePrompt: myCustomPromptFn,
135
+ * oauth: { clientId: '...', clientSecret: '...' },
136
+ * })
137
+ * ```
138
+ */
139
+ export declare function createAllRoutes(config: AllRoutesConfig): AllRoutes;
140
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAe,aAAa,EAAyB,UAAU,EAAE,MAAM,YAAY,CAAA;AA+C7G,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAK3F,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE;QACP,QAAQ,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAChC,IAAI,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;QAC3B,MAAM,EAAE;YAAE,GAAG,EAAE,YAAY,CAAC;YAAC,MAAM,EAAE,YAAY,CAAA;SAAE,CAAA;QACnD,SAAS,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QACjC,IAAI,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;KAC5B,CAAA;IACD,QAAQ,EAAE;QACR,IAAI,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;QAC3B,MAAM,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;QAC7B,KAAK,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE;YAAE,GAAG,EAAE,YAAY,CAAC;YAAC,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QACjD,WAAW,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QACnC,OAAO,EAAE;YAAE,GAAG,EAAE,YAAY,CAAC;YAAC,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAClD,iBAAiB,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QACzC,QAAQ,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAChC,UAAU,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAClC,YAAY,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QACpC,QAAQ,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;QAChC,SAAS,EAAE;YAAE,IAAI,EAAE,YAAY,CAAA;SAAE,CAAA;KAClC,CAAA;IACD,MAAM,EAAE;QACN,KAAK,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;QAC5B,QAAQ,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;QAC/B,aAAa,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;KACrC,CAAA;IACD,MAAM,EAAE;QAAE,GAAG,EAAE,YAAY,CAAA;KAAE,CAAA;IAC7B,OAAO,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAA;IAClD,OAAO,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAA;IAClD,KAAK,EAAE;QACL,QAAQ,EAAE;YAAE,GAAG,EAAE,YAAY,CAAA;SAAE,CAAA;KAChC,CAAA;IACD,iBAAiB,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,GAAG,EAAE,YAAY,CAAA;KAAE,CAAA;CAC7D;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa,EAAE,UAAU;IAChE,mDAAmD;IACnD,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAyDlE"}