@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,139 @@
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 { NextResponse } from 'next/server';
8
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
9
+ import { claimWork, requeueWork, releaseClaim, claimSession, getSessionState, addWorkerSession, getWorker, createLogger, } from '@renseiai/agentfactory-server';
10
+ const log = createLogger('api:sessions:claim');
11
+ export function createSessionClaimHandler() {
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 } = body;
20
+ if (!workerId || typeof workerId !== 'string') {
21
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
22
+ }
23
+ const work = await claimWork(sessionId, workerId);
24
+ if (!work) {
25
+ log.debug('Failed to claim work from queue', { sessionId, workerId });
26
+ return NextResponse.json({
27
+ claimed: false,
28
+ reason: 'Work item not available or already claimed',
29
+ });
30
+ }
31
+ // Validate project routing: reject if the worker's project list
32
+ // doesn't include this work item's project. Prevents cross-repo
33
+ // execution when work is claimed by the wrong worker.
34
+ if (work.projectName) {
35
+ const worker = await getWorker(workerId);
36
+ if (worker?.projects && worker.projects.length > 0) {
37
+ if (!worker.projects.includes(work.projectName)) {
38
+ log.error('Project mismatch on claim — requeuing', {
39
+ sessionId,
40
+ workerId,
41
+ workProject: work.projectName,
42
+ workerProjects: worker.projects,
43
+ });
44
+ await requeueWork(work);
45
+ return NextResponse.json({
46
+ claimed: false,
47
+ reason: `Worker not authorized for project ${work.projectName}`,
48
+ });
49
+ }
50
+ }
51
+ }
52
+ let claimSucceeded = false;
53
+ try {
54
+ const claimed = await claimSession(sessionId, workerId);
55
+ if (!claimed) {
56
+ const sessionState = await getSessionState(sessionId);
57
+ if (!sessionState) {
58
+ log.warn('Session state expired, dropping orphaned work item', {
59
+ sessionId,
60
+ workerId,
61
+ issueIdentifier: work.issueIdentifier,
62
+ });
63
+ await releaseClaim(sessionId);
64
+ return NextResponse.json({
65
+ claimed: false,
66
+ reason: 'Session state expired, work item dropped',
67
+ });
68
+ }
69
+ if (sessionState.status !== 'pending') {
70
+ log.warn('Session not in pending status, dropping work item', {
71
+ sessionId,
72
+ workerId,
73
+ issueIdentifier: work.issueIdentifier,
74
+ sessionStatus: sessionState.status,
75
+ });
76
+ await releaseClaim(sessionId);
77
+ return NextResponse.json({
78
+ claimed: false,
79
+ reason: `Session in ${sessionState.status} status, work item dropped`,
80
+ });
81
+ }
82
+ log.warn('Transient failure updating session state, re-queuing', {
83
+ sessionId,
84
+ workerId,
85
+ });
86
+ await requeueWork(work);
87
+ return NextResponse.json({
88
+ claimed: false,
89
+ reason: 'Session state update failed, work re-queued',
90
+ });
91
+ }
92
+ claimSucceeded = true;
93
+ await addWorkerSession(workerId, sessionId);
94
+ const session = await getSessionState(sessionId);
95
+ log.info('Session claimed', {
96
+ sessionId,
97
+ workerId,
98
+ issueIdentifier: work.issueIdentifier,
99
+ });
100
+ return NextResponse.json({
101
+ claimed: true,
102
+ session,
103
+ work,
104
+ });
105
+ }
106
+ catch (innerError) {
107
+ // claimWork succeeded (work removed from queue, claim key set) but
108
+ // something after it threw. Release claim and requeue the work to
109
+ // prevent the item from being stuck.
110
+ log.error('Error after claimWork succeeded, requeuing', {
111
+ error: innerError,
112
+ sessionId,
113
+ workerId,
114
+ claimSucceeded,
115
+ });
116
+ try {
117
+ await requeueWork(work);
118
+ }
119
+ catch (requeueError) {
120
+ // Last resort: at least release the claim key so the work
121
+ // (if it somehow remains in the queue) isn't permanently blocked
122
+ log.error('Failed to requeue work after claim error', {
123
+ requeueError,
124
+ sessionId,
125
+ });
126
+ try {
127
+ await releaseClaim(sessionId);
128
+ }
129
+ catch { /* best effort */ }
130
+ }
131
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to claim session' }, { status: 500 });
132
+ }
133
+ }
134
+ catch (error) {
135
+ log.error('Failed to claim session', { error, sessionId });
136
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to claim session' }, { status: 500 });
137
+ }
138
+ };
139
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * POST /api/sessions/[id]/completion
3
+ *
4
+ * Post agent completion comment to Linear.
5
+ * Uses multi-comment splitting for long messages.
6
+ */
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import type { RouteConfig } from '../../types.js';
9
+ interface RouteParams {
10
+ params: Promise<{
11
+ id: string;
12
+ }>;
13
+ }
14
+ export declare function createSessionCompletionHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
15
+ export {};
16
+ //# sourceMappingURL=completion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/completion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;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;AAWD,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,WAAW,IACrC,SAAS,WAAW,EAAE,YAAY,WAAW,oCA6FzE"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * POST /api/sessions/[id]/completion
3
+ *
4
+ * Post agent completion comment to Linear.
5
+ * Uses multi-comment splitting for long messages.
6
+ */
7
+ import { NextResponse } from 'next/server';
8
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
9
+ import { getSessionState, createLogger } from '@renseiai/agentfactory-server';
10
+ import { buildCompletionComments } from '@renseiai/agentfactory-linear';
11
+ const log = createLogger('api:sessions:completion');
12
+ export function createSessionCompletionHandler(config) {
13
+ return async function POST(request, { params }) {
14
+ const authError = requireWorkerAuth(request);
15
+ if (authError)
16
+ return authError;
17
+ const { id: sessionId } = await params;
18
+ try {
19
+ const body = (await request.json());
20
+ const { workerId, summary, planItems = [] } = body;
21
+ if (!summary || typeof summary !== 'string') {
22
+ return NextResponse.json({ error: 'Bad Request', message: 'summary is required' }, { status: 400 });
23
+ }
24
+ const session = await getSessionState(sessionId);
25
+ if (!session) {
26
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
27
+ }
28
+ if (workerId && session.workerId && session.workerId !== workerId) {
29
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
30
+ }
31
+ const comments = buildCompletionComments(summary, planItems, sessionId);
32
+ log.info('Posting completion comments', {
33
+ sessionId,
34
+ issueId: session.issueId,
35
+ parts: comments.length,
36
+ summaryLength: summary.length,
37
+ });
38
+ const linearClient = await config.linearClient.getClient(session.organizationId);
39
+ let postedCount = 0;
40
+ const errors = [];
41
+ for (const chunk of comments) {
42
+ try {
43
+ await linearClient.createComment(session.issueId, chunk.body);
44
+ postedCount++;
45
+ log.debug(`Posted completion comment part ${chunk.partNumber}/${chunk.totalParts}`, {
46
+ sessionId,
47
+ issueId: session.issueId,
48
+ });
49
+ if (chunk.partNumber < chunk.totalParts) {
50
+ await new Promise((resolve) => setTimeout(resolve, 100));
51
+ }
52
+ }
53
+ catch (error) {
54
+ const errorMsg = error instanceof Error ? error.message : String(error);
55
+ errors.push(`Part ${chunk.partNumber}: ${errorMsg}`);
56
+ log.error(`Failed to post completion comment part ${chunk.partNumber}`, {
57
+ error,
58
+ sessionId,
59
+ issueId: session.issueId,
60
+ });
61
+ }
62
+ }
63
+ if (postedCount === 0) {
64
+ return NextResponse.json({
65
+ error: 'Internal Server Error',
66
+ message: 'Failed to post any completion comments',
67
+ errors,
68
+ }, { status: 500 });
69
+ }
70
+ return NextResponse.json({
71
+ posted: true,
72
+ partsPosted: postedCount,
73
+ totalParts: comments.length,
74
+ errors: errors.length > 0 ? errors : undefined,
75
+ });
76
+ }
77
+ catch (error) {
78
+ log.error('Failed to process completion', { error, sessionId });
79
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process completion' }, { status: 500 });
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/sessions/[id]/external-urls
3
+ *
4
+ * Update external URLs for an agent session.
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 createSessionExternalUrlsHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=external-urls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-urls.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/external-urls.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGvD,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;AAOD,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,WAAW,IACvC,SAAS,WAAW,EAAE,YAAY,WAAW,oCAoFzE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * POST /api/sessions/[id]/external-urls
3
+ *
4
+ * Update external URLs for an agent session.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { getSessionState, createLogger } from '@renseiai/agentfactory-server';
9
+ const log = createLogger('api:sessions:external-urls');
10
+ export function createSessionExternalUrlsHandler(config) {
11
+ return async function POST(request, { params }) {
12
+ const authError = requireWorkerAuth(request);
13
+ if (authError)
14
+ return authError;
15
+ const { id: sessionId } = await params;
16
+ try {
17
+ const body = await request.json();
18
+ const { externalUrls, workspaceId } = body;
19
+ if (!externalUrls || !Array.isArray(externalUrls)) {
20
+ return NextResponse.json({ error: 'Bad Request', message: 'externalUrls array is required' }, { status: 400 });
21
+ }
22
+ for (const extUrl of externalUrls) {
23
+ if (!extUrl.label || !extUrl.url) {
24
+ return NextResponse.json({ error: 'Bad Request', message: 'Each external URL must have label and url' }, { status: 400 });
25
+ }
26
+ }
27
+ const session = await getSessionState(sessionId);
28
+ if (!session) {
29
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
30
+ }
31
+ // Skip Linear forwarding for governor-generated fake session IDs.
32
+ if (sessionId.startsWith('governor-')) {
33
+ log.debug('Skipping Linear external URLs update for governor-generated session', {
34
+ sessionId,
35
+ });
36
+ return NextResponse.json({
37
+ updated: false,
38
+ reason: 'Governor-generated session — no Linear agent session exists',
39
+ });
40
+ }
41
+ const effectiveWorkspaceId = workspaceId || session.organizationId;
42
+ if (!effectiveWorkspaceId) {
43
+ log.warn('No workspace ID available, falling back to default client', {
44
+ sessionId,
45
+ hasWorkspaceIdInRequest: !!workspaceId,
46
+ hasOrganizationIdInSession: !!session.organizationId,
47
+ });
48
+ }
49
+ const linearClient = await config.linearClient.getClient(effectiveWorkspaceId);
50
+ await linearClient.updateAgentSession({
51
+ sessionId,
52
+ externalUrls,
53
+ });
54
+ log.info('External URLs updated', {
55
+ sessionId,
56
+ urlCount: externalUrls.length,
57
+ labels: externalUrls.map((u) => u.label),
58
+ });
59
+ return NextResponse.json({
60
+ updated: true,
61
+ externalUrls,
62
+ });
63
+ }
64
+ catch (error) {
65
+ const errorMessage = error instanceof Error ? error.message : String(error);
66
+ log.error('Failed to update external URLs', { error: errorMessage, sessionId });
67
+ return NextResponse.json({ error: 'Internal Server Error', message: `Failed to update external URLs: ${errorMessage}` }, { status: 500 });
68
+ }
69
+ };
70
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * GET /api/sessions/[id]
3
+ * Returns a single agent session by ID
4
+ */
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+ import type { AgentSessionResponse } from './list.js';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createSessionGetHandler(): (_request: NextRequest, { params }: RouteParams) => Promise<NextResponse<{
13
+ error: string;
14
+ message: string;
15
+ }> | NextResponse<{
16
+ session: AgentSessionResponse;
17
+ }>>;
18
+ export {};
19
+ //# sourceMappingURL=get.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/get.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAIrD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAyBD,wBAAgB,uBAAuB,KACX,UAAU,WAAW,EAAE,YAAY,WAAW;;;;;IAwBzE"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * GET /api/sessions/[id]
3
+ * Returns a single agent session by ID
4
+ */
5
+ import { NextResponse } from 'next/server';
6
+ import { getSessionState, createLogger } from '@renseiai/agentfactory-server';
7
+ const log = createLogger('api:sessions:[id]');
8
+ function toResponse(session) {
9
+ return {
10
+ id: session.linearSessionId,
11
+ linearSessionId: session.linearSessionId,
12
+ issueId: session.issueId,
13
+ identifier: session.issueIdentifier || session.issueId.slice(0, 8),
14
+ providerSessionId: session.providerSessionId || undefined,
15
+ provider: session.provider || undefined,
16
+ status: session.status,
17
+ createdAt: new Date(session.createdAt * 1000).toISOString(),
18
+ updatedAt: new Date(session.updatedAt * 1000).toISOString(),
19
+ worktreePath: session.worktreePath,
20
+ workerId: session.workerId || undefined,
21
+ queuedAt: session.queuedAt
22
+ ? new Date(session.queuedAt).toISOString()
23
+ : undefined,
24
+ claimedAt: session.claimedAt
25
+ ? new Date(session.claimedAt * 1000).toISOString()
26
+ : undefined,
27
+ agentId: session.agentId || undefined,
28
+ };
29
+ }
30
+ export function createSessionGetHandler() {
31
+ return async function GET(_request, { params }) {
32
+ const { id: sessionId } = await params;
33
+ try {
34
+ const session = await getSessionState(sessionId);
35
+ if (!session) {
36
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
37
+ }
38
+ return NextResponse.json({
39
+ session: toResponse(session),
40
+ });
41
+ }
42
+ catch (error) {
43
+ log.error('Failed to fetch session', { error, sessionId });
44
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to fetch session' }, { status: 500 });
45
+ }
46
+ };
47
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * GET /api/sessions
3
+ * Returns list of all agent sessions from Redis
4
+ */
5
+ import { NextResponse } from 'next/server';
6
+ export interface AgentSessionResponse {
7
+ id: string;
8
+ linearSessionId: string;
9
+ issueId: string;
10
+ identifier: string;
11
+ providerSessionId?: string;
12
+ provider?: string;
13
+ status: 'pending' | 'claimed' | 'running' | 'finalizing' | 'completed' | 'failed' | 'stopped';
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ worktreePath: string;
17
+ workerId?: string;
18
+ queuedAt?: string;
19
+ claimedAt?: string;
20
+ agentId?: string;
21
+ }
22
+ export declare function createSessionListHandler(): () => Promise<NextResponse<{
23
+ sessions: AgentSessionResponse[];
24
+ count: number;
25
+ timestamp: string;
26
+ }>>;
27
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/list.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC7F,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAyBD,wBAAgB,wBAAwB;;;;IAsBvC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * GET /api/sessions
3
+ * Returns list of all agent sessions from Redis
4
+ */
5
+ import { NextResponse } from 'next/server';
6
+ import { getAllSessions, createLogger } from '@renseiai/agentfactory-server';
7
+ const log = createLogger('api/sessions');
8
+ function toResponse(session) {
9
+ return {
10
+ id: session.linearSessionId,
11
+ linearSessionId: session.linearSessionId,
12
+ issueId: session.issueId,
13
+ identifier: session.issueIdentifier || session.issueId.slice(0, 8),
14
+ providerSessionId: session.providerSessionId || undefined,
15
+ provider: session.provider || undefined,
16
+ status: session.status,
17
+ createdAt: new Date(session.createdAt * 1000).toISOString(),
18
+ updatedAt: new Date(session.updatedAt * 1000).toISOString(),
19
+ worktreePath: session.worktreePath,
20
+ workerId: session.workerId || undefined,
21
+ queuedAt: session.queuedAt
22
+ ? new Date(session.queuedAt).toISOString()
23
+ : undefined,
24
+ claimedAt: session.claimedAt
25
+ ? new Date(session.claimedAt * 1000).toISOString()
26
+ : undefined,
27
+ agentId: session.agentId || undefined,
28
+ };
29
+ }
30
+ export function createSessionListHandler() {
31
+ return async function GET() {
32
+ try {
33
+ const allSessions = await getAllSessions();
34
+ const sessions = allSessions.map(toResponse);
35
+ return NextResponse.json({
36
+ sessions,
37
+ count: sessions.length,
38
+ timestamp: new Date().toISOString(),
39
+ });
40
+ }
41
+ catch (error) {
42
+ log.error('Failed to fetch sessions', { error });
43
+ return NextResponse.json({
44
+ sessions: [],
45
+ count: 0,
46
+ timestamp: new Date().toISOString(),
47
+ error: 'Failed to fetch sessions from Redis',
48
+ });
49
+ }
50
+ };
51
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * POST /api/sessions/[id]/lock-refresh
3
+ *
4
+ * Refresh the issue lock TTL for an active session.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createSessionLockRefreshHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export {};
14
+ //# sourceMappingURL=lock-refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-refresh.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/lock-refresh.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAMvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,+BAA+B,KAClB,SAAS,WAAW,EAAE,YAAY,WAAW,oCA6CzE"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * POST /api/sessions/[id]/lock-refresh
3
+ *
4
+ * Refresh the issue lock TTL for an active session.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { getSessionState, refreshIssueLockTTL, createLogger } from '@renseiai/agentfactory-server';
9
+ const log = createLogger('api:sessions:lock-refresh');
10
+ export function createSessionLockRefreshHandler() {
11
+ return async function POST(request, { params }) {
12
+ const authError = requireWorkerAuth(request);
13
+ if (authError)
14
+ return authError;
15
+ const { id: sessionId } = await params;
16
+ try {
17
+ const body = await request.json();
18
+ const { workerId, issueId } = body;
19
+ if (!workerId || !issueId) {
20
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId and issueId are required' }, { status: 400 });
21
+ }
22
+ const session = await getSessionState(sessionId);
23
+ if (!session) {
24
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
25
+ }
26
+ if (session.workerId && session.workerId !== workerId) {
27
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
28
+ }
29
+ const refreshed = await refreshIssueLockTTL(issueId);
30
+ log.debug('Lock TTL refreshed', { sessionId, issueId, refreshed });
31
+ return NextResponse.json({ refreshed });
32
+ }
33
+ catch (error) {
34
+ log.error('Failed to refresh lock TTL', { error, sessionId });
35
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to refresh lock TTL' }, { status: 500 });
36
+ }
37
+ };
38
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/sessions/[id]/progress
3
+ *
4
+ * Post a progress update as an agent activity 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 createSessionProgressHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/progress.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGvD,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;AAeD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,WAAW,IACnC,SAAS,WAAW,EAAE,YAAY,WAAW,oCAmGzE"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * POST /api/sessions/[id]/progress
3
+ *
4
+ * Post a progress update as an agent activity 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
+ const log = createLogger('api:sessions:progress');
10
+ const MILESTONE_EMOJI = {
11
+ claimed: '\u{1f527}',
12
+ worktree: '\u{1f4c2}',
13
+ started: '\u{1f916}',
14
+ running: '\u23f3',
15
+ tests: '\u{1f9ea}',
16
+ pr: '\u{1f500}',
17
+ completed: '\u2705',
18
+ failed: '\u274c',
19
+ stopped: '\u{1f6d1}',
20
+ resumed: '\u{1f504}',
21
+ };
22
+ export function createSessionProgressHandler(config) {
23
+ return async function POST(request, { params }) {
24
+ const authError = requireWorkerAuth(request);
25
+ if (authError)
26
+ return authError;
27
+ const { id: sessionId } = await params;
28
+ try {
29
+ const body = await request.json();
30
+ const { workerId, milestone, message } = body;
31
+ if (!workerId || typeof workerId !== 'string') {
32
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
33
+ }
34
+ if (!message || typeof message !== 'string') {
35
+ return NextResponse.json({ error: 'Bad Request', message: 'message is required' }, { status: 400 });
36
+ }
37
+ const session = await getSessionState(sessionId);
38
+ if (!session) {
39
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
40
+ }
41
+ if (session.workerId && session.workerId !== workerId) {
42
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
43
+ }
44
+ // Skip Linear forwarding for governor-generated fake session IDs.
45
+ if (sessionId.startsWith('governor-')) {
46
+ log.debug('Skipping Linear progress post for governor-generated session', {
47
+ sessionId,
48
+ milestone,
49
+ });
50
+ return NextResponse.json({
51
+ posted: false,
52
+ reason: 'Governor-generated session — no Linear agent session exists',
53
+ });
54
+ }
55
+ const emoji = milestone ? MILESTONE_EMOJI[milestone] || '\u2139\ufe0f' : '';
56
+ const formattedMessage = emoji ? `${emoji} ${message}` : message;
57
+ try {
58
+ const linearClient = await config.linearClient.getClient(session.organizationId);
59
+ await linearClient.createAgentActivity({
60
+ agentSessionId: session.linearSessionId,
61
+ content: { type: 'response', body: formattedMessage },
62
+ ephemeral: false,
63
+ });
64
+ log.info('Progress activity posted', {
65
+ sessionId,
66
+ linearSessionId: session.linearSessionId,
67
+ milestone,
68
+ messageLength: message.length,
69
+ });
70
+ return NextResponse.json({
71
+ posted: true,
72
+ milestone,
73
+ });
74
+ }
75
+ catch (linearError) {
76
+ const errorMessage = linearError instanceof Error ? linearError.message : String(linearError);
77
+ log.error('Failed to post progress activity to Linear', {
78
+ error: errorMessage,
79
+ sessionId,
80
+ linearSessionId: session.linearSessionId,
81
+ milestone,
82
+ });
83
+ return NextResponse.json({
84
+ posted: false,
85
+ reason: `Failed to post to Linear: ${errorMessage}`,
86
+ });
87
+ }
88
+ }
89
+ catch (error) {
90
+ log.error('Failed to process progress update', { error, sessionId });
91
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process progress update' }, { status: 500 });
92
+ }
93
+ };
94
+ }