@supaku/agentfactory-nextjs 0.3.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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/factory.d.ts +105 -0
  3. package/dist/src/factory.d.ts.map +1 -0
  4. package/dist/src/factory.js +89 -0
  5. package/dist/src/handlers/cleanup.d.ts +44 -0
  6. package/dist/src/handlers/cleanup.d.ts.map +1 -0
  7. package/dist/src/handlers/cleanup.js +34 -0
  8. package/dist/src/handlers/public/session-detail.d.ts +31 -0
  9. package/dist/src/handlers/public/session-detail.d.ts.map +1 -0
  10. package/dist/src/handlers/public/session-detail.js +91 -0
  11. package/dist/src/handlers/public/sessions-list.d.ts +20 -0
  12. package/dist/src/handlers/public/sessions-list.d.ts.map +1 -0
  13. package/dist/src/handlers/public/sessions-list.js +73 -0
  14. package/dist/src/handlers/public/stats.d.ts +22 -0
  15. package/dist/src/handlers/public/stats.d.ts.map +1 -0
  16. package/dist/src/handlers/public/stats.js +53 -0
  17. package/dist/src/handlers/sessions/activity.d.ts +15 -0
  18. package/dist/src/handlers/sessions/activity.d.ts.map +1 -0
  19. package/dist/src/handlers/sessions/activity.js +77 -0
  20. package/dist/src/handlers/sessions/claim.d.ts +15 -0
  21. package/dist/src/handlers/sessions/claim.d.ts.map +1 -0
  22. package/dist/src/handlers/sessions/claim.js +87 -0
  23. package/dist/src/handlers/sessions/completion.d.ts +16 -0
  24. package/dist/src/handlers/sessions/completion.d.ts.map +1 -0
  25. package/dist/src/handlers/sessions/completion.js +82 -0
  26. package/dist/src/handlers/sessions/external-urls.d.ts +15 -0
  27. package/dist/src/handlers/sessions/external-urls.d.ts.map +1 -0
  28. package/dist/src/handlers/sessions/external-urls.js +59 -0
  29. package/dist/src/handlers/sessions/get.d.ts +19 -0
  30. package/dist/src/handlers/sessions/get.d.ts.map +1 -0
  31. package/dist/src/handlers/sessions/get.js +46 -0
  32. package/dist/src/handlers/sessions/list.d.ts +26 -0
  33. package/dist/src/handlers/sessions/list.d.ts.map +1 -0
  34. package/dist/src/handlers/sessions/list.js +50 -0
  35. package/dist/src/handlers/sessions/lock-refresh.d.ts +14 -0
  36. package/dist/src/handlers/sessions/lock-refresh.d.ts.map +1 -0
  37. package/dist/src/handlers/sessions/lock-refresh.js +38 -0
  38. package/dist/src/handlers/sessions/progress.d.ts +15 -0
  39. package/dist/src/handlers/sessions/progress.d.ts.map +1 -0
  40. package/dist/src/handlers/sessions/progress.js +82 -0
  41. package/dist/src/handlers/sessions/prompts.d.ts +15 -0
  42. package/dist/src/handlers/sessions/prompts.d.ts.map +1 -0
  43. package/dist/src/handlers/sessions/prompts.js +91 -0
  44. package/dist/src/handlers/sessions/status.d.ts +18 -0
  45. package/dist/src/handlers/sessions/status.d.ts.map +1 -0
  46. package/dist/src/handlers/sessions/status.js +131 -0
  47. package/dist/src/handlers/sessions/tool-error.d.ts +15 -0
  48. package/dist/src/handlers/sessions/tool-error.d.ts.map +1 -0
  49. package/dist/src/handlers/sessions/tool-error.js +91 -0
  50. package/dist/src/handlers/sessions/transfer-ownership.d.ts +14 -0
  51. package/dist/src/handlers/sessions/transfer-ownership.d.ts.map +1 -0
  52. package/dist/src/handlers/sessions/transfer-ownership.js +56 -0
  53. package/dist/src/handlers/workers/get-delete.d.ts +15 -0
  54. package/dist/src/handlers/workers/get-delete.d.ts.map +1 -0
  55. package/dist/src/handlers/workers/get-delete.js +58 -0
  56. package/dist/src/handlers/workers/heartbeat.d.ts +14 -0
  57. package/dist/src/handlers/workers/heartbeat.d.ts.map +1 -0
  58. package/dist/src/handlers/workers/heartbeat.js +42 -0
  59. package/dist/src/handlers/workers/list.d.ts +22 -0
  60. package/dist/src/handlers/workers/list.d.ts.map +1 -0
  61. package/dist/src/handlers/workers/list.js +33 -0
  62. package/dist/src/handlers/workers/poll.d.ts +14 -0
  63. package/dist/src/handlers/workers/poll.d.ts.map +1 -0
  64. package/dist/src/handlers/workers/poll.js +78 -0
  65. package/dist/src/handlers/workers/register.d.ts +9 -0
  66. package/dist/src/handlers/workers/register.d.ts.map +1 -0
  67. package/dist/src/handlers/workers/register.js +41 -0
  68. package/dist/src/index.d.ts +39 -0
  69. package/dist/src/index.d.ts.map +1 -0
  70. package/dist/src/index.js +41 -0
  71. package/dist/src/middleware/cron-auth.d.ts +21 -0
  72. package/dist/src/middleware/cron-auth.d.ts.map +1 -0
  73. package/dist/src/middleware/cron-auth.js +46 -0
  74. package/dist/src/middleware/worker-auth.d.ts +25 -0
  75. package/dist/src/middleware/worker-auth.d.ts.map +1 -0
  76. package/dist/src/middleware/worker-auth.js +43 -0
  77. package/dist/src/types.d.ts +62 -0
  78. package/dist/src/types.d.ts.map +1 -0
  79. package/dist/src/types.js +7 -0
  80. package/dist/src/webhook/handlers/issue-updated.d.ts +14 -0
  81. package/dist/src/webhook/handlers/issue-updated.d.ts.map +1 -0
  82. package/dist/src/webhook/handlers/issue-updated.js +462 -0
  83. package/dist/src/webhook/handlers/session-created.d.ts +9 -0
  84. package/dist/src/webhook/handlers/session-created.d.ts.map +1 -0
  85. package/dist/src/webhook/handlers/session-created.js +229 -0
  86. package/dist/src/webhook/handlers/session-prompted.d.ts +9 -0
  87. package/dist/src/webhook/handlers/session-prompted.d.ts.map +1 -0
  88. package/dist/src/webhook/handlers/session-prompted.js +197 -0
  89. package/dist/src/webhook/handlers/session-updated.d.ts +9 -0
  90. package/dist/src/webhook/handlers/session-updated.d.ts.map +1 -0
  91. package/dist/src/webhook/handlers/session-updated.js +29 -0
  92. package/dist/src/webhook/processor.d.ts +22 -0
  93. package/dist/src/webhook/processor.d.ts.map +1 -0
  94. package/dist/src/webhook/processor.js +98 -0
  95. package/dist/src/webhook/signature.d.ts +16 -0
  96. package/dist/src/webhook/signature.d.ts.map +1 -0
  97. package/dist/src/webhook/signature.js +23 -0
  98. package/dist/src/webhook/utils.d.ts +61 -0
  99. package/dist/src/webhook/utils.d.ts.map +1 -0
  100. package/dist/src/webhook/utils.js +159 -0
  101. package/package.json +66 -0
