@sudocode-ai/local-server 0.1.0 → 0.1.1
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.
- package/dist/cli.js +6 -104
- package/dist/cli.js.map +1 -7
- package/dist/execution/engine/engine.js +10 -0
- package/dist/execution/engine/engine.js.map +1 -0
- package/dist/execution/engine/simple-engine.js +611 -0
- package/dist/execution/engine/simple-engine.js.map +1 -0
- package/dist/execution/engine/types.js +10 -0
- package/dist/execution/engine/types.js.map +1 -0
- package/dist/execution/output/ag-ui-adapter.js +438 -0
- package/dist/execution/output/ag-ui-adapter.js.map +1 -0
- package/dist/execution/output/ag-ui-integration.js +96 -0
- package/dist/execution/output/ag-ui-integration.js.map +1 -0
- package/dist/execution/output/claude-code-output-processor.js +769 -0
- package/dist/execution/output/claude-code-output-processor.js.map +1 -0
- package/dist/execution/output/index.js +15 -0
- package/dist/execution/output/index.js.map +1 -0
- package/dist/execution/output/types.js +22 -0
- package/dist/execution/output/types.js.map +1 -0
- package/dist/execution/process/builders/claude.js +59 -0
- package/dist/execution/process/builders/claude.js.map +1 -0
- package/dist/execution/process/index.js +15 -0
- package/dist/execution/process/index.js.map +1 -0
- package/dist/execution/process/manager.js +10 -0
- package/dist/execution/process/manager.js.map +1 -0
- package/dist/execution/process/simple-manager.js +336 -0
- package/dist/execution/process/simple-manager.js.map +1 -0
- package/dist/execution/process/types.js +10 -0
- package/dist/execution/process/types.js.map +1 -0
- package/dist/execution/process/utils.js +97 -0
- package/dist/execution/process/utils.js.map +1 -0
- package/dist/execution/resilience/circuit-breaker.js +291 -0
- package/dist/execution/resilience/circuit-breaker.js.map +1 -0
- package/dist/execution/resilience/executor.js +10 -0
- package/dist/execution/resilience/executor.js.map +1 -0
- package/dist/execution/resilience/index.js +15 -0
- package/dist/execution/resilience/index.js.map +1 -0
- package/dist/execution/resilience/resilient-executor.js +261 -0
- package/dist/execution/resilience/resilient-executor.js.map +1 -0
- package/dist/execution/resilience/retry.js +234 -0
- package/dist/execution/resilience/retry.js.map +1 -0
- package/dist/execution/resilience/types.js +30 -0
- package/dist/execution/resilience/types.js.map +1 -0
- package/dist/execution/transport/event-buffer.js +208 -0
- package/dist/execution/transport/event-buffer.js.map +1 -0
- package/dist/execution/transport/index.js +10 -0
- package/dist/execution/transport/index.js.map +1 -0
- package/dist/execution/transport/sse-transport.js +282 -0
- package/dist/execution/transport/sse-transport.js.map +1 -0
- package/dist/execution/transport/transport-manager.js +231 -0
- package/dist/execution/transport/transport-manager.js.map +1 -0
- package/dist/execution/workflow/index.js +13 -0
- package/dist/execution/workflow/index.js.map +1 -0
- package/dist/execution/workflow/linear-orchestrator.js +683 -0
- package/dist/execution/workflow/linear-orchestrator.js.map +1 -0
- package/dist/execution/workflow/memory-storage.js +68 -0
- package/dist/execution/workflow/memory-storage.js.map +1 -0
- package/dist/execution/workflow/orchestrator.js +9 -0
- package/dist/execution/workflow/orchestrator.js.map +1 -0
- package/dist/execution/workflow/types.js +9 -0
- package/dist/execution/workflow/types.js.map +1 -0
- package/dist/execution/workflow/utils.js +152 -0
- package/dist/execution/workflow/utils.js.map +1 -0
- package/dist/execution/worktree/config.js +280 -0
- package/dist/execution/worktree/config.js.map +1 -0
- package/dist/execution/worktree/git-cli.js +189 -0
- package/dist/execution/worktree/git-cli.js.map +1 -0
- package/dist/execution/worktree/index.js +15 -0
- package/dist/execution/worktree/index.js.map +1 -0
- package/dist/execution/worktree/manager.js +452 -0
- package/dist/execution/worktree/manager.js.map +1 -0
- package/dist/execution/worktree/types.js +42 -0
- package/dist/execution/worktree/types.js.map +1 -0
- package/dist/index.js +356 -104
- package/dist/index.js.map +1 -7
- package/dist/routes/executions-stream.js +55 -0
- package/dist/routes/executions-stream.js.map +1 -0
- package/dist/routes/executions.js +267 -0
- package/dist/routes/executions.js.map +1 -0
- package/dist/routes/feedback.js +329 -0
- package/dist/routes/feedback.js.map +1 -0
- package/dist/routes/issues.js +280 -0
- package/dist/routes/issues.js.map +1 -0
- package/dist/routes/relationships.js +308 -0
- package/dist/routes/relationships.js.map +1 -0
- package/dist/routes/specs.js +270 -0
- package/dist/routes/specs.js.map +1 -0
- package/dist/services/db.js +85 -0
- package/dist/services/db.js.map +1 -0
- package/dist/services/execution-lifecycle.js +286 -0
- package/dist/services/execution-lifecycle.js.map +1 -0
- package/dist/services/execution-service.js +676 -0
- package/dist/services/execution-service.js.map +1 -0
- package/dist/services/executions.js +164 -0
- package/dist/services/executions.js.map +1 -0
- package/dist/services/export.js +106 -0
- package/dist/services/export.js.map +1 -0
- package/dist/services/feedback.js +54 -0
- package/dist/services/feedback.js.map +1 -0
- package/dist/services/issues.js +35 -0
- package/dist/services/issues.js.map +1 -0
- package/dist/services/prompt-template-engine.js +212 -0
- package/dist/services/prompt-template-engine.js.map +1 -0
- package/dist/services/prompt-templates.js +236 -0
- package/dist/services/prompt-templates.js.map +1 -0
- package/dist/services/relationships.js +42 -0
- package/dist/services/relationships.js.map +1 -0
- package/dist/services/specs.js +35 -0
- package/dist/services/specs.js.map +1 -0
- package/dist/services/watcher.js +69 -0
- package/dist/services/watcher.js.map +1 -0
- package/dist/services/websocket.js +389 -0
- package/dist/services/websocket.js.map +1 -0
- package/dist/utils/sudocode-dir.js +9 -0
- package/dist/utils/sudocode-dir.js.map +1 -0
- package/package.json +4 -6
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Stream Routes
|
|
3
|
+
*
|
|
4
|
+
* SSE endpoint for streaming execution events to clients.
|
|
5
|
+
* Integrates with TransportManager to broadcast AG-UI events.
|
|
6
|
+
*
|
|
7
|
+
* @module routes/executions-stream
|
|
8
|
+
*/
|
|
9
|
+
import { Router } from "express";
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
11
|
+
/**
|
|
12
|
+
* Create execution stream routes
|
|
13
|
+
*
|
|
14
|
+
* @param transportManager - Transport manager for SSE connections
|
|
15
|
+
* @returns Express router with SSE endpoints
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const router = createExecutionStreamRoutes(transportManager);
|
|
20
|
+
* app.use('/api/executions', router);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function createExecutionStreamRoutes(transportManager) {
|
|
24
|
+
const router = Router();
|
|
25
|
+
/**
|
|
26
|
+
* SSE endpoint for execution event stream
|
|
27
|
+
*
|
|
28
|
+
* GET /api/executions/:executionId/stream
|
|
29
|
+
*
|
|
30
|
+
* Establishes SSE connection and streams execution events to client.
|
|
31
|
+
* Events are filtered to only include events for the specified execution.
|
|
32
|
+
*
|
|
33
|
+
* @param executionId - Execution ID to stream events for
|
|
34
|
+
*/
|
|
35
|
+
router.get("/:executionId/stream", (req, res) => {
|
|
36
|
+
const { executionId } = req.params;
|
|
37
|
+
// TODO: Add authentication/authorization check
|
|
38
|
+
// Verify user has permission to access this execution
|
|
39
|
+
// Generate unique client ID
|
|
40
|
+
const clientId = randomUUID();
|
|
41
|
+
// Get buffered events for replay
|
|
42
|
+
const bufferedEvents = transportManager.getBufferedEvents(executionId);
|
|
43
|
+
const replayEvents = bufferedEvents.map((buffered) => ({
|
|
44
|
+
event: buffered.event.type,
|
|
45
|
+
data: buffered.event,
|
|
46
|
+
}));
|
|
47
|
+
// Establish SSE connection through transport manager
|
|
48
|
+
// This will set appropriate headers, send connection acknowledgment, and replay buffered events
|
|
49
|
+
transportManager
|
|
50
|
+
.getSseTransport()
|
|
51
|
+
.handleConnection(clientId, res, executionId, replayEvents);
|
|
52
|
+
});
|
|
53
|
+
return router;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=executions-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executions-stream.js","sourceRoot":"","sources":["../../src/routes/executions-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,2BAA2B,CACzC,gBAAkC;IAElC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACjE,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;QAEnC,+CAA+C;QAC/C,sDAAsD;QAEtD,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;QAE9B,iCAAiC;QACjC,MAAM,cAAc,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACvE,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACrD,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI;YAC1B,IAAI,EAAE,QAAQ,CAAC,KAAK;SACrB,CAAC,CAAC,CAAC;QAEJ,qDAAqD;QACrD,gGAAgG;QAChG,gBAAgB;aACb,eAAe,EAAE;aACjB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executions API routes (mapped to /api)
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API for managing issue executions.
|
|
5
|
+
*/
|
|
6
|
+
import { Router } from "express";
|
|
7
|
+
import { ExecutionService } from "../services/execution-service.js";
|
|
8
|
+
/**
|
|
9
|
+
* Create executions router
|
|
10
|
+
*
|
|
11
|
+
* @param db - Database instance
|
|
12
|
+
* @param repoPath - Path to git repository
|
|
13
|
+
* @param transportManager - Optional transport manager for SSE streaming
|
|
14
|
+
* @returns Express router with execution endpoints
|
|
15
|
+
*/
|
|
16
|
+
export function createExecutionsRouter(db, repoPath, transportManager, executionService) {
|
|
17
|
+
const router = Router();
|
|
18
|
+
const service = executionService ||
|
|
19
|
+
new ExecutionService(db, repoPath, undefined, transportManager);
|
|
20
|
+
/**
|
|
21
|
+
* POST /api/issues/:issueId/executions/prepare
|
|
22
|
+
*
|
|
23
|
+
* Prepare an execution - render template and show preview
|
|
24
|
+
*/
|
|
25
|
+
router.post("/issues/:issueId/executions/prepare", async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const { issueId } = req.params;
|
|
28
|
+
const options = req.body || {};
|
|
29
|
+
const result = await service.prepareExecution(issueId, options);
|
|
30
|
+
res.json({
|
|
31
|
+
success: true,
|
|
32
|
+
data: result,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error("[API Route] ERROR: Failed to prepare execution:", error);
|
|
37
|
+
res.status(500).json({
|
|
38
|
+
success: false,
|
|
39
|
+
data: null,
|
|
40
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
41
|
+
message: "Failed to prepare execution",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* POST /api/issues/:issueId/executions
|
|
47
|
+
*
|
|
48
|
+
* Create and start a new execution
|
|
49
|
+
*/
|
|
50
|
+
router.post("/issues/:issueId/executions", async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { issueId } = req.params;
|
|
53
|
+
const { config, prompt } = req.body;
|
|
54
|
+
// Validate required fields
|
|
55
|
+
if (!prompt) {
|
|
56
|
+
res.status(400).json({
|
|
57
|
+
success: false,
|
|
58
|
+
data: null,
|
|
59
|
+
message: "Prompt is required",
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const execution = await service.createExecution(issueId, config || {}, prompt);
|
|
64
|
+
res.status(201).json({
|
|
65
|
+
success: true,
|
|
66
|
+
data: execution,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error("[API Route] ERROR: Failed to create execution:", error);
|
|
71
|
+
// Handle specific error cases
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
+
const statusCode = errorMessage.includes("not found") ? 404 : 500;
|
|
74
|
+
res.status(statusCode).json({
|
|
75
|
+
success: false,
|
|
76
|
+
data: null,
|
|
77
|
+
error_data: errorMessage,
|
|
78
|
+
message: "Failed to create execution",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* GET /api/executions/:executionId
|
|
84
|
+
*
|
|
85
|
+
* Get a specific execution by ID
|
|
86
|
+
*/
|
|
87
|
+
router.get("/executions/:executionId", (req, res) => {
|
|
88
|
+
try {
|
|
89
|
+
const { executionId } = req.params;
|
|
90
|
+
const execution = service.getExecution(executionId);
|
|
91
|
+
if (!execution) {
|
|
92
|
+
res.status(404).json({
|
|
93
|
+
success: false,
|
|
94
|
+
data: null,
|
|
95
|
+
message: `Execution not found: ${executionId}`,
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
res.json({
|
|
100
|
+
success: true,
|
|
101
|
+
data: execution,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Error getting execution:", error);
|
|
106
|
+
res.status(500).json({
|
|
107
|
+
success: false,
|
|
108
|
+
data: null,
|
|
109
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
110
|
+
message: "Failed to get execution",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* GET /api/issues/:issueId/executions
|
|
116
|
+
*
|
|
117
|
+
* List all executions for an issue
|
|
118
|
+
*/
|
|
119
|
+
router.get("/issues/:issueId/executions", (req, res) => {
|
|
120
|
+
try {
|
|
121
|
+
const { issueId } = req.params;
|
|
122
|
+
const executions = service.listExecutions(issueId);
|
|
123
|
+
res.json({
|
|
124
|
+
success: true,
|
|
125
|
+
data: executions,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error("Error listing executions:", error);
|
|
130
|
+
res.status(500).json({
|
|
131
|
+
success: false,
|
|
132
|
+
data: null,
|
|
133
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
134
|
+
message: "Failed to list executions",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* POST /api/executions/:executionId/follow-up
|
|
140
|
+
*
|
|
141
|
+
* Create a follow-up execution that reuses the parent's worktree
|
|
142
|
+
*/
|
|
143
|
+
router.post("/executions/:executionId/follow-up", async (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
const { executionId } = req.params;
|
|
146
|
+
const { feedback } = req.body;
|
|
147
|
+
// Validate required fields
|
|
148
|
+
if (!feedback) {
|
|
149
|
+
res.status(400).json({
|
|
150
|
+
success: false,
|
|
151
|
+
data: null,
|
|
152
|
+
message: "Feedback is required",
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const followUpExecution = await service.createFollowUp(executionId, feedback);
|
|
157
|
+
res.status(201).json({
|
|
158
|
+
success: true,
|
|
159
|
+
data: followUpExecution,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error("Error creating follow-up execution:", error);
|
|
164
|
+
// Handle specific error cases
|
|
165
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
166
|
+
const statusCode = errorMessage.includes("not found") ||
|
|
167
|
+
errorMessage.includes("no worktree")
|
|
168
|
+
? 404
|
|
169
|
+
: 500;
|
|
170
|
+
res.status(statusCode).json({
|
|
171
|
+
success: false,
|
|
172
|
+
data: null,
|
|
173
|
+
error_data: errorMessage,
|
|
174
|
+
message: "Failed to create follow-up execution",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* DELETE /api/executions/:executionId
|
|
180
|
+
*
|
|
181
|
+
* Cancel a running execution
|
|
182
|
+
*/
|
|
183
|
+
router.delete("/executions/:executionId", async (req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
const { executionId } = req.params;
|
|
186
|
+
await service.cancelExecution(executionId);
|
|
187
|
+
res.json({
|
|
188
|
+
success: true,
|
|
189
|
+
data: { executionId },
|
|
190
|
+
message: "Execution cancelled successfully",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error("Error cancelling execution:", error);
|
|
195
|
+
// Handle specific error cases
|
|
196
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
197
|
+
const statusCode = errorMessage.includes("not found") ? 404 : 500;
|
|
198
|
+
res.status(statusCode).json({
|
|
199
|
+
success: false,
|
|
200
|
+
data: null,
|
|
201
|
+
error_data: errorMessage,
|
|
202
|
+
message: "Failed to cancel execution",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
/**
|
|
207
|
+
* GET /api/executions/:executionId/worktree
|
|
208
|
+
*
|
|
209
|
+
* Check if worktree exists for an execution
|
|
210
|
+
*/
|
|
211
|
+
router.get("/executions/:executionId/worktree", async (req, res) => {
|
|
212
|
+
try {
|
|
213
|
+
const { executionId } = req.params;
|
|
214
|
+
const exists = await service.worktreeExists(executionId);
|
|
215
|
+
res.json({
|
|
216
|
+
success: true,
|
|
217
|
+
data: { exists },
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
console.error("Error checking worktree:", error);
|
|
222
|
+
res.status(500).json({
|
|
223
|
+
success: false,
|
|
224
|
+
data: null,
|
|
225
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
226
|
+
message: "Failed to check worktree status",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
/**
|
|
231
|
+
* DELETE /api/executions/:executionId/worktree
|
|
232
|
+
*
|
|
233
|
+
* Delete the worktree for an execution
|
|
234
|
+
*/
|
|
235
|
+
router.delete("/executions/:executionId/worktree", async (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
const { executionId } = req.params;
|
|
238
|
+
await service.deleteWorktree(executionId);
|
|
239
|
+
res.json({
|
|
240
|
+
success: true,
|
|
241
|
+
data: { executionId },
|
|
242
|
+
message: "Worktree deleted successfully",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error("Error deleting worktree:", error);
|
|
247
|
+
// Handle specific error cases
|
|
248
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
249
|
+
let statusCode = 500;
|
|
250
|
+
if (errorMessage.includes("not found")) {
|
|
251
|
+
statusCode = 404;
|
|
252
|
+
}
|
|
253
|
+
else if (errorMessage.includes("has no worktree") ||
|
|
254
|
+
errorMessage.includes("Cannot delete worktree")) {
|
|
255
|
+
statusCode = 400;
|
|
256
|
+
}
|
|
257
|
+
res.status(statusCode).json({
|
|
258
|
+
success: false,
|
|
259
|
+
data: null,
|
|
260
|
+
error_data: errorMessage,
|
|
261
|
+
message: "Failed to delete worktree",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return router;
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=executions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executions.js","sourceRoot":"","sources":["../../src/routes/executions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback API routes (mapped to /api/feedback)
|
|
3
|
+
*/
|
|
4
|
+
import { Router } from "express";
|
|
5
|
+
import { createNewFeedback, getFeedbackById, updateExistingFeedback, deleteExistingFeedback, getAllFeedback, } from "../services/feedback.js";
|
|
6
|
+
import { broadcastFeedbackUpdate } from "../services/websocket.js";
|
|
7
|
+
export function createFeedbackRouter(db) {
|
|
8
|
+
const router = Router();
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/feedback - List all feedback with optional filters
|
|
11
|
+
* Query params: spec_id, issue_id, feedback_type, dismissed, limit, offset
|
|
12
|
+
*/
|
|
13
|
+
router.get("/", (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const options = {};
|
|
16
|
+
if (req.query.spec_id) {
|
|
17
|
+
options.spec_id = req.query.spec_id;
|
|
18
|
+
}
|
|
19
|
+
if (req.query.issue_id) {
|
|
20
|
+
options.issue_id = req.query.issue_id;
|
|
21
|
+
}
|
|
22
|
+
if (req.query.feedback_type) {
|
|
23
|
+
options.feedback_type = req.query.feedback_type;
|
|
24
|
+
}
|
|
25
|
+
if (req.query.dismissed !== undefined) {
|
|
26
|
+
options.dismissed = req.query.dismissed === "true";
|
|
27
|
+
}
|
|
28
|
+
if (req.query.limit) {
|
|
29
|
+
options.limit = parseInt(req.query.limit, 10);
|
|
30
|
+
}
|
|
31
|
+
if (req.query.offset) {
|
|
32
|
+
options.offset = parseInt(req.query.offset, 10);
|
|
33
|
+
}
|
|
34
|
+
const feedback = getAllFeedback(db, options);
|
|
35
|
+
res.json({
|
|
36
|
+
success: true,
|
|
37
|
+
data: feedback,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error("Error listing feedback:", error);
|
|
42
|
+
res.status(500).json({
|
|
43
|
+
success: false,
|
|
44
|
+
data: null,
|
|
45
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
46
|
+
message: "Failed to list feedback",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* GET /api/feedback/:id - Get a specific feedback entry
|
|
52
|
+
*/
|
|
53
|
+
router.get("/:id", (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { id } = req.params;
|
|
56
|
+
const feedback = getFeedbackById(db, id);
|
|
57
|
+
if (!feedback) {
|
|
58
|
+
res.status(404).json({
|
|
59
|
+
success: false,
|
|
60
|
+
data: null,
|
|
61
|
+
message: `Feedback not found: ${id}`,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
res.json({
|
|
66
|
+
success: true,
|
|
67
|
+
data: feedback,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error("Error getting feedback:", error);
|
|
72
|
+
res.status(500).json({
|
|
73
|
+
success: false,
|
|
74
|
+
data: null,
|
|
75
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
76
|
+
message: "Failed to get feedback",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* POST /api/feedback - Create a new feedback entry
|
|
82
|
+
*/
|
|
83
|
+
router.post("/", (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const { issue_id, spec_id, feedback_type, content, agent, anchor, dismissed, } = req.body;
|
|
86
|
+
// Validate required fields
|
|
87
|
+
if (!issue_id || typeof issue_id !== "string") {
|
|
88
|
+
res.status(400).json({
|
|
89
|
+
success: false,
|
|
90
|
+
data: null,
|
|
91
|
+
message: "issue_id is required and must be a string",
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!spec_id || typeof spec_id !== "string") {
|
|
96
|
+
res.status(400).json({
|
|
97
|
+
success: false,
|
|
98
|
+
data: null,
|
|
99
|
+
message: "spec_id is required and must be a string",
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!feedback_type || typeof feedback_type !== "string") {
|
|
104
|
+
res.status(400).json({
|
|
105
|
+
success: false,
|
|
106
|
+
data: null,
|
|
107
|
+
message: "feedback_type is required and must be a string",
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Validate feedback_type
|
|
112
|
+
const validTypes = ["comment", "suggestion", "request"];
|
|
113
|
+
if (!validTypes.includes(feedback_type)) {
|
|
114
|
+
res.status(400).json({
|
|
115
|
+
success: false,
|
|
116
|
+
data: null,
|
|
117
|
+
message: `Invalid feedback_type. Must be one of: ${validTypes.join(", ")}`,
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!content || typeof content !== "string") {
|
|
122
|
+
res.status(400).json({
|
|
123
|
+
success: false,
|
|
124
|
+
data: null,
|
|
125
|
+
message: "content is required and must be a string",
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Validate anchor if provided
|
|
130
|
+
if (anchor !== undefined && anchor !== null) {
|
|
131
|
+
if (typeof anchor !== "object") {
|
|
132
|
+
res.status(400).json({
|
|
133
|
+
success: false,
|
|
134
|
+
data: null,
|
|
135
|
+
message: "anchor must be an object if provided",
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Validate anchor structure if provided
|
|
140
|
+
if (anchor.anchor_status) {
|
|
141
|
+
const validAnchorStatuses = ["valid", "relocated", "stale"];
|
|
142
|
+
if (!validAnchorStatuses.includes(anchor.anchor_status)) {
|
|
143
|
+
res.status(400).json({
|
|
144
|
+
success: false,
|
|
145
|
+
data: null,
|
|
146
|
+
message: `Invalid anchor.anchor_status. Must be one of: ${validAnchorStatuses.join(", ")}`,
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Create feedback using CLI operation
|
|
153
|
+
const feedback = createNewFeedback(db, {
|
|
154
|
+
issue_id,
|
|
155
|
+
spec_id,
|
|
156
|
+
feedback_type: feedback_type,
|
|
157
|
+
content,
|
|
158
|
+
agent: agent || undefined,
|
|
159
|
+
anchor: anchor,
|
|
160
|
+
dismissed: dismissed || false,
|
|
161
|
+
});
|
|
162
|
+
// Broadcast feedback creation to WebSocket clients
|
|
163
|
+
broadcastFeedbackUpdate("created", feedback);
|
|
164
|
+
res.status(201).json({
|
|
165
|
+
success: true,
|
|
166
|
+
data: feedback,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error("Error creating feedback:", error);
|
|
171
|
+
// Handle specific errors
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
if (error.message.includes("not found")) {
|
|
174
|
+
res.status(404).json({
|
|
175
|
+
success: false,
|
|
176
|
+
data: null,
|
|
177
|
+
message: error.message,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (error.message.includes("Constraint violation")) {
|
|
182
|
+
res.status(409).json({
|
|
183
|
+
success: false,
|
|
184
|
+
data: null,
|
|
185
|
+
message: error.message,
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
res.status(500).json({
|
|
191
|
+
success: false,
|
|
192
|
+
data: null,
|
|
193
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
194
|
+
message: "Failed to create feedback",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
/**
|
|
199
|
+
* PUT /api/feedback/:id - Update an existing feedback entry
|
|
200
|
+
*/
|
|
201
|
+
router.put("/:id", (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
const { id } = req.params;
|
|
204
|
+
const { content, dismissed, anchor } = req.body;
|
|
205
|
+
// Validate that at least one field is provided
|
|
206
|
+
if (content === undefined &&
|
|
207
|
+
dismissed === undefined &&
|
|
208
|
+
anchor === undefined) {
|
|
209
|
+
res.status(400).json({
|
|
210
|
+
success: false,
|
|
211
|
+
data: null,
|
|
212
|
+
message: "At least one field must be provided for update",
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Validate anchor if provided
|
|
217
|
+
if (anchor !== undefined) {
|
|
218
|
+
if (typeof anchor !== "object") {
|
|
219
|
+
res.status(400).json({
|
|
220
|
+
success: false,
|
|
221
|
+
data: null,
|
|
222
|
+
message: "anchor must be an object",
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (!anchor.anchor_status || typeof anchor.anchor_status !== "string") {
|
|
227
|
+
res.status(400).json({
|
|
228
|
+
success: false,
|
|
229
|
+
data: null,
|
|
230
|
+
message: "anchor.anchor_status is required and must be a string",
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const validAnchorStatuses = ["valid", "relocated", "stale"];
|
|
235
|
+
if (!validAnchorStatuses.includes(anchor.anchor_status)) {
|
|
236
|
+
res.status(400).json({
|
|
237
|
+
success: false,
|
|
238
|
+
data: null,
|
|
239
|
+
message: `Invalid anchor.anchor_status. Must be one of: ${validAnchorStatuses.join(", ")}`,
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Build update input
|
|
245
|
+
const updateInput = {};
|
|
246
|
+
if (content !== undefined)
|
|
247
|
+
updateInput.content = content;
|
|
248
|
+
if (dismissed !== undefined)
|
|
249
|
+
updateInput.dismissed = dismissed;
|
|
250
|
+
if (anchor !== undefined)
|
|
251
|
+
updateInput.anchor = anchor;
|
|
252
|
+
// Update feedback using CLI operation
|
|
253
|
+
const feedback = updateExistingFeedback(db, id, updateInput);
|
|
254
|
+
// Broadcast feedback update to WebSocket clients
|
|
255
|
+
broadcastFeedbackUpdate("updated", feedback);
|
|
256
|
+
res.json({
|
|
257
|
+
success: true,
|
|
258
|
+
data: feedback,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
console.error("Error updating feedback:", error);
|
|
263
|
+
// Handle "not found" errors
|
|
264
|
+
if (error instanceof Error && error.message.includes("not found")) {
|
|
265
|
+
res.status(404).json({
|
|
266
|
+
success: false,
|
|
267
|
+
data: null,
|
|
268
|
+
message: error.message,
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
res.status(500).json({
|
|
273
|
+
success: false,
|
|
274
|
+
data: null,
|
|
275
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
276
|
+
message: "Failed to update feedback",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
/**
|
|
281
|
+
* DELETE /api/feedback/:id - Delete a feedback entry
|
|
282
|
+
*/
|
|
283
|
+
router.delete("/:id", (req, res) => {
|
|
284
|
+
try {
|
|
285
|
+
const { id } = req.params;
|
|
286
|
+
// Check if feedback exists first
|
|
287
|
+
const existingFeedback = getFeedbackById(db, id);
|
|
288
|
+
if (!existingFeedback) {
|
|
289
|
+
res.status(404).json({
|
|
290
|
+
success: false,
|
|
291
|
+
data: null,
|
|
292
|
+
message: `Feedback not found: ${id}`,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Delete feedback using CLI operation
|
|
297
|
+
const deleted = deleteExistingFeedback(db, id);
|
|
298
|
+
if (deleted) {
|
|
299
|
+
// Broadcast feedback deletion to WebSocket clients
|
|
300
|
+
broadcastFeedbackUpdate("deleted", { id });
|
|
301
|
+
res.json({
|
|
302
|
+
success: true,
|
|
303
|
+
data: {
|
|
304
|
+
id,
|
|
305
|
+
deleted: true,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
res.status(500).json({
|
|
311
|
+
success: false,
|
|
312
|
+
data: null,
|
|
313
|
+
message: "Failed to delete feedback",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
console.error("Error deleting feedback:", error);
|
|
319
|
+
res.status(500).json({
|
|
320
|
+
success: false,
|
|
321
|
+
data: null,
|
|
322
|
+
error_data: error instanceof Error ? error.message : String(error),
|
|
323
|
+
message: "Failed to delete feedback",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return router;
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=feedback.js.map
|