@sudocode-ai/local-server 0.1.0 → 0.1.2
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.d.ts.map +1 -1
- package/dist/index.js +357 -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.d.ts.map +1 -1
- package/dist/services/execution-lifecycle.js +291 -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
package/dist/index.js
CHANGED
|
@@ -1,104 +1,357 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`}),await this._processManager.sendInput(s.id,e.prompt),this._processManager.closeInput(s.id),await this.waitForProcessExit(s,e.config.timeout);let c=new Date,u={taskId:e.id,executionId:s.id,success:s.exitCode===0,exitCode:s.exitCode??-1,output:r,error:n||void 0,startedAt:t,completedAt:c,duration:c.getTime()-t.getTime(),metadata:this.parseMetadata(r)};this.completedResults.set(e.id,u),this.metrics.completedTasks++;let l=this.taskResolvers.get(e.id);l&&(l.forEach(d=>d.resolve(u)),this.taskResolvers.delete(e.id));for(let d of this.completeHandlers)d(u);this.trackTaskComplete(e.id)}catch(i){this.handleTaskFailure(e.id,i)}finally{if(s)try{await this._processManager.releaseProcess(s.id)}catch{}}}async waitForProcessExit(e,t){return new Promise((s,r)=>{if(e.exitCode!==null){s();return}let n=setInterval(()=>{let i=this._processManager.getProcess(e.id);(!i||i.exitCode!==null)&&(clearInterval(n),this.pollingIntervals.delete(e.id),i?i.status==="crashed"?r(new Error(`Process crashed with exit code ${i.exitCode}`)):s():r(new Error("Process not found")))},10);this.pollingIntervals.set(e.id,n),t&&setTimeout(()=>{clearInterval(n),this.pollingIntervals.delete(e.id),r(new Error("Process execution timeout"))},t)})}parseMetadata(e){return{toolsUsed:[],filesChanged:[],tokensUsed:0,cost:0}}handleTaskFailure(e,t){let s=this.runningTasks.get(e);if(s){let a=s.task,c=a.config.maxRetries,u=s.attempt;if(c!==void 0&&u<=c){let l={...a,metadata:{...a.metadata,_retryAttempt:u+1}};this.taskQueue.unshift(l),this.metrics.queuedTasks++,this.trackTaskComplete(e);return}}let r=new Date,n={taskId:e,executionId:`failed-${e}`,success:!1,exitCode:-1,output:"",error:t.message,startedAt:r,completedAt:r,duration:0,metadata:{toolsUsed:[],filesChanged:[],tokensUsed:0,cost:0}};this.completedResults.set(e,n),this.metrics.failedTasks++,this.trackTaskComplete(e);let i=this.taskResolvers.get(e);i&&(i.forEach(a=>a.reject(t)),this.taskResolvers.delete(e));for(let a of this.failedHandlers)a(e,t)}async cancelTask(e){let t=this.taskQueue.findIndex(r=>r.id===e);if(t>=0){this.taskQueue.splice(t,1),this.metrics.queuedTasks--;return}let s=this.runningTasks.get(e);if(s){let r=this.pollingIntervals.get(s.process.id);r&&(clearInterval(r),this.pollingIntervals.delete(s.process.id));try{await this._processManager.terminateProcess(s.process.id)}catch{}this.runningTasks.delete(e),this.metrics.currentlyRunning=this.runningTasks.size,this.metrics.availableSlots=this.metrics.maxConcurrent-this.runningTasks.size,this.processQueue();return}}getTaskStatus(e){let t=this.completedResults.get(e);if(t)return{state:"completed",result:t};let s=this.runningTasks.get(e);if(s)return{state:"running",processId:s.process.id,startedAt:s.startedAt};let r=this.taskQueue.findIndex(n=>n.id===e);return r>=0?{state:"queued",position:r}:null}async waitForTask(e){let t=this.completedResults.get(e);return t||new Promise((s,r)=>{let n=this.taskResolvers.get(e)||[];n.push({resolve:s,reject:r}),this.taskResolvers.set(e,n)})}async waitForTasks(e){return Promise.all(e.map(t=>this.waitForTask(t)))}getMetrics(){return{...this.metrics}}onTaskComplete(e){this.completeHandlers.push(e)}onTaskFailed(e){this.failedHandlers.push(e)}async shutdown(){this.taskQueue=[],this.metrics.queuedTasks=0;let e=Array.from(this.runningTasks.keys());await Promise.all(e.map(t=>this.cancelTask(t)));for(let[t,s]of this.pollingIntervals.entries())clearInterval(s),this.pollingIntervals.delete(t);await this._processManager.shutdown(),this.runningTasks.clear(),this.completedResults.clear(),this.taskResolvers.clear(),this.completeHandlers=[],this.failedHandlers=[],this.metrics.currentlyRunning=0,this.metrics.availableSlots=this.metrics.maxConcurrent}};var ne=class{breakers=new Map;get(e){return this.breakers.get(e)||null}getOrCreate(e,t={failureThreshold:5,successThreshold:2,timeout:6e4}){let s=this.breakers.get(e);return s||(s={name:e,state:"closed",config:t,metrics:{totalRequests:0,failedRequests:0,successfulRequests:0}},this.breakers.set(e,s)),s}canExecute(e){let t=this.breakers.get(e);return t&&t.state==="open"?this.shouldTransitionToHalfOpen(t)?(t.state="half-open",!0):!1:!0}recordSuccess(e){let t=this.breakers.get(e);t&&(t.metrics.totalRequests++,t.metrics.successfulRequests++,t.metrics.lastSuccessTime=new Date,t.state==="half-open"&&this.getConsecutiveSuccesses(t)>=t.config.successThreshold&&(t.state="closed",t.metrics.failedRequests=0))}recordFailure(e,t){let s=this.breakers.get(e);s&&(s.metrics.totalRequests++,s.metrics.failedRequests++,s.metrics.lastFailureTime=new Date,s.state==="closed"?s.metrics.failedRequests>=s.config.failureThreshold&&(s.state="open"):s.state==="half-open"&&(s.state="open"))}reset(e){let t=this.breakers.get(e);t&&(t.state="closed",t.metrics.failedRequests=0,t.metrics.successfulRequests=0)}getAll(){return new Map(this.breakers)}shouldTransitionToHalfOpen(e){return e.metrics.lastFailureTime?Date.now()-e.metrics.lastFailureTime.getTime()>=e.config.timeout:!0}getConsecutiveSuccesses(e){return e.metrics.successfulRequests}};function ke(o,e){let t;switch(e.type){case"exponential":t=e.baseDelayMs*Math.pow(2,o-1);break;case"linear":t=e.baseDelayMs*o;break;case"fixed":t=e.baseDelayMs;break;default:t=e.baseDelayMs}if(t=Math.min(t,e.maxDelayMs),e.jitter){let s=t*.1,r=Math.random()*s*2-s;t+=r,t=Math.max(0,Math.min(t,e.maxDelayMs))}return Math.floor(t)}function ds(o,e){let t=o.message||"";for(let s of e.retryableErrors)if(t.includes(s))return!0;return!1}function ps(o,e){return e.retryableExitCodes.includes(o)}function nt(o,e){if(o.exitCode!==void 0&&o.exitCode!==null&&ps(o.exitCode,e))return!0;if(o.error){let t=new Error(o.error);if(ds(t,e))return!0}return!1}function we(o){return new Promise(e=>setTimeout(e,o))}function Q(o,e,t={}){let s=new Date;return{attemptNumber:o,startedAt:s,completedAt:t.duration!==void 0?s:void 0,duration:t.duration,success:e,error:t.error,exitCode:t.exitCode,willRetry:t.willRetry||!1,nextRetryAt:t.nextRetryAt}}var ot={maxAttempts:3,backoff:{type:"exponential",baseDelayMs:1e3,maxDelayMs:3e4,jitter:!0},retryableErrors:["ECONNREFUSED","ETIMEDOUT","ENOTFOUND","timeout","network","Process execution timeout"],retryableExitCodes:[1,137]};var O=class{_engine;_circuitManager;_defaultPolicy;_metrics;_retryHandlers=[];_circuitOpenHandlers=[];constructor(e,t=ot){this._engine=e,this._circuitManager=new ne,this._defaultPolicy=t,this._metrics={totalRetries:0,successfulRetries:0,failedRetries:0,averageAttemptsToSuccess:0,circuitBreakers:new Map}}async executeTask(e,t){let s=t||this._defaultPolicy,r=e.type,n=[],i=this._circuitManager.getOrCreate(r,{failureThreshold:5,successThreshold:2,timeout:6e4});for(let a=1;a<=s.maxAttempts;a++){if(!this._circuitManager.canExecute(r))return this._circuitOpenHandlers.forEach(l=>{l(r,i)}),{taskId:e.id,executionId:"circuit-breaker-open",success:!1,exitCode:-1,output:"",error:`Circuit breaker is open for ${r}`,startedAt:new Date,completedAt:new Date,duration:0,attempts:[],totalAttempts:0,finalAttempt:Q(0,!1,{error:new Error(`Circuit breaker is open for ${r}`)}),circuitBreakerTriggered:!0};let c=new Date;try{let u=await this._engine.submitTask(e),l=await this._engine.waitForTask(u),d=Date.now()-c.getTime();if(l.success){this._circuitManager.recordSuccess(r);let m=Q(a,!0,{exitCode:l.exitCode,duration:d});return n.push(m),a>1&&(this._metrics.successfulRetries++,this._updateAverageAttempts(a)),this._createResilientResult(l,n)}let g=nt(l,s)&&a<s.maxAttempts,f=Q(a,!1,{error:new Error(l.error||"Task failed"),exitCode:l.exitCode,duration:d,willRetry:g});if(g){let m=ke(a+1,s.backoff);f.nextRetryAt=new Date(Date.now()+m),this._retryHandlers.forEach(y=>{y(e.id,f)}),this._metrics.totalRetries++,n.push(f),await we(m);continue}return n.push(f),this._circuitManager.recordFailure(r,new Error(l.error||"Task failed")),a>1&&(this._metrics.failedRetries+=a-1),this._createResilientResult(l,n)}catch(u){let l=Date.now()-c.getTime(),d=u instanceof Error?u:new Error(String(u)),g=s.retryableErrors.some(m=>d.message.includes(m))&&a<s.maxAttempts,f=Q(a,!1,{error:d,duration:l,willRetry:g});if(g){let m=ke(a+1,s.backoff);f.nextRetryAt=new Date(Date.now()+m),this._retryHandlers.forEach(y=>{y(e.id,f)}),this._metrics.totalRetries++,n.push(f),await we(m);continue}throw n.push(f),this._circuitManager.recordFailure(r,d),a>1&&(this._metrics.failedRetries+=a-1),d}}throw new Error("Unexpected state: exceeded max attempts without return")}async executeTasks(e,t){let s=e.map(r=>this.executeTask(r,t));return Promise.all(s)}getCircuitBreaker(e){return this._circuitManager.get(e)}resetCircuitBreaker(e){this._circuitManager.reset(e)}getRetryMetrics(){return this._metrics.circuitBreakers=this._circuitManager.getAll(),{...this._metrics,circuitBreakers:new Map(this._metrics.circuitBreakers)}}onRetryAttempt(e){this._retryHandlers.push(e)}onCircuitOpen(e){this._circuitOpenHandlers.push(e)}_createResilientResult(e,t){let s=t[t.length-1];return{...e,attempts:t,totalAttempts:t.length,finalAttempt:s,failureReason:e.success?void 0:e.error}}_updateAverageAttempts(e){let t=this._metrics.successfulRetries,r=this._metrics.averageAttemptsToSuccess*(t-1);this._metrics.averageAttemptsToSuccess=(r+e)/t}};function it(o="id"){let e=Date.now(),t=Math.random().toString(36).substring(2,9);return`${o}-${e}-${t}`}function xe(o,e){let t=o,s=/\{\{(\w+(?:\.\w+)*)\}\}/g;return t=t.replace(s,(r,n)=>{let i=Se(e,n);return i==null?r:String(i)}),t}function Se(o,e){if(o==null)return;if(!e.includes("."))return o[e];let t=e.split("."),s=o;for(let r of t){if(s==null)return;s=s[r]}return s}function at(o,e){let t=xe(o,e);return t.includes("{{")&&t.includes("}}")?!1:t==="true"||t==="1"?!0:t==="false"||t==="0"||t===""?!1:!!t}var K=class{_executions=new Map;_cleanedUpExecutions=new Set;_storage;_executor;_agUiAdapter;_lifecycleService;_workflowStartHandlers=[];_workflowCompleteHandlers=[];_workflowFailedHandlers=[];_stepStartHandlers=[];_stepCompleteHandlers=[];_stepFailedHandlers=[];_checkpointHandlers=[];_resumeHandlers=[];_pauseHandlers=[];_cancelHandlers=[];constructor(e,t,s,r){this._executor=e,this._storage=t,this._agUiAdapter=s,this._lifecycleService=r}async startWorkflow(e,t,s){let r={executionId:s.executionId,workflowId:e.id,definition:e,status:"pending",currentStepIndex:0,context:s?.initialContext||e.initialContext||{},stepResults:[],startedAt:new Date};return this._executions.set(r.executionId,r),this._executeWorkflow(e,r,t,s?.checkpointInterval).catch(n=>{r.status="failed",r.completedAt=new Date,r.error=n.message,this._workflowFailedHandlers.forEach(i=>{i(r.executionId,n)}),this._agUiAdapter&&this._agUiAdapter.emitRunError(n.message,n.stack),this._cleanupExecution(r).catch(()=>{})}),r.executionId}async resumeWorkflow(e,t){if(!this._storage)throw new Error("Cannot resume workflow: no storage configured");let s=await this._storage.loadCheckpoint(e);if(!s)throw new Error(`No checkpoint found for execution ${e}`);let r={workflowId:s.workflowId,executionId:s.executionId,definition:s.definition,status:"running",currentStepIndex:s.state.currentStepIndex,context:{...s.state.context},stepResults:[...s.state.stepResults],startedAt:s.state.startedAt,resumedAt:new Date};this._executions.set(e,r),this._resumeHandlers.forEach(i=>{i(e,s)});let n=s.definition.metadata?.workDir||process.cwd();return this._executeWorkflow(s.definition,r,n,t?.checkpointInterval).catch(i=>{r.status="failed",r.completedAt=new Date,r.error=i.message,this._workflowFailedHandlers.forEach(a=>{a(r.executionId,i)}),this._agUiAdapter&&this._agUiAdapter.emitRunError(i.message,i.stack),this._cleanupExecution(r).catch(()=>{})}),e}async pauseWorkflow(e){let t=this._executions.get(e);if(t){if(t.status!=="running")throw new Error(`Cannot pause workflow in ${t.status} state`);t.status="paused",t.pausedAt=new Date,await new Promise(s=>setTimeout(s,250)),this._storage&&await this._saveCheckpoint(t),this._pauseHandlers.forEach(s=>{s(e)})}}async cancelWorkflow(e){let t=this._executions.get(e);t&&(["completed","cancelled"].includes(t.status)||(t.status="cancelled",t.completedAt=new Date,this._storage&&await this._saveCheckpoint(t),this._cancelHandlers.forEach(s=>{s(e)}),await this._cleanupExecution(t)))}getExecution(e){return this._executions.get(e)||null}getStepStatus(e,t){let s=this._executions.get(e);if(!s)return null;let r=s.definition.steps.findIndex(a=>a.id===t);if(r===-1)return null;let n,i=s.stepResults[r];return i!==void 0?n=i.success?"completed":"failed":r===s.currentStepIndex?n="running":n="pending",{stepId:t,status:n,result:i,attempts:1}}async waitForWorkflow(e){let t=this._executions.get(e);if(!t)throw new Error(`Workflow execution ${e} not found`);return["completed","failed","cancelled"].includes(t.status)?t:new Promise((s,r)=>{let n=setInterval(()=>{let i=this._executions.get(e);if(!i){clearInterval(n),r(new Error(`Workflow execution ${e} not found`));return}["completed","failed","cancelled"].includes(i.status)&&(clearInterval(n),s(i))},100);setTimeout(()=>{clearInterval(n),r(new Error(`Timeout waiting for workflow ${e}`))},3e5)})}async listCheckpoints(e){return this._storage?this._storage.listCheckpoints(e):[]}onWorkflowStart(e){this._workflowStartHandlers.push(e)}onWorkflowComplete(e){this._workflowCompleteHandlers.push(e)}onWorkflowFailed(e){this._workflowFailedHandlers.push(e)}onStepStart(e){this._stepStartHandlers.push(e)}onStepComplete(e){this._stepCompleteHandlers.push(e)}onStepFailed(e){this._stepFailedHandlers.push(e)}onCheckpoint(e){this._checkpointHandlers.push(e)}onResume(e){this._resumeHandlers.push(e)}onPause(e){this._pauseHandlers.push(e)}onCancel(e){this._cancelHandlers.push(e)}async _executeWorkflow(e,t,s,r){t.status="running",this._workflowStartHandlers.forEach(i=>{i(t.executionId,e.id)}),this._agUiAdapter&&this._agUiAdapter.emitRunStarted({workflowId:e.id,executionId:t.executionId});for(let i=t.currentStepIndex;i<e.steps.length;i++){let a=e.steps[i];if(["paused","cancelled"].includes(t.status))return;if(t.stepResults[i]&&t.stepResults[i].success){t.currentStepIndex=i+1;continue}if(!this._areDependenciesMet(a,t)){let c=new Error(`Dependencies not met for step ${a.id}`);if(this._stepFailedHandlers.forEach(u=>{u(t.executionId,a.id,c)}),!e.config?.continueOnStepFailure){t.status="failed",t.completedAt=new Date,t.error=c.message,this._workflowFailedHandlers.forEach(u=>{u(t.executionId,c)}),await this._cleanupExecution(t);return}t.currentStepIndex=i+1;continue}if(!this._shouldExecuteStep(a,t.context)){t.currentStepIndex=i+1;continue}this._stepStartHandlers.forEach(c=>{c(t.executionId,a.id,i)}),this._agUiAdapter&&this._agUiAdapter.emitStepStarted(a.id,a.taskType||`Step ${i+1}`);try{let c=await this._executeStep(a,t,s);if(t.stepResults[i]=c,!c.success){let u=new Error(c.error||`Step ${a.id} failed`);if(this._stepFailedHandlers.forEach(l=>{l(t.executionId,a.id,u)}),!e.config?.continueOnStepFailure){t.status="failed",t.completedAt=new Date,t.error=u.message,this._workflowFailedHandlers.forEach(l=>{l(t.executionId,u)}),await this._cleanupExecution(t);return}t.currentStepIndex=i+1;continue}this._applyOutputMapping(a,c,t.context),this._stepCompleteHandlers.forEach(u=>{u(t.executionId,a.id,c)}),this._agUiAdapter&&this._agUiAdapter.emitStepFinished(a.id,"success",c.output),t.currentStepIndex=i+1,r&&this._storage&&(i+1)%r===0&&await this._saveCheckpoint(t)}catch(c){if(this._stepFailedHandlers.forEach(u=>{u(t.executionId,a.id,c)}),this._agUiAdapter&&this._agUiAdapter.emitStepFinished(a.id,"error"),!e.config?.continueOnStepFailure){t.status="failed",t.completedAt=new Date,t.error=c.message,this._workflowFailedHandlers.forEach(u=>{u(t.executionId,c)}),this._agUiAdapter&&this._agUiAdapter.emitRunError(c.message,c.stack),await this._cleanupExecution(t);return}t.currentStepIndex=i+1}}t.status="completed",t.completedAt=new Date;let n={executionId:t.executionId,success:t.stepResults.every(i=>i.success),completedSteps:t.stepResults.filter(i=>i.success).length,failedSteps:t.stepResults.filter(i=>!i.success).length,skippedSteps:0,outputs:t.context,duration:t.completedAt.getTime()-t.startedAt.getTime()};this._workflowCompleteHandlers.forEach(i=>{i(t.executionId,n)}),this._agUiAdapter&&this._agUiAdapter.emitRunFinished(n),await this._cleanupExecution(t)}async _saveCheckpoint(e){if(!this._storage)return;let t={workflowId:e.workflowId,executionId:e.executionId,definition:e.definition,state:{status:e.status,currentStepIndex:e.currentStepIndex,context:e.context,stepResults:e.stepResults,error:e.error,startedAt:e.startedAt,completedAt:e.completedAt},createdAt:new Date};await this._storage.saveCheckpoint(t),this._checkpointHandlers.forEach(s=>{s(t)})}async _cleanupExecution(e){if(!(!this._lifecycleService||!e.executionId)&&!this._cleanedUpExecutions.has(e.executionId)){this._cleanedUpExecutions.add(e.executionId);try{await this._lifecycleService.cleanupExecution(e.executionId)}catch(t){console.error(`Failed to cleanup execution ${e.executionId}:`,t)}}}async _executeStep(e,t,s){let r=xe(e.prompt,t.context),n={id:it("task"),type:e.taskType,prompt:r,workDir:s,priority:0,dependencies:[],createdAt:new Date,config:e.taskConfig||{}};return await this._executor.executeTask(n,e.retryPolicy)}_applyOutputMapping(e,t,s){if(e.outputMapping)for(let[r,n]of Object.entries(e.outputMapping)){let i=Se(t,n);s[r]=i}}_areDependenciesMet(e,t){if(!e.dependencies||e.dependencies.length===0)return!0;for(let s of e.dependencies){let r=t.definition.steps.findIndex(i=>i.id===s);if(r===-1||r>=t.currentStepIndex)return!1;let n=t.stepResults[r];if(!n||!n.success)return!1}return!0}_shouldExecuteStep(e,t){return e.condition?at(e.condition,t):!0}};var oe=class{_metrics;_toolCalls;_fileChanges;_toolCallHandlers=[];_fileChangeHandlers=[];_progressHandlers=[];_errorHandlers=[];_messageHandlers=[];_usageHandlers=[];_lineNumber=0;constructor(){this._metrics={totalMessages:0,toolCalls:[],fileChanges:[],usage:{inputTokens:0,outputTokens:0,cacheTokens:0,totalTokens:0,cost:0,provider:"anthropic",model:"claude"},errors:[],startedAt:new Date,lastUpdate:new Date},this._toolCalls=new Map,this._fileChanges=[]}async processLine(e){this._lineNumber++;let t=e.trim();if(t)try{let s=JSON.parse(t),r=this._detectMessageType(s),n=this._parseMessage(s,r);switch(this._metrics.totalMessages++,this._metrics.lastUpdate=new Date,n.type){case"tool_use":this._handleToolUse(n);break;case"tool_result":this._handleToolResult(n);break;case"text":this._handleText(n);break;case"usage":this._handleUsage(n);break;case"error":this._handleError(n);break;case"unknown":break}this._emitProgress()}catch(s){let r=s instanceof Error?s.message:String(s),n={message:`Failed to parse Claude Code output line ${this._lineNumber}: ${r}`,timestamp:new Date,details:{line:t,error:r,lineNumber:this._lineNumber}};this._metrics.errors.push(n),this._emitError(n)}}getMetrics(){return{...this._metrics,toolCalls:[...this._metrics.toolCalls],fileChanges:[...this._metrics.fileChanges],errors:[...this._metrics.errors]}}getToolCalls(){return Array.from(this._toolCalls.values())}getFileChanges(){return[...this._fileChanges]}onToolCall(e){this._toolCallHandlers.push(e)}onFileChange(e){this._fileChangeHandlers.push(e)}onProgress(e){this._progressHandlers.push(e)}onError(e){this._errorHandlers.push(e)}onMessage(e){this._messageHandlers.push(e)}onUsage(e){this._usageHandlers.push(e)}getToolCallsByName(e){return this.getToolCalls().filter(t=>t.name===e)}getFileChangesByPath(e){return this.getFileChanges().filter(t=>t.path===e)}getFileChangesByOperation(e){return this.getFileChanges().filter(t=>t.operation===e)}getFailedToolCalls(){return this.getToolCalls().filter(e=>e.status==="error")}getSuccessfulToolCalls(){return this.getToolCalls().filter(e=>e.status==="success")}getTotalCost(){return this._metrics.usage.cost||0}getExecutionSummary(){let e=this.getToolCalls(),t=this.getFileChanges(),s={};for(let l of e)s[l.name]=(s[l.name]||0)+1;let r={};for(let l of t)r[l.operation]=(r[l.operation]||0)+1;let n=e.filter(l=>l.status==="success"||l.status==="error"),i=e.filter(l=>l.status==="success"),a=n.length>0?i.length/n.length*100:0,u=(this._metrics.endedAt||new Date).getTime()-this._metrics.startedAt.getTime();return{totalMessages:this._metrics.totalMessages,toolCallsByType:s,fileOperationsByType:r,successRate:a,totalTokens:{input:this._metrics.usage.inputTokens,output:this._metrics.usage.outputTokens,cache:this._metrics.usage.cacheTokens},totalCost:this._metrics.usage.cost||0,duration:u,startTime:this._metrics.startedAt,endTime:this._metrics.endedAt}}_handleToolUse(e){if(e.type!=="tool_use")return;let t={id:e.id,name:e.name,input:e.input,status:"pending",timestamp:e.timestamp};this._toolCalls.set(t.id,t),this._metrics.toolCalls.push(t);for(let s of this._toolCallHandlers)try{s(t)}catch(r){console.error("Tool call handler error:",r)}}_handleToolResult(e){if(e.type!=="tool_result")return;let t=this._toolCalls.get(e.toolUseId);if(!t)return;if(t.status=e.isError?"error":"success",t.result=e.result,t.error=e.isError?String(e.result):void 0,t.completedAt=e.timestamp,["Read","Write","Edit","Glob"].includes(t.name)){let r=this._detectFileChange(t,e);if(r){this._fileChanges.push(r),this._metrics.fileChanges.push(r);for(let n of this._fileChangeHandlers)try{n(r)}catch(i){console.error("File change handler error:",i)}}}}_handleText(e){if(e.type==="text")for(let t of this._messageHandlers)try{t(e)}catch(s){console.error("Message handler error:",s)}}_handleUsage(e){if(e.type!=="usage")return;this._metrics.usage.inputTokens+=e.tokens.input,this._metrics.usage.outputTokens+=e.tokens.output,this._metrics.usage.cacheTokens+=e.tokens.cache,this._metrics.usage.totalTokens=this._metrics.usage.inputTokens+this._metrics.usage.outputTokens;let t=this._metrics.usage.inputTokens/1e6*3,s=this._metrics.usage.outputTokens/1e6*15,r=this._metrics.usage.cacheTokens/1e6*.3;this._metrics.usage.cost=t+s+r;for(let n of this._usageHandlers)try{n(this._metrics.usage)}catch(i){console.error("Usage handler error:",i)}}_handleError(e){if(e.type!=="error")return;let t={message:e.message,timestamp:e.timestamp,details:e.details};this._metrics.errors.find(s=>s.timestamp===e.timestamp)||this._metrics.errors.push(t),this._emitError(t)}_detectFileChange(e,t){let s=e.input.file_path||e.input.path;if(!s||typeof s!="string")return null;let r;switch(e.name){case"Read":case"Glob":r="read";break;case"Write":r="write";break;case"Edit":r="edit";break;default:return null}return{path:s,operation:r,timestamp:t.timestamp,toolCallId:e.id,metadata:{toolName:e.name,success:e.status==="success"}}}_detectMessageType(e){if(e.type==="error")return"error";if(e.type==="result"&&e.usage)return"usage";if(e.message?.content){let t=Array.isArray(e.message.content)?e.message.content[0]:e.message.content;if(t?.type==="tool_use")return"tool_use";if(t?.type==="tool_result")return"tool_result";if(typeof t=="string"||t?.type==="text")return"text"}return"unknown"}_parseMessage(e,t){let s=new Date;switch(t){case"text":return{type:"text",content:this._extractTextContent(e),timestamp:s,metadata:{raw:e,source:"claude-code"}};case"tool_use":{let r=this._extractToolUse(e);return{type:"tool_use",id:r.id,name:r.name,input:r.input,timestamp:s,metadata:{raw:e,source:"claude-code"}}}case"tool_result":{let r=this._extractToolResult(e);return{type:"tool_result",toolUseId:r.toolUseId,result:r.result,isError:r.isError,timestamp:s,metadata:{raw:e,source:"claude-code"}}}case"usage":return{type:"usage",tokens:this._extractUsage(e),timestamp:s,metadata:{raw:e,source:"claude-code"}};case"error":return{type:"error",message:e.error?.message||e.message||"Unknown error",details:e.error||e,timestamp:s,metadata:{raw:e,source:"claude-code"}};case"unknown":default:return{type:"unknown",raw:JSON.stringify(e),timestamp:s,metadata:{raw:e,source:"claude-code"}}}}_extractTextContent(e){return typeof e.message?.content=="string"?e.message.content:Array.isArray(e.message?.content)?e.message.content.filter(s=>s.type==="text"||typeof s=="string").map(s=>typeof s=="string"?s:s.text).join(""):""}_extractToolUse(e){let t=Array.isArray(e.message?.content)?e.message.content.find(s=>s.type==="tool_use"):e.message?.content;return{id:t?.id||"unknown",name:t?.name||"unknown",input:t?.input||{}}}_extractToolResult(e){let t=Array.isArray(e.message?.content)?e.message.content.find(s=>s.type==="tool_result"):e.message?.content;return{toolUseId:t?.tool_use_id||"unknown",result:t?.content||t?.result||null,isError:t?.is_error||!1}}_extractUsage(e){let t=e.usage||{};return{input:t.input_tokens||0,output:t.output_tokens||0,cache:t.cache_creation_input_tokens||t.cache_read_input_tokens||0}}_emitProgress(){let e=this.getMetrics();for(let t of this._progressHandlers)try{t(e)}catch(s){console.error("Progress handler error:",s)}}_emitError(e){for(let t of this._errorHandlers)try{t(e)}catch(s){console.error("Error handler error:",s)}}};import{EventType as E}from"@ag-ui/core";var ie=class{runId;threadId;listeners=new Set;processor=null;currentState={};activeToolCalls=new Map;messageCounter=0;constructor(e,t){this.runId=e,this.threadId=t||e}connectToProcessor(e){this.processor=e,e.onToolCall(this.handleToolCall.bind(this)),e.onFileChange(this.handleFileChange.bind(this)),e.onProgress(this.handleProgress.bind(this)),e.onError(this.handleError.bind(this)),e.onMessage(this.handleMessage.bind(this)),e.onUsage(this.handleUsage.bind(this))}onEvent(e){this.listeners.add(e)}offEvent(e){this.listeners.delete(e)}emitRunStarted(e){let t={type:E.RUN_STARTED,threadId:this.threadId,runId:this.runId,timestamp:Date.now(),...e&&{rawEvent:e}};this.emit(t),this.emitStateSnapshot()}emitRunFinished(e){let t={type:E.RUN_FINISHED,threadId:this.threadId,runId:this.runId,timestamp:Date.now(),...e&&{result:e}};this.emit(t)}emitStateSnapshot(){let e=this.processor?.getMetrics(),t={type:E.STATE_SNAPSHOT,timestamp:Date.now(),snapshot:{...this.currentState,...e&&{totalMessages:e.totalMessages,toolCallCount:e.toolCalls.length,fileChangeCount:e.fileChanges.length,errorCount:e.errors.length,usage:{inputTokens:e.usage.inputTokens,outputTokens:e.usage.outputTokens,totalTokens:e.usage.totalTokens}}}};this.emit(t)}handleToolCall=e=>{let t=e.id,s=Date.now();if(!this.activeToolCalls.has(t)){let r=`msg-${this.messageCounter++}`;this.activeToolCalls.set(t,{startTime:Date.now(),messageId:r});let n={type:E.TOOL_CALL_START,timestamp:s,toolCallId:t,toolCallName:e.name};this.emit(n);let i={type:E.TOOL_CALL_ARGS,timestamp:s,toolCallId:t,delta:JSON.stringify(e.input)};this.emit(i)}if(e.status==="success"||e.status==="error"){let r=this.activeToolCalls.get(t),n=r?Date.now()-r.startTime:void 0,i={type:E.TOOL_CALL_END,timestamp:s,toolCallId:t,...n!==void 0&&{rawEvent:{duration:n}}};if(this.emit(i),r){let a={type:E.TOOL_CALL_RESULT,timestamp:s,messageId:r.messageId,toolCallId:t,content:e.status==="success"?typeof e.result=="string"?e.result:JSON.stringify(e.result):e.error||"Tool call failed"};this.emit(a)}this.activeToolCalls.delete(t),this.emitStateDelta({toolCallCount:this.processor?.getToolCalls().length||0})}};handleFileChange=e=>{let t={type:E.CUSTOM,timestamp:Date.now(),name:"file_change",value:{path:e.path,operation:e.operation,toolCallId:e.toolCallId,changes:e.changes}};this.emit(t),this.emitStateDelta({fileChangeCount:this.processor?.getFileChanges().length||0})};handleProgress=e=>{this.emitStateDelta({totalMessages:e.totalMessages,toolCallCount:e.toolCalls.length,fileChangeCount:e.fileChanges.length,errorCount:e.errors.length,usage:{inputTokens:e.usage.inputTokens,outputTokens:e.usage.outputTokens,totalTokens:e.usage.totalTokens}})};handleError=e=>{let t={type:E.RUN_ERROR,timestamp:e.timestamp.getTime(),message:e.message,...e.details&&{rawEvent:{details:e.details}}};this.emit(t),this.emitStateDelta({errorCount:this.processor?.getMetrics().errors.length||0})};handleMessage=e=>{if(e.type!=="text")return;let t=`msg-${this.messageCounter++}`,s=Date.now(),r={type:E.TEXT_MESSAGE_START,timestamp:s,messageId:t,role:"assistant"};this.emit(r);let n={type:E.TEXT_MESSAGE_CONTENT,timestamp:s,messageId:t,delta:e.content};this.emit(n);let i={type:E.TEXT_MESSAGE_END,timestamp:s,messageId:t};this.emit(i)};handleUsage=e=>{let t=Date.now(),s={type:E.CUSTOM,timestamp:t,name:"USAGE_UPDATE",value:{inputTokens:e.inputTokens,outputTokens:e.outputTokens,cacheTokens:e.cacheTokens,totalTokens:e.totalTokens,cost:e.cost,provider:e.provider,model:e.model}};this.emit(s),this.emitStateDelta({usage:{inputTokens:e.inputTokens,outputTokens:e.outputTokens,totalTokens:e.totalTokens}})};emitStateDelta(e){this.currentState={...this.currentState,...e};let t=Object.entries(e).map(([r,n])=>({op:"replace",path:`/${r}`,value:n})),s={type:E.STATE_DELTA,timestamp:Date.now(),delta:t};this.emit(s)}emit(e){this.listeners.forEach(t=>{try{t(e)}catch(s){console.error("Error in AG-UI event listener:",s)}})}emitStepStarted(e,t){let s={type:E.STEP_STARTED,timestamp:Date.now(),stepName:t,rawEvent:{runId:this.runId,stepId:e}};this.emit(s)}emitStepFinished(e,t,s){let r={type:E.STEP_FINISHED,timestamp:Date.now(),stepName:e,rawEvent:{runId:this.runId,stepId:e,status:t,...s&&{output:s}}};this.emit(r)}emitRunError(e,t,s){let r={type:E.RUN_ERROR,timestamp:Date.now(),message:e,...s&&{code:s},...t&&{rawEvent:{stack:t}}};this.emit(r)}getState(){return{...this.currentState}}getRunId(){return this.runId}};function _e(o,e){let t=new oe,s=new ie(o,e);return s.connectToProcessor(t),{processor:t,adapter:s}}var q=class{db;templateEngine;lifecycleService;repoPath;transportManager;activeOrchestrators=new Map;constructor(e,t,s,r){this.db=e,this.repoPath=t,this.templateEngine=new U,this.lifecycleService=s||new N(e,t),this.transportManager=r}async prepareExecution(e,t){let s=this.db.prepare("SELECT * FROM issues WHERE id = ?").get(e);if(!s)throw new Error(`Issue ${e} not found`);let r=this.db.prepare(`
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`).all(e)}getExecution(e){return x(this.db,e)}};ae();import{Router as Cs}from"express";import{generateIssueId as Is}from"@sudocode-ai/cli/dist/id-generator.js";import{WebSocketServer as Es,WebSocket as Pe}from"ws";import{randomUUID as vs}from"crypto";var ce=!1,De=class{wss=null;clients=new Map;heartbeatInterval=null;HEARTBEAT_INTERVAL=3e4;init(e,t="/ws"){if(this.wss){console.warn("[websocket] WebSocket server already initialized");return}this.wss=new Es({server:e,path:t}),console.log(`[websocket] WebSocket server initialized on path: ${t}`),this.wss.on("connection",this.handleConnection.bind(this)),this.startHeartbeat()}handleConnection(e,t){let s=vs(),r={id:s,ws:e,subscriptions:new Set,isAlive:!0,connectedAt:new Date};this.clients.set(s,r),ce&&console.log(`[websocket] Client connected: ${s} (total: ${this.clients.size})`),e.on("message",n=>this.handleMessage(s,n)),e.on("close",()=>this.handleDisconnection(s)),e.on("error",n=>this.handleError(s,n)),e.on("pong",()=>this.handlePong(s)),this.sendToClient(s,{type:"pong",message:"Connected to sudocode server"})}handleDisconnection(e){let t=this.clients.get(e);t&&(ce&&console.log(`[websocket] Client disconnected: ${e} (subscriptions: ${t.subscriptions.size})`),this.clients.delete(e))}handleError(e,t){console.error(`[websocket] Client error (${e}):`,t.message)}handlePong(e){let t=this.clients.get(e);t&&(t.isAlive=!0)}handleMessage(e,t){if(this.clients.get(e))try{let r=JSON.parse(t.toString());switch(r.type){case"subscribe":this.handleSubscribe(e,r);break;case"unsubscribe":this.handleUnsubscribe(e,r);break;case"ping":this.sendToClient(e,{type:"pong"});break;default:this.sendToClient(e,{type:"error",message:`Unknown message type: ${r.type}`})}}catch(r){console.error(`[websocket] Failed to parse message from ${e}:`,r),this.sendToClient(e,{type:"error",message:"Invalid message format"})}}handleSubscribe(e,t){let s=this.clients.get(e);if(!s)return;let r;if(t.entity_type==="all")r="all";else if(t.entity_type&&t.entity_id)r=`${t.entity_type}:${t.entity_id}`;else if(t.entity_type)r=`${t.entity_type}:*`;else{this.sendToClient(e,{type:"error",message:"Invalid subscription request"});return}s.subscriptions.add(r),ce&&console.log(`[websocket] Client ${e} subscribed to: ${r}`),this.sendToClient(e,{type:"subscribed",subscription:r,message:`Subscribed to ${r}`})}handleUnsubscribe(e,t){let s=this.clients.get(e);if(!s)return;let r;if(t.entity_type==="all")r="all";else if(t.entity_type&&t.entity_id)r=`${t.entity_type}:${t.entity_id}`;else if(t.entity_type)r=`${t.entity_type}:*`;else{this.sendToClient(e,{type:"error",message:"Invalid unsubscription request"});return}s.subscriptions.delete(r),ce&&console.log(`[websocket] Client ${e} unsubscribed from: ${r}`),this.sendToClient(e,{type:"unsubscribed",subscription:r,message:`Unsubscribed from ${r}`})}sendToClient(e,t){let s=this.clients.get(e);if(!(!s||s.ws.readyState!==Pe.OPEN))try{s.ws.send(JSON.stringify(t))}catch(r){console.error(`[websocket] Failed to send message to ${e}:`,r)}}broadcast(e,t,s){let r=`${e}:${t}`,n=`${e}:*`,i=0;this.clients.forEach(a=>{if(a.ws.readyState===Pe.OPEN&&(a.subscriptions.has(r)||a.subscriptions.has(n)||a.subscriptions.has("all")))try{a.ws.send(JSON.stringify(s)),i++}catch(c){console.error(`[websocket] Failed to broadcast to ${a.id}:`,c)}}),i>0&&console.log(`[websocket] Broadcasted ${s.type} for ${r} to ${i} clients`)}broadcastGeneric(e){let t=0;this.clients.forEach(s=>{if(s.ws.readyState===Pe.OPEN&&s.subscriptions.has("all"))try{s.ws.send(JSON.stringify(e)),t++}catch(r){console.error(`[websocket] Failed to broadcast to ${s.id}:`,r)}}),t>0&&console.log(`[websocket] Broadcasted ${e.type} to ${t} clients`)}startHeartbeat(){this.heartbeatInterval||(this.heartbeatInterval=setInterval(()=>{this.clients.forEach((e,t)=>{if(!e.isAlive){console.log(`[websocket] Terminating dead connection: ${t}`),e.ws.terminate(),this.clients.delete(t);return}e.isAlive=!1;try{e.ws.ping()}catch(s){console.error(`[websocket] Failed to ping ${t}:`,s)}})},this.HEARTBEAT_INTERVAL),console.log(`[websocket] Heartbeat started (interval: ${this.HEARTBEAT_INTERVAL}ms)`))}stopHeartbeat(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null,console.log("[websocket] Heartbeat stopped"))}getStats(){return{totalClients:this.clients.size,clients:Array.from(this.clients.values()).map(e=>({id:e.id,subscriptions:Array.from(e.subscriptions),connectedAt:e.connectedAt.toISOString(),isAlive:e.isAlive}))}}async shutdown(){if(console.log("[websocket] Shutting down WebSocket server..."),this.stopHeartbeat(),this.clients.forEach(e=>{try{e.ws.close(1e3,"Server shutting down")}catch(t){console.error(`[websocket] Error closing client ${e.id}:`,t)}}),this.clients.clear(),this.wss)return new Promise(e=>{this.wss.close(()=>{console.log("[websocket] WebSocket server closed"),this.wss=null,e()})})}},W=new De;function lt(o,e){W.init(o,e)}function j(o,e,t){W.broadcast("issue",o,{type:`issue_${e}`,data:t})}function H(o,e,t){W.broadcast("spec",o,{type:`spec_${e}`,data:t})}function ue(o,e){W.broadcastGeneric({type:`feedback_${o}`,data:e})}function Ae(o,e){W.broadcastGeneric({type:`relationship_${o}`,data:e})}function dt(){return W.getStats()}async function Me(){await W.shutdown()}import*as pt from"path";function $(){return process.env.SUDOCODE_DIR||pt.join(process.cwd(),".sudocode")}import{exportToJSONL as _s}from"@sudocode-ai/cli/dist/export.js";import{syncJSONLToMarkdown as gt}from"@sudocode-ai/cli/dist/sync.js";import*as de from"path";var He=null;function Ts(o){return He||(He={db:o,timeoutId:null,pending:!1}),He}async function Rs(o){let e=$();await _s(o,{outputDir:e})}async function J(o,e,t){let s=$();if(t==="issue"){let{getIssueById:r}=await Promise.resolve().then(()=>(ae(),ut)),n=r(o,e);if(n){let i=de.join(s,"issues",`${n.id}.md`);await gt(o,n.id,"issue",i)}}else{let{getSpecById:r}=await Promise.resolve().then(()=>(le(),ft)),n=r(o,e);if(n){let i=n.file_path?de.join(s,n.file_path):de.join(s,"specs",`${n.id}.md`);await gt(o,n.id,"spec",i)}}}function S(o){let e=Ts(o);e.pending=!0,e.timeoutId&&clearTimeout(e.timeoutId),e.timeoutId=setTimeout(async()=>{try{await Rs(o)}catch(t){console.error("Export failed:",t)}finally{e.pending=!1,e.timeoutId=null}},2e3)}function mt(o){let e=Cs();return e.get("/",(t,s)=>{try{let r={};t.query.status&&(r.status=t.query.status),t.query.priority&&(r.priority=parseInt(t.query.priority,10)),t.query.assignee&&(r.assignee=t.query.assignee),r.archived=t.query.archived!==void 0?t.query.archived==="true":!1,t.query.limit&&(r.limit=parseInt(t.query.limit,10)),t.query.offset&&(r.offset=parseInt(t.query.offset,10));let n=Te(o,r);s.json({success:!0,data:n})}catch(r){console.error("Error listing issues:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to list issues"})}}),e.get("/:id",(t,s)=>{try{let{id:r}=t.params,n=G(o,r);if(!n){s.status(404).json({success:!1,data:null,message:`Issue not found: ${r}`});return}s.json({success:!0,data:n})}catch(r){console.error("Error getting issue:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get issue"})}}),e.post("/",(t,s)=>{try{let{title:r,content:n,status:i,priority:a,assignee:c,parent_id:u}=t.body;if(!r||typeof r!="string"){s.status(400).json({success:!1,data:null,message:"Title is required and must be a string"});return}if(r.length>500){s.status(400).json({success:!1,data:null,message:"Title must be 500 characters or less"});return}let l=$(),d=Is(o,l),p=Re(o,{id:d,title:r,content:n||"",status:i||"open",priority:a!==void 0?a:2,assignee:c||void 0,parent_id:u||void 0});S(o),J(o,p.id,"issue").catch(g=>{console.error(`Failed to sync issue ${p.id} to markdown:`,g)}),j(p.id,"created",p),s.status(201).json({success:!0,data:p})}catch(r){console.error("Error creating issue:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to create issue"})}}),e.put("/:id",(t,s)=>{try{let{id:r}=t.params,{title:n,content:i,status:a,priority:c,assignee:u,parent_id:l,archived:d}=t.body;if(n===void 0&&i===void 0&&a===void 0&&c===void 0&&u===void 0&&l===void 0&&d===void 0){s.status(400).json({success:!1,data:null,message:"At least one field must be provided for update"});return}if(n!==void 0&&typeof n=="string"&&n.length>500){s.status(400).json({success:!1,data:null,message:"Title must be 500 characters or less"});return}let p={};n!==void 0&&(p.title=n),i!==void 0&&(p.content=i),a!==void 0&&(p.status=a),c!==void 0&&(p.priority=c),u!==void 0&&(p.assignee=u),l!==void 0&&(p.parent_id=l),d!==void 0&&(p.archived=d,p.archived_at=d?new Date().toISOString():null);let g=Ce(o,r,p);S(o),J(o,g.id,"issue").catch(f=>{console.error(`Failed to sync issue ${g.id} to markdown:`,f)}),j(g.id,"updated",g),s.json({success:!0,data:g})}catch(r){if(console.error("Error updating issue:",r),r instanceof Error&&r.message.includes("not found")){s.status(404).json({success:!1,data:null,message:r.message});return}s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to update issue"})}}),e.delete("/:id",(t,s)=>{try{let{id:r}=t.params;if(!G(o,r)){s.status(404).json({success:!1,data:null,message:`Issue not found: ${r}`});return}Ie(o,r)?(S(o),j(r,"deleted",{id:r}),s.json({success:!0,data:{id:r,deleted:!0}})):s.status(500).json({success:!1,data:null,message:"Failed to delete issue"})}catch(r){console.error("Error deleting issue:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to delete issue"})}}),e}le();import{Router as Ps}from"express";import{generateSpecId as Ds}from"@sudocode-ai/cli/dist/id-generator.js";import*as ht from"path";function yt(o){let e=Ps();return e.get("/",(t,s)=>{try{let r={};t.query.priority&&(r.priority=parseInt(t.query.priority,10)),r.archived=t.query.archived!==void 0?t.query.archived==="true":!1,t.query.limit&&(r.limit=parseInt(t.query.limit,10)),t.query.offset&&(r.offset=parseInt(t.query.offset,10));let n=Fe(o,r);s.json({success:!0,data:n})}catch(r){console.error("Error listing specs:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to list specs"})}}),e.get("/:id",(t,s)=>{try{let{id:r}=t.params,n=z(o,r);if(!n){s.status(404).json({success:!1,data:null,message:`Spec not found: ${r}`});return}s.json({success:!0,data:n})}catch(r){console.error("Error getting spec:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get spec"})}}),e.post("/",(t,s)=>{try{let{title:r,content:n,priority:i,parent_id:a}=t.body;if(!r||typeof r!="string"){s.status(400).json({success:!1,data:null,message:"Title is required and must be a string"});return}if(r.length>500){s.status(400).json({success:!1,data:null,message:"Title must be 500 characters or less"});return}let c=$(),u=Ds(o,c),l=ht.join(c,"specs",`${u}.md`),d=Oe(o,{id:u,title:r,file_path:l,content:n||"",priority:i!==void 0?i:2,parent_id:a||void 0});S(o),J(o,d.id,"spec").catch(p=>{console.error(`Failed to sync spec ${d.id} to markdown:`,p)}),H(d.id,"created",d),s.status(201).json({success:!0,data:d})}catch(r){console.error("Error creating spec:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to create spec"})}}),e.put("/:id",(t,s)=>{try{let{id:r}=t.params,{title:n,content:i,priority:a,parent_id:c,archived:u}=t.body;if(n===void 0&&i===void 0&&a===void 0&&c===void 0&&u===void 0){s.status(400).json({success:!1,data:null,message:"At least one field must be provided for update"});return}if(n!==void 0&&typeof n=="string"&&n.length>500){s.status(400).json({success:!1,data:null,message:"Title must be 500 characters or less"});return}let l={};n!==void 0&&(l.title=n),i!==void 0&&(l.content=i),a!==void 0&&(l.priority=a),c!==void 0&&(l.parent_id=c),u!==void 0&&(l.archived=u,l.archived_at=u?new Date().toISOString():null);let d=We(o,r,l);S(o),J(o,d.id,"spec").catch(p=>{console.error(`Failed to sync spec ${d.id} to markdown:`,p)}),H(d.id,"updated",d),s.json({success:!0,data:d})}catch(r){if(console.error("Error updating spec:",r),r instanceof Error&&r.message.includes("not found")){s.status(404).json({success:!1,data:null,message:r.message});return}s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to update spec"})}}),e.delete("/:id",(t,s)=>{try{let{id:r}=t.params;if(!z(o,r)){s.status(404).json({success:!1,data:null,message:`Spec not found: ${r}`});return}je(o,r)?(S(o),H(r,"deleted",{id:r}),s.json({success:!0,data:{id:r,deleted:!0}})):s.status(500).json({success:!1,data:null,message:"Failed to delete spec"})}catch(r){console.error("Error deleting spec:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to delete spec"})}}),e}import{Router as js}from"express";import{addRelationship as As,getRelationship as io,removeRelationship as Ms,getOutgoingRelationships as Fs,getIncomingRelationships as Os,getAllRelationships as Ws}from"@sudocode-ai/cli/dist/operations/relationships.js";function Et(o,e){return As(o,e)}function vt(o,e,t,s,r,n){return Ms(o,e,t,s,r,n)}function bt(o,e,t,s){return Fs(o,e,t,s)}function kt(o,e,t,s){return Os(o,e,t,s)}function wt(o,e,t){return Ws(o,e,t)}function xt(o){let e=js();return e.get("/:entity_type/:entity_id",(t,s)=>{try{let{entity_type:r,entity_id:n}=t.params;if(r!=="spec"&&r!=="issue"){s.status(400).json({success:!1,data:null,message:"Invalid entity_type. Must be 'spec' or 'issue'"});return}let i=wt(o,n,r);s.json({success:!0,data:i})}catch(r){console.error("Error getting relationships:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get relationships"})}}),e.get("/:entity_type/:entity_id/outgoing",(t,s)=>{try{let{entity_type:r,entity_id:n}=t.params,{relationship_type:i}=t.query;if(r!=="spec"&&r!=="issue"){s.status(400).json({success:!1,data:null,message:"Invalid entity_type. Must be 'spec' or 'issue'"});return}let a=bt(o,n,r,i);s.json({success:!0,data:a})}catch(r){console.error("Error getting outgoing relationships:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get outgoing relationships"})}}),e.get("/:entity_type/:entity_id/incoming",(t,s)=>{try{let{entity_type:r,entity_id:n}=t.params,{relationship_type:i}=t.query;if(r!=="spec"&&r!=="issue"){s.status(400).json({success:!1,data:null,message:"Invalid entity_type. Must be 'spec' or 'issue'"});return}let a=kt(o,n,r,i);s.json({success:!0,data:a})}catch(r){console.error("Error getting incoming relationships:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get incoming relationships"})}}),e.post("/",(t,s)=>{try{let{from_id:r,from_type:n,to_id:i,to_type:a,relationship_type:c,metadata:u}=t.body;if(!r||typeof r!="string"){s.status(400).json({success:!1,data:null,message:"from_id is required and must be a string"});return}if(!n||n!=="spec"&&n!=="issue"){s.status(400).json({success:!1,data:null,message:"from_type is required and must be 'spec' or 'issue'"});return}if(!i||typeof i!="string"){s.status(400).json({success:!1,data:null,message:"to_id is required and must be a string"});return}if(!a||a!=="spec"&&a!=="issue"){s.status(400).json({success:!1,data:null,message:"to_type is required and must be 'spec' or 'issue'"});return}if(!c||typeof c!="string"){s.status(400).json({success:!1,data:null,message:"relationship_type is required and must be a string"});return}let l=["blocks","related","discovered-from","implements","references","depends-on"];if(!l.includes(c)){s.status(400).json({success:!1,data:null,message:`Invalid relationship_type. Must be one of: ${l.join(", ")}`});return}let d=Et(o,{from_id:r,from_type:n,to_id:i,to_type:a,relationship_type:c,metadata:u||null});Ae("created",d),S(o),s.status(201).json({success:!0,data:d})}catch(r){if(console.error("Error creating relationship:",r),r instanceof Error){if(r.message.includes("not found")){s.status(404).json({success:!1,data:null,message:r.message});return}if(r.message.includes("already exists")){s.status(409).json({success:!1,data:null,message:r.message});return}}s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to create relationship"})}}),e.delete("/",(t,s)=>{try{let{from_id:r,from_type:n,to_id:i,to_type:a,relationship_type:c}=t.body;if(!r||typeof r!="string"){s.status(400).json({success:!1,data:null,message:"from_id is required and must be a string"});return}if(!n||n!=="spec"&&n!=="issue"){s.status(400).json({success:!1,data:null,message:"from_type is required and must be 'spec' or 'issue'"});return}if(!i||typeof i!="string"){s.status(400).json({success:!1,data:null,message:"to_id is required and must be a string"});return}if(!a||a!=="spec"&&a!=="issue"){s.status(400).json({success:!1,data:null,message:"to_type is required and must be 'spec' or 'issue'"});return}if(!c||typeof c!="string"){s.status(400).json({success:!1,data:null,message:"relationship_type is required and must be a string"});return}vt(o,r,n,i,a,c)?(Ae("deleted",{from_id:r,from_type:n,to_id:i,to_type:a,relationship_type:c}),S(o),s.json({success:!0,data:{from_id:r,from_type:n,to_id:i,to_type:a,relationship_type:c,deleted:!0}})):s.status(404).json({success:!1,data:null,message:"Relationship not found"})}catch(r){console.error("Error deleting relationship:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to delete relationship"})}}),e}import{Router as Ls}from"express";import{createFeedback as Hs,getFeedback as $s,updateFeedback as Us,deleteFeedback as Bs,listFeedback as Ns,getFeedbackForSpec as yo,getFeedbackForIssue as Eo,dismissFeedback as vo}from"@sudocode-ai/cli/dist/operations/feedback.js";function St(o,e){return Hs(o,e)}function $e(o,e){return $s(o,e)}function _t(o,e,t){return Us(o,e,t)}function Tt(o,e){return Bs(o,e)}function Rt(o,e){return Ns(o,e||{})}function Ct(o){let e=Ls();return e.get("/",(t,s)=>{try{let r={};t.query.spec_id&&(r.spec_id=t.query.spec_id),t.query.issue_id&&(r.issue_id=t.query.issue_id),t.query.feedback_type&&(r.feedback_type=t.query.feedback_type),t.query.dismissed!==void 0&&(r.dismissed=t.query.dismissed==="true"),t.query.limit&&(r.limit=parseInt(t.query.limit,10)),t.query.offset&&(r.offset=parseInt(t.query.offset,10));let n=Rt(o,r);s.json({success:!0,data:n})}catch(r){console.error("Error listing feedback:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to list feedback"})}}),e.get("/:id",(t,s)=>{try{let{id:r}=t.params,n=$e(o,r);if(!n){s.status(404).json({success:!1,data:null,message:`Feedback not found: ${r}`});return}s.json({success:!0,data:n})}catch(r){console.error("Error getting feedback:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to get feedback"})}}),e.post("/",(t,s)=>{try{let{issue_id:r,spec_id:n,feedback_type:i,content:a,agent:c,anchor:u,dismissed:l}=t.body;if(!r||typeof r!="string"){s.status(400).json({success:!1,data:null,message:"issue_id is required and must be a string"});return}if(!n||typeof n!="string"){s.status(400).json({success:!1,data:null,message:"spec_id is required and must be a string"});return}if(!i||typeof i!="string"){s.status(400).json({success:!1,data:null,message:"feedback_type is required and must be a string"});return}let d=["comment","suggestion","request"];if(!d.includes(i)){s.status(400).json({success:!1,data:null,message:`Invalid feedback_type. Must be one of: ${d.join(", ")}`});return}if(!a||typeof a!="string"){s.status(400).json({success:!1,data:null,message:"content is required and must be a string"});return}if(u!=null){if(typeof u!="object"){s.status(400).json({success:!1,data:null,message:"anchor must be an object if provided"});return}if(u.anchor_status){let g=["valid","relocated","stale"];if(!g.includes(u.anchor_status)){s.status(400).json({success:!1,data:null,message:`Invalid anchor.anchor_status. Must be one of: ${g.join(", ")}`});return}}}let p=St(o,{issue_id:r,spec_id:n,feedback_type:i,content:a,agent:c||void 0,anchor:u,dismissed:l||!1});ue("created",p),s.status(201).json({success:!0,data:p})}catch(r){if(console.error("Error creating feedback:",r),r instanceof Error){if(r.message.includes("not found")){s.status(404).json({success:!1,data:null,message:r.message});return}if(r.message.includes("Constraint violation")){s.status(409).json({success:!1,data:null,message:r.message});return}}s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to create feedback"})}}),e.put("/:id",(t,s)=>{try{let{id:r}=t.params,{content:n,dismissed:i,anchor:a}=t.body;if(n===void 0&&i===void 0&&a===void 0){s.status(400).json({success:!1,data:null,message:"At least one field must be provided for update"});return}if(a!==void 0){if(typeof a!="object"){s.status(400).json({success:!1,data:null,message:"anchor must be an object"});return}if(!a.anchor_status||typeof a.anchor_status!="string"){s.status(400).json({success:!1,data:null,message:"anchor.anchor_status is required and must be a string"});return}let l=["valid","relocated","stale"];if(!l.includes(a.anchor_status)){s.status(400).json({success:!1,data:null,message:`Invalid anchor.anchor_status. Must be one of: ${l.join(", ")}`});return}}let c={};n!==void 0&&(c.content=n),i!==void 0&&(c.dismissed=i),a!==void 0&&(c.anchor=a);let u=_t(o,r,c);ue("updated",u),s.json({success:!0,data:u})}catch(r){if(console.error("Error updating feedback:",r),r instanceof Error&&r.message.includes("not found")){s.status(404).json({success:!1,data:null,message:r.message});return}s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to update feedback"})}}),e.delete("/:id",(t,s)=>{try{let{id:r}=t.params;if(!$e(o,r)){s.status(404).json({success:!1,data:null,message:`Feedback not found: ${r}`});return}Tt(o,r)?(ue("deleted",{id:r}),s.json({success:!0,data:{id:r,deleted:!0}})):s.status(500).json({success:!1,data:null,message:"Failed to delete feedback"})}catch(r){console.error("Error deleting feedback:",r),s.status(500).json({success:!1,data:null,error_data:r instanceof Error?r.message:String(r),message:"Failed to delete feedback"})}}),e}import{Router as qs}from"express";function It(o,e,t,s){let r=qs(),n=s||new q(o,e,void 0,t);return r.post("/issues/:issueId/executions/prepare",async(i,a)=>{try{let{issueId:c}=i.params,u=i.body||{},l=await n.prepareExecution(c,u);a.json({success:!0,data:l})}catch(c){console.error("[API Route] ERROR: Failed to prepare execution:",c),a.status(500).json({success:!1,data:null,error_data:c instanceof Error?c.message:String(c),message:"Failed to prepare execution"})}}),r.post("/issues/:issueId/executions",async(i,a)=>{try{let{issueId:c}=i.params,{config:u,prompt:l}=i.body;if(!l){a.status(400).json({success:!1,data:null,message:"Prompt is required"});return}let d=await n.createExecution(c,u||{},l);a.status(201).json({success:!0,data:d})}catch(c){console.error("[API Route] ERROR: Failed to create execution:",c);let u=c instanceof Error?c.message:String(c),l=u.includes("not found")?404:500;a.status(l).json({success:!1,data:null,error_data:u,message:"Failed to create execution"})}}),r.get("/executions/:executionId",(i,a)=>{try{let{executionId:c}=i.params,u=n.getExecution(c);if(!u){a.status(404).json({success:!1,data:null,message:`Execution not found: ${c}`});return}a.json({success:!0,data:u})}catch(c){console.error("Error getting execution:",c),a.status(500).json({success:!1,data:null,error_data:c instanceof Error?c.message:String(c),message:"Failed to get execution"})}}),r.get("/issues/:issueId/executions",(i,a)=>{try{let{issueId:c}=i.params,u=n.listExecutions(c);a.json({success:!0,data:u})}catch(c){console.error("Error listing executions:",c),a.status(500).json({success:!1,data:null,error_data:c instanceof Error?c.message:String(c),message:"Failed to list executions"})}}),r.post("/executions/:executionId/follow-up",async(i,a)=>{try{let{executionId:c}=i.params,{feedback:u}=i.body;if(!u){a.status(400).json({success:!1,data:null,message:"Feedback is required"});return}let l=await n.createFollowUp(c,u);a.status(201).json({success:!0,data:l})}catch(c){console.error("Error creating follow-up execution:",c);let u=c instanceof Error?c.message:String(c),l=u.includes("not found")||u.includes("no worktree")?404:500;a.status(l).json({success:!1,data:null,error_data:u,message:"Failed to create follow-up execution"})}}),r.delete("/executions/:executionId",async(i,a)=>{try{let{executionId:c}=i.params;await n.cancelExecution(c),a.json({success:!0,data:{executionId:c},message:"Execution cancelled successfully"})}catch(c){console.error("Error cancelling execution:",c);let u=c instanceof Error?c.message:String(c),l=u.includes("not found")?404:500;a.status(l).json({success:!1,data:null,error_data:u,message:"Failed to cancel execution"})}}),r.get("/executions/:executionId/worktree",async(i,a)=>{try{let{executionId:c}=i.params,u=await n.worktreeExists(c);a.json({success:!0,data:{exists:u}})}catch(c){console.error("Error checking worktree:",c),a.status(500).json({success:!1,data:null,error_data:c instanceof Error?c.message:String(c),message:"Failed to check worktree status"})}}),r.delete("/executions/:executionId/worktree",async(i,a)=>{try{let{executionId:c}=i.params;await n.deleteWorktree(c),a.json({success:!0,data:{executionId:c},message:"Worktree deleted successfully"})}catch(c){console.error("Error deleting worktree:",c);let u=c instanceof Error?c.message:String(c),l=500;u.includes("not found")?l=404:(u.includes("has no worktree")||u.includes("Cannot delete worktree"))&&(l=400),a.status(l).json({success:!1,data:null,error_data:u,message:"Failed to delete worktree"})}}),r}import{Router as Gs}from"express";import{randomUUID as zs}from"crypto";function Pt(o){let e=Gs();return e.get("/:executionId/stream",(t,s)=>{let{executionId:r}=t.params,n=zs(),a=o.getBufferedEvents(r).map(c=>({event:c.event.type,data:c.event}));o.getSseTransport().handleConnection(n,s,r,a)}),e}var pe=class{clients=new Map;heartbeatInterval=null;HEARTBEAT_INTERVAL_MS=3e4;constructor(){this.startHeartbeat()}handleConnection(e,t,s,r){t.setHeader("Content-Type","text/event-stream"),t.setHeader("Cache-Control","no-cache"),t.setHeader("Connection","keep-alive"),t.setHeader("X-Accel-Buffering","no"),t.setHeader("Access-Control-Allow-Origin","*"),t.flushHeaders();let n={clientId:e,response:t,runId:s,connectedAt:new Date,lastActivity:new Date};if(this.clients.set(e,n),this.sendToClient(e,{event:"connected",data:{clientId:e,runId:s,timestamp:Date.now()}}),r&&r.length>0)for(let i of r)this.sendToClient(e,i);t.on("close",()=>{this.removeClient(e)})}sendToClient(e,t){let s=this.clients.get(e);return s?this.writeEvent(s,t):!1}broadcast(e){let t=0;for(let s of this.clients.values())this.writeEvent(s,e)&&t++;return t}broadcastToRun(e,t){let s=0,r=0;for(let n of this.clients.values())n.runId===e&&(r++,this.writeEvent(n,t)&&s++);return s}removeClient(e){let t=this.clients.get(e);if(!t)return!1;try{t.response.writableEnded||t.response.end()}catch{}return this.clients.delete(e),!0}getClientCount(){return this.clients.size}getRunClientCount(e){let t=0;for(let s of this.clients.values())s.runId===e&&t++;return t}getClientIds(){return Array.from(this.clients.keys())}shutdown(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null);for(let e of this.clients.keys())this.removeClient(e)}writeEvent(e,t){try{let{response:s}=e;if(s.writableEnded||!s.writable)return this.removeClient(e.clientId),!1;let r="";t.event&&(r+=`event: ${t.event}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import dotenv from "dotenv";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as http from "http";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { readFileSync, existsSync } from "fs";
|
|
8
|
+
// ES Module __dirname equivalent
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
import { initDatabase, getDatabaseInfo } from "./services/db.js";
|
|
12
|
+
import { ExecutionLifecycleService } from "./services/execution-lifecycle.js";
|
|
13
|
+
import { ExecutionService } from "./services/execution-service.js";
|
|
14
|
+
import { WorktreeManager } from "./execution/worktree/manager.js";
|
|
15
|
+
import { getWorktreeConfig } from "./execution/worktree/config.js";
|
|
16
|
+
import { createIssuesRouter } from "./routes/issues.js";
|
|
17
|
+
import { createSpecsRouter } from "./routes/specs.js";
|
|
18
|
+
import { createRelationshipsRouter } from "./routes/relationships.js";
|
|
19
|
+
import { createFeedbackRouter } from "./routes/feedback.js";
|
|
20
|
+
import { createExecutionsRouter } from "./routes/executions.js";
|
|
21
|
+
import { createExecutionStreamRoutes } from "./routes/executions-stream.js";
|
|
22
|
+
import { TransportManager } from "./execution/transport/transport-manager.js";
|
|
23
|
+
import { getIssueById } from "./services/issues.js";
|
|
24
|
+
import { getSpecById } from "./services/specs.js";
|
|
25
|
+
import { startServerWatcher, } from "./services/watcher.js";
|
|
26
|
+
import { initWebSocketServer, getWebSocketStats, shutdownWebSocketServer, broadcastIssueUpdate, broadcastSpecUpdate, } from "./services/websocket.js";
|
|
27
|
+
// Load environment variables
|
|
28
|
+
dotenv.config();
|
|
29
|
+
const app = express();
|
|
30
|
+
const DEFAULT_PORT = 3000;
|
|
31
|
+
const MAX_PORT_ATTEMPTS = 20;
|
|
32
|
+
// Falls back to current directory for development/testing
|
|
33
|
+
const SUDOCODE_DIR = process.env.SUDOCODE_DIR || path.join(process.cwd(), ".sudocode");
|
|
34
|
+
const DB_PATH = path.join(SUDOCODE_DIR, "cache.db");
|
|
35
|
+
// TODO: Include sudocode install package for serving static files.
|
|
36
|
+
// Derive repo root from SUDOCODE_DIR (which is <repo>/.sudocode)
|
|
37
|
+
// This ensures consistency across database and execution paths
|
|
38
|
+
const REPO_ROOT = path.dirname(SUDOCODE_DIR);
|
|
39
|
+
// Initialize database and transport manager
|
|
40
|
+
let db;
|
|
41
|
+
let watcher = null;
|
|
42
|
+
let transportManager;
|
|
43
|
+
let executionService = null;
|
|
44
|
+
// Async initialization function
|
|
45
|
+
async function initialize() {
|
|
46
|
+
try {
|
|
47
|
+
console.log(`Initializing database at: ${DB_PATH}`);
|
|
48
|
+
db = initDatabase({ path: DB_PATH });
|
|
49
|
+
const info = getDatabaseInfo(db);
|
|
50
|
+
console.log(`Database initialized with ${info.tables.length} tables`);
|
|
51
|
+
if (!info.hasCliTables) {
|
|
52
|
+
// TODO: Automatically import and sync.
|
|
53
|
+
console.warn("Warning: CLI tables not found. Run 'sudocode sync' to initialize the database.");
|
|
54
|
+
}
|
|
55
|
+
// Initialize transport manager for SSE streaming
|
|
56
|
+
transportManager = new TransportManager();
|
|
57
|
+
console.log("Transport manager initialized");
|
|
58
|
+
// Initialize execution service globally for cleanup on shutdown
|
|
59
|
+
executionService = new ExecutionService(db, REPO_ROOT, undefined, transportManager);
|
|
60
|
+
console.log("Execution service initialized");
|
|
61
|
+
// Cleanup orphaned worktrees on startup (if configured)
|
|
62
|
+
const worktreeConfig = getWorktreeConfig(REPO_ROOT);
|
|
63
|
+
if (worktreeConfig.cleanupOrphanedWorktreesOnStartup) {
|
|
64
|
+
try {
|
|
65
|
+
// TODO: Log if there are worktrees to cleanup
|
|
66
|
+
const worktreeManager = new WorktreeManager(worktreeConfig);
|
|
67
|
+
const lifecycleService = new ExecutionLifecycleService(db, REPO_ROOT, worktreeManager);
|
|
68
|
+
console.log("Cleaning up orphaned worktrees...");
|
|
69
|
+
await lifecycleService.cleanupOrphanedWorktrees();
|
|
70
|
+
console.log("Orphaned worktree cleanup complete");
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error("Failed to cleanup orphaned worktrees:", error);
|
|
74
|
+
// Don't exit - this is best-effort cleanup
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("Failed to initialize database:", error);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Run initialization
|
|
84
|
+
await initialize();
|
|
85
|
+
// Start file watcher (enabled by default, disable with WATCH=false)
|
|
86
|
+
const WATCH_ENABLED = process.env.WATCH !== "false";
|
|
87
|
+
const SYNC_JSONL_TO_MARKDOWN = process.env.SYNC_JSONL_TO_MARKDOWN === "true";
|
|
88
|
+
if (WATCH_ENABLED) {
|
|
89
|
+
try {
|
|
90
|
+
watcher = startServerWatcher({
|
|
91
|
+
db,
|
|
92
|
+
baseDir: SUDOCODE_DIR,
|
|
93
|
+
debounceDelay: parseInt(process.env.WATCH_DEBOUNCE || "2000", 10),
|
|
94
|
+
syncJSONLToMarkdown: SYNC_JSONL_TO_MARKDOWN,
|
|
95
|
+
onFileChange: (info) => {
|
|
96
|
+
console.log(`[server] File change detected: ${info.entityType || "unknown"} ${info.entityId || ""}`);
|
|
97
|
+
// Broadcast WebSocket updates for issue and spec changes
|
|
98
|
+
if (info.entityType === "issue" && info.entityId) {
|
|
99
|
+
if (info.entityId === "*") {
|
|
100
|
+
// Wildcard update (JSONL file changed) - broadcast to all issue subscribers
|
|
101
|
+
broadcastIssueUpdate("*", "updated", null);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Specific issue update - fetch and broadcast the specific issue
|
|
105
|
+
const issue = getIssueById(db, info.entityId);
|
|
106
|
+
if (issue) {
|
|
107
|
+
broadcastIssueUpdate(info.entityId, "updated", issue);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (info.entityType === "spec" && info.entityId) {
|
|
112
|
+
if (info.entityId === "*") {
|
|
113
|
+
// Wildcard update (JSONL file changed) - broadcast to all spec subscribers
|
|
114
|
+
broadcastSpecUpdate("*", "updated", null);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Specific spec update - fetch and broadcast the specific spec
|
|
118
|
+
const spec = getSpecById(db, info.entityId);
|
|
119
|
+
if (spec) {
|
|
120
|
+
broadcastSpecUpdate(info.entityId, "updated", spec);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
console.log(`[server] File watcher started on: ${SUDOCODE_DIR}`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error("Failed to start file watcher:", error);
|
|
130
|
+
console.warn("Continuing without file watcher. Set WATCH=false to suppress this warning.");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Middleware
|
|
134
|
+
app.use(cors());
|
|
135
|
+
app.use(express.json());
|
|
136
|
+
// API Routes
|
|
137
|
+
app.use("/api/issues", createIssuesRouter(db));
|
|
138
|
+
app.use("/api/specs", createSpecsRouter(db));
|
|
139
|
+
app.use("/api/relationships", createRelationshipsRouter(db));
|
|
140
|
+
app.use("/api/feedback", createFeedbackRouter(db));
|
|
141
|
+
// Mount execution routes (must be before stream routes to avoid conflicts)
|
|
142
|
+
app.use("/api", createExecutionsRouter(db, REPO_ROOT, transportManager, executionService));
|
|
143
|
+
app.use("/api/executions", createExecutionStreamRoutes(transportManager));
|
|
144
|
+
// Health check endpoint
|
|
145
|
+
app.get("/health", (_req, res) => {
|
|
146
|
+
const dbInfo = getDatabaseInfo(db);
|
|
147
|
+
res.status(200).json({
|
|
148
|
+
status: "ok",
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
uptime: process.uptime(),
|
|
151
|
+
database: {
|
|
152
|
+
path: DB_PATH,
|
|
153
|
+
tables: dbInfo.tables.length,
|
|
154
|
+
hasCliTables: dbInfo.hasCliTables,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// Version endpoint - returns versions of all packages
|
|
159
|
+
app.get("/api/version", (_req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
// Read package.json files - going up from server/dist to project root
|
|
162
|
+
const projectRoot = path.join(__dirname, "../..");
|
|
163
|
+
const cliPackagePath = path.join(projectRoot, "cli/package.json");
|
|
164
|
+
const serverPackagePath = path.join(projectRoot, "server/package.json");
|
|
165
|
+
const frontendPackagePath = path.join(projectRoot, "frontend/package.json");
|
|
166
|
+
const cliPackage = JSON.parse(readFileSync(cliPackagePath, "utf-8"));
|
|
167
|
+
const serverPackage = JSON.parse(readFileSync(serverPackagePath, "utf-8"));
|
|
168
|
+
const frontendPackage = JSON.parse(readFileSync(frontendPackagePath, "utf-8"));
|
|
169
|
+
res.status(200).json({
|
|
170
|
+
cli: cliPackage.version,
|
|
171
|
+
server: serverPackage.version,
|
|
172
|
+
frontend: frontendPackage.version,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error("Failed to read version information:", error);
|
|
177
|
+
res.status(500).json({ error: "Failed to read version information" });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// Config endpoint - returns sudocode configuration
|
|
181
|
+
app.get("/api/config", (_req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const configPath = path.join(SUDOCODE_DIR, "config.json");
|
|
184
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
185
|
+
res.status(200).json(config);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error("Failed to read config:", error);
|
|
189
|
+
res.status(500).json({ error: "Failed to read config" });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// WebSocket stats endpoint
|
|
193
|
+
app.get("/ws/stats", (_req, res) => {
|
|
194
|
+
const stats = getWebSocketStats();
|
|
195
|
+
res.status(200).json(stats);
|
|
196
|
+
});
|
|
197
|
+
// Serve static frontend
|
|
198
|
+
// In development: ../../../frontend/dist (workspace)
|
|
199
|
+
// In production: ./public (bundled with server package in dist/public)
|
|
200
|
+
const isDev = process.env.NODE_ENV !== "production" &&
|
|
201
|
+
existsSync(path.join(__dirname, "../../../frontend/dist"));
|
|
202
|
+
const frontendPath = isDev
|
|
203
|
+
? path.join(__dirname, "../../../frontend/dist")
|
|
204
|
+
: path.join(__dirname, "public");
|
|
205
|
+
console.log(`[server] Serving static frontend from: ${frontendPath}`);
|
|
206
|
+
// Serve static files
|
|
207
|
+
app.use(express.static(frontendPath));
|
|
208
|
+
// SPA fallback - serve index.html for all non-API/non-WS routes
|
|
209
|
+
app.get("*", (req, res) => {
|
|
210
|
+
// Skip API and WebSocket routes
|
|
211
|
+
if (req.path.startsWith("/api") ||
|
|
212
|
+
req.path.startsWith("/ws") ||
|
|
213
|
+
req.path.startsWith("/health")) {
|
|
214
|
+
res.status(404).json({ error: "Not found" });
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
res.sendFile(path.join(frontendPath, "index.html"));
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Create HTTP server
|
|
221
|
+
const server = http.createServer(app);
|
|
222
|
+
/**
|
|
223
|
+
* Attempts to start the server on the given port, incrementing if unavailable.
|
|
224
|
+
* Only scans for ports if no explicit PORT was provided.
|
|
225
|
+
*/
|
|
226
|
+
async function startServer(initialPort, maxAttempts) {
|
|
227
|
+
const explicitPort = process.env.PORT;
|
|
228
|
+
const shouldScan = !explicitPort;
|
|
229
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
230
|
+
const port = initialPort + attempt;
|
|
231
|
+
try {
|
|
232
|
+
await new Promise((resolve, reject) => {
|
|
233
|
+
const errorHandler = (err) => {
|
|
234
|
+
server.removeListener("error", errorHandler);
|
|
235
|
+
server.removeListener("listening", listeningHandler);
|
|
236
|
+
reject(err);
|
|
237
|
+
};
|
|
238
|
+
const listeningHandler = () => {
|
|
239
|
+
server.removeListener("error", errorHandler);
|
|
240
|
+
resolve();
|
|
241
|
+
};
|
|
242
|
+
server.once("error", errorHandler);
|
|
243
|
+
server.once("listening", listeningHandler);
|
|
244
|
+
server.listen(port);
|
|
245
|
+
});
|
|
246
|
+
// Success! Return the port we successfully bound to
|
|
247
|
+
return port;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const error = err;
|
|
251
|
+
if (error.code === "EADDRINUSE") {
|
|
252
|
+
if (!shouldScan) {
|
|
253
|
+
// Explicit port was specified and it's in use - fail immediately
|
|
254
|
+
throw new Error(`Port ${port} is already in use. Please specify a different PORT.`);
|
|
255
|
+
}
|
|
256
|
+
// Port is in use, try next one if we have attempts left
|
|
257
|
+
if (attempt < maxAttempts - 1) {
|
|
258
|
+
console.log(`Port ${port} is already in use, trying ${port + 1}...`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
throw new Error(`Could not find an available port after ${maxAttempts} attempts (${initialPort}-${port})`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// Some other error - fail immediately
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`Could not start server after ${maxAttempts} attempts`);
|
|
272
|
+
}
|
|
273
|
+
// Start listening with port scanning
|
|
274
|
+
const startPort = process.env.PORT
|
|
275
|
+
? parseInt(process.env.PORT, 10)
|
|
276
|
+
: DEFAULT_PORT;
|
|
277
|
+
const actualPort = await startServer(startPort, MAX_PORT_ATTEMPTS);
|
|
278
|
+
// Initialize WebSocket server AFTER successfully binding to a port
|
|
279
|
+
initWebSocketServer(server, "/ws");
|
|
280
|
+
// Format URLs as clickable links with color
|
|
281
|
+
const httpUrl = `http://localhost:${actualPort}`;
|
|
282
|
+
const wsUrl = `ws://localhost:${actualPort}/ws`;
|
|
283
|
+
// ANSI escape codes for green color and clickable links
|
|
284
|
+
const green = "\u001b[32m";
|
|
285
|
+
const bold = "\u001b[1m";
|
|
286
|
+
const reset = "\u001b[0m";
|
|
287
|
+
const makeClickable = (url, text) => `\u001b]8;;${url}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
288
|
+
console.log(`WebSocket server available at: ${makeClickable(wsUrl, wsUrl)}`);
|
|
289
|
+
console.log(`${bold}${green}sudocode local server running on: ${makeClickable(httpUrl, httpUrl)}${reset}`);
|
|
290
|
+
// Error handlers for debugging
|
|
291
|
+
process.on("uncaughtException", (error) => {
|
|
292
|
+
console.error("Uncaught exception:", error);
|
|
293
|
+
console.error("Stack trace:", error.stack);
|
|
294
|
+
});
|
|
295
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
296
|
+
console.error("Unhandled rejection at:", promise);
|
|
297
|
+
console.error("Reason:", reason);
|
|
298
|
+
});
|
|
299
|
+
// Graceful shutdown
|
|
300
|
+
process.on("SIGINT", async () => {
|
|
301
|
+
console.log("\nShutting down server...");
|
|
302
|
+
// Shutdown execution service (cancel active executions)
|
|
303
|
+
if (executionService) {
|
|
304
|
+
await executionService.shutdown();
|
|
305
|
+
}
|
|
306
|
+
// Stop file watcher
|
|
307
|
+
if (watcher) {
|
|
308
|
+
await watcher.stop();
|
|
309
|
+
}
|
|
310
|
+
// Shutdown WebSocket server
|
|
311
|
+
await shutdownWebSocketServer();
|
|
312
|
+
// Shutdown transport manager
|
|
313
|
+
if (transportManager) {
|
|
314
|
+
transportManager.shutdown();
|
|
315
|
+
console.log("Transport manager shutdown complete");
|
|
316
|
+
}
|
|
317
|
+
// Close database
|
|
318
|
+
db.close();
|
|
319
|
+
// Close HTTP server
|
|
320
|
+
server.close(() => {
|
|
321
|
+
console.log("Server closed");
|
|
322
|
+
process.exit(0);
|
|
323
|
+
});
|
|
324
|
+
// Force exit after 10 seconds if graceful shutdown hangs
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
console.error("Shutdown timeout - forcing exit");
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}, 10000);
|
|
329
|
+
});
|
|
330
|
+
process.on("SIGTERM", async () => {
|
|
331
|
+
console.log("\nShutting down server...");
|
|
332
|
+
// Shutdown execution service (cancel active executions)
|
|
333
|
+
if (executionService) {
|
|
334
|
+
await executionService.shutdown();
|
|
335
|
+
}
|
|
336
|
+
// Stop file watcher
|
|
337
|
+
if (watcher) {
|
|
338
|
+
await watcher.stop();
|
|
339
|
+
}
|
|
340
|
+
// Shutdown WebSocket server
|
|
341
|
+
await shutdownWebSocketServer();
|
|
342
|
+
// Shutdown transport manager
|
|
343
|
+
if (transportManager) {
|
|
344
|
+
transportManager.shutdown();
|
|
345
|
+
console.log("Transport manager shutdown complete");
|
|
346
|
+
}
|
|
347
|
+
// Close database
|
|
348
|
+
db.close();
|
|
349
|
+
// Close HTTP server
|
|
350
|
+
server.close(() => {
|
|
351
|
+
console.log("Server closed");
|
|
352
|
+
process.exit(0);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
export default app;
|
|
356
|
+
export { db, transportManager };
|
|
357
|
+
//# sourceMappingURL=index.js.map
|