@@ -0,0 +1,82 @@
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 '@supaku/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
+ const emoji = milestone ? MILESTONE_EMOJI[milestone] || '\u2139\ufe0f' : '';
45
+ const formattedMessage = emoji ? `${emoji} ${message}` : message;
46
+ try {
47
+ const linearClient = await config.linearClient.getClient(session.organizationId);
48
+ await linearClient.createAgentActivity({
49
+ agentSessionId: session.linearSessionId,
50
+ content: { type: 'response', body: formattedMessage },
51
+ ephemeral: false,
52
+ });
53
+ log.info('Progress activity posted', {
54
+ sessionId,
55
+ linearSessionId: session.linearSessionId,
56
+ milestone,
57
+ messageLength: message.length,
58
+ });
59
+ return NextResponse.json({
60
+ posted: true,
61
+ milestone,
62
+ });
63
+ }
64
+ catch (linearError) {
65
+ log.error('Failed to post progress activity to Linear', {
66
+ error: linearError,
67
+ sessionId,
68
+ linearSessionId: session.linearSessionId,
69
+ milestone,
70
+ });
71
+ return NextResponse.json({
72
+ posted: false,
73
+ reason: 'Failed to post to Linear',
74
+ });
75
+ }
76
+ }
77
+ catch (error) {
78
+ log.error('Failed to process progress update', { error, sessionId });
79
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process progress update' }, { status: 500 });
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * GET, POST /api/sessions/[id]/prompts
3
+ *
4
+ * Get pending prompts or claim a specific prompt.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createSessionPromptsGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export declare function createSessionPromptsPostHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAYvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,8BAA8B,KAClB,SAAS,WAAW,EAAE,YAAY,WAAW,oCAwDxE;AAED,wBAAgB,+BAA+B,KAClB,SAAS,WAAW,EAAE,YAAY,WAAW,oCAoDzE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * GET, POST /api/sessions/[id]/prompts
3
+ *
4
+ * Get pending prompts or claim a specific prompt.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { getSessionState, getPendingPrompts, popPendingPrompt, claimPendingPrompt, createLogger, } from '@supaku/agentfactory-server';
9
+ const log = createLogger('api:sessions:prompts');
10
+ export function createSessionPromptsGetHandler() {
11
+ return async function GET(request, { params }) {
12
+ const authError = requireWorkerAuth(request);
13
+ if (authError)
14
+ return authError;
15
+ const { id: sessionId } = await params;
16
+ const url = new URL(request.url);
17
+ const shouldPop = url.searchParams.get('pop') === 'true';
18
+ try {
19
+ const session = await getSessionState(sessionId);
20
+ if (!session) {
21
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
22
+ }
23
+ if (shouldPop) {
24
+ const prompt = await popPendingPrompt(sessionId);
25
+ if (prompt) {
26
+ log.info('Prompt popped successfully', {
27
+ sessionId,
28
+ promptId: prompt.id,
29
+ promptLength: prompt.prompt.length,
30
+ });
31
+ }
32
+ return NextResponse.json({
33
+ prompt,
34
+ hasMore: prompt ? true : false,
35
+ });
36
+ }
37
+ const prompts = await getPendingPrompts(sessionId);
38
+ if (prompts.length > 0) {
39
+ log.info('Prompts retrieved', {
40
+ sessionId,
41
+ promptCount: prompts.length,
42
+ promptIds: prompts.map((p) => p.id),
43
+ });
44
+ }
45
+ return NextResponse.json({
46
+ prompts,
47
+ count: prompts.length,
48
+ });
49
+ }
50
+ catch (error) {
51
+ log.error('Failed to get pending prompts', { error, sessionId });
52
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get pending prompts' }, { status: 500 });
53
+ }
54
+ };
55
+ }
56
+ export function createSessionPromptsPostHandler() {
57
+ return async function POST(request, { params }) {
58
+ const authError = requireWorkerAuth(request);
59
+ if (authError)
60
+ return authError;
61
+ const { id: sessionId } = await params;
62
+ try {
63
+ const body = await request.json();
64
+ const { promptId } = body;
65
+ if (!promptId || typeof promptId !== 'string') {
66
+ return NextResponse.json({ error: 'Bad Request', message: 'promptId is required' }, { status: 400 });
67
+ }
68
+ const session = await getSessionState(sessionId);
69
+ if (!session) {
70
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
71
+ }
72
+ const prompt = await claimPendingPrompt(sessionId, promptId);
73
+ if (!prompt) {
74
+ return NextResponse.json({ error: 'Not Found', message: 'Prompt not found or already claimed' }, { status: 404 });
75
+ }
76
+ log.info('Prompt claimed', {
77
+ sessionId,
78
+ promptId,
79
+ promptLength: prompt.prompt.length,
80
+ });
81
+ return NextResponse.json({
82
+ claimed: true,
83
+ prompt,
84
+ });
85
+ }
86
+ catch (error) {
87
+ log.error('Failed to claim prompt', { error, sessionId });
88
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to claim prompt' }, { status: 500 });
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GET, POST /api/sessions/[id]/status
3
+ *
4
+ * Report status update from worker or get current status.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createSessionStatusPostHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export declare function createSessionStatusGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<{
14
+ error: string;
15
+ message: string;
16
+ }> | NextResponse<import("@supaku/agentfactory-server").AgentSessionState>>;
17
+ export {};
18
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkBvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAYD,wBAAgB,8BAA8B,KACjB,SAAS,WAAW,EAAE,YAAY,WAAW,oCA4HzE;AAED,wBAAgB,6BAA6B,KACjB,SAAS,WAAW,EAAE,YAAY,WAAW;;;4EAsBxE"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * GET, POST /api/sessions/[id]/status
3
+ *
4
+ * Report status update from worker or get current status.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { getSessionState, updateSessionStatus, updateClaudeSessionId, startSession, removeWorkerSession, releaseClaim, markAgentWorked, releaseIssueLock, promoteNextPendingWork, createLogger, } from '@supaku/agentfactory-server';
9
+ const log = createLogger('api:sessions:status');
10
+ const VALID_STATUSES = [
11
+ 'running',
12
+ 'finalizing',
13
+ 'completed',
14
+ 'failed',
15
+ 'stopped',
16
+ ];
17
+ const TERMINAL_STATUSES = ['completed', 'failed', 'stopped'];
18
+ export function createSessionStatusPostHandler() {
19
+ return async function POST(request, { params }) {
20
+ const authError = requireWorkerAuth(request);
21
+ if (authError)
22
+ return authError;
23
+ const { id: sessionId } = await params;
24
+ try {
25
+ const body = await request.json();
26
+ const { workerId, status, claudeSessionId, worktreePath, error: errorInfo } = body;
27
+ if (!workerId || typeof workerId !== 'string') {
28
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
29
+ }
30
+ if (!status || !VALID_STATUSES.includes(status)) {
31
+ return NextResponse.json({
32
+ error: 'Bad Request',
33
+ message: `status must be one of: ${VALID_STATUSES.join(', ')}`,
34
+ }, { status: 400 });
35
+ }
36
+ const session = await getSessionState(sessionId);
37
+ if (!session) {
38
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
39
+ }
40
+ if (session.workerId && session.workerId !== workerId) {
41
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
42
+ }
43
+ if (status === 'running') {
44
+ if (worktreePath) {
45
+ await startSession(sessionId, workerId, worktreePath);
46
+ }
47
+ else {
48
+ await updateSessionStatus(sessionId, 'running');
49
+ }
50
+ if (claudeSessionId) {
51
+ await updateClaudeSessionId(sessionId, claudeSessionId);
52
+ }
53
+ }
54
+ else if (status === 'finalizing') {
55
+ await updateSessionStatus(sessionId, 'finalizing');
56
+ }
57
+ else if (TERMINAL_STATUSES.includes(status)) {
58
+ await updateSessionStatus(sessionId, status);
59
+ if (status === 'completed' && session.issueId) {
60
+ try {
61
+ await markAgentWorked(session.issueId, {
62
+ issueIdentifier: session.issueIdentifier || 'unknown',
63
+ sessionId: sessionId,
64
+ });
65
+ log.info('Issue marked as agent-worked', {
66
+ issueId: session.issueId,
67
+ sessionId,
68
+ });
69
+ }
70
+ catch (err) {
71
+ log.error('Failed to mark agent-worked', { sessionId, error: err });
72
+ }
73
+ }
74
+ await releaseClaim(sessionId);
75
+ await removeWorkerSession(workerId, sessionId);
76
+ if (session.issueId) {
77
+ try {
78
+ await releaseIssueLock(session.issueId);
79
+ const promoted = await promoteNextPendingWork(session.issueId);
80
+ if (promoted) {
81
+ log.info('Promoted pending work after lock release', {
82
+ issueId: session.issueId,
83
+ promotedSessionId: promoted.sessionId,
84
+ promotedWorkType: promoted.workType,
85
+ });
86
+ }
87
+ }
88
+ catch (err) {
89
+ log.error('Failed to release issue lock or promote pending work', {
90
+ sessionId,
91
+ issueId: session.issueId,
92
+ error: err,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ log.info('Session status updated', {
98
+ sessionId,
99
+ workerId,
100
+ status,
101
+ hasClaudeSessionId: !!claudeSessionId,
102
+ hasError: !!errorInfo,
103
+ });
104
+ const updatedSession = await getSessionState(sessionId);
105
+ return NextResponse.json({
106
+ updated: true,
107
+ session: updatedSession,
108
+ });
109
+ }
110
+ catch (error) {
111
+ log.error('Failed to update session status', { error, sessionId });
112
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to update session status' }, { status: 500 });
113
+ }
114
+ };
115
+ }
116
+ export function createSessionStatusGetHandler() {
117
+ return async function GET(request, { params }) {
118
+ const { id: sessionId } = await params;
119
+ try {
120
+ const session = await getSessionState(sessionId);
121
+ if (!session) {
122
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
123
+ }
124
+ return NextResponse.json(session);
125
+ }
126
+ catch (error) {
127
+ log.error('Failed to get session status', { error, sessionId });
128
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get session status' }, { status: 500 });
129
+ }
130
+ };
131
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * POST /api/sessions/[id]/tool-error
3
+ *
4
+ * Report a tool error as a Linear issue for tracking.
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 createSessionToolErrorHandler(config: RouteConfig): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=tool-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-error.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/tool-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAIvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAYD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,WAAW,IACpC,SAAS,WAAW,EAAE,YAAY,WAAW,oCA+GzE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * POST /api/sessions/[id]/tool-error
3
+ *
4
+ * Report a tool error as a Linear issue for tracking.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { getSessionState, createLogger } from '@supaku/agentfactory-server';
9
+ import { createAgentSession, ENVIRONMENT_ISSUE_TYPES } from '@supaku/agentfactory-linear';
10
+ const log = createLogger('api:sessions:tool-error');
11
+ export function createSessionToolErrorHandler(config) {
12
+ return async function POST(request, { params }) {
13
+ const authError = requireWorkerAuth(request);
14
+ if (authError)
15
+ return authError;
16
+ const { id: sessionId } = await params;
17
+ try {
18
+ const body = (await request.json());
19
+ const { workerId, toolName, errorMessage, context } = body;
20
+ if (!workerId || typeof workerId !== 'string') {
21
+ return NextResponse.json({ error: 'Bad Request', message: 'workerId is required' }, { status: 400 });
22
+ }
23
+ if (!toolName || typeof toolName !== 'string') {
24
+ return NextResponse.json({ error: 'Bad Request', message: 'toolName is required' }, { status: 400 });
25
+ }
26
+ if (!errorMessage || typeof errorMessage !== 'string') {
27
+ return NextResponse.json({ error: 'Bad Request', message: 'errorMessage is required' }, { status: 400 });
28
+ }
29
+ const session = await getSessionState(sessionId);
30
+ if (!session) {
31
+ return NextResponse.json({ error: 'Not Found', message: 'Session not found' }, { status: 404 });
32
+ }
33
+ if (session.workerId && session.workerId !== workerId) {
34
+ return NextResponse.json({ error: 'Forbidden', message: 'Session is owned by another worker' }, { status: 403 });
35
+ }
36
+ try {
37
+ const linearClient = await config.linearClient.getClient(session.organizationId);
38
+ const agentSession = createAgentSession({
39
+ client: linearClient.linearClient,
40
+ issueId: session.issueId,
41
+ sessionId,
42
+ autoTransition: false,
43
+ });
44
+ const issue = await agentSession.reportEnvironmentIssue(`Tool error: ${toolName}`, `The agent encountered an error while using the **${toolName}** tool.\n\n**Error:**\n\`\`\`\n${errorMessage}\n\`\`\``, {
45
+ issueType: ENVIRONMENT_ISSUE_TYPES.TOOL,
46
+ sourceIssueId: context?.issueIdentifier ?? session.issueId,
47
+ additionalContext: {
48
+ toolName,
49
+ sessionId,
50
+ workerId,
51
+ ...context?.additionalContext,
52
+ },
53
+ });
54
+ if (issue) {
55
+ log.info('Tool error reported to Linear', {
56
+ sessionId,
57
+ toolName,
58
+ issueIdentifier: issue.identifier,
59
+ });
60
+ return NextResponse.json({
61
+ created: true,
62
+ issue: {
63
+ id: issue.id,
64
+ identifier: issue.identifier,
65
+ url: issue.url,
66
+ },
67
+ });
68
+ }
69
+ return NextResponse.json({
70
+ created: false,
71
+ reason: 'Failed to create issue in Linear',
72
+ });
73
+ }
74
+ catch (linearError) {
75
+ log.error('Failed to report tool error to Linear', {
76
+ error: linearError,
77
+ sessionId,
78
+ toolName,
79
+ });
80
+ return NextResponse.json({
81
+ created: false,
82
+ reason: 'Failed to report to Linear',
83
+ });
84
+ }
85
+ }
86
+ catch (error) {
87
+ log.error('Failed to process tool error report', { error, sessionId });
88
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to process tool error report' }, { status: 500 });
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * POST /api/sessions/[id]/transfer-ownership
3
+ *
4
+ * Transfer session ownership to a new worker.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createSessionTransferOwnershipHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export {};
14
+ //# sourceMappingURL=transfer-ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-ownership.d.ts","sourceRoot":"","sources":["../../../../src/handlers/sessions/transfer-ownership.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAYvD,UAAU,WAAW;IACnB,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChC;AAED,wBAAgB,qCAAqC,KACxB,SAAS,WAAW,EAAE,YAAY,WAAW,oCA8DzE"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * POST /api/sessions/[id]/transfer-ownership
3
+ *
4
+ * Transfer session ownership to a new worker.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { transferSessionOwnership, getSessionState, removeWorkerSession, addWorkerSession, createLogger, } from '@supaku/agentfactory-server';
9
+ const log = createLogger('api:sessions:transfer-ownership');
10
+ export function createSessionTransferOwnershipHandler() {
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 { newWorkerId, oldWorkerId } = body;
19
+ if (!newWorkerId || typeof newWorkerId !== 'string') {
20
+ return NextResponse.json({ error: 'Bad Request', message: 'newWorkerId is required' }, { status: 400 });
21
+ }
22
+ if (!oldWorkerId || typeof oldWorkerId !== 'string') {
23
+ return NextResponse.json({ error: 'Bad Request', message: 'oldWorkerId is required' }, { status: 400 });
24
+ }
25
+ const result = await transferSessionOwnership(sessionId, newWorkerId, oldWorkerId);
26
+ if (!result.transferred) {
27
+ log.warn('Session ownership transfer failed', {
28
+ sessionId,
29
+ newWorkerId,
30
+ oldWorkerId,
31
+ reason: result.reason,
32
+ });
33
+ return NextResponse.json({
34
+ transferred: false,
35
+ reason: result.reason,
36
+ });
37
+ }
38
+ await removeWorkerSession(oldWorkerId, sessionId);
39
+ await addWorkerSession(newWorkerId, sessionId);
40
+ log.info('Session ownership transferred', {
41
+ sessionId,
42
+ oldWorkerId,
43
+ newWorkerId,
44
+ });
45
+ const session = await getSessionState(sessionId);
46
+ return NextResponse.json({
47
+ transferred: true,
48
+ session,
49
+ });
50
+ }
51
+ catch (error) {
52
+ log.error('Failed to transfer session ownership', { error, sessionId });
53
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to transfer session ownership' }, { status: 500 });
54
+ }
55
+ };
56
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * GET, DELETE /api/workers/[id]
3
+ *
4
+ * Get worker details or deregister a worker.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createWorkerDeleteHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export declare function createWorkerGetHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
14
+ export {};
15
+ //# sourceMappingURL=get-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-delete.d.ts","sourceRoot":"","sources":["../../../../src/handlers/workers/get-delete.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,yBAAyB,KACV,SAAS,WAAW,EAAE,YAAY,WAAW,oCAyC3E;AAED,wBAAgB,sBAAsB,KACV,SAAS,WAAW,EAAE,YAAY,WAAW,oCAyBxE"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * GET, DELETE /api/workers/[id]
3
+ *
4
+ * Get worker details or deregister a worker.
5
+ */
6
+ import { NextResponse } from 'next/server';
7
+ import { requireWorkerAuth } from '../../middleware/worker-auth.js';
8
+ import { deregisterWorker, getWorker, createLogger } from '@supaku/agentfactory-server';
9
+ const log = createLogger('api:workers:detail');
10
+ export function createWorkerDeleteHandler() {
11
+ return async function DELETE(request, { params }) {
12
+ const authError = requireWorkerAuth(request);
13
+ if (authError)
14
+ return authError;
15
+ const { id: workerId } = await params;
16
+ try {
17
+ const worker = await getWorker(workerId);
18
+ if (!worker) {
19
+ return NextResponse.json({ error: 'Not Found', message: 'Worker not found' }, { status: 404 });
20
+ }
21
+ const result = await deregisterWorker(workerId);
22
+ if (!result.deregistered) {
23
+ return NextResponse.json({ error: 'Service Unavailable', message: 'Failed to deregister worker' }, { status: 503 });
24
+ }
25
+ log.info('Worker deregistered via API', {
26
+ workerId,
27
+ unclaimedSessions: result.unclaimedSessions.length,
28
+ });
29
+ return NextResponse.json({
30
+ deregistered: true,
31
+ unclaimedSessions: result.unclaimedSessions,
32
+ });
33
+ }
34
+ catch (error) {
35
+ log.error('Failed to deregister worker', { error, workerId });
36
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to deregister worker' }, { status: 500 });
37
+ }
38
+ };
39
+ }
40
+ export function createWorkerGetHandler() {
41
+ return async function GET(request, { params }) {
42
+ const authError = requireWorkerAuth(request);
43
+ if (authError)
44
+ return authError;
45
+ const { id: workerId } = await params;
46
+ try {
47
+ const worker = await getWorker(workerId);
48
+ if (!worker) {
49
+ return NextResponse.json({ error: 'Not Found', message: 'Worker not found' }, { status: 404 });
50
+ }
51
+ return NextResponse.json(worker);
52
+ }
53
+ catch (error) {
54
+ log.error('Failed to get worker', { error, workerId });
55
+ return NextResponse.json({ error: 'Internal Server Error', message: 'Failed to get worker' }, { status: 500 });
56
+ }
57
+ };
58
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * POST /api/workers/[id]/heartbeat
3
+ *
4
+ * Send a heartbeat from a worker to keep registration active.
5
+ */
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ interface RouteParams {
8
+ params: Promise<{
9
+ id: string;
10
+ }>;
11
+ }
12
+ export declare function createWorkerHeartbeatHandler(): (request: NextRequest, { params }: RouteParams) => Promise<NextResponse<unknown>>;
13
+ export {};
14
+ //# sourceMappingURL=heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../../../src/handlers/workers/heartbeat.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,4BAA4B,KACf,SAAS,WAAW,EAAE,YAAY,WAAW,oCAiDzE"